Source code for aeat.domain.calculations.registry._schema_extraction
"""Extraction-profile schema contracts for registry revisions.
These :class:`~domain.calculations.registry._schema_base.RegistryModel`
definitions describe how a registry revision maps declaration PDFs, submitted
files, justificantes, and official workbooks into target casillas. The contract
records parser identity, accepted artefact kind, match strategy, confidence,
coverage, legal grounding, source grounding, and the explicit specimen /
round-trip flags that prevent silent provisional extraction.
The schema remains declarative authority only: it does not import parser
adapters, read artefacts, or run extraction. Registry validation checks local
reference closure and evidence sufficiency before
:class:`~domain.calculations.registry.RegistrySnapshot` exposes the profiles to
application and adapter consumers.
See Also:
:class:`~domain.calculations.registry.ModeloRevision`
Revision record that owns committed extraction-profile rows.
:class:`~domain.calculations.registry.RegistrySnapshot`
Validated filing-context view that exposes extraction profiles by id.
:func:`~domain.calculations.registry._validate_record_sections.validate_extraction_profile_section`
Record-section validator that checks casilla, export-field, legal, and
source closure for each profile.
:mod:`~domain.calculations.registry._validate_extraction_profiles`
Artefact-kind, dotted-parser, bbox-anchor, specimen, and round-trip
validator helpers for these contracts.
:class:`~domain.calculations.registry.CasillaDefinition`
Target casilla metadata each extraction target must reference.
:class:`~domain.calculations.registry.LegalReference`
Legal evidence rows cited through ``legal_refs``.
:class:`~domain.calculations.registry.SourceReference`
Source evidence rows cited through ``source_refs``.
"""
from __future__ import annotations
from decimal import Decimal
from typing import Literal
from pydantic import Field, field_validator, model_validator
from ._errors import RegistryValidationError
from ._ids import CasillaId, ExtractionProfileId
from ._schema_base import LegalRefs, RegistryModel, SourceRefs
from ._schema_scalars import DecimalValue
[docs]
class BboxAnchorSpec(RegistryModel):
r"""Spatial anchor configuration for the ``bbox_anchored`` extraction strategy."""
box_number_pattern: str
value_offset: Literal["left_of_number", "above_number", "right_of_number"]
anchor_x_min: float | None = None
anchor_x_max: float | None = None
value_x_max: float | None = None
column_anchor: str | None = None
[docs]
class ExtractionTargetDefinition(RegistryModel):
"""Per-target descriptor for a registry extraction profile."""
casilla_id: CasillaId
match_strategy: Literal["numeric_casilla", "named_label", "bbox_anchored"]
value_kind: Literal["amount", "text", "enum"]
label_pattern: str | None = None
bbox_anchor: BboxAnchorSpec | None = None
@model_validator(mode="after")
def _field_strategy_consistency(self) -> ExtractionTargetDefinition:
if self.match_strategy == "named_label" and not self.label_pattern:
raise RegistryValidationError("named_label extraction targets require label_pattern")
if self.match_strategy == "numeric_casilla" and self.label_pattern is not None:
raise RegistryValidationError("numeric_casilla extraction targets must not define label_pattern")
if self.match_strategy == "bbox_anchored" and self.bbox_anchor is None:
raise RegistryValidationError("bbox_anchored extraction targets require bbox_anchor")
if self.match_strategy != "bbox_anchored" and self.bbox_anchor is not None:
raise RegistryValidationError("bbox_anchor must be None for non-bbox_anchored strategies")
return self
[docs]
class ExtractionProfileDefinition(RegistryModel):
"""Registry extraction profile for declaration/borrador/workbook artefacts."""
id: ExtractionProfileId
surface: Literal["borrador_pdf", "declaracion_pdf", "justificante_pdf", "export_record", "official_workbook"]
artefact_kind: str
accepted_artefact_kinds: tuple[
Literal["submitted_file", "declaration_pdf", "justificante_pdf", "official_workbook"],
...,
] = Field(min_length=1)
parser: str
target_casillas: tuple[ExtractionTargetDefinition, ...] = Field(min_length=1)
confidence: Literal["strict", "review_required"]
provisional_pending_specimen: bool = False
corpus_round_trip_verified: bool = False
verification_source: (
Literal[
"real_aeat_corpus_pdf",
"synthetic_from_aeat_published_text",
"historical_suppression",
"not_applicable",
]
| None
) = None
min_coverage: DecimalValue = Field(ge=Decimal("0"), le=Decimal("1"))
failure_semantics: Literal["fail_hard"]
legal_refs: LegalRefs
source_refs: SourceRefs
@field_validator("accepted_artefact_kinds")
@classmethod
def _accepted_artefact_kinds_unique(cls, value: tuple[str, ...]) -> tuple[str, ...]:
if len(set(value)) != len(value):
raise RegistryValidationError("extraction profile accepted_artefact_kinds entries must be unique")
return value
@field_validator("target_casillas")
@classmethod
def _target_casillas_unique(
cls,
value: tuple[ExtractionTargetDefinition, ...],
) -> tuple[ExtractionTargetDefinition, ...]:
casilla_ids = [target.casilla_id for target in value]
if len(set(casilla_ids)) != len(casilla_ids):
raise RegistryValidationError("extraction profile target_casillas casilla_id entries must be unique")
return value
__all__ = ["BboxAnchorSpec", "ExtractionProfileDefinition", "ExtractionTargetDefinition"]