From 7aac3ff3ec8d9787ea416323a3e9549cdcbdbe7e Mon Sep 17 00:00:00 2001 From: dschlueter Date: Thu, 17 Sep 2026 13:34:54 +0200 Subject: [PATCH] fix: Vergleichstabellen nicht als Summenabweichung melden; Verstehen-Prompt in Prompt-Kennung Co-Authored-By: Claude Opus 5 --- benchmark/benchmark.py | 5 +++-- solving/solver_stage.py | 13 +++++++++++++ tests/test_pipeline.py | 1 + tests/test_result_tables.py | 23 +++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index 1b783b6..5a77f8c 100644 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -26,9 +26,10 @@ RESULTS_DIR = BENCHMARK_DIR / "results" # Nach dem ersten fatalen API-Fehler (Kontingent/Guthaben/Key) keine weiteren Läufe starten quota_exhausted = threading.Event() -# Dateien, die das LLM-Verhalten steuern: die drei System-Prompts und die Feldbeschreibungen +# Dateien, die das LLM-Verhalten steuern: die vier System-Prompts und die Feldbeschreibungen # (Field(description=...) geht als JSON-Schema mit und wirkt wie eine Prompt-Anweisung). -PROMPT_FILES = ("modeling/Routing-und-Extraktions-Prompt.txt", "checks/Pruef-Prompt.txt", "solving/Solver-Prompt.txt", +PROMPT_FILES = ("understanding/Verstehen-Prompt.txt", "modeling/Routing-und-Extraktions-Prompt.txt", "checks/Pruef-Prompt.txt", + "solving/Solver-Prompt.txt", "core/model_schema.py") # Umgebungsvariablen, die das Verhalten ändern – ohne sie ließe sich ein A/B-Lauf nicht zuordnen diff --git a/solving/solver_stage.py b/solving/solver_stage.py index c0a4f2a..59e2322 100644 --- a/solving/solver_stage.py +++ b/solving/solver_stage.py @@ -271,6 +271,17 @@ def _is_sum_row(row, value=None, others=None) -> bool: return _close(value, sum(others)) +def _is_comparison(values, no_sum_row: bool, total: float, objective_value) -> bool: + """Vergleichstabelle: jede Zeile ist ein eigener Zielwert (Konfiguration, Szenario), keine Zerlegung. + + Erkennbar daran, dass die Summe nicht passt, aber genau eine Zeile den gemeldeten Zielwert trägt. + Beobachtet: "Vergleich der Konfigurationen" (Portfolio Nr. 2) galt sonst als "Summe weicht ab". + """ + if objective_value is None or not no_sum_row or len(values) < 2 or _close(total, objective_value): + return False + return sum(_close(v, objective_value) for v in values) == 1 + + def _close(a: float, b: float) -> bool: return abs(a - b) <= 1e-3 * max(abs(a), abs(b)) + 1e-6 @@ -303,6 +314,8 @@ def parse_result_tables(raw_tables: list, objective_value) -> tuple: warnings.append(f"Tabelle '{table.title}': Spalte '{table.objective_column}' enthält nicht nur Zahlen – Summe nicht prüfbar.") continue total = sum(values) + if _is_comparison(values, len(values) == len(cells), total, objective_value): + continue if objective_value is not None and not _close(total, objective_value): warnings.append( f"Tabelle '{table.title}': Summe der Spalte '{table.objective_column}' ({total:.6g}) " diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index ab3a431..ee8339d 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -309,6 +309,7 @@ def test_benchmark_records_the_prompt_state(): assert len(stand["prompts"]) == 12 and stand["commit"] assert "modeling/Routing-und-Extraktions-Prompt.txt" in PROMPT_FILES assert "core/model_schema.py" in PROMPT_FILES # Feldbeschreibungen wirken wie Prompt-Anweisungen + assert "understanding/Verstehen-Prompt.txt" in PROMPT_FILES def test_deviating_settings_are_recorded(monkeypatch): diff --git a/tests/test_result_tables.py b/tests/test_result_tables.py index 0551d7d..9f6cc3a 100644 --- a/tests/test_result_tables.py +++ b/tests/test_result_tables.py @@ -228,3 +228,26 @@ def test_ascii_form_present_in_the_task_is_not_replaced(): zuordnung = umlaut_map("Herr Müller und die Firma Mueller liefern.") assert "mueller" not in zuordnung assert fix_transliterations("Firma Mueller", zuordnung) == "Firma Mueller" + + +# beobachtet in Portfolio Nr. 2: eine Zeile je Konfiguration, die gewählte trägt den Zielwert +COMPARISON = { + "title": "Vergleich der Konfigurationen", + "columns": ["Konfiguration", "lambda1", "lambda2", "Zielfunktionswert"], + "rows": [["Reine Varianzminimierung", "1.0", "0.0", 0.18217355], + ["Reine Korrelationsminimierung", "0.0", "1.0", -187.29514112], + ["Ausgewogene Skalarisierung", "0.5", "0.5", -93.56364364]], + "objective_column": "Zielfunktionswert", +} + + +def test_comparison_table_is_not_a_sum_mismatch(): + tables, warnings = parse_result_tables([COMPARISON], objective_value=0.18217353985482126) + assert warnings == [] and len(tables) == 1 + + +def test_decomposition_with_one_term_equal_to_objective_is_still_checked(): + # eine Summenzeile macht daraus eine Zerlegung – deren Fehler bleibt sichtbar + table = dict(DECOMPOSITION, rows=[["Rendite", 0.097557], ["Kosten", -0.05], ["Zielwert", 0.2]]) + _, warnings = parse_result_tables([table], objective_value=0.097557) + assert any("weicht vom Zielwert" in w for w in warnings)