Source code for cadrumo.domain.calculations.registry.schema_base

"""Shared base classes and aliases for registry schema models.

The ``SensitivityClassField`` alias coerces registry ``output_sensitivity``
tokens into :class:`SensitivityClass` members before strict schema validation.
``RevisionReviewStatusField`` does the same for the per-revision governance
stamp's ``review_status`` token, and ``RegistryAuthorityGradeField`` for the
per-revision ``authority_grade`` token.
"""

from __future__ import annotations

from collections.abc import Callable, Iterable
from dataclasses import dataclass
from decimal import Decimal
from enum import StrEnum
from types import UnionType
from typing import Annotated, Final, Literal, TypeGuard, Union, get_args, get_origin

from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, field_validator

from ....core.authority_grade import RegistryAuthorityGrade
from ....core.classification.policies import SensitivityClass
from ....core.errors.hierarchy import pydantic_validation_boundary
from ....core.models import STRICT_FROZEN_CONFIG
from ....core.period import Period, is_administrative_period_token, is_symbolic_event_selector
from ....core.revision_review import RevisionReviewStatus
from .errors import RegistryValidationError
from .ids import LegalRefId, SourceRefId

__all__ = [
    "CHAIN_FAMILY",
    "GOVERNANCE_STAMP",
    "MANIFEST_ONLY",
    "NUMERIC_CASILLA_DATA_TYPES",
    "REGISTRY_SOURCE_GROUNDING_TIERS",
    "SCHEMA_FAMILY",
    "ZERO_PADDED_EXPORT_DATA_TYPES",
    "CalculationClass",
    "CalculationClassField",
    "CasillaSignConstraint",
    "CasillaSignConstraintField",
    "CasillaSignConstraintValue",
    "ChainFamilyMarker",
    "DateAxis",
    "DateAxisField",
    "EvidenceTier",
    "FormulaOperator",
    "GovernanceStampMarker",
    "LegalRefs",
    "ManifestOnlyMarker",
    "ModeloFilingCapabilities",
    "ModeloFilingCapability",
    "RegistryAuthorityGradeField",
    "RegistryModel",
    "RevisionReviewStatusField",
    "SchemaFamilyMarker",
    "SensitivityClassField",
    "SourceCitation",
    "SourceCitationText",
    "SourceRefs",
    "chain_family_fields",
    "coerce_decimal_tuple",
    "coerce_enum_member",
    "coerce_enum_tuple",
    "collection_shaped_fields",
    "filing_period_from_scope",
    "governance_stamp_fields",
    "is_schema_model_element",
    "manifest_only_fields",
    "schema_family_fields",
    "sorted_unique_capabilities",
]


@pydantic_validation_boundary
def _coerce_sensitivity_class(value: object) -> object:
    if isinstance(value, SensitivityClass):
        return value
    if isinstance(value, str):
        return SensitivityClass(value)
    return value


SensitivityClassField = Annotated[SensitivityClass, BeforeValidator(_coerce_sensitivity_class)]


@pydantic_validation_boundary
def _coerce_revision_review_status(value: object) -> object:
    if isinstance(value, RevisionReviewStatus):
        return value
    if isinstance(value, str):
        return RevisionReviewStatus(value)
    return value


RevisionReviewStatusField = Annotated[RevisionReviewStatus, BeforeValidator(_coerce_revision_review_status)]
"""Registry ``review_status`` token coerced into a :class:`RevisionReviewStatus` member.

Registry schema models validate under ``strict=True``, which refuses a bare TOML
string for an enum-typed field, so the governance stamp needs this coercion hop
exactly as ``output_sensitivity`` does. An unknown token raises out of the enum
constructor and surfaces as a registry load failure naming the offending value.

Shared with the legal catalogue rows, which reach the same vocabulary.
"""


@pydantic_validation_boundary
def _coerce_registry_authority_grade(value: object) -> object:
    if isinstance(value, RegistryAuthorityGrade):
        return value
    if isinstance(value, str):
        return RegistryAuthorityGrade(value)
    return value


RegistryAuthorityGradeField = Annotated[RegistryAuthorityGrade, BeforeValidator(_coerce_registry_authority_grade)]
"""Registry ``authority_grade`` token coerced into a :class:`RegistryAuthorityGrade` member.

The same coercion hop :data:`RevisionReviewStatusField` needs, for the same
reason: registry schema models validate under ``strict=True``, which refuses a
bare TOML string for an enum-typed field. Registry TOML therefore stays
free-form and the loader boundary hydrates the typed member, so an unknown grade
surfaces as a registry load failure naming the offending value rather than
reaching a downstream branch on a string.

Distinct subject from :data:`RevisionReviewStatusField`: that vocabulary records
who signed a revision off, this one records how far the revision's authority
reaches.
"""


#: A parsed TOML tuple/list, typed at the boundary rather than left with the
#: ``Unknown`` element type an ``isinstance(value, (tuple, list))`` narrow of a
#: bare ``object`` carries -- both coercion hops below iterate a validated,
#: fully-typed ``list[object]`` through this one adapter.
_OBJECT_LIST_ADAPTER: TypeAdapter[list[object]] = TypeAdapter(list[object])


[docs] def coerce_enum_member(enum_cls: type) -> Callable[[object], object]: """Return a scalar coercion hop for one enum-typed field. The generated function is the identical shape as :func:`_coerce_sensitivity_class` and its siblings above -- registry schema models validate under ``strict=True``, which refuses a bare TOML string for an enum-typed field, so the loader boundary hydrates the typed member here. An unknown token still raises out of the enum constructor and surfaces as a registry load failure naming the offending value. Factored out because the ledger binding-selector families (IVA, OSS/IOSS, retenciones) need this same coercion for several enum types each; hand- duplicating the four-line function per type is the drift risk this project's architecture rules warn against, not the discipline they ask for. """ def _coerce(value: object) -> object: if isinstance(value, enum_cls): return value if isinstance(value, str): return enum_cls(value) return value return _coerce
[docs] def coerce_enum_tuple(enum_cls: type) -> Callable[[object], object]: """Return a ``tuple[enum_cls, ...]`` coercion hop, element-wise. Same rationale and mechanism as :func:`coerce_enum_member`, applied to each element of an enum-typed tuple field rather than to a scalar field: strict mode refuses a bare string for an enum-typed tuple ELEMENT exactly as it refuses one for a scalar enum field, so each element is coerced independently and an unknown token still raises out of the enum constructor unchanged. """ def _coerce(value: object) -> object: if not isinstance(value, (tuple, list)): return value items = _OBJECT_LIST_ADAPTER.validate_python(value) return tuple( item if isinstance(item, enum_cls) else enum_cls(item) if isinstance(item, str) else item for item in items ) return _coerce
[docs] def coerce_decimal_tuple(value: object) -> object: """Coerce a ``tuple[Decimal, ...]`` field's elements from TOML string/int tokens. TOML has no native ``Decimal`` type; committed registry TOML stores a rate tuple as quoted decimal strings (e.g. ``["0.21"]``) to avoid float imprecision. Strict mode refuses that bare string for a ``Decimal``-typed tuple element exactly as it refuses one for any other strict-typed element, so this is the same coercion-hop mechanism as :func:`coerce_enum_tuple`, targeting ``Decimal`` instead of an enum. An unparseable token still raises out of the ``Decimal`` constructor and surfaces as a registry load failure. """ if not isinstance(value, (tuple, list)): return value items = _OBJECT_LIST_ADAPTER.validate_python(value) return tuple( item if isinstance(item, Decimal) else Decimal(item) if isinstance(item, (str, int)) else item for item in items )
[docs] @dataclass(frozen=True, slots=True) class ManifestOnlyMarker: """``Annotated`` metadata pinning one field to the revision's own manifest. A revision compiles from its ``revision.toml`` manifest plus up to several hundred per-section fragment files, and the merge takes a scalar from whichever file declares it. A field marked here is refused inside a section fragment: it is a statement about the WHOLE revision, so it has to be readable in the one place a reviewer opens, not merged in from a file thousands deep in a fragment tree where the manifest still reads silent on it. Manifest-only-ness is not derivable from a field's annotation - the marked fields are a free-text attribution, a closed enum, a date and two id tuples, shapes a dozen unmarked fields on the same model share - so the enrolment has to be declared. Declaring it *at the field* rather than in a hand-kept list is the point: the list can be forgotten when a field is added, and it is the sole input to the loader's placement refusal, so a forgotten entry silently reopens the laundering route the refusal exists to close. """
MANIFEST_ONLY = ManifestOnlyMarker() """The singleton marker attached to every manifest-only field declaration."""
[docs] @dataclass(frozen=True, slots=True) class GovernanceStampMarker(ManifestOnlyMarker): """``Annotated`` metadata enrolling one field into the governance stamp. Narrower than :class:`ManifestOnlyMarker`, and a subclass of it rather than a sibling. Governance provenance and legal grounding are different subjects - one is a claim about who built and signed off a revision, the other is the law the revision stands on - and only the governance fields belong to the stamp vocabulary the conformance tooling reads and writes. What they share is the placement guarantee, and expressing that as a type relation rather than a second marker on the same fields is what keeps it: a governance field added tomorrow cannot be manifest-pinned by memory, because it already is one by construction. """
GOVERNANCE_STAMP = GovernanceStampMarker() """The singleton marker attached to every governance-stamp field declaration."""
[docs] @dataclass(frozen=True, slots=True) class SchemaFamilyMarker: """``Annotated`` metadata enrolling one field as a revision schema family. A family is one of the revision's declared content collections - its casillas, its formulas, its bindings, its export layouts. Coverage asks one question of each: is it populated, is it honestly not applicable, or is it empty for a reason nobody has recorded. The marker is what makes a field subject to that question. Unrelated to :class:`ManifestOnlyMarker`, and deliberately not a subclass of it. Placement asks WHERE a field may be written; enrolment asks WHETHER a field's emptiness is a coverage claim. The two sets barely intersect: the manifest-only fields are scalars, and the families are collections. A marker hierarchy between them would assert a relationship that does not exist. Family-ness IS derivable from the annotation - a family is exactly a ``tuple`` of a schema model - which is why the marker carries a completeness check rather than standing alone: :func:`schema_family_fields` reads the declarations, the check compares that against the shape-derived set, and a collection added without the marker reds. Marking alone would catch a rename and never an addition; deriving alone would enrol collections nobody meant as families. Requiring both is what makes the enrolment exhaustive AND deliberate. """
SCHEMA_FAMILY = SchemaFamilyMarker() """The singleton marker attached to every revision schema-family declaration."""
[docs] class ChainFamilyMarker: """``Annotated`` metadata enrolling one field as a chain-statement family. A chain family holds authored statements about an identifier-keyed family's inheritance chain across editions - retirements and replacements - rather than content of the revision itself. Its natural state is empty: an edition that withdraws nothing declares nothing. Emptiness is therefore resolved by construction and is never a coverage claim, which is why this marker is distinct from :class:`SchemaFamilyMarker` and why coverage ignores it. The loader still discovers the section by shape, and the conformance check accepts either marker as deliberate enrolment. """
CHAIN_FAMILY = ChainFamilyMarker() """The singleton marker attached to every chain-statement family declaration."""
[docs] def chain_family_fields(model: type[BaseModel]) -> frozenset[str]: """Return the names of ``model``'s fields marked :data:`CHAIN_FAMILY`.""" return frozenset[str]( name for name, field in model.model_fields.items() if any(isinstance(meta, ChainFamilyMarker) for meta in field.metadata) )
[docs] def manifest_only_fields(model: type[BaseModel]) -> frozenset[str]: """Return the names of ``model``'s fields marked :data:`MANIFEST_ONLY`. Includes every field marked :data:`GOVERNANCE_STAMP`, since the governance marker is a :class:`ManifestOnlyMarker`. Reads the marker back out of pydantic's retained ``Annotated`` metadata, so a field carrying either marker - including one added by a subclass - enrols itself without any second list being edited. """ return frozenset[str]( name for name, field in model.model_fields.items() if any(isinstance(meta, ManifestOnlyMarker) for meta in field.metadata) )
[docs] def schema_family_fields(model: type[BaseModel]) -> frozenset[str]: """Return the names of ``model``'s fields marked :data:`SCHEMA_FAMILY`. The declared enrolment. ``collection_shaped_fields`` computes the set this one is meant to equal; the gate that compares them is what stops a new collection shipping outside coverage. """ return frozenset[str]( name for name, field in model.model_fields.items() if any(isinstance(meta, SchemaFamilyMarker) for meta in field.metadata) )
[docs] def is_schema_model_element(element: object) -> bool: """Return whether a tuple element type is a registry model or a closed union of them. A family may be a plain model class or an ``Annotated`` discriminated union of model classes; both are collections of schema models for enrolment and loader purposes. """ if isinstance(element, type): return issubclass(element, BaseModel) origin = get_origin(element) if origin is Annotated: return is_schema_model_element(get_args(element)[0]) if origin in (Union, UnionType): return all(is_schema_model_element(member) for member in get_args(element)) return False
[docs] def collection_shaped_fields(model: type[BaseModel]) -> frozenset[str]: """Return the names of ``model``'s tuple fields whose element is a schema model.""" shaped: set[str] = set() for name, field in model.model_fields.items(): if get_origin(field.annotation) is not tuple: continue args = get_args(field.annotation) if args and is_schema_model_element(args[0]): shaped.add(name) return frozenset(shaped)
[docs] def governance_stamp_fields(model: type[BaseModel]) -> frozenset[str]: """Return the names of ``model``'s fields marked :data:`GOVERNANCE_STAMP`. The stamp vocabulary proper, narrower than :func:`manifest_only_fields`: a field pinned to the manifest for legal-grounding reasons is not part of the provenance claim and must not be written or read as one. """ return frozenset[str]( name for name, field in model.model_fields.items() if any(isinstance(meta, GovernanceStampMarker) for meta in field.metadata) )
[docs] class CalculationClass(StrEnum): """What kind of declaration a modelo is, as an enforcement posture.""" FILING = "filing" """A self-assessment the taxpayer settles.""" INFORMATIVE = "informative" """An informative declaration carrying no liquidacion.""" SUMMARY = "summary" """A summary rolling up period filings without settling on its own."""
CalculationClassField = Annotated[CalculationClass, BeforeValidator(coerce_enum_member(CalculationClass))] """Registry ``calculation_class`` token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """ ModeloFilingCapability = Literal["borrador", "renta_ledger_default"] """Discriminator for the calculation role of a ModeloDefinition. - ``filing``: The modelo computes and submits filing-grade amounts. Most modelos fall into this class. - ``informative``: The modelo collects and reports data but does not compute filing-grade amounts. Revisions must have empty ``formulas`` and empty ``relations``; every casilla must be ``manual`` or ``informational``. Modelo 232 is the canonical example. - ``summary``: The modelo aggregates other modelos (e.g. 390 over 303) and may declare cross-model relations but is not a filing modelo. """ def _is_object_iterable(value: object) -> TypeGuard[Iterable[object]]: """Narrow an arbitrary iterable to object-valued entries without changing its shape.""" return isinstance(value, Iterable)
[docs] @pydantic_validation_boundary def sorted_unique_capabilities(value: object) -> object: """Order declared modelo capabilities and refuse a repeated declaration. A set-shaped container orders its members by hash, which varies between processes, so the same declaration would serialise and digest differently from one run to the next. Sorting on construction makes the declared order irrelevant to the stored value; rejecting duplicates keeps the tuple's length a truthful count of what was declared. """ if isinstance(value, str) or not _is_object_iterable(value): return value items = tuple(value) if not all(isinstance(item, str) for item in items): return items if len(set(items)) != len(items): raise RegistryValidationError("modelo capabilities must be declared at most once each") string_items = tuple(item for item in items if isinstance(item, str)) return tuple(sorted(string_items))
ModeloFilingCapabilities = Annotated[ tuple[ModeloFilingCapability, ...], BeforeValidator(sorted_unique_capabilities), ] """Declared modelo capabilities, stored sorted so serialisation is deterministic.""" DesignAuthority = Literal["authoritative", "provenance_only"] """Whether a bundled record design is a machine-readable authority or provenance. ``authoritative`` is the default and the case for every design a revision may cite as an export-layout authority. ``provenance_only`` marks a design the corpus keeps as evidence but which the registry must never treat as a layout map: the raw BOE ordenes that carry a modelo's design as an annex and so refuse to parse from wire position 1, and a superseded draft AEAT published alongside a definitive edition. The distinction has to be declared rather than inferred -- parse failure alone cannot tell "this is provenance" from "this design is broken", and neither can the absence of a record_design_epoch, which also covers designs whose selection window is merely not assigned yet. """
[docs] class DateAxis(StrEnum): """Which date a registry value or bracket table is keyed to.""" FILING_PERIOD = "filing_period" """The period the declaration covers, rather than any single date.""" DEVENGO_DATE = "devengo_date" """The accrual date the liability arises on.""" TRANSACTION_DATE = "transaction_date" """The date the underlying transaction occurred.""" INVOICE_DATE = "invoice_date" """The date the invoice was issued.""" SUBMISSION_DATE = "submission_date" """The date the declaration was submitted."""
[docs] class ThresholdComparison(StrEnum): """How a dated value is compared when it is used as a threshold. A provision can be redrafted so that only the COMPARISON changes: LIVA art. 103.Dos.2 read "exceda en un 20 por 100" until 2014 and "exceda en un 10 por ciento o mas" from 2015, and the added "o mas" moves the boundary case from excluded to included. A value alone cannot express that, so two redactions of one provision could not be two dated values while the operator lived in code. EXCLUSIVE is the default because it is the semantics every shipped value is read with today, so declaring the field changes no existing meaning. """ EXCLUSIVE = "exclusive" """The threshold is met only when the compared amount is strictly beyond it.""" INCLUSIVE = "inclusive" """The threshold is met when the compared amount reaches it."""
DateAxisField = Annotated[DateAxis, BeforeValidator(coerce_enum_member(DateAxis))] ThresholdComparisonField = Annotated[ ThresholdComparison, BeforeValidator(coerce_enum_member(ThresholdComparison)), ] """Registry date-axis token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """
[docs] class EvidenceTier(StrEnum): """What kind of authority grounds a registry entity.""" LEGAL_AUTHORITY = "legal_authority" """The law itself: a BOE provision or the orden that approves a design.""" OFFICIAL_SOURCE_GUIDANCE = "official_source_guidance" """AEAT-published guidance, instructions or a form specification.""" EXECUTABLE_PARITY_EVIDENCE = "executable_parity_evidence" """An artefact that can be executed and compared, such as a safe calculator.""" LAYOUT_AUTHORITY = "layout_authority" """A published record design fixing field offsets, widths and order."""
EvidenceTierField = Annotated[EvidenceTier, BeforeValidator(coerce_enum_member(EvidenceTier))] """Registry ``evidence_tier`` token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """
[docs] class CasillaSignConstraint(StrEnum): """Which side of zero a casilla's computed value may fall on. Declared by the registry and consumed unchanged by the spreadsheet surface that renders the same restriction as a cell validation rule. The two stated it separately before, so a member added here would have left the workbook accepting a value the engine rejects. """ ANY = "any" """Unrestricted; the casilla admits either sign.""" NON_NEGATIVE = "non_negative" """Zero or positive. A negative computed value is a constraint violation.""" NON_POSITIVE = "non_positive" """Zero or negative, as for a casilla that only ever records a deduction."""
CasillaSignConstraintField = Annotated[ CasillaSignConstraint, BeforeValidator(coerce_enum_member(CasillaSignConstraint)) ] """Registry ``sign`` token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """ CasillaSignConstraintValue = Literal[ CasillaSignConstraint.ANY, CasillaSignConstraint.NON_NEGATIVE, CasillaSignConstraint.NON_POSITIVE, ] """The same vocabulary where a coercing annotation may not be used. A strict payload or operation surface refuses a bare enum for a raw token and forbids a customised core schema, so those fields take this literal over the members above rather than restating the tokens. """ #: The tiers that ground a registry entity on AEAT-published MATERIAL, as distinct #: from the law itself and from executable parity artefacts. Three section validators #: require exactly this pair through ``require_any_source_tier``. Derived from the #: vocabulary above rather than restated, so a tier added there cannot leave this #: narrowing silently measuring the old set. REGISTRY_SOURCE_GROUNDING_TIERS: tuple[EvidenceTier, ...] = ( EvidenceTier.OFFICIAL_SOURCE_GUIDANCE, EvidenceTier.LAYOUT_AUTHORITY, )
[docs] class CorpusTier(StrEnum): """How much of a legal instrument the bundled corpus artefact carries.""" FULL_CONSOLIDATED = "full_consolidated" """The whole consolidated instrument, not a provision-suffixed extract.""" PROVISION_EXCERPT = "provision_excerpt" """A single provision lifted from the instrument."""
CorpusTierField = Annotated[CorpusTier, BeforeValidator(coerce_enum_member(CorpusTier))] """Registry ``corpus_tier`` token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """
[docs] class PublishingAuthority(StrEnum): """Who published the instrument or artefact a registry row cites.""" BOE = "boe" """The Boletin Oficial del Estado, where Spanish law is promulgated.""" AEAT = "aeat" """The Agencia Estatal de Administracion Tributaria.""" EU = "eu" """A European Union instrument.""" AUTONOMOUS_COMMUNITY = "autonomous_community" """An autonomous community exercising its own competence.""" OTHER = "other" """A publisher outside the four named above, recorded rather than assumed."""
PublishingAuthorityField = Annotated[PublishingAuthority, BeforeValidator(coerce_enum_member(PublishingAuthority))] """Registry ``authority`` token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """
[docs] class SettlementDirection(StrEnum): """Which way a declaration's result settles between taxpayer and AEAT.""" INGRESAR = "ingresar" """The taxpayer owes: the result is paid in.""" DEVOLVER = "devolver" """The AEAT owes: the result is refunded."""
SettlementDirectionField = Annotated[SettlementDirection, BeforeValidator(coerce_enum_member(SettlementDirection))] """Registry settlement-direction token hydrated into a member. Used as a mapping KEY as well as a field. Registry schema models validate strictly, which refuses a bare TOML string, and a coercion hop on the key type hydrates each key the same way it hydrates a scalar field. """
[docs] class RegistrySourceKind(StrEnum): """What kind of official artefact a source reference cites.""" RECORD_DESIGN = "record_design" """A diseno de registro fixing field offsets, widths and order.""" MANUAL_PDF = "manual_pdf" """An AEAT manual published as PDF.""" INSTRUCTIONS = "instructions" """Filing instructions accompanying a modelo.""" FORM_SPEC = "form_spec" """A published specification of the form's own fields.""" DICTIONARY = "dictionary" """A data dictionary for an XML or structured submission.""" XSD = "xsd" """An XML schema definition.""" SUPPRESSION_NOTICE = "suppression_notice" """A notice withdrawing or superseding a previously published artefact."""
RegistrySourceKindField = Annotated[RegistrySourceKind, BeforeValidator(coerce_enum_member(RegistrySourceKind))] """Registry source ``kind`` token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """
[docs] class CasillaDataType(StrEnum): """The scalar kind a casilla declares, and the root of every narrowing of it.""" DECIMAL = "decimal" """A decimal quantity that is not money.""" MONEY = "money" """A monetary amount, the default for a casilla.""" INTEGER = "integer" """A whole count.""" RATIO = "ratio" """A proportion or rate expressed as a fraction.""" TEXT = "text" """Free text the taxpayer or AEAT supplies.""" BOOLEAN = "boolean" """A declared yes or no.""" NIF = "nif" """A Spanish tax identity number.""" YEAR = "year" """A four-digit ejercicio.""" PERIOD_CODE = "period_code" """A filing-period token.""" COUNTRY_CODE = "country_code" """An ISO country code.""" IBAN = "iban" """An international bank account number.""" NAME = "name" """A party name.""" NIF_IVA = "nif_iva" """An intra-community NIF-IVA identifier.""" CCAA_CODE = "ccaa_code" """An autonomous-community code.""" PROVINCE_CODE = "province_code" """A Spanish province code.""" POSTAL_CODE = "postal_code" """A Spanish postal code.""" MUNICIPALITY_CODE = "municipality_code" """A municipality code.""" BIC = "bic" """A SWIFT business identifier code.""" DATE = "date" """A calendar date."""
CasillaDataTypeField = Annotated[CasillaDataType, BeforeValidator(coerce_enum_member(CasillaDataType))] """Registry casilla ``data_type`` token hydrated into a member. Registry schema models validate strictly, which refuses a bare TOML string for an enum-typed field, so the token is coerced at the boundary. """ NUMERIC_CASILLA_DATA_TYPES: Final[frozenset[CasillaDataType]] = frozenset( { CasillaDataType.DECIMAL, CasillaDataType.INTEGER, CasillaDataType.MONEY, CasillaDataType.RATIO, }, ) """Data types a formula may consume as a scalar operand. A narrowing of :class:`CasillaDataType`, held as members rather than as tokens so a member added or renamed cannot leave this set validating the old spelling. A ``StrEnum`` member hashes as its own string, so this set accepts a raw token and a member alike -- verified against the live enum, not assumed. Distinct from :data:`ZERO_PADDED_EXPORT_DATA_TYPES`, which excludes ``RATIO``: a ratio is a scalar a formula can read, and is not written as a zero-padded numeral. """ ZERO_PADDED_EXPORT_DATA_TYPES: Final[frozenset[CasillaDataType]] = frozenset( { CasillaDataType.DECIMAL, CasillaDataType.INTEGER, CasillaDataType.MONEY, }, ) """Data types written right-justified and zero-padded in a fixed-width export. The narrower of the two numeric sets. Keeping both named, rather than one set with an exception at the ratio, is what stops a later reader assuming they are the same population. """ CasillaDataTypeValue = Literal[ CasillaDataType.DECIMAL, CasillaDataType.MONEY, CasillaDataType.INTEGER, CasillaDataType.RATIO, CasillaDataType.TEXT, CasillaDataType.BOOLEAN, CasillaDataType.NIF, CasillaDataType.YEAR, CasillaDataType.PERIOD_CODE, CasillaDataType.COUNTRY_CODE, CasillaDataType.IBAN, CasillaDataType.NAME, CasillaDataType.NIF_IVA, CasillaDataType.CCAA_CODE, CasillaDataType.PROVINCE_CODE, CasillaDataType.POSTAL_CODE, CasillaDataType.MUNICIPALITY_CODE, CasillaDataType.BIC, CasillaDataType.DATE, ] """The same vocabulary in the one form an operation public schema admits. An operation model graph must not customise its Pydantic core schema, which the coercing field alias does, and a bare enum under strict validation refuses the plain token every caller sends. A literal over the enum's own members satisfies both: it carries no core-schema hook and it still accepts the token. Rooted in the vocabulary above rather than restated, so a member added there reaches this form too. """ LegalRefs = Annotated[tuple[LegalRefId, ...], Field(min_length=1)] SourceRefs = Annotated[tuple[SourceRefId, ...], Field(min_length=1)] SourceCitationText = Annotated[tuple[str, ...], Field(min_length=1)] FormulaOperator = Literal[ "add", "subtract", "multiply", "divide", "percent", "less_than", "less_equal", "greater_than", "greater_equal", "equal", "sum", "min", "max", "clamp", "negate", "copy", "if_then_else", "lookup_parameter", "lookup_bracket", "lookup_bracket_by_ccaa", "m100_resolve_renta_inmobiliaria_imputada", "irnr_resolve_tipo_gravamen", "m210_resolve_base_imponible", "lookup_parameter_by_entity_type", "lookup_bracket_by_entity_type", "previous_period_value", "previous_period_sum", "cross_model_sum", "age_at_year_end", "m131_resolve_modulos_previo", "m131_resolve_modulos_minoracion_empleo", "m131_resolve_modulos_indice_exceso", "m131_resolve_modulos_indices_generales", "m131_resolve_modulos_pequena_dimension_ignorado_flag", "m131_resolve_modulos_temporada_inicio_conflicto_flag", "m100_resolve_eo_agraria_indices_correctores", ]
[docs] class RegistryModel(BaseModel): """Strict frozen base for registry schema objects.""" model_config = STRICT_FROZEN_CONFIG
[docs] class SourceCitation(RegistryModel): """A source reference paired with the exact text required to ground it.""" source_ref: SourceRefId required_text: SourceCitationText @field_validator("required_text") @classmethod @pydantic_validation_boundary def _required_text_non_empty(cls, value: tuple[str, ...]) -> tuple[str, ...]: if any(not item.strip() for item in value): raise RegistryValidationError("source citation required_text entries must be non-empty") if len(set(value)) != len(value): raise RegistryValidationError("source citation required_text entries must be unique") return value
[docs] def filing_period_from_scope(filing_year: int, period: str) -> Period | None: """Return a core :class:`Period` when the registry token is a real filing-period code. An administrative registry token (Modelo 036 ``ALTA``, Modelo 145 ``comunicacion``) addresses a revision rather than naming a period, and the symbolic ``EVENT-N`` selector (Modelo 210) covers a set of event periods without being one, so neither has a filing period and both yield ``None``. Every other token must build a :class:`Period`; a malformed token or out-of-range year still raises. Raises: PeriodError: When a non-administrative token cannot build a period. """ if is_administrative_period_token(period) or is_symbolic_event_selector(period): return None return Period.from_year_and_code(filing_year, period)