Files
Stirling-PDF/engine/tests/ledger/test_models.py
ConnorYoh de8c483054 Feat/math validation agent (#6012)
Co-authored-by: James Brunton <jbrunton96@gmail.com>
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
2026-04-17 10:36:45 +01:00

145 lines
4.0 KiB
Python

"""
Ledger models — unit tests for serialisation and business logic.
These tests confirm the wire contract: models round-trip through JSON
correctly and their helper properties behave as documented.
"""
import pytest
from pydantic import ValidationError
from stirling.contracts.ledger import (
Discrepancy,
DiscrepancyKind,
Evidence,
Folio,
FolioManifest,
FolioType,
Requisition,
Severity,
Verdict,
)
# ---------------------------------------------------------------------------
# FolioManifest
# ---------------------------------------------------------------------------
def test_folio_manifest_round_trip() -> None:
manifest = FolioManifest(
session_id="abc-123",
page_count=3,
folio_types=[FolioType.TEXT, FolioType.IMAGE, FolioType.MIXED],
)
reloaded = FolioManifest.model_validate_json(manifest.model_dump_json())
assert reloaded == manifest
def test_folio_manifest_round_bounds() -> None:
with pytest.raises(ValidationError):
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=0)
with pytest.raises(ValidationError):
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=4)
# ---------------------------------------------------------------------------
# Requisition
# ---------------------------------------------------------------------------
def test_requisition_empty() -> None:
req = Requisition(rationale="nothing needed")
assert req.need_text == []
assert req.need_tables == []
assert req.need_ocr == []
def test_requisition_type_discriminator() -> None:
req = Requisition(need_text=[0, 1], rationale="needs text")
assert req.type == "requisition"
# ---------------------------------------------------------------------------
# Folio.readable_text
# ---------------------------------------------------------------------------
def test_folio_readable_text_prefers_ocr() -> None:
folio = Folio(page=0, text="digital text", ocr_text="ocr text")
assert folio.readable_text == "ocr text"
def test_folio_readable_text_falls_back_to_text() -> None:
folio = Folio(page=0, text="digital text")
assert folio.readable_text == "digital text"
def test_folio_readable_text_empty_when_none() -> None:
folio = Folio(page=0)
assert folio.readable_text == ""
# ---------------------------------------------------------------------------
# Verdict
# ---------------------------------------------------------------------------
def test_verdict_clean_flag() -> None:
verdict = Verdict(
session_id="s1",
discrepancies=[],
pages_examined=[0, 1],
rounds_taken=2,
summary="All figures balance.",
clean=True,
)
assert verdict.error_count == 0
assert verdict.warning_count == 0
assert verdict.clean is True
def test_verdict_error_and_warning_counts() -> None:
discrepancies = [
Discrepancy(
page=0,
kind=DiscrepancyKind.TALLY,
severity=Severity.ERROR,
description="bad sum",
stated="100",
expected="110",
),
Discrepancy(
page=1,
kind=DiscrepancyKind.CONSISTENCY,
severity=Severity.WARNING,
description="mismatched figure",
stated="500",
expected="550",
),
]
verdict = Verdict(
session_id="s1",
discrepancies=discrepancies,
pages_examined=[0, 1],
rounds_taken=1,
summary="Issues found.",
clean=False,
)
assert verdict.error_count == 1
assert verdict.warning_count == 1
# ---------------------------------------------------------------------------
# Evidence.final_round
# ---------------------------------------------------------------------------
def test_evidence_final_round() -> None:
evidence = Evidence(
session_id="s",
folios=[Folio(page=0, text="hello")],
round=3,
final_round=True,
)
assert evidence.final_round is True