Source code for cadrumo.application.calculations.errors

"""Typed exception hierarchy for the calculations application layer.

These exceptions are raised by previous-filing, binding-prefill, IVA
compensation, and encrypted observation-repository services at the application
boundary. Every class inherits from :class:`~core.errors.hierarchy.CoreError`;
validation failures use :class:`~core.errors.hierarchy.CoreValidationError` so CLI
and API callers receive registry-backed envelopes instead of generic
``ValueError`` or ``TypeError`` failures.

Every refusal raised across this package carries a registered locale key plus
machine facts; no raise site authors an operator-facing sentence, so
``str(exc)`` renders the key and the prose is resolved once, per locale, at the
presentation boundary.

A narrow subset is additionally a *safety* disposition: the calculation
observed evidence whose contradiction cannot be resolved by choosing one side,
because either choice silently changes a declared amount. Those raise sites
attach a :class:`~application.operator_actions.models.PreconditionVerdict` whose
``no_recovery_outcome`` is :attr:`~core.operator_action_enums.NoRecoveryOutcome.SAFETY`, so the CLI
boundary projects an explicit "there is deliberately no recovery here" instead
of manufacturing a retry that would re-derive the same contradiction.

See Also:
    :mod:`application.calculations.binding_prefill`:
        Previous-filing binding readers that raise
        :exc:`BindingPrefillTypeError`.
    :mod:`application.calculations.iva_compensation_history`:
        Modelo 303 IVA compensation carry-forward readers that raise
        :exc:`IvaCompensationModeloError`.
    :mod:`application.calculations.observations_repository`:
        Encrypted observation storage that raises :exc:`ObservationKeyError`
        and :exc:`ObservationCasillaReferenceError`.
"""

from __future__ import annotations

from collections.abc import Mapping
from enum import StrEnum
from typing import TYPE_CHECKING

from ...core.errors.hierarchy import CoreError, CoreValidationError, TerminalPreconditionErrorMixin
from ...core.operator_action_enums import ActionEvidenceProvenance, NoRecoveryOutcome

if TYPE_CHECKING:
    from ..operator_actions.models import PreconditionVerdict

    _CalculationPreconditionErrorMixin = TerminalPreconditionErrorMixin[PreconditionVerdict]
else:
    _CalculationPreconditionErrorMixin = TerminalPreconditionErrorMixin


[docs] class CalculationRefusalPrecondition(StrEnum): """Closed failed-condition identities for calculation safety dispositions. Membership is deliberately narrow. A condition earns a place here only when the refusal reports two sources of the same declared amount that disagree, so no automatic remedy exists: resolving it means deciding which figure the taxpayer actually declared, which is an operator judgement made against AEAT's own record. An evidence-incomplete refusal an operator fixes by supplying better evidence is not a safety disposition and stays a plain typed refusal. """ M303_CARRY_DISPOSITION_CONSISTENT = "calculations.m303_carry.disposition_consistent" M303_CARRY_DERIVATION_CONSISTENT = "calculations.m303_carry.derivation_consistent" M303_CARRY_MATCHES_REGISTRY_FORMULA = "calculations.m303_carry.matches_registry_formula"
[docs] def calculation_no_recovery_verdict( condition: CalculationRefusalPrecondition, *, facts: Mapping[str, str | int | bool], outcome: NoRecoveryOutcome = NoRecoveryOutcome.SAFETY, ) -> PreconditionVerdict: """Return one explicitly non-actionable outcome for a calculation refusal. The facts name exactly what the two disagreeing sources reported; the closed outcome is what stops a downstream boundary from manufacturing a recovery command that would simply re-derive the same contradiction. No action is bound because none of these conditions has a safe automatic remedy. Args: condition: The calculation condition that failed. facts: Stable machine facts, never prose, describing the observation. outcome: The closed no-recovery reason. Defaults to :attr:`~core.operator_action_enums.NoRecoveryOutcome.SAFETY`. Returns: The :class:`~application.operator_actions.models.PreconditionVerdict` carrying the failed condition and its explicit no-recovery outcome. """ from ..operator_actions.preconditions import no_action_precondition_verdict return no_action_precondition_verdict( condition_id=condition.value, facts=facts, provenance=ActionEvidenceProvenance.RUNTIME_OBSERVATION, outcome=outcome, )
[docs] class IvaCompensationModeloError(CoreError): """Raised when a non-Modelo 303 observation is passed to IVA compensation history. The IVA compensation carry-forward pipeline is exclusively sourced from Modelo 303 filed observations. Passing any other modelo to :func:`~application.calculations.iva_compensation_history.iva_compensation_state_from_observation_envelope` violates the calculation boundary contract. """
[docs] class BindingPrefillTypeError(CoreValidationError): """Raised when a binding selector field carries an unexpected runtime type. Binding selectors flow through pydantic with a union value type, so static analysis loses the per-key shape. This error is raised by the selector narrowing helpers in :mod:`application.calculations.binding_prefill`. It protects :func:`~application.calculations.binding_prefill.resolve_bindings_from_local_store` from selector values that do not match the expected ``int | str`` or ``str | tuple[str, ...]`` shape. """
[docs] class ObservationKeyError(CoreValidationError): """Raised when an observation key component fails its repository contract. The repository key for a ``(modelo, filing_year, period)`` triple must satisfy :func:`~adapters.persistence.storage.path_safety.safe_repository_id` for string components and fall within the supported year range ``[2000, 2099]`` for the integer year component. The key builders in :mod:`application.calculations.observations_repository` raise this error instead of a bare :class:`ValueError` so failures propagate through the typed error registry and produce structured envelopes. """
[docs] class ObservationCasillaReferenceError(CoreValidationError): """Raised when a persisted filing observation names undeclared casillas. :class:`~application.calculations.observations_repository.CalculationObservationRepositoryProtocol` is the encrypted calculation-history substrate for cross-period and cross-modelo reads. It must not persist a :class:`~domain.calculations.registry.bindings.RegistryModeloObservation` whose casilla keys are only syntactically valid ``CasillaId`` strings; every key must be declared by the resolved :class:`~domain.calculations.registry.schema.RegistrySnapshot` for that modelo, year, and period via :func:`~domain.calculations.registry.casilla_membership.undeclared_casilla_ids`. """