fix(backend): detect corrupted PDF text layer by control character density

This commit is contained in:
omp committed 2026-11-01 19:30:07 -03:00
1 parent b25bf8ff7d
commit 8f79993646
2 files changed
+109 -2

No files matched your search

+74 -1
View File
@@ -13,7 +13,12 @@ import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from app.extraction import OcrError, PdfExtractor, legible_ratio # noqa: E402
from app.extraction import ( # noqa: E402
OcrError,
PdfExtractor,
control_char_ratio,
legible_ratio,
)
class FakePage:
@@ -95,6 +100,40 @@ def test_legible_ratio_is_low_for_control_characters():
assert legible_ratio(text) < 0.35
def _corrupted_index_layer() -> str:
"""Reproduz a camada textual corrompida da página 3 do índice real.
Medido no PDF: 442 caracteres não-branco, sendo 276 controles C0 (62%) e
166 legíveis (38%) — pontuação e letras soltas. A letra é o que empurra a
razão de legibilidade para 0.376, acima do limiar de 0.35.
"""
controls = "".join(chr(index) for index in range(1, 32)) * 9 # 279 controles
legible = "!\"#$%&'()*+,-./0123:;<=>?Gênesis:6" * 8 # letras e pontuação
return f"ÍNDICE\n{controls}{legible}\n"
def test_control_char_ratio_is_high_for_corrupted_layer():
"""A camada corrompida do PDF real mistura letras avulsas e controles.
A razão de caracteres legíveis não basta para detectar o problema: no
índice do PDF, letras soltas entre os controles elevam o placar de
legibilidade acima do limiar.
"""
corrupted = _corrupted_index_layer()
assert legible_ratio(corrupted) > 0.35, "premissa do teste: legível alto"
assert control_char_ratio(corrupted) > 0.01
def test_control_char_ratio_is_low_for_clean_text():
text = "GÊNESIS\n[1]\n1 No princípio criou Deus os céus e a terra."
assert control_char_ratio(text) < 0.01
def test_control_char_ratio_of_empty_text_is_zero():
assert control_char_ratio("") == 0.0
assert control_char_ratio(" \n\t ") == 0.0
def test_legible_ratio_of_empty_text_is_zero():
assert legible_ratio("") == 0.0
assert legible_ratio(" \n\t ") == 0.0
@@ -140,6 +179,40 @@ def test_corrupted_text_layer_triggers_ocr(tmp_cache, tmp_path, monkeypatch):
assert pages[0].text.startswith("1 No princípio")
def test_partially_legible_layer_with_controls_still_triggers_ocr(
tmp_cache, tmp_path, monkeypatch
):
"""Regressão do índice do PDF real.
A página do índice tem "ÍNDICE" legível e o resto corrompido. A razão de
legibilidade fica acima de 0.35, mas os caracteres de controle revelam a
corrupção — sem esta regra a página seria indexada com lixo.
"""
corrupted = _corrupted_index_layer()
assert legible_ratio(corrupted) > 0.35
page = FakePage(text=corrupted, pixmap=FakePixmap())
extractor = PdfExtractor(
cache_dir=tmp_cache,
pymupdf_module=FakePyMuPDF(FakeDocument([page])),
)
calls: list[int] = []
def fake_tesseract(pixmap, language, timeout):
calls.append(pixmap.width)
return "ÍNDICE\nGênesis: 6"
monkeypatch.setattr(extractor, "_run_tesseract", fake_tesseract)
pdf = tmp_path / "biblia.pdf"
pdf.write_bytes(b"%PDF-1.3")
pages = list(extractor.extract_document(pdf))
assert calls == [10], "OCR deveria ter sido chamado apesar da legibilidade alta"
assert "Gênesis" in pages[0].text
def test_ocr_result_is_cached_and_reused(tmp_cache, tmp_path, monkeypatch):
corrupted = "".join(chr(i) for i in range(1, 32))
page = FakePage(text=corrupted, pixmap=FakePixmap())