update
This commit is contained in:
@@ -178,7 +178,7 @@ def test_det_auftraege_blockquote_und_artefakt():
|
||||
b_id = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu")
|
||||
a = db.insert("atome", topic=topic, titel="A", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||
lang = (f"<!-- atom: {a} | A | E -->\n" + "Fließtext. " * 45
|
||||
lang = (f"<!-- atom: {a} | A -->\n" + "Fließtext. " * 45
|
||||
+ "\n> wörtliches Rohzitat aus der Quelle\nEs gilt u 6= v."
|
||||
+ "\n\nDas Blank-Symbol $[. ist speziell und liegt in $[ \\in \\Gamma$.")
|
||||
kompakt = "- Definition mit x_i und Laufzeit 2^(n/2) als Klartext."
|
||||
@@ -189,6 +189,62 @@ def test_det_auftraege_blockquote_und_artefakt():
|
||||
assert "Kompakt-Fassung" in text and "x_i" in text # Klartext-Formeln gefangen
|
||||
|
||||
|
||||
def test_det_auftraege_neue_checks():
|
||||
import db
|
||||
import guide
|
||||
from conftest import topic_anlegen
|
||||
topic = topic_anlegen("detcheck2")
|
||||
ziel = db.insert("lernziele", topic=topic, text="Kann X", status="aktiv")
|
||||
b_id = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu")
|
||||
a = db.insert("atome", topic=topic, titel="A", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||
wand = "Wort " * 130
|
||||
lang = (f"<!-- atom: {a} | A -->\n<!-- atom: 99999 | Fremd -->\n"
|
||||
"Der Beweis folgt aus Satz 6.25, dazu später mehr.\n\n"
|
||||
+ wand + "\n\n#### Leere Überschrift\n\n#### Noch eine\nText dahinter.\n\n"
|
||||
"The proof is left as an exercise for the reader and this paragraph"
|
||||
" should be detected by the language check here.")
|
||||
kompakt = f"<!-- atom: {a} | A -->\n- Punkt"
|
||||
text = " ".join(guide._det_auftraege(topic, {"id": b_id, "ord": 0}, lang, kompakt))
|
||||
assert "gehört nicht zu diesem Baustein" in text # marker_fremd
|
||||
assert "Vertröstungs-Floskel" in text # „später mehr"
|
||||
assert "Quellen-Referenz" in text # Satz 6.25
|
||||
assert "Wörter (Regel: 40–90)" in text # Absatz-Wand
|
||||
assert "ohne folgenden Text" in text # leere Überschrift
|
||||
assert "englische Passage" in text
|
||||
assert "Marker gehören nur" in text # Marker in Kompakt-Fassung
|
||||
# qa=True: Marker/Länge/Vorwärts haben eigene Befund-Arten, Stil-Checks bleiben
|
||||
qa_text = " ".join(guide._det_auftraege(topic, {"id": b_id, "ord": 0},
|
||||
lang, kompakt, qa=True))
|
||||
assert "gehört nicht zu diesem Baustein" not in qa_text
|
||||
assert "Vertröstungs-Floskel" in qa_text
|
||||
|
||||
|
||||
async def test_fix_fehlschlag_behaelt_befunde(monkeypatch):
|
||||
import db
|
||||
import fake_agents
|
||||
import guide
|
||||
import llm
|
||||
from conftest import run_anlegen, topic_anlegen
|
||||
topic = topic_anlegen("fixfail")
|
||||
run = run_anlegen(topic)
|
||||
ziel = db.insert("lernziele", topic=topic, text="Kann X", status="aktiv")
|
||||
b_id = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu")
|
||||
db.insert("atome", topic=topic, titel="A", typ="begriff", definition="d",
|
||||
status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||
db.insert("sections", baustein_id=b_id, stage="fix",
|
||||
text_lang="alt", text_kompakt="", befunde=db.j(["Behebe (x): y"]))
|
||||
# Fix liefert Text OHNE Marker → Marker-Invariante bricht → Fehlschlag-Pfad
|
||||
monkeypatch.setitem(fake_agents._HANDLER, "Guide-Fix",
|
||||
lambda p: {"kompakt": "", "lang": "kein Marker"})
|
||||
ctx = llm.Kontext(run, topic, "minimax")
|
||||
ctx.ebene = "guide"
|
||||
stage = await guide._stage_fix(ctx, db.one("SELECT * FROM bausteine WHERE id=?", (b_id,)))
|
||||
sec = db.one("SELECT * FROM sections WHERE baustein_id=?", (b_id,))
|
||||
assert stage == "done" and sec["text_lang"] == "alt"
|
||||
assert db.uj(sec["befunde"]) == ["Behebe (x): y"] # Aufträge bleiben sichtbar
|
||||
|
||||
|
||||
def test_katex_gate_und_markdown_hygiene(monkeypatch):
|
||||
import guide
|
||||
monkeypatch.delenv("CREATOR_FAKE_AGENTS", raising=False) # Gate echt laufen lassen
|
||||
|
||||
@@ -36,6 +36,7 @@ def _pruefe_endzustand(topic, run_id):
|
||||
md = guide.guide_markdown(topic)
|
||||
for a in atome:
|
||||
assert f"<!-- atom: {a['id']} |" in md
|
||||
assert "| E -->" not in md and "| M -->" not in md # Marker tragen kein Level mehr
|
||||
# Kapitel-Struktur: H2 = Kapitel (Struktur-Ebene, mit Intro), H3 = Baustein
|
||||
assert "\n## " in "\n" + md and "### " in md
|
||||
assert "**Kernpunkte:**" in md and "**Prüfe dich:**" in md
|
||||
@@ -48,6 +49,17 @@ def _pruefe_endzustand(topic, run_id):
|
||||
folge = [b["kapitel_id"] for b in bausteine]
|
||||
segmente = [k for k, _ in itertools.groupby(folge)]
|
||||
assert len(segmente) == len(set(segmente)) == len(kaps) # kontiguierlich, lückenlos
|
||||
# Stufen-Bausteine: jeder Baustein trägt genau ein Level, alle seine Atome
|
||||
# dieses Level; Kapitel-Level monoton E→M→S entlang der globalen ord
|
||||
rang = {"E": 0, "M": 1, "S": 2}
|
||||
kap_level = {k["id"]: k["level"] for k in kaps}
|
||||
for b in bausteine:
|
||||
assert kap_level[b["kapitel_id"]] == b["level"]
|
||||
for a in atome:
|
||||
if a["baustein_id"] == b["id"]:
|
||||
assert a["level"] == b["level"], (a["titel"], a["level"], b["level"])
|
||||
level_folge = [rang[b["level"]] for b in bausteine]
|
||||
assert level_folge == sorted(level_folge), "Durchgänge nicht E<M<S geordnet"
|
||||
for b in db.query("SELECT titel FROM bausteine WHERE topic=?", (topic,)):
|
||||
assert not b["titel"].startswith("Kann"), b["titel"] # Kurztitel, kein Ziel-Satz
|
||||
events = db.query("SELECT * FROM events WHERE run_id=?", (run_id,))
|
||||
@@ -58,9 +70,9 @@ async def test_e2e_thema():
|
||||
topic = topic_anlegen("fake-thema", art="thema")
|
||||
run_id = await _lauf_komplett(topic)
|
||||
_pruefe_endzustand(topic, run_id)
|
||||
# zwei kleine Ziele → Merge über Soll-Grenzen (Entkopplung): EIN Baustein
|
||||
# Fake-Welt: 3 Atome mit Level E/M/S → je Level ein eigener Baustein-Durchgang
|
||||
bausteine = db.query("SELECT * FROM bausteine WHERE topic=? ORDER BY ord", (topic,))
|
||||
assert len(bausteine) == 1
|
||||
assert [b["level"] for b in bausteine] == ["E", "M", "S"]
|
||||
|
||||
|
||||
async def test_e2e_uni(tmp_path):
|
||||
|
||||
@@ -132,11 +132,10 @@ async def test_level_kalibrierung(monkeypatch):
|
||||
topic = topic_anlegen("level")
|
||||
run = run_anlegen(topic)
|
||||
ziel = db.insert("lernziele", topic=topic, text="Kann X", status="aktiv")
|
||||
b_id = db.insert("bausteine", topic=topic, ziel_id=ziel, titel="B", ord=0, status="neu")
|
||||
a1 = db.insert("atome", topic=topic, titel="Kern", typ="begriff", definition="d",
|
||||
level="M", status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||
level="M", status="neu", ziel_id=ziel, braucht=db.j([]))
|
||||
a2 = db.insert("atome", topic=topic, titel="Detail", typ="aussage", definition="d",
|
||||
level="M", status="neu", baustein_id=b_id, braucht=db.j([]))
|
||||
level="M", status="neu", ziel_id=ziel, braucht=db.j([]))
|
||||
|
||||
def judge(prompt):
|
||||
return [{"atom": a1, "level": "E"}, {"atom": a2, "level": "S"},
|
||||
@@ -147,3 +146,67 @@ async def test_level_kalibrierung(monkeypatch):
|
||||
await struktur._level_kalibrieren(ctx)
|
||||
assert db.one("SELECT level FROM atome WHERE id=?", (a1,))["level"] == "E"
|
||||
assert db.one("SELECT level FROM atome WHERE id=?", (a2,))["level"] == "S"
|
||||
|
||||
|
||||
def test_level_konflikt_absenkung():
|
||||
# v(E) braucht z(S), z braucht w(M) → Kaskade senkt z UND w auf E
|
||||
topic = topic_anlegen("konflikt")
|
||||
run = run_anlegen(topic)
|
||||
ziel = db.insert("lernziele", topic=topic, text="Kann X", status="aktiv")
|
||||
def atom(titel, level):
|
||||
return db.insert("atome", topic=topic, titel=titel, typ="begriff",
|
||||
definition="d", level=level, status="neu", ziel_id=ziel,
|
||||
braucht=db.j([]))
|
||||
v, z, w = atom("V", "E"), atom("Z", "S"), atom("W", "M")
|
||||
for von, zu in ((v, z), (z, w)):
|
||||
db.execute("INSERT INTO kanten(topic, von_atom, zu_atom, art)"
|
||||
" VALUES(?,?,?,'braucht')", (topic, von, zu))
|
||||
struktur._level_konflikte_loesen(topic)
|
||||
assert db.one("SELECT level FROM atome WHERE id=?", (z,))["level"] == "E"
|
||||
assert db.one("SELECT level FROM atome WHERE id=?", (w,))["level"] == "E"
|
||||
ctx = llm.Kontext(run, topic, "minimax")
|
||||
assert not [b for b in struktur.messen(ctx) if b["art"] == "level_konflikt"]
|
||||
|
||||
|
||||
async def test_level_split_ordnung_kapitel():
|
||||
# Ein Ziel mit 4×E + 4×M → zwei Bausteine mit level, E-ord < M-ord,
|
||||
# Kapitel je Durchgang, kein level_mix/kapitel_level
|
||||
topic = topic_anlegen("split")
|
||||
run = run_anlegen(topic)
|
||||
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")
|
||||
for i in range(4):
|
||||
db.insert("atome", topic=topic, titel=f"E{i}", typ="begriff", definition="d",
|
||||
level="E", status="neu", soll_id=soll, ziel_id=ziel, braucht=db.j([]))
|
||||
db.insert("atome", topic=topic, titel=f"M{i}", typ="begriff", definition="d",
|
||||
level="M", status="neu", soll_id=soll, ziel_id=ziel, braucht=db.j([]))
|
||||
ctx = llm.Kontext(run, topic, "minimax")
|
||||
ctx.ebene = "struktur"
|
||||
struktur._bausteine_schneiden(ctx)
|
||||
bausteine = db.query("SELECT * FROM bausteine WHERE topic=? ORDER BY ord", (topic,))
|
||||
assert [b["level"] for b in bausteine] == ["E", "M"]
|
||||
await struktur._kapitel_bilden(ctx)
|
||||
kaps = db.query("SELECT * FROM kapitel WHERE topic=? ORDER BY ord", (topic,))
|
||||
assert [k["level"] for k in kaps] == ["E", "M"]
|
||||
arten = {b["art"] for b in struktur.messen(ctx)}
|
||||
assert not arten & {"level_mix", "kapitel_level", "band", "partition"}
|
||||
|
||||
|
||||
def test_kein_merge_ueber_level():
|
||||
# kleine E-Gruppe darf NICHT mit kleiner M-Gruppe mergen
|
||||
topic = topic_anlegen("levelmerge")
|
||||
run = run_anlegen(topic)
|
||||
soll = db.insert("soll", topic=topic, punkt="P", status="bestaetigt", belege=db.j([]))
|
||||
z1 = db.insert("lernziele", topic=topic, text="Kann P1", soll_id=soll, status="aktiv")
|
||||
z2 = db.insert("lernziele", topic=topic, text="Kann P2", soll_id=soll, status="aktiv")
|
||||
for i in range(2):
|
||||
db.insert("atome", topic=topic, titel=f"E{i}", typ="begriff", definition="d",
|
||||
level="E", status="neu", soll_id=soll, ziel_id=z1, braucht=db.j([]))
|
||||
db.insert("atome", topic=topic, titel=f"M{i}", typ="begriff", definition="d",
|
||||
level="M", status="neu", soll_id=soll, ziel_id=z2, braucht=db.j([]))
|
||||
ctx = llm.Kontext(run, topic, "minimax")
|
||||
struktur._bausteine_schneiden(ctx)
|
||||
for b in db.query("SELECT * FROM bausteine WHERE topic=?", (topic,)):
|
||||
levels = {a["level"] for a in struktur._atome(topic)
|
||||
if a["baustein_id"] == b["id"]}
|
||||
assert levels == {b["level"]}
|
||||
|
||||
@@ -42,3 +42,10 @@ async def test_export_import_roundtrip():
|
||||
for b in db.query("SELECT * FROM bausteine WHERE topic=?", (topic,)):
|
||||
assert db.one("SELECT id FROM kapitel WHERE id=?", (b["kapitel_id"],))
|
||||
assert db.one("SELECT baustein_id FROM sections WHERE baustein_id=?", (b["id"],))
|
||||
# Marker-Remap: jede Marker-id im importierten Text existiert (aak: 421/421 tot)
|
||||
atom_ids = {a["id"] for a in db.query("SELECT id FROM atome WHERE topic=?", (topic,))}
|
||||
for s in db.query("SELECT s.* FROM sections s JOIN bausteine b ON b.id=s.baustein_id"
|
||||
" WHERE b.topic=?", (topic,)):
|
||||
for feld in ("text_lang", "text_kompakt"):
|
||||
for m in re.findall(r"<!--\s*atom:\s*(\d+)\s*\|", s[feld] or ""):
|
||||
assert int(m) in atom_ids, f"toter Marker {m}"
|
||||
|
||||
Reference in New Issue
Block a user