Spaces:
Sleeping
Sleeping
| """Tests for the word_box_json adapter (PaddleOCR format).""" | |
| from __future__ import annotations | |
| import json | |
| from typing import TYPE_CHECKING | |
| import pytest | |
| from src.app.domain.models import ( | |
| EvidenceType, | |
| GeometryStatus, | |
| RawProviderPayload, | |
| ) | |
| from src.app.domain.models.geometry import GeometryContext | |
| from src.app.providers.adapters.word_box_json import WordBoxJsonAdapter | |
| if TYPE_CHECKING: | |
| from pathlib import Path | |
| def adapter() -> WordBoxJsonAdapter: | |
| return WordBoxJsonAdapter() | |
| def paddle_payload(fixtures_dir: Path) -> list: | |
| with open(fixtures_dir / "paddle_ocr_sample.json") as f: | |
| return json.load(f) | |
| def raw(paddle_payload: list) -> RawProviderPayload: | |
| return RawProviderPayload( | |
| provider_id="paddleocr", | |
| adapter_id="adapter.word_box_json.v1", | |
| runtime_type="local", | |
| payload=paddle_payload, | |
| image_width=2480, | |
| image_height=3508, | |
| ) | |
| def geo_ctx() -> GeometryContext: | |
| return GeometryContext(source_width=2480, source_height=3508) | |
| class TestWordBoxJsonAdapter: | |
| def test_family(self, adapter: WordBoxJsonAdapter) -> None: | |
| assert adapter.family == "word_box_json" | |
| def test_version(self, adapter: WordBoxJsonAdapter) -> None: | |
| assert "v1" in adapter.version | |
| def test_normalize_produces_valid_document( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize( | |
| raw, geo_ctx, document_id="test_doc", source_filename="test.png" | |
| ) | |
| assert doc.document_id == "test_doc" | |
| assert doc.source.filename == "test.png" | |
| assert len(doc.pages) == 1 | |
| def test_page_dimensions( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| page = doc.pages[0] | |
| assert page.width == 2480 | |
| assert page.height == 3508 | |
| def test_correct_word_count( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| page = doc.pages[0] | |
| # 5 items in fixture → 5 lines, each with 1 word | |
| total_words = sum( | |
| len(line.words) | |
| for region in page.text_regions | |
| for line in region.lines | |
| ) | |
| assert total_words == 5 | |
| def test_word_text_preserved( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| words = [ | |
| w.text | |
| for r in doc.pages[0].text_regions | |
| for ln in r.lines | |
| for w in ln.words | |
| ] | |
| assert words[0] == "Bonjour" | |
| assert words[1] == "le" | |
| assert words[2] == "monde" | |
| def test_confidence_preserved( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| first_word = doc.pages[0].text_regions[0].lines[0].words[0] | |
| assert first_word.confidence == pytest.approx(0.96) | |
| def test_geometry_is_exact( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| first_word = doc.pages[0].text_regions[0].lines[0].words[0] | |
| assert first_word.geometry.status == GeometryStatus.EXACT | |
| def test_polygon_preserved( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| first_word = doc.pages[0].text_regions[0].lines[0].words[0] | |
| assert first_word.geometry.polygon is not None | |
| assert len(first_word.geometry.polygon) == 4 | |
| def test_bbox_correct( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| first_word = doc.pages[0].text_regions[0].lines[0].words[0] | |
| # From fixture: [[100,200],[300,200],[300,240],[100,240]] | |
| # → bbox = (100, 200, 200, 40) | |
| x, y, w, h = first_word.geometry.bbox | |
| assert x == pytest.approx(100) | |
| assert y == pytest.approx(200) | |
| assert w == pytest.approx(200) | |
| assert h == pytest.approx(40) | |
| def test_provenance_native( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| first_word = doc.pages[0].text_regions[0].lines[0].words[0] | |
| assert first_word.provenance.evidence_type == EvidenceType.PROVIDER_NATIVE | |
| assert first_word.provenance.provider == "paddleocr" | |
| assert "$[0]" in first_word.provenance.source_ref | |
| def test_block_is_inferred( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| region = doc.pages[0].text_regions[0] | |
| assert region.geometry.status == GeometryStatus.INFERRED | |
| assert region.provenance.evidence_type == EvidenceType.DERIVED | |
| def test_reading_order( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| assert doc.pages[0].reading_order == ["tb1"] | |
| def test_empty_payload_rejected( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| raw = RawProviderPayload( | |
| provider_id="test", | |
| adapter_id="v1", | |
| runtime_type="local", | |
| payload=[], | |
| ) | |
| with pytest.raises(ValueError, match="no items"): | |
| adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| def test_dict_payload_rejected( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| raw = RawProviderPayload( | |
| provider_id="test", | |
| adapter_id="v1", | |
| runtime_type="local", | |
| payload={"not": "a list"}, | |
| ) | |
| with pytest.raises(ValueError, match="list payload"): | |
| adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| def test_json_roundtrip( | |
| self, | |
| adapter: WordBoxJsonAdapter, | |
| raw: RawProviderPayload, | |
| geo_ctx: GeometryContext, | |
| ) -> None: | |
| doc = adapter.normalize(raw, geo_ctx, document_id="test_doc") | |
| data = doc.model_dump(mode="json") | |
| from src.app.domain.models import CanonicalDocument | |
| doc2 = CanonicalDocument.model_validate(data) | |
| assert doc2.document_id == doc.document_id | |