cadrumo.application.modelo.profile_binding module¶
Application-owned profile-sourced Modelo 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.
The ModeloRevision on that snapshot is also what decides whether a
derived-fact injector runs at all: the injectors are gated on the revision
declaring a binding that consumes their output, so extending coverage to a
new filing year is registry work rather than a code edit.
Channel selection is the load-bearing decision. The registry runtime
resolves profile bindings through four engine channels:
date_binding_values for date operands, enum_binding_values for
dispatch keys, boolean_binding_values for truth values, and
Decimal-valued binding_values for numeric operands. The boolean channel
is selected from the binding’s OWN declared value contract
(value.channel = "boolean"), never from the Python type of the fact that
turned up: a truth value routed by type would follow whatever the profile
happened to store, which is how a yes/no fact came to be labelled as currency.
The remaining three are determined by the consumer shape:
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. A
calculation-only typed enum also uses the enum 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, translated_message=None)[source]¶
Bases:
ModeloErrorRaised when a profile-sourced binding cannot be resolved for a calculation.
- Parameters:
- Return type:
None
- code¶
- inject_ordinary_work_maritime_facts(fact_index)[source]¶
Resolve the maritime exemption inputs of a taxpayer with no maritime classification.
The profile schema states that an unclassified worker receives the ordinary treatment of employment income, so neither the art. 7.p LIRPF nor the art. 75 Ley 19/1994 exemption reaches them: the exemption path is not REBECA and every exempt-income input is a proven zero. A taxpayer classified as a maritime worker is left untouched, so an incomplete maritime profile still refuses the calculation instead of borrowing these values. Facts already present are never overwritten.
- inject_derived_marriage_facts(fact_index, filing_year)[source]¶
Inject computed matrimonio-sobrevenido integers into fact_index in-place.
When
renta_taxpayer.marriage_dateis present as adate-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’sfiling_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. The two month figures are injected asDecimalcounts. The full-year fact is a yes/no question and is injected as a realbool, which is what its binding’s boolean value contract requires: aDecimal("0")standing in for “not married the whole year” is indistinguishable from a zero-valued count once it reaches the Decimal channel.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.
- class MaternidadMesesResolution(pairs, withheld_indices, ceilings_resolved, declares_meses, cotizaciones_ceiling_inexpressible=False, alta_posterior_hijos=frozenset({}))[source]¶
Bases:
objectWhat the profile contributes to the Art. 81.1 deducción, and what it does not.
Carries the withheld indices and the ceilings verdict alongside the granted pairs because all three come from one evaluation of one predicate over one reconstruction of the record. Returning only the pairs would force the caller to ask again for the other two, and a second evaluation is how a disclosure comes to describe a withholding the grant path never made.
- Parameters:
-
cotizaciones_ceiling_inexpressible:
bool¶ True when the filing year predates 2023 and the engine cannot apply the ceiling.
Until 2022 Art. 81.1 limited the deducción to the mother’s Social Security cotizaciones devengadas in the period. Neither the registry nor the profile schema can express that figure for those years – the binding and the fact are both 2024-pinned – so the deducción is withheld rather than granted un-ceilinged, and the caller discloses it.
-
alta_posterior_hijos:
frozenset[str]¶ hijo_idvalues frompairsthat also carry the Art. 81.1 post-birthalta increment (
maternidad_alta_posterior_increment_applies()) for the resolved filing year. Ahijo_idabsent frompairsis never a member here even if its record declares a completion month, because a withheld pair means the ordinary predicate already excludes the descendant, and the increment can only ever add to a contributing pair, never grant one.
- resolve_maternidad_meses(record, snapshot, *, schema=None, operation)[source]¶
Resolve the Art. 81.1 maternidad months a profile record contributes.
Owns the fact-index projection as well as the predicate, so the calculate path asks one question of this module rather than assembling the inputs itself. The index is built exactly as profile-sourced bindings build it, so the Art. 81.1 eligibility question is asked of the same facts every other Art. 58 question is asked of.
ceilings_resolvedisFalsewhen the revision on theRegistrySnapshotsnapshotdoes not declare the Art. 58.1 / Art. 61 norma 2ª parameters. The deducción is then withheld rather than granted against a fabricated ceiling — the same refusal the mínimo aggregate makes, for the same reason — and the caller discloses it rather than letting a declared figure vanish.- Return type:
- Parameters:
record (object)
snapshot (RegistrySnapshot)
schema (ProfileSchemaDefinition | None)
operation (PinnedAuthorityOperation)
- second_entitled_filer_indicated(fact_index)[source]¶
Derive whether a second contribuyente is also entitled to the mínimo.
Art. 61 norma 1ª prorates whenever two or more contribuyentes hold the right to the same descendant’s mínimo. Whether a second filer claims is not a fact about the descendant, so it is derived here from signals the profile already carries rather than demanded as new operator input.
An INDIVIDUAL return by a partnered filer IS prorated, because the other progenitor is a separate contribuyente entitled to the same descendant — the ordinary two-parent household this derivation exists to correct.
A tributación CONJUNTA return turns on WHO the unidad familiar contains, which LIRPF art. 82.1 makes a function of marriage:
MARRIED (art. 82.1.1ª) — both progenitores are inside the one unit, it files once, and the mínimo is applied once within it. There is no second contribuyente to share with, so no prorrateo.
NOT married (art. 82.1.2ª) — the unit is ONE progenitor plus the minor children, and both progenitores cannot form a single unit at all. The other progenitor therefore remains a separate entitled contribuyente and norma 1ª still prorates, EVEN THOUGH this return is conjunta.
Collapsing those two into “conjunta is never prorated” over-grants the mínimo for every unmarried cohabiting couple, which is an under-declaration of the tax. The AEAT Renta manual’s Capítulo 14 worked examples print both outcomes and disagree by 2.550 euros on the same three children.
Returns
Falsefor an unpartnered filer and whenever the signals are absent, which claims the full mínimo; the caller raises a visible advisory whenever this derivation decided the factor, so a wrong inference is correctable rather than silent.
- inject_derived_minimo_descendientes_facts(fact_index, snapshot, *, context=None, operation)[source]¶
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 bybirth_date, and computes two aggregates viaminimo_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-registry-authority-flow):the ESTATAL aggregate, from the revision’s
renta-{year}-minimo- descendientes-{suffix}-{year}Art. 58 parameters, projected ontorenta_family.descendientes_minimos_aggregate_{year}— a user-profile schema field that was previously a dangling selector (declared, never populated); this injector retires that gap rather than minting a new key. Feeds casilla 0513 via the registry bindingrenta-{year}-profile-minimo-descendientes-estatal.the AUTONÓMICO aggregate, from the SAME estatal parameters UNLESS the filer’s declared
tax_residence.ccaahas 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 ontorenta_family.descendientes_minimos_aggregate_autonomico_{year}. Feeds casilla 0514 via the registry bindingrenta-{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. Computes ALWAYS: both keys are declared derived, so a value present at either can only be stale or hand-planted, and deferring to it silently substituted an operator’s figure for the Art. 58/61 computation. The write is to the ephemeral per-calculation index and never persists.No filing-year gate: the parameter-presence checks below already refuse a revision that does not declare the full tranche and threshold tables, which is the same ground the former year frozenset covered but derived from the registry rather than restated as a Python constant.
Core types:
RegistrySnapshot.- Return type:
- Parameters:
snapshot (RegistrySnapshot)
context (FamilyFactResolutionContext | None)
operation (PinnedAuthorityOperation)
- inject_derived_anualidades_eligibility_facts(fact_index, snapshot, *, context=None, operation)[source]¶
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 = truethe mínimo por descendientes is split 50/50, the payer retains it, and the régimen does NOT apply (flag 0).Eligibility here is the SAME Art. 58.1 / Art. 61 predicate the two aggregates use, so a descendant excluded by the rentas ceiling or by the norma 2ª own-return rule generates no mínimo, leaves the payer legally sin derecho, and correctly RESTORES the separate-escala régimen. Before the predicate carried those conditions this flag read
0for such a descendant and denied a régimen the payer was entitled to — the one gap in this campaign that over-taxes rather than under-declares.Computes ALWAYS: the path is declared derived, so a value present there can only be stale or hand-planted, and deferring to it silently decided a régimen question the law owns. Only the revisions carrying the separate-escala régimen are handled, identified by a declared consuming binding rather than by a hardcoded filing-year set.
THE OTHER HALF OF THE ART. 58 DEPENDENCY INCOMPATIBILITY, stated here because the two rules are one pair and landing one half of a pair is this campaign’s most repeated defect. This flag says “the payer has no right to the mínimo, so the separate escala applies”; the dependency assimilation says “a non-cohabiting supporter DOES hold the mínimo”. Both cannot be true of the same descendant.
They cannot collide today, and by construction rather than by luck. The assimilation requires the filer to declare NO positive anualidades, while this régimen exists only for a filer who pays them. So wherever this flag can be 1, the assimilation is already suppressed for every descendant, and the eligibility evaluated below therefore passes
dependencia_assimilation_available=Falseexplicitly rather than relying on the parameter’s default - an explicit False documents the reasoning at the call site, where the next reader of this function will be.When per-child attribution of anualidades lands, that reasoning expires: a filer could then pay anualidades for one child and assimilate another, and this flag would have to be evaluated per descendant rather than once. Whoever lands that attribution must revisit this function in the same change.
Core types:
RegistrySnapshot.- Return type:
- Parameters:
snapshot (RegistrySnapshot)
context (FamilyFactResolutionContext | None)
operation (PinnedAuthorityOperation)
- is_madrid_autonomic_deduccion_filing_year(filing_year, *, context=None, operation)[source]¶
Return whether the registry declares the Madrid deduction for filing_year.
- Return type:
- Parameters:
filing_year (int)
context (FamilyFactResolutionContext | None)
operation (PinnedAuthorityOperation)
- is_madrid_resident(fact_index)[source]¶
Return whether
tax_residence.ccaanames the Comunidad de Madrid.
- is_indeterminate_unidad_familiar(fact_index)[source]¶
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.
- madrid_nacimiento_adopcion_candidate_weighted_count(fact_index, filing_year, *, context=None, operation)[source]¶
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:
- Parameters:
filing_year (int)
context (FamilyFactResolutionContext | None)
operation (PinnedAuthorityOperation)
- inject_derived_autonomic_deduccion_facts(fact_index, filing_year, *, context=None, operation)[source]¶
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 existingrenta_family.descendiente.{n}.*facts andtax_residence.ccaaand 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 registry-declared filing year is handled (the first-slice registry formula); other years return early. Idempotent: keys already present are not overwritten.
- Return type:
- Parameters:
filing_year (int)
context (FamilyFactResolutionContext | None)
operation (PinnedAuthorityOperation)
- resolve_profile_binding_channels(bindings, fact_index, *, caller_binding_ids, formula_date_consumed, enum_bindings, effective_date=None, operation)[source]¶
Resolve each selected binding into the Decimal / enum / date / boolean channels.
- Return type:
_ResolvedBindingChannels- Parameters:
bindings (tuple[BindingDefinition, ...])
caller_binding_ids (frozenset[BindingId])
formula_date_consumed (frozenset[BindingId])
enum_bindings (frozenset[BindingId])
effective_date (date | None)
operation (PinnedAuthorityOperation)
- profile_resolved_binding_ids(resolution)[source]¶
Return the binding ids a profile resolution actually satisfied.
CalculationSourceResolutionis the shared envelope for every source resolver and carries eight value channels; a profile resolution populates exactly four of them. Which four is a fact about this resolver, so it is stated here once rather than re-encoded by each consumer – the Modelo binding-readiness gate and the operator state projection previously open-coded the same union independently, and a fourth channel would have been picked up by whichever site was edited.- Parameters:
resolution (
CalculationSourceResolution) – The result returned byresolve_profile_sourced_bindings().- Return type:
frozenset[TypeAliasType]- Returns:
The binding ids the profile satisfied, across all populated channels.
- resolve_profile_sourced_bindings(snapshot, *, bucket_id, profile_record=None, caller_binding_ids=frozenset({}), schema=None, operation)[source]¶
Resolve every
source = "profile"binding the revision declares.- Parameters:
snapshot (
RegistrySnapshot) – TheRegistrySnapshotwhose 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) – OptionalUserProfileRecordoverride 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.operation (
PinnedAuthorityOperation) – Caller-held pinned authority operation for every governed schema, vocabulary, and catalogue lookup in this resolution.
- Return type:
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, date, or boolean channel: boolean from the binding’s own declared value contract, the other three from the consuming formula, bound numeric casilla, or calculation-only selector with no export address.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
CalculationSourceResolutionwith resolved binding values split across the Decimal, enum, date, and boolean engine channels and aCalculationSourceProvenancerow 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.