update
This commit is contained in:
@@ -7,8 +7,12 @@ setzen (Lektion 57). Der Prüfer ist EIN verschmolzener Call (Fakten+Coverage,
|
||||
Lektion 52); deterministische Checks laufen davor im Code."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import db
|
||||
import llm
|
||||
@@ -17,6 +21,33 @@ from config import SECTION_WOERTER_PRO_ATOM
|
||||
|
||||
log = logging.getLogger("creator2.guide")
|
||||
|
||||
_KATEX_SKRIPT = Path(__file__).resolve().parent / "katex_check.mjs"
|
||||
_KATEX_BASE = Path(__file__).resolve().parent.parent / "frontend"
|
||||
_katex_gewarnt = False
|
||||
|
||||
|
||||
def _katex_fehler(formeln: list[dict]) -> list[str]:
|
||||
"""Parse-Gate: Formeln durch DENSELBEN Renderer wie das Frontend jagen.
|
||||
Regex-Checks jagen eine offene Menge (heute \\textsc, morgen der nächste
|
||||
unbekannte Befehl) — das Gate schließt die Klasse. Fake-Modus überspringt
|
||||
(deterministisch + schnell); Unit-Test ruft direkt."""
|
||||
global _katex_gewarnt
|
||||
if not formeln or os.getenv("CREATOR_FAKE_AGENTS"):
|
||||
return []
|
||||
try:
|
||||
res = subprocess.run(
|
||||
["node", str(_KATEX_SKRIPT)], input=json.dumps(formeln),
|
||||
capture_output=True, text=True, timeout=30,
|
||||
env={**os.environ, "KATEX_BASE": str(_KATEX_BASE)})
|
||||
if res.returncode:
|
||||
raise RuntimeError(res.stderr.strip()[:200])
|
||||
return json.loads(res.stdout)
|
||||
except Exception as e: # nie fail-open OHNE Signal (Befund-Prinzip)
|
||||
if not _katex_gewarnt:
|
||||
_katex_gewarnt = True
|
||||
log.warning("KaTeX-Gate nicht verfügbar (%s) — Formel-Validierung fällt aus", e)
|
||||
return []
|
||||
|
||||
EBENE = "guide"
|
||||
_MARKER = re.compile(r"<!--\s*atom:\s*(\d+)\s*\|")
|
||||
|
||||
@@ -125,6 +156,24 @@ def _mathe_auftraege(text: str, wo: str) -> list[str]:
|
||||
if reste:
|
||||
auftraege.append(f"{wo}: Formeln im Klartext ({', '.join(reste[:4])}…):"
|
||||
f" auch Indizes/Potenzen gehören in $…$ (x_i → $x_i$).")
|
||||
betraege = sorted(set(re.findall(r"\|[A-Za-z][^|\n]{0,8}\|\*?", ohne)))
|
||||
if betraege: # |C*| im Klartext: Markdown deutet * als Kursiv-Start
|
||||
auftraege.append(f"{wo}: Beträge im Klartext ({', '.join(betraege[:4])}…):"
|
||||
f" in $…$ setzen ($|C^*|$) — sonst kollidiert * mit Markdown.")
|
||||
for z in ohne.splitlines(): # einzelnes * = Kursiv-Unfall (nur **fett** erlaubt)
|
||||
z = re.sub(r"^\s*[*-]\s", "", z)
|
||||
if re.search(r"(?<!\*)\*(?!\*)", z):
|
||||
auftraege.append(f"{wo}: einzelnes „*“ im Fließtext startet ungewollt"
|
||||
f" Kursivschrift — Sterne nur als **fett** oder in $…$.")
|
||||
break
|
||||
if re.search(r"\\n(?![a-zA-Z])", text): # \neq etc. bleiben unberührt
|
||||
auftraege.append(f"{wo}: literales „\\n“ im Text — durch echten"
|
||||
f" Zeilenumbruch bzw. Leerzeile ersetzen.")
|
||||
formeln = ([{"tex": m, "display": True} for m in re.findall(r"\$\$([\s\S]+?)\$\$", text)]
|
||||
+ [{"tex": m, "display": False} for m in re.findall(r"\$([^$\n]+?)\$", text)])
|
||||
for f in _katex_fehler(formeln):
|
||||
auftraege.append(f"{wo}: Formel rendert nicht — {f}. In gültiges KaTeX"
|
||||
f" umschreiben (Standardbefehle, kein LaTeX-Textsatz).")
|
||||
return auftraege
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user