fix: Nebenpfade behalten Kontext – Gegenprobe, --von, --resolve, UNDERSTAND_RETRIES=-1

- UNDERSTAND_RETRIES=-1 (A/B-Arm der Verstehensstufe) stürzte seit dem Ablehnungstor ab
- Unlösbar-Gegenprobe bekommt Verständnis, Datenprofil und Datendateien; ihre Lösung
  durchläuft dieselbe Abnahme (_assess_run)
- --von pruefung behält Verständnis, Abbildungen und Datenprofil
- --resolve überschreibt frühere Befunde nicht mehr (nur Zulässigkeit neu)
- --daten wirkt auch mit --resolve und --von
- Prüfung und Korrektur sehen das Datenprofil
- Reparaturmeldung nennt alle Befundarten zugleich
- Test der Balkenhöhe prüft die Höhe tatsächlich; unbenutzte Imports entfernt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-18 13:22:53 +02:00
commit 3c4d2fb92c

View file

@ -457,7 +457,6 @@ if __name__ == "__main__":
def test_report_shows_data_profile_and_scenarios(tmp_path):
from core.model_schema import Scenario
from reporting.report import build_document, to_markdown
datei = tmp_path / "werte.csv"
datei.write_text("name,menge\nA,3\nB,4\n")
@ -609,3 +608,84 @@ def test_persistent_scenario_without_result_is_unreliable():
result = run_pipeline("Minimiere die Kosten.", session=session, max_revisions=0, max_restarts=0, solver_retries=1)
assert not result.reliable
assert any(p.startswith("Szenario ohne Ergebnis") for p in result.reliability_issues)
# --- Nebenpfade: Verstehensstufe aus, Gegenprobe und --von mit vollem Kontext ---
def test_pipeline_runs_with_understanding_stage_disabled(monkeypatch):
# UNDERSTAND_RETRIES=-1 war der A/B-Arm der Verstehensstufe; das Ablehnungstor griff dann auf None zu
import TextToModel
monkeypatch.setattr(TextToModel, "UNDERSTAND_RETRIES", -1)
session = FakeSession([GOOD], [OK], [SolverCode(code=STRUCTURED_CODE)])
result = run_pipeline("Aufgabe", session=session, max_revisions=0, max_restarts=0)
assert result.understanding is None and not result.rejected
assert session.steps[0] == "Extraktion"
assert result.solver_run.status == SolverStatus.OPTIMAL
def test_infeasible_recheck_keeps_understanding_and_data_files(tmp_path, monkeypatch):
# Ohne Datendateien fand das Programm der Gegenprobe seine CSV nicht, ohne Verständnis fehlten die Antworten
import TextToModel
aufrufe = []
echtes_solve = TextToModel.solve
def protokoll(*args, **kwargs):
aufrufe.append(kwargs)
return echtes_solve(*args, **kwargs)
monkeypatch.setattr(TextToModel, "solve", protokoll)
csv = tmp_path / "werte.csv"
csv.write_text("a,b\n1,2\n", encoding="utf-8")
session = FakeSession(specs=[GOOD, GOOD], reviews=[OK, OK],
codes=[SolverCode(code=INFEASIBLE_CODE), SolverCode(code=STRUCTURED_CODE)])
result = run_pipeline("Aufgabe", session=session, max_revisions=0, max_restarts=0, data_files=[str(csv)])
assert result.restarts == 1 and len(aufrufe) == 2
gegenprobe = aufrufe[1]
assert gegenprobe["understanding"] is not None
assert list(gegenprobe["data_files"]) == [str(csv)] and "werte.csv" in gegenprobe["data_prompt"]
assert "werte.csv" in session.tasks[1] # auch die neue Extraktion sieht das Datenprofil
def test_continue_from_review_keeps_understanding(tmp_path):
from TextToModel import continue_pipeline
path = stored_result(tmp_path)
session = FakeSession([], [OK], [SolverCode(code=STRUCTURED_CODE)])
result = continue_pipeline(str(path), "pruefung", session=session)
assert result.understanding is not None
assert [q.id for q in result.understanding.sub_questions] == ["1"]
assert "ZU BEANTWORTENDE TEILFRAGEN" in session.letzter_prompt # der Solver bekommt die Teilfragen
def test_review_and_revision_see_the_data_profile():
from checks.review import review_assumptions
from modeling.extraction import revise_model
session = FakeSession([GOOD], [OK], [])
review_assumptions(session, "Aufgabe", GOOD, "BEIGELEGTE DATEN: kurse.csv")
assert "kurse.csv" in session.letzter_prompt
revise_model(session, "Aufgabe", GOOD, OK, data_prompt="BEIGELEGTE DATEN: kurse.csv")
assert "kurse.csv" in session.letzter_prompt
def test_repair_message_names_every_kind_of_finding():
# Vorher erfuhr das LLM nur von der ersten Befundart und verbrauchte für die zweite einen eigenen Versuch
from solving.solver_stage import solve
code = '''
import numpy as np
def main():
"""Verletzt die Restriktion und erzeugt Zahlen per Zufall, ohne sie auszuweisen."""
np.random.seed(0)
report("optimal", 9.0, {"x": 1.0},
constraints=[{"name": "Bedarf", "lhs": 1.0, "sense": ">=", "rhs": 4.0}],
variables=[{"name": "x", "value": 1.0, "var_type": "continuous", "lb": 0, "ub": None}])
if __name__ == "__main__":
main()
'''
session = FakeSession([], [], [SolverCode(code=code), SolverCode(code=code)])
solve(session, "Aufgabe", GOOD, retries=1)
assert session.steps == ["Solver-Code", "Solver-Reparatur"]
assert "hält das Modell nicht ein" in session.letzter_prompt
assert "Eingabedaten nicht ausgewiesen" in session.letzter_prompt