update
This commit is contained in:
@@ -55,16 +55,10 @@ _MARKER = re.compile(r"<!--\s*atom:\s*(\d+)\s*\|")
|
||||
# den Match und die Marker-Zeile bliebe in den Text-Checks stehen (Marker sind
|
||||
# einzeilig, also kein DOTALL). Muster spiegelt frontend/src/markdown.js.
|
||||
_MARKER_VOLL = re.compile(r"<!--\s*atom:.*?-->")
|
||||
_MARKER_DIAGRAMM = re.compile(r"<!--\s*diagramm:\s*(\d+)\s*-->")
|
||||
_MARKER_BEISPIEL = re.compile(r"<!--\s*beispiel:\s*(\d+)\s*-->")
|
||||
|
||||
# Kurzbeschreibung je Diagramm-Typ (Writer-Angebot)
|
||||
_DIAGRAMM_BESCHR = {"dependency": "Abhängigkeitsgraph der Begriffe dieses Bausteins",
|
||||
"state": "Zustandsdiagramm/Automat", "flow": "Ablaufdiagramm",
|
||||
"tree": "Baumstruktur"}
|
||||
_BEISPIEL_BESCHR = {"code": "Code-Beispiel", "tabelle": "Tabelle/Vergleich",
|
||||
"diagramm": "Diagramm-Beispiel"}
|
||||
_VERBATIM_FORMEN = ("code", "tabelle", "diagramm")
|
||||
_BEISPIEL_BESCHR = {"code": "Code-Beispiel", "tabelle": "Tabelle/Vergleich"}
|
||||
_VERBATIM_FORMEN = ("code", "tabelle")
|
||||
|
||||
|
||||
def _ohne_marker(text: str) -> str:
|
||||
@@ -72,12 +66,12 @@ def _ohne_marker(text: str) -> str:
|
||||
ALLE Text-Checks (Stil, Mathe, Länge, Vorwärtsverweise) laufen auf dem
|
||||
gestrippten Text — sonst erzeugen Atom-TITEL wie „a_n" oder „Satz 6.26"
|
||||
unfixierbare Aufträge (der Fix darf Marker nie ändern → Endlos-Loop).
|
||||
Diagramm- und Beispiel-Marker gehören ebenso raus."""
|
||||
return _MARKER_BEISPIEL.sub("", _MARKER_DIAGRAMM.sub("", _MARKER_VOLL.sub("", text)))
|
||||
Beispiel-Marker gehören ebenso raus."""
|
||||
return _MARKER_BEISPIEL.sub("", _MARKER_VOLL.sub("", text))
|
||||
|
||||
|
||||
def _beispiel_verbatim(atom_id: int) -> list[tuple[int, dict]]:
|
||||
"""Verifizierte Beispiele des Atoms in VERBATIM-Form (code/tabelle/diagramm) —
|
||||
"""Verifizierte Beispiele des Atoms in VERBATIM-Form (code/tabelle) —
|
||||
(artefakt_id, inhalt). Woven-Formen (text/mathe) gehen über `_beispiel`."""
|
||||
out = []
|
||||
for r in db.query("SELECT id, inhalt FROM artefakte WHERE atom_id=? AND typ='beispiel'"
|
||||
@@ -89,23 +83,20 @@ def _beispiel_verbatim(atom_id: int) -> list[tuple[int, dict]]:
|
||||
|
||||
|
||||
def _beispiel_render(inh: dict) -> str:
|
||||
"""Verbatim-Beispiel → Markdown (Code-Fence / Tabelle / Mermaid-Fence)."""
|
||||
"""Verbatim-Beispiel → Markdown (Code-Fence / Tabelle)."""
|
||||
form = inh.get("form")
|
||||
if form == "code" and inh.get("code"):
|
||||
return f"```{inh.get('sprache', '')}\n{inh['code']}\n```"
|
||||
if form == "tabelle" and inh.get("tabelle"):
|
||||
return inh["tabelle"]
|
||||
if form == "diagramm" and inh.get("mermaid"):
|
||||
return f"```mermaid\n{inh['mermaid']}\n```"
|
||||
return ""
|
||||
|
||||
|
||||
def _marker_platzieren(b: dict, lang: str) -> str:
|
||||
"""Verbatim-Marker DETERMINISTISCH setzen (der Writer tut es nicht mehr — er
|
||||
ignorierte die Regel, 2/13 platziert). Beispiel-Marker hinter den Atom-Absatz
|
||||
(atom-genau via Atom-Marker); Diagramme (baustein-scoped) ans Section-Ende.
|
||||
Idempotent: alte Verbatim-Marker erst raus."""
|
||||
lang = _MARKER_BEISPIEL.sub("", _MARKER_DIAGRAMM.sub("", lang))
|
||||
(atom-genau via Atom-Marker). Idempotent: alte Verbatim-Marker erst raus."""
|
||||
lang = _MARKER_BEISPIEL.sub("", lang)
|
||||
je_atom = {}
|
||||
for a in _atome_von(b["id"]):
|
||||
vs = _beispiel_verbatim(a["id"])
|
||||
@@ -124,10 +115,6 @@ def _marker_platzieren(b: dict, lang: str) -> str:
|
||||
if pending is not None:
|
||||
out += ["", f"<!-- beispiel: {pending} -->"]
|
||||
lang = "\n".join(out)
|
||||
diags = _diagramme_von(b["id"])
|
||||
if diags:
|
||||
block = "\n".join(f"<!-- diagramm: {d['id']} -->" for d in diags)
|
||||
lang = lang.rstrip() + "\n\n" + block
|
||||
return lang
|
||||
|
||||
|
||||
@@ -145,24 +132,6 @@ def _beispiel_einsetzen(baustein_id: int, lang: str) -> str:
|
||||
return _MARKER_BEISPIEL.sub(sub, lang)
|
||||
|
||||
|
||||
def _diagramme_von(baustein_id: int) -> list[dict]:
|
||||
return db.query("SELECT * FROM diagramme WHERE baustein_id=? AND status='verifiziert'"
|
||||
" ORDER BY id", (baustein_id,))
|
||||
|
||||
|
||||
def _diagramm_einsetzen(baustein_id: int, lang: str) -> str:
|
||||
"""Assembly: `<!-- diagramm: id -->` → ```mermaid-Fence des verifizierten
|
||||
Diagramms. Tote/unverifizierte Marker fallen ersatzlos weg."""
|
||||
if "<!-- diagramm:" not in lang and "<!--diagramm:" not in lang:
|
||||
return lang
|
||||
je_id = {d["id"]: d for d in _diagramme_von(baustein_id)}
|
||||
|
||||
def sub(m: re.Match) -> str:
|
||||
d = je_id.get(int(m.group(1)))
|
||||
return f"```mermaid\n{d['mermaid']}\n```" if d and d["mermaid"] else ""
|
||||
|
||||
return _MARKER_DIAGRAMM.sub(sub, lang)
|
||||
|
||||
# Kritische Befund-Arten routen zurück auf den Prüfer, Stil-Arten nur in den Fix
|
||||
# (vorwaerts ist Stil: ein Halbsatz-Fix, kein Fakten-Problem)
|
||||
KRITISCH = ("marker_fehlend", "marker_fremd", "ziel_ohne_anker", "fachlich_falsch")
|
||||
@@ -185,7 +154,7 @@ def _zitate(atom_id: int, max_n: int = 3) -> list[str]:
|
||||
|
||||
def _beispiel(atom_id: int) -> str:
|
||||
"""Nur die WOVEN-Formen (text/mathe) als Prosa für den Writer. Verbatim-Formen
|
||||
(code/tabelle/diagramm) kommen über den `<!-- beispiel: id -->`-Marker rein."""
|
||||
(code/tabelle) kommen über den `<!-- beispiel: id -->`-Marker rein."""
|
||||
row = db.one("SELECT inhalt FROM artefakte WHERE atom_id=? AND typ='beispiel'"
|
||||
" AND status='verifiziert' LIMIT 1", (atom_id,))
|
||||
if not row:
|
||||
@@ -233,23 +202,35 @@ def _anknuepf_kontext(b: dict) -> str:
|
||||
|
||||
# ── Stages ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _writer_saeubern(s: str) -> str:
|
||||
"""Geleakte Writer-Delimiter/Modelltokens entfernen. Der Writer beendet seine
|
||||
Ausgabe mit `=====`; steht diese Zeile unter einem Bullet, ist sie eine
|
||||
Setext-H1-Unterstreichung → marked macht daraus ein riesiges <h1>. Reine
|
||||
`===…`-Zeilen und `<s>`/`</s>`-Tokens fallen daher ganz raus."""
|
||||
s = re.sub(r"</?s>", "", s or "")
|
||||
s = re.sub(r"(?m)^\s*={3,}\s*$", "", s)
|
||||
return s.strip()
|
||||
|
||||
|
||||
def _split_writer(text: str) -> tuple[str, str]:
|
||||
"""Delimited-Ausgabe des Writers/Fix parsen: ===LANG===… / ===KOMPAKT===….
|
||||
Grund: rohe LaTeX-Backslashes im Text sind invalides JSON (34 % Parse-Fehler).
|
||||
Robust: Blöcke unabhängig von der Reihenfolge; fehlt der Delimiter, jsonx-Fallback
|
||||
(alt-JSON), sonst ganzer Text = lang."""
|
||||
(alt-JSON), sonst ganzer Text = lang. Beide Blöcke werden von geleakten
|
||||
Terminatoren (`=====`/`</s>`) gesäubert."""
|
||||
def block(name: str) -> str | None:
|
||||
m = re.search(rf"===\s*{name}\s*===\s*(.*?)(?=\n===\s*[A-ZÄÖÜ]+\s*===|$)",
|
||||
text or "", re.S)
|
||||
return m.group(1).strip() if m else None
|
||||
return _writer_saeubern(m.group(1)) if m else None
|
||||
|
||||
lang, kompakt = block("LANG"), block("KOMPAKT")
|
||||
if lang is not None or kompakt is not None:
|
||||
return kompakt or "", lang or ""
|
||||
daten = jsonx.parse(text or "") # Fallback: altes JSON-Format
|
||||
if isinstance(daten, dict):
|
||||
return str(daten.get("kompakt", "")), str(daten.get("lang", ""))
|
||||
return "", (text or "").strip()
|
||||
return (_writer_saeubern(str(daten.get("kompakt", ""))),
|
||||
_writer_saeubern(str(daten.get("lang", ""))))
|
||||
return "", _writer_saeubern(text or "")
|
||||
|
||||
|
||||
async def _stage_writer(ctx: llm.Kontext, b: dict) -> str:
|
||||
@@ -275,7 +256,7 @@ async def _stage_writer(ctx: llm.Kontext, b: dict) -> str:
|
||||
k2, l2 = _split_writer(res2)
|
||||
if not _marker_fehlend(l2, atome):
|
||||
kompakt, lang = k2, l2
|
||||
lang = _marker_platzieren(b, lang) # Beispiel-/Diagramm-Marker deterministisch setzen
|
||||
lang = _marker_platzieren(b, lang) # Beispiel-Marker deterministisch setzen
|
||||
db.update("sections", "baustein_id", b["id"], text_kompakt=kompakt, text_lang=lang)
|
||||
return "pruefer"
|
||||
|
||||
@@ -604,15 +585,17 @@ def kapitel_struktur(topic: str) -> list[dict]:
|
||||
"titel": k.get("titel", "Weitere Themen"),
|
||||
"intro": k.get("intro", ""),
|
||||
"level": k.get("level", b["level"]), "sections": []})
|
||||
# Diagramm- und Beispiel-Marker gegen ihre Fences/Tabellen tauschen (Assembly);
|
||||
# Atom-Marker bleiben — die strippt erst das Frontend.
|
||||
# Beispiel-Marker gegen ihre Fences/Tabellen tauschen (Assembly);
|
||||
# Atom-Marker bleiben — die strippt erst das Frontend. _writer_saeubern hier
|
||||
# AUCH beim Lesen: bestehende Sections mit geleaktem `=====`/`</s>` (Setext-H1
|
||||
# → riesiger Bullet) werden ohne Neu-Generierung sauber ausgeliefert.
|
||||
lang = ""
|
||||
if sec:
|
||||
lang = _beispiel_einsetzen(b["id"], _diagramm_einsetzen(b["id"], sec["text_lang"]))
|
||||
lang = _beispiel_einsetzen(b["id"], _writer_saeubern(sec["text_lang"]))
|
||||
kapitel[-1]["sections"].append({
|
||||
"baustein": b["id"], "titel": b["titel"],
|
||||
"ziel": ziele.get(b["ziel_id"], {}).get("text", ""),
|
||||
"kompakt": sec["text_kompakt"] if sec else "",
|
||||
"kompakt": _writer_saeubern(sec["text_kompakt"]) if sec else "",
|
||||
"lang": lang})
|
||||
# NULL-kapitel_id durch eindeutige synthetische Keys ersetzen — mehrere
|
||||
# kapitellose Gruppen kollidierten sonst im Vue-:key (null == null).
|
||||
@@ -658,12 +641,6 @@ def messen(ctx: llm.Kontext) -> list[dict]:
|
||||
for i in _marker_fremd_ids(lang, atome):
|
||||
befunde.append({"art": "marker_fremd", "item": str(b["id"]),
|
||||
"detail": f"Atom {i}"})
|
||||
gueltige_d = {d["id"] for d in _diagramme_von(b["id"])}
|
||||
for m in _MARKER_DIAGRAMM.findall(lang):
|
||||
if int(m) not in gueltige_d: # Marker ohne verifiziertes Diagramm → entfernen
|
||||
befunde.append({"art": "diagramm_marker_tot", "item": str(b["id"]),
|
||||
"detail": f"Stray Diagramm-Marker <!-- diagramm: {m} -->"
|
||||
f" entfernen (kein solches verifiziertes Diagramm)"})
|
||||
gueltige_b = {aid for a in atome for aid, _ in _beispiel_verbatim(a["id"])}
|
||||
for m in _MARKER_BEISPIEL.findall(lang):
|
||||
if int(m) not in gueltige_b: # Marker ohne verifiziertes Verbatim-Beispiel
|
||||
|
||||
Reference in New Issue
Block a user