107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
"""Guide-QA: Fehler-Injektion auf Mini-Guide-Karten — deterministisch, ohne LLM."""
|
||
|
||
import guide_qa as gq
|
||
import qa
|
||
|
||
|
||
def _card(block, md):
|
||
return {"block": block, "block_norm": block.casefold(), "md": md}
|
||
|
||
|
||
AUSF = ("<!-- section: Alpha -->\n<!-- compact -->\n- m\n<!-- ausführlich -->\n"
|
||
"Einstieg in den Block.\n"
|
||
"<!-- sub: beginner | Kantenzug Definition -->\n"
|
||
"Ein Kantenzug verbindet Knoten über Kanten im Graphen.\n")
|
||
|
||
|
||
def test_ausfuehrlich_extrahiert_lerntext():
|
||
assert gq._ausfuehrlich(AUSF).startswith("\nEinstieg")
|
||
assert gq._ausfuehrlich("nur text") == "nur text"
|
||
|
||
|
||
def test_marker_fehlend():
|
||
cards = [_card("Alpha", AUSF)]
|
||
rel = {"alpha": {"kantenzug definition", "fehlender aspekt"}}
|
||
out = gq.marker_fehlend(cards, rel)
|
||
assert out == ["Alpha · fehlender aspekt"]
|
||
|
||
|
||
def test_ziel_ohne_anker():
|
||
cards = [_card("Alpha", AUSF)]
|
||
ziele = [{"block_norm": "alpha", "ziel_id": "z1", "text": "Kantenzug im Graphen erklären"},
|
||
{"block_norm": "alpha", "ziel_id": "z2", "text": "Adjazenzmatrix aufstellen können"}]
|
||
out = gq.ziel_ohne_anker(cards, ziele)
|
||
assert len(out) == 1 and "z2" in out[0]
|
||
|
||
|
||
def test_laengen_ausreisser():
|
||
duenn = _card("Alpha", "<!-- ausführlich -->\nkurz")
|
||
ok = _card("Beta", "<!-- ausführlich -->\n" + "x" * 500)
|
||
out = gq.laengen_ausreisser([duenn, ok], {"alpha": {"s1"}, "beta": {"s1"}})
|
||
assert [x["block"] for x in out] == ["Alpha"]
|
||
|
||
|
||
def test_redundanz_findet_absatz_doppel():
|
||
a = "Der Kantenzug verbindet Knoten über mehrere Kanten und darf Knoten wiederholen. " * 3
|
||
b = "Der Kantenzug verbindet Knoten über mehrere Kanten und darf Knoten wiederholen, genau. " * 3
|
||
c = "Völlig anderes Thema: Matrizen, Determinanten und lineare Abbildungen im Vektorraum. " * 3
|
||
cards = [_card("Alpha", f"<!-- ausführlich -->\n{a}\n\n{c}"),
|
||
_card("Beta", f"<!-- ausführlich -->\n{b}")]
|
||
out = gq.redundanz(cards)
|
||
assert len(out) == 1 and out[0]["a"].startswith("Alpha")
|
||
|
||
|
||
def test_lesbarkeit_fail_open(monkeypatch):
|
||
def kaputt(md_by_num):
|
||
raise RuntimeError("Modell fehlt")
|
||
monkeypatch.setattr(gq.readability, "rate_sections", kaputt)
|
||
assert gq.lesbarkeit([_card("Alpha", AUSF)]) == []
|
||
|
||
|
||
def test_note_guide_kalibrierung():
|
||
"""Gewicht = Punktabzug bei 100 %: 10 % fachlich falsch × 3.0 → 7.0; ungemessen zählt nicht."""
|
||
assert qa.note({"fachlich_falsch": 0.1}, gq.NOTE_GEWICHTE_GUIDE) == 7.0
|
||
ohne = {"marker_fehlend": 0.0, "ziel_ohne_anker": 0.0}
|
||
assert qa.note(ohne, gq.NOTE_GEWICHTE_GUIDE) == 10.0
|
||
|
||
|
||
def test_marker_escaping_tolerant():
|
||
"""Escapte Titel (h\\~2\\~o) sind kein „Marker fehlt" — Writer und DB escapen verschieden."""
|
||
md = "<!-- ausführlich -->\n<!-- sub: beginner | ^ und ~ müssen escaped werden (h\\\\~2\\\\~o) -->\nText."
|
||
cards = [{"block": "Hoch", "block_norm": "hoch", "md": md}]
|
||
rel = {"hoch": {gq._norm_title("^ und ~ müssen escaped werden (h~2~o)")}}
|
||
assert gq.marker_fehlend(cards, rel) == []
|
||
|
||
|
||
async def test_fachlich_falsch_braucht_beide_bestaetiger(monkeypatch):
|
||
"""Befund zählt nur, wenn BEIDE Bestätiger zustimmen — Einzel-/Zweifach-Urteile
|
||
ließen die Note desselben Guides zwischen 2.0 und 6.6 springen."""
|
||
import agents
|
||
calls = {"n": 0}
|
||
|
||
async def fake_agent(key, prompt, timeout, **kw):
|
||
calls["n"] += 1
|
||
if "-fakten-3-" in key: # Bestätiger 2: nur #1 bleibt
|
||
return 0, '{"relevant": {"1": "ja"}}', ""
|
||
if "-fakten-2-" in key: # Bestätiger 1: #1 und #2
|
||
return 0, '{"relevant": {"1": "ja", "2": "ja"}}', ""
|
||
return 0, '{"relevant": {"1": "ja", "2": "ja", "3": "nein"}}', "" # Pass 1
|
||
|
||
monkeypatch.setattr(agents, "run_agent", fake_agent)
|
||
cards = [_card("Alpha", AUSF), _card("Beta", AUSF), _card("Gamma", AUSF)]
|
||
out = await gq._fachlich_falsch("t", cards)
|
||
assert out == ["Alpha"] and calls["n"] == 3
|
||
|
||
|
||
async def test_fachlich_falsch_ohne_verdacht_kein_zweiter_pass(monkeypatch):
|
||
import agents
|
||
calls = {"n": 0}
|
||
|
||
async def fake_agent(key, prompt, timeout, **kw):
|
||
calls["n"] += 1
|
||
return 0, '{"relevant": {"1": "nein", "2": "nein"}}', ""
|
||
|
||
monkeypatch.setattr(agents, "run_agent", fake_agent)
|
||
out = await gq._fachlich_falsch("t", [_card("Alpha", AUSF), _card("Beta", AUSF)])
|
||
assert out == [] and calls["n"] == 1
|