cadrumo.domain.calculations.registry.ledger_renta_income_bindings module

Canonical Renta income ledger aggregation binding family.

validate_ledger_renta_income_aggregation_binding_definition(binding)[source]

Validate a ledger_renta_income_aggregation binding definition.

Return type:

None

Parameters:

binding (BindingDefinition)

class RentaIncomeObservationProtocol(*args, **kwargs)[source]

Bases: Protocol

Structural protocol for actividad-económica income observations.

The registry only needs these attributes to resolve ledger_renta_income_aggregation bindings; the full RentaIncomeObservation satisfies this protocol without any explicit declaration.

property transaction_id: str

Return the ledger row this observation was projected from.

Declared because a consumer samples these ids to name the offending rows in an ungrounded-income diagnostic. Leaving it off the protocol let a conforming implementation omit it and fail there at runtime.

property target_casilla_id: CasillaId

Return the declaration casilla receiving the income aggregate.

property gross_amount: Decimal

Return the observation’s gross or cash-received amount.

property taxable_base_amount: Decimal | None

Return the declared IVA-exclusive taxable base, when available.

property withheld_amount: Decimal

Return the IRPF amount withheld at source for the observation.

property grounding: LedgerIncomeGrounding

Return the substrate-grounding marker for the income observation.

resolve_ledger_renta_income_aggregation_binding_values(revision, observations)[source]

Resolve every ledger_renta_income_aggregation binding on revision.

The fact declared in the binding selector controls which field is summed: "ingresos_integros_sum" → observation.taxable_base_amount when declared, else observation.gross_amount (per-observation fallback); "cash_received_sum" → observation.gross_amount; "taxable_base_sum" → observation.taxable_base_amount (a base-less row contributes nothing); "withheld_amount_sum" → observation.withheld_amount. Delegates the filter/aggregate skeleton to resolve_ledger_family_binding_values(), shared by every ledger family resolver.

Parameters:
Return type:

dict[TypeAliasType, Decimal]

unsupported_ledger_renta_income_observations(revision, observations)[source]

Return the RentaIncomeObservationProtocol rows no binding on revision can consume.

Delegates the screen to unsupported_ledger_family_observations() — see that function for the shared fail-closed contract (why an unmatched observation is a modelling gap, not a legitimate zero). This family’s own contribution is narrow: the target_casilla_id match predicate (reused from the resolver’s _renta_income_build_matcher) and a false-fire guard that excludes an observation whose declarable amount — max(gross_amount, taxable_base_amount) when a base is declared, gross_amount otherwise — is zero. No extra_exclusion.

Parameters:
Return type:

tuple[RentaIncomeObservationProtocol, ...]

Returns:

Tuple of observations whose non-zero income is selected by no ledger_renta_income_aggregation binding.

class UngroundedRentaIncome(facts, observations)[source]

Bases: NamedTuple

Base-less income rows that a base-reading binding still consumes.

facts is the set of base-reading facts the revision actually declares, so a caller can describe the consequence precisely rather than guessing: ingresos_integros_sum means the listed rows contributed bank cash in place of a base, taxable_base_sum means they contributed nothing. observations are the contributing rows, in input order.

Empty observations means every consumed row declared its substrate; empty facts means the revision declares no base-reading income binding, in which case observations is empty too.

Parameters:
facts: frozenset[str]

Alias for field number 0

observations: tuple[RentaIncomeObservationProtocol, ...]

Alias for field number 1

ungrounded_ledger_renta_income_observations(revision, observations)[source]

Return the base-less rows a base-reading income binding on revision consumes.

The companion to unsupported_ledger_renta_income_observations(), for the opposite failure: that screen catches a row NO binding consumes, this one catches a row a binding DOES consume but without the substrate the binding’s fact assumes. Both are no-silent-under-declaration screens; neither changes a value.

A row reaches an income casilla through declared substrate or through the cash fallback, and only the row’s LedgerIncomeGrounding marker distinguishes them — this screen keys on that marker, never on taxable_base_amount is None, so the grounding fact has exactly one definition. Rows are screened against the target_casilla_id of the base-reading bindings only, so a revision whose income bindings all read cash or withholdings reports nothing.

The caller surfaces the result as a NON-BLOCKING advisory: the fallback is deliberately kept (dropping an untagged income row would under-declare by the whole row, strictly worse than mis-measuring it), so what this screen buys is visibility, not exclusion.

Parameters:
Return type:

UngroundedRentaIncome

Returns:

An UngroundedRentaIncome pairing the declared base-reading facts with the base-less rows those bindings consume.

unrouted_ledger_renta_income_quantities(revision, observations)[source]

Return independent quantities the rows carry that no binding on revision draws.

