Files
creator2/backend/belege.py
2026-07-13 05:18:55 +02:00

63 lines
2.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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]