aeat.application.modelo._profile_binding module

Application-owned profile-sourced binding resolution.

A registry binding with source = "profile" carries a value the operator already entered onto their user profile (tax-residence CCAA, censo status, declaration type, …). Without an explicit resolution step the calculation engine never sees those facts: the operator would have to re-type, via --binding KEY=VALUE, data the profile already holds, and a formula that consumes an unsupplied profile binding fails with binding ... has no supplied value.

This module loads the bucket’s UserProfileRecord, walks every source = "profile" binding the RegistrySnapshot revision declares, and projects the matching profile fact into the correct engine channel. The resolved bindings use ProfileSchemaDefinition and UserProfileFactValue to translate raw facts.

Channel selection is the load-bearing decision. The registry runtime resolves profile bindings through three engine channels: date_binding_values for date operands, enum_binding_values for dispatch keys, and Decimal-valued binding_values for numeric operands. The channel is determined by the consumer shape, not by the binding’s typed_enum annotation: expression_date_binding_refs() finds date operands, enum_consumed_binding_ids() finds enum dispatch operands, and formula-consumed or bound numeric casillas use the Decimal channel. Profile bindings that only populate identity or export-layout fields are intentionally left out of the calculation source mesh.

exception ProfileBindingResolutionError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: ModeloError

Raised when a profile-sourced binding cannot be resolved for a calculation.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
resolve_profile_sourced_bindings(snapshot, *, bucket_id, profile_record=None, caller_binding_ids=frozenset({}), schema=None)[source]

Resolve every source = "profile" binding the revision declares.

Parameters:
  • snapshot (RegistrySnapshot) – The RegistrySnapshot whose revision’s profile bindings are resolved against the bucket’s user profile facts.

  • bucket_id (str) – Stable bucket identifier used to load the user profile.

  • profile_record (object | None) – Optional UserProfileRecord override for testing.

  • caller_binding_ids (frozenset[TypeAliasType]) – Binding ids already supplied by the caller; these are skipped so caller overrides take precedence over the profile.

  • schema (ProfileSchemaDefinition | None) – Optional profile schema definition override.

Return type:

CalculationSourceResolution

Walks the registry revision’s source = "profile" bindings, matches each against a fact on the bucket’s user profile, and routes the value into the Decimal, enum, or date channel according to the consuming formula or bound numeric casilla.

A binding the profile cannot satisfy is skipped silently: the engine surfaces the missing-binding error only if a formula needs it. A bucket with no profile yields an empty result.

Returns a CalculationSourceResolution with resolved binding values split across the Decimal, enum, and date engine channels and a CalculationSourceProvenance row per profile-sourced binding.

See also

enum_consumed_binding_ids():

Identifies profile bindings consumed as enum dispatch keys.

expression_date_binding_refs():

Identifies profile bindings consumed by date-aware formula ops.

inject_derived_marriage_facts(fact_index, filing_year)

Inject computed matrimonio-sobrevenido integers into fact_index in-place.

When renta_taxpayer.marriage_date is present as a date-typed fact, the three derived binding keys (marriage_full_year, marriage_month_start, marriage_month_end) are computed from the raw date and the snapshot’s filing_year. For an explicitly unmarried taxpayer the same casillas are neutral zeros: Art. 82 marriage-month facts are not applicable, and the CLI must not force a single filer to invent a marriage date. Married taxpayers without a marriage date remain unresolved. Values are injected as Decimal so the Decimal-channel binding resolver picks them up without a special case in the main loop.

This function is idempotent: if the keys are already present (e.g. written as explicit profile facts by an older tooling version) they are not overwritten.

Return type:

None

Parameters:
  • fact_index (dict[str, UserProfileFactValue])

  • filing_year (int)

inject_derived_autonomic_deduccion_facts(fact_index, filing_year)

Inject the Madrid nacimiento/adopción deducción derived facts (casilla 1039).

Companion to _inject_derived_marriage_facts() and _inject_derived_family_facts(). Reads the existing renta_family.descendiente.{n}.* facts and tax_residence.ccaa and computes the prorrateo-weighted count of descendants inside the Comunidad de Madrid nacimiento/adopción applicability window (DL 1/2010 arts. 4 y 18.1) who cohabit, projecting it onto the synthetic Decimal keys the registry formula on casilla 1039 consumes.

The trigger is fail-closed: it auto-populates only the unambiguous single / monoparental individual filer. A tributación conjunta declaration or a married filer needs the spouse’s base imponible for the unidad-familiar 61.860 € límite, which the app does not persist (research F9); for those cases no count is injected, the registry formula’s binding default resolves casilla 1039 to 0, and the operator-facing eligibility advisory surfaces the entitlement instead. A deducción’s failure mode is over-claim, so silence on an indeterminate unidad-familiar aggregate is the safe default.

Only the 2025 filing year is handled (the first-slice registry formula); other years return early. Idempotent: keys already present are not overwritten.

Return type:

None

Parameters:
  • fact_index (dict[str, UserProfileFactValue])

  • filing_year (int)

inject_derived_anualidades_eligibility_facts(fact_index, filing_year)

Inject the LIRPF art. 64/75 anualidades separate-escala eligibility flag.

Art. 64 grants judicial anualidades por alimentos a favor de los hijos the separate-escala régimen only to a payer “sin derecho a la aplicación por estos últimos del mínimo por descendientes previsto en el artículo 58”. This “no right to the mínimo por descendientes” fact is not derivable from any other casilla; it is projected here onto the synthetic Decimal key renta_family.anualidades_sin_minimo_descendientes_{year} the registry régimen predicate consumes.

Form-faithful default: filling casilla 0527 (anualidades por decisión judicial) implies the non-custodial payer without the mínimo, so the flag is 1 (eligible) unless custody is shared. When at least one eligible descendant has custodia_compartida = true the mínimo por descendientes is split 50/50, the payer retains it, and the régimen does NOT apply (flag 0).

Idempotent: an explicit fact already present is not overwritten. Only the revisions carrying the separate-escala régimen are handled.

Return type:

None

Parameters:
  • fact_index (dict[str, UserProfileFactValue])

  • filing_year (int)

inject_derived_minimo_descendientes_facts(fact_index, snapshot)

Inject the Art. 58/61 LIRPF mínimo por descendientes aggregates (casillas 0513/0514).

Reads the existing renta_family.descendiente.{n}.* facts, ranks every Art. 58.1-eligible descendant by birth_date, and computes two aggregates via minimo_descendientes_estatal() (a CCAA-agnostic birth-order-tranche aggregator despite its name — it takes the tranche amounts as caller-supplied parameters, never a hardcoded euro figure per aeat-schema-central-config):

  • the ESTATAL aggregate, from the revision’s renta-{year}-minimo- descendientes-{suffix}-{year} Art. 58 parameters, projected onto renta_family.descendientes_minimos_aggregate_{year} — the user-profile schema field the modelo-100-minimo-descendientes-engine ADR named as a dangling selector (declared, never populated, its former binding deleted in commit bc3b89594); this injector retires that gap rather than minting a new key. Feeds casilla 0513 via the registry binding renta-{year}-profile-minimo-descendientes-estatal.

  • the AUTONÓMICO aggregate, from the SAME estatal parameters UNLESS the filer’s declared tax_residence.ccaa has a wired divergent tranche table (Ley 22/2009 art. 46.1.a cedes this to each comunidad within a ±10% band; see _MINIMO_DESCENDIENTES_AUTONOMICO_CCAA_INFIXES), projected onto renta_family.descendientes_minimos_aggregate_autonomico_{year}. Feeds casilla 0514 via the registry binding renta-{year}-profile-minimo-descendientes-autonomico. A CCAA absent from the wired set mirrors the estatal aggregate exactly (the pre-existing default, matching the 0511/0512 mínimo-del-contribuyente precedent).

Always injects both keys (Decimal("0") for a profile with no eligible descendant) so a genuinely childless filer’s casillas resolve to the legally correct zero rather than an unresolved binding failing the calculation outright. Idempotent per key: a key already present (an explicit profile fact written by an older tooling version) is not overwritten. Only the 2020-2025 filing years are handled; other years are ignored until the engine is extended.

Return type:

None

Parameters:
profile_fact_index(record, schema)

Build a selector -> typed-value index covering both selector forms.

A profile binding’s selector resolves either as the canonical section.field fact path (profile_key form) or as a schema model_selector alias (profile_model + field form). The index exposes each non-null fact under its canonical path AND under every model_selector the schema declares for it, so both selector forms find the value.

Values are preserved as their original UserProfileFactValue type (bool, Decimal, date, str, …) so that downstream channel routing can branch on the concrete Python type rather than re-parsing a str(value) rendering.

Return type:

dict[str, TypeAliasType]

Parameters:
resolve_profile_binding_value(binding, fact_index)

Return the typed profile fact value for one profile binding, or None if absent.

Return type:

TypeAliasType | None

Parameters:
is_madrid_resident(fact_index)

Return whether tax_residence.ccaa names the Comunidad de Madrid.

Return type:

bool

Parameters:

fact_index (Mapping[str, UserProfileFactValue])

is_indeterminate_unidad_familiar(fact_index)

Return whether the filer’s unit is a tributación-conjunta or partnered case.

This is exactly the condition that makes the Madrid nacimiento/adopción over-claim guard fail-closed in _inject_derived_autonomic_deduccion_facts() (research F9 — no persisted spouse base imponible to evaluate the unidad-familiar 61.860 € límite). Shared with the verify-path D4 eligibility advisory so both surfaces agree on exactly which units are indeterminate.

Return type:

bool

Parameters:

fact_index (Mapping[str, UserProfileFactValue])

madrid_nacimiento_adopcion_candidate_weighted_count(fact_index, filing_year)

Return the prorrateo-weighted Madrid nacimiento/adopción eligible count.

Evaluates only the per-descendant window/cohabitation condition (DL 1/2010 arts. 4 y 18.1); it does NOT gate on CCAA, declaration type, or marital status — those over-claim guards are the caller’s responsibility. This is the shared candidate-count primitive: the injector uses it to populate the casilla-1039 binding for the determinable single/monoparental case, and the verify-path D4 advisory uses it to detect a would-be-eligible but indeterminate (conjunta/married) unit that should not silently resolve to zero.

Return type:

Decimal

Parameters:
  • fact_index (Mapping[str, UserProfileFactValue])

  • filing_year (int)