Skip to content

feat(eval): generische CID-Korruptions-Warnung ohne Messverhaltens-Aenderung (#306)#337

Merged
TillQuandel merged 2 commits into
masterfrom
feat/306-cid-detection
Jul 17, 2026
Merged

feat(eval): generische CID-Korruptions-Warnung ohne Messverhaltens-Aenderung (#306)#337
TillQuandel merged 2 commits into
masterfrom
feat/306-cid-detection

Conversation

@TillQuandel

@TillQuandel TillQuandel commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Closes #306.

Problem

PR #298 fixt exakt zwei Codepoints eines defekten eingebetteten Fonts im
Jockisch (2010)-PDF (U+0231 → Leerzeichen, 5173×; U+022C → Trennstrich,
240×; per PyMuPDF am Original nachgemessen, siehe eval_common._normalize).
Jedes andere PDF mit einem anderen CID-Glyph-Mapping erzeugt dasselbe
Fehlerbild — quellentreue Notes werden fälschlich als Halluzination
markiert — und bleibt unrepariert, bis jemand manuell eine neue
Replace-Zeile einbaut. Die adversariale Kontrolle zu #298 bestätigte am
selben PDF weitere seltene (≤8×) Rest-Artefakte (ĺ, Έ, ȟ) als Vorboten
dieser Fehlerklasse.

Fix: Erkennung + Warnung, kein Rückmapping

Neues Modul generative/eval_text_quality.py::detect_cid_suspect():
prüft den per PyMuPDF extrahierten Volltext auf ein verdächtiges
Nicht-ASCII-Häufigkeitsprofil. Erwartbarer "gesunder" Codepoint-Bereich für
deutschen/englischen Fachtext: ASCII + Latin-1 Supplement (Umlaute, ß) +
Latin Extended-A, abzüglich einer Whitelist gängiger typografischer
Satzzeichen (Gedankenstriche, "smarte" Anführungszeichen, Ellipse —
PyMuPDF-Extraktionsartefakte, keine CID-Signale). Dominiert ein einzelner
Codepoint außerhalb davon mit ≥ 0,5 % aller Zeichen, gilt das als
CID-Verdacht — exakt das Muster, das U+0231 im Jockisch-PDF zeigte (ein
Glyph ersetzt praktisch jedes Leerzeichen).

Verdrahtung in eval_quality_v4.py (additiv, in _pdf_artifacts()/
_aggregate()):

  • neues Feld result["pdf_text_suspect_cid"] ({codepoint, count, ratio, total_chars} oder None)
  • Flag "cid_font_suspect" in result["quality_flags"] (dasselbe
    Mechanismus-Muster wie die bereits existierenden Flags
    possible_retrieval_miss, parse_errors_present etc.)
  • prominente stderr-Warnung beim Öffnen der PDF (einmal pro PDF pro
    Prozess, nicht pro Note — dank bestehendem PDF-Artefakt-Cache)

Kein generisches Rückmapping (das bleibt bewusst Handarbeit pro PDF wie
in #298 — ein falsches automatisches Mapping würde stillschweigend
Text verändern), keine Label-Änderung, keine Raten-Änderung.

Kalibrierungs-Belege (synthetisch, siehe test_cid_suspect_detection.py)

Profil Ergebnis
Jockisch-artiges Muster (U+0231 ersetzt Leerzeichen, wiederholter Satz) count=280, total_chars=3040, ratio=9,21 % → Flag gesetzt
Sauberer deutscher Text, hohe Umlaut-Dichte (ä ö ü ß, 16 320 Zeichen) kein Flag (Umlaute liegen strukturell in Latin-1, egal wie häufig)
Grenzfall 499/100 000 Zeichen = 0,499 % kein Flag (knapp unter Schwelle)
Grenzfall 501/100 000 Zeichen = 0,501 % Flag gesetzt (knapp über Schwelle)
Typografische Satzzeichen (Gedankenstriche, hohe Dichte) kein Flag (Whitelist)

Warum kein EVAL_VERSION-Bump nötig ist

Jede geänderte Zeile wurde daraufhin geprüft, ob sie Chunk-Texte, Embeddings,
Evidence-Corpus, Judge-Prompt, Labels oder Raten beeinflusst:

  • detect_cid_suspect() liest nur den bereits vorhandenen full_text lesend,
    verändert ihn nicht — Chunking/Retrieval/Evidence-Verifikation laufen exakt
    wie vorher auf demselben Text.
  • _aggregate() bekommt den neuen cid_suspect-Parameter nur additiv: er
    fügt einen Eintrag zu quality_flags hinzu und setzt ein neues Feld
    pdf_text_suspect_cid — alle anderen Result-Keys (Raten, Label-Counts,
    claim_scores, hallucination_rate, coverage_rate, ...) bleiben
    unverändert. Dediziert getestet:
    TestAggregateCidSuspectAdditive::test_cid_suspect_does_not_change_any_other_result_field
    vergleicht ein _aggregate()-Ergebnis mit und ohne CID-Verdacht bei
    identischem Input und belegt: die einzige Differenz sind die zwei
    neuen additiven Keys.
  • Der content-adressierte Eval-Cache (EVAL_CACHE_NAMESPACE) bleibt an
    EVAL_VERSION gebunden, die unverändert bleibt — kein Grund zur
    Invalidierung, da bit-identisches Messverhalten bei identischem Input.

Bei identischem Input sind gemessene Halluzinationsraten/Labels vor und
nach diesem PR bit-identisch.

TDD: RED → GREEN

RED (vor der Verdrahtung, 5 Fehlschläge — die reine Heuristik-Funktion war
zu diesem Zeitpunkt schon implementiert/kalibriert, die Verdrahtung in
_pdf_artifacts/_aggregate noch nicht):

FAILED generative/tests/test_cid_suspect_detection.py::TestPdfArtifactsCidWiring::test_clean_pdf_has_no_cid_suspect
  - AttributeError: '_PdfArtifacts' object has no attribute 'cid_suspect'
FAILED generative/tests/test_cid_suspect_detection.py::TestPdfArtifactsCidWiring::test_corrupted_full_text_sets_cid_suspect
  - AttributeError: '_PdfArtifacts' object has no attribute 'cid_suspect'
FAILED generative/tests/test_cid_suspect_detection.py::TestAggregateCidSuspectAdditive::test_cid_suspect_adds_flag_and_detail_field
  - TypeError: _aggregate() got an unexpected keyword argument 'cid_suspect'
FAILED generative/tests/test_cid_suspect_detection.py::TestAggregateCidSuspectAdditive::test_no_cid_suspect_leaves_field_none_and_no_flag
  - TypeError: _aggregate() got an unexpected keyword argument 'cid_suspect'
FAILED generative/tests/test_cid_suspect_detection.py::TestAggregateCidSuspectAdditive::test_cid_suspect_does_not_change_any_other_result_field
  - TypeError: _aggregate() got an unexpected keyword argument 'cid_suspect'
5 failed, 6 passed in 4.55s

GREEN (nach Verdrahtung in _PdfArtifacts/_pdf_artifacts()/_aggregate()/
eval_note()): 11 passed in 2.01s.

Suite-Zahlen (voller Lauf, sauber, keine Parallel-Prozesse)

uv run pytest generative lib/decision_engine/tests shared/tests -q
6192 passed, 3 skipped, 8 deselected, 33 warnings in 311.11s (0:05:11)
EXIT_CODE=0

uv run ruff check . → All checks passed.
uv run ruff format --check . → 303 files already formatted.

(Ein erster Suite-Lauf zeigte 1 Fehlschlag in
test_audit_double_call.py::TestEvalNoteResultUnchanged::test_full_result_matches_pre_fix_snapshot
— Zeitpunkt-Artefakt: die Testsammlung dieses Laufs hatte den alten Stand der
Datei erfasst, bevor ich den Snapshot um das neue additive Feld ergänzt
hatte. Der zweite, saubere Lauf mit finalem Code-Stand bestätigt 0
Fehlschläge.)

Geänderte Dateien

  • generative/eval_text_quality.py (neu) — Heuristik + CidSuspectResult
  • generative/eval_quality_v4.py — Verdrahtung (additiv)
  • generative/tests/test_cid_suspect_detection.py (neu) — Heuristik- +
    Verdrahtungs-Tests
  • generative/tests/test_audit_double_call.py — Snapshot um additives Feld
    ergänzt
  • docs/evaluation.md — Abschnitt zur neuen Warnung + Tabellen-/Absatz-Update
    in Kapitel 5

Abgrenzung

#27 (Textqualitäts-Gate + OCR-Fallback) adressiert die Pipeline-/
Chunker-Seite. Dieser PR betrifft ausschließlich die Eval-Messseite
(eval_quality_v4.py, direkter PyMuPDF-Zugriff). Nicht angefasst:
eval_dashboard_server.py, orchestrator.py, agents/extractor.py,
tools/pdf_enrich.py, pipeline/pdf_chunker.py (parallele Arbeit).

Nachbesserung: Griechisch/Kyrillisch-False-Positive (Commit dad3827)

Adversariale Kontrolle bestätigte die Bump-Freiheit über 6 Vektoren, fand
aber einen realen False-Positive: ein statistik-lastiger Absatz mit
griechischer Koeffizienten-Notation (α, β, χ²) triggerte die Warnung
fälschlich (empirisch CidSuspectResult(U+03B1, ratio 1,93 %) bei
0,5 %-Schwelle). Ursache: Griechisch/Kyrillisch sind etablierte
Schriftblöcke, die in Statistik-/Linguistik-/Klassik-/Slawistik-Fachtexten
legitim und gehäuft vorkommen — CID-Korruptionsglyphen landen dagegen
empirisch in Latin Extended-B (U+0231/U+022C, #278), nicht dort.

Fix: _ESTABLISHED_SCRIPT_RANGES in eval_text_quality.py nimmt Greek and
Coptic (U+0370–U+03FF) und Kyrillisch (U+0400–U+04FF) von der
Verdachtszählung aus. Neue Tests (test_greek_statistics_paragraph_does_not_trigger,
test_cyrillic_paragraph_does_not_trigger) reproduzieren den False-Positive
als RED (vor dem Fix: AssertionError, echte CidSuspectResult für α bzw.
т), GREEN danach; Regressionstest test_jockisch_pattern_still_triggers_despite_script_exclusion
belegt, dass das eigentliche Jockisch-Muster (Latin Extended-B) weiterhin
erkannt wird.

docs/evaluation.md ergänzt: bekannte Grenze (exotische Schriften außerhalb
Griechisch/Kyrillisch/Latein könnten weiterhin fälschlich flaggen — reine
Diagnostik, kein Messeinfluss) und Alt-Cache-Hinweis (da kein
EVAL_VERSION-Bump erfolgt, feuert die Warnung für bereits unter 4.3
gecachte Evals erst bei Re-Eval/--fresh-run, bestehende Baselines bleiben
unmarkiert).

Suite nach Nachbesserung: 6195 passed, 3 skipped, 8 deselected (EXIT_CODE 0,
303,94 s). ruff check/ruff format --check weiterhin grün.

Nicht gemergt — zur Review.

Tilltime added 2 commits July 17, 2026 12:38
…nderung (#306)

PR #298 fixt exakt zwei Codepoints eines defekten Fonts im Jockisch-PDF
(U+0231/U+022C). Jedes andere PDF mit anderem CID-Glyph-Mapping erzeugt
dasselbe Fehlerbild (quellentreue Notes faelschlich als Halluzination)
und blieb bisher unentdeckt.

Neu: generative/eval_text_quality.py::detect_cid_suspect() prueft den per
PyMuPDF extrahierten Volltext auf ein verdaechtiges Nicht-ASCII-
Haeufigkeitsprofil (ein einzelner Codepoint ausserhalb ASCII/Latin-1/
Latin-Extended-A, abzueglich einer Whitelist gaengiger typografischer
Satzzeichen, >= 0,5% aller Zeichen). Verdrahtet additiv in
eval_quality_v4.py: neues Feld result["pdf_text_suspect_cid"] + Flag
"cid_font_suspect" in result["quality_flags"], stderr-Warnung beim
Oeffnen der PDF. Kein Rueckmapping, keine Label-/Raten-Aenderung -> kein
EVAL_VERSION-Bump.

test_audit_double_call.py-Snapshot um das neue additive Feld ergaenzt.
docs/evaluation.md um Abschnitt zur neuen Warnung erweitert.
…reek/Cyrillic-FP) (#306)

Adversarialer Fund: ein statistik-lastiger Absatz mit griechischer
Koeffizienten-Notation (α, β, χ²) triggerte die CID-Verdachts-Warnung
faelschlich (CidSuspectResult(U+03B1, ratio 1,93%) bei 0,5%-Schwelle).
CID-Korruptionsglyphen landen empirisch in Latin Extended-B (U+0231/U+022C,
#278), nicht in etablierten Nicht-Latein-Schriftbloecken -- Statistik-,
Linguistik-, Klassik- und Slawistik-Fachtexte nutzen Griechisch/Kyrillisch
dagegen legitim und gehaeuft.

generative/eval_text_quality.py: Greek and Coptic (U+0370-U+03FF) und
Kyrillisch (U+0400-U+04FF) neben der bestehenden Satzzeichen-Whitelist von
der Verdachtszaehlung ausgenommen (_ESTABLISHED_SCRIPT_RANGES).

Neue Tests: griechischer und kyrillischer Absatz -> kein Flag (RED vor dem
Fix reproduziert: beide Faelle schlugen mit realer CidSuspectResult fehl);
Regressionstest bestaetigt, dass das Jockisch-Muster (Latin Extended-B)
weiterhin triggert.

docs/evaluation.md: #306-Abschnitt um Schriftblock-Ausnahme, bekannte
Grenze (exotische Schriften ausserhalb Griechisch/Kyrillisch/Latein
koennten weiterhin flaggen) und Alt-Cache-Hinweis (Warnung feuert fuer
bereits gecachte Evals erst bei Re-Eval/--fresh-run) ergaenzt.
@TillQuandel
TillQuandel merged commit 1400e81 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.

[MITTEL] Generische CID-Font-/Korruptions-Erkennung auf Eval-Seite (Verallgemeinerung von #278)

2 participants