This commit is contained in:
team3
2026-07-08 23:46:35 +02:00
parent f9d77a113b
commit 2178c6faf4
15 changed files with 557 additions and 379 deletions

View File

@@ -621,6 +621,10 @@ def test_reference_strip_and_is_reference():
== "N P via nicht-deterministische Turingmaschine"
assert strip("Bemerkung 7.22") == ""
assert strip("Vertex Cover") == "Vertex Cover" # kein Katalog-Gerüst → unverändert
# Zuschreibungs-Klammer vor ':' gehört zum Katalog-Gerüst (aak: "(Lawler 1976): …" überlebte)
assert strip("Satz 7.12 (Lawler 1976): Minimum-Weight Perfect Matching") \
== "Minimum-Weight Perfect Matching"
assert strip("(a1=1)-SubsetSum Problem") == "(a1=1)-SubsetSum Problem" # Mathe-Klammer bleibt
assert isref("Bemerkung 7.22") and isref("Satz 7.18") and isref("Korollar 6.18")
assert isref("Bedingung (**)")
assert not isref("Satz 7.13 (Christofides)") # hat Konzept → keine Referenz
@@ -1577,3 +1581,62 @@ def test_gruppierung_prompt_rendert_zielband(tmp_path):
p = _prompt("Blocks-Gruppierung", topic="t", candidates="x", list="1. a",
out_path=tmp_path / "g.json", theme_lo=4, theme_hi=8, n_items=9)
assert "~48 themes" in p and "(9 items)" in p
async def test_attach_nachzuegler_wird_sub_statt_zwergblock(testdb, tmp_path, monkeypatch):
"""Nachzügler-Atom (Supplement-/Lücken-Welle) mit passendem fertigen Block →
wird dessen Sub (Bottom-up), Karte → grouped/attach, Board-2-Karte → generate.
Ohne Zuordnung bleibt das Atom standalone (Erstwelle: keine done_blocks → no-op)."""
db = testdb
ctx = GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False)
flow = _mk_flow(tmp_path)
# fertiger Block mit Board-2-Karte
await db.kanban_upsert_card(TOPIC, B, "b1", "block", "done_block",
{"title": "Turingmaschinen", "description": "Modelle und Akzeptanz",
"mirrored_norm": "turingmaschinen", "children": ["Palindrom-TM"]})
await db.kanban_upsert_card(TOPIC, "artefacts", "turingmaschinen", "ablock", "done_artefact",
{"title": "Turingmaschinen"})
rows = [{"card_id": "n1", "payload": {"title": "Halteproblem", "description": "hält die TM?"},
"title": "Halteproblem", "description": "hält die TM?", "title_norm": "halteproblem"}]
await db.kanban_upsert_card(TOPIC, B, "n1", "block", "grouping", rows[0]["payload"])
async def fake_slot(ctx2, label, *, key, prompt, role, capabilities, payload, timeout):
assert "-gruppierung-attach-" in key and "Turingmaschinen" in prompt
m = re.search(r"(/\S+\.json)", prompt) # files-Agent: out_path aus dem Prompt schreiben
from pathlib import Path
Path(m.group(1)).write_text('{"additions": [{"umbrella": 0, "members": [1]}]}', encoding="utf-8")
return bi.OK, payload((0, "", ""))
monkeypatch.setattr(bi, "run_single_slot", fake_slot)
attached = await bi._attach_nachzuegler(ctx, flow, rows)
assert attached == {"n1"}
subs = {r["sub_norm"] for r in await db.list_subblocks(TOPIC, "turingmaschinen")}
assert "halteproblem" in subs
card = await db.kanban_get_card(TOPIC, B, "n1")
assert card["stage"] == "grouped" and card["payload"]["reason"] == "attach"
assert card["payload"]["merged_into"] == "Turingmaschinen"
b2 = await db.kanban_get_card(TOPIC, "artefacts", "turingmaschinen")
assert b2["stage"] == "generate" # Re-Anreicherung des neuen Subs
block = await db.kanban_get_card(TOPIC, B, "b1")
assert "Halteproblem" in block["payload"]["children"]
async def test_attach_nachzuegler_ohne_zuordnung_bleibt_standalone(testdb, tmp_path, monkeypatch):
db = testdb
ctx = GenContext(topic=TOPIC, provider="claude", is_cancelled=lambda: False)
flow = _mk_flow(tmp_path)
await db.kanban_upsert_card(TOPIC, B, "b1", "block", "done_block",
{"title": "Turingmaschinen", "mirrored_norm": "turingmaschinen"})
rows = [{"card_id": "n2", "payload": {"title": "Ganz neues Konzept"},
"title": "Ganz neues Konzept", "description": "", "title_norm": "ganz neues konzept"}]
await db.kanban_upsert_card(TOPIC, B, "n2", "block", "grouping", rows[0]["payload"])
async def fake_slot(ctx2, label, **kw):
m = re.search(r"(/\S+\.json)", kw["prompt"])
from pathlib import Path
Path(m.group(1)).write_text('{"additions": []}', encoding="utf-8")
return bi.OK, kw["payload"]((0, "", ""))
monkeypatch.setattr(bi, "run_single_slot", fake_slot)
assert await bi._attach_nachzuegler(ctx, flow, rows) == set()
assert (await db.kanban_get_card(TOPIC, B, "n2"))["stage"] == "grouping"

View File

@@ -103,3 +103,13 @@ def test_fidelity_guard_prefers_faithful_plaintext():
assert blx._pick_conversion(None, plain)[1] == "pdftotext"
assert blx._pick_conversion(md_ok, None)[1] == "pymupdf4llm"
assert blx._pick_conversion(None, None) is None
def test_entzerre_hyphen_space():
"""Regel C: GROSSLAUF + Leerzeichen vor Bindestrich-Kompositum („NP -vollständig",
Makro-Spacing) wird gemerged; echter Gedankenstrich („X - y") bleibt."""
assert blx._entzerre_pdf_woerter("die NP -vollständigkeit gilt") == "die NP-vollständigkeit gilt"
assert blx._entzerre_pdf_woerter("Begriffe der NP -schwere und NP -vollständigkeit") == \
"Begriffe der NP-schwere und NP-vollständigkeit"
assert blx._entzerre_pdf_woerter("Term X - y bleibt") == "Term X - y bleibt"
assert blx._entzerre_pdf_woerter("Liste:\n-punkt eins") == "Liste:\n-punkt eins"

View File

@@ -333,3 +333,15 @@ def test_offene_luecken_filtert_freispruch_und_nein():
"konzept_luecken": ["Satz von Foo"]}
lk, kl = qa.offene_luecken(rep)
assert [x["vorschau"] for x in lk] == ["a"] and kl == ["Satz von Foo"]
def test_named_results_ueberspringt_umbrochene_saetze():
"""Doppelpunkt-Fang, der am Zeilenumbruch endet und klein weiterläuft, ist ein
umbrochener SATZ, kein Konzeptname — solche Phantom-Lücken kann kein Block ankern.
Echte Namen (auch \\n-terminiert mit großer Folgezeile) bleiben."""
text = ("Konsequenz von Satz 6.16: Wenn ein NP-vollständiges Entscheidungsproblem in P\n"
"liegt, dann sind alle NP Entscheidungsprobleme in P.\n\n"
"Satz 7.13 (Christofides). Beweis folgt.\n"
"Satz 6.24: Cook-Levin.\n"
"Satz 9.1: Vier-Farben-Satz\nDer Beweis nutzt Computer.\n")
assert sorted(qa._named_results({"f": text})) == ["Christofides", "Cook-Levin", "Vier-Farben-Satz"]