63 lines
2.4 KiB
Python
63 lines
2.4 KiB
Python
"""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-<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]
|