2026-09-17 11:42:58 +02:00
|
|
|
|
"""Punkt 1: dasselbe Programm mit geänderten Daten neu rechnen – ohne LLM-Aufruf."""
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
refactor: Pipeline-Stufen in Ordner mit ihren Prompts
Im Wurzelverzeichnis liegt nur noch TextToModel.py (Ablauf und CLI).
Jede Stufe liegt mit Code und Prompt in einem eigenen Ordner: core,
understanding, modeling, checks, solving, reporting, data, benchmark;
Tests unter tests/, Aufgaben unter tasks/. understand_task,
text_to_model/revise_model und review_assumptions/needs_work/select_best
sind aus TextToModel.py zu ihren Prompts gewandert, inhaltlich
unveraendert. Prompt-Kennung des Benchmarks bleibt 93744636095a.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:11:51 +02:00
|
|
|
|
from core.model_schema import SolverStatus
|
|
|
|
|
|
from solving.solver_stage import run_solver_code
|
2026-09-17 11:42:58 +02:00
|
|
|
|
|
|
|
|
|
|
# Programm nach der Vorgabe des Solver-Prompts: alle Zahlen stehen in DATA
|
|
|
|
|
|
DATA_CODE = '''
|
|
|
|
|
|
from scipy.optimize import linprog
|
|
|
|
|
|
|
|
|
|
|
|
DATA = {
|
|
|
|
|
|
"kosten": [3, 2], # Kosten je Tonne [Tsd. €]
|
|
|
|
|
|
"bedarf": 4, # Mindestmenge [t]
|
|
|
|
|
|
"obergrenze": 3, # je Rohstoff lieferbar [t]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def solve_model():
|
|
|
|
|
|
"""Baut das LP aus DATA auf und löst es."""
|
|
|
|
|
|
grenze = DATA["obergrenze"]
|
|
|
|
|
|
return linprog(DATA["kosten"], A_ub=[[-1, -1]], b_ub=[-DATA["bedarf"]],
|
|
|
|
|
|
bounds=[(0, grenze), (0, grenze)], method="highs")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""Löst das Modell und meldet Ergebnis samt Restriktionen."""
|
|
|
|
|
|
res = solve_model()
|
|
|
|
|
|
x, y = float(res.x[0]), float(res.x[1])
|
|
|
|
|
|
report("optimal" if res.status == 0 else "error", float(res.fun), {"x": x, "y": y},
|
|
|
|
|
|
constraints=[{"name": "Bedarf", "lhs": x + y, "sense": ">=", "rhs": DATA["bedarf"]}],
|
|
|
|
|
|
variables=[{"name": "x", "value": x, "var_type": "continuous", "lb": 0, "ub": DATA["obergrenze"]}])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_without_override_uses_own_data():
|
|
|
|
|
|
run = run_solver_code(DATA_CODE)
|
|
|
|
|
|
assert run.status == SolverStatus.OPTIMAL
|
|
|
|
|
|
assert run.objective_value == pytest.approx(9) # 1t * 3 + 3t * 2
|
|
|
|
|
|
assert run.data["bedarf"] == 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_changed_data_is_used_without_touching_the_code():
|
|
|
|
|
|
run = run_solver_code(DATA_CODE, data={"bedarf": 5})
|
|
|
|
|
|
assert run.status == SolverStatus.OPTIMAL
|
|
|
|
|
|
assert run.objective_value == pytest.approx(12) # 2t * 3 + 3t * 2
|
|
|
|
|
|
assert run.data["bedarf"] == 5 and run.data["kosten"] == [3, 2] # nur der geänderte Wert wird ersetzt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_constraints_follow_the_changed_data():
|
|
|
|
|
|
run = run_solver_code(DATA_CODE, data={"bedarf": 5})
|
|
|
|
|
|
assert run.constraints[0].rhs == 5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_infeasible_data_is_reported_not_crashing():
|
|
|
|
|
|
run = run_solver_code(DATA_CODE, data={"bedarf": 99})
|
|
|
|
|
|
assert run.status in (SolverStatus.ERROR, SolverStatus.INFEASIBLE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- CLI-Modus: gespeichertes Ergebnis neu rechnen ---
|
|
|
|
|
|
|
|
|
|
|
|
def gespeichertes_ergebnis(tmp_path):
|
refactor: Pipeline-Stufen in Ordner mit ihren Prompts
Im Wurzelverzeichnis liegt nur noch TextToModel.py (Ablauf und CLI).
Jede Stufe liegt mit Code und Prompt in einem eigenen Ordner: core,
understanding, modeling, checks, solving, reporting, data, benchmark;
Tests unter tests/, Aufgaben unter tasks/. understand_task,
text_to_model/revise_model und review_assumptions/needs_work/select_best
sind aus TextToModel.py zu ihren Prompts gewandert, inhaltlich
unveraendert. Prompt-Kennung des Benchmarks bleibt 93744636095a.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:11:51 +02:00
|
|
|
|
from core.model_schema import PipelineResult, SolverCode
|
2026-09-17 11:42:58 +02:00
|
|
|
|
from test_model_checks import make_spec
|
|
|
|
|
|
from test_review import make_review
|
|
|
|
|
|
result = PipelineResult(task="Aufgabe", specification=make_spec(), review=make_review(), revisions=0,
|
|
|
|
|
|
solver_code=SolverCode(code=DATA_CODE))
|
|
|
|
|
|
pfad = tmp_path / "ergebnis.json"
|
|
|
|
|
|
pfad.write_text(result.model_dump_json(indent=2), encoding="utf-8")
|
|
|
|
|
|
return pfad
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_without_data_reproduces_the_result(tmp_path):
|
|
|
|
|
|
from TextToModel import resolve_result
|
|
|
|
|
|
neu = resolve_result(str(gespeichertes_ergebnis(tmp_path)))
|
|
|
|
|
|
assert neu.solver_run.objective_value == pytest.approx(9)
|
|
|
|
|
|
assert neu.reliable and neu.usage is None # kein LLM-Aufruf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_with_changed_data(tmp_path):
|
|
|
|
|
|
import json
|
|
|
|
|
|
from TextToModel import resolve_result
|
|
|
|
|
|
daten = tmp_path / "daten.json"
|
|
|
|
|
|
daten.write_text(json.dumps({"bedarf": 5}), encoding="utf-8")
|
|
|
|
|
|
neu = resolve_result(str(gespeichertes_ergebnis(tmp_path)), str(daten))
|
|
|
|
|
|
assert neu.solver_run.objective_value == pytest.approx(12)
|
|
|
|
|
|
assert neu.solver_run.data["bedarf"] == 5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_keeps_specification_and_checks_feasibility(tmp_path):
|
|
|
|
|
|
from TextToModel import resolve_result
|
|
|
|
|
|
neu = resolve_result(str(gespeichertes_ergebnis(tmp_path)))
|
|
|
|
|
|
assert neu.specification.problem_class.value.startswith("Linear")
|
|
|
|
|
|
assert neu.solver_run.feasibility == []
|
2026-09-18 13:22:53 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_keeps_earlier_reliability_issues(tmp_path):
|
|
|
|
|
|
# Neurechnen prüft nur die Zulässigkeit neu – eine falsche Zielgröße bleibt ein Befund
|
|
|
|
|
|
from core.model_schema import PipelineResult
|
|
|
|
|
|
from TextToModel import resolve_result
|
|
|
|
|
|
pfad = gespeichertes_ergebnis(tmp_path)
|
|
|
|
|
|
alt = PipelineResult.model_validate_json(pfad.read_text(encoding="utf-8"))
|
|
|
|
|
|
alt.reliable = False
|
|
|
|
|
|
alt.reliability_issues = ["Zielfunktion passt nicht zur Frage", "Zulässigkeitsprüfung: alter Befund"]
|
|
|
|
|
|
pfad.write_text(alt.model_dump_json(), encoding="utf-8")
|
|
|
|
|
|
neu = resolve_result(str(pfad))
|
|
|
|
|
|
assert neu.reliability_issues == ["Zielfunktion passt nicht zur Frage"] # alter Zulässigkeitsbefund entfällt
|
|
|
|
|
|
assert not neu.reliable
|