cadrumo.domain.calculations.registry.withholding_bindings module

Withholding row-set binding helpers.

Withholding-source bindings declared on a ModeloRevision are resolved from per-perceptor withholding observations into scalar values or row outputs.

resolve_retencion_clave(value, effective_date, *, modelo=None, authority=None)[source]

Project one raw clave through the selected registry vocabulary.

Membership, canonical value, and model applicability are all authored in the withholding catalogue. The projection intentionally fails closed when any declaration is missing or inconsistent; it never turns an unknown raw token into a core value.

Return type:

RetencionClave

Parameters:
class WithholdingObservation(**data)[source]

Bases: BaseModel

Per-perceptor retencion / ingreso-a-cuenta observation for modelo 190 / 193.

perceptor_tax_id is normalised to its canonical identity token on construction, so the identity the clave/subclave aggregations count is the identity the encrypted percepciones store keys by. Holding the raw declaration here split the two: the repository trimmed and uppercased the tax ID before hashing it into the object key, so two canonically-equal declarations were counted as two distinct percepciones while sharing one stored row, and the later write overwrote the earlier evidence.

Parameters:

data (Any)

source_id: str
perceptor_tax_id: TaxIdIdentityToken
country_code: CountryCodeAlpha2 | None

The party’s country, or None when the source stated none.

Nullable rather than defaulted to ES, because these forms carry the NON-RESIDENT population by construction – a perceptor on a withholding form is routinely foreign, and an attribution member can be – so a default silently declares a foreign party Spanish on a filing surface.

Absence propagates as an ABSENT KEY in the built row rather than as a value, so a binding that needs the country refuses with the shipped not-produced error naming itself. That is the visible failure the silent default replaced.

transaction_date: date
clave: RetencionClave
subclave: str
percibido_dinerario: Decimal

the design’s campo 11 excludes the incapacidad-laboral prestaciones, which file in their own block (255-281), so the operator records them in the incap facts.

Type:

The NON-incapacidad part of the row’s dineraria percepciones

percibido_especie: Decimal
retencion_practicada: Decimal
ingreso_a_cuenta: Decimal
province_code: str | None

Perceptor domicilio province code (01-52, 53 La Palma), or 98 for a Spanish IRPF contributor resident abroad, per the Modelo 190 record design’s own list.

Nullable rather than defaulted: the design’s 98 special case makes a default province a fabricated residence. Absence propagates as an ABSENT KEY in the built row, so a binding that needs the province refuses with the shipped not-produced error naming itself.

territorial_deduction_clave: int | None

1 when the payer applied the art. 68.4 deduction for Ceuta/Melilla rentas, 2 for the Isla de La Palma exceptional deduction, 0 otherwise. A retention-rate determination fact the payer’s own data carries; never derived from the province code.

Type:

Modelo 190 CEUTA O MELILLA clave

perceptor_birth_year: int | None

Perceptor birth year, only declared by the design for claves A, B (subclaves 01, 03, 04, 99) and C.

perceptor_situacion_familiar: int | None

Perceptor family-situation clave (1-3) per the design’s own relation, only declared for claves A, B (subclaves 01, 03, 04, 99) and C.

representative_tax_id: TaxIdIdentityToken | None

NIF of the perceptor’s legal representative, declared by the design only when the perceptor is under 14; every other row writes the design’s own spaces.

spouse_or_unit_titular_tax_id: TaxIdIdentityToken | None

NIF of the perceptor’s spouse (situacion familiar 2, claves A/B/C) or of the unidad de convivencia’s titular (clave L.29 with titular clave 2); spaces elsewhere.

disability_clave: int | None

Perceptor disability degree clave (0 none/<33%, 1 33-65%, 2 33-65% needing third-party help or reduced mobility, 3 >=65%), declared for claves A, B (subclaves 01, 03, 04, 99) and C.

contract_relation_clave: int | None

Contract-or-relation type clave (1 general, 2 under-a-year/artists, 3 other special dependent relations, 4 sporadic peonadas), declared for clave A only.

unit_convivencia_titular_clave: int | None

Whether the perceptor is the unidad de convivencia’s titular (1 yes, 2 no), declared for clave L.29 only.

geographic_mobility_clave: int | None

Art. 19.2.f) geographic-mobility deduction flag (1 entitled, 0 not), declared for clave A only.

ingreso_a_cuenta_repercutido: Decimal

Ingresos a cuenta efectuados on non-incapacidad especie payments that the payer repercutido to the perceptor (design positions 135-147).

accrual_year: int | None

Ejercicio de devengo (design positions 148-151), declared only for atrasos devengados in earlier exercises or reintegros of earlier-exercise amounts; every other row writes the design’s own zeros.

