"""Ebene 3.5 Diagramme: LLM-Diagramme (Automat/Ablauf/Baum), Spec→Mermaid, Assembly.""" import db import diagramme import llm from conftest import run_anlegen, topic_anlegen def _baustein_mit_kette(topic, n=4): """Baustein mit n Atomen und einer braucht-Kette a0←a1←…←a(n-1) (n-1 Kanten).""" soll = db.insert("soll", topic=topic, punkt="P", status="bestaetigt", belege=db.j([])) ziel = db.insert("lernziele", topic=topic, text="Kann P", soll_id=soll, status="aktiv") b = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu") atome = [db.insert("atome", topic=topic, titel=f"Atom {i}", typ="begriff", definition="d", status="neu", baustein_id=b, ord=i, braucht=db.j([])) for i in range(n)] for i in range(1, n): # Atom i braucht Atom i-1 db.execute("INSERT INTO kanten(topic, von_atom, zu_atom, art) VALUES(?,?,?,'braucht')", (topic, atome[i], atome[i - 1])) return b, atome def test_spec_zu_mermaid_flowchart(): spec = {"typ": "flow", "knoten": [{"id": "1", "label": "A"}, {"id": "2", "label": 'B "x" [y]'}], "kanten": [{"von": "1", "zu": "2", "label": ""}]} out = diagramme._spec_zu_mermaid(spec) assert out.startswith("flowchart TD") assert 'n1["A"]' in out and "n1 --> n2" in out assert '"' not in out.split("\n", 1)[1].replace('["', "").replace('"]', "") # Label entschärft async def test_fake_judge_nein_keine_diagramme(): topic = topic_anlegen("diag-leer") b, _ = _baustein_mit_kette(topic, n=2) ctx = llm.Kontext(run_anlegen(topic), topic, "minimax") await diagramme.bauen(ctx) # Fake-Judge sagt noetig=False → nur 'kein'-Merkzeile assert db.query("SELECT id FROM diagramme WHERE topic=? AND status='verifiziert'", (topic,)) == [] assert db.one("SELECT status FROM diagramme WHERE baustein_id=?", (b,))["status"] == "kein" async def test_llm_diagramm_plan_spec_verify(monkeypatch): """LLM-Zweig: Judge sagt nötig → Spec → Mermaid → Grounding-Panel verifiziert.""" import fake_agents topic = topic_anlegen("diag-llm") # Baustein OHNE genug Kanten (kein DAG) → LLM-Zweig entscheidet soll = db.insert("soll", topic=topic, punkt="P", status="bestaetigt", belege=db.j([])) ziel = db.insert("lernziele", topic=topic, text="Kann P", soll_id=soll, status="aktiv") b = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="Automat", ord=0, status="neu") for i in range(3): db.insert("atome", topic=topic, titel=f"Zustand {i}", typ="begriff", definition="d", status="neu", baustein_id=b, ord=i, braucht=db.j([])) monkeypatch.setitem(fake_agents._HANDLER, "Diagramm-Plan", lambda p: {"noetig": True, "typ": "state"}) ctx = llm.Kontext(run_anlegen(topic), topic, "minimax") await diagramme.bauen(ctx) d = db.one("SELECT * FROM diagramme WHERE baustein_id=?", (b,)) assert d["status"] == "verifiziert" and d["quelle"] == "llm" and d["typ"] == "state" assert d["mermaid"].startswith("stateDiagram-v2") async def test_llm_diagramm_grounding_verwirft(monkeypatch): """Grounding-Panel einstimmig ok=false → Diagramm verworfen (fail-closed).""" import fake_agents topic = topic_anlegen("diag-verwirf") soll = db.insert("soll", topic=topic, punkt="P", status="bestaetigt", belege=db.j([])) ziel = db.insert("lernziele", topic=topic, text="Kann P", soll_id=soll, status="aktiv") b = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="X", ord=0, status="neu") for i in range(3): db.insert("atome", topic=topic, titel=f"K{i}", typ="begriff", definition="d", status="neu", baustein_id=b, ord=i, braucht=db.j([])) monkeypatch.setitem(fake_agents._HANDLER, "Diagramm-Plan", lambda p: {"noetig": True, "typ": "flow"}) monkeypatch.setitem(fake_agents._HANDLER, "Diagramm-Verify", lambda p: [{"ok": False, "mangel": "erfundener Knoten"}]) ctx = llm.Kontext(run_anlegen(topic), topic, "minimax") await diagramme.bauen(ctx) assert db.one("SELECT status FROM diagramme WHERE baustein_id=?", (b,))["status"] == "verworfen" async def test_marker_wird_zu_fence(): """Assembly: platzierter → ```mermaid-Fence.""" import guide topic = topic_anlegen("diag-place") b, _ = _baustein_mit_kette(topic, n=2) mermaid = "flowchart TD\n n1[\"A\"]\n n2[\"B\"]\n n1 --> n2" d = db.insert("diagramme", topic=topic, baustein_id=b, typ="flow", quelle="llm", spec=db.j({}), mermaid=mermaid, status="verifiziert") db.insert("sections", baustein_id=b, stage="done", text_kompakt="- k", text_lang=f"Text.\n\n\n\nMehr Text.") lang = guide.kapitel_struktur(topic)[0]["sections"][0]["lang"] assert "```mermaid" in lang and "flowchart TD" in lang assert "\n\nMehr.") lang = guide.kapitel_struktur(topic)[0]["sections"][0]["lang"] assert "\n\n" + "Wort " * 60) ctx = llm.Kontext(run_anlegen(topic), topic, "minimax") ctx.ebene = "guide" assert "diagramm_marker_tot" in {x["art"] for x in guide.messen(ctx)} async def test_marker_platzieren_deterministisch(): """Der Writer setzt keine Beispiel-Marker mehr — _marker_platzieren fügt sie hinter den Atom-Absatz ein (atom-genau).""" import guide topic = topic_anlegen("platz") b = db.insert("bausteine", topic=topic, ziel_id=1, titel="B", ord=0, status="neu") a = db.insert("atome", topic=topic, titel="X", typ="begriff", definition="d", status="neu", baustein_id=b, ord=0, braucht=db.j([])) art = db.insert("artefakte", atom_id=a, typ="beispiel", status="verifiziert", inhalt=db.j({"form": "tabelle", "tabelle": "| A | B |\n|---|---|\n| 1 | 2 |"})) lang = f"#### X\n\nErklärung von X.\n\nWeiterer Absatz zum Thema." out = guide._marker_platzieren(db.one("SELECT * FROM bausteine WHERE id=?", (b,)), lang) assert f"" in out # der Marker steht NACH dem Atom-Absatz assert out.index("