This commit is contained in:
team3
2026-07-10 15:43:11 +02:00
commit 0a41166cca
72 changed files with 7772 additions and 0 deletions

94
tests/test_inventar.py Normal file
View File

@@ -0,0 +1,94 @@
"""Anker-Repair: unverankerte Zitate (start=-1) werden erst deterministisch
neu gematcht — ohne LLM-Call."""
import db
import inventar
import llm
from conftest import run_anlegen, topic_anlegen
def test_merge_kollision_und_kette():
topic = topic_anlegen("mergekante")
a = db.insert("atome", topic=topic, titel="A", typ="begriff", definition="lang genug",
status="neu", braucht=db.j([]))
b = db.insert("atome", topic=topic, titel="B", typ="begriff", definition="kurz",
status="neu", braucht=db.j([]))
c = db.insert("atome", topic=topic, titel="C", typ="begriff", definition="x",
status="neu", braucht=db.j([]))
# beide haben dieselbe Kante zu C → blindes Umhängen würde UNIQUE verletzen
db.execute("INSERT INTO kanten(topic, von_atom, zu_atom, art) VALUES(?,?,?,'braucht')",
(topic, a, c))
db.execute("INSERT INTO kanten(topic, von_atom, zu_atom, art) VALUES(?,?,?,'braucht')",
(topic, b, c))
# und eine Kante zwischen den Merge-Partnern → würde Selbstkante
db.execute("INSERT INTO kanten(topic, von_atom, zu_atom, art) VALUES(?,?,?,'braucht')",
(topic, b, a))
inventar._merge(topic, a, b)
kanten = db.query("SELECT * FROM kanten WHERE topic=?", (topic,))
assert len(kanten) == 1 and kanten[0]["von_atom"] == a and kanten[0]["zu_atom"] == c
# Merge-Kette: (B, C) — B ist schon gemerged, Wurzel A übernimmt
inventar._merge(topic, b, c)
assert db.one("SELECT merged_into FROM atome WHERE id=?", (c,))["merged_into"] == a
async def test_anker_rematch_ohne_llm(tmp_path):
topic = topic_anlegen("rematch")
run = run_anlegen(topic)
snap = tmp_path / "q.md"
snap.write_text("Vorher. Der Satz steht hier drin. Nachher.", encoding="utf-8")
q = db.insert("quellen", topic=topic, art="datei", titel="q",
snapshot=str(snap), hash="h", status="atome")
a = db.insert("atome", topic=topic, titel="T", typ="begriff", definition="d",
status="ohne_anker", braucht=db.j([]))
# Extraktion fand das Zitat nicht (Whitespace-Differenz), hat es aber gespeichert
db.insert("anker", atom_id=a, quelle_id=q, start=-1, ende=-1,
zitat="Der Satz steht hier drin.")
ctx = llm.Kontext(run, topic, "minimax")
ctx.ebene = "inventar"
assert await inventar._anker_fixen_batch(ctx, [a]) is True
row = db.one("SELECT * FROM anker WHERE atom_id=? AND start>=0", (a,))
assert row is not None
assert db.one("SELECT status FROM atome WHERE id=?", (a,))["status"] == "neu"
assert db.query("SELECT id FROM events WHERE run_id=?", (run,)) == [] # kein LLM-Call
assert inventar.messen(ctx) == [] or all(
b["art"] != "atom_ohne_anker" for b in inventar.messen(ctx))
def test_titel_kern_faltet_schreibvarianten():
import textkit
assert textkit.titel_kern("ΔTSP2 TourLänge") == textkit.titel_kern("ΔTSP2 Tour-Länge")
assert textkit.titel_kern("FeedbackVertexSet ∈ NP") == textkit.titel_kern("Feedback Vertex Set ∈ NP")
assert textkit.titel_kern("!!!") == ""
def test_fallback_nutzt_jaccard_schwelle(monkeypatch):
import embedding
a = "Polynomialzeitreduktionen sind transitiv wenn L1 auf L2 und L2 auf L3 reduzierbar sind"
b = ("Polynomialzeitreduktionen sind transitiv wenn eine Sprache L1 auf L2"
" und L2 auf L3 in Polynomialzeit reduzierbar ist")
# Kosinus-Schwelle allein: Paraphrase rutscht durch (aak Lauf 8)
assert embedding.kandidaten_paare([a, b], 0.75) == []
paare = embedding.kandidaten_paare([a, b], 0.75, 0.3)
assert [(i, j) for i, j, _ in paare] == [(0, 1)]
async def test_titel_dublette_wird_gemerged_trotz_ferner_definition():
topic = topic_anlegen("titeldup")
run = run_anlegen(topic)
# identischer Titel, Definitionen lexikalisch fern (Jaccard < 0.3):
# nur der Titel-Kern-Generator bringt das Paar ans Panel
a = db.insert("atome", topic=topic, titel="VERTEX COVER Problem", typ="begriff",
definition="Entscheidungsproblem, ob ein Graph ein Vertex Cover"
" der Größe höchstens k enthält.",
status="neu", braucht=db.j([]))
b = db.insert("atome", topic=topic, titel="VERTEX COVER Problem", typ="begriff",
definition="Gefragt wird nach einer Knotenmenge, die jede Kante"
" abdeckt und maximal k Elemente hat.",
status="neu", braucht=db.j([]))
ctx = llm.Kontext(run, topic, "minimax")
ctx.ebene = "inventar"
await inventar._judge_dedup(ctx)
stati = {r["id"]: r["status"] for r in db.query(
"SELECT id, status FROM atome WHERE topic=?", (topic,))}
assert sorted(stati.values()) == ["gemerged", "neu"]