#!/usr/bin/env python3 """Erzeugt das Messprotokoll-Geruest eines Laufs aus seinen Rohdaten. Fuellt ausschliesslich Felder, die aus `RawResult.json`, `_meta` und dem Dateibestand **belegbar** sind. Alles Uebrige bleibt als Platzhalter stehen und ist von Hand zu ergaenzen - insbesondere der Abschnitt *Anmerkungen*, der die Deutung enthaelt und nicht generierbar ist. Nicht ermittelbare Groessen werden als `nicht erfasst` ausgewiesen, niemals geschaetzt (Randbedingung des Skills). python protokoll-geruest.py "" [--ueberschreiben] """ from __future__ import annotations import argparse import hashlib import json import subprocess from pathlib import Path EBENEN_DATEIEN = ("StRS.md", "SyRS.md", "SwRS.md") def sha256(pfad: Path) -> str: return hashlib.sha256(pfad.read_bytes()).hexdigest().upper() def lies(pfad: Path, standard: str = "nicht erfasst") -> str: try: return pfad.read_text(encoding="utf-8").strip() or standard except OSError: return standard def zahl(n) -> str: """Tausenderpunkte nach deutscher Schreibweise.""" try: return f"{int(n):,}".replace(",", ".") except (TypeError, ValueError): return "nicht erfasst" def dauer(ms: int) -> str: s = ms // 1000 return f"{s // 3600:02d}:{(s % 3600) // 60:02d}:{s % 60:02d}" def git_kopf(repo: Path) -> str: try: r = subprocess.run( ["git", "-C", str(repo), "rev-parse", "HEAD"], capture_output=True, text=True, check=False, ) return r.stdout.strip() or "nicht erfasst" except OSError: return "nicht erfasst" def main() -> int: parser = argparse.ArgumentParser(description="Protokollgeruest erzeugen") parser.add_argument("lauf") parser.add_argument("--repo", default="C:/DEV/MasterArbeit") parser.add_argument("--ueberschreiben", action="store_true") args = parser.parse_args() lauf = Path(args.lauf).resolve() ziel = lauf / "Protokoll.md" if ziel.exists() and not args.ueberschreiben: print(f"{ziel} existiert bereits - mit --ueberschreiben erzwingen.") return 1 roh = json.loads((lauf / "RawResult.json").read_text(encoding="utf-8")) meta = lauf / "_meta" u = roh.get("usage", {}) lr = roh.get("local_runtime") or {} erfasst = roh.get("usage_captured", True) def tok(feld: str) -> str: return zahl(u.get(feld, 0)) if erfasst else "nicht erfasst" # Ebene und Iteration aus der Ablagestruktur ableiten teile = lauf.parts iteration = next((p for p in teile if p.startswith("Iteration")), "?") zelle = "/".join(teile[teile.index(iteration):-1]) if iteration in teile else "?" # Die Prompt-Datei liegt im Versuchsordner, also oberhalb der # Bedingungsebenen; deren Anzahl schwankt mit der Modell-ID (ein- oder # zweiteilig). Deshalb aufwaerts suchen statt eine feste Tiefe annehmen. prompt_datei = None for eltern in lauf.parents[:8]: treffer = sorted(eltern.glob("*_Prompt.md")) if treffer: prompt_datei = treffer[-1] break anforderungen = lies(meta / "anforderungen.md", "*(nicht ausgewertet)*") dateien = sorted(p.name for p in (lauf / "Ergebnisse").glob("*") if p.is_file()) vorher, nachher = lies(meta / "before.txt", ""), lies(meta / "after.txt", "") text = f"""# Messprotokoll – {zelle} > **GERUEST** – maschinell aus den Rohdaten erzeugt. Die Abschnitte *Anmerkungen* > und *Gueltigkeit* sind von Hand zu pruefen und zu ergaenzen. ## Lauf - **Prompt-Datei:** `{prompt_datei.name if prompt_datei else 'nicht erfasst'}` - **SHA-256 (Prompt):** `{sha256(prompt_datei) if prompt_datei else 'nicht erfasst'}` - **Startzeit:** {lies(meta / 'startzeit.txt')} - **Endzeit:** {lies(meta / 'endzeit.txt')} - **Dauer gesamt:** {dauer(roh.get('duration_ms', 0))} (API: nicht erfasst – OpenCode liefert keine separate API-Zeit) - **Root-Verzeichnis:** `{args.repo}/QuellCode/CentronERP` - **Codebasis-Commit:** `{git_kopf(Path(args.repo))}` (vor dem Lauf dirty: {'nein' if not vorher else 'ja – ' + vorher.replace(chr(10), '; ')}) - **Snapshot-Zustand:** bereinigt von KI-Konfigurationen: ja; Remote entkoppelt: ja ## Werkzeugkonfiguration - **Skill-Version:** {lauf.name.split('_v')[-1].split('-')[0] if '_v' in lauf.name else 'nicht erfasst'} - **Werkzeugadapter:** OpenCode, `opencode-adapter.py --provider {roh.get('provider')}` (Adapter-Version {roh.get('adapter_version')}) - **CLI-Version:** OpenCode {roh.get('opencode_version', 'nicht erfasst')} - **Modell (angefordert):** `{roh.get('model_requested')}` - **Modell (tatsaechlich):** `{roh.get('model')}` - **Kontrolle Modell:** {'bestanden' if roh.get('model') == roh.get('model_requested') else '**verletzt**'} - **Effort:** `{roh.get('effort')}` angefordert, **{'wirksam' if roh.get('effort_applied') else 'nicht wirksam'}** (`effort_applied: {str(roh.get('effort_applied')).lower()}`) - **Ablage:** `{zelle}/` - **Agentenmodus:** `{roh.get('mode')}` - **Kontextfenster:** {zahl(roh.get('context_window', 0))} Tokens geladen (Modellmaximum {zahl(lr.get('max_context_length', 0))}) - **Sampling-Parameter:** nicht steuerbar - **Lokaler Modellbetrieb:** Runtime `{lr.get('compatibility_type', '?')}`, `lms` {lr.get('lms_version', '?')}, Architektur `{lr.get('arch', '?')}`, **Quantisierung `{lr.get('quantization', '?')}`**, {lr.get('parallel_slots', '?')} Slots, GPU-Offload `{lr.get('gpu_offload', '?')}`, alleiniges Modell: {str(lr.get('alleiniges_modell', '?')).lower()} - **Abbruchsicherungen:** `--stall-timeout 0`, `--max-runtime` siehe Matrixskript - **Subagenten:** `spawned` = {roh.get('subagent_stats', {}).get('spawned', 0)}, `completed` = {roh.get('subagent_stats', {}).get('completed', 0)}, `failed` = {roh.get('subagent_stats', {}).get('failed', 0)} - **Rollen:** {json.dumps(roh.get('subagent_stats', {}).get('by_type', {}), ensure_ascii=False)} ## Validierungsstichprobe - **Stand:** entfaellt ## Verbrauch | Messgroesse | Wert | |---|---| | Input-Tokens | {tok('prompt_tokens')} | | Output-Tokens | {tok('completion_tokens')} | | Reasoning-Tokens | {tok('reasoning_tokens')} | | Cache-Write-/Cache-Read-Tokens | nicht erfasst – der lokale Server liefert keine | | Agent-Turns | {roh.get('num_turns', 0)} | **Tokens gesamt: {tok('total_tokens')}.** Kosten `0` – lokaler Betrieb ({roh.get('cost_source', '')}). {'' if erfasst else chr(10) + '**Achtung:** ' + roh.get('usage_note', '') + chr(10)} ## Gefundene Anforderungen {anforderungen} ## Ergebnis - **Status:** `is_error: {str(roh.get('is_error')).lower()}`, `subtype: {roh.get('subtype')}`, `exit_code: {roh.get('exit_code')}`, `timed_out: {str(roh.get('timed_out')).lower()}` - **Session-ID:** `{roh.get('session_id')}` - **Werkzeugaufrufe:** {roh.get('tool_call_count', 0)} – {json.dumps(roh.get('tool_call_types', {}), ensure_ascii=False)} - **Kontrolle Agentenmodus:** `subagent_stats.spawned` = {roh.get('subagent_stats', {}).get('spawned', 0)}{' – korrekt fuer solo' if roh.get('mode') == 'solo' else ''} - **Gueltigkeit:** *(pruefen)* {'**Fehlmessung** – Ergebnisverzeichnis leer.' if not dateien else 'Ergebnisdateien vorhanden.'} - **Erzeugte Dateien:** {', '.join(dateien) if dateien else 'keine'} - **Root unveraendert:** {'ja' if vorher == nachher else '**nein – pruefen**'} - **Fehlermeldungen:** {json.dumps(roh.get('errors', []), ensure_ascii=False)} ## Anmerkungen/Auffaelligkeiten *(von Hand zu ergaenzen)* """ ziel.write_text(text, encoding="utf-8") print(f"{ziel}") return 0 if __name__ == "__main__": raise SystemExit(main())