fix(document-processors): escalate glyph-corrupt PDFs to the structured tier

The fast (pypdfium2) extractor can leak raw glyph codes on subset fonts with a
broken /ToUnicode CMap. The result scores high on the existing text-quality
heuristic -- a uniform glyph/Caesar offset preserves whitespace and token
lengths -- yet is unsearchable. The structured (pymupdf) tier extracts the same
pages correctly.

Add a language-agnostic C0-control-character-ratio signal to the tier-0
classifier that detects this corruption and routes the document to a new
`structured` recommended_tier. Wire the fast->structured hop on the inline path
and generalise it so a low-quality-but-non-empty layer also tries structured
before OCR -- the inline and external ingest modes now follow the full
fast->structured->ocr ladder identically. A scanned / no-text-layer document
(total_chars == 0) still shortcuts straight to OCR, since a text extractor
cannot recover a pure raster.

New per-tenant tunable DOCUMENT_GLYPH_CORRUPTION_RATIO (default 0.02); escalation
metrics gain a `corrupt_glyphs` reason label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-16 20:06:35 +02:00
co-authored by Claude Opus 4.8
parent 60df141442
commit cf7209cd85
8 changed files with 412 additions and 54 deletions
+24
View File
@@ -266,6 +266,30 @@ class TestChunkConfigValidation:
_reload_config()
assert get_settings().document_max_pdf_size_mb == pytest.approx(12.5)
def test_glyph_corruption_ratio_default_and_env_override(self):
"""document_glyph_corruption_ratio defaults to 0.02 and reads its env var.
Guards the _DEFAULTS-key-must-match-env-var footgun.
"""
assert Settings().document_glyph_corruption_ratio == pytest.approx(0.02)
with patch.dict(
os.environ, {"DOCUMENT_GLYPH_CORRUPTION_RATIO": "0.05"}, clear=True
):
_reload_config()
assert get_settings().document_glyph_corruption_ratio == pytest.approx(0.05)
@patch.dict(
os.environ,
{"DOCUMENT_GLYPH_CORRUPTION_RATIO": "1.5"},
clear=True,
)
def test_glyph_corruption_ratio_out_of_range_raises_error(self):
"""The ratio must be within [0, 1]."""
from dynaconf import ValidationError
with pytest.raises(ValidationError, match="DOCUMENT_GLYPH_CORRUPTION_RATIO"):
_reload_config()
def test_valid_chunk_settings(self):
"""Test valid chunk size and overlap configuration."""
settings = Settings(