XmLLM / tests /unit /test_readiness_validator.py
Claude
Sprint 4: validation layer and document policy
e2ec8a2 unverified
Raw
History Blame Contribute Delete
6.79 kB
"""Tests for the readiness validator."""
from __future__ import annotations
from src.app.domain.models import (
AltoReadiness,
CanonicalDocument,
EvidenceType,
Geometry,
GeometryStatus,
Page,
PageXmlReadiness,
Provenance,
ReadinessLevel,
Source,
TextLine,
TextRegion,
Word,
)
from src.app.domain.models.status import InputType, MissingCapability
from src.app.validators.readiness_validator import (
compute_document_readiness,
compute_page_alto_readiness,
compute_page_pagexml_readiness,
)
def _prov() -> Provenance:
return Provenance(
provider="test", adapter="v1", source_ref="$",
evidence_type=EvidenceType.PROVIDER_NATIVE,
)
def _geo(status: GeometryStatus = GeometryStatus.EXACT) -> Geometry:
return Geometry(bbox=(10, 10, 100, 30), status=status)
def _complete_page() -> Page:
"""A fully complete page with all data."""
return Page(
id="p1", page_index=0, width=2480, height=3508,
alto_readiness=AltoReadiness(level=ReadinessLevel.FULL),
page_readiness=PageXmlReadiness(level=ReadinessLevel.FULL),
reading_order=["tb1"],
text_regions=[
TextRegion(
id="tb1", geometry=_geo(), provenance=_prov(), lang="fra",
lines=[
TextLine(
id="tl1", geometry=_geo(), provenance=_prov(),
words=[
Word(id="w1", text="Hello", geometry=_geo(),
provenance=_prov(), confidence=0.95),
],
),
],
),
],
)
class TestAltoReadiness:
def test_complete_page_is_full(self) -> None:
page = _complete_page()
r = compute_page_alto_readiness(page)
assert r.level == ReadinessLevel.FULL
assert r.missing == []
def test_missing_word_geometry_is_none(self) -> None:
page = Page(
id="p1", page_index=0, width=2480, height=3508,
text_regions=[
TextRegion(
id="tb1", geometry=_geo(), provenance=_prov(),
lines=[TextLine(
id="tl1", geometry=_geo(), provenance=_prov(),
words=[Word(
id="w1", text="Hello",
geometry=_geo(GeometryStatus.UNKNOWN),
provenance=_prov(),
)],
)],
),
],
)
r = compute_page_alto_readiness(page)
assert r.level == ReadinessLevel.NONE
assert MissingCapability.WORD_GEOMETRY in r.missing
def test_missing_confidence_is_partial(self) -> None:
page = Page(
id="p1", page_index=0, width=2480, height=3508,
reading_order=["tb1"],
text_regions=[
TextRegion(
id="tb1", geometry=_geo(), provenance=_prov(),
lines=[TextLine(
id="tl1", geometry=_geo(), provenance=_prov(),
words=[Word(
id="w1", text="Hello", geometry=_geo(),
provenance=_prov(), confidence=None,
)],
)],
),
],
)
r = compute_page_alto_readiness(page)
assert r.level == ReadinessLevel.PARTIAL
assert MissingCapability.CONFIDENCE in r.missing
def test_no_reading_order_is_partial(self) -> None:
page = Page(
id="p1", page_index=0, width=2480, height=3508,
reading_order=[],
text_regions=[
TextRegion(
id="tb1", geometry=_geo(), provenance=_prov(),
lines=[TextLine(
id="tl1", geometry=_geo(), provenance=_prov(),
words=[Word(
id="w1", text="Hello", geometry=_geo(),
provenance=_prov(), confidence=0.9,
)],
)],
),
],
)
r = compute_page_alto_readiness(page)
assert r.level == ReadinessLevel.PARTIAL
assert MissingCapability.READING_ORDER in r.missing
def test_empty_page_is_none(self) -> None:
page = Page(id="p1", page_index=0, width=2480, height=3508)
r = compute_page_alto_readiness(page)
assert r.level == ReadinessLevel.NONE
class TestPageXmlReadiness:
def test_complete_page_is_full(self) -> None:
page = _complete_page()
r = compute_page_pagexml_readiness(page)
assert r.level == ReadinessLevel.FULL
def test_no_regions_is_none(self) -> None:
page = Page(id="p1", page_index=0, width=2480, height=3508)
r = compute_page_pagexml_readiness(page)
assert r.level == ReadinessLevel.NONE
def test_regions_without_word_geo_still_ok(self) -> None:
"""PAGE XML is more lenient — word geometry is not critical."""
page = Page(
id="p1", page_index=0, width=2480, height=3508,
reading_order=["tb1"],
text_regions=[
TextRegion(
id="tb1", geometry=_geo(), provenance=_prov(),
lines=[TextLine(
id="tl1", geometry=_geo(), provenance=_prov(),
words=[Word(
id="w1", text="Hello",
geometry=_geo(GeometryStatus.UNKNOWN),
provenance=_prov(),
)],
)],
),
],
)
r = compute_page_pagexml_readiness(page)
# PAGE doesn't require word geometry — should still be achievable
assert r.level in (ReadinessLevel.FULL, ReadinessLevel.PARTIAL)
class TestDocumentReadiness:
def test_single_full_page(self) -> None:
doc = CanonicalDocument(
document_id="test",
source=Source(input_type=InputType.IMAGE),
pages=[_complete_page()],
)
dr = compute_document_readiness(doc)
assert dr.level == ReadinessLevel.FULL
def test_mixed_pages(self) -> None:
full_page = _complete_page()
empty_page = Page(id="p2", page_index=1, width=2480, height=3508)
doc = CanonicalDocument(
document_id="test",
source=Source(input_type=InputType.IMAGE),
pages=[full_page, empty_page],
)
dr = compute_document_readiness(doc)
assert dr.level == ReadinessLevel.DEGRADED
assert len(dr.page_readiness) == 2