update
This commit is contained in:
@@ -55,6 +55,99 @@ async def test_anker_rematch_ohne_llm(tmp_path):
|
||||
b["art"] != "atom_ohne_anker" for b in inventar.messen(ctx))
|
||||
|
||||
|
||||
async def test_resume_liest_teilextrahierte_quelle_weiter(tmp_path):
|
||||
"""Abbruch mitten in der Extraktion (atome_stand leer): der Resume liest die
|
||||
Quelle komplett neu, statt sie als fertig zu überspringen."""
|
||||
topic = topic_anlegen("resume-ext")
|
||||
run = run_anlegen(topic)
|
||||
snap = tmp_path / "q.md"
|
||||
snap.write_text("Absatz eins über Kompaktheit.\n\nAbsatz zwei über Vollständigkeit.",
|
||||
encoding="utf-8")
|
||||
# Quelle hat schon EIN verankertes Atom, aber atome_stand ist leer (Abbruch)
|
||||
q = db.insert("quellen", topic=topic, art="datei", titel="q",
|
||||
snapshot=str(snap), hash="h", status="extrahiert", atome_stand="")
|
||||
a = db.insert("atome", topic=topic, titel="Alt", typ="begriff", definition="d",
|
||||
status="neu", braucht=db.j([]))
|
||||
db.insert("anker", atom_id=a, quelle_id=q, start=0, ende=5, zitat="Absatz")
|
||||
ctx = llm.Kontext(run, topic, "minimax")
|
||||
ctx.ebene = "inventar"
|
||||
await inventar._extrahiere_quelle(ctx, db.one("SELECT * FROM quellen WHERE id=?", (q,)))
|
||||
# neu gelesen → Extraktions-Events da, atome_stand jetzt gesetzt
|
||||
assert db.query("SELECT id FROM events WHERE run_id=? AND stage='extraktion'", (run,))
|
||||
assert db.one("SELECT atome_stand FROM quellen WHERE id=?", (q,))["atome_stand"] == "atome"
|
||||
|
||||
# Gegenprobe: mit gesetztem Merker wird NICHT neu gelesen
|
||||
run2 = run_anlegen(topic)
|
||||
ctx2 = llm.Kontext(run2, topic, "minimax")
|
||||
ctx2.ebene = "inventar"
|
||||
await inventar._extrahiere_quelle(ctx2, db.one("SELECT * FROM quellen WHERE id=?", (q,)))
|
||||
assert db.query("SELECT id FROM events WHERE run_id=?", (run2,)) == []
|
||||
|
||||
|
||||
async def test_stichentscheid_braucht_zwei_stimmen(monkeypatch):
|
||||
"""Verwerfen (destruktiv) nur bei vollzähligem einstimmigem „fremd"-Panel."""
|
||||
topic = topic_anlegen("stich")
|
||||
run = run_anlegen(topic)
|
||||
db.insert("soll", topic=topic, punkt="P1", status="bestaetigt", belege=db.j([]))
|
||||
ctx = llm.Kontext(run, topic, "minimax")
|
||||
ctx.ebene = "inventar"
|
||||
|
||||
def neu_atom():
|
||||
return db.insert("atome", topic=topic, titel="X", typ="begriff",
|
||||
definition="d", status="neu", braucht=db.j([]))
|
||||
|
||||
def panel_mit(votes):
|
||||
async def _p(ctx, groesse, **kw):
|
||||
return list(votes)
|
||||
return _p
|
||||
|
||||
a1 = neu_atom() # 1× fremd → nicht vollzählig → Atom bleibt
|
||||
monkeypatch.setattr(llm, "panel", panel_mit([{"soll": "fremd"}]))
|
||||
await inventar._soll_stichentscheid(ctx, a1)
|
||||
assert db.one("SELECT status FROM atome WHERE id=?", (a1,))["status"] == "neu"
|
||||
|
||||
a2 = neu_atom() # 2× fremd → verworfen
|
||||
monkeypatch.setattr(llm, "panel", panel_mit([{"soll": "fremd"}, {"soll": "fremd"}]))
|
||||
await inventar._soll_stichentscheid(ctx, a2)
|
||||
assert db.one("SELECT status FROM atome WHERE id=?", (a2,))["status"] == "verworfen"
|
||||
|
||||
a3 = neu_atom() # 2× ohne soll-Feld → kein fremd-Votum → bleibt
|
||||
monkeypatch.setattr(llm, "panel", panel_mit([{}, {}]))
|
||||
await inventar._soll_stichentscheid(ctx, a3)
|
||||
assert db.one("SELECT status FROM atome WHERE id=?", (a3,))["status"] == "neu"
|
||||
|
||||
|
||||
async def test_anker_batch_ausfall_verwirft_nicht(monkeypatch, tmp_path):
|
||||
"""Call-Ausfall (None) ≠ „keine Quellstelle" → Atom bleibt ohne_anker, bewegt=False."""
|
||||
import fake_agents
|
||||
topic = topic_anlegen("ankerfail")
|
||||
run = run_anlegen(topic)
|
||||
snap = tmp_path / "q.md"
|
||||
snap.write_text("Ganz anderer Text ohne die gesuchte Stelle.", 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([]))
|
||||
db.insert("anker", atom_id=a, quelle_id=q, start=-1, ende=-1,
|
||||
zitat="Ein Zitat das nirgends im Quelltext steht und lang genug ist.")
|
||||
monkeypatch.setitem(fake_agents._HANDLER, "Atom-Anker-Fix-Batch", lambda p: {})
|
||||
ctx = llm.Kontext(run, topic, "minimax")
|
||||
ctx.ebene = "inventar"
|
||||
bewegt = await inventar._anker_fixen_batch(ctx, [a])
|
||||
assert db.one("SELECT status FROM atome WHERE id=?", (a,))["status"] == "ohne_anker"
|
||||
assert bewegt is False
|
||||
|
||||
|
||||
def test_finde_zitat_casefold_sz_offsets():
|
||||
"""casefold-Stufe: ß→ss-Expansionen vor dem Treffer dürfen den Span nicht
|
||||
verschieben; Treffer am Textende darf nicht in einen IndexError laufen."""
|
||||
import textkit
|
||||
text = "Straße und Fußweg. " * 30 + "DER KERNSATZ STEHT AM ENDE."
|
||||
span = textkit.finde_zitat(text, "der kernsatz steht am ende.")
|
||||
assert span is not None
|
||||
assert text[span[0]:span[1]] == "DER KERNSATZ STEHT AM ENDE."
|
||||
|
||||
|
||||
def test_titel_kern_faltet_schreibvarianten():
|
||||
import textkit
|
||||
assert textkit.titel_kern("ΔTSP2 Tour‑Länge") == textkit.titel_kern("ΔTSP2 Tour-Länge")
|
||||
|
||||
Reference in New Issue
Block a user