feat: Warnung bei ungeschützter Sandbox, Prüf-Regel für konstante Kosten, Grund je Korrekturrunde

- sandbox_warning: fehlt bubblewrap oder ist SOLVER_SANDBOX=aus, steht das im Log
  und in SolverRun.warnings (vorher lief der Code stillschweigend ungeschützt)
- Dijkstra-Markierung: der Report erklärt den Begriff selbst (DIJKSTRA_EXPLANATION),
  check_text_quality zählt das als Erklärung; Stamm 'markier' -> 'markierung'
- Pruef-Prompt Regel 7: entscheidungsunabhängige Terme ohne Zahlenwert dürfen
  weggelassen oder symbolisch mitgeführt werden (wagner_whitin_dijkstra: 1+1 statt
  2+1 Korrekturrunden, n=2)
- revision_reasons: jede Korrekturrunde nennt ihren Auslöser im Log

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 16:28:39 +02:00
commit 0f1ab04a8d

View file

@ -64,3 +64,33 @@ def test_fewer_errors_preferred_when_all_have_errors():
def test_mixed_units_trigger_revision():
# beobachtet: Rendite/Kosten in Euro, Risiko als dimensionslose Summe w_i^2 sigma_i^2 (Faktor 100.000 zu klein)
assert make_review(units_consistent=False).needs_revision
# --- Grund jeder Korrekturrunde im Log (vorher nur „Probleme gefunden“) ---
def test_revision_reasons_name_every_trigger():
from checks.review import needs_work, revision_reasons
from core.model_schema import CheckIssue, CheckSeverity
fehlend = {"unclear_point": "Einkaufspreis nicht beziffert", "assumption": "-", "alternative": "-", "impact": "-"}
review = make_review(matches=False, verdict="weaker_than_task", missing=[fehlend])
issues = [CheckIssue(severity=CheckSeverity.ERROR, code="MIXED_OBJECTIVE_UNITS", message="Risiko dimensionslos"),
CheckIssue(severity=CheckSeverity.WARNING, code="MISSING_DATA", message="kein Wert für p")]
gruende = revision_reasons(review, issues)
assert needs_work(review, issues)
assert any(g.startswith("Zielgröße passt nicht") for g in gruende)
assert any("weaker_than_task" in g for g in gruende)
assert any("Einkaufspreis nicht beziffert" in g for g in gruende)
assert any("MIXED_OBJECTIVE_UNITS" in g for g in gruende)
assert not any("MISSING_DATA" in g for g in gruende) # löst keine Runde aus, gehört nicht in den Grund
def test_clean_state_has_no_revision_reasons():
from checks.review import revision_reasons
assert revision_reasons(make_review(), []) == []
def test_review_prompt_allows_both_forms_of_decision_independent_costs():
# wagner_whitin_dijkstra: Einkaufspreis ohne Zahl – je 1–2 Korrekturrunden, weil Regel 4 beide Formen beanstanden konnte
from pathlib import Path
prompt = (Path(__file__).parent.parent / "checks" / "Pruef-Prompt.txt").read_text(encoding="utf-8")
assert "entscheidungsunabhängige Terme" in prompt and "Verlange keine Umstellung" in prompt