Source code for cadrumo.domain.period

"""Date-boundary helpers for filing-period registry tokens.

:class:`~cadrumo.core.Period` is the backend filing-period authority: a filing
year plus registry token, constructed with ``Period.from_year_and_code``.
This module provides only date-boundary helpers for bare registry tokens.

Do not treat this module as canonical period storage. Prefer typed
:class:`~cadrumo.core.Period` at domain boundaries and decompose to
``filing_year`` plus ``registry_token`` only at registry/helper seams.

The ``nP`` tokens are the Impuesto sobre Sociedades pago-fraccionado
instalment claves (Modelo 202). Per the AEAT Modelo 202 instructions,
``1P`` is the payment made in the first twenty days of April, ``2P``
the equivalent October payment, and ``3P`` the December payment; the
period-boundary helpers map each instalment to its payment month.

These helpers are not a display parser, not a ledger date-span authority, and
not a replacement for registry period validation. They accept the bare tokens
already selected by an owning caller and raise typed domain period errors when
the token cannot be mapped.

Callers wrap :class:`ValueError` into their own domain error type
(e.g. ``ModeloBuilderError``, ``RegistrySnapshotError``) at the boundary
they own.
"""

from __future__ import annotations

from datetime import date

from ..core.errors.hierarchy import CadrumoError
from ..core.period import Period
from ..core.period import PeriodError as CorePeriodError


[docs] class RegistryPeriodError(CadrumoError): """Base class for errors raised by this bare registry-token helper module."""
[docs] class PeriodValidationError(RegistryPeriodError): """Raised when a bare registry token cannot be mapped to helper dates."""
[docs] def period_start_date(filing_year: int, registry_period: str) -> date: """Return the inclusive start-of-period date for a registry token. Args: filing_year: The filing year carried by the typed period scope. registry_period: Bare registry token such as ``"1T"`` ... ``"4T"``, ``"01"`` ... ``"12"``, ``"0A"``, or Modelo 202 instalments ``"1P"`` ... ``"3P"``. Returns: The first day of the period the token covers (e.g. ``"1T"`` → ``YYYY-01-01``, ``"4T"`` → ``YYYY-10-01``, ``"0A"`` → ``YYYY-01-01``, ``"03"`` → ``YYYY-03-01``, ``"1P"`` → ``YYYY-04-01``, ``"2P"`` → ``YYYY-10-01``, ``"3P"`` → ``YYYY-12-01``). Raises: PeriodValidationError: When ``registry_period`` is not a recognised shape. """ period = _periodfrom_registry_token(filing_year, registry_period) if period.has_date_span(): return period.start_date if registry_period == "1P": return date(filing_year, 4, 1) if registry_period == "2P": return date(filing_year, 10, 1) if registry_period == "3P": return date(filing_year, 12, 1) raise PeriodValidationError(f"invalid registry period {registry_period!r}")
[docs] def period_end_date(filing_year: int, registry_period: str) -> date: """Return the inclusive end-of-period date for a registry token. Args: filing_year: The filing year carried by the typed period scope. registry_period: Bare registry token such as ``"1T"`` ... ``"4T"``, ``"01"`` ... ``"12"``, ``"0A"``, or Modelo 202 instalments ``"1P"`` ... ``"3P"``. Returns: The last day of the period the token covers (e.g. ``"1T"`` → ``YYYY-03-31``, ``"0A"`` and ``"4T"`` → ``YYYY-12-31``, ``"03"`` → ``YYYY-03-31``, ``"1P"`` → ``YYYY-04-30``, ``"2P"`` → ``YYYY-10-31``, ``"3P"`` → ``YYYY-12-31``). Raises: PeriodValidationError: When ``registry_period`` is not a recognised shape. """ period = _periodfrom_registry_token(filing_year, registry_period) if period.has_date_span(): return period.end_date if registry_period == "1P": return date(filing_year, 4, 30) if registry_period == "2P": return date(filing_year, 10, 31) if registry_period == "3P": return date(filing_year, 12, 31) raise PeriodValidationError(f"invalid registry period {registry_period!r}")
[docs] def calculation_filing_date(period: Period) -> date: """Resolve the deterministic filing date used by a calculation context. This is deliberately distinct from :func:`period_end_date`. The latter is a strict bare-token range helper and continues to refuse tokens that do not represent a ledger date span. A calculation already owns a typed :class:`Period`, however, and needs a date for every accepted code so temporal registry parameters resolve consistently across calculation, verification, filing replay, and calculation-sheet paths. Args: period: The typed filing-period scope of the calculation. Returns: The inclusive end date for contiguous periods; the corresponding ordinary quarter end for ``EXT-nT``; Modelo 202 payment-month ends for ``1P`` through ``3P``; and 31 December for ``4P``, ``AD-HOC``, and ``EVENT-N``. """ if period.has_date_span(): return period.end_date code = period.registry_token if code.startswith("EXT-"): return Period.from_year_and_code(period.filing_year, code.removeprefix("EXT-")).end_date if code == "1P": return date(period.filing_year, 4, 30) if code == "2P": return date(period.filing_year, 10, 31) if code == "3P": return date(period.filing_year, 12, 31) if code == "4P" or code == "AD-HOC" or code.startswith("EVENT-"): return date(period.filing_year, 12, 31) raise PeriodValidationError(f"calculation filing date is undefined for registry period {code!r}")
def _periodfrom_registry_token(filing_year: int, registry_period: str) -> Period: """Construct the canonical typed period or preserve this module's public error contract.""" try: return Period.from_year_and_code(filing_year, registry_period) except CorePeriodError as exc: raise PeriodValidationError(f"invalid registry period {registry_period!r}") from exc __all__ = [ "PeriodValidationError", "RegistryPeriodError", "calculation_filing_date", "period_end_date", "period_start_date", ]