fix(eval-dashboard): Re-Eval-Dedup je Note in KPI-Aggregation + Corpus-Overlap-Guard fuer Versions-Delta#293
Merged
Conversation
…s-Overlap-Guard fuer Versions-Delta
Statistik-Review 2026-07-15 (3 unabhaengige Opus-Statistiker, konvergent +
adversarial bestaetigt), zwei Methodik-Fehler:
Fix 1 -- Re-Eval-Doppelzaehlung (Pseudoreplikation): note_evals enthaelt
mehrere Eval-Zeilen derselben Note innerhalb einer pipeline_version
(Re-Evals + identische Duplikat-Inserts; Produktionsbeleg v0.3.140 = 52
Zeilen / 40 distinct Notes, 12 Duplikate). Die gepoolte Fehlerquote/Coverage
zaehlte Anker mehrfach-evaluierter Notes mehrfach; Kachel zeigte n=40, die
Pooling-Basis war aber 52 Zeilen -- inkonsistent. Fix: _dedup_latest_per_note()
haelt pro (pipeline_version, note) nur die neueste Zeile (max timestamp,
Tie-Break eval_id), Note-Identitaet normalisiert ueber Namespace-Prefix
(vault__/inbox__/merge__ gestrippt, Notes koennen zwischen Re-Evals den
Namespace wechseln). Angewendet in _calc_kpis, _calc_pdf_table und im
Server-Aufbau von quality_by_version/kpi_trend -- dieselbe Basis fuer
Kachel, per-PDF-Tabelle und Sparkline. calibration/collect.py zusaetzlich
mit ORDER BY timestamp deterministisch auf "neueste Zeile gewinnt" gebracht.
Fix 2 -- reliable-Flag prueft nur n, nicht Corpus-Vergleichbarkeit:
version_delta() markierte ein Delta ab n>=20 in beiden Versionen als
belastbar, unabhaengig davon ob beide Versionen denselben PDF-Corpus
evaluieren. Produktionsbeleg v0.3.140->v0.3.143: beide n>=20, aber nur 3
von 9/5 PDFs geteilt -- nur 8 von 22 Notes (36 %) der neueren Version
stammen aus einer in v0.3.140 vertretenen PDF-Quelle. Fix: reliable
zusaetzlich an notengewichteten Corpus-Overlap gekoppelt (_DELTA_MIN_PDF_OVERLAP
= 50 %, konfigurierbar). Server liefert dafuer kpi_trend["pdf_notes"]
({pdf_group_key: n_notes} je Version). Neues reason-Feld ("n_lt_20" vs.
"pdf_mix") laesst den Client zwischen den beiden Nicht-belastbar-Gruenden
unterscheiden; Tooltip-Texte in internal/dashboard/eval_dashboard.html an
allen 3 Stellen ergaenzt (KPI-Kachel-Chip, Spark-Row, Insights-Text).
Zahlen-Beleg (C:/tmp/kpi-db.db, read-only, eigenes Skript):
v0.3.140 Headline-Hall: 7.5 % (52 Zeilen) -> 9.5 % (40 distinct Notes)
v0.3.143 Headline-Hall: 10.2 % (30 Zeilen) -> 12.0 % (22 distinct Notes)
Delta 140->143: 2.7pp reliable=True -> 2.5pp reliable=False (reason=pdf_mix,
pdf_overlap=36.4 %)
Tests: 2 neue Dateien (test_dashboard_reeval_dedup.py, 14 Tests;
test_dashboard_delta_pdf_overlap.py, 8 Tests inkl. Server-Integrationstest),
RED vor Implementierung verifiziert. Bestehende Tests unveraendert gruen --
synthetische Fixtures ohne note_path/pdf_notes durchlaufen den Dedup/Overlap-
Guard unveraendert (Fallback-Verhalten), keine Altwerte mussten angepasst
werden. Volle Suite: 5913 passed, 3 skipped. ruff: clean.
Quelle: Statistik-Review 2026-07-15
…versariale Kontrolle #293) Adversariale Kontrolle des PR (Verdikt: merge-tauglich, 1 MEDIUM-Finding): _chart_scatter_versioned deduplizierte NICHT -- der Scatter zeigte 52 Punkte (v0.3.140), waehrend die KPI-Kachel nach Fix 1 korrekt "40 evaluierte Notes" sagte; re-evaluierte Notes erschienen doppelt (z. B. "Asynchronous E-Learning" bei x=0,0 UND x=29,4). Dieselbe "Instanzen vs. distinct"- Bugklasse (#194), die dieser PR schliesst. Fix: quality_rows vor dem Punkte-Aufbau je pipeline_version gruppieren und mit der bestehenden _dedup_latest_per_note-Logik deduplizieren -- gleiche Basis wie KPI-Kachel/per-PDF-Tabelle/kpi_trend. Dedup bewusst JE Version, nicht global: dieselbe Note in zwei Versionen bleibt zwei Punkte (der Versions-Filter des Scatters vergleicht Versionen). Zahlen-Beleg (C:/tmp/kpi-db.db, read-only, echte Funktion): v0.3.140: 52 Scatter-Punkte -> 40 v0.3.143: 30 Scatter-Punkte -> 22 "Asynchronous E-Learning" (v0.3.140): nur noch 1 Punkt (x=29,4 -- neueste Eval-Zeile), nicht mehr zusaetzlich der veraltete 0,0-Punkt. Tests: 2 neue (RED verifiziert: 4 statt 2 Punkte vor dem Fix) -- Scatter = distinct Notes je Version + Guard gegen Ueberkorrektur (kein globaler Dedup ueber Versionen hinweg). Volle Suite: 5915 passed, 3 skipped. ruff: clean. Quelle: Statistik-Review 2026-07-15 (adversariale Kontrollrunde)
TillQuandel
added a commit
that referenced
this pull request
Jul 15, 2026
…305) * feat(dashboard): Versions×PDF-Paarvergleich mit Schnittmengen-Delta Neue Dashboard-Ansicht "Paarvergleich" (Multi-Perspektiven-Dashboard-Review 2026-07-15, P1-Empfehlung aller 3 Statistiker, vom adversarialen Statistiker modifiziert): Matrix-Tabelle Median-Fehlerquote/-Belegrate je Pipeline-Version x PDF-Quelle, plus paarweiser Versions-Vergleich mit Deltas NUR auf der PDF-Schnittmenge zweier gewaehlter Versionen. Kernbefund (#293): gepoolte Versions-Deltas ueber den vollen Corpus sind Artefakte, sobald sich der PDF-Mix zwischen zwei Versionen aendert (v0.3.140 vs v0.3.143 teilten nur 3 von 9/5 PDFs). #293 hat das fuer EIN Sparkline-Delta gefixt (version_delta Corpus-Overlap-Guard); diese Ansicht erweitert das Prinzip auf eine frei waehlbare Versions×PDF-Matrix. Datenmodell (generative/eval_dashboard.py): - _matrix_cell_stats: Median-Fehlerquote/-Belegrate + n + Min/Max-Spanne je (PDF, Version)-Zelle, dedupliziert per _dedup_latest_per_note (#293) -- keine Pseudoreplikation durch Re-Evals. - _calc_version_pdf_matrix: Matrix-Rohdaten, Versions-Auswahl per _top_versions (SSoT mit dem bestehenden Dropdown), aufsteigend sortiert. - _version_pair_compare: Paarvergleich zweier Versionen. Deltas NUR ueber gemeinsame PDFs (Requirement 1); Note-Paarung ueber echte _note_key- Uebereinstimmung wo vorhanden, sonst PDF-Ebene mit paired-Kennzeichnung (Requirement 2). Ein Regressionstest haertet gegen den Index-Fallback von _note_key (waere sonst eine Scheinpaarung ueber Zeilen ohne Identifier). - build_version_pdf_matrix: Server-Fassade, berechnet alle Versionspaare vorab (bis zu 15x14/2=105, unproblematisch fuer den lokalen Server). Server (generative/eval_dashboard_server.py): eigene Rohdaten-Basis (_matrix_base_rows), gescoped auf die aktive eval_version (Requirement 5), aber bewusst UNABHAENGIG von den bestehenden Einzelwert-Filtern (PDF/Version/ Modell/Sprache/Lauf) -- sonst kollabiert die Matrix auf 1 Zelle. Bestehender Archiv-Mechanismus (pipeline_runs_archive/query_archived_pipeline_versions, #193) schliesst archivierte WIP-Versionen aus UND macht das kenntlich (excluded_archived_versions) -- Requirement 4. Der bekannte, NICHT quarantaenierte #232-Artefakt-Lauf wird NICHT hartkodiert ausgeschlossen; Zell-n und Tooltip (Min/Max-Spanne) machen Einzelzeilen-Dominanz sichtbar. Frontend (internal/dashboard/eval_dashboard.html): neue Section "Versions×PDF-Paarvergleich" (Nav-Item, Matrix-Tabelle im bestehenden .cmp-Stil, Farbcodierung ueber die etablierten good-t/warn-t/bad-t-Klassen). Zellen mit n<3 gedimmt, nie versteckt (Requirement 3). Zwei Dropdowns fuer den Versionspaar-Vergleich (Default: die zwei juengsten Versionen), Delta- Tabelle je gemeinsamer PDF-Quelle inkl. Paarungs-Kennzeichnung, Hinweis auf nicht geteilte Quellen. Tests: 23 neue (generative/tests/test_dashboard_version_pdf_matrix.py), RED vor Implementierung verifiziert. Volle Suite 6014 passed/3 skipped (vorher identisch), ruff clean. Sichtprüfung gegen eine read-only Kopie der echten Produktionsdaten (Playwright/firefox-1532, hell+dunkel, 0 Page-Errors) reproduziert den #293-Befund exakt: 3/9/5 PDFs gemeinsam, 36% Notes-Overlap, und zeigt fuer Hrastinski (2008) eine notengepaarte Zeile (n=1) mit dem im Task genannten 42,9%-Signaturwert -- der Artefakt-Lauf fliesst unveraendert ein, seine Dominanz ist ueber n=1 und die Paarungs-Kennzeichnung sichtbar. * fix(dashboard): D4-Anti-Pattern in Matrix-Coverage — echte 0.0 nicht per or verschlucken Spec-Review-Befund am PR: beide neuen Metrik-Funktionen (_matrix_cell_stats, _pair_metric_stats) nutzten `coverage_factual or coverage_rate` — das bekannte D4-Anti-Pattern aus dem Dashboard-Review 2026-07-15. `or` verschluckt eine ECHTE coverage_factual=0.0 (falsy) und nimmt faelschlich coverage_rate bzw. verwirft die Zeile komplett, wenn coverage_rate fehlt. 0%-Coverage-Zeilen existieren real in den Daten (Jockisch-Faelle). Fix: geteilte Hilfsfunktion _row_coverage() — coverage_factual, wenn NICHT None (auch bei 0.0), NUR bei None Fallback auf coverage_rate. Beide neuen Stellen umgestellt; die Bestands-Stellen mit demselben Muster (D4 nennt 5, u.a. eval_dashboard.py:610/933/1005/1523) bleiben unangetastet (separates Ticket, ausserhalb dieses PR-Scopes). Tests: 4 neue Regressionstests, RED vor dem Fix verifiziert (80.0 statt 0.0 bzw. None statt 0.0 an drei Stellen; der legitime None->coverage_rate- Fallback-Fall war schon gruen und bleibt es): - coverage_factual=0.0 + coverage_rate=0.8 -> median_cov 0.0 (nicht 80.0) - coverage_factual=0.0 + coverage_rate=None -> zaehlt als 0.0 (nicht verworfen) - coverage_factual=None + coverage_rate=0.8 -> 80.0 (Fallback bleibt) - derselbe 0.0-Fall im Paarvergleichs-Pfad (cov_a/cov_b/cov_delta) Volle Suite: 6018 passed, 3 skipped. ruff check + format: clean. HTML unveraendert (kein NUL-Byte-Check noetig). * fix(dashboard): Paarvergleich-Overall gepoolt + zeilen-konsistent, Cap- und Key-Drift-Disclosure Adversariale Opus-Statistiker-Abnahme zu PR #305, drei Befunde: B1+B2 (Kernfix) -- overall in _version_pair_compare war doppelt unsauber: (a) Median kollabierte bei der zero-inflated hallucination_rate auf 0,0pp (Audit-Beleg Default-Paar 140/143: Headline "0,0pp", obwohl 2 von 3 gemeinsamen PDFs sichtbar regredieren; dieselbe Falle, die _pooled_hall_stats fuer die KPI-Kachel dokumentiert). (b) overall poolte UNGEPAART (12/8 Notes), waehrend die per-PDF-Zeilen die gepaarte Teilmenge zeigen (4/4) -- n-Widerspruch in der Anzeige. Fix: overall nutzt jetzt (1) die ankergewichtet gepoolte Fehlerquote via _pooled_hall_pct (SSoT mit der KPI-Kachel, Mean-Fallback ohne Roh-Counts) und (2) EXAKT die Zeilen-Auswahl (gepaarte Teilmenge wo paired, sonst PDF-Ebene) -- Headline-n == Summe der Zeilen-n. Coverage im overall: notengewichtetes Mittel mit demselben Gueltigkeitsfilter (inkl. D4-Fix _row_coverage); ein Anker-Pool analog _pooled_hall_stats existiert fuer Coverage nicht (note_evals traegt keine Claim-Roh-Counts) -- im Docstring dokumentiert. Zell-/Zeilen-Mediane bleiben unveraendert (mit Min/Max abgenommen). notes_overlap_b bleibt bewusst auf voller PDF-Ebene (misst Corpus-Ueberlappung, version_delta.pdf_overlap-Semantik, dokumentiert). Neue Zahlen Default-Paar 140/143 (echte Datenkopie, read-only): Headline vorher "0,0pp (n=12/8)" -> jetzt "+14,0pp -- 3,3 % -> 17,3 % (n=4/4, Zeilen-Basis)". Abweichung vom Audit-Wert +4,42pp begruendet: das Audit rechnete ankergewichtet auf der UNGEPAARTEN 12/8-Basis (reproduziert: 5,7 % -> 10,2 % = +4,5pp); die vom Audit ebenfalls geforderte zeilen-konsistente (gepaarte 4/4) Basis laesst die Hrastinski-Artefaktzeile (42,9 %) 1 von 4 statt 1 von 8 B-Notes stellen -> +14,0pp. n-Konsistenz Headline==Zeilen-Summe verifiziert (4/4 == 1+1+2). B3 (Disclosure) -- PDF-Key-Drift-Hinweis: common_pdfs=[] bei nicht-leeren only_a/only_b ist im Bestand oft dieselbe Quelle unter zwei Gruppen-Keys (bates-2017 vs bates-information-behavior, #194/#202-Linie). Der leere Vergleich zeigt jetzt einen Hinweistext statt nur "kein Delta". Keine Heuristik-Zusammenfuehrung (separates Ticket). PR-Body wird entsprechend geschaerft. B4 (Cap-Fussnote) -- _MATRIX_VERSION_LIMIT droppte 35 von 50 Versionen ohne Kennzeichnung. Neu: n_versions_dropped in der Matrix-Payload (analog excluded_archived_versions; Versionen MIT Eval-Zeilen, die nicht gezeigt werden) + Fussnote im UI ("35 weitere Version(en) mit Eval-Daten nicht gezeigt (Top-15-Deckelung, min. n>=3 je Version -- die neueste immer dabei)"). Tests: 9 neue (RED vor Implementierung verifiziert: Median-Kollaps 0,0 statt 10,0; n=2 statt 1 gepaart; cov-Median 10,0 statt Mean 30,0; 3x KeyError n_versions_dropped; 2x fehlende HTML-Anker). Bestehende Overall-Tests blieben unveraendert gruen (homogene Fixtures: Mean==Median). Volle Suite: 6027 passed, 3 skipped. ruff check + format: clean. NUL-Byte-Check nach HTML-Edits: 1 (unveraendert). Sichtpruefung (Playwright/firefox-1532, hell+dunkel, Klick-Pfade inkl. Dropdown-Wechsel auf leere Schnittmenge): 0 Page-Errors; Screenshots C:/tmp/pairview-verify/nachbesserung-*.png. --------- Co-authored-by: TillQuandel <tillq@live.de>
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.
Befund (Statistik-Review 2026-07-15, 3 unabhängige Opus-Statistiker, konvergent + adversarial bestätigt)
Fix 1 — Re-Eval-Doppelzählung (Pseudoreplikation).
note_evalsenthält mehrere Eval-Zeilen derselben Note innerhalb einerpipeline_version(Re-Evals + identische Duplikat-Inserts). Beleg auf der Produktionskopie: v0.3.140 = 52 Zeilen von nur 40 distinct Notes (12 Duplikate); v0.3.143 = 30 Zeilen / 22 distinct Notes (8 Duplikate). Die gepoolte Fehlerquote/Coverage zählte Anker mehrfach-evaluierter Notes mehrfach — Bias nach unten, weil oft re-evaluierte Notes tendenziell gute Raten haben. Zusätzlich: die Kachel zeigte n=40 (distinct Notes), die Pooling-Basis war aber 52 Zeilen — inkonsistent.Fix 2 —
reliable-Flag beim Versions-Delta prüft nur n, nicht Corpus-Vergleichbarkeit.version_delta()(generative/eval_dashboard.py) markierte ein Delta ab n≥20 in beiden Versionen als belastbar — unabhängig davon, ob beide Versionen überhaupt denselben PDF-Corpus evaluieren. Auf der Produktionskopie wird so ein +2,7pp-Hall-Delta 140→143 als "reliable" gefärbt, obwohl die Versionen nur 3 von 9/5 PDFs teilen — reines PDF-Mix-Artefakt.Fix
Fix 1: Neue Funktion
_dedup_latest_per_note()(generative/eval_dashboard.py) hält pro (pipeline_version, note) nur die neueste Eval-Zeile (maxtimestamp, Tie-Breakeval_id). Note-Identität normalisiert über_note_key()— Namespace-Prefix (vault__/inbox__/merge__) gestrippt, da eine Note zwischen zwei Re-Evals den Namespace wechseln kann (Routing-Änderung). Angewendet an allen Stellen, die note_evals je Version poolen:_calc_kpis(generative/eval_dashboard.py:568ff) — KPI-Kachel_calc_pdf_table(generative/eval_dashboard.py:866ff) — per-PDF-Tabelle, dieselbe Basis wie die Kachelquality_by_version-Aufbau im Server (generative/eval_dashboard_server.py:710ff) — Sparkline/kpi_trendgenerative/calibration/collect.py(Kalibrierung) zusätzlich mitORDER BY timestampdeterministisch auf "neueste Zeile gewinnt" gebracht (vorher implizite, unspezifizierte Zeilenreihenfolge).Fix 2:
reliablezusätzlich an den notengewichteten Corpus-Overlap gekoppelt (_DELTA_MIN_PDF_OVERLAP = 0.5, als Konstante konfigurierbar) — Anteil der Notes der neuesten Version, deren PDF-Quelle auch in der Vergleichsversion vorkommt. Server liefert dafürkpi_trend["pdf_notes"]({pdf_group_key: n_notes}je Version, generative/eval_dashboard_server.py). Neuesreason-Feld ("n_lt_20"vs."pdf_mix") im Rückgabewert vonversion_delta()lässt den Client zwischen den beiden Nicht-belastbar-Gründen unterscheiden. Ohnepdf_notes(ältere Aufrufer/Tests) bleibt reines n≥20-Verhalten rückwärtskompatibel. Tooltip-Texte ininternal/dashboard/eval_dashboard.htmlan allen 3 Stellen ergänzt (KPI-Kachel-Chip, Spark-Row-Delta, Insights-Text) — vorher suggerierte der Text durchgängig "belastbar ab n≥20".Zahlen-Beleg (C:/tmp/kpi-db.db, read-only, eigenes Verify-Skript gegen die echten Fix-Funktionen)
n blieb in beiden Versionen bei [40, 22] (distinct Notes war schon vor Fix 1 die n-Basis) — der alte n≥20-Guard allein hätte reliable=True belassen; erst der Corpus-Overlap-Guard fängt das PDF-Mix-Artefakt.
Tests (TDD, RED vor Implementierung verifiziert)
generative/tests/test_dashboard_reeval_dedup.py(14 Tests):_note_key-Normalisierung (Prefix-Drift),_dedup_latest_per_note(Tie-Break, Fallback ohne Identifier, leere Liste), Integration in_calc_kpis/_calc_pdf_table(hall + coverage nutzen dieselbe deduplizierte Basis wie n).generative/tests/test_dashboard_delta_pdf_overlap.py(8 Tests, inkl. Produktionsmuster-Fixture 140/143 und einbuild_data()-Server-Integrationstest): Overlap-Schwelle, Prioritäts-Reihenfolge derreason-Codes, Rückwärtskompatibilität ohnepdf_notes.Bestehende Tests unverändert grün — synthetische Fixtures ohne
note_path/pdf_notesdurchlaufen Dedup/Overlap-Guard über die dokumentierten Fallback-Pfade unverändert; keine Altwerte mussten angepasst werden.Suite
Volle Suite (via
uv run pytest, Worktree-lokales.venvnachuv sync --extra dev— die editable atomic-notes-Installation zeigte sonst auf das Hauptrepo statt den Worktree): 5913 passed, 3 skipped, 0 failed.ruff check .+ruff format --check: clean.Quelle: Statistik-Review 2026-07-15
Adversariale Kontrolle + Nachbesserung (2026-07-15)
Kontroll-Verdikt: merge-tauglich, 1 MEDIUM-Finding, 2 LOW-Punkte.
MEDIUM (gefixt, Commit
ca1e2c9):_chart_scatter_versioned(eval_dashboard_server.py) deduplizierte nicht — der Scatter zeigte 52 Punkte (v0.3.140), während die KPI-Kachel nach Fix 1 korrekt "40 evaluierte Notes" sagte; re-evaluierte Notes erschienen doppelt ("Asynchronous E-Learning" bei x=0,0 UND x=29,4). Fix:quality_rowsvor dem Punkte-Aufbau jepipeline_versiongruppieren und mit_dedup_latest_per_notededuplizieren (gleiche Basis wie KPI/Trend; Dedup bewusst je Version, nicht global — dieselbe Note in zwei Versionen bleibt zwei Punkte). RED verifiziert (4 statt 2 Punkte vor dem Fix), 2 neue Tests. Beleg auf der DB-Kopie: Scatter-Punkte v0.3.140 52→40, v0.3.143 30→22; der Doppel-Punkt ist jetzt einfach (nur x=29,4, die neueste Eval-Zeile). Suite nach Nachbesserung: 5915 passed, 3 skipped; ruff clean.LOW (Doku-Notiz, keine Code-Änderung):
_DELTA_MIN_PDF_OVERLAPist post-hoc gewählt (der Produktionsfall 140→143 liegt mit 36,4 % darunter), aber robust: jede Schwelle im Bereich 0.4–0.6 klassifiziert den Beleg-Fall identisch. Als Konstante konfigurierbar, falls sich die Wahl in der Praxis als zu streng/lax zeigt.pdf=NULLfallen auspdf_notesheraus (leerer Gruppen-Key wird übersprungen) und zählen damit nicht in den Overlap-Nenner. Auf den Realdaten gibt es keine solchen Zeilen (kein Trigger); sollte das auftreten, wäre der Overlap gegenüber der ehrlichen Basis überschätzt — bewusst nicht spekulativ behandelt.