update
This commit is contained in:
@@ -11,7 +11,6 @@ import subprocess
|
||||
import tempfile
|
||||
|
||||
import db
|
||||
import diagramme
|
||||
import llm
|
||||
from config import ARTEFAKT_CHUNK_ATOME, BEISPIEL_FORMEN, VERIFY_PANEL
|
||||
|
||||
@@ -30,8 +29,8 @@ _QUELLEN_REFERENZ = re.compile(
|
||||
|
||||
|
||||
def _prosa_teile(inhalt: dict) -> str:
|
||||
"""Nur menschenlesbare Prosa fürs Quellen-Referenz-Gate — NICHT Code/Mermaid/
|
||||
Tabelle (dort matchen Bezeichner/Kommentare den Regex falsch, z. B. „blatt 3")."""
|
||||
"""Nur menschenlesbare Prosa fürs Quellen-Referenz-Gate — NICHT Code/Tabelle
|
||||
(dort matchen Bezeichner/Kommentare den Regex falsch, z. B. „blatt 3")."""
|
||||
return " ".join(str(inhalt.get(k, "")) for k in ("frage", "antwort", "text"))
|
||||
|
||||
|
||||
@@ -80,9 +79,7 @@ def _beispiel_inhalt(e: dict) -> dict:
|
||||
"text": str(e.get("text", "")),
|
||||
"code": str(e.get("code", "")),
|
||||
"sprache": str(e.get("sprache", "")).strip().lower(),
|
||||
"tabelle": str(e.get("tabelle", "")),
|
||||
"spec": e.get("spec") if isinstance(e.get("spec"), dict) else {},
|
||||
"mermaid": ""}
|
||||
"tabelle": str(e.get("tabelle", ""))}
|
||||
|
||||
|
||||
async def _generieren(ctx: llm.Kontext, chunk: list[dict]) -> None:
|
||||
@@ -188,7 +185,7 @@ async def _fixen(ctx: llm.Kontext, k: dict, maengel: list[str], atom: dict) -> N
|
||||
status="verifiziert" if eintrag.get("ok") else "verworfen")
|
||||
|
||||
|
||||
_VERBATIM_FORMEN = ("code", "tabelle", "diagramm") # kommen verbatim in den Guide
|
||||
_VERBATIM_FORMEN = ("code", "tabelle") # kommen verbatim in den Guide
|
||||
|
||||
|
||||
def _tabelle_ok(md: str) -> bool:
|
||||
@@ -242,24 +239,12 @@ def _code_ok(sprache: str, code: str) -> bool:
|
||||
|
||||
def _beispiel_pruefen(inh: dict) -> tuple[bool, dict]:
|
||||
"""Form-spezifisches Gate (deterministisch, parse-only — nie Ausführung).
|
||||
→ (ok, inhalt); der Diagramm-Zweig füllt `mermaid`. Unbekannte/woven Formen
|
||||
passieren (der Judge prüft inhaltlich)."""
|
||||
→ (ok, inhalt). Unbekannte/woven Formen passieren (der Judge prüft inhaltlich)."""
|
||||
form = inh.get("form", "text")
|
||||
if form == "tabelle":
|
||||
return _tabelle_ok(inh.get("tabelle", "")), inh
|
||||
if form == "code":
|
||||
return _code_ok(inh.get("sprache", ""), inh.get("code", "")), inh
|
||||
if form == "diagramm":
|
||||
# Struktur-JSON → Mermaid (deterministisch) → Parse-Gate; wiederverwendet die
|
||||
# Diagramm-Ebene. Der LLM schreibt NIE rohes Mermaid — nur die Spec.
|
||||
spec = diagramme._spec_normalisieren(inh.get("spec") or {},
|
||||
(inh.get("spec") or {}).get("typ", "flow"))
|
||||
if not spec["knoten"]:
|
||||
return False, inh
|
||||
mermaid = diagramme._spec_zu_mermaid(spec)
|
||||
if not mermaid or 0 in diagramme._mermaid_fehler([{"id": 0, "mermaid": mermaid}]):
|
||||
return False, inh
|
||||
return True, {**inh, "spec": spec, "mermaid": mermaid}
|
||||
return True, inh
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ LUECKEN_RUNDEN_MAX = 2 # gezielte Nachextraktion pro Soll-Lücke
|
||||
BRAUCHT_KANDIDATEN = 8 # Kandidaten-Cap je unaufgelöstem braucht-Titel (Lektion 34)
|
||||
|
||||
# ── Ebene 2: Artefakte ────────────────────────────────────────────────────────
|
||||
BEISPIEL_FORMEN = ("text", "mathe", "code", "tabelle", "diagramm") # Worked-Example-Formen
|
||||
BEISPIEL_FORMEN = ("text", "mathe", "code", "tabelle") # Worked-Example-Formen
|
||||
CODE_SPRACHEN = ("python", "javascript") # Syntax-Gate parse-only (Container: python3, node)
|
||||
ARTEFAKT_CHUNK_ATOME = 8 # Atome pro Generate-Call (ein Soll-Punkt, gestückelt)
|
||||
VERIFY_PANEL = 2 # unabhängige Prüfer, einstimmig = verifiziert
|
||||
@@ -107,9 +107,6 @@ BAUSTEIN_MAX_ATOME = 8
|
||||
ZIELE_CHUNK_ATOME = 30 # Atome pro Lernziel-Call
|
||||
KAPITEL_BAUSTEINE = 5 # Richtwert Bausteine je Kapitel (Kapitel-Schnitt-Judge)
|
||||
|
||||
# ── Ebene 3.5: Diagramme (nur LLM: Automat/Ablauf/Baum — Term-DAGs waren nutzlos) ──
|
||||
DIAGRAMM_TYPEN = ("state", "flow", "tree")
|
||||
|
||||
# ── Ebene 4: Guide ────────────────────────────────────────────────────────────
|
||||
SECTION_WOERTER_PRO_ATOM = (40, 220) # Längenband ausführlicher Teil je Atom
|
||||
|
||||
@@ -137,9 +134,6 @@ TIMEOUTS = {
|
||||
"writer": (450, 60),
|
||||
"pruefer": (600, 5),
|
||||
"qa_judge": (600, 0),
|
||||
"diagramm_plan": (200, 0),
|
||||
"diagramm_spec": (300, 0),
|
||||
"diagramm_verify": (200, 5),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -96,12 +96,6 @@ CREATE TABLE IF NOT EXISTS kapitel(
|
||||
id INTEGER PRIMARY KEY, topic TEXT NOT NULL, titel TEXT NOT NULL,
|
||||
intro TEXT NOT NULL DEFAULT '', ord INTEGER DEFAULT 0,
|
||||
art TEXT NOT NULL DEFAULT 'judge', level TEXT NOT NULL DEFAULT 'M');
|
||||
CREATE TABLE IF NOT EXISTS diagramme(
|
||||
id INTEGER PRIMARY KEY, topic TEXT NOT NULL, baustein_id INTEGER NOT NULL,
|
||||
typ TEXT NOT NULL, quelle TEXT NOT NULL DEFAULT 'dag',
|
||||
spec TEXT NOT NULL DEFAULT '{}', mermaid TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'geplant', befunde TEXT NOT NULL DEFAULT '[]');
|
||||
CREATE INDEX IF NOT EXISTS idx_diagramme_baustein ON diagramme(baustein_id);
|
||||
CREATE TABLE IF NOT EXISTS sections(
|
||||
baustein_id INTEGER PRIMARY KEY, stage TEXT NOT NULL DEFAULT 'writer',
|
||||
text_kompakt TEXT DEFAULT '', text_lang TEXT DEFAULT '', befunde TEXT DEFAULT '[]',
|
||||
@@ -116,7 +110,7 @@ CREATE TABLE IF NOT EXISTS befunde(
|
||||
|
||||
# Tabellen, deren Änderungen das Live-Board interessieren.
|
||||
_LIVE_TABELLEN = {"topics", "runs", "quellen", "soll", "atome", "artefakte",
|
||||
"lernziele", "bausteine", "kapitel", "sections", "befunde", "diagramme"}
|
||||
"lernziele", "bausteine", "kapitel", "sections", "befunde"}
|
||||
|
||||
|
||||
def _init_schema(con: sqlite3.Connection) -> None:
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
"""Ebene 3.5: Diagramme. Pro Baustein ein Mermaid-Diagramm, wenn es Struktur oder
|
||||
Prozess sichtbar macht (Multimedia-Prinzip: instruktive Bilder heben Transfer,
|
||||
Deko schadet). Zuverlässigkeits-Prinzip: der LLM liefert NIE rohes Mermaid, nur
|
||||
Struktur-JSON; Python kompiliert deterministisch; ein Parse-Gate (mermaid_check.mjs)
|
||||
ist das Sicherheitsnetz. Abhängigkeitsdiagramme kommen direkt aus dem braucht-DAG,
|
||||
ganz ohne LLM — korrekt per Konstruktion, null Halluzination.
|
||||
|
||||
Phasen von bauen(): planen (welche + Typ) → Spec (nur LLM-Zweig) → kompilieren →
|
||||
Gate → verifizieren (Grounding). Platziert werden Diagramme erst im Guide-Writer
|
||||
(E4) über `<!-- diagramm: id -->`; das Assembly tauscht den Marker gegen den Fence."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import db
|
||||
import llm
|
||||
from config import DIAGRAMM_TYPEN, DURCHGANG, VERIFY_PANEL
|
||||
|
||||
log = logging.getLogger("creator2.diagramme")
|
||||
|
||||
EBENE = "diagramme"
|
||||
|
||||
_MERMAID_SKRIPT = Path(__file__).resolve().parent / "mermaid_check.mjs"
|
||||
_MERMAID_BASE = Path(__file__).resolve().parent.parent / "frontend"
|
||||
_mermaid_gewarnt = False
|
||||
|
||||
|
||||
def _mermaid_fehler(diagramme: list[dict]) -> dict[int, str]:
|
||||
"""Parse-Gate: Diagramme durch DENSELBEN Parser wie das Frontend jagen (Zwilling
|
||||
zu guide._katex_fehler). → {id: fehlermeldung} nur der fehlerhaften. Fake-Modus
|
||||
überspringt (deterministisch); fail-open MIT Warnung, wenn node/mermaid fehlt."""
|
||||
global _mermaid_gewarnt
|
||||
if not diagramme or os.getenv("CREATOR_FAKE_AGENTS"):
|
||||
return {}
|
||||
try:
|
||||
res = subprocess.run(
|
||||
["node", str(_MERMAID_SKRIPT)],
|
||||
input=json.dumps([{"id": d["id"], "code": d["mermaid"]} for d in diagramme]),
|
||||
capture_output=True, text=True, timeout=60,
|
||||
env={**os.environ, "MERMAID_BASE": str(_MERMAID_BASE)})
|
||||
if res.returncode:
|
||||
raise RuntimeError(res.stderr.strip()[:200])
|
||||
return {e["id"]: e["fehler"] for e in json.loads(res.stdout)}
|
||||
except Exception as e: # nie fail-open OHNE Signal (Befund-Prinzip)
|
||||
if not _mermaid_gewarnt:
|
||||
_mermaid_gewarnt = True
|
||||
log.warning("Mermaid-Gate nicht verfügbar (%s) — Diagramm-Validierung fällt aus", e)
|
||||
return {}
|
||||
|
||||
|
||||
def _bausteine(topic: str) -> list[dict]:
|
||||
return db.query("SELECT * FROM bausteine WHERE topic=? ORDER BY ord", (topic,))
|
||||
|
||||
|
||||
def _atome_von(baustein_id: int) -> list[dict]:
|
||||
return db.query("SELECT * FROM atome WHERE baustein_id=? AND status NOT IN"
|
||||
" ('gemerged','verworfen') ORDER BY ord", (baustein_id,))
|
||||
|
||||
|
||||
# ── Kompilieren (deterministisch, kein LLM) ───────────────────────────────────
|
||||
|
||||
def _sid(raw) -> str:
|
||||
"""Mermaid-Knoten-ID: nur alnum/_ erlaubt, führender Buchstabe."""
|
||||
return "n" + re.sub(r"[^A-Za-z0-9]", "_", str(raw))
|
||||
|
||||
|
||||
def _lbl(text) -> str:
|
||||
"""Label für Mermaid entschärfen: keine Anführungszeichen/Klammern/Zeilenumbrüche,
|
||||
die die Syntax brechen. Gekürzt (Lesbarkeit)."""
|
||||
t = str(text).replace('"', "'").replace("\n", " ")
|
||||
t = t.replace("[", "(").replace("]", ")").replace("{", "(").replace("}", ")")
|
||||
return t.strip()[:80] or "?"
|
||||
|
||||
|
||||
def _spec_zu_mermaid(spec: dict) -> str:
|
||||
"""Struktur-JSON → Mermaid-Quelltext. state → stateDiagram-v2, sonst flowchart."""
|
||||
knoten = spec.get("knoten") or []
|
||||
kanten = spec.get("kanten") or []
|
||||
if not knoten:
|
||||
return ""
|
||||
if spec.get("typ") == "state":
|
||||
zeilen = ["stateDiagram-v2"]
|
||||
for k in kanten:
|
||||
lab = _lbl(k.get("label", ""))
|
||||
pfeil = f": {lab}" if lab and lab != "?" else ""
|
||||
zeilen.append(f" {_sid(k['von'])} --> {_sid(k['zu'])}{pfeil}")
|
||||
return "\n".join(zeilen)
|
||||
zeilen = ["flowchart TD"]
|
||||
for n in knoten:
|
||||
zeilen.append(f' {_sid(n["id"])}["{_lbl(n["label"])}"]')
|
||||
for k in kanten:
|
||||
lab = _lbl(k.get("label", ""))
|
||||
pfeil = f"-- {lab} -->" if lab and lab != "?" else "-->"
|
||||
zeilen.append(f' {_sid(k["von"])} {pfeil} {_sid(k["zu"])}')
|
||||
return "\n".join(zeilen)
|
||||
|
||||
|
||||
def _kompilieren(topic: str) -> None:
|
||||
for d in db.query("SELECT * FROM diagramme WHERE topic=? AND status='geplant'"
|
||||
" AND spec!='{}'", (topic,)):
|
||||
mermaid = _spec_zu_mermaid(db.uj(d["spec"], {}))
|
||||
if mermaid:
|
||||
db.update("diagramme", "id", d["id"], mermaid=mermaid, status="kandidat")
|
||||
|
||||
|
||||
# ── Planung (welche + Typ) ────────────────────────────────────────────────────
|
||||
|
||||
def _atom_liste(atome: list[dict]) -> str:
|
||||
return "\n".join(f"{a['id']}: {a['titel']} — {a['definition']}" for a in atome)
|
||||
|
||||
|
||||
async def _plan_judge(ctx: llm.Kontext) -> None:
|
||||
"""LLM-Zweig, Schritt „welche + Typ": je Baustein OHNE Diagramm-Zeile entscheidet
|
||||
ein Judge, ob ein Automaten-/Ablauf-/Baum-Diagramm nötig ist (Multimedia-Regel
|
||||
im Prompt). „noetig" → status='geplant'/quelle='llm'; sonst eine 'kein'-Zeile,
|
||||
damit der Judge nicht jede Repair-Iteration erneut fragt (idempotent)."""
|
||||
alle = _bausteine(ctx.topic)
|
||||
je_kap: dict = {}
|
||||
for b in alle:
|
||||
je_kap.setdefault(b["kapitel_id"], []).append(b["titel"])
|
||||
|
||||
async def einer(b: dict) -> None:
|
||||
if db.one("SELECT id FROM diagramme WHERE baustein_id=?", (b["id"],)):
|
||||
return
|
||||
atome = _atome_von(b["id"])
|
||||
if not atome:
|
||||
return
|
||||
nachbarn = "\n".join(f"- {t}" for t in je_kap.get(b["kapitel_id"], [])
|
||||
if t != b["titel"]) or "(keine)"
|
||||
res = await llm.call(ctx, stage="diagramm_plan", template="Diagramm-Plan",
|
||||
role="judge", n=len(atome), item=f"dp{b['id']}",
|
||||
werte={"titel": b["titel"], "atome": _atom_liste(atome),
|
||||
"nachbarn": nachbarn,
|
||||
"durchgang": DURCHGANG.get(b["level"], DURCHGANG["M"])},
|
||||
erwartet=dict)
|
||||
if res is None:
|
||||
return # Ausfall → nächste Runde erneut (kein 'kein'-Eintrag)
|
||||
typ = str(res.get("typ", "")).strip()
|
||||
if res.get("noetig") and typ in DIAGRAMM_TYPEN:
|
||||
db.insert("diagramme", topic=ctx.topic, baustein_id=b["id"], typ=typ,
|
||||
quelle="llm", status="geplant")
|
||||
else:
|
||||
db.insert("diagramme", topic=ctx.topic, baustein_id=b["id"], typ="",
|
||||
quelle="llm", status="kein") # kein Diagramm — nicht erneut fragen
|
||||
|
||||
await llm.alle(einer(b) for b in alle)
|
||||
|
||||
|
||||
def _spec_normalisieren(res: dict, typ: str) -> dict:
|
||||
"""LLM-Spec säubern: nur Knoten mit id+label, nur Kanten zwischen echten Knoten."""
|
||||
knoten = [{"id": str(n.get("id", "")).strip(), "label": str(n.get("label", "")).strip()}
|
||||
for n in (res.get("knoten") or []) if isinstance(n, dict) and n.get("id")]
|
||||
gueltig = {n["id"] for n in knoten}
|
||||
kanten = [{"von": str(k.get("von", "")).strip(), "zu": str(k.get("zu", "")).strip(),
|
||||
"label": str(k.get("label", "")).strip()}
|
||||
for k in (res.get("kanten") or [])
|
||||
if isinstance(k, dict) and str(k.get("von")) in gueltig
|
||||
and str(k.get("zu")) in gueltig]
|
||||
return {"typ": typ, "knoten": knoten, "kanten": kanten}
|
||||
|
||||
|
||||
async def _spec_bauen(ctx: llm.Kontext) -> None:
|
||||
"""LLM liefert NUR Struktur-JSON (kein Mermaid) — die Hauptfehlerquelle
|
||||
(roh geschriebenes Mermaid) fällt so weg. Kompiliert wird deterministisch."""
|
||||
offen = db.query("SELECT * FROM diagramme WHERE topic=? AND status='geplant'"
|
||||
" AND quelle='llm' AND spec='{}'", (ctx.topic,))
|
||||
|
||||
async def einer(d: dict) -> None:
|
||||
b = db.one("SELECT * FROM bausteine WHERE id=?", (d["baustein_id"],))
|
||||
atome = _atome_von(d["baustein_id"])
|
||||
res = await llm.call(ctx, stage="diagramm_spec", template="Diagramm-Spec",
|
||||
role="guide", n=len(atome), item=f"ds{d['id']}",
|
||||
werte={"titel": b["titel"] if b else "", "typ": d["typ"],
|
||||
"atome": _atom_liste(atome)}, erwartet=dict)
|
||||
if res is None:
|
||||
return # Ausfall → bleibt 'geplant', nächste Runde erneut
|
||||
spec = _spec_normalisieren(res, d["typ"])
|
||||
if spec["knoten"]:
|
||||
db.update("diagramme", "id", d["id"], spec=db.j(spec))
|
||||
|
||||
await llm.alle(einer(d) for d in offen)
|
||||
|
||||
|
||||
async def _verifizieren(ctx: llm.Kontext) -> None:
|
||||
"""Grounding-Panel für LLM-Diagramme: jeder Knoten muss aus einem Atom ableitbar
|
||||
sein, keine erfundene Struktur. Verwerfen (destruktiv) nur einstimmig
|
||||
(llm.einstimmig) — Panel-Ausfall lässt den Kandidaten stehen."""
|
||||
offen = db.query("SELECT * FROM diagramme WHERE topic=? AND status='kandidat'"
|
||||
" AND quelle='llm'", (ctx.topic,))
|
||||
|
||||
def urteil(stimme):
|
||||
if stimme and isinstance(stimme[0], dict) and "ok" in stimme[0]:
|
||||
return bool(stimme[0]["ok"])
|
||||
return None
|
||||
|
||||
async def einer(d: dict) -> None:
|
||||
atome = _atome_von(d["baustein_id"])
|
||||
stimmen = await llm.panel(ctx, VERIFY_PANEL, stage="diagramm_verify",
|
||||
template="Diagramm-Verify", schritt="diagramm_verify",
|
||||
role="judge", n=len(atome), item=f"dv{d['id']}",
|
||||
werte={"atome": _atom_liste(atome), "mermaid": d["mermaid"]},
|
||||
erwartet=list)
|
||||
if llm.einstimmig(stimmen, VERIFY_PANEL, urteil):
|
||||
db.update("diagramme", "id", d["id"], status="verifiziert")
|
||||
elif llm.einstimmig(stimmen, VERIFY_PANEL,
|
||||
lambda s: None if urteil(s) is None else not urteil(s)):
|
||||
db.update("diagramme", "id", d["id"], status="verworfen",
|
||||
befunde=db.j(["Grounding-Panel: nicht aus den Atomen ableitbar"]))
|
||||
# sonst: Kandidat bleibt (Panel-Ausfall / uneins) — nächste Runde erneut
|
||||
|
||||
await llm.alle(einer(d) for d in offen)
|
||||
|
||||
|
||||
# ── Ebenen-Vertrag ────────────────────────────────────────────────────────────
|
||||
|
||||
async def bauen(ctx: llm.Kontext) -> None:
|
||||
ctx.ebene = EBENE
|
||||
await _plan_judge(ctx) # welche/Typ: Judge (Automaten/Abläufe/Bäume) — kein DAG mehr
|
||||
await _spec_bauen(ctx) # LLM → Struktur-JSON (kein rohes Mermaid)
|
||||
_kompilieren(ctx.topic) # Spec → Mermaid (deterministisch)
|
||||
_gate_kandidaten(ctx.topic) # Parse-Gate: ungültige Mermaid-Quelle → verworfen
|
||||
await _verifizieren(ctx) # Grounding-Panel gegen die Atome
|
||||
|
||||
|
||||
def _gate_kandidaten(topic: str) -> None:
|
||||
"""Kandidaten durchs Parse-Gate: Syntaxfehler → verworfen (mit Befund). Ein
|
||||
kaputtes Diagramm darf nie in den Guide (fail-closed)."""
|
||||
kandidaten = db.query("SELECT * FROM diagramme WHERE topic=? AND status='kandidat'"
|
||||
" AND mermaid!=''", (topic,))
|
||||
fehler = _mermaid_fehler(kandidaten)
|
||||
for d in kandidaten:
|
||||
if d["id"] in fehler:
|
||||
db.update("diagramme", "id", d["id"], status="verworfen",
|
||||
befunde=db.j([f"Parse-Fehler: {fehler[d['id']]}"]))
|
||||
|
||||
|
||||
def messen(ctx: llm.Kontext) -> list[dict]:
|
||||
befunde = []
|
||||
for d in db.query("SELECT * FROM diagramme WHERE topic=? AND status='kandidat'",
|
||||
(ctx.topic,)):
|
||||
# hängengebliebener Kandidat (Spec/Verify offen) — sichtbar, kein stiller Verlust
|
||||
befunde.append({"art": "diagramm_unverifiziert", "item": str(d["id"]),
|
||||
"detail": f"Baustein {d['baustein_id']}"})
|
||||
return befunde
|
||||
|
||||
|
||||
async def reparieren(ctx: llm.Kontext, befunde: list[dict]) -> bool:
|
||||
ctx.ebene = EBENE
|
||||
if not befunde:
|
||||
return False
|
||||
await bauen(ctx)
|
||||
return True
|
||||
|
||||
|
||||
def gate(ctx: llm.Kontext) -> str | None:
|
||||
"""Diagramme sind optional — KEINE Diagramme blockiert den Lauf nicht. Backstop:
|
||||
ein verifiziertes Diagramm ohne Mermaid-Quelltext darf nie ausgeliefert werden."""
|
||||
tot = db.one("SELECT id FROM diagramme WHERE topic=? AND status='verifiziert'"
|
||||
" AND mermaid='' LIMIT 1", (ctx.topic,))
|
||||
return "verifiziertes Diagramm ohne Quelltext" if tot else None
|
||||
@@ -237,7 +237,7 @@ def _writer(prompt: str):
|
||||
absaetze = [f"Ziel dieser Einheit: {ziel.group(1) if ziel else 'Lernen'}. Formal: $n \\ge 0$."]
|
||||
fuellung = "Daran anknüpfend gehen wir das Schritt für Schritt am Beispiel durch. " * 5
|
||||
for (mk, titel), d in zip(marker, defs):
|
||||
# Beispiel-/Diagramm-Marker setzt jetzt _marker_platzieren deterministisch,
|
||||
# Beispiel-Marker setzt jetzt _marker_platzieren deterministisch,
|
||||
# nicht mehr der Writer.
|
||||
absaetze.append(f"#### {titel} verstehen\n{mk}\n{d} {fuellung}")
|
||||
lang = "\n\n".join(absaetze)
|
||||
@@ -247,23 +247,6 @@ def _writer(prompt: str):
|
||||
"lang": lang}
|
||||
|
||||
|
||||
def _diagramm_plan(prompt: str):
|
||||
# deterministisch: die Fake-Welt braucht kein Diagramm. Unit-Tests für den
|
||||
# LLM-Diagramm-Zweig monkeypatchen diesen Handler auf noetig=True.
|
||||
return {"noetig": False, "typ": ""}
|
||||
|
||||
|
||||
def _diagramm_spec(prompt: str):
|
||||
ids = [i for i in re.findall(r"^(\d+):", _text_nach(prompt, "ATOME:"), re.MULTILINE)]
|
||||
knoten = [{"id": i, "label": f"Konzept {i}"} for i in ids]
|
||||
kanten = [{"von": ids[k], "zu": ids[k + 1], "label": ""} for k in range(len(ids) - 1)]
|
||||
return {"typ": "flow", "knoten": knoten, "kanten": kanten}
|
||||
|
||||
|
||||
def _diagramm_verify(prompt: str):
|
||||
return [{"ok": True, "mangel": ""}]
|
||||
|
||||
|
||||
def _pruefer(prompt: str):
|
||||
return {"befunde": []}
|
||||
|
||||
@@ -298,6 +281,4 @@ _HANDLER = {
|
||||
"Kanten-Zyklus": _zyklus, "Braucht-Aufloesung": _braucht_aufloesung,
|
||||
"Guide-Writer": _writer, "Guide-Pruefer": _pruefer, "Guide-Fix": _fix,
|
||||
"QA-Guide-Falsch": _qa_falsch, "QA-Guide-Falsch-Check": _qa_falsch_check,
|
||||
"Diagramm-Plan": _diagramm_plan, "Diagramm-Spec": _diagramm_spec,
|
||||
"Diagramm-Verify": _diagramm_verify,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -153,10 +153,6 @@ def state(topic: str):
|
||||
"bausteine": db.query(
|
||||
"SELECT b.*, s.stage FROM bausteine b LEFT JOIN sections s ON s.baustein_id=b.id"
|
||||
" WHERE b.topic=? ORDER BY b.ord", (topic,)),
|
||||
"diagramme": db.query(
|
||||
"SELECT d.id, d.baustein_id, d.typ, d.quelle, d.status, b.titel, b.ord"
|
||||
" FROM diagramme d JOIN bausteine b ON b.id=d.baustein_id"
|
||||
" WHERE d.topic=? AND d.status!='kein' ORDER BY b.ord, d.id", (topic,)),
|
||||
# topic-scoped: offene Befunde früherer Läufe bleiben nach Resume sichtbar
|
||||
"befunde": db.query(
|
||||
"SELECT b.* FROM befunde b JOIN runs r ON r.id=b.run_id"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// Mermaid-Parse-Gate: prüft Diagramm-Quelltext mit DEMSELBEN Parser wie das Frontend.
|
||||
// stdin: JSON [{id, code}] → stdout: JSON [{id, fehler}, …] (nur die fehlerhaften).
|
||||
// Zwilling zu katex_check.mjs: was hier parst, rendert auch im Guide.
|
||||
//
|
||||
// Mermaid braucht zum Sanitisieren der Labels ein DOM — headless via jsdom, sonst
|
||||
// wirft parse() für JEDES Diagramm mit Label „DOMPurify.addHook is not a function"
|
||||
// (nicht ein Diagramm-Fehler, sondern ein Umgebungsfehler → alles fälschlich verworfen).
|
||||
import { createRequire } from 'node:module'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
function laden(name) {
|
||||
try {
|
||||
return createRequire(import.meta.url)(name) // Container: backend/node_modules
|
||||
} catch {
|
||||
const basis = process.env.MERMAID_BASE // Dev: frontend/node_modules
|
||||
return createRequire(basis.endsWith('/') ? basis : basis + '/')(name)
|
||||
}
|
||||
}
|
||||
|
||||
const { JSDOM } = laden('jsdom')
|
||||
const dom = new JSDOM('<!DOCTYPE html><body></body>')
|
||||
globalThis.window = dom.window
|
||||
globalThis.document = dom.window.document
|
||||
|
||||
const mermaid = laden('mermaid').default
|
||||
mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' })
|
||||
|
||||
const diagramme = JSON.parse(readFileSync(0, 'utf8'))
|
||||
const fehler = []
|
||||
for (const d of diagramme) {
|
||||
try {
|
||||
await mermaid.parse(d.code) // wirft bei Syntaxfehler
|
||||
} catch (e) {
|
||||
fehler.push({ id: d.id, fehler: String(e.message || e).slice(0, 160) })
|
||||
}
|
||||
}
|
||||
process.stdout.write(JSON.stringify(fehler))
|
||||
@@ -10,7 +10,6 @@ import agents
|
||||
import artefakte
|
||||
import auto_loop
|
||||
import db
|
||||
import diagramme
|
||||
import guide
|
||||
import inventar
|
||||
import korpus
|
||||
@@ -27,11 +26,10 @@ EBENEN = [
|
||||
("inventar", inventar, "inventar_fertig"),
|
||||
("artefakte", artefakte, "artefakte_fertig"),
|
||||
("struktur", struktur, "struktur_fertig"),
|
||||
("diagramme", diagramme, "diagramme_fertig"),
|
||||
("guide", guide, "fertig"),
|
||||
]
|
||||
_ORDNUNG = ["neu", "korpus", "korpus_fertig", "inventar_fertig",
|
||||
"artefakte_fertig", "struktur_fertig", "diagramme_fertig", "fertig"]
|
||||
"artefakte_fertig", "struktur_fertig", "fertig"]
|
||||
_laeufe: dict[str, asyncio.Task] = {}
|
||||
|
||||
|
||||
@@ -65,7 +63,7 @@ def lauf_stoppen(topic: str) -> None:
|
||||
db.update("runs", "id", run["id"], status="stopped", beendet=db.now())
|
||||
|
||||
|
||||
_ROLLBACK = {"guide": "diagramme_fertig", "diagramme": "struktur_fertig",
|
||||
_ROLLBACK = {"guide": "struktur_fertig",
|
||||
"struktur": "artefakte_fertig", "artefakte": "inventar_fertig",
|
||||
"inventar": "korpus_fertig", "korpus": "neu"}
|
||||
|
||||
@@ -77,25 +75,23 @@ def ebenen_entfernen(topic: str, ebene: str) -> None:
|
||||
if ebene not in _ROLLBACK:
|
||||
raise ValueError(f"unbekannte Ebene: {ebene}")
|
||||
lauf_stoppen(topic)
|
||||
stufe = ["guide", "diagramme", "struktur", "artefakte", "inventar", "korpus"].index(ebene)
|
||||
stufe = ["guide", "struktur", "artefakte", "inventar", "korpus"].index(ebene)
|
||||
db.execute("DELETE FROM sections WHERE baustein_id IN"
|
||||
" (SELECT id FROM bausteine WHERE topic=?)", (topic,))
|
||||
db.execute("UPDATE bausteine SET status='neu' WHERE topic=?", (topic,))
|
||||
db.execute("UPDATE kapitel SET intro='' WHERE topic=?", (topic,)) # Intro ist Guide-Text
|
||||
if stufe >= 1: # diagramme
|
||||
db.execute("DELETE FROM diagramme WHERE topic=?", (topic,))
|
||||
if stufe >= 2: # struktur
|
||||
if stufe >= 1: # struktur
|
||||
db.execute("DELETE FROM bausteine WHERE topic=?", (topic,))
|
||||
db.execute("DELETE FROM kapitel WHERE topic=?", (topic,))
|
||||
db.execute("DELETE FROM lernziele WHERE topic=?", (topic,))
|
||||
db.execute("UPDATE atome SET ziel_id=NULL, baustein_id=NULL, ord=0 WHERE topic=?",
|
||||
(topic,))
|
||||
if stufe >= 3: # artefakte
|
||||
if stufe >= 2: # artefakte
|
||||
db.execute("DELETE FROM leitner WHERE artefakt_id IN (SELECT ar.id FROM artefakte ar"
|
||||
" JOIN atome a ON ar.atom_id=a.id WHERE a.topic=?)", (topic,))
|
||||
db.execute("DELETE FROM artefakte WHERE atom_id IN"
|
||||
" (SELECT id FROM atome WHERE topic=?)", (topic,))
|
||||
if stufe >= 4: # inventar
|
||||
if stufe >= 3: # inventar
|
||||
db.execute("DELETE FROM anker WHERE atom_id IN (SELECT id FROM atome WHERE topic=?)",
|
||||
(topic,))
|
||||
db.execute("DELETE FROM kanten WHERE topic=?", (topic,))
|
||||
@@ -103,7 +99,7 @@ def ebenen_entfernen(topic: str, ebene: str) -> None:
|
||||
# atome_stand mit-nullen — sonst überspringt der Resume-Guard die Neu-Extraktion
|
||||
db.execute("UPDATE quellen SET status='extrahiert', atome_stand='' WHERE topic=?",
|
||||
(topic,))
|
||||
if stufe >= 5: # korpus
|
||||
if stufe >= 4: # korpus
|
||||
db.execute("DELETE FROM soll WHERE topic=?", (topic,))
|
||||
db.execute("DELETE FROM quellen WHERE topic=?", (topic,))
|
||||
import shutil
|
||||
@@ -118,7 +114,7 @@ def ebenen_entfernen(topic: str, ebene: str) -> None:
|
||||
# append-only), aber die Zählung beginnt hinter dem Reset-Marker neu.
|
||||
letzte = db.one("SELECT MAX(id) AS m FROM events")["m"] or 0
|
||||
resets = db.uj(db.one("SELECT resets FROM topics WHERE name=?", (topic,))["resets"], {})
|
||||
for name in ["guide", "diagramme", "struktur", "artefakte", "inventar", "korpus"][:stufe + 1]:
|
||||
for name in ["guide", "struktur", "artefakte", "inventar", "korpus"][:stufe + 1]:
|
||||
resets[name] = letzte
|
||||
db.update("topics", "name", topic, status=_ROLLBACK[ebene], resets=db.j(resets))
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import logging
|
||||
|
||||
import artefakte
|
||||
import db
|
||||
import diagramme
|
||||
import guide
|
||||
import inventar
|
||||
import korpus
|
||||
@@ -19,7 +18,7 @@ import struktur
|
||||
log = logging.getLogger("creator2.qa")
|
||||
|
||||
MODULE = {"korpus": korpus, "inventar": inventar, "artefakte": artefakte,
|
||||
"struktur": struktur, "diagramme": diagramme, "guide": guide}
|
||||
"struktur": struktur, "guide": guide}
|
||||
|
||||
# Gewichte je Befund-Art: kritisch 3.0 (verletzt Kernprinzip), mittel 1.5, stil 0.5.
|
||||
GEWICHTE = {
|
||||
@@ -34,8 +33,6 @@ GEWICHTE = {
|
||||
"section_fehlt": 3.0, "marker_fehlend": 3.0, "marker_fremd": 3.0,
|
||||
"ziel_ohne_anker": 3.0, "fachlich_falsch": 3.0, "laenge": 0.5, "vorwaerts": 0.5,
|
||||
"det_check": 0.5, "fix_offen": 1.5,
|
||||
"diagramm_parse_fehler": 3.0, "diagramm_ungegroundet": 3.0,
|
||||
"diagramm_unverifiziert": 0.5, "diagramm_marker_tot": 3.0,
|
||||
"beispiel_marker_tot": 3.0,
|
||||
}
|
||||
|
||||
@@ -49,7 +46,6 @@ def _basis(topic: str, ebene: str) -> int:
|
||||
" AND status NOT IN ('gemerged','verworfen')",
|
||||
"struktur": "SELECT COUNT(*) n FROM atome WHERE topic=?"
|
||||
" AND status NOT IN ('gemerged','verworfen')",
|
||||
"diagramme": "SELECT COUNT(*) n FROM bausteine WHERE topic=?",
|
||||
"guide": "SELECT COUNT(*) n FROM bausteine WHERE topic=?"}[ebene]
|
||||
row = db.one(z, (topic,))
|
||||
return max(1, int(row["n"]) if row else 1)
|
||||
|
||||
Reference in New Issue
Block a user