feat: Erfundene Einheiten in Antworten prüfen; data_assumptions-Kanal klargestellt

check_answer_units meldet Einheiten hinter Zahlen, die weder Aufgabentext noch
Datenprofil hergeben (nach Größenart, GE/ME neutral bei Kosten-/Mengenwörtern);
Befund löst Solver-Reparatur aus, bleibt er, Warnung. Reparaturmeldung und
Solver-Prompt nennen data_assumptions jetzt als Antwortfeld, nicht report()-Argument.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 14:07:57 +02:00
commit c68a293755
2 changed files with 107 additions and 5 deletions

View file

@ -422,7 +422,7 @@ if __name__ == "__main__":
sub_questions=[SubQuestion(id="a", quote="Formulieren Sie das Modell!", kind="formulieren", needs_solver=False),
SubQuestion(id="b", quote="Berechnen Sie die Kosten!", kind="rechnen", needs_solver=True)])
session = FakeSession([GOOD], [OK], [SolverCode(code=code)], understandings=[verstanden])
result = run_pipeline("Aufgabe", session=session, max_revisions=0, max_restarts=0)
result = run_pipeline("Minimiere die Kosten.", session=session, max_revisions=0, max_restarts=0)
markdown = to_markdown(build_document(result))
assert "Antworten auf die Aufgabe" in markdown
assert markdown.index("Formulieren Sie das Modell!") < markdown.index("| Status |")

View file

@ -251,3 +251,45 @@ def test_decomposition_with_one_term_equal_to_objective_is_still_checked():
table = dict(DECOMPOSITION, rows=[["Rendite", 0.097557], ["Kosten", -0.05], ["Zielwert", 0.2]])
_, warnings = parse_result_tables([table], objective_value=0.097557)
assert any("weicht vom Zielwert" in w for w in warnings)
# --- Einheiten in Antworten: nur, was der Text hergibt ---
def _run_with_answer(text):
from core.model_schema import Answer
return SolverRun(status=SolverStatus.OPTIMAL, objective_value=459, answers=[Answer(question_id="1", text=text)])
def test_invented_unit_in_answer_is_found():
# beobachtet an tasks/nur_formeln.md: reines ILP ohne jede Einheit
from solving.solver_stage import check_answer_units
befunde = check_answer_units(_run_with_answer("Der minimale Zielfunktionswert beträgt 459 Geldeinheiten (GE)."),
"min z = 17*x1 + 23*x2 s.t. 6*x1 + 4*x2 >= 120")
assert len(befunde) == 1 and "Geldeinheiten" in befunde[0]
def test_currency_is_invented_when_the_text_only_says_costs():
from solving.solver_stage import check_answer_units
aufgabe = "Die Kostenmatrix C enthält die Kosten je Nutzungsdauer."
assert check_answer_units(_run_with_answer("Die Gesamtkosten betragen 29 €."), aufgabe)
assert check_answer_units(_run_with_answer("Die Gesamtkosten betragen 29 GE."), aufgabe) == [] # neutral
def test_unit_of_the_same_kind_in_the_text_is_covered():
from solving.solver_stage import check_answer_units
aufgabe = "Kosten in Tausend Euro; Rüstzeit 2 Stunden."
run = _run_with_answer("Die Kosten betragen 29 Tsd. € bei 90 min Rüstzeit, Auslastung 85 %.")
assert check_answer_units(run, aufgabe) == []
def test_unit_from_attached_data_is_covered():
from solving.solver_stage import check_answer_units
run = _run_with_answer("Der Transport kostet 120 kg CO2.")
assert check_answer_units(run, "Minimiere die Emissionen.") != []
assert check_answer_units(run, "Minimiere die Emissionen.", data_summary="Spalte emission_kg") == []
def test_numbers_and_words_are_not_mistaken_for_units():
from solving.solver_stage import check_answer_units
run = _run_with_answer("Teil 2 zeigt: x1 = 6, 3 Maschinen, Stufe 2a, am Tag 3 sind alle 5 Aufträge erledigt.")
assert check_answer_units(run, "Plane die Aufträge.") == []