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:
parent
26f988a953
commit
0f1ab04a8d
3 changed files with 148 additions and 8 deletions
|
|
@ -309,3 +309,31 @@ def test_repair_leaves_correct_words_and_identifiers_alone():
|
|||
assert fix_transliterations(richtig) == richtig
|
||||
namen = "Bindend: Spaet_zu_Frueh_Verbot und KeinFruehNachSpaet sowie Kapazitaet_M1."
|
||||
assert fix_transliterations(namen) == namen
|
||||
|
||||
|
||||
def _dijkstra_run(title, note, steps=True):
|
||||
from core.model_schema import GraphSolution, ResultTable
|
||||
graph = GraphSolution.model_validate({
|
||||
"nodes": ["0", "1"], "arcs": [{"source": "0", "target": "1", "weight": 8}], "path": ["0", "1"],
|
||||
"steps": [{"node": "0", "label": 0}, {"node": "1", "label": 8}] if steps else [],
|
||||
"node_meaning": "Zeitpunkt", "arc_meaning": "Kauf und Nutzung"})
|
||||
return SolverRun(status=SolverStatus.OPTIMAL, objective_value=8, graph=graph,
|
||||
tables=[ResultTable(title=title, columns=["Knoten", "Marke"], rows=[["0", 0]], note=note)])
|
||||
|
||||
|
||||
def test_dijkstra_marks_explained_by_the_report_are_not_flagged():
|
||||
# beobachtet: „Dijkstra-Markierungen“ gemeldet, obwohl der Report den Markierungsverlauf erklärt
|
||||
from solving.solver_stage import check_text_quality
|
||||
run = _dijkstra_run("Dijkstra-Markierungen", "Die Knoten in der Reihenfolge ihrer Markierung.")
|
||||
assert check_text_quality(run, "Ersatzplanung als kürzester Weg.") == []
|
||||
|
||||
|
||||
def test_marks_without_dijkstra_steps_are_still_flagged():
|
||||
from solving.solver_stage import check_text_quality
|
||||
run = _dijkstra_run("Marken je Knoten", None, steps=False)
|
||||
assert any("Markierung" in b for b in check_text_quality(run, "Ersatzplanung als kürzester Weg."))
|
||||
|
||||
|
||||
def test_everyday_marked_is_no_jargon():
|
||||
from checks.model_checks import unexplained_jargon
|
||||
assert unexplained_jargon("nur die mit 'ja' markierten Kombinationen gehören zum Plan", "Zusammenfassung") == []
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -811,3 +811,26 @@ def test_scenario_without_result_is_a_finding_unless_proven_unsolvable(wert, sta
|
|||
assert bool(befunde) == befund
|
||||
if befund:
|
||||
assert "Ausgewogene Skalarisierung" in befunde[0] and "Startlösung" in befunde[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("modus, bwrap_da, erwartet", [
|
||||
("auto", False, "bubblewrap ist nicht installiert"), # früher stillschweigend ungeschützt
|
||||
("aus", True, "abgeschaltet"),
|
||||
("auto", True, None),
|
||||
("bwrap", False, None), # Lauf wird ohnehin verweigert
|
||||
])
|
||||
def test_sandbox_warning_names_unprotected_runs(monkeypatch, modus, bwrap_da, erwartet):
|
||||
from solving import solver_stage
|
||||
monkeypatch.setattr(solver_stage, "SANDBOX", modus)
|
||||
monkeypatch.setattr(solver_stage.shutil, "which", lambda name: "/usr/bin/bwrap" if bwrap_da else None)
|
||||
warnung = solver_stage.sandbox_warning()
|
||||
assert (warnung is None) if erwartet is None else (erwartet in warnung)
|
||||
|
||||
|
||||
def test_unprotected_run_carries_the_warning_into_the_result(monkeypatch):
|
||||
from solving import solver_stage
|
||||
from test_model_checks import make_spec
|
||||
monkeypatch.setattr(solver_stage, "SANDBOX", "aus")
|
||||
code = 'def main():\n """Test."""\n report("optimal", 1.0, {})\n\n\nif __name__ == "__main__":\n main()\n'
|
||||
run = solver_stage._check_and_run(code, 30, make_spec())
|
||||
assert any("Sandbox abgeschaltet" in w for w in run.warnings)
|
||||
|
|
|
|||
Loading…
Reference in a new issue