Skip to content

perf(eval): Audit-Pass rechnet nur auditierte Claims neu (#151)#215

Merged
TillQuandel merged 2 commits into
masterfrom
fix/151-audit-doppelberechnung
Jul 12, 2026
Merged

perf(eval): Audit-Pass rechnet nur auditierte Claims neu (#151)#215
TillQuandel merged 2 commits into
masterfrom
fix/151-audit-doppelberechnung

Conversation

@TillQuandel

@TillQuandel TillQuandel commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Problem

Teilfix zu #151 (Nebenbefund PR #181): eval_note (generative/eval_quality_v4.py) rief _claim_scores_from_judge im Audit-Pfad zweimal mit identischen Primaer-Inputs auf:

claim_scores = _claim_scores_from_judge(
    claims, retrieved, judge_rows, pdf_text, corpus_normalized=corpus_normalized
)
audit_indices = _audit_indices(claim_scores, note_title)
if audit_indices:
    ...
    claim_scores = _claim_scores_from_judge(
        claims, retrieved, judge_rows, pdf_text, audit_rows, corpus_normalized=corpus_normalized
    )

Der zweite Aufruf lief ueber ALLE judge_rows, nicht nur ueber die auditierten Claims. Fuer nicht-auditierte Claims ist audit_by_idx.get(claim_idx) in beiden Durchlaeufen immer None — das Ergebnis war also bit-identische Doppelarbeit (reine CPU, kein LLM-Call, aber unnoetige Judge-Row-Verarbeitung + Evidence-Verifikation fuer jeden Claim ein zweites Mal).

Fix

Der zweite Durchlauf beschraenkt sich jetzt auf die judge_rows der tatsaechlich auditierten Claims (audited_judge_rows) und mergt das Ergebnis per claim_idx zurueck in claim_scores. Nicht-auditierte Eintraege bleiben unveraendert aus dem ersten Durchlauf.

RED-Beweis

Aufruf-Zaehler-Spy auf _claim_scores_from_judge (generative/tests/test_audit_double_call.py::TestAuditPassRecomputesOnlyAuditedClaims): gegen den unveraenderten Code zeigte der Test, dass der Audit-Durchlauf exakt dieselbe Claim-Index-Menge {0,1,2,3,4,5} wie der Primaer-Durchlauf abdeckte (assert audit_idxs < set(primary_call["judge_idxs"]) schlug fehl: "Both sets are equal"). Nach dem Fix ist es eine echte Teilmenge.

Aequivalenz-Nachweis

TestEvalNoteResultUnchanged vergleicht den vollen eval_note-Output (claim_scores, Aggregat-Kennzahlen, llm_usage) gegen einen eingefrorenen Snapshot des UNVERAENDERTEN Codes auf origin/master, erzeugt mit demselben synthetischen Fixture (6 Claims, davon 2 auditiert — einer per Low-Cosine-System-Trigger, einer per stratifiziertem Sample; einer der beiden zeigt eine echte Label-Aenderung durch Audit-Override). Ergebnis: bit-identisch bis auf timestamp (per Design bei jedem Lauf datetime.now()). Vor dem Fix ist dieser Test gruen (Snapshot korrekt eingefroren); nach dem Fix bleibt er gruen (kein Verhaltensunterschied).

Suite-Ergebnis

pytest generative lib/decision_engine/tests shared/tests -q: 5695 passed, 3 skipped, 4 deselected, 0 Failures. Testzahl gesamt (inkl. deselected): origin/master 5700, dieser Branch 5702 (+2 durch diesen PR). ruff format --check . und ruff check .: sauber.

Cross-Model-Review

Mistral (magistral-medium-latest) hat den Diff geprueft: Merge-Logik korrekt bestaetigt, Edge Cases (leere audit_indices, Reihenfolge, doppelte claim_idx) abgedeckt, Diff minimal und im Scope. Kein Fund.

Scope

Nur generative/eval_quality_v4.py + Test. Angefasst wird ausschliesslich der doppelte _claim_scores_from_judge-Aufruf im Audit-Pfad — Stage-8-Parallelisierung, concept_text_window, quality_history-Format/Retention, Cache-Root-Konsolidierung (config.py vs. db.py) und der source_coverage/covered_pages-Befund aus #151 sind bewusst NICHT Teil dieses PRs (brauchen Maintainer-Entscheidungen).

Teilfix zu #151 — Issue bleibt offen.

Nachbesserung (Fable-Kontrollreview)

  • Aequivalenz-Snapshot entkoppelt (test_audit_double_call.py): EXPECTED_RESULT pinnte zuvor "version": "v0.3.140" und das komplette MODEL_CONFIG-Dict als Literale. Der Maintainer-Modus _auto_version_bump schreibt AGENT_VERSION bei jeder Pipeline-Code-Aenderung hoch — der Snapshot-Test haette dann grundlos gebrochen. Fix: _run_eval_note ruft eq.eval_note(..., pipeline_version="v0.0.0") mit fixem Wert auf (Konvention wie test_per_agent_tracking.py::test_aggregate_includes_model_config), EXPECTED_RESULT["version"] folgt; model_config referenziert jetzt direkt eq.MODEL_CONFIG statt eine Kopie zu pflegen. Die Bit-Identitaets-Aussage des Tests bleibt erhalten (_aggregate() schreibt exakt dieses Objekt unveraendert ins Ergebnis).
  • Zahlendreher in der Suite-Angabe korrigiert: die urspruengliche "5699 Tests gesamt" stimmte mit keiner Zaehlung ueberein (origin/master = 5700, dieser Branch = 5702). Oben korrigiert.
  • Bekannter vorbestehender Randfall (nicht Teil dieses PRs): beim Fuzzen von _normalize_judge_rows in generative/eval_quality_v4.py (~Z. 481-501) gefunden — bei ungueltigem Label wird parse_error gesetzt, evidence bleibt aber stehen. _claim_scores_from_judge berechnet daraus evidence_verified=True/False statt None, was einen latenten ClaimInput.__post_init__-Crash ausloesen kann. Ausserhalb des Scopes dieses PRs; als Hinweis fuer eine moegliche Folge-Issue dokumentiert.

Tilltime added 2 commits July 12, 2026 02:05
…151)

Teilfix zu #151 (Nebenbefund PR #181): eval_note rief
_claim_scores_from_judge im Audit-Pfad zweimal mit identischen
Primaer-Inputs auf (einmal ohne audit_rows, dann nochmal MIT
audit_rows aber wieder ueber ALLE judge_rows). Fuer nicht-auditierte
Claims war der zweite Durchlauf bit-identische Doppelarbeit (reine
CPU, kein LLM-Call), da audit_by_idx.get(claim_idx) fuer sie in
beiden Durchlaeufen ohnehin immer None liefert.

Der zweite Durchlauf beschraenkt sich jetzt auf die judge_rows der
tatsaechlich auditierten Claims; das Ergebnis wird per claim_idx in
claim_scores zurueckgemergt. Nicht-auditierte Eintraege bleiben
unveraendert aus dem ersten Durchlauf.

Zwei Tests (generative/tests/test_audit_double_call.py):
- Aufruf-Zaehler-Spy auf _claim_scores_from_judge: RED zeigte, dass
  der Audit-Durchlauf exakt dieselbe Claim-Menge wie der Primaer-
  Durchlauf abdeckte; nach dem Fix ist es eine echte Teilmenge.
- Aequivalenz-Regression gegen einen eingefrorenen Snapshot des
  unveraenderten Codes (gleiches Fixture, Claims mit und ohne
  audit_rows-Eintrag): voller eval_note-Output bit-identisch bis auf
  timestamp.

Kanonische Suite (generative lib/decision_engine/tests shared/tests):
5695 passed, 3 skipped, 4 deselected. ruff format/check sauber.
Cross-Model-Review (Mistral magistral-medium-latest): kein Fund.

Teilfix zu #151 -- Issue bleibt offen (Stage-8-Parallelisierung,
concept_text_window, quality_history-Format, Cache-Root-
Konsolidierung, source_coverage/covered_pages sind separate Punkte).
@TillQuandel TillQuandel merged commit eb060ea into master Jul 12, 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.

2 participants