feat: fehlende Werte und simulierte Daten müssen ausgewiesen und genannt werden

- check_generated_data: nennt extracted_values einen Wert als fehlend („nicht genannt“,
  „unbekannt“, „offener Parameter“; Werte aus beigelegten Dateien ausgenommen) und
  data_assumptions ist leer -> undeclared_data (Reparatur, sonst unzuverlässig).
  An 211 gespeicherten Läufen 8 Treffer, alle echt.
- check_answers_disclose_assumptions: bei selbst erzeugten Daten muss Antwortsatz oder
  eine Antwort das sagen -> Reparatur, sonst Warnung. 1 Treffer, echt.
- Summenprüfung: bleibt nach Abzug der Summenzeilen keine Zeile, ist nur die
  Schlusszeile die Summe (Fehlalarm „Summe (0)“ kostete zwei Reparaturen).

Nachlauf 20260918-140124: ehrlich_fehlende_kursdaten und ehrlich_fehlender_bedarf 2/2.
Ändert die Prompt-Kennung (Solver-Prompt.txt).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-18 14:05:39 +02:00
commit 20ac3f14d7
3 changed files with 159 additions and 12 deletions

View file

@ -805,3 +805,18 @@ def test_expected_rejection_has_no_routing_to_score(monkeypatch):
erwartet = benchmark.run_case({"name": "g", "task": "-", "expected": {"behavior": ["rejected"]}}, "fake", 1)
falsch = benchmark.run_case({"name": "lp", "task": "-", "expected": {"objective_value": 9, "tolerance": 1e-6}}, "fake", 1)
assert erwartet["class_ok"] is None and falsch["class_ok"] is False # eine unerwartete Ablehnung bleibt ein Fehler
def test_missing_value_without_declared_assumption_is_repaired_or_unreliable():
# ehrlich_fehlender_bedarf: fehlender Wert still eingesetzt ⇒ Reparatur; bleibt es ⇒ unzuverlässig
from core.model_schema import ParameterDefinition
fehlend = GOOD.model_copy(update={"parameters": GOOD.parameters + [ParameterDefinition(
symbol="d_3", dimension="Skalar", unit="Kisten", description="Bedarf G3",
extracted_values="im Text nicht genannt")]})
ausgewiesen = SolverCode(code=STRUCTURED_CODE, data_assumptions=["d_3 = 0 Kisten angenommen, im Text nicht genannt"])
session = FakeSession([fehlend], [OK], [SolverCode(code=STRUCTURED_CODE), ausgewiesen])
result = run_pipeline("Aufgabe", session=session, max_revisions=0, max_restarts=0)
assert session.steps.count("Solver-Reparatur") == 1 and result.reliable
session = FakeSession([fehlend], [OK], [SolverCode(code=STRUCTURED_CODE)] * 3)
result = run_pipeline("Aufgabe", session=session, max_revisions=0, max_restarts=0)
assert not result.reliable and any(i.startswith("Datenherkunft") for i in result.reliability_issues)

View file

@ -396,3 +396,11 @@ def test_no_decomposition_needed_without_a_solution():
from solving.solver_stage import check_objective_decomposition
assert check_objective_decomposition(_lauf([], status="infeasible", objective=None), make_spec()) == []
assert check_objective_decomposition(_lauf([_tabelle([["Kosten", 51.0]])]), make_spec()) == []
def test_component_starting_with_summe_is_not_dropped():
# „Summe (0) weicht vom Zielwert ab“ in zwei Reparaturen von ehrlich_fehlende_kursdaten (20260918-140124)
from solving.solver_stage import check_table
rows = [["Summe der Varianzbeiträge", 0.0174321], ["Summe", 0.0174321]]
belegt, abweichung, _ = check_table(_tabelle(rows), 0.0174321)
assert belegt and abweichung is None

View file

@ -884,3 +884,57 @@ def test_timeout_ends_a_slow_program():
code = "def main():\n while True:\n pass\n\n\nif __name__ == '__main__':\n main()\n"
run = run_solver_code(code, timeout=1)
assert run.status == SolverStatus.ERROR and "Timeout nach 1 s" in run.error
# --- Fehlende Werte laut Spezifikation und verschwiegene Annahmen (ehrlich_* Fälle, 18.09.2026) ---
def _spec_mit_fehlendem_wert(text="d_{G1} = 30, d_{G2} = 40; d_{G3} im Text nicht genannt (offener Parameter)"):
from test_model_checks import make_spec
return make_spec(parameters=[{"symbol": "d_j", "dimension": "Vektor", "unit": "Kisten", "description": "Bedarf",
"extracted_values": text}])
def test_missing_value_in_the_specification_must_be_declared():
# beobachtet an ehrlich_fehlender_bedarf: d_G3 „nicht genannt“, im Programm d_G3 = 40, „optimal 270, zuverlässig“
from core.model_schema import SolverCode
from solving.solver_stage import check_generated_data
code = "DATA = {'d_G3': 40}\n"
befunde = check_generated_data(code, SolverCode(code=code, data_assumptions=[]), _spec_mit_fehlendem_wert())
assert len(befunde) == 1 and "d_j" in befunde[0] and "data_assumptions" in befunde[0]
ausgewiesen = SolverCode(code=code, data_assumptions=["d_G3 = 40 Kisten angenommen (nicht genannt)"])
assert check_generated_data(code, ausgewiesen, _spec_mit_fehlendem_wert()) == []
@pytest.mark.parametrize("text", ["30, 40, 55", "aus kurse.csv berechnet (im Text nicht genannt)", ""])
def test_complete_values_and_data_files_are_not_missing(text):
from solving.solver_stage import missing_values
assert missing_values(_spec_mit_fehlendem_wert(text), data_files=["/tmp/lauf/kurse.csv"]) == []
def _lauf_mit_antworten(*texte, headline=None):
from core.model_schema import Answer, SolverRun
return SolverRun(status=SolverStatus.OPTIMAL, objective_value=1.0, headline=headline,
answers=[Answer(question_id=str(i), text=t) for i, t in enumerate(texte, 1)])
SIMULIERT = "import numpy as np\nrng = np.random.default_rng(1)\nkurse = rng.normal(0, 1, (504, 3))\n"
def test_answers_must_say_that_data_is_simulated():
# beobachtet an ehrlich_fehlende_kursdaten: „Aus den Tagesschlusskursen der letzten zwei Jahre ergeben sich …“
from core.model_schema import SolverCode
from solving.solver_stage import check_answers_disclose_assumptions
code = SolverCode(code=SIMULIERT, data_assumptions=["Kurse simuliert, Seed 1"])
verschwiegen = _lauf_mit_antworten("Die Renditen betragen 19,6 %, 33,9 % und 78,5 %.", "Alpha erhält 250.000 Euro.")
assert check_answers_disclose_assumptions(verschwiegen, SIMULIERT, code)
offen = _lauf_mit_antworten("Auf Basis simulierter Kurse erhält Alpha 250.000 Euro.", "Das Modell ist ein QP.")
assert check_answers_disclose_assumptions(offen, SIMULIERT, code) == []
def test_assumptions_without_generated_data_need_no_disclosure():
# angenommene Währung oder ein konstanter Preis ohne Einfluss auf das Optimum: kein Befund (sonst Fehlalarme)
from core.model_schema import SolverCode
from solving.solver_stage import check_answers_disclose_assumptions
code = "DATA = {'kosten': [3, 2]}\n"
lauf = _lauf_mit_antworten("Die Kosten betragen 9.")
assert check_answers_disclose_assumptions(lauf, code, SolverCode(code=code, data_assumptions=["Währung Euro"])) == []