This commit is contained in:
team3
2026-07-27 11:29:57 +02:00
parent 15ad6dfe8a
commit 1c8a4fd4fd
30 changed files with 1133 additions and 173 deletions

107
backend/struktur.py Normal file
View File

@@ -0,0 +1,107 @@
"""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"]}'