"""Stufe 1 der Prüfung: alles, was sich zählen lässt, zählt der Code. Das Modell erfand hier Regeln, die im Katalog nicht stehen — „Im" als Füllwort, Satzzeichen, wo keins ist. Diese Klasse ist jetzt abschließend hier erledigt, und der Prompt verbietet dem Modell, sie noch einmal zu melden. Ein Befund mit Vorschlag geht ohne Anfrage durch. Ein Befund ohne Vorschlag heißt: der Code weiß, dass die Stelle falsch ist, aber nicht, was richtig wäre — den Auftrag bekommt das Modell. """ import re # Höchstzahl Wörter je Zeile, nach (bereich, feld) WORTGRENZEN = { ("feature", "titel"): 3, ("funktion", "titel"): 5, ("feature", "regeln"): 10, ("funktion", "regeln"): 10, ("funktion", "testen"): 8, } ZIEL_SAETZE = 2 ZIEL_WOERTER = 12 # Abschließend. Genau diese Wörter, keine sinnverwandten. FUELLWOERTER = ("auch", "noch", "einfach", "jeweils", "ggf") BADGE = ("kern", "optional") ZEILENFELDER = ("regeln", "testen") SATZENDE = ".;" _KLAMMER = re.compile(r"[()/]") _STRICH = re.compile(r"^[-*]\s*") def _zeilen(feld: str, text: str) -> list[str]: """regeln und testen sind Listen, alles andere ist ein Block.""" if feld in ZEILENFELDER: return [_STRICH.sub("", z.strip()) for z in text.splitlines() if z.strip()] return [text.strip()] if text.strip() else [] def _problem(feld: str, original: str, problem: str, vorschlag: str = "") -> dict: return {"feld": feld, "original": original, "problem": problem, "vorschlag": vorschlag} def _ohne_wort(zeile: str, wort: str) -> str: neu = re.sub(rf"\s*\b{re.escape(wort)}\b\.?", "", zeile, flags=re.I).strip() return re.sub(r"\s{2,}", " ", neu) def _reparierbar(zeile: str) -> tuple[list[str], str]: """Verstöße, die sich wegstreichen lassen, samt der bereinigten Zeile.""" gruende, neu = [], zeile if neu[-1] in SATZENDE: gruende.append("Satzzeichen am Zeilenende") neu = neu[:-1].rstrip() for wort in FUELLWOERTER: if re.search(rf"\b{wort}\b", neu, re.I): gruende.append(f"Füllwort „{wort}“") neu = _ohne_wort(neu, wort) return gruende, neu def _umschreiben(bereich: str, feld: str, zeile: str) -> list[str]: """Verstöße, die der Code sieht, aber nicht beheben kann.""" gruende = [] if _KLAMMER.search(zeile): gruende.append("Klammer oder Schrägstrich") grenze = WORTGRENZEN.get((bereich, feld)) if grenze and len(zeile.split()) > grenze: gruende.append(f"Mehr als {grenze} Wörter") if feld == "ziel": saetze = [s.strip() for s in re.split(r"[.!?]+", zeile) if s.strip()] if len(saetze) > ZIEL_SAETZE: gruende.append(f"Mehr als {ZIEL_SAETZE} Sätze") if any(len(s.split()) > ZIEL_WOERTER for s in saetze): gruende.append(f"Satz mit mehr als {ZIEL_WOERTER} Wörtern") return gruende def _zeile(bereich: str, feld: str, zeile: str) -> list[dict]: """Höchstens ein Befund je Zeile. Zwei Vorschläge für dieselbe Zeile wären nicht unabhängig — der zweite fände seine Stelle nach dem ersten nicht mehr.""" gruende, neu = _reparierbar(zeile) offen = _umschreiben(bereich, feld, zeile) if offen: # Muss ohnehin neu geschrieben werden — dann alles in einem Zug, vom Modell return [_problem(feld, zeile, ", ".join(gruende + offen))] return [_problem(feld, zeile, ", ".join(gruende), neu)] if gruende else [] def befunde(bereich: str, felder: dict) -> list[dict]: """Alle messbaren Verstöße einer Prüfstelle, in Reihenfolge der Felder.""" raus = [] for feld, text in felder.items(): if feld == "badge": if text and text not in BADGE: raus.append(_problem(feld, text, "Weder kern noch optional", "kern")) continue for zeile in _zeilen(feld, text or ""): raus += _zeile(bereich, feld, zeile) return raus def auftrag(befund: dict) -> str: """Zeile für das Modell: der Code hat gemessen, den Ersatz schreibt das Modell.""" return f'- {befund["feld"]} „{befund["original"]}“: {befund["problem"]}'