reducciones_aplicables: Decimal

Art. 18.2/18.3, DT 11/12 and art. 32.1 reductions the payer actually considered (design positions 171-183); the design’s own zeros when none.

gastos_deducibles: Decimal

Art. 19.2 a)-c) deductible expenses the payer considered to determine the retention rate (design positions 184-196); the design’s own zeros when none.

pension_compensatoria: Decimal

Annual compensatory pension to the spouse by judicial resolution (design positions 197-209); the design’s own zeros when none.

anualidades_alimentos: Decimal

Annual food annuities to children by judicial decision (design positions 210-222); the design’s own zeros when none.

descendants_under_3_total: int | None

Descendants under 3 (design position 223), counted per art. 58 m�nimo por descendientes rules; the design’s own zero when none.

descendants_under_3_whole: int | None

Of the position-223 descendants, those computed por entero (design 224).

descendants_rest_total: int | None

Remaining descendants (design positions 225-226).

descendants_rest_whole: int | None

Of the position 225-226 descendants, those computed por entero (227-228).

descendants_disabled_33_65_total: int | None

Disabled descendants 33-65% (design positions 229-230, art. 60.2).

descendants_disabled_33_65_whole: int | None

Of the 229-230 descendants, those computed por entero (231-232).

descendants_disabled_mobility_total: int | None

Disabled 33-65% descendants with reduced mobility or third-party help need (design positions 233-234).

descendants_disabled_mobility_whole: int | None

Of the 233-234 descendants, those computed por entero (235-236).

descendants_disabled_65_plus_total: int | None

Disabled descendants at 65% or more (design positions 237-238).

descendants_disabled_65_plus_whole: int | None

Of the 237-238 descendants, those computed por entero (239-240).

ascendants_under_75_total: int | None

Ascendants under 75 (design position 241, art. 59).

ascendants_under_75_whole: int | None

Of the position-241 ascendants, those computed por entero (242).

ascendants_75_plus_total: int | None

Ascendants at 75 or more (design position 243).

ascendants_75_plus_whole: int | None

Of the position-243 ascendants, those computed por entero (244).

ascendants_disabled_33_65_total: int | None

Disabled ascendants 33-65% (design position 245).

ascendants_disabled_33_65_whole: int | None

Of the position-245 ascendants, those computed por entero (246).

ascendants_disabled_mobility_total: int | None

Disabled 33-65% ascendants with reduced mobility or third-party help need (design position 247).

ascendants_disabled_mobility_whole: int | None

Of the position-247 ascendants, those computed por entero (248).

ascendants_disabled_65_plus_total: int | None

Disabled ascendants at 65% or more (design position 249).

ascendants_disabled_65_plus_whole: int | None

Of the position-249 ascendants, those computed por entero (250).

first_child_compute: int | None

How the first child was computed for the retention rate (design position 251): 1 por entero, 2 por mitad.

second_child_compute: int | None

How the second child was computed (design position 252).

third_child_compute: int | None

How the third child was computed (design position 253).

housing_loan_communication_clave: int | None

Whether the perceptor communicated vivienda-habitual loan amounts at some point in the exercise (design position 254, art. 86.1 RIRPF last paragraph): clave 0 never applied, 1 applied – both are recorded facts, never defaults.

incapacity_cash_perception: Decimal
incapacity_cash_withholding: Decimal
incapacity_kind_value: Decimal
incapacity_kind_ingreso_a_cuenta: Decimal
incapacity_kind_repercutido: Decimal
complemento_infancia_clave: int | None

Whether any mensualidad of the L.29 prestacion included the IMV complemento de ayuda para la infancia (design position 322): clave 1 included, 2 not – both are recorded facts, never defaults.

foral_retention_estatal: Decimal
foral_retention_navarra: Decimal
foral_retention_araba: Decimal
foral_retention_gipuzkoa: Decimal
foral_retention_bizkaia: Decimal
emerging_stock_excess_clave: int | None

Whether the row’s in-kind percepciones include emerging-company stock over the art. 42.3.f) exempt amount (design position 388): clave 1 yes, 0 the rest of the in-kind retributions – both recorded facts, declared only for clave A and only when the especie block has content.

startup_fund_rendimientos_clave: int | None

Whether the row’s payments include D.A. 53a fund-management rendimientos del trabajo (2025-edition design position 389): clave 1 yes, 0 the rest. The cumplimentacion trigger is the payer’s own composition judgment, so the clave is recorded-when-applicable: spaces when absent, refused outside clave A.

pension_prestacion_jubilacion: int | None

Whether the clave B.01 prestaciones include jubilacion (2025-edition design position 390): each type’s 0/1 flag is always recorded for B.01.

