Skip to content

fix(dashboard): per-PDF-Tabelle auf eine Datengrundlage — kanonischer pdf_key, Version+n pro Zeile, Union-Iteration (#194)#222

Open
TillQuandel wants to merge 2 commits into
masterfrom
fix/194-pdf-tabelle-datengrundlage
Open

fix(dashboard): per-PDF-Tabelle auf eine Datengrundlage — kanonischer pdf_key, Version+n pro Zeile, Union-Iteration (#194)#222
TillQuandel wants to merge 2 commits into
masterfrom
fix/194-pdf-tabelle-datengrundlage

Conversation

@TillQuandel

@TillQuandel TillQuandel commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Behebt #194. Die per-PDF-Tabelle stand strukturell auf drei Datengrundlagen je Zeile. Jede Zeile steht jetzt auf EINER Datengrundlage (Eval-Daten aus note_evals); accept bleibt bewusst aus dem Routing (pipeline_runs) und wird getrennt beschriftet.

Befund → Fix (Vorher/Nachher, echte Daten, read-only geprüft)

# Befund Vorher Nachher
1 HOCH hall/cov über ALLE Versionen gepoolt, version/accept aus anderer Quelle bates hall 10,5 % (gepoolt über 18 Versionen), Version v0.3.74/v0.3.134 gemischt hall/cov aus GENAU der neuesten Eval-Version → bates 16,7 % @ v0.3.134
2 HOCH Substring-Match key in pdf + pdf_key-Drift → Bates-Dreifachzeile 3 Zeilen (bates n=86, bates-2017 n=22, bates---2017 n=0) 1 kanonische Zeile bates-2017
KPI n_pdfs überzählt len(log_data) = 25 (bates 3×, ohne Hertzum/Kaletski) distinct kanonische Quellen = 25 (bates 1×, +Hertzum +Kaletski — die Über-/Unterzählung hob sich zufällig auf, Zusammensetzung jetzt korrekt)
3 MITTEL PDFs nur in quality_rows fehlen (Iteration über log_data) Hertzum, Kaletski unsichtbar Union-Iteration → Hertzum (n=8), Kaletski (n=2) sichtbar
4 MITTEL n_notes = Eval-Instanzen statt distinct bates 86; KPI n_notes 52 Tabelle: distinct @ Version (bates 1 aktuell / 39 gesamt); KPI n_notes 40 distinct
5 NIEDRIG pages-Spalte strukturell tot; accept-Median ohne N Seiten-Spalte durchgehend leer; kein n Seiten-Spalte entfernt; accept zeigt n=Läufe + ggf. abweichende Routing-Version

Design-Entscheidungen

  • Kanonisierung = SSoT mit dem PDF-Filter (PDF-Filter allein bricht mehrere Panels: leere Charts ohne Meldung, Vergleichstabelle ohne Zeilen, Bates matcht v0.1.0-Kosten #202/PR fix(dashboard): PDF-Filter kanonisiert pdf_key/pdf_label-Drift (closes #202) #206): neuer Helfer _pdf_group_key = _pdf_slug(_pdf_filter_key(...)) — genau die Gruppierung, die _dedupe_pdf_options schon nutzt (jetzt gemeinsam). Autor-Jahr-Slug: kollabiert Volltitel („Bates - 2017 - …") und Kebab („bates-2017") derselben Quelle, hält aber Jahrgänge getrennt (Beutelspacher 2014 vs. 2022) — anders als der reine Segment-Präfix (_pdf_matches), der zwei Jahrgänge transitiv über einen Autor-Kurzkey brücken würde. Kein zweites Matching-System.
  • accept aus anderer Quelle, bewusst getrennt: note_evals.acceptance_status ist in eval_version 4.1 durchgehend NULL (485/485); genau 1 historische 1.3-Zeile trägt 'vault' — accept MUSS also aus dem Routing (pipeline_runs) kommen. Es wird auf die Eval-Version der Zeile gepinnt (prefer_ver); fehlt sie in den Runs, auf die neueste gekappte Routing-Version, dann getrennt via accept_ver/accept_n ausgewiesen. In den echten Daten stimmt accept_ver == Eval-Version für alle Gruppen. Spaltenkopf: „Akzeptiert (Routing)" vs. „Version (Eval)".
  • Dashboard zeigt verwaiste WIP-Version als aktuellen Stand — Versionswahl per Nummer statt Code-Stand + Auto-Bump-Kollisionsrisiko #191-Orphan-Guard: die „neueste Eval-Version" wird über _newest_capped_version gegen config.AGENT_VERSION gekappt — verwaiste WIP-Zeilen (Nummer > Code-Version) können „neueste" nicht kapern. Fällt nur auf die neueste vorhandene zurück, wenn ALLE Versionen Orphans sind (Zeile bleibt sichtbar; Orphan-Banner meldet es separat).
  • pages-Spalte: entfernt. Strukturell tot (DB-Fallback setzt pages hart 0 → falsy → immer None) und aus den vorhandenen Quellen nicht ableitbar (Baseline-Logs existieren nicht, DB kennt keine Seitenzahl). Befüllen unmöglich → entfernen.
  • Test-PDFs (test-short, cobaltite-paper, extreme-events-decision-making, ml-interpretability-advances): NICHT hartcodiert gefiltert. Die Kanonisierung dedupliziert, filtert aber keine Test-Artefakte — die sind reguläre Daten. Eine Namens-Blacklist wäre ein Maintainer-Workaround (AGENTS.md-Maxime 1+2) und wurde bewusst unterlassen. Sauber lösbar wäre nur ein is_test-Flag an der Datenquelle (pipeline_runs) — außerhalb dieses Scopes. Die zwei Routing-only-Test-PDFs erscheinen als Union-Zeilen (accept vorhanden, keine Eval-Daten) und werden aus dem ins-quality-Streifen via n>0 herausgefiltert.

ins-quality-Streifen (Issue-Kommentar)

Der Streifen las den Accept-Chart-Datensatz, in dem „0 % von n Notes" nicht von „0 Notes" unterscheidbar war → meldete „Akzeptanz reicht von 0,0 % (bates---2017---information-behavior) …". Fix: _chart_acceptance leitet jetzt aus den kanonischen Tabellenzeilen ab (SSoT) und trägt n (distinct Eval-Notes) je Balken; der Streifen filtert n > 0 (Muster wie der n_notes-Guard von ins-compare, PR #200).
Vorher: „… von 0,0 % (bates---2017---information-behavior) …". Nachher (Playwright): „Akzeptanz reicht von 40,0 % (Jaiswal (2020)) bis 100,0 % (Afzal (2017)) — Spannweite 60,0 Punkte über 21 PDFs."

RED-Beweis / Tests

generative/tests/test_dashboard_pdf_table.py (neu, 9 Tests) reproduziert die vier Hauptbefunde synthetisch. RED vor dem Fix: ImportError: cannot import name '_pdf_group_key' bzw. gegen die Alt-Logik falsche Gruppierung/Zählung. GRÜN nach dem Fix. Abgedeckt: (a) Version-Mischung, (b) Bates-Substring-Doppelzählung → 1 Zeile, (c) PDF nur in quality_rows sichtbar, (d) n_notes distinct, Orphan-Version-Guard, accept-n, accept-Chart-n-Feld.

Sichtverifikation (Playwright, Firefox)

C:/tmp/verify-194/ — per-PDF-Tabelle + Qualitäts-Sektion, hell + dunkel. Bestätigt: Bates einmal, Hertzum/Kaletski sichtbar, accept-n unter dem Balken, keine Seiten-Spalte, ins-quality-Streifen ohne Routing-only-0 %-Eintrag.

Suite / Lint

pytest generative lib/decision_engine/tests shared/tests -q5701 passed, 0 failed (3 skipped, 4 deselected; Baseline 5693 — +9 neue Tests). (Ein bestehender Test test_calc_kpis_version_anchored_not_highest_number erwartete n_notes als Zeilenzahl; da synthetische Rows keinen note_path haben, zählt der Laufindex jede Zeile einzeln — Alt-Verhalten erhalten, echte Re-Evals mit note_path werden dedupliziert.) ruff format --check . + ruff check . grün.

Merge-Reihenfolge + Konfliktflächen (empirisch via git merge-tree)

Empfohlene Reihenfolge: #217#219#221 → dieser PR (dieser zuletzt).

gegen Konflikte Fläche Auflösung
#217 (KPI-CSS) 0 konfliktfrei (nur KPI-CSS ~245–292)
#219 (Tooltips) 1 <thead> der cmp-Tabelle Meine Spaltenstruktur übernehmen (keine Seiten-Spalte, „Version (Eval)"/„Akzeptiert (Routing)"). Wichtig: #219s per-PDF-Tabellen-Tooltips beschreiben die ALTE Semantik („Durchschnitt … über alle Pipeline-Versionen", „nicht gepoolt") — nach diesem Fix faktisch falsch (jetzt gepoolt, EINE Version). Beim Merge die Tooltip-Texte an die neue Semantik anpassen oder entfernen.
#221 (P4–P8) 1 <thead> + renderTable thead: beide entfernen Spalten (mein Seiten + #221s Gutter) → beide Entfernungen zusammenführen, meine Labels behalten. renderTable: #221 entfernt die Gutter-<td>, ich baue die Zellen um (Seiten-td raus, accept-n rein) — automerge-fähig bis auf den thead.

Kein Python-Zeilenkonflikt: #221s einzige eval_dashboard.py-Änderung ist in _chart_longitudinal, meine in _calc_pdf_table/_calc_kpis/_chart_acceptance.

Cross-Model-Review (Mistral magistral-medium-latest)

Schwerpunkt Aggregationslogik / Versions-Max-Fallen (#191). Fazit: keine echten Bugs gefunden; explizit bestätigt: _newest_capped_version verhindert Orphan-Kaperung, _pdf_group_key trennt Jahrgänge korrekt, _log_gk-Set verhindert Union-Doppelzählung. Selbst nachgeprüft: prefer_ver kann nie ein Orphan sein (stammt aus der bereits gekappten Eval-Version); longest-match-Zuordnung deterministisch. Kein Fake-Fund zu verwerfen.


Nachbesserung (Commit 74ef83f) — 7 Funde aus Cross-Model-Review

Konsolidierte, am Code bestätigte Funde (Fable + Mistral-Resample + Qwen). TDD für 1–7 (RED→GRÜN, +8 Tests in test_dashboard_pdf_table.py).

# Schwere Befund Fix
1 HOCH Legacy-Renderpfad (python eval_dashboard.pymain_build_html_render_pdf_table) crasht mit KeyError 'pages'_calc_pdf_table liefert das Feld nicht mehr Seiten-Spalte auch im Legacy-Render entfernt (colspan 9→8), None-Version abgefangen. Deprecated, crasht aber nicht mehr.
2 HOCH Jahrloser Autor-Key („beutelspacher") matcht via _pdf_matches BEIDE Jahrgangs-Gruppen (2014 und 2022); max(matched, key=len)-Tie ordnete order-abhängig/willkürlich zu Eindeutige Zuordnung: exakter Gruppen-Key gewinnt → sonst nur bei GENAU einem Präfix-Match zuordnen → mehrdeutiger Kurz-Key wird eigene Routing-only-Zeile (ehrlichste schlichte Variante: nicht raten). Deterministisch (Test: order-unabhängig).
3 MITTEL renderTable: const latest = rows[rows.length-1].version — „cur"-Highlight aus der letzten Tabellenzeile; die neuen Routing-only-Zeilen am Ende kapern „aktuell" (#191-Bugklasse) „aktuell" aus dem Server-Payload (warnings.current_version) statt aus der Zeilenreihenfolge. renderTable(rows, thresh, currentVer).
4 MITTEL KPI-Kachel „Evaluierte Notes" zählt distinct (40), die Sparkline kpi_trend.n weiter Eval-Instanzen (52) Neuer SSoT-Helfer _distinct_notes(rows), genutzt von Kachel (_calc_kpis) und Sparkline (Server quality_by_version[v]["n"]).
5 MITTEL Reine Orphan-Zeilen (alle Versionen > config.AGENT_VERSION) zeigen die versions[-1]-Fallback-Version unmarkiert orphan-Flag an der Zeile; Client rendert ⚠-Badge + Tooltip „Version neuer als config.AGENT_VERSION — vermutlich Branch-/WIP-Lauf", konsistent zum Waisen-Banner (#209).
6 NIEDRIG _pdf_group_key('') erzeugt Geisterzeile; _pdf_matches('') matcht ALLES und zieht jeden Routing-Run an sich Sentinel "(unbenannt)"; leere Keys werden nie als Match-Filter benutzt (in _calc_pdf_table und _calc_kpis n_pdfs).
7 NIEDRIG Routing-only-Zeilen tragen die Routing-Version im „Version (Eval)"-Feld, der Spaltenkopf verspricht aber „Version mit Eval-Daten" routing_only-Flag; Client zeigt „nur Routing"-Marker + präzisen Tooltip.
— (Fable) NIEDRIG max(matched, key=len) bei gleich langen Keys insertion-order-abhängig durch die Neu-Zuordnung (Punkt 2) obsolet — kein längenbasierter Tie mehr.

Merge-Hinweis für #219-Tooltips (Punkt 8)

Wichtig: Die per-PDF-Tabellen-.hint-Tooltips aus PR #219 sind auf diesem Branch NICHT enthalten (#219 ist nicht gemergt) — sie können hier nicht korrigiert werden. Beim Merge von #222 nach #219 sind die Tooltip-Texte an DIESE Semantik anzupassen (Herleitung aus _calc_pdf_table / _accept_from_runs):

Spalte Berechnung (dieser Branch) Korrekter Tooltip-Text
(a) Fehlerquote (hall) _pooled_hall_pct(at) über die Notes GENAU der neuesten Eval-Version des PDFs (gegen config.AGENT_VERSION gekappt, #191); gepoolt Σ halluzinierte Anker / Σ Anker; Quelle note_evals „Halluzinationsrate der neuesten Eval-Version dieses PDFs (gegen die Code-Version gekappt), gepoolt über die evaluierten Notes (Σ halluzinierte Anker / Σ Anker). Aus note_evals — nicht dem Routing."
(b) Belegrate (cov) _median(coverage_factual) über dieselben Notes der neuesten Eval-Version; Quelle note_evals „Belegrate (Anteil faktisch belegter Aussagen) der neuesten Eval-Version, Median über die evaluierten Notes. Aus note_evals."
(c) Akzeptanzrate je PDF (accept, Kachel-Balken + Chart) _accept_from_runs: gepoolte Rate Σ n_vault / Σ n_total über die Routing-Läufe der Version; Quelle pipeline_runs (Routing) — andere Quelle als hall/cov „Anteil automatisch in den Vault übernommener Notes, gepoolt über die Routing-Läufe (Σ Vault-Notes / Σ generierte Notes). Aus pipeline_runs (Routing) — andere Datenquelle als Fehlerquote/Belegrate; n = Zahl der Läufe, ggf. abweichende Routing-Version separat."

Suite / Lint (nach Nachbesserung)

pytest generative lib/decision_engine/tests shared/tests -q5709 passed, 0 failed (3 skipped, 4 deselected). ruff format --check . + ruff check . grün.

… pdf_key, Version+n pro Zeile, Union-Iteration (closes #194)

Die per-PDF-Tabelle stand strukturell auf drei Datengrundlagen je Zeile:
version/accept aus den Routing-Logs, hall/cov über ALLE Versionen gepoolt
(Substring-Match), n_notes = Eval-Instanzen. Ergebnis: Bates-Dreifachzeilen
(pdf_key-Drift bates/bates-2017/bates---2017), fehlende PDFs (nur in
quality_rows: Hertzum, Kaletski) und version-gemischte Kennzahlen.

- _pdf_group_key: kanonischer Gruppen-Schlüssel (SSoT mit PDF-Filter/-Dropdown,
  #202). Autor-Jahr-Slug — kollabiert Volltitel+Kebab derselben Quelle, hält
  Jahrgänge getrennt (Beutelspacher 2014 vs. 2022). _dedupe_pdf_options nutzt
  ihn mit.
- _calc_pdf_table: Union-Iteration über Eval- und Routing-Quelle; hall/cov/
  n_notes aus GENAU der neuesten Eval-Version (gegen config.AGENT_VERSION
  gekappt, #191-Orphan-Guard); n_notes = distinct Notes. accept bleibt aus dem
  Routing (note_evals hat kein acceptance_status) und wird getrennt via
  accept_ver/accept_n ausgewiesen.
- _chart_acceptance: leitet aus den kanonischen Tabellenzeilen ab (SSoT) und
  trägt n je Balken → ins-quality-Streifen filtert n>0 („0 % von n Notes" vs.
  „0 Notes").
- KPI n_pdfs: distinct kanonische Quellen statt len(log_data) (zählte bates 3×,
  verpasste eval-only PDFs). KPI n_notes: distinct Notes statt Eval-Instanzen.
- HTML: Seiten-Spalte entfernt (strukturell tot, DB-Fallback hart 0, nicht
  ableitbar); Version-/Akzeptiert-Spalten als Eval- bzw. Routing-Quelle
  beschriftet, accept-n in der Zelle.
- Tests: test_dashboard_pdf_table.py reproduziert die 4 Hauptbefunde (RED→GRÜN).
TillQuandel pushed a commit that referenced this pull request Jul 12, 2026
…valides Fokus-Markup (#187 Nachbesserung)

Cross-Model-Review (Fable + Qwen) an PR #219 fand 6 verbleibende Fehler:

1. Die 4 Hint-Trigger auf den KPI-Kacheln (Label, n-Tag, Delta-Chip,
   Ziel-Chip) hatten tabindex="0", sassen aber innerhalb des Kachel-
   <button> (ungueltige Verschachtelung fokussierbarer Elemente).
   tabindex entfernt -- Tooltip bleibt hover-faehig, Keyboard-Zugang
   laeuft ueber den Button.
2. Hints "Laufzeit"/"Pipeline-Tokens" behaupteten einen Fallback auf
   Gesamtwerte, falls die aktuelle Version keine Laeufe hat. cur_dur_h/
   cur_tokens (eval_dashboard.py:_calc_kpis) sind aber immer Zahlen
   (Summe ueber leere Liste = 0), der ??-Fallback greift nie -- Laufzeit
   zeigt dann "0,0 h", Pipeline-Tokens (zusaetzliche Falsy-Pruefung im
   Template) einen Strich "-". Texte korrigiert.
3. Hint "Evaluierte Notes" (Ziel-Chip) verschwieg den Zaehler/Nenner-
   Mismatch: n_notes ist nur die aktuelle Version, total_generated alle
   Versionen. Text praezisiert.
4. Hint "Fehlerquote" behauptete unbedingt gepoolte Berechnung;
   _pooled_hall_pct hat einen dokumentierten Mean-Fallback fuer Rows
   ohne Roh-Zaehler (aeltere DB-Eintraege vor 2026-06-27). Caveat
   ergaenzt.
5. Kalibrierungs-Strip: .hint lag auf dem Label-<div> (block) statt wie
   ueberall sonst auf einem <span> -- Unterstreichung zog sich ueber
   die volle Blockbreite. Auf <span> innerhalb des Label-<div>
   umgestellt (4 Stellen).
6. KPI-Kachel-<button> behielt title="Klick: Versions-Trend" --
   ueberlagerte beim Hover ueber Label/n-Tag/Chip das native title mit
   dem CSS-Hint-Pop. title durch aria-label ersetzt.

Nicht Teil dieser Nachbesserung: die 3 Tooltips der Vergleichstabelle
(Spaltenkoepfe Fehlerquote/Belegrate) und des Charts "Akzeptanzrate je
PDF" beschreiben die Aggregations-Semantik von _calc_pdf_table vor
PR #222 -- werden im #222-Branch korrigiert (#222 muss nach diesem PR
gemergt werden).

PR-Body von #219 um Nachbesserungs-Abschnitt ergaenzt (gh pr edit).
…Version aus Payload, Orphan-Kennzeichnung (#194 Nachbesserung)

Nachbesserung an PR #222 nach Cross-Model-Review (Fable/Mistral/Qwen, alle
am Code bestätigt).

1 HOCH  Legacy-Renderpfad (`python eval_dashboard.py` → _render_pdf_table)
        crashte mit KeyError 'pages' — _calc_pdf_table liefert das Feld nicht
        mehr. Seiten-Spalte auch im Legacy-Render entfernt (colspan 9→8),
        None-Version abgefangen.
2 HOCH  Ein jahrloser Autor-Key ("beutelspacher") ist Segment-Präfix MEHRERER
        Jahrgangs-Gruppen und wurde per max(key=len)-Tie order-abhängig einer
        Gruppe zugeschlagen. Neue Regel: exakter Gruppen-Key gewinnt; sonst nur
        bei GENAU einem Match zuordnen; mehrdeutiger Kurz-Key → eigene
        Routing-only-Zeile (nicht raten). Zuordnung deterministisch.
3 MITTEL renderTable: "cur"-Highlight kam aus rows[last].version (Zeilen-
        reihenfolge) — die neuen Routing-only-Zeilen am Ende kaperten "aktuell"
        (#191-Bugklasse). Jetzt aus warnings.current_version (Server-Payload).
4 MITTEL kpi_trend.n zählte Eval-Instanzen (52), die "Evaluierte Notes"-Kachel
        distinct (40). Neuer SSoT-Helfer _distinct_notes, von Kachel UND
        Sparkline genutzt.
5 MITTEL Reine Orphan-Zeilen (alle Versionen > config.AGENT_VERSION) zeigten
        die Fallback-Version unmarkiert. orphan-Flag + ⚠-Badge/Tooltip.
6 NIEDRIG Leerer Gruppen-Key (pdf=".pdf") erzeugte Geisterzeile und
        _pdf_matches('') matchte ALLES. Sentinel "(unbenannt)", leere Keys nie
        als Match-Filter (auch in _calc_kpis n_pdfs).
7 NIEDRIG Routing-only-Zeilen tragen die Routing-Version im "Version (Eval)"-
        Feld. routing_only-Flag + "nur Routing"-Marker + präziser Tooltip.

TDD: generative/tests/test_dashboard_pdf_table.py +8 Tests (RED→GRÜN) für
1,2,4,5,6,7. Suite 5709 passed, 0 failed. ruff format --check + check grün.
TillQuandel pushed a commit that referenced this pull request Jul 12, 2026
…/Extractor/Faithfulness-Skip-Events + Dashboard-Event-Robustheit (#197 Nachbesserung)

Extern verifizierte Funde (Mistral-Resample + Fable). Der Gate-Funnel ist oben
dicht: jeder Note-Verlust traegt jetzt ein stage_outcome-Event.

Funnel-Luecken (orchestrator.py, TDD):
- entity_resolution Cluster-Merge (consumed-Set): dedup/dropped/
  entity_resolution_merge, Survivor als detail (strukturell wie sibling-Dedup).
- _collect_stage6_results BaseException-Zweig: droppte ohne Event UND ohne
  Crash-Report — jetzt beides (Crash-Report ohne per-Note-Payload + Event).
- run_extractors_per_concept: empty-ctext (Konzept nicht im Volltext), None
  (leere Extraktion) und Exception (call_failed) → extractor/dropped-Events.
- _apply_faithfulness_gate: Gate aktiv + create, aber leerer page_index (PDF
  ohne [S. N]-Marker) → faithfulness/skipped/no_page_index.

Dashboard-/DB-Event-Robustheit (minimale Hunks):
- eval_dashboard._read_token_runs: Event-Records (kein model) vom call-Zaehler
  ausschliessen — verhindert aufgeblaehte calls + 0-Token-Phantomzeile.
- eval_dashboard_server model-Backfill: Scan-Fenster 5 -> 50 Zeilen (Events am
  Trace-Anfang tragen kein model).
- db.init_db: PRAGMA busy_timeout=5000 (kein WAL-Umbau) + _add_column-Helper,
  der nur 'duplicate column name' verschluckt und andere OperationalError
  (z.B. 'database is locked') re-raised.

Tests: 8 neu (5 orchestrator TDD in test_stage_outcome_events.py, 3 in neuem
test_stage_outcome_robustness.py). Suite 5705 -> 5713 passed, 0 failed.
ruff format/check clean.

Konflikt: #216 fasst run_extractors_per_concept Exception-Zweig an (failures) —
beim Merge beide Zeilen behalten. #222/#212 konfliktfrei zu Punkt 5/6.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants