Source code for cadrumo.domain.bienes_inversion.regularizacion_parameters

"""Registry-resolved LIVA art-107/109 figures for the capital-goods regularización.

A registry parameter is hosted on a REVISION, and only the filing context names
one. This domain package holds no filing context and performs no revision
selection -- no domain package in the tree does -- so it cannot read these
figures for itself. The accepted placement decision resolves them at the
application boundary, which already holds the compiled revision it selected, and
passes the result in as :class:`BienesInversionRegularizacionParameters`.

The bundle carries no defaults and has one legitimate constructor,
:func:`resolve_bienes_inversion_regularizacion_parameters`, which reads the
validated authority. A calculator therefore cannot be called without resolved
values, and a missing parameter leaves the bundle unconstructable rather than
silently zero or silently equal to yesterday's law.

:class:`BienesInversionParameterProvenance` travels with the values into every
result. Without it, handing the same bundle to a producer and to its oracle
would make a WRONG bundle self-consistent and cost the oracle its independence;
with it, a replay can refuse a result whose provenance disagrees with the bundle
it was handed.

See Also:
    :mod:`domain.bienes_inversion.register`
        The art-109 and art-110 computations that consume the bundle.
"""

from __future__ import annotations

from collections.abc import Mapping
from datetime import date
from decimal import Decimal

from pydantic import BaseModel, Field, model_validator

from ...core.errors.hierarchy import CadrumoError as _CadrumoError
from ...core.errors.hierarchy import pydantic_validation_boundary
from ...core.models import STRICT_FROZEN_CONFIG as _STRICT_FROZEN_CONFIG
from ..calculations.registry.formula_runtime_ops import resolve_dated_value
from ..calculations.registry.schema import ModeloRevision
from ..calculations.registry.schema_base import ThresholdComparison
from ..calculations.registry.schema_formula import DatedValue


[docs] class BienesInversionParameterResolutionError(_CadrumoError): """Raised when a revision cannot supply the whole art-107/109 figure set. Refusing here rather than substituting a default is the point: an incomplete set means the revision does not govern this arithmetic, and a partial bundle would regularise a capital good on figures nothing grounded. """
#: Parameter id stem shared by every figure in the family, per modelo. _ID_STEM = "bien-inversion-" #: Slug of each figure, in the bundle's own field order. _VENTANA_MUEBLE = "ventana-anos-mueble" _VENTANA_INMUEBLE = "ventana-anos-inmueble" _DIVISOR_MUEBLE = "divisor-mueble" _DIVISOR_INMUEBLE = "divisor-inmueble" _UMBRAL_PUNTOS = "regularizacion-umbral-puntos" _REQUIRED_SLUGS = ( _VENTANA_MUEBLE, _VENTANA_INMUEBLE, _DIVISOR_MUEBLE, _DIVISOR_INMUEBLE, _UMBRAL_PUNTOS, )
[docs] class BienesInversionParameterProvenance(BaseModel): """Which registry declaration produced a bundle's values. Attributes: modelo_id: Modelo whose revision hosts the parameters. revision_id: The hosting revision's canonical id. parameter_ids: Every parameter read, in bundle field order. resolved_on: The filing-period date the values were selected for. """ model_config = _STRICT_FROZEN_CONFIG modelo_id: str = Field(min_length=1, max_length=16) revision_id: str = Field(min_length=1, max_length=64) parameter_ids: tuple[str, ...] = Field(min_length=len(_REQUIRED_SLUGS), max_length=len(_REQUIRED_SLUGS)) resolved_on: date @model_validator(mode="after") @pydantic_validation_boundary def _names_the_whole_family(self) -> BienesInversionParameterProvenance: """Refuse provenance that does not describe the bundle it travels with. A bundle resolves five figures, so provenance naming fewer is not a record of where those values came from -- it is a partial claim that would still satisfy an oracle comparing provenance by equality. Pinning the count and the distinctness here means a hand-built bundle cannot carry a provenance that describes something else. """ if len(set(self.parameter_ids)) != len(self.parameter_ids): raise ValueError("parameter_ids must not repeat an id") return self
[docs] class BienesInversionRegularizacionParameters(BaseModel): """The LIVA art-107/109 figures one revision declares, resolved. Every field is required. There is no default anywhere in this model, so an instance can only exist because a validated revision supplied every value. Attributes: ventana_anos_mueble: Art. 107.Uno window for a non-real-property good. ventana_anos_inmueble: Art. 107.Tres window for terrenos o edificaciones. divisor_mueble: Art. 109.3a divisor for a non-real-property good. divisor_inmueble: Art. 109.3a divisor for terrenos o edificaciones. umbral_puntos: Art. 107.Uno de-minimis difference in percentage points. umbral_comparison: Whether the de-minimis is exceeded strictly or not. Registry data rather than a fixed operator, because a redaction changing "superior a" to "igual o superior a" would change the arithmetic without changing the number. provenance: The declaration these values came from. """ model_config = _STRICT_FROZEN_CONFIG ventana_anos_mueble: int = Field(gt=0) ventana_anos_inmueble: int = Field(gt=0) divisor_mueble: Decimal = Field(gt=Decimal(0)) divisor_inmueble: Decimal = Field(gt=Decimal(0)) umbral_puntos: Decimal = Field(ge=Decimal(0)) umbral_comparison: ThresholdComparison provenance: BienesInversionParameterProvenance
[docs] def regularizacion_applies(self, diferencia_puntos: Decimal) -> bool: """Whether the art-107.Uno de-minimis gate admits a regularisation. The comparison direction is registry data rather than a hardcoded operator, so the day a redaction changes it the change lands in the declaration and not in this file. """ if self.umbral_comparison is ThresholdComparison.INCLUSIVE: return diferencia_puntos >= self.umbral_puntos return diferencia_puntos > self.umbral_puntos
[docs] def resolve_bienes_inversion_regularizacion_parameters( revision: ModeloRevision, *, modelo_id: str, filing_period_date: date, ) -> BienesInversionRegularizacionParameters: """Build the bundle from one compiled revision's declared parameters. Args: revision: The compiled revision the application layer selected. modelo_id: Modelo the revision belongs to, recorded in provenance. filing_period_date: Date on the ``filing_period`` axis to select values for. Returns: The fully resolved :class:`BienesInversionRegularizacionParameters`. Raises: BienesInversionParameterResolutionError: When the revision declares none of the family, only part of it, or a value that does not resolve for ``filing_period_date``. Core types: :class:`~cadrumo.domain.calculations.registry.schema.ModeloRevision`. """ declared = { parameter.id.split(_ID_STEM, 1)[1]: parameter for parameter in revision.parameters if _ID_STEM in parameter.id } missing = tuple(slug for slug in _REQUIRED_SLUGS if slug not in declared) if missing: raise BienesInversionParameterResolutionError( f"modelo {modelo_id} revision {revision.id} declares no capital-goods " f"regularisation figure for {', '.join(missing)}; the LIVA art-107/109 " "arithmetic has no grounded source for this filing context", ) date_context: Mapping[str, date] = {"filing_period": filing_period_date} resolved: dict[str, DatedValue] = {} for slug in _REQUIRED_SLUGS: parameter = declared[slug] try: resolved[slug] = resolve_dated_value(parameter, date_context) except Exception as exc: raise BienesInversionParameterResolutionError( f"modelo {modelo_id} revision {revision.id} parameter {parameter.id} " f"does not resolve for filing-period date {filing_period_date.isoformat()}: {exc}", ) from exc return BienesInversionRegularizacionParameters( ventana_anos_mueble=int(resolved[_VENTANA_MUEBLE].value), ventana_anos_inmueble=int(resolved[_VENTANA_INMUEBLE].value), divisor_mueble=Decimal(resolved[_DIVISOR_MUEBLE].value), divisor_inmueble=Decimal(resolved[_DIVISOR_INMUEBLE].value), umbral_puntos=Decimal(resolved[_UMBRAL_PUNTOS].value), umbral_comparison=resolved[_UMBRAL_PUNTOS].comparison, provenance=BienesInversionParameterProvenance( modelo_id=modelo_id, revision_id=revision.id, parameter_ids=tuple(declared[slug].id for slug in _REQUIRED_SLUGS), resolved_on=filing_period_date, ), )