pension_prestacion_viudedad: int | None

Whether the clave B.01 prestaciones include viudedad (position 391).

pension_prestacion_incapacidad: int | None

Whether the clave B.01 prestaciones include incapacidad permanente total o parcial (position 392).

pension_prestacion_no_contributiva: int | None

Whether the clave B.01 prestaciones include pensiones no contributivas por invalidez o jubilacion (position 393).

pension_prestacion_resto: int | None

Whether the clave B.01 prestaciones include the remaining non-exempt art. 17.2.a).1a prestaciones (position 394).

perceptor_mediador_flag: str | None

Modelo 193 ‘X’ flag (position 76) marking a perceptor that is itself a mediator entity paying by proxy, declared only for claves A, B and D.

clave_codigo: int | None

Modelo 193 clave codigo (position 79) identifying what the codigo emisor and ISIN fields carry, always recorded for claves A, B and D (clave 4 is the general case).

codigo_emisor: str | None

Modelo 193 codigo emisor (positions 80-91), declared only for claves A, B and D: the issuer’s NIF for clave codigo 1/4, empty for 2, ZXX country code for 3.

naturaleza: str | None

the two-digit subclave of the clave de percepcion, always recorded.

Type:

Modelo 193 naturaleza (positions 93-94)

pago: int | None

who paid, always recorded for claves A, B and D (1 emisor, 2/3/4 mediador, 5 other mediation).

Type:

Modelo 193 pago (position 95)

tipo_codigo: str | None

what the codigo cuenta field holds, always recorded for claves A, B and D.

Type:

Modelo 193 tipo codigo (position 96)

codigo_cuenta: str | None

Modelo 193 codigo cuenta valores / numero operacion prestamo (positions 97-116), recorded only when a financial entity manages the valores.

pendiente_flag: str | None

Modelo 193 ‘X’ flag (position 117) marking percepciones devengadas but not yet paid because the holder did not claim them.

tipo_percepcion: int | None

1 dinerarias, 2 en especie, always recorded.

Type:

Modelo 193 tipo de percepcion (position 122)

reducciones: Decimal

Modelo 193 art. 26.2 reductions (positions 139-151) applied when the perceptor is an IRPF contribuyente; the design’s own zeros when none.

base_retenciones: Decimal
porcentaje_retencion: Percentage

Modelo 193 retention/ingreso-a-cuenta percentage applied (positions 165-168), generally 19 with the design’s clave-naturaleza specific rates; the last percentage applied when several were used.

penalizaciones: Decimal

Modelo 193 penalizaciones (positions 182-192), declared only for claves B and D; the design’s own zeros when none.

isin_code: str | None

Modelo 193 codigo ISIN (positions 193-204, 2025 edition), recorded when clave codigo is 2 or 4.

naturaleza_declarante: str | None

Modelo 193 perceptor-record naturaleza del declarante (position 208): ‘S’ when the declarante is outside the design’s categories, blank otherwise – recorded-when-applicable.

fecha_inicio_prestamo: str | None

Modelo 193 fecha de inicio del prestamo (positions 209-216, AAAAMMDD), declared only when tipo codigo is ‘P’.

fecha_vencimiento_prestamo: str | None

Modelo 193 fecha de vencimiento del prestamo (positions 217-224, AAAAMMDD), declared only when tipo codigo is ‘P’.

compensaciones: Decimal

Modelo 193 compensaciones (positions 225-236), declared only for prestamo de valores rows; the design’s own zeros when none.

garantias: Decimal

Modelo 193 garantias (positions 237-248), declared only for prestamo de valores rows; the design’s own zeros when none.

nif_pagador_anterior: TaxIdIdentityToken | None

Modelo 193 NIF of the immediately previous payer in the payment chain (positions 322-330), obligatory when pago is 2-5 except where the previous payer is foreign without a Spanish NIF – recorded-when-applicable.

fecha_devengo: str | None

Modelo 193 fecha de devengo (positions 331-338, DDMMAAAA), declared only for clave A.

clave_mercado: str | None

Modelo 193 clave de mercado (position 339), always recorded for claves A, B and D.

numero_orden: int | None
class WithholdingProvider(**data)[source]

Bases: BaseModel

The per-perceptor withholding provider: one retencion fact over a clave set.

Parameters:

data (Any)

kind: Literal[BindingSourceKind.WITHHOLDING]
fact: _WithholdingFact
claves: tuple[str, ...]
row_field: _WithholdingRowField | None
grouping: WithholdingGrouping | None
record: str | None
data_type: ExportFieldDataType | None

Scalar type of the value this row field contributes to the export.

The same fact BindingRowExportSelector.data_type carries; declared here so the selector model admits the key, since a source-family selector is validated whole against its own strict model. Optional while the families adopt it.

validate_withholding_binding_selector_shape(binding)[source]

Validate the withholding fact/op invariants for snapshot build.

The provider shape itself is the union member’s own gate, already enforced when the row was constructed; what is left to lift to build time is the cross-invariant between the declared fact and the aggregation operation.

Return type:

list[str]

Parameters:

binding (BindingDefinition)

distinct_percepcion_keys(observations)[source]

Return the distinct (perceptor, clave, subclave) percepci�n keys.

Modelo 190’s “n�mero total de percepciones” counts DISTINCT type-2 “registro de perceptor” records (AEAT Dise�o de Registros), not distinct NIFs: one perceptor paid under two claves files two percepciones. The key is therefore clave-bearing.

Shared so the bound percepcion_count fact and the per-clave breakdown count the same thing. Grouping by clave and keying on (perceptor, subclave) yields the same per-clave counts as this key does across a scope, which is why the breakdown can build on it.

Return type:

set[tuple[str, RetencionClave, str]]

Parameters:

observations (Iterable[WithholdingObservation])

percibido_total(observations)[source]

Sum percibido dinerario, en especie, and both incapacidad-laboral parts.

The base amount facts carry the NON-incapacidad part (the design field’s own meaning); the incapacidad blocks are separate parts the design files at 255-321, so the row’s full percibido total is the four magnitudes together. Shared by the bound percibido_sum fact and the per-clave breakdown so a change to what counts as percibido reaches both.

Return type:

Decimal

Parameters:

observations (Iterable[WithholdingObservation])

retencion_total(observations)[source]

Sum retenci�n practicada, ingreso a cuenta, and the incap retentions.

The base amount facts carry the NON-incapacidad part; the retentions on the incapacidad-laboral percepciones file in their own design block, so the row’s full retenido total is the four magnitudes together. Shared by the bound retencion_sum fact and the per-clave breakdown so a change to what counts as retenido reaches both.

Return type:

Decimal

Parameters:

observations (Iterable[WithholdingObservation])

resolve_withholding_binding_values(revision, observations, *, authority=None)[source]

Resolve scalar withholding-source bindings into Decimal aggregates.

The ModeloRevision contributes scalar withholding bindings; row producer bindings are handled by resolve_withholding_binding_row_values.

Return type:

dict[TypeAliasType, Decimal]

Parameters:
resolve_withholding_binding_row_values(revision, observations)[source]

Resolve row-producer withholding bindings into per-row indexed values.

One row per observation, ordered by perceptor, clave, subclave and source so the row index is stable across reads. An unset field (None or blank text) is left out of the row rather than filled, so a required value stays visibly absent.

Parameters:
Return type:

dict[tuple[TypeAliasType, int], Decimal | str | int | bool]

class WithholdingClaveBreakdown(**data)[source]

Bases: BaseModel

One per-clave row of the Modelo 190 retenci�n reconciliation breakdown.

Groups the per-perceptor-clave withholding detail (the AEAT Dise�o de Registros type-2 records) by clave de percepci�n and carries that clave’s distinct percepci�n count and percibido / retenci�n magnitudes. The figures reuse the scalar withholding-fact arithmetic (resolve_withholding_binding_values()): percepcion_count is the distinct (perceptor, clave, subclave) count, percibido_total is percibido_dinerario + percibido_especie, and retencion_total is retencion_practicada + ingreso_a_cuenta. It is a projection of the same store the percepciones-count resolver reads, so the operator can reconcile the annual Modelo 190 totals against the individual Modelo 111 quarterly filings clave by clave.

Parameters:

data (Any)

clave: RetencionClave
percepcion_count: NonNegativeInt
percibido_total: Decimal
retencion_total: Decimal
aggregate_withholding_by_clave(observations)[source]

Project withholding observations into WithholdingClaveBreakdown rows.

Pure function: identical observations in any order yield the same tuple, sorted by clave. No new aggregation is introduced � each magnitude is produced by the same distinct_percepcion_keys() / percibido_total() / retencion_total() helper that resolve_withholding_binding_values() uses for the corresponding bound fact, so the breakdown cannot drift from the facts that feed the calculation. The clave is the grouping axis here and part of the key there; grouping first and counting the clave-bearing key within each group gives the same per-clave totals.

That sentence used to be a claim rather than a guarantee: this function re-implemented all three formulas inline. An operator reconciles the annual Modelo 190 against the four quarterly Modelo 111 filings from this breakdown, so a formula changed in the resolver alone would have shown up as a reconciliation mismatch with nothing pointing at the cause.

Return type:

tuple[WithholdingClaveBreakdown, ...]

Parameters:

observations (Iterable[WithholdingObservation])