This commit is contained in:
team3
2026-07-23 16:07:36 +02:00
commit cb68cd671b
88 changed files with 10029 additions and 0 deletions

73
backend/belege.py Normal file
View File

@@ -0,0 +1,73 @@
"""Beleg-Fenster um Anker (300/+1200): Faktenbasis für Writer und Prüfer.
Überlappende Fenster derselben Quelle werden gemerged (creator2-Muster).
Außerdem: Facetten-Abdeckung — jede Soll-BELEG-Stelle braucht ein Atom in
der Nähe (das feine 100-%-Maß, Gutachten-Lücke „Namensherkunft")."""
from . import config, db, laden, textkit
def beleg_stellen(topic: str) -> list[dict]:
"""Alle auffindbaren Beleg-Stellen bestätigter Soll-Punkte."""
stellen = []
for s in db.query("SELECT id, belege FROM soll WHERE topic=? AND "
"status='bestaetigt' AND freispruch=''", topic):
for beleg in db.uj(s["belege"], []):
quelle = db.one("SELECT * FROM quellen WHERE id=? AND "
"status='geladen'", beleg["quelle"])
if not quelle:
continue
try:
text = laden.snapshot_lesen(quelle)
except OSError:
continue
pos = textkit.finde_zitat(text, beleg["zitat"])
if pos:
stellen.append({"soll_id": s["id"], "quelle_id": quelle["id"],
"start": pos[0], "ende": pos[1],
"zitat": beleg["zitat"][:120]})
return stellen
def unbedeckte_belege(topic: str) -> list[dict]:
"""Beleg-Stellen OHNE aktives Atom im ±EXTRAKT_FENSTER derselben Quelle."""
anker = {}
for a in db.query(
"SELECT an.quelle_id q, an.start s FROM anker an JOIN atome a ON "
"a.id=an.atom_id WHERE a.topic=? AND a.status='aktiv' AND "
"an.start>=0", topic):
anker.setdefault(a["q"], []).append(a["s"])
return [st for st in beleg_stellen(topic)
if not any(abs(s - st["start"]) <= config.EXTRAKT_FENSTER
for s in anker.get(st["quelle_id"], []))]
def abdeckung_prozent(topic: str) -> float | None:
stellen = beleg_stellen(topic)
if not stellen:
return None
frei = len(unbedeckte_belege(topic))
return round((len(stellen) - frei) / len(stellen) * 100, 1)
def fenster_fuer_atome(atom_ids: list[int]) -> str:
spans: dict[int, list[list[int]]] = {}
for aid in atom_ids:
for a in db.query("SELECT * FROM anker WHERE atom_id=? AND start>=0", aid):
von = max(0, a["start"] - config.BELEG_FENSTER_VOR)
bis = a["ende"] + config.BELEG_FENSTER_NACH
spans.setdefault(a["quelle_id"], []).append([von, bis])
teile = []
for qid, fenster in spans.items():
quelle = db.one("SELECT * FROM quellen WHERE id=?", qid)
if not quelle or quelle["status"] != "geladen":
continue
text = laden.snapshot_lesen(quelle)
fenster.sort()
gemerged = [fenster[0]]
for von, bis in fenster[1:]:
if von <= gemerged[-1][1]:
gemerged[-1][1] = max(gemerged[-1][1], bis)
else:
gemerged.append([von, bis])
for von, bis in gemerged:
teile.append(f"[Quelle: {quelle['titel'][:60]}]\n{text[von:bis]}")
return "\n\n---\n\n".join(teile)