"""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 42–150 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-.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]