The third renta-income screen, and it watches an axis the other two cannot see. unsupported_ledger_renta_income_observations() asks whether a ROW is selected by some binding; ungrounded_ledger_renta_income_observations() asks whether a consumed row carried the substrate its binding’s fact assumes. Both key on the row. This one keys on the QUANTITY.

The distinction is load-bearing because every RentaIncomeObservation is built with target_casilla_id = "01" regardless of which fact a binding reads off it (see the note in the M130 0003-m130-income-cumulative.toml bindings fragment). A row therefore matches the income bindings on that key and counts as consumed — while a SECOND, independent quantity it carries, the retención suffered, reaches nothing at all. Drop the withheld_amount_sum binding from a revision and the row-level screen stays silent: every row is still consumed, for its income. The taxpayer’s whole retención credit disappears with a clean screen on both sides, which is precisely the silent under-declaration the screens exist to prevent.

Alternative MEASURES of one quantity are excluded (_RENTA_INCOME_ALTERNATIVE_MEASURE_FACTS): a revision picks one income measure and omitting the other two is correct, so demanding all three would fire on every revision and train the operator to ignore the advisory. Only a genuinely independent quantity is screened.

Reports nothing when the rows carry nothing: a taxpayer who suffered no retención has a zero total, which is a legitimate zero rather than a modelling gap, and this screen must not manufacture a finding from it.

Parameters:
Return type:

tuple[UnroutedLedgerQuantity[RentaIncomeObservationProtocol], ...]

Returns:

One UnroutedLedgerQuantity per uncovered non-zero quantity, ordered by fact name. Empty when every quantity the rows carry is drawn.

validate_ledger_renta_income_aggregation_binding(binding)[source]

Validate a ledger_renta_income_aggregation binding at registry-build time.

Accumulating list[str] validator over LedgerRentaIncomeProvider; runs the fact/aggregation-op invariant at build time through invariant_diagnostics(), whose raise-style body is validate_ledger_renta_income_aggregation_binding_definition().

Return type:

list[str]

Parameters:

binding (BindingDefinition)

class LedgerRentaIncomeProvider(**data)[source]

Bases: BaseModel

Validated form of a ledger_renta_income_aggregation binding selector.

modelo names the declaration series the aggregation feeds: M130’s cumulative quarter, M100’s annual ejercicio, or M131’s agrarian quarterly volumen de ingresos. target_casilla_id is the casilla that receives the total. The M131 leg is NOT cumulative – art. 110.1.c) fixes its payment on el volumen de ingresos del trimestre – and its projection applies two filters the others do not: the art. 110.1.c) activity set, and the exclusion of subvenciones de capital and indemnizaciones. Both live in the projection rather than in this selector because they decide which rows EXIST as observations, not which observations a binding claims.

fact is REQUIRED and carries no default. Each accepted value names a different legal measure of the same rows, so a default would silently pick a legal claim on the taxpayer’s behalf: cash_received_sum is the raw bank-credited amount — net of any retención practicada and possibly IVA-inclusive — while ingresos_integros_sum is the ingresos íntegros the M130 instructions actually ask for. An omitting binding fails registry validation naming the accepted set rather than inheriting the weakest measure.

fact controls which aggregation path is applied:

  • "ingresos_integros_sum" sums the fiscally computable ingreso per observation: taxable_base_amount (the IVA-exclusive base imponible) when the transaction carries an explicit IVA tagging, falling back to gross_amount when no base is declared — the canonical ingresos-íntegros path feeding casilla "01". The AEAT M130 instructions define casilla 01 as the “ingresos íntegros fiscalmente computables”; IVA repercutido is collected on behalf of Hacienda and is not computable income, so a tagged invoice contributes its base, never its IVA-inclusive gross.

  • "cash_received_sum" sums RentaIncomeObservation.gross_amount (raw.amount or its business fraction) across the window, ignoring any declared taxable base. Named for what it computes: the cash the bank credited, which is neither gross of retención nor IVA-exclusive and is therefore NOT the ingresos-íntegros measure.

  • "taxable_base_sum" sums RentaIncomeObservation.taxable_base_amount (the IVA-exclusive base imponible). Observations whose taxable_base_amount is None declare no base and contribute nothing to this sum; ungrounded_ledger_renta_income_observations() surfaces every such row so the omission is visible rather than silent.

  • "withheld_amount_sum" sums the IRPF amount withheld at source from net-paid professional receipts.

Parameters:

data (Any)

kind: Literal[BindingSourceKind.LEDGER_RENTA_INCOME_AGGREGATION]
modelo: Modelo
target_casilla_id: CasillaId
fact: LedgerIncomeFactValue