Files
buchwerk/backend/gliederung.py
team3 140d6e4864 Umfang aus der Gliederung statt aus Zahlenvorgaben
Der Gliederungs-Agent vergibt je Abschnitt ein Gewicht (kurz/mittel/lang,
800/1300/2000 Wörter); Kapitel typisch 4 Abschnitte (2–7), Summe höchstens
8000 Wörter. Autor und Prüfer richten sich nach dem Richtwert, keine
Untergrenze mehr. Parser liest das Gewicht und prüft die Grenzen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 15:09:14 +02:00

61 lines
3.1 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.
"""Parser für buch/GLIEDERUNG.md (Format siehe prompts/gliederung.md)."""
import re
KAPITEL_RE = re.compile(r"^\*\*Kapitel (\d+) – (.+?)\*\* \(`([0-9]{2}-[a-z0-9-]+\.md)`\)\s*$")
TEIL_RE = re.compile(r"^## (Teil .+?)\s*$")
TITEL_RE = re.compile(r"^# Gliederung – (.+?)(?:\. (.+))?$")
# Richtwerte je Abschnitt (Wörter); ein Kapitel soll eine Lesesitzung bleiben.
RICHTWERT = {"kurz": 800, "mittel": 1300, "lang": 2000}
KAPITEL_MAX = 8000
def parsen(text: str) -> dict:
"""→ {"titel", "untertitel", "kapitel": [{"n","titel","datei","teil","abschnitte":[{"nr","titel","gewicht","richtwert"}]}], "fehler": [...]}"""
aus = {"titel": "", "untertitel": "", "kapitel": [], "fehler": []}
teil = ""
zeilen = text.splitlines()
i = 0
while i < len(zeilen):
z = zeilen[i].rstrip()
m = TITEL_RE.match(z)
if m and not aus["titel"]:
aus["titel"], aus["untertitel"] = m.group(1).strip(), (m.group(2) or "").strip()
elif TEIL_RE.match(z):
teil = TEIL_RE.match(z).group(1)
elif KAPITEL_RE.match(z):
m = KAPITEL_RE.match(z)
n = int(m.group(1))
k = {"n": n, "titel": m.group(2).strip(), "datei": m.group(3), "teil": teil, "abschnitte": []}
# nächste nicht-leere Zeile: Abschnitte
j = i + 1
while j < len(zeilen) and not zeilen[j].strip():
j += 1
if j < len(zeilen):
for teilstueck in re.split(r"\s+·\s+", zeilen[j].strip()):
am = re.match(r"^(\d+)\.(\d+)\s+(.+?)(?:\s*\((kurz|mittel|lang)\))?$", teilstueck.strip())
if am and int(am.group(1)) == n:
gewicht = am.group(4) or "mittel"
k["abschnitte"].append({"nr": f"{am.group(1)}.{am.group(2)}", "titel": am.group(3).strip(),
"gewicht": gewicht, "richtwert": RICHTWERT[gewicht]})
else:
aus["fehler"].append(f"Kapitel {n}: Abschnittszeile unlesbar: „{teilstueck.strip()[:60]}“")
i = j
if not k["datei"].startswith(f"{n:02d}-"):
aus["fehler"].append(f"Kapitel {n}: Dateiname muss mit {n:02d}- beginnen ({k['datei']})")
aus["kapitel"].append(k)
elif z.startswith("**Kapitel"):
aus["fehler"].append(f"Kapitelzeile nicht im Format: „{z[:80]}“")
i += 1
for k in aus["kapitel"]:
if not 2 <= len(k["abschnitte"]) <= 7:
aus["fehler"].append(f"Kapitel {k['n']} hat {len(k['abschnitte'])} Abschnitte (2–7 verlangt; mehr Stoff → Kapitel teilen)")
summe = sum(a["richtwert"] for a in k["abschnitte"])
if summe > KAPITEL_MAX:
aus["fehler"].append(f"Kapitel {k['n']}: Richtwerte summieren sich auf {summe} Wörter (höchstens {KAPITEL_MAX}) – Kapitel teilen oder Gewichte senken")
nummern = [k["n"] for k in aus["kapitel"]]
if nummern != list(range(1, len(nummern) + 1)):
aus["fehler"].append(f"Kapitelnummern nicht fortlaufend: {nummern}")
if not aus["kapitel"]:
aus["fehler"].append("Keine Kapitelzeile gefunden")
return aus