Skip to content

feat(db): 0-Notes-Läufe in pipeline_runs erfassen (#330) - #338

Merged
TillQuandel merged 1 commit into
masterfrom
feat/330-zero-notes-runs
Jul 17, 2026
Merged

feat(db): 0-Notes-Läufe in pipeline_runs erfassen (#330)#338
TillQuandel merged 1 commit into
masterfrom
feat/330-zero-notes-runs

Conversation

@TillQuandel

Copy link
Copy Markdown
Owner

Closes #330.

Problem

Läufe, die 0 Notes erzeugen, kehrten in orchestrator.main() an zwei Stellen VOR dem Erfolgspfad-Insert (#198 P1) zurück und hinterliessen dadurch keine pipeline_runs-Zeile. Solche Läufe waren in DB und Dashboard unsichtbar — Run-Zählung, Token-/Kosten-Tracking und die Frage „wie oft produziert die Pipeline nichts?" waren systematisch untererfasst. 2 Belege aus der Coverage-Serie 2 (Lauf 1 dbv-Framework: 198s/15.325 Tokens, Lauf 4 Witt: 268s/51.272 Tokens — beide Exit 0, beide ohne DB-Zeile).

Abgedeckte Frühausstiege

Beide if not drafts:-Rückkehrpunkte in main() (nach _run_extraction_stages, vor Dedup/Stage-6/Vault-Write) bekommen jetzt einen insert_run-Aufruf mit n_generated=0 und echten Token-/Dauer-Werten:

  1. Direkt nach der Extraktion (if not drafts:) — deckt sowohl legitimen Konzeptmangel (0 Konzepte geplant, alle action=skip, oder alle origin=secondary_mention) als auch den stillen [MITTEL] Lauf mit Totalverlust endet mit "Fertig."/Exit 0 — Fehlersignal nur in stderr #281-Totalverlust (>=1 Konzept versucht, 0 überlebt, exit_code == _EXIT_TOTAL_LOSS) ab.
  2. Nach _drop_artifacts() (if not drafts:) — Konzepte wurden extrahiert (n_extracted >= 1), landeten aber komplett als Abwesenheits-Artefakte statt echter Notes.

abort_reason unterscheidet den Grund:

abort_reason Bedeutung
no_concepts 0 Konzepte versucht, keine Sekundär-Erwähnungen (Planner fand nichts Actionable)
all_secondary_mentions 0 Konzepte versucht, aber Sekundär-Erwähnungen vorhanden (alles als secondary_mention gefiltert)
extraction_total_loss >=1 Konzept versucht, 0 überlebt (stiller #281-Drop)
all_artifacts Konzepte extrahiert, aber komplett als Abwesenheits-Notes verworfen

Erfolgspfad-Inserts (weiter unten in main(), >=1 geschriebene Note) bleiben unverändert — abort_reason dort implizit NULL (Key wird nicht gesetzt).

Feld-/Migrationsentscheidung

Additive nullable Spalte abort_reason TEXT in pipeline_runs (kein bestehendes Feld passte semantisch). Migration über das etablierte _add_column-Muster in db.py::init_db() (analog #235 profile, #239 wall_clock_s) — Bestandszeilen bleiben NULL, kein Datenverlust, kein Schema-Bruch. Bewusst kein DEFAULT '', damit NULL echtes „kein Abbruch" bedeutet statt eines leeren, aber gesetzten Strings.

Ein neuer Helper _insert_zero_notes_run() in orchestrator.py bündelt den DB-Write (Trace-Token-Aggregation, AGENT_VERSION, runtime_config.profile, eigenes try/except analog dem Erfolgspfad-Insert) und wird an beiden Frühausstiegen aufgerufen.

Dashboard-Neutralität (Punkt 4)

Geprüft, keine Anpassung nötig — eval_dashboard_server.py/eval_dashboard.py bleiben unverändert:

  • Der DB-Fallback in eval_dashboard_server.py berechnet accept_pct = round(n_vlt / n_gen * 100, 1) if n_gen > 0 else 0.0 — für einen 0-Notes-Run ist n_gen == 0, der Guard greift, kein ZeroDivisionError.
  • runs_by_version/pdf_n_total-Aggregation (fix(dashboard): Overlap-Guard auf Routing-Corpus mit n_total-Gewichtung (#314) #336) summiert nur n_total/n_vault/… — ein 0-Notes-Run addiert überall 0, verzerrt also keine Summen. n_runs steigt korrekt um 1 (das ist der Zweck des Fixes: die Run-Zählung soll diese Läufe jetzt mitzählen).
  • note_evals-basierte KPIs (Halluzinationsrate, Coverage, query_kpi_trend) sind komplett unberührt — ein 0-Notes-Run schreibt nie note_evals-Zeilen (keine Note wurde evaluiert).
  • version_delta()'s PDF-Overlap-Guard (total = sum(latest_pdf_notes.values()); if total: …) bleibt sicher, da ein 0-Notes-Run nur einen 0-Beitrag zur Summe liefert.

RED-Nachweis

generative/tests/test_zero_notes_run_persisted.py (neu, 4 Tests) + generative/tests/test_db.py (4 neue Tests) schlugen vor der Implementierung fehl:

FAILED generative/tests/test_zero_notes_run_persisted.py::test_no_concepts_run_persists_with_abort_reason - sqlite3.OperationalError: no such column: abort_reason
FAILED generative/tests/test_zero_notes_run_persisted.py::test_all_secondary_mention_run_persists_with_distinct_abort_reason - sqlite3.OperationalError: no such column: abort_reason
FAILED generative/tests/test_zero_notes_run_persisted.py::test_extraction_total_loss_run_persists_with_abort_reason - sqlite3.OperationalError: no such column: abort_reason
FAILED generative/tests/test_zero_notes_run_persisted.py::test_all_artifacts_dropped_run_persists_with_abort_reason - sqlite3.OperationalError: no such column: abort_reason
FAILED generative/tests/test_db.py::test_pipeline_runs_has_abort_reason_column
FAILED generative/tests/test_db.py::test_insert_run_stores_abort_reason
FAILED generative/tests/test_db.py::test_insert_run_abort_reason_defaults_to_null
FAILED generative/tests/test_db.py::test_orchestrator_migrates_existing_db_missing_abort_reason_column
8 failed, 11 passed

Nach Implementierung: alle grün (siehe Suite-Zahlen unten).

Suite-Zahlen

  • uv run pytest generative lib/decision_engine/tests shared/tests -q: 6197 passed, 3 skipped, 8 deselected, 0 failed (475s)
  • uv run ruff check .: All checks passed!
  • uv run ruff format --check .: 303 files already formatted (nach ruff format auf den einen betroffenen Testfile)

Geänderte Dateien

  • generative/orchestrator.py_insert_zero_notes_run()-Helper + 2 Aufrufstellen (beide Frühausstiege NACH Planner/Extractor, VOR Dedup/Stage-6/Vault-Write; die separat bearbeitete Extractor-Dispatch-Region/run_extractors_per_concept bleibt unangetastet)
  • generative/db.pyinsert_run() + Migration um abort_reason erweitert
  • shared/db_schema.py — Schema um abort_reason TEXT erweitert
  • generative/tests/test_zero_notes_run_persisted.py — neu, 4 Integrationstests (--load-drafts-Harness wie test_orchestrator_total_loss_exit_code.py)
  • generative/tests/test_db.py — 4 neue Unit-Tests + Regex-Fix in der bestehenden wall_clock_s-Migrationsprobe (jetzt nicht mehr letzte Spalte, brauchte ein Komma-Update)

Laeufe, die 0 Notes erzeugen (Konzeptmangel oder stiller Totalverlust,
#281), kehrten in orchestrator.main() an zwei Stellen vor dem
Erfolgspfad-Insert zurueck und hinterliessen dadurch keine
pipeline_runs-Zeile. Solche Laeufe waren in DB/Dashboard unsichtbar --
Run-Zaehlung, Token-/Kosten-Tracking und die Frage "wie oft produziert
die Pipeline nichts?" waren systematisch untererfasst.

Beide Fruehausstiege (direkt nach der Extraktion, und nach
_drop_artifacts()) schreiben jetzt per neuem _insert_zero_notes_run()-
Helper eine Zeile mit n_generated=0 und echten Token-/Dauer-Werten.
Additive nullable Spalte abort_reason unterscheidet den Grund
(no_concepts, all_secondary_mentions, extraction_total_loss,
all_artifacts) -- Migration ueber das etablierte _add_column-Muster,
Erfolgspfad-Zeilen bleiben NULL.
@TillQuandel
TillQuandel merged commit 1afbed0 into master Jul 17, 2026
4 checks passed
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.

[NIEDRIG] 0-Notes-Läufe hinterlassen keinen pipeline_runs-Eintrag — unsichtbar in DB und Dashboard

2 participants