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>
61 lines
3.1 KiB
Python
61 lines
3.1 KiB
Python
"""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
|