feat(ocr): PDF 文本层可用时跳过 worker 调用并补装 poppler-data
- OcrService 提取 PDF 文本层后若有效字符达到阈值,直接构建文档并写入结果缓存,不再触发 OCR worker,仅无文本层时才解析 python_bin/worker_path 调用 worker - _build_text_layer_document 复用 AggregatedOcrDocument 聚合文本层片段,_has_usable_pdf_text_layer 基于 meaningful_char_count 判定 - docker-compose 与 paddleocr bootstrap 脚本补装 poppler-data,保证 PDF 文本层抽取的中文编码正确 - 新增文本层直取与运行时依赖两项 ocr_service 单测
This commit is contained in:
@@ -8,6 +8,18 @@ from app.core.config import get_settings
|
||||
from app.services.ocr import OcrService
|
||||
|
||||
|
||||
def test_ocr_runtime_installers_include_poppler_cjk_data() -> None:
|
||||
repo_root = Path(__file__).resolve().parents[2]
|
||||
dependency_sources = [
|
||||
repo_root / "docker-compose.yml",
|
||||
repo_root / "server" / "scripts" / "bootstrap_paddleocr_mobile.sh",
|
||||
repo_root / "server" / "scripts" / "bootstrap_paddleocr_gpu.sh",
|
||||
]
|
||||
|
||||
for path in dependency_sources:
|
||||
assert "poppler-data" in path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_ocr_service_uses_worker_runtime_and_keeps_unsupported_files_as_warnings(
|
||||
monkeypatch,
|
||||
tmp_path: Path,
|
||||
@@ -220,6 +232,59 @@ def test_ocr_service_converts_pdf_to_images_and_returns_image_preview(
|
||||
assert recognized.lines[1].page_index == 1
|
||||
|
||||
|
||||
def test_ocr_service_uses_pdf_text_layer_without_worker_runtime(
|
||||
monkeypatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
def fake_convert_pdf_to_images(self, *, pdf_path: Path, output_dir: Path) -> list[Path]:
|
||||
page = output_dir / "page-1.png"
|
||||
page.write_bytes(b"fake-rendered-page")
|
||||
return [page]
|
||||
|
||||
def fail_resolve_python(self) -> str:
|
||||
raise AssertionError("PDF 文本层可用时不应强制解析 OCR worker。")
|
||||
|
||||
def fail_invoke_worker(self, **kwargs) -> dict:
|
||||
raise AssertionError("PDF 文本层可用时不应调用 OCR worker。")
|
||||
|
||||
monkeypatch.setenv("STORAGE_ROOT_DIR", str(tmp_path / "storage"))
|
||||
monkeypatch.setattr(OcrService, "_resolve_python_bin", fail_resolve_python)
|
||||
monkeypatch.setattr(OcrService, "_resolve_worker_path", lambda self: "worker.py")
|
||||
monkeypatch.setattr(OcrService, "_convert_pdf_to_images", fake_convert_pdf_to_images)
|
||||
monkeypatch.setattr(OcrService, "_invoke_worker", fail_invoke_worker)
|
||||
monkeypatch.setattr(
|
||||
OcrService,
|
||||
"_extract_pdf_text_layer",
|
||||
lambda self, pdf_path: (
|
||||
"电子发票(铁路电子客票)\n"
|
||||
"发票号码:26429165800002785705\n"
|
||||
"武汉站\n"
|
||||
"上海虹桥站\n"
|
||||
"G458\n"
|
||||
"票价:¥354.00\n"
|
||||
"电子客票号:6580061086021391007342026"
|
||||
),
|
||||
)
|
||||
get_settings.cache_clear()
|
||||
try:
|
||||
result = OcrService().recognize_files(
|
||||
[
|
||||
("train-ticket.pdf", b"%PDF-1.7 fake", "application/pdf"),
|
||||
]
|
||||
)
|
||||
finally:
|
||||
get_settings.cache_clear()
|
||||
|
||||
recognized = result.documents[0]
|
||||
assert result.success_count == 1
|
||||
assert recognized.document_type == "train_ticket"
|
||||
assert "电子发票(铁路电子客票)" in recognized.text
|
||||
assert "电子客票号:6580061086021391007342026" in recognized.text
|
||||
assert any(field.label == "金额" and field.value == "354元" for field in recognized.document_fields)
|
||||
assert recognized.preview_kind == "image"
|
||||
assert recognized.preview_data_url.startswith("data:image/png;base64,")
|
||||
|
||||
|
||||
def test_ocr_service_reuses_cached_document_for_same_content(
|
||||
monkeypatch,
|
||||
tmp_path: Path,
|
||||
@@ -351,5 +416,5 @@ def test_ocr_service_prefers_pdf_text_layer_when_rendered_ocr_is_placeholder_hea
|
||||
assert "上海虹桥站" in recognized.text
|
||||
assert "□□□□" not in recognized.summary
|
||||
assert recognized.document_type == "train_ticket"
|
||||
assert recognized.preview_kind == ""
|
||||
assert recognized.preview_data_url == ""
|
||||
assert recognized.preview_kind == "image"
|
||||
assert recognized.preview_data_url.startswith("data:image/png;base64,")
|
||||
|
||||
Reference in New Issue
Block a user