"""Export eligibility validator — decides what can be exported. Consumes readiness assessments and document policy to produce an ExportEligibility decision for the whole document. """ from __future__ import annotations from typing import TYPE_CHECKING from src.app.domain.models.readiness import ExportEligibility from src.app.domain.models.status import ReadinessLevel from src.app.policies.document_policy import DocumentPolicy from src.app.validators.readiness_validator import ( compute_page_alto_readiness, compute_page_pagexml_readiness, ) if TYPE_CHECKING: from src.app.domain.models import CanonicalDocument def compute_export_eligibility( doc: CanonicalDocument, policy: DocumentPolicy | None = None, ) -> ExportEligibility: """Compute export eligibility for a document. Args: doc: The canonical document. policy: Document policy (uses default if None). Returns: ExportEligibility with per-format readiness levels. """ if policy is None: policy = DocumentPolicy() alto_levels: list[ReadinessLevel] = [] page_levels: list[ReadinessLevel] = [] for page in doc.pages: alto_levels.append(compute_page_alto_readiness(page).level) page_levels.append(compute_page_pagexml_readiness(page).level) alto_export = _aggregate_levels(alto_levels) page_export = _aggregate_levels(page_levels) # Apply policy constraints if policy.strict_mode: # In strict mode, partial is downgraded to none if alto_export == ReadinessLevel.PARTIAL: alto_export = ReadinessLevel.NONE if page_export == ReadinessLevel.PARTIAL: page_export = ReadinessLevel.NONE # Viewer is more lenient — it can render degraded content if alto_export != ReadinessLevel.NONE or page_export != ReadinessLevel.NONE: viewer_render = ReadinessLevel.FULL elif any(len(p.text_regions) > 0 for p in doc.pages): viewer_render = ReadinessLevel.DEGRADED else: viewer_render = ReadinessLevel.NONE return ExportEligibility( alto_export=alto_export, page_export=page_export, viewer_render=viewer_render, ) def _aggregate_levels(levels: list[ReadinessLevel]) -> ReadinessLevel: """Aggregate per-page readiness into a single document-level readiness.""" if not levels: return ReadinessLevel.NONE if all(lv == ReadinessLevel.FULL for lv in levels): return ReadinessLevel.FULL if all(lv == ReadinessLevel.NONE for lv in levels): return ReadinessLevel.NONE if any(lv in (ReadinessLevel.FULL, ReadinessLevel.PARTIAL) for lv in levels): return ReadinessLevel.PARTIAL return ReadinessLevel.DEGRADED