fix: Code-Hygiene-Buendel B (#315/#316/#318/#319/#331/#332) - #340
Merged
Conversation
_build_quality_chart_data() filterte hallucination_rate/coverage_rate beim Aufbau der Slope-Datasets nur auf `is not None`, nicht auf den -1.0-Sentinel, den eval_quality_v4.py bei ungueltigen Laeufen schreibt. Ein Sentinel -1.0 wurde nach der *100-Rundung zu -100.0 und rutschte als valider Wert in die Median-Berechnung ein. Fix: derselbe >= 0-Guard wie in _chart_scatter/_chart_scatter_versioned. Betraf ausschliesslich den Legacy-Einmal-Render-Pfad (main()/_build_html) -- der Live-Server (eval_dashboard_server.py) ruft diese Funktion nicht auf. 3 neue Tests (RED vor dem Fix: -100.0/-40.0 statt None/20.0 im Slope-Median).
`.get("coverage_factual", r.get("coverage_rate", 0))` faellt bei einem
explizit gespeicherten None-Wert NICHT auf coverage_rate zurueck --
dict.get's Default greift nur bei fehlendem Key. Betraf drei Stellen:
- eval_progress.py:63 -- crashte mit TypeError beim Prozent-Formatieren
(None statt Fallback-Wert), reproduziert vor dem Fix.
- orchestrator.py (Stage-8-Run-Ende-Print) -- derselbe TypeError-Crash bei
`r.get(...) >= 0`, reproduziert vor dem Fix. In `_stage8_report_averages()`
extrahiert (testbar, vorher inline in main()).
- db.py::query_kpi_trend -- AVG(coverage_factual) ganz ohne Fallback, liefert
NULL fuer avg_cov bei jeder eval_version=4.x-Zeile (coverage_factual seit
v4 strukturell NULL, #233). Fix: AVG(COALESCE(coverage_factual, coverage_rate)).
Neuer geteilter Helper `coverage_value()` in eval_common.py (dependency-frei
von eval_dashboard.py, das dieselbe Logik bereits fuer die Dashboard-eigenen
Stellen in _row_coverage hat -- separates, aelteres Ticket, hier nicht
angefasst). 13 neue Tests, RED vor dem Fix jeweils gezeigt (TypeError-Crash
bzw. None statt Fallback-Wert).
Historische eval_version=4.1-Zeilen tragen decision_source="audit" (67 JSONL-Zeilen, Eval-Doku-Audit #313) -- aktueller Code schreibt nur noch "audit_override" (rules.py::rule_audit_stricter_override). Leser die nach decision_source filtern/gruppieren muessten sonst beide Werte kennen. Neuer Lese-Seiten-Normalizer normalize_decision_source() (models.py, ueber lib/decision_engine exportiert) mit dokumentiertem Alias-Mapping. Angewendet an der einen Stelle im Code, die einen decision_source-String wieder in ein ClaimDecision-Objekt liest (eval_quality_v4.py::_aggregate). KEINE Mutation der Bestandsdaten -- JSONL/DB bleiben unveraendert. 3 neue Tests fuer den Normalizer.
eval_quality_v4.py nutzte intern weiterhin agent="eval_quality_v3_..." fuer Cache-/Kostenzuordnung (JSON-Repair-Call und Judge-Call je Variante) -- Altlast aus dem v3-Vorgaenger. Verwirrend bei Auswertungen nach Agent-Label. Cache-Invalidierungs-Nebenwirkung geprueft: agent ist Teil des LLM-Call- Cache-Keys (agents/base.py::_cache_key). Die Umbenennung invalidiert einmalig den Disk-Cache fuer Judge-/Repair-Calls innerhalb der aktuellen eval_version -- eine inhaltlich unveraenderte Note trifft dort einen Cache-Miss statt -Hit (ein Sonnet-Call statt Cache-Treffer). Der Re-Eval-Hash-Guard (separater Mechanismus ueber content_hash+eval_version+pipeline_version in quality_history.jsonl) ist NICHT betroffen -- bereits gespeicherte Eval-Ergebnisse bleiben gueltig, nur der darunterliegende LLM-Response-Cache wird kalt. 2 neue Tests (RED vor dem Fix: agent-Label trug "eval_quality_v3_...").
confirmation_rate im Verifier-Trace konnte > 100 % werden -- Coverage-Serie 2 belegte 2.0 (Poege, Lauf 3, mit Refine) und 3.0 (Kok, Lauf 5, OHNE Refine). Der Kok-Beleg widerlegte die urspruengliche Refine-Hypothese. Root Cause: _log_anchor_stats() teilte `confirmed` (gezaehlt aus final_anchors, NACH dem Lauf) durch `total_in` (Momentaufnahme VOR dem Lauf, in run()). _run_inner() haengt an JEDEM Ausgang ueber sync_anchors_from_body() neue, bereits bestaetigte Anker an draft.source_anchors an (Body-Zitate der Form „..." (S. N)) -- Zaehler und Nenner bezogen sich dadurch auf unterschiedliche Mengen, auch ohne Refine-Zyklus. Fix: Nenner = len(final_anchors), dieselbe Menge wie der Zaehler. Rate ist damit mathematisch garantiert <= 1.0. total_in bleibt als separates Feld im Trace fuer Transparenz erhalten. 1 neuer Test (RED vor dem Fix: confirmation_rate=3.0, exakt reproduziert per 1 pre-pass-bestaetigtem Original-Anker + 2 body-synced Ankern / total_in=1).
Die "0 Anker-Matches"-Diagnosemeldung nannte unbedingt einen --load-drafts-Kontext als Erklaerung -- bind_figures_to_drafts() kennt den tatsaechlichen Aufrufkontext gar nicht. Coverage-Serie 2, Lauf 5 (Kok) belegt 0 Anker-Matches auch mit --fresh-run woertlich im Log -- dieselbe Fehlklasse wie das bereits gefixte #288. Reine Textaenderung (wie im Issue vorgeschlagen): neutrale Formulierung ohne unbelegte Kontext-Annahme, keine Logikaenderung. 1 neuer Test (RED vor dem Fix: Meldung nannte --load-drafts bedingungslos).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sammel-PR "Bündel B Code-Hygiene"
Sechs NIEDRIG-Prio-Issues aus der Eval-Doku-Auditserie (#313), je ein Commit pro Issue. Format wie Sammel-PR #312.
Sperrzone beachtet:
internal/dashboard/eval_dashboard.htmlundgenerative/eval_dashboard_server.pywurden nicht angefasst (paralleler Bündel-A-Builder).generative/eval_dashboard.py(Legacy-Modul, nicht der Live-Server) wurde für #315 editiert — laut Auftrag erlaubt.#315 — Sentinel-Filter im Legacy-Slope-Chart
_build_quality_chart_data()(generative/eval_dashboard.py, aktuelle Zeile 1831, nicht mehr 1671/1611 wie im Issue) filtertehallucination_rate/coverage_ratebeim Aufbau der Slope-Datasets nur aufis not None, nicht auf den-1.0-Sentinel. Ein Sentinel wurde nach*100-Rundung zu-100.0und rutschte in die Slope-Mediane ein. Fix: derselbe>= 0-Guard wie in_chart_scatter/_chart_scatter_versioned. Betrifft ausschließlich den Legacy-Einmal-Render-Pfad, nicht den Live-Server.Test:
generative/tests/test_dashboard_slope_sentinel_filter.py— 3 Tests. RED vor dem Fix:-100.0/-40.0stattNone/20.0im Slope-Median.#316 — Coverage-Fallback-Bugklasse außerhalb des Dashboards
.get("coverage_factual", r.get("coverage_rate", 0))fällt bei explizit gespeichertemNoneNICHT aufcoverage_ratezurück (dict.gets Default greift nur bei fehlendem Key). Betraf drei Stellen außerhalb des Dashboards (die Dashboard-eigenen Stellen sind bereits über_row_coverage/#305 gefixt, separates Ticket, hier nicht angefasst):eval_progress.py:63— crashte real mitTypeErrorbeim Prozent-Formatieren (reproduziert).orchestrator.py(Stage-8-Run-Ende-Print) — derselbeTypeError-Crash beir.get(...) >= 0(reproduziert). In_stage8_report_averages()extrahiert (vorher inline inmain(), jetzt testbar).db.py::query_kpi_trend—AVG(coverage_factual)ganz ohne Fallback, liefertNULLfüravg_covbei jedereval_version=4.x-Zeile (coverage_factual seit v4 strukturell NULL, coverage_factual: abgeschaffte v1.3-Metrik — Kalibrierungs-View zeigt LLM-Coverage seit v4 leer (stiller NULL-Leser) #233). Fix:AVG(COALESCE(coverage_factual, coverage_rate)).Neuer geteilter Helper
coverage_value()ineval_common.py(dependency-frei voneval_dashboard.py).Tests:
test_eval_common_coverage_value.py(5),test_eval_progress_coverage_fallback.py(2),test_orchestrator_stage8_report_averages.py(4),test_db.py(+2) — 13 neue Tests. RED jeweils gezeigt (TypeError-Crash bzw.Nonestatt Fallback-Wert).#318 — decision_source-Vokabular vereinheitlichen
Historische
eval_version=4.1-Zeilen tragendecision_source="audit"(67 JSONL-Zeilen, Eval-Doku-Audit #313) — aktueller Code schreibt nur noch"audit_override". Neuer Lese-Seiten-Normalizernormalize_decision_source()(lib/decision_engine/models.py, überlib/decision_engineexportiert) mit dokumentiertem Alias-Mapping{"audit": "audit_override"}. Angewendet an der einzigen Stelle im Code, die einendecision_source-String wieder in einClaimDecision-Objekt liest (eval_quality_v4.py::_aggregate). Keine Mutation der Bestandsdaten — JSONL/DB bleiben unverändert.Test:
lib/decision_engine/tests/test_models.py— 3 Tests für den Normalizer.#319 — Agent-Label v3→v4
eval_quality_v4.pynutzte intern weiterhinagent="eval_quality_v3_..."für Cache-/Kostenzuordnung (JSON-Repair-Call und Judge-Call je Variante). Umbenannt aufeval_quality_v4_....Cache-Invalidierungs-Nebenwirkung geprüft (wie im Issue gefordert):
agentist Teil des LLM-Call-Cache-Keys (agents/base.py::_cache_key). Die Umbenennung invalidiert einmalig den Disk-Cache für Judge-/Repair-Calls innerhalb der aktuelleneval_version— eine inhaltlich unveränderte Note trifft dort beim nächsten Lauf einen Cache-Miss statt -Hit (ein zusätzlicher Sonnet-Call). Der davon unabhängige Re-Eval-Hash-Guard (content_hash+eval_version+pipeline_versioninquality_history.jsonl) ist nicht betroffen — bereits gespeicherte Eval-Ergebnisse bleiben gültig._canonical_agent()im Dashboard (Sperrzone, nicht angefasst) matcht generisch auf den"eval_quality"-Präfix und bricht durch die Umbenennung nicht.Test:
test_eval_agent_label_v4.py— 2 Tests. RED vor dem Fix: Label trug noch"eval_quality_v3_...".#331 — confirmation_rate-Zählartefakt
confirmation_rateim Verifier-Trace konnte > 100 % werden (Coverage-Serie 2: 2.0 mit Refine, 3.0 ohne Refine — widerlegt die ursprüngliche Refine-Hypothese). Root Cause:_log_anchor_stats()teilteconfirmed(gezählt ausfinal_anchors, NACH dem Lauf) durchtotal_in(Momentaufnahme VOR dem Lauf)._run_inner()hängt an jedem Ausgang übersync_anchors_from_body()neue, bereits bestätigte Anker an — Zähler und Nenner bezogen sich auf unterschiedliche Mengen, auch ohne Refine-Zyklus. Fix: Nenner =len(final_anchors), dieselbe Menge wie der Zähler — Rate ist damit mathematisch garantiert ≤ 1.0.total_inbleibt als separates Feld im Trace erhalten.Test:
test_verifier_confirmation_rate_bound.py— 1 Test, reproduziert exakt den Kok-Fall (1 pre-pass-bestätigter Original-Anker + 2 body-synced Anker /total_in=1→ 3.0 vor dem Fix).#332 — figure_alt-Logmeldung kontextneutral
Die "0 Anker-Matches"-Diagnosemeldung nannte unbedingt einen
--load-drafts-Kontext als Erklärung, obwohlbind_figures_to_drafts()den Aufrufkontext gar nicht kennt (Kok-Beleg: 0 Anker-Matches trat auch mit--fresh-runauf). Reine Textänderung auf eine neutrale Formulierung, keine Logikänderung.Test:
test_figure_alt_neutral_no_match_message.py— 1 Test.Suite / Lint
uv run pytest generative lib/decision_engine/tests shared/tests -q→ 6244 passed, 3 skipped, 9 deselected, 45 Warnings (alle vorbestehendeDeprecationWarning, kein Bezug zu diesem PR), 446s.uv run ruff check .→ All checks passed.uv run ruff format --check .→ 314 files already formatted.Closes #315
Closes #316
Closes #318
Closes #319
Closes #331
Closes #332