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

"""The profile provider: a binding that reads the taxpayer's own censo record.

A profile binding names a field of the taxpayer profile substrate (declarante,
conyuge, hijos, ascendientes, ...) rather than a filed value or a ledger fold.
The three shapes it admits -- a scalar key, a composite projection of several
keys through a format renderer, and a field of a typed profile model -- are
mutually exclusive by validation, because a declaration that named two of them
would leave the value's origin ambiguous.
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field, model_validator

from ....core.aggregation import BindingSourceKind
from ....core.errors.hierarchy import pydantic_validation_boundary
from ....core.models import STRICT_FROZEN_CONFIG
from .errors import RegistryValidationError

__all__ = ["ProfileProvider"]


[docs] class ProfileProvider(BaseModel): """Strict validator for the selector mapping of a profile-source binding. Profile-source bindings read values from the taxpayer profile substrate (declarante, conyuge, hijos, ascendientes, ...). They land on the fichero-BOE record either as a typed scalar (single ``profile_key``) or via a composite projection (``profile_keys`` with a ``format`` rendering function), and optionally as a sub-collection field of a typed profile model (``profile_model`` + ``collection`` + ``field``). Two cross-cutting fields apply to every shape: * ``xsd_path`` / ``xsd_attribute`` / ``dictionary_field``: how the value is addressed on the on-wire record. * ``required_when_profile_key`` / ``required_when_value``: a conditional applicability gate; only certain profile shapes set these. """ model_config = STRICT_FROZEN_CONFIG kind: Literal[BindingSourceKind.PROFILE] = BindingSourceKind.PROFILE # Scalar shape profile_key: str | None = Field(default=None, min_length=1, max_length=128) # Composite shape profile_keys: tuple[str, ...] = () # Collection shape (typed sub-models on the profile) profile_model: str | None = Field(default=None, min_length=1, max_length=128) collection: str | None = Field(default=None, min_length=1, max_length=64) field: str | None = Field(default=None, min_length=1, max_length=128) repeating: bool = False # On-wire addressing xsd_path: str | None = Field(default=None, min_length=1, max_length=512) xsd_attribute: str | None = Field(default=None, min_length=1, max_length=128) dictionary_field: str | None = Field(default=None, min_length=1, max_length=128) # Rendering / formatting format: str | None = Field(default=None, min_length=1, max_length=64) # Conditional applicability required_when_profile_key: str | None = Field(default=None, min_length=1, max_length=128) required_when_value: str | None = Field(default=None, min_length=1, max_length=256) @model_validator(mode="after") @pydantic_validation_boundary def _validate_profile_shape(self) -> ProfileProvider: has_scalar = self.profile_key is not None has_composite = bool(self.profile_keys) has_collection = self.profile_model is not None shape_count = sum((has_scalar, has_composite, has_collection)) if shape_count != 1: raise RegistryValidationError( "profile selector must declare exactly one of profile_key (scalar), " "profile_keys (composite), or profile_model (collection)", ) if has_composite and self.format is None: raise RegistryValidationError("profile composite selector (profile_keys) requires a format renderer") if has_collection: if self.field is None: raise RegistryValidationError("profile model selector must declare field") # ``collection`` is only required when the profile model # selector targets a repeating sub-collection # (``repeating = true`` plus a named ``collection``). Scalar # fields on a typed profile model (e.g. ``profile_model = # "TaxResidenceProfile"`` + ``field = "ccaa"``) omit # ``collection`` because the field IS at the model root. if self.repeating and self.collection is None: raise RegistryValidationError("profile collection selector with repeating=true must declare collection") # required_when_* must be paired if (self.required_when_profile_key is None) != (self.required_when_value is None): raise RegistryValidationError( "profile selector required_when_profile_key and required_when_value must be declared together", ) return self
# --------------------------------------------------------------------------- # Per-family build-time validator dispatch # # The selector lookup table this module used to own is gone: the provider union # in :mod:`~domain.calculations.registry.binding_provider` hydrates a binding # into its family model at construction, so no second table is needed to say # which model a source kind validates against. What remains is the op/fact # cross-invariant validator per family, keyed by the same discriminator. # ---------------------------------------------------------------------------