This commit is contained in:
team3
2026-07-12 16:13:50 +02:00
parent a284e03225
commit 31a42bbf1d
48 changed files with 3625 additions and 253 deletions

View File

@@ -10,6 +10,7 @@ import agents
import artefakte
import auto_loop
import db
import diagramme
import guide
import inventar
import korpus
@@ -26,10 +27,11 @@ EBENEN = [
("inventar", inventar, "inventar_fertig"),
("artefakte", artefakte, "artefakte_fertig"),
("struktur", struktur, "struktur_fertig"),
("diagramme", diagramme, "diagramme_fertig"),
("guide", guide, "fertig"),
]
_ORDNUNG = ["neu", "korpus", "korpus_fertig", "inventar_fertig",
"artefakte_fertig", "struktur_fertig", "fertig"]
"artefakte_fertig", "struktur_fertig", "diagramme_fertig", "fertig"]
_laeufe: dict[str, asyncio.Task] = {}
@@ -60,11 +62,12 @@ def lauf_stoppen(topic: str) -> None:
run = db.one("SELECT id FROM runs WHERE topic=? AND status='running'"
" ORDER BY id DESC", (topic,))
if run:
db.update("runs", "id", run["id"], status="stopped", beendet="jetzt")
db.update("runs", "id", run["id"], status="stopped", beendet=db.now())
_ROLLBACK = {"guide": "struktur_fertig", "struktur": "artefakte_fertig",
"artefakte": "inventar_fertig", "inventar": "korpus_fertig", "korpus": "neu"}
_ROLLBACK = {"guide": "diagramme_fertig", "diagramme": "struktur_fertig",
"struktur": "artefakte_fertig", "artefakte": "inventar_fertig",
"inventar": "korpus_fertig", "korpus": "neu"}
def ebenen_entfernen(topic: str, ebene: str) -> None:
@@ -74,39 +77,48 @@ def ebenen_entfernen(topic: str, ebene: str) -> None:
if ebene not in _ROLLBACK:
raise ValueError(f"unbekannte Ebene: {ebene}")
lauf_stoppen(topic)
stufe = ["guide", "struktur", "artefakte", "inventar", "korpus"].index(ebene)
stufe = ["guide", "diagramme", "struktur", "artefakte", "inventar", "korpus"].index(ebene)
db.execute("DELETE FROM sections WHERE baustein_id IN"
" (SELECT id FROM bausteine WHERE topic=?)", (topic,))
db.execute("UPDATE bausteine SET status='neu' WHERE topic=?", (topic,))
db.execute("UPDATE kapitel SET intro='' WHERE topic=?", (topic,)) # Intro ist Guide-Text
if stufe >= 1: # struktur
if stufe >= 1: # diagramme
db.execute("DELETE FROM diagramme WHERE topic=?", (topic,))
if stufe >= 2: # struktur
db.execute("DELETE FROM bausteine WHERE topic=?", (topic,))
db.execute("DELETE FROM kapitel WHERE topic=?", (topic,))
db.execute("DELETE FROM lernziele WHERE topic=?", (topic,))
db.execute("UPDATE atome SET ziel_id=NULL, baustein_id=NULL, ord=0 WHERE topic=?",
(topic,))
if stufe >= 2: # artefakte
if stufe >= 3: # artefakte
db.execute("DELETE FROM leitner WHERE artefakt_id IN (SELECT ar.id FROM artefakte ar"
" JOIN atome a ON ar.atom_id=a.id WHERE a.topic=?)", (topic,))
db.execute("DELETE FROM artefakte WHERE atom_id IN"
" (SELECT id FROM atome WHERE topic=?)", (topic,))
if stufe >= 3: # inventar
if stufe >= 4: # inventar
db.execute("DELETE FROM anker WHERE atom_id IN (SELECT id FROM atome WHERE topic=?)",
(topic,))
db.execute("DELETE FROM kanten WHERE topic=?", (topic,))
db.execute("DELETE FROM atome WHERE topic=?", (topic,))
db.execute("UPDATE quellen SET status='extrahiert' WHERE topic=?", (topic,))
if stufe >= 4: # korpus
# atome_stand mit-nullen — sonst überspringt der Resume-Guard die Neu-Extraktion
db.execute("UPDATE quellen SET status='extrahiert', atome_stand='' WHERE topic=?",
(topic,))
if stufe >= 5: # korpus
db.execute("DELETE FROM soll WHERE topic=?", (topic,))
db.execute("DELETE FROM quellen WHERE topic=?", (topic,))
import shutil
from config import KORPUS_DIR
shutil.rmtree(KORPUS_DIR / topic, ignore_errors=True)
# Gürtel-und-Hosenträger vor dem destruktiven rmtree: der Zielpfad MUSS
# unter KORPUS_DIR liegen (Alt-DBs könnten Namen ohne heutige Validierung
# tragen). Sonst nichts löschen.
ziel = (KORPUS_DIR / topic).resolve()
if ziel != KORPUS_DIR.resolve() and ziel.is_relative_to(KORPUS_DIR.resolve()):
shutil.rmtree(ziel, ignore_errors=True)
# Token-/Dauer-Anzeige der entfernten Ebenen nullen: Events bleiben (Ledger ist
# append-only), aber die Zählung beginnt hinter dem Reset-Marker neu.
letzte = db.one("SELECT MAX(id) AS m FROM events")["m"] or 0
resets = db.uj(db.one("SELECT resets FROM topics WHERE name=?", (topic,))["resets"], {})
for name in ["guide", "struktur", "artefakte", "inventar", "korpus"][:stufe + 1]:
for name in ["guide", "diagramme", "struktur", "artefakte", "inventar", "korpus"][:stufe + 1]:
resets[name] = letzte
db.update("topics", "name", topic, status=_ROLLBACK[ebene], resets=db.j(resets))
@@ -134,6 +146,8 @@ def voll_reset(topic: str) -> None:
(topic,))
db.execute("DELETE FROM kanten WHERE topic=?", (topic,))
db.execute("DELETE FROM atome WHERE topic=?", (topic,))
# Atome weg → Merker nullen, sonst überspringt der Guard die Neu-Extraktion
db.execute("UPDATE quellen SET atome_stand='' WHERE topic=?", (topic,))
db.update("topics", "name", topic, status="neu")
@@ -174,25 +188,31 @@ async def _lauf(topic: str, run_id: int) -> None:
note, grund = await auto_loop.auto_repair_loop(name, erst_note, reparieren)
log.info("%s/%s: Note %.1f (%s)", topic, name, note, grund)
# Fail-closed-Gate vor dem Ebenen-Marker (nur wo das Modul eins hat):
# ein RuntimeError landet im Exception-Handler → Run 'failed', der
# Ebenen-Marker wird NICHT gesetzt, die nächste Ebene startet nie.
gate = getattr(modul, "gate", None)
if gate and (grund_gate := gate(ctx)):
raise RuntimeError(f"{name}-Gate: {grund_gate}")
db.update("topics", "name", topic, status=marke)
flags = db.uj(db.one("SELECT auto FROM topics WHERE name=?", (topic,))["auto"], {})
if not flags.get(name, True) and marke != "fertig":
db.update("runs", "id", run_id, status="done", beendet="jetzt",
db.update("runs", "id", run_id, status="done", beendet=db.now(),
grund=f"Auto-Stopp nach {name}")
return
db.update("runs", "id", run_id, status="done", beendet="jetzt")
db.update("runs", "id", run_id, status="done", beendet=db.now())
except llm.LaufPause as e:
agents.abbrechen(f"{topic}-") # Nachzügler-Tasks dürfen keine Tokens mehr ziehen
db.update("runs", "id", run_id, status="paused", grund=str(e)[:300], beendet="jetzt")
db.update("runs", "id", run_id, status="paused", grund=str(e)[:300], beendet=db.now())
log.warning("%s: Lauf pausiert — %s", topic, e)
except ledger.BudgetErschoepft as e:
agents.abbrechen(f"{topic}-")
db.update("runs", "id", run_id, status="budget", grund=str(e)[:300], beendet="jetzt")
db.update("runs", "id", run_id, status="budget", grund=str(e)[:300], beendet=db.now())
log.warning("%s: Budget erreicht", topic)
except asyncio.CancelledError:
raise
except Exception as e:
agents.abbrechen(f"{topic}-")
db.update("runs", "id", run_id, status="failed", grund=f"{type(e).__name__}: {e}"[:300],
beendet="jetzt")
beendet=db.now())
log.exception("%s: Lauf fehlgeschlagen", topic)