Files
claude-regelwerk/.vault-tools/trading-plattform-pruefung/protokoll_vergleich.py
T
Pascal MichelandClaude Opus 5 395642c480 Automatischer Push: 19 Datei(en) geändert
- .vault-tools/plan-kopie/LIESMICH.md
- .vault-tools/plan-kopie/rot_nachweis.py
- .vault-tools/plan-kopie/welle_vergleich.py
- .vault-tools/plan-kopie/welle_vergleich_treiber.py
- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/angriffe.py
- .vault-tools/trading-plattform-pruefung/browserlauf.py
- .vault-tools/trading-plattform-pruefung/browserlauf_zugang.py
- .vault-tools/trading-plattform-pruefung/c_eigenlauf.py
- .vault-tools/trading-plattform-pruefung/d_eigenlauf.py
- .vault-tools/trading-plattform-pruefung/handelstag_auswertung.py
- .vault-tools/trading-plattform-pruefung/ntfy_probe_server.py
- … und 7 weitere

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-19 04:00:01 +02:00

123 lines
5.1 KiB
Python

"""Vergleicht zwei JSONL-Protokolle der Messwerkzeuge Wert fuer Wert (Bitgleichheit nach einem Umbau).
Koordinator-Werkzeug, nicht fuers Repo. Frage (Herkunft: Projektvorgabe, Anforderung Paket B vom
2026-09-18, "Die eine Regel ueber allem"): Liefert der umgebaute Code auf denselben echten Daten dieselben
Zahlen wie das Referenzprotokoll -- jede Gleitkommazahl exakt gleich, nan gleich nan?
Es unterscheidet drei Dinge und vermischt sie nicht:
ABWEICHUNG ein Blattwert unter demselben Pfad ist anders -> Befund (Exit 2)
NUR NEU ein Pfad steht nur im neuen Protokoll (neuer Schluessel) -> gemeldet, kein Befund, ausser mit --streng
NUR ALT ein Pfad steht nur im alten Protokoll -> Befund (Exit 2)
Zeilen werden in ihrer Reihenfolge gepaart; verschiedene Zeilenzahlen oder Zeilenarten sind ein Befund.
Uebergangen werden die Schluessel aus --ohne (Vorgabe: zeit, dauer_s, protokoll, name), weil sie vom Lauf
abhaengen und nicht von der Rechnung.
Was es nicht sieht: ob das Referenzprotokoll selbst stimmt, und Zeilen, die in beiden Protokollen fehlen.
Regressionsnachweis: --sabotage veraendert im neuen Protokoll eine Zahl in der letzten Stelle und muss rot enden.
Aufruf: python protokoll_vergleich.py <alt.jsonl> <neu.jsonl> [--ohne k1 k2 ...] [--streng] [--sabotage]
Exit 0 ohne Befund, 2 mit Befund, 3 wenn eine Datei fehlt oder leer ist.
"""
import json
import math
import sys
from pathlib import Path
def lies(pfad: Path) -> list[dict]:
zeilen = []
with open(pfad, encoding="utf-8") as datei:
for zeile in datei:
if zeile.strip():
zeilen.append(json.loads(zeile, parse_constant=float))
return zeilen
def flach(knoten, ohne: set[str], praefix: str = "") -> dict:
werte: dict = {}
if isinstance(knoten, dict):
for name, inhalt in knoten.items():
if name in ohne:
continue
werte.update(flach(inhalt, ohne, f"{praefix}.{name}"))
elif isinstance(knoten, list):
for nummer, inhalt in enumerate(knoten):
werte.update(flach(inhalt, ohne, f"{praefix}[{nummer}]"))
else:
werte[praefix] = knoten
return werte
def gleich(a, b) -> bool:
if isinstance(a, float) and isinstance(b, float):
return a == b or (math.isnan(a) and math.isnan(b))
return type(a) is type(b) and a == b
def main() -> int:
argumente = sys.argv[1:]
streng = "--streng" in argumente
sabotage = "--sabotage" in argumente
ohne = {"zeit", "dauer_s", "protokoll", "name"}
if "--ohne" in argumente:
stelle = argumente.index("--ohne")
ohne = set()
for wort in argumente[stelle + 1 :]:
if wort.startswith("--"):
break
ohne.add(wort)
pfade = [Path(a) for a in argumente if not a.startswith("--") and a not in ohne]
if len(pfade) != 2 or not all(p.is_file() for p in pfade):
print(__doc__)
return 3
alt, neu = lies(pfade[0]), lies(pfade[1])
if not alt or not neu:
print("VORAUSSETZUNG FEHLT: ein Protokoll ist leer")
return 3
if sabotage:
for zeile in reversed(neu):
zahlen = [k for k, v in zeile.items() if isinstance(v, float) and math.isfinite(v) and v != 0.0]
if zahlen:
zeile[zahlen[0]] = math.nextafter(zeile[zahlen[0]], math.inf)
print(f"sabotage: {zahlen[0]} in einer Zeile der Art {zeile.get('art')} um ein ULP verschoben")
break
befunde: list[str] = []
if len(alt) != len(neu):
befunde.append(f"Zeilenzahl: alt {len(alt)}, neu {len(neu)}")
verglichen = 0
nur_neu: dict[str, int] = {}
for nummer, (a, n) in enumerate(zip(alt, neu), start=1):
if a.get("art") != n.get("art"):
befunde.append(f"Zeile {nummer}: Art alt {a.get('art')!r}, neu {n.get('art')!r}")
continue
fa, fn = flach(a, ohne), flach(n, ohne)
for pfad in fa.keys() - fn.keys():
befunde.append(f"Zeile {nummer} ({a.get('art')}): NUR ALT {pfad}")
for pfad in fn.keys() - fa.keys():
kurz = pfad.split("[")[0]
nur_neu[kurz] = nur_neu.get(kurz, 0) + 1
if streng:
befunde.append(f"Zeile {nummer} ({a.get('art')}): NUR NEU {pfad}")
for pfad in fa.keys() & fn.keys():
verglichen += 1
if not gleich(fa[pfad], fn[pfad]):
befunde.append(f"Zeile {nummer} ({a.get('art')}): ABWEICHUNG {pfad}: alt {fa[pfad]!r}, neu {fn[pfad]!r}")
print(f"{min(len(alt), len(neu))} Zeilen gepaart, {verglichen} Werte verglichen, uebergangene Schluessel: {sorted(ohne)}")
for pfad, zahl in sorted(nur_neu.items()):
print(f"NUR NEU: {pfad} in {zahl} Zeile(n)")
for befund in befunde[:25]:
print("BEFUND:", befund)
if len(befunde) > 25:
print(f"... und {len(befunde) - 25} weitere")
print(f"{len(befunde)} Befund(e).")
return 2 if befunde else 0
if __name__ == "__main__":
try:
sys.exit(main())
except Exception as ausnahme: # noqa: BLE001 - ein Absturz des Werkzeugs ist selbst ein Befund
print(f"BEFUND: das Werkzeug ist abgebrochen: {ausnahme!r}")
sys.exit(2)