aeat.application.modelo._taxation_comparison module

Conjunta vs. individual taxation comparison for Modelo 100 (IRPF).

Runs the registry engine twice – once with declaration_type=2 (tributación conjunta, Art. 82-84 LIRPF) and once with declaration_type=1 (tributación individual) – over identical casilla inputs and profile bindings, then surfaces the cuota differential so married couples can pick the lower-tax regime.

This is a pure, ephemeral operation: no work unit is required and no revision is persisted. The caller supplies all inputs explicitly via a RegistrySnapshot; the registry formula engine evaluates both paths and returns a typed TaxationComparisonResult.

Scope honesty (ADR 2026-07-01-tributacion-conjunta-individual-adr): the individual run reuses the single input set assembled for the unidad familiar and only flips declaration_type to 1, so it faithfully models a single-earner household. It does not compute two separate spouse returns; a genuine two-earner individual comparison requires a per-spouse income axis that is absent at HEAD (the deferred second slice). Every result therefore carries individual_branch_single_earner_only and an individual_branch_caveat so no surface presents the individual figure as authoritative for a two-earner couple.

The comparison is snapshot-grounded. It resolves the result casillas by their declared semantic roles, refuses ambiguous role matches before choosing a CasillaId, and requires the revision’s profile-declaration-type binding so the stored profile value can be replaced independently for each run.

compare_taxation_for_work_unit() is the high-level entry point for CLI use: it resolves the registry snapshot and profile bindings from an existing work unit, deliberately excludes the stored declaration-type value, and delegates to compare_taxation_modes().

See also

_semantic_role_resolution:

Provides the canonical semantic-role-to-casilla resolver and ambiguity refusal used for the cuota resultante and cuota diferencial roles.

_binding_resolution:

Supplies the profile-bound values used by the work-unit entry point.

_work_addressing:

Resolves natural or exact work addresses before CLI comparison.

INDIVIDUAL_BRANCH_SINGLE_EARNER_CAVEAT

Honesty scope caveat for the individual filing branch (ADR 2026-07-01-tributacion-conjunta-individual-adr). The individual run reuses the single input set assembled for the unidad familiar and merely flips declaration_type to 1, so it faithfully models only a single-earner household. It does NOT compute two separate spouse returns (each on that spouse’s own income); a genuine two-earner individual comparison requires a per-spouse income attribution axis that does not exist at HEAD. Surfacing this statement keeps the comparator from presenting an unfaithful two-earner figure as authoritative (no-silent-under-declaration / aeat-safety-legal-gates).

class TaxationRecommendation(*values)[source]

Bases: StrEnum

Recommended filing mode based on the computed cuota differential.

The enum reports the lower-tax path after applying the materiality threshold, or INDIFFERENT when the two calculated results differ by less than one euro.

CONJUNTA
INDIVIDUAL
INDIFFERENT
class TaxationComparisonResult(**data)[source]

Bases: BaseModel

Typed result of a conjunta-vs-individual comparison run.

All cuota amounts are in euros (Decimal rounded to 2dp by the registry formula engine). Positive values are amounts to pay (a ingresar); negative values are amounts to refund (a devolver).

*_cuota_resultante carries the Modelo 100 cuota resultante de la autoliquidación semantic role, while *_resultado carries the cuota diferencial/result role used to compute delta_resultado and the recommendation.

Parameters:
filing_year: int
modelo: str
revision: str
conjunta_cuota_resultante: Decimal
individual_cuota_resultante: Decimal
conjunta_resultado: Decimal
individual_resultado: Decimal
delta_resultado: Decimal
recommendation: TaxationRecommendation
recommendation_reason: str
individual_branch_single_earner_only: bool
individual_branch_caveat: str
compare_taxation_modes(snapshot, *, inputs, binding_values, enum_binding_values, relation_values=None, date_binding_values=None, date_context=None)[source]

Run the registry engine for conjunta and individual, then diff the results.

The function is pure and does not read, create, or persist work units. It requires the caller to provide a loaded RegistrySnapshot, casilla inputs, and already-resolved profile bindings. It then injects declaration_type separately for the conjunta and individual runs and compares the calculated cuota diferencial/result casilla.

Parameters:
  • snapshot (RegistrySnapshot) – The RegistrySnapshot whose revision is executed for both conjunta and individual taxation modes.

  • inputs (Mapping[TypeAliasType, Decimal]) – Casilla input values shared across both runs.

  • binding_values (Mapping[TypeAliasType, Decimal]) – Pre-resolved Decimal binding values; must include all profile-sourced bindings except declaration_type.

  • enum_binding_values (Mapping[TypeAliasType, str]) – Pre-resolved string enum binding values.

  • relation_values (Mapping[TypeAliasType, Decimal] | None) – Optional cross-revision aggregation values.

  • date_binding_values (Mapping[TypeAliasType, date] | None) – Optional date-typed binding values.

  • date_context (Mapping[str, date] | None) – Optional date context for temporal casilla resolution.

Return type:

TaxationComparisonResult

Returns:

A TaxationComparisonResult with both run outcomes, the signed individual - conjunta delta, and the recommendation after the one-euro materiality threshold.

The caller must supply all profile-sourced bindings (CCAA, birth date, etc.) that the revision needs, except declaration_type: this function injects declaration_type=2 for the conjunta run and declaration_type=1 for the individual run automatically.

Raises TaxationComparisonError when the revision does not declare a declaration_type binding or lacks the cuota casillas required to build the differential. Ambiguous semantic-role casillas are also refused through the same error type.

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

Bases: CoreError

Raised when a conjunta-vs-individual comparison cannot be performed.

Covers unsupported revisions, missing declaration-type bindings, missing result casillas, and ambiguous semantic-role resolution.

Parameters:
  • message (str | None)

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

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
compare_taxation_for_work_unit(work_unit_id)[source]

Run conjunta-vs-individual comparison for an existing Modelo 100 work unit.

Resolves the registry snapshot and all profile-sourced bindings from the stored work unit, then delegates to compare_taxation_modes(). The declaration_type binding is injected by the comparison engine; the stored profile value is intentionally ignored so both paths are always evaluated.

Return type:

TaxationComparisonResult

Returns:

A TaxationComparisonResult with the conjunta and individual calculation outcomes.

Parameters:

work_unit_id (str)

Raises TaxationComparisonError when the work unit’s modelo does not support the comparison (e.g. not Modelo 100). Raises WorkUnitNotFoundError when work_unit_id does not exist in the active bucket.

See also

compare_taxation_modes():

Performs the pure snapshot comparison after this function resolves work-unit state.

compare_taxation_for_work_address(address)[source]

Run conjunta-vs-individual comparison for a natural or exact work address.

Parameters:

address (object) – The ModeloWorkAddress selected by CLI work-address parsing.

Return type:

TaxationComparisonResult

Returns:

A TaxationComparisonResult for the resolved work unit.