Automatischer Push: 46 Datei(en) geändert
- .claude/agents/planer.md - .claude/skills/plan-executer-pruefung/SKILL.md - .claude/workflows/plankritik.js - .claude/workflows/planung.js - .claude/workflows/pruefung.js - .claude/workflows/verbrauch.js - .claude/workflows/welle-mit-pruefung.js - .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/plan_zeichen.py - .vault-tools/plan-kopie/rot_nachweis.py - .vault-tools/trading-plattform-pruefung/LIESMICH.md - .vault-tools/trading-plattform-pruefung/abendkontrolle.py - … und 34 weitere Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
67d674cd0b
commit
ea278dc24f
46 files changed
+8403
-80
No files matched your search
@@ -0,0 +1,304 @@
|
||||
"""Faelle fuer verbrauch_reihe.py: baut je Fall einen kuenstlichen Session-Speicher und faehrt das Skript als eigenen Prozess.
|
||||
|
||||
Die Grundlinie laeuft zuerst; scheitert sie, messen die uebrigen Faelle nichts. Der Prozess laeuft
|
||||
ohne PYTHONIOENCODING, damit eine Ausgabe ausserhalb von ASCII auf cp1252 auffaellt.
|
||||
Aufruf: python faelle_verbrauch_reihe.py
|
||||
Exit 0, wenn alle Faelle bestehen, sonst 1.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
SKRIPT = Path(__file__).with_name("verbrauch_reihe.py")
|
||||
|
||||
|
||||
def fenster(fuenf, woche, reset5="2026-09-24T18:30:00Z", resetw="2026-09-28T00:00:00Z"):
|
||||
return [
|
||||
{"label": "5-hour limit", "percentUsed": fuenf, "resetsAt": reset5},
|
||||
{"label": "Weekly · all models", "percentUsed": woche, "resetsAt": resetw},
|
||||
]
|
||||
|
||||
|
||||
def baue(speicher, laeufe):
|
||||
"""laeufe: {lauf: [agent]}; agent: {id, label, start, ende, modell, ergebnis, ohne_zeit, kaputt}."""
|
||||
for lauf, agenten in laeufe.items():
|
||||
ordner = speicher / "sitzung-1" / "subagents" / "workflows" / lauf
|
||||
ordner.mkdir(parents=True)
|
||||
with (ordner / "journal.jsonl").open("w", encoding="utf-8") as journal:
|
||||
journal.write(json.dumps({"type": "launched"}) + "\n")
|
||||
for a in agenten:
|
||||
journal.write(json.dumps({"type": "started", "agentId": a["id"], "label": a["label"]}) + "\n")
|
||||
if a.get("kaputt"):
|
||||
journal.write("{kein json\n")
|
||||
if "ergebnis" in a:
|
||||
journal.write(json.dumps({"type": "result", "agentId": a["id"], "result": a["ergebnis"]}) + "\n")
|
||||
for a in agenten:
|
||||
(ordner / f"agent-{a['id']}.meta.json").write_text(json.dumps({"model": a.get("modell", "haiku")}), encoding="utf-8")
|
||||
if a.get("ohne_zeit"):
|
||||
continue
|
||||
with (ordner / f"agent-{a['id']}.jsonl").open("w", encoding="utf-8") as d:
|
||||
d.write(json.dumps({"timestamp": a["start"], "type": "user"}) + "\n")
|
||||
d.write(json.dumps({"timestamp": a["ende"], "type": "assistant"}) + "\n")
|
||||
|
||||
|
||||
def ablesung(nr, anlass, zeitpunkt, fuenf, woche, **extra):
|
||||
ende = extra.pop("bis", None) or zeitpunkt
|
||||
return {"id": f"v{nr}", "label": "verbrauch:" + anlass, "start": zeitpunkt, "ende": ende, "modell": "haiku",
|
||||
"ergebnis": {"verfuegbar": True, "status": "ok", "plan": "Pro", "fenster": extra.pop("liste", None) or fenster(fuenf, woche, **extra), "fehler": ""}}
|
||||
|
||||
|
||||
# resetsAt schwankt bei echten Ablesungen um Millisekunden und springt ueber Sekunden- und
|
||||
# Datumsgrenzen (gemessen 2026-09-24); die Grundlinie traegt deshalb solche Werte.
|
||||
ZITTER = {"reset5": "2026-09-24T18:29:59.664Z", "resetw": "2026-09-27T23:59:59.664Z"}
|
||||
|
||||
|
||||
def opus(nr, start, ende, modell="opus"):
|
||||
return {"id": f"o{nr}", "label": f"plankritik:linse{nr}", "start": start, "ende": ende, "modell": modell, "ergebnis": {}}
|
||||
|
||||
|
||||
def fahre(speicher, *zusatz):
|
||||
umgebung = {k: v for k, v in os.environ.items() if k not in ("PYTHONIOENCODING", "PYTHONUTF8")}
|
||||
lauf = subprocess.run([sys.executable, str(SKRIPT), "--speicher", str(speicher), *zusatz],
|
||||
capture_output=True, env=umgebung)
|
||||
return lauf.returncode, lauf.stdout.decode("ascii", "replace"), lauf.stderr.decode("utf-8", "replace")
|
||||
|
||||
|
||||
GRUND = {
|
||||
"wf_a": [
|
||||
ablesung(1, "plankritik:vor Linsen", "2026-09-24T10:00:00Z", 10, 5),
|
||||
opus(1, "2026-09-24T10:00:10Z", "2026-09-24T10:30:10Z"),
|
||||
ablesung(2, "plankritik:Ende", "2026-09-24T10:30:10Z", 14, 6, **ZITTER),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def f_grundlinie(tmp):
|
||||
baue(tmp, GRUND)
|
||||
code, aus, err = fahre(tmp)
|
||||
fehler = []
|
||||
if code != 0:
|
||||
fehler.append(f"Exit {code}: {err[-300:]}")
|
||||
for erwartet in ["2 Ablesungen, 1 Paare", "| wf_a | plankritik:vor Linsen -> plankritik:Ende | 30.2 | opus 30.0 | 4.0 | 1.0 |",
|
||||
"Naeherung: 8.0 % des 5-Stunden-Fensters je Opus-Agentenstunde (1 Paare, 0.5 Opus-Stunden"]:
|
||||
if erwartet not in aus:
|
||||
fehler.append("fehlt: " + erwartet)
|
||||
if "Hinweise" in aus:
|
||||
fehler.append("unerwartete Hinweise")
|
||||
return fehler
|
||||
|
||||
|
||||
def f_reset(tmp):
|
||||
baue(tmp, {"wf_r": [ablesung(1, "a", "2026-09-24T10:00:00Z", 80, 5), opus(1, "2026-09-24T10:01:00Z", "2026-09-24T10:20:00Z"),
|
||||
ablesung(2, "b", "2026-09-24T10:30:00Z", 3, 6, reset5="2026-09-24T15:30:00Z")]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
fehler = []
|
||||
if code != 0 or "| Reset | 1.0 |" not in aus:
|
||||
fehler.append("Reset nicht erkannt: " + aus[-300:])
|
||||
if "Keine auswertbaren Paare mit Opus-Agenten." not in aus:
|
||||
fehler.append("Reset-Paar wurde mitgerechnet")
|
||||
return fehler
|
||||
|
||||
|
||||
def f_speicher_fehlt(tmp):
|
||||
code, aus, _ = fahre(tmp / "gibt-es-nicht")
|
||||
return [] if code == 2 and "Speicher fehlt" in aus else [f"Exit {code}, Ausgabe {aus!r}"]
|
||||
|
||||
|
||||
def f_null_ablesungen(tmp):
|
||||
baue(tmp, {"wf_n": [opus(1, "2026-09-24T10:00:00Z", "2026-09-24T10:10:00Z")]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "0 Ablesungen, 0 Paare" in aus else [f"Exit {code}, Ausgabe {aus!r}"]
|
||||
|
||||
|
||||
def f_kaputte_zeile(tmp):
|
||||
agenten = [dict(GRUND["wf_a"][0], kaputt=True), GRUND["wf_a"][1], GRUND["wf_a"][2]]
|
||||
baue(tmp, {"wf_k": agenten})
|
||||
code, aus, _ = fahre(tmp)
|
||||
fehler = []
|
||||
if code != 0 or "1 Paare" not in aus:
|
||||
fehler.append("Auswertung trotz kaputter Zeile gescheitert: " + aus[-300:])
|
||||
if "ist kein JSON" not in aus:
|
||||
fehler.append("kaputte Zeile nicht als Hinweis gemeldet")
|
||||
return fehler
|
||||
|
||||
|
||||
def f_ohne_zeitstempel(tmp, position):
|
||||
agenten = [dict(a) for a in GRUND["wf_a"]]
|
||||
agenten[position]["ohne_zeit"] = True
|
||||
baue(tmp, {"wf_z": agenten})
|
||||
code, aus, _ = fahre(tmp)
|
||||
fehler = []
|
||||
if code != 0 or "1 Ablesungen, 0 Paare" not in aus:
|
||||
fehler.append(f"Exit {code}, Ausgabe {aus[-300:]!r}")
|
||||
if "ohne Zeitstempel" not in aus:
|
||||
fehler.append("fehlender Zeitstempel nicht gemeldet")
|
||||
return fehler
|
||||
|
||||
|
||||
def f_fenster_kaputt(tmp, liste, erwartet="| unbekannt | unbekannt |"):
|
||||
baue(tmp, {"wf_f": [ablesung(1, "a", "2026-09-24T10:00:00Z", 10, 5, liste=liste), opus(1, "2026-09-24T10:01:00Z", "2026-09-24T10:20:00Z"),
|
||||
ablesung(2, "b", "2026-09-24T10:30:00Z", 12, 6)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and erwartet in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_umlaut_im_anlass(tmp):
|
||||
baue(tmp, {"wf_u": [ablesung(1, "pruefung:vor Prüfung", "2026-09-24T10:00:00Z", 10, 5), ablesung(2, "pruefung:nach Prüfung", "2026-09-24T10:30:00Z", 12, 5)]})
|
||||
code, aus, err = fahre(tmp)
|
||||
return [] if code == 0 and "pruefung:vor Pruefung -> pruefung:nach Pruefung" in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}, stderr {err[-200:]!r}"]
|
||||
|
||||
|
||||
def f_seit(tmp):
|
||||
baue(tmp, {"wf_s": [ablesung(1, "alt", "2026-09-20T10:00:00Z", 10, 5), ablesung(2, "a", "2026-09-24T10:00:00Z", 10, 5),
|
||||
ablesung(3, "b", "2026-09-24T11:00:00Z", 12, 5)]})
|
||||
code, aus, _ = fahre(tmp, "--seit", "2026-09-24")
|
||||
return [] if code == 0 and "2 Ablesungen, 1 Paare" in aus and "alt ->" not in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_seit_ungueltig(tmp):
|
||||
baue(tmp, GRUND)
|
||||
code, aus, _ = fahre(tmp, "--seit", "gestern")
|
||||
return [] if code == 2 and "kein Datum" in aus else [f"Exit {code}, Ausgabe {aus!r}"]
|
||||
|
||||
|
||||
def f_voller_modellname(tmp):
|
||||
baue(tmp, {"wf_m": [GRUND["wf_a"][0], opus(1, "2026-09-24T10:00:10Z", "2026-09-24T10:30:10Z", modell="claude-opus-5-5"), GRUND["wf_a"][2]]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "opus 30.0" in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_agent_ausserhalb(tmp):
|
||||
# Ein Agent, der vor der ersten Ablesung endet, zaehlt nicht mit; einer, der halb hineinreicht, anteilig.
|
||||
baue(tmp, {"wf_x": [opus(1, "2026-09-24T09:00:00Z", "2026-09-24T09:50:00Z"), GRUND["wf_a"][0],
|
||||
opus(2, "2026-09-24T09:55:00Z", "2026-09-24T10:10:00Z"), GRUND["wf_a"][2]]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "| opus 10.0 |" in aus else [f"Exit {code}, Ausgabe {aus[-400:]!r}"]
|
||||
|
||||
|
||||
def f_absturz_ist_befund(tmp):
|
||||
# Ein Zeitstempel ohne Zeitzone neben einem mit ihr laesst den Vergleich mit TypeError scheitern.
|
||||
# Der Lauf muss das als Hinweis melden und die anderen Laeufe trotzdem auswerten.
|
||||
kaputt = [dict(GRUND["wf_a"][0]), dict(GRUND["wf_a"][1], start="2026-09-24T10:00:10"), dict(GRUND["wf_a"][2])]
|
||||
baue(tmp, {"wf_a": GRUND["wf_a"], "wf_b": kaputt})
|
||||
code, aus, err = fahre(tmp)
|
||||
fehler = []
|
||||
if code != 0:
|
||||
fehler.append(f"Exit {code}: {err[-300:]}")
|
||||
if "wf_b: Auswertung abgebrochen (TypeError" not in aus:
|
||||
fehler.append("Absturz nicht als Hinweis gemeldet")
|
||||
if "| wf_a |" not in aus:
|
||||
fehler.append("der gesunde Lauf fehlt")
|
||||
return fehler
|
||||
|
||||
|
||||
def f_verschiebung(tmp, reset5, erwartet):
|
||||
# Nur resetsAt verschiebt sich, der Wert steigt; entscheidend ist allein die Toleranz.
|
||||
baue(tmp, {"wf_v": [ablesung(1, "a", "2026-09-24T10:00:00Z", 10, 5), ablesung(2, "b", "2026-09-24T10:20:00Z", 12, 5, reset5=reset5)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and erwartet in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_fallender_wert(tmp):
|
||||
baue(tmp, {"wf_d": [ablesung(1, "a", "2026-09-24T10:00:00Z", 30, 5), ablesung(2, "b", "2026-09-24T10:20:00Z", 5, 5)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "| Reset | 0.0 |" in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_reset_unlesbar(tmp):
|
||||
baue(tmp, {"wf_u": [ablesung(1, "a", "2026-09-24T10:00:00Z", 10, 5, reset5="bald"), ablesung(2, "b", "2026-09-24T10:20:00Z", 12, 6)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "| unbekannt | 1.0 |" in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_hoechstes_fenster(tmp):
|
||||
liste = [{"label": "5-hour limit", "percentUsed": 10, "resetsAt": "2026-09-24T18:30:00Z"},
|
||||
{"label": "5-hour limit", "percentUsed": 30, "resetsAt": "2026-09-24T18:30:00Z"},
|
||||
{"label": "Weekly · all models", "percentUsed": 5, "resetsAt": "2026-09-28T00:00:00Z"}]
|
||||
baue(tmp, {"wf_h": [ablesung(1, "a", "2026-09-24T10:00:00Z", 0, 0, liste=liste), ablesung(2, "b", "2026-09-24T10:20:00Z", 35, 6)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "| 5.0 | 1.0 |" in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_ablesezeit_ist_ende(tmp):
|
||||
# Der Opus-Agent laeuft ganz innerhalb des ersten Ableseagenten, also vor der Ablesung.
|
||||
baue(tmp, {"wf_t": [ablesung(1, "a", "2026-09-24T10:00:00Z", 10, 5, bis="2026-09-24T10:00:10Z"),
|
||||
opus(1, "2026-09-24T10:00:02Z", "2026-09-24T10:00:08Z"),
|
||||
ablesung(2, "b", "2026-09-24T10:30:00Z", 12, 5)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "| 29.8 | - | 2.0 | 0.0 |" in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
def f_agent_ueber_das_paar_hinaus(tmp):
|
||||
baue(tmp, {"wf_b": [ablesung(1, "a", "2026-09-24T10:00:00Z", 10, 5), ablesung(2, "b", "2026-09-24T10:30:00Z", 12, 5),
|
||||
opus(1, "2026-09-24T10:20:00Z", "2026-09-24T10:50:00Z"), ablesung(3, "c", "2026-09-24T11:00:00Z", 15, 5)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
fehler = []
|
||||
if code != 0 or "| a -> b | 30.0 | opus 10.0 |" not in aus:
|
||||
fehler.append("erstes Paar nicht am Ende gekappt: " + aus[-400:])
|
||||
if "| b -> c | 30.0 | opus 20.0 |" not in aus:
|
||||
fehler.append("zweites Paar falsch: " + aus[-400:])
|
||||
return fehler
|
||||
|
||||
|
||||
def f_ueber_hundert(tmp):
|
||||
baue(tmp, {"wf_g": [ablesung(1, "a", "2026-09-24T10:00:00Z", 98, 5), ablesung(2, "b", "2026-09-24T10:20:00Z", 103, 5)]})
|
||||
code, aus, _ = fahre(tmp)
|
||||
return [] if code == 0 and "| 5.0 | 0.0 |" in aus else [f"Exit {code}, Ausgabe {aus[-300:]!r}"]
|
||||
|
||||
|
||||
FAELLE = [
|
||||
("Grundlinie: zwei Ablesungen, ein Opus-Agent dazwischen", f_grundlinie),
|
||||
("Reset zwischen zwei Ablesungen", f_reset),
|
||||
("Speicher fehlt: Exit 2", f_speicher_fehlt),
|
||||
("null Ablesungen: Exit 0 mit Zaehlung", f_null_ablesungen),
|
||||
("kaputte Journalzeile: Hinweis, kein Absturz", f_kaputte_zeile),
|
||||
("erste Ablesung ohne Zeitstempel", lambda t: f_ohne_zeitstempel(t, 0)),
|
||||
("letzte Ablesung ohne Zeitstempel", lambda t: f_ohne_zeitstempel(t, 2)),
|
||||
("5-Stunden-Fenster fehlt", lambda t: f_fenster_kaputt(t, [{"label": "Weekly · all models", "percentUsed": 5, "resetsAt": "2026-09-28T00:00:00Z"}], "| unbekannt | 1.0 |")),
|
||||
("Umlaut im Anlass auf cp1252", f_umlaut_im_anlass),
|
||||
("Prozent als Text", lambda t: f_fenster_kaputt(t, [{"label": "5-hour limit", "percentUsed": "10", "resetsAt": "2026-09-24T18:30:00Z"}, {"label": "Weekly · all models", "percentUsed": 5, "resetsAt": "2026-09-28T00:00:00Z"}])),
|
||||
("Prozent als Wahrheitswert", lambda t: f_fenster_kaputt(t, [{"label": "5-hour limit", "percentUsed": True, "resetsAt": "2026-09-24T18:30:00Z"}, {"label": "Weekly · all models", "percentUsed": 5, "resetsAt": "2026-09-28T00:00:00Z"}])),
|
||||
("fenster kein Array", lambda t: f_fenster_kaputt(t, {"a": 1})),
|
||||
("--seit filtert aeltere Ablesungen", f_seit),
|
||||
("--seit ungueltig: Exit 2", f_seit_ungueltig),
|
||||
("voller Modellname zaehlt als opus", f_voller_modellname),
|
||||
("Agent ausserhalb und halb innerhalb des Paars", f_agent_ausserhalb),
|
||||
("Absturz in einem Lauf ist ein Hinweis", f_absturz_ist_befund),
|
||||
("resetsAt 5 Stunden weiter, Wert steigt: Reset", lambda t: f_verschiebung(t, "2026-09-24T23:30:00Z", "| Reset | 0.0 |")),
|
||||
("resetsAt 4:59 verschoben: dasselbe Fenster", lambda t: f_verschiebung(t, "2026-09-24T18:34:59Z", "| 2.0 | 0.0 |")),
|
||||
("resetsAt 5:01 verschoben: Reset", lambda t: f_verschiebung(t, "2026-09-24T18:35:01Z", "| Reset | 0.0 |")),
|
||||
("resetsAt 5:01 frueher: Reset", lambda t: f_verschiebung(t, "2026-09-24T18:24:59Z", "| Reset | 0.0 |")),
|
||||
("fallender Wert im selben Fenster ist ein Reset", f_fallender_wert),
|
||||
("resetsAt ohne Datum: unbekannt", f_reset_unlesbar),
|
||||
("doppeltes 5-Stunden-Fenster: das hoechste zaehlt", f_hoechstes_fenster),
|
||||
("Ablesezeit ist das Ende des Ableseagenten", f_ablesezeit_ist_ende),
|
||||
("Agent ueber das Paar hinaus wird am Ende gekappt", f_agent_ueber_das_paar_hinaus),
|
||||
("ueber 100 Prozent ist ein Wert, kein Fehler", f_ueber_hundert),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
# Fehlertexte koennen Zeichen ausserhalb der Konsolenkodierung tragen; der Treiber darf daran nicht sterben.
|
||||
sys.stdout.reconfigure(errors="replace")
|
||||
gescheitert = 0
|
||||
for name, fall in FAELLE:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
try:
|
||||
fehler = fall(Path(tmp))
|
||||
except Exception as e: # ein Absturz im Fall ist selbst ein Befund
|
||||
fehler = [f"Absturz im Fall: {type(e).__name__}: {e}"]
|
||||
if fehler:
|
||||
gescheitert += 1
|
||||
print(f"FEHLER {name}\n " + "\n ".join(fehler))
|
||||
else:
|
||||
print(f"OK {name}")
|
||||
print(f"{len(FAELLE)} Faelle, {gescheitert} gescheitert")
|
||||
return 1 if gescheitert else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user