This commit is contained in:
team3
2026-07-13 05:18:55 +02:00
parent 7ee425f7fa
commit 8926f87185
19 changed files with 703 additions and 41 deletions

62
backend/belege.py Normal file
View File

@@ -0,0 +1,62 @@
"""Faktenbasis eines Atoms = Quelltext-FENSTER um seine Anker, nicht nur das Zitat.
Rollen-Trennung: Der Anker identifiziert die Stelle (kurz, verbatim, fuzzy-matchbar);
die Substanz eines Verfahrens (Pseudo-Code, Ablauf) steht in der Quelle DANEBEN.
Nur gegen das Zitat geprüft war jede inhaltliche Karte formal „unbelegt" — die
Top-Verworfen-Atome (GA 73×, LPT 33×) hatten 42150 Zeichen Beleg."""
from functools import lru_cache
import db
from config import BELEG_FENSTER_NACH, BELEG_FENSTER_VOR
@lru_cache(maxsize=64)
def _snapshot(pfad: str) -> str:
# Pfade sind hash-benannt (q-<hash>.md) — neuer Korpus = neuer Pfad, Cache safe
try:
return open(pfad, encoding="utf-8").read()
except OSError:
return ""
def _wortgrenzen(text: str, a: int, b: int) -> str:
aus = text[a:b]
if a > 0 and " " in aus[:80]:
aus = aus.split(" ", 1)[1]
if b < len(text) and " " in aus[-80:]:
aus = aus.rsplit(" ", 1)[0]
return aus.strip()
def fenster_liste(atom_id: int, max_fenster: int = 2) -> list[str]:
"""Beleg-Blöcke des Atoms: Quellumgebung je Anker (überlappende gemerged),
gedeckelt auf max_fenster. Anker ohne Span oder ohne lesbaren Snapshot
fallen auf ihr Zitat zurück — nie schlechter als vorher."""
anker = db.query(
"SELECT k.start, k.ende, k.zitat, q.snapshot FROM anker k"
" JOIN quellen q ON q.id=k.quelle_id WHERE k.atom_id=?", (atom_id,))
spans: dict[str, list[tuple[int, int]]] = {}
fallback: list[str] = []
for k in anker:
text = _snapshot(k["snapshot"]) if k["start"] >= 0 else ""
if not text:
fallback.append(k["zitat"])
continue
a = max(0, k["start"] - BELEG_FENSTER_VOR)
b = min(len(text), k["ende"] + BELEG_FENSTER_NACH)
spans.setdefault(k["snapshot"], []).append((a, b))
bloecke: list[str] = []
for snap, sp in spans.items():
text = _snapshot(snap)
sp.sort()
merged: list[list[int]] = []
for a, b in sp:
if merged and a <= merged[-1][1]:
merged[-1][1] = max(merged[-1][1], b)
else:
merged.append([a, b])
bloecke += [_wortgrenzen(text, a, b) for a, b in merged]
out = bloecke[:max_fenster]
if not out: # kein Span verwertbar → wenigstens die Zitate
out = fallback
return [b for b in out if b]