fix: Index-Platzhalter im Namensvertrag, englische Einheiten, fett gesetzte Teilfragen

- Routing-Prompt: Restriktionsnamen als Muster beschrieben statt kopierbarer Beispiele,
  kein Indexbuchstabe im identifier (Weiter_ab_Woche_k löste in 3/3 Läufen eine
  unnötige Solver-Reparatur aus)
- check_constraint_coverage: identifier …_k passt zu …_2, nur ohne direkten Treffer
  (nonneg_x/nonneg_y bleiben getrennt)
- check_answer_units: englische Einheitenwörter (tons, hours, euros …); vorher strich
  eine Reparatur die richtige Einheit aus einer Antwort auf eine englische Aufgabe
- QUESTION_PATTERN erkennt **Teil 1:** Text

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 16:50:27 +02:00
commit 915c79c1c8
3 changed files with 61 additions and 10 deletions

View file

@ -149,3 +149,24 @@ def test_relaxation_bound_equal_up_to_rounding_is_no_warning():
# beobachtet (Portfolio Nr. 2): 0.13896862211718014 gegen 0.13896862211718017
run = sens_run([], objective=0.13896862211718017, relaxation=0.13896862211718014)
assert check_sensitivity(run, "maximize") == []
def test_index_placeholder_in_identifier_matches_the_written_out_index():
# beobachtet (20260917-164603): Spezifikation „Weiter_ab_Woche_k“, Programm „Weiter_ab_Woche_2“ – je eine Reparatur
from test_model_checks import make_spec
spec = make_spec(constraints=[
{"identifier": "Start_Woche_1", "latex_formula": "x = 1", "description": "-", "is_hard_constraint": True},
{"identifier": "Weiter_ab_Woche_k", "latex_formula": "x = y", "description": "-", "is_hard_constraint": True}])
assert check_constraint_coverage([Named("Start_Woche_1"), Named("Weiter_ab_Woche_2"), Named("Weiter_ab_Woche_3")], spec) == []
# ein anderer Name mit gleichem Anfang bleibt ein Befund
assert check_constraint_coverage([Named("Start_Woche_1"), Named("Weiter_ab_Monat_2")], spec)
def test_real_names_ending_in_a_letter_are_not_placeholders():
# Rückrechnung hard_transport_lagerwahl: nonneg_x und nonneg_y sind zwei echte Restriktionen
from test_model_checks import make_spec
spec = make_spec(constraints=[
{"identifier": "nonneg_x", "latex_formula": "x >= 0", "description": "-", "is_hard_constraint": True},
{"identifier": "nonneg_y", "latex_formula": "y >= 0", "description": "-", "is_hard_constraint": True}])
assert check_constraint_coverage([Named("nonneg_x"), Named("nonneg_y")], spec) == []
assert check_constraint_coverage([Named("nonneg_x")], spec) == ["Restriktion 'nonneg_y' aus der Spezifikation fehlt im Programm"]

View file

@ -469,3 +469,10 @@ def test_english_sub_question_markers_are_recognised():
for zeile in ("Part 2: Solve the model", "* **Question 1: How many units**", "- Task 3: Compare"):
assert QUESTION_PATTERN.search(zeile), zeile
assert not QUESTION_PATTERN.search("In part 2 of the text we see") # Fließtext bleibt Fließtext
def test_bold_marker_with_colon_inside_is_recognised():
# vorher nur "**Teil 1: Daten**", nicht "**Teil 1:** Daten" (Doppelpunkt vor dem schließenden **)
from checks.model_checks import QUESTION_PATTERN
for zeile in ("**Teil 1:** Daten laden", "* **Question 2:** Solve", "**a)** Graph zeichnen"):
assert QUESTION_PATTERN.search(zeile), zeile

View file

@ -337,3 +337,11 @@ def test_marks_without_dijkstra_steps_are_still_flagged():
def test_everyday_marked_is_no_jargon():
from checks.model_checks import unexplained_jargon
assert unexplained_jargon("nur die mit 'ja' markierten Kombinationen gehören zum Plan", "Zusammenfassung") == []
def test_english_unit_words_cover_german_answers():
# beobachtet: lp_klein auf Englisch – „1 Tonne“ galt als erfunden, weil die Aufgabe „tons“ schreibt
from solving.solver_stage import check_answer_units
aufgabe = "One ton of raw material X costs 3 thousand euros; at least 4 tons are required."
assert check_answer_units(_run_with_answer("Es sollten 1 Tonne X und 3 Tonnen Y gekauft werden, Kosten 9 Tausend Euro."), aufgabe) == []
assert check_answer_units(_run_with_answer("It takes 12 hours."), "Minimize the cost.") != [] # erfunden bleibt erfunden