fix(eval): CID-Font-Artefakte (U+0231/U+022C) im Quelltext zurückmappen — Grounder bestraft OCR-degradierte PDFs nicht mehr (#278)#298
Merged
Conversation
…ppen — quellentreue Notes nicht mehr als Halluzination markiert (#278) Root Cause (per PyMuPDF am Jockisch-2010-PDF verifiziert): Die eingebettete Schriftart mappt ihr Leerzeichen-Glyph auf U+0231 (ȱ, 5173x) und ihren Trennstrich auf U+022C (Ȭ, 240x). Der extrahierte Quelltext besteht dadurch fast nur aus zusammengeschriebenen Woertern (DieȱAkzeptanzȱistȱ...) — Cosine- Chunk-Retrieval und Zitat-Fuzzy-Verifikation scheitern an einer Quelle, die das generierende LLM korrekt gelesen hat. Ergebnis: False-Positive- Halluzinationen (57,1%/42,9%) und 0,0% Coverage bei laut Opus-Tiefenreview ~100% quellentreuen Notes (Repro-Runs 20260714-185639, 20260714-215345). Fix: Rueckmapping U+022C→'-' und U+0231→' ' am Anfang von eval_common._normalize, VOR Silbentrennungs- und Whitespace-Normalisierung — wirkt damit konsistent auf Chunks, Embeddings, Judge-Kontext-Pool und Evidence-Corpus (_normalize_for_evidence). PDFs ohne diese Artefakte bleiben byte-identisch (Regressionstest). EVAL_VERSION 4.2 → 4.3: Der Judge-Kontext-Pool aendert sich fuer betroffene PDFs — dieselbe Methodik-Aenderungs-Klasse wie der 4.1→4.2-Bump (#232). Ohne Bump wuerde der content-adressierte Eval-Cache fuer unveraenderte Notes das alte Artefakt-Ergebnis liefern (stille Staleness, genau der dokumentierte Schutzzweck des Bumps). Snapshot in test_audit_double_call nachgezogen. Tests: TDD (3 RED → GREEN) mit woertlichen PyMuPDF-Extrakten aus dem Original-PDF; volle Suite 5940 passed.
TillQuandel
added a commit
that referenced
this pull request
Jul 15, 2026
…achen (#303) Ist-Zustand (Dry-Run auf master a720399): "Zu evaluieren: 0 Notes" — alle 14 Baseline-Ordner in .cache/eval/baseline/ wurden uebersprungen. Zwei Bugs: 1. _already_done() pruefte hartkodiert eval_version='4.1'. Nach dem EVAL_VERSION-Bump auf 4.3 (#298) markierte das jede historisch mit 4.1 evaluierte Note als "schon erledigt" — obwohl deren Re-Evaluierung unter der aktuellen Version der Zweck des Sweeps ist. 2. PDF_MAP war veraltet (verwies auf nicht-existierende Dateinamen, kannte die meisten der 14 Ordner gar nicht) und nutzte Praefix-Matching (folder.startswith(prefix)) — das kollidiert real: "Porst-2014-Auszug- S1-40-bak-preclean-20260530" beginnt mit "Porst-2014-Auszug-S1-40" und haette denselben Eintrag geerbt, obwohl der Backup-Ordner ausgeschlossen sein soll. Fix: - Guard scopt auf _eq.EVAL_VERSION (additiv — Alt-Zeilen bleiben, Resume innerhalb einer Version bleibt erhalten). - PDF_MAP komplett rekonstruiert aus der historischen Note->PDF-Zuordnung in atomic_analytics.db (note_evals.pdf je vault__*.md-Datei, eval_version=4.1) + Existenz-Check gegen LITERATURE_DIR (Config-Konstante statt hartkodierter OneDrive-Pfade — CLAUDE.md-Pfadregel). Exakter Ordnername statt Praefix. 6 Ordner gemappt, 8 explizit ausgeschlossen (dokumentiert im Code-Kommentar je Eintrag): leerer Alt-Ordner, Backup, 3x Test-/Struktur-Fixture, 2x fehlendes historisches PDF (bates-2017.pdf, zettelkasten-primer.pdf), 1x Auszug-PDF fehlt (Porst — bewusst NICHT durchs Voll-PDF ersetzt, Score- Verwaesserung durch mehr Chunks; offener Punkt s. PR-Body). - Neue --baseline-dir-Option (Default ROOT-relativ) fuer Funktionsnachweise aus einem isolierten Worktree ohne eigenes .cache. Dry-Run danach (--baseline-dir gegen das Haupt-Repo-Cache, read-only): 27 Notes ueber 6 Ordner zu evaluieren, 0 uebersprungen, 8 Ordner sauber geskippt. Tests: TDD (9 RED -> GREEN, test_reeval_baseline_guard.py neu). Volle Suite (generative + lib/decision_engine/tests + shared/tests): 5969 passed, 3 skipped, 8 deselected. 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.
Problem
Issue #278: Der Eval-Grounder scheitert am extrahierten Quelltext des Jockisch-2010-PDFs — quellentreue Notes werden fälschlich als Halluzination markiert:
20260714-185639)20260714-215345)Ursache
Per PyMuPDF direkt am Original-PDF verifiziert (nicht angenommen — der im Issue vermutete generische „Whitespace-Kollaps" ist beim Nachmessen präziser einzugrenzen): Die eingebettete Schriftart des PDFs mappt ihr Leerzeichen-Glyph auf U+0231 („ȱ", 5173×) und ihren Trennstrich auf U+022C („Ȭ", 240×) statt auf ASCII-Space/-Hyphen. Der von
eval_common._extract_page_text/_raw_page_linesgelesene Text besteht dadurch fast nur aus zusammengeschriebenen Wörtern:Alle anderen Nicht-ASCII-Zeichen im PDF (echte Umlaute, vereinzelte Formel-/Griechisch-Glyphen ≤8×) sind Größenordnungen seltener und ohne belegte Wirkung auf das Symptom.
Folge: Cosine-Chunk-Retrieval (
_retrieve_claim_contexts) und Zitat-Fuzzy-Verifikation (_verify_evidence_normalized, Schwelle 0.90) scheitern an einer Quelle, die das generierende LLM korrekt gelesen hat — der saubere Note-Claim erreicht gegen den korrupten Corpus nur Score 0.622 (RED-Test unten).Fix
Rückmapping
U+022C → "-"undU+0231 → " "am Anfang voneval_common._normalize, vor Silbentrennungs- und Whitespace-Normalisierung (der korrupte Trennstrich wird so von der bestehenden Silbentrennungs-Regex direkt mitrepariert)._normalizeist der gemeinsame Trichter für Chunk-Texte, Embeddings, Judge-Kontext-Pool und Evidence-Corpus (_normalize_for_evidenceruft es auf) — der Fix wirkt damit konsistent auf Retrieval UND Zitat-Verifikation, ohne neue Codepfade. PDFs ohne diese Artefakte bleiben byte-identisch (Regressionstest; die zwei Codepoints kommen in gesundem Text nicht vor).Bewusst NICHT in diesem PR: der im Issue zusätzlich genannte CID-Filter vor der Akronym-Expansion (Pipeline-Seite,
tanzmodellȱ-Durchschlag in den Note-Body) — das ist Generierungs-, nicht Eval-Scope und überschneidet sich mit #279/PR #295 (H1-/Akronym-Fix). Falls gewünscht, als Folge-Issue.Auswirkung auf die Messung — warum das ehrlicher ist (nicht schöngerechnet)
Was sich ändert: Bei PDFs mit diesen CID-Artefakten sieht der Judge erstmals lesbaren Quelltext; die Zitat-Verifikation vergleicht sauberen gegen sauberen Text.
hallucination_ratesinkt undcoverage_ratesteigt auf solchen PDFs — nicht, weil die Messlatte gesenkt wurde, sondern weil bisher gegen einen Quelltext gemessen wurde, den die Quelle so nicht enthält. Der Beleg, dass die alten Werte Artefakte waren (nicht echte Halluzinationen), ist extern und unabhängig vom Fix: Opus-Tiefenreview mit 10/16/14 geprüften Ankern, mehrere wörtliche Belege, 0 echte Halluzinationen.Kein Masking-Risiko-Zuwachs: Der Fix injiziert keinen Kontext, ändert keine Labels, keine Schwellen und keine Decision-Logik — er repariert deterministisch zwei Glyph-Codepoints im Quelltext-Kanal. Eine echte Halluzination gegen ein Jockisch-artiges PDF wird nach dem Fix strenger geprüft als vorher, weil der Judge die Quelle jetzt tatsächlich lesen kann (vorher war auf solchen PDFs jedes Urteil Rauschen — in beide Richtungen). Für alle anderen PDFs: keinerlei Verhaltensänderung.
Testnachweis (TDD)
RED (vor Fix), Testdaten sind wörtliche PyMuPDF-Extrakte aus dem Original-PDF:
GREEN (nach Fix):
4 passed. End-to-End-Stichprobe:_extract_page_textauf Jockisch S. 6 liefert jetzt lesbares Deutsch, 0× U+0231 / 0× U+022C (vorher dominierten sie die Seite).Volle Suite (nach Fix inkl. EVAL_VERSION-Bump): 5940 passed, 3 skipped, 8 deselected in 329 s.
ruff check+ruff format --checkauf allen 4 geänderten Dateien sauber.EVAL_VERSION 4.2 → 4.3 (gebumpt, Begründung)
Die im Code dokumentierte Konvention (Kommentar am 4.1→4.2-Bump +
EVAL_CACHE_NAMESPACE-Docstring) verlangt den Bump hier zwingend: Der Judge-Kontext-Pool ändert sich für betroffene PDFs (dieselbe Methodik-Änderungs-Klasse wie #232), und der content-adressierte Eval-Cache ist versions-gescoped — ohne Bump würde eine inhaltlich unveränderte Jockisch-Note das alte Artefakt-Ergebnis aus demeval-v4.2-Cache ziehen (exakt die „stille Staleness", vor der der Namespace-Mechanismus laut Docstring schützt). Kosten: Cache-Invalidierung auch für nicht betroffene PDFs (deren Ergebnisse bleiben inhaltlich identisch, werden aber einmal frisch gerechnet). Snapshot intest_audit_double_call.pynachgezogen (gleiche Stelle wie beim 4.2-Bump). Falls Till stattdessen 4.2 behalten will: einzelner Revert von zwei Zeilen, keine weiteren Abhängigkeiten.Offene Entscheidungen / Grenzen
quality_history/runs/baseline/note_evals) wurden nicht angefasst. Der 4.2/4.3-Skew bleibt gewollt sichtbar.Fixes #278
Adversariale Kontrolle (Opus, vor Merge)
Verdikt: Freigabe. Alle Zahlen unabhängig reproduziert: U+0231 = 5173×, U+022C = 240× am Original-PDF (beide Extraktionswege); RED-Score 0.6216 → 0.622 bestätigt; Bump-Konvention identisch zum 4.1→4.2-Muster (
git show 70fee82), kein hängender 4.2-Literal; Mapping idempotent und vor Silbentrennung/Lowercasing korrekt platziert; Normalisierung wirkt symmetrisch auf beide Vergleichsseiten (kein Masking-Mechanismus durch diesen Fix).Framing-Präzisierung aus der Kontrolle: Der Fix bringt das Jockisch-PDF auf Parität mit allen anderen PDFs (vorher: Evidence-Check bei „reject-all" festgenagelt). Die danach greifende, pre-existing systemische Schwäche —
token_set_ratiogegen den Gesamt-Volltext lässt kurze In-Domain-Rekombinationen mit Score 1.0 durch — gilt für jedes PDF und ist nicht Teil dieses PRs (Folge-Issue-Kandidat: Evidence-Verifikation gegen kontiguierliches Fenster/Chunk). Randnotiz: U+022C/U+0231 haben legitime Vorkommen in livischer Orthographie bzw. phonetischer Transkription — für diesen Korpus (DE/EN-Wissenschaftstexte) praktisch irrelevant, und das Mapping wirkt ausschließlich im Eval-Matching, nie auf Nutzer-Text.