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

"""Deadline-window and filing-schedule declarations for registry revisions."""

from __future__ import annotations

from collections.abc import Mapping
from datetime import date
from enum import StrEnum
from typing import Annotated

from pydantic import BeforeValidator, Field, field_validator, model_validator

from ....core.errors.hierarchy import pydantic_validation_boundary
from ....core.filing_year import FilingYear
from ....core.period import Period, PeriodKind, RegistrySelectorPeriodCode, registry_period_kind
from ....core.result_disposition import ResultDisposition
from .condition_mode import ConditionMode, ConditionModeField
from .errors import RegistryValidationError
from .ids import DeadlineWindowId
from .schema_base import LegalRefs, RegistryModel, SourceRefs, coerce_enum_member
from .schema_verification import ProfilePredicateDefinition

__all__ = [
    "DeadlineWindowDefinition",
    "ModeloScheduleDefinition",
    "filing_schedule_period_kind_mismatches",
]


def _parse_deadline_window_period(value: object) -> Period:
    """Hydrate a deadline-window period through :class:`~core.period.Period`."""
    if isinstance(value, Period):
        return value
    if isinstance(value, Mapping):
        try:
            return Period.model_validate(value)
        except ValueError as exc:
            raise ValueError(f"invalid deadline window period mapping {value!r}: {exc}") from exc
    if not isinstance(value, str):
        raise ValueError(f"deadline window period must be a string or Period, got {type(value).__name__}")

    try:
        return Period.from_string(value)
    except ValueError as exc:
        raise ValueError(f"invalid deadline window period {value!r}: {exc}") from exc


[docs] class FilingCadence(StrEnum): """The filing cadence a deadline window or modelo schedule declares. Distinct from the period kind in the core package, which shares three member values but names a different axis: that vocabulary describes a period's shape, including instalment and extended periods, and has no ad-hoc member. Both are referenced from this module, so the names must not collide. """ MONTHLY = "monthly" """One filing period per calendar month.""" QUARTERLY = "quarterly" """One filing period per calendar quarter.""" ANNUAL = "annual" """One filing period covering the ejercicio.""" AD_HOC = "ad_hoc" """No fixed cadence; the period is opened by an event rather than a calendar."""
FilingCadenceField = Annotated[FilingCadence, BeforeValidator(coerce_enum_member(FilingCadence))] """Registry ``period_kind`` token hydrated into a cadence 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 DeadlineWindowDefinition(RegistryModel): """Declare the applicable opening, closing, and payment dates for a filing.""" id: DeadlineWindowId filing_year: FilingYear period: Annotated[Period, BeforeValidator(pydantic_validation_boundary(_parse_deadline_window_period))] period_kind: FilingCadenceField opens_on: date closes_on: date payment_cutoff_on: date | None = None applicability_condition_mode: ConditionModeField = ConditionMode.ALL applicability_conditions: tuple[ProfilePredicateDefinition, ...] = () resultado_scope: ( Annotated[ ResultDisposition, BeforeValidator(lambda value: ResultDisposition(value) if isinstance(value, str) else value), ] | None ) = None tipo_renta_scope: tuple[str, ...] | None = None legal_refs: LegalRefs source_refs: SourceRefs @field_validator("tipo_renta_scope") @classmethod @pydantic_validation_boundary def _validate_tipo_renta_scope(cls, value: tuple[str, ...] | None) -> tuple[str, ...] | None: """Preserve official M210 codes without folding them into rate concepts.""" if value is None: return None if not value: raise RegistryValidationError("deadline window tipo_renta_scope must not be empty") if len(set(value)) != len(value): raise RegistryValidationError("deadline window tipo_renta_scope entries must be unique") malformed_codes = tuple(code for code in value if len(code) != 2 or not code.isdecimal()) if malformed_codes: raise RegistryValidationError( f"deadline window tipo_renta_scope contains malformed official Modelo 210 codes " f"{malformed_codes!r}; codes must be two-digit decimal tokens", ) return value @model_validator(mode="after") @pydantic_validation_boundary def _validate_window(self) -> DeadlineWindowDefinition: if self.filing_year != self.period.filing_year: raise RegistryValidationError( f"deadline window {self.id!r} filing_year {self.filing_year} must match " f"period filing_year {self.period.filing_year}", ) if self.opens_on > self.closes_on: raise RegistryValidationError(f"deadline window {self.id!r} opens_on must not be after closes_on") if self.payment_cutoff_on is not None and self.payment_cutoff_on > self.closes_on: raise RegistryValidationError(f"deadline window {self.id!r} payment_cutoff_on must not be after closes_on") if self.applicability_condition_mode is ConditionMode.ANY and not self.applicability_conditions: raise RegistryValidationError(f"deadline window {self.id!r} any-mode requires applicability conditions") return self
_SCHEDULE_PERIOD_KINDS: dict[str, frozenset[PeriodKind]] = { "monthly": frozenset({PeriodKind.MONTHLY}), "quarterly": frozenset({PeriodKind.QUARTERLY, PeriodKind.INSTALMENT, PeriodKind.EXTENDED}), "annual": frozenset({PeriodKind.ANNUAL}), # Event and administrative tokens classify as EXTENDED. Modelo 840 uses # annual 0A as its exercise coordinate, so this cadence also admits ANNUAL; # legal/source grounding still establishes whether a declaration is correct. "ad_hoc": frozenset({PeriodKind.EXTENDED, PeriodKind.ANNUAL}), }
[docs] def filing_schedule_period_kind_mismatches(period_kind: str, periods: tuple[str, ...]) -> tuple[str, ...]: """Return schedule tokens whose canonical cadence contradicts ``period_kind``.""" accepted = _SCHEDULE_PERIOD_KINDS[period_kind] mismatches: list[str] = [] for token in periods: try: canonical_kind = registry_period_kind(token) except ValueError: mismatches.append(token) continue if canonical_kind not in accepted: mismatches.append(token) return tuple(mismatches)
[docs] class ModeloScheduleDefinition(RegistryModel): """Declare the filing periods and profile conditions for a modelo schedule.""" id: str = Field(min_length=1, max_length=128, pattern=r"^[A-Za-z0-9_.-]+$") period_kind: FilingCadenceField periods: tuple[RegistrySelectorPeriodCode, ...] = Field(min_length=1) profile_condition_mode: ConditionModeField = ConditionMode.ALL profile_conditions: tuple[ProfilePredicateDefinition, ...] = () legal_refs: LegalRefs source_refs: SourceRefs @field_validator("periods") @classmethod @pydantic_validation_boundary def _periods_unique(cls, value: tuple[str, ...]) -> tuple[str, ...]: if len(set(value)) != len(value): raise RegistryValidationError("filing schedule periods must be unique") return value @model_validator(mode="after") @pydantic_validation_boundary def _validate_schedule(self) -> ModeloScheduleDefinition: if self.profile_condition_mode is ConditionMode.ANY and not self.profile_conditions: raise RegistryValidationError(f"filing schedule {self.id!r} any-mode requires profile conditions") mismatches = filing_schedule_period_kind_mismatches(self.period_kind, self.periods) if mismatches: raise RegistryValidationError( f"filing schedule {self.id!r} period_kind {self.period_kind!r} contradicts periods {mismatches!r}", ) return self