fix: Umlaute im Code erlaubt, Lesertexte still berichtigt

Ursache der Umschreibungen ist kein Encoding-Problem: das LLM schreibt
im erzeugten Python-Code ASCII (Portfolio Nr. 2: Code-Strings 0 echte
gegen 60 umschriebene Umlaute, Spezifikation 128 echte). Die
Prompt-Regel wurde ignoriert, die Warnung stand in fast jedem Lauf.

- Solver-Prompt: Umschreibungen in Bezeichnern, DATA-Schluesseln,
  Restriktionsnamen und Kommentaren erlaubt; echte Umlaute nur in
  Lesertexten, Regel direkt bei den report()-Argumenten.
- check_text_quality meldet keine Umlaute mehr (Fachbegriffe bleiben).
- umlaut_map baut je Lauf ein Woerterbuch aus Aufgabe und Spezifikation;
  repair_texts wendet es vor UMLAUT_STEMS an. ASCII-Formen aus dem
  Aufgabentext (Namen) bleiben, Restriktions-/Variablennamen unberuehrt.

An vier gespeicherten Laeufen: alle Umschreibungen in Lesertexten
berichtigt, 11 Ersetzungen, keine falsch. Prompt-Kennung 694c3eb3d59a.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 13:28:43 +02:00
commit 0d9b5d718f

View file

@ -156,15 +156,15 @@ def test_report_shows_multistart_stability():
assert "nicht nachweislich das globale Optimum" in markdown
def test_transliterated_umlauts_are_reported():
# beobachtet: "Der guenstigste Ersatzplan", Tabelle "Gewaehlte Nutzungszeitraeume"
def test_transliterated_umlauts_are_no_longer_reported():
# Umschreibungen im Code sind erlaubt; Lesertexte werden still berichtigt statt gemeldet
from core.model_schema import ResultTable, SolverRun, SolverStatus
from solving.solver_stage import check_text_quality
run = SolverRun(status=SolverStatus.OPTIMAL, headline="Der guenstigste Plan kostet 29 €.",
tables=[ResultTable(title="Gewaehlte Zeitraeume", columns=["Kauf"], rows=[])])
befunde = check_text_quality(run)
assert any("guenstigste" in b for b in befunde)
assert any("Gewaehlte" in b and "Zeitraeume" in b for b in befunde)
assert check_text_quality(run) == []
befunde = check_text_quality(run, summary="Es wird ein Plan gewählt.")
assert not any("Umlaut" in b for b in befunde)
def test_correct_german_is_not_flagged():
@ -177,16 +177,14 @@ def test_correct_german_is_not_flagged():
assert check_text_quality(run) == []
def test_transliterations_are_repaired_after_being_reported():
# Die Warnung dokumentiert den Prompt-Verstoss, der Report soll trotzdem lesbar sein
def test_transliterations_are_repaired_silently():
from core.model_schema import Answer, ResultTable, SolverRun, SolverStatus
from solving.solver_stage import check_text_quality, fix_transliterations, repair_texts
from solving.solver_stage import repair_texts
run = SolverRun(status=SolverStatus.OPTIMAL, headline="Der guenstigste Plan kostet 29 €.",
answers=[Answer(question_id="a", text="Die Loesung waehlt zwei Aktien.")],
tables=[ResultTable(title="Gewaehlte Zeitraeume", columns=["Naechste Periode"],
rows=[["gewaehlt", 3]], note="Gemaess Aufgabe.")])
assert check_text_quality(run) # erst melden
repair_texts(run) # dann beheben
repair_texts(run)
assert run.headline == "Der günstigste Plan kostet 29 €."
assert run.answers[0].text == "Die Lösung wählt zwei Aktien."
assert run.tables[0].title == "Gewählte Zeiträume"
@ -205,3 +203,28 @@ def test_repair_covers_stems_seen_in_the_lot_sizing_case():
# beobachtet in wagner_whitin_dijkstra: "110 Baelle", "Das heisst:" im Antworttext
from solving.solver_stage import fix_transliterations
assert fix_transliterations("Das heisst: 110 Baelle, der Ball") == "Das heißt: 110 Bälle, der Ball"
def test_words_from_the_run_repair_what_the_fixed_list_misses():
# beobachtet in "Portfolio Nr. 2": Volatilitaet, Kardinalitaet, vollstaendigen, binaere im Report
from core.model_schema import Answer, ConstraintValue, SolverRun, SolverStatus
from solving.solver_stage import repair_texts, umlaut_map
aufgabe = "Datenlieferant: Mueller GmbH."
spezifikation = ("Minimiere die Volatilität unter der Kardinalität k; nur Titel mit vollständigen Reihen, "
"binäre Auswahl. Restriktion: Kardinalitaet") # ASCII-Bezeichner des LLM
run = SolverRun(status=SolverStatus.OPTIMAL,
headline="Volatilitaet 12 %, Kardinalitaet eingehalten.",
answers=[Answer(question_id="1", text="Nur Titel mit vollstaendigen Reihen, binaere Variablen; Quelle: Mueller GmbH.")],
constraints=[ConstraintValue(name="Kardinalitaet", lhs=20, sense="==", rhs=20)])
repair_texts(run, umlaut_map(aufgabe, spezifikation))
assert run.headline == "Volatilität 12 %, Kardinalität eingehalten."
assert run.answers[0].text == "Nur Titel mit vollständigen Reihen, binäre Variablen; Quelle: Mueller GmbH."
assert run.constraints[0].name == "Kardinalitaet" # Bezeichner bleibt: Namensabgleich hängt daran
def test_ascii_form_present_in_the_task_is_not_replaced():
# steht die ASCII-Form selbst im Text (z. B. ein Name), ist sie echt
from solving.solver_stage import fix_transliterations, umlaut_map
zuordnung = umlaut_map("Herr Müller und die Firma Mueller liefern.")
assert "mueller" not in zuordnung
assert fix_transliterations("Firma Mueller", zuordnung) == "Firma Mueller"