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

"""Typed registry selector contract for the Modelo 100 inventory projection.

This module declares only the binding selector and its accumulating shape
validator. Source resolution, readiness, valuation, provenance, and registry
enrollment are owned by later integration steps; a selector declaration cannot
make an inventory schedule authoritative or complete.

The filing year is deliberately NOT pinned here. Which years carry an inventory
projection is a revision-owned fact, declared by the bindings the authoring tree
ships, so a later revision adding one must be an authoring change rather than an
edit to this file. Today only the Modelo 100 2025 revision declares them.

The operation-to-casilla identity below is a structural invariant of the
projection vocabulary: an operation names WHICH figure it produces, so its
destination is what the operation means, not a value the law re-sets per year.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from pydantic import BaseModel, model_validator

from ....core.aggregation import BindingAggregationOp, BindingSourceKind
from ....core.casilla_id import CasillaId, validated_casilla_id
from ....core.errors.hierarchy import pydantic_validation_boundary
from ....core.models import STRICT_FROZEN_CONFIG
from .binding_selector_utils import selector_against_model
from .binding_temporal import BindingTemporalSelector, SameTargetContext
from .errors import RegistryValidationError

if TYPE_CHECKING:
    from .schema import BindingDefinition

type InventoryProjectionOperation = Literal[
    "complete_acquisition_cost",
    "closing_minus_opening_positive",
    "opening_minus_closing_positive",
]

_INVENTORY_DESTINATION_BY_OPERATION: dict[InventoryProjectionOperation, CasillaId] = {
    "complete_acquisition_cost": validated_casilla_id(
        "0181",
        surface="inventory complete-acquisition-cost destination",
    ),
    "closing_minus_opening_positive": validated_casilla_id(
        "0177",
        surface="inventory positive closing-minus-opening destination",
    ),
    "opening_minus_closing_positive": validated_casilla_id(
        "0182",
        surface="inventory positive opening-minus-closing destination",
    ),
}


[docs] class InventoryProvider(BaseModel): """One immutable operation template expanded over runtime activity rows. ``complete_acquisition_cost`` names the legally complete acquisition-cost fact, never the existing IVA-exclusive purchase subtotal. The two stock variation operations name non-negative magnitudes in opposite directions, so no generic signed variation can enter the selector vocabulary. """ model_config = STRICT_FROZEN_CONFIG kind: Literal[BindingSourceKind.INVENTORY] = BindingSourceKind.INVENTORY modelo: Literal["100"] # The source year is the TARGET's year, never an authored constant: the # declaration states timeless intent and the filing context supplies the # year. The casilla-renumbering hazard the former absolute ``filing_year`` # guarded against is carried by the revision the binding is declared in # together with ``_INVENTORY_DESTINATION_BY_OPERATION``, which still pins # each operation to the exact destination casilla of its own revision. temporal: BindingTemporalSelector = SameTargetContext() projection_grain: Literal["taxpayer_year_activity"] fact: Literal["row_field"] record: Literal["inventory_activity"] grouping: Literal["per_inventory_activity"] row_field: InventoryProjectionOperation target_casilla_id: CasillaId @model_validator(mode="after") @pydantic_validation_boundary def _require_operation_destination_identity(self) -> InventoryProvider: expected = _INVENTORY_DESTINATION_BY_OPERATION[self.row_field] if self.target_casilla_id != expected: raise RegistryValidationError( f"inventory operation {self.row_field!r} must target casilla {expected!r}, " f"not {self.target_casilla_id!r}", ) return self
[docs] def validate_inventory_binding(binding: BindingDefinition) -> list[str]: """Validate the inventory operation invariants for snapshot build. The operation-to-destination identity is re-run here through the shared selector gate rather than trusted to have happened earlier: a snapshot is built from provider objects that may reach this boundary without having passed model validation, and an operation silently landing on another operation's casilla is a filing-grade misdeclaration, not a shape detail. The identity itself is never restated here -- the union member owns it. """ failures = selector_against_model(binding, InventoryProvider) if binding.aggregation is None or binding.aggregation.op is not BindingAggregationOp.ROWS: failures.append(f"binding {binding.id!r} inventory operation template requires aggregation op 'rows'") return failures
__all__ = [ "InventoryProjectionOperation", "InventoryProvider", "validate_inventory_binding", ]