aeat.domain.calculations.registry._applicability module

Registry-grounded modelo-applicability derivation from the taxpayer model.

The overview surfaces (explain / calendar / agenda / backlog) used to treat every profile as an autónomo en estimación directa: the DeadlineEngine produces an obligation for every modelo with a registered deadline window, and no layer asked which kind of taxpayer this is. A pure landlord was told Modelo 130 was overdue.

This module is the derivation layer: each modelo’s applicable verdict is DERIVED from the three-axis TaxpayerProfile model (entity type, IRPF income categories, estimation regime) through a registry-grounded rule table. The autónomo-by-default assumption is removed.

Four verdicts are possible:

  • ApplicabilityVerdict.APPLICABLE — the taxpayer model triggers this modelo.

  • ApplicabilityVerdict.NOT_APPLICABLE — the taxpayer model positively excludes this modelo (a landlord has no Modelo 130; an S.L. has no Modelo 100).

  • ApplicabilityVerdict.ATTRIBUTION_PASS_THROUGH — the profile is an attribution entity (comunidad de bienes, sociedad civil sin objeto mercantil) and the modelo asked about is a cuota self-assessment (the IRPF Modelo 100 / 130 or the IS Modelo 200 / 202). An attribution entity runs no IS and no IRPF cuota of its own — the régimen de atribución de rentas (LIRPF Title X Section 2) attributes the income to the members, who file the substantive tax. The honest answer to “what is my cuota” is “none — taxed in the members’ returns”. This is structurally distinct from a plain NOT_APPLICABLE: a salaried-only natural person is positively excluded from Modelo 200 because they file a different cuota (Modelo 100); an attribution entity files no cuota at all.

  • ApplicabilityVerdict.INCOMPLETE — the taxpayer model is undeclared (no entity_type and, for a natural person, no income categories), or the entity form is recognised-but-unsupported. The engine refuses to guess: it never reports a confident wrong obligation, and it never runs an IRPF cuota for a company or an IS cuota for an attribution entity.

The entity-type axis selects the income-tax route: a legal entity routes to the IS path (Modelo 200 / 202), a natural person to the IRPF path (Modelo 100 / 130), and an attribution entity to member pass-through for cuota self-assessments. IVA and payer-fact modelos are then decided by their own declared profile facts (IVA regime, withholding-payer facts, trade thresholds). This is the corporate-entity ADR §4 engine routing contract without treating pass-through income taxation as an exemption from non-income-tax obligations.

Canonical applicability authority — modelo level. _MODELO_APPLICABILITY_RULES is the single canonical source for modelo-level applicability. Any question of the form “does this taxpayer ever owe this modelo?” is answered here. Code that derives applicability verdicts MUST read from this table; it MUST NOT re-implement the logic in another module or maintain a parallel copy of the rules dict.

Relation to ``applicability_conditions`` on ``ModeloDeadlineWindow``. ModeloDeadlineWindow carries a applicability_conditions mapping that governs window-level scheduling — which specific deadline window applies for a profile within the set of applicable windows (e.g. Modelo 202 uses different modality windows for the April / October / December instalments, and some windows filter by entity_size). These conditions are COMPLEMENTARY to the modelo-level rules, not replacements: applicability_conditions operates after the modelo-level gate confirms the modelo applies at all; it never overrides the modelo-level verdict. Adding a condition to a deadline window does not affect the ApplicabilityVerdict returned by derive_modelo_applicability().

Every rule carries legal_refs — scoped registry citation keys in the law-slug:art-N form (e.g. ley-35-2006:art-99) that resolve against src/aeat/_data/registry/aeat/legal/*.toml — per .claude/rules/aeat-calculation-grounding.md: applicability is regulatory data and must be registry-grounded, and every typed-ID reference must point at an existing registry entity. The seed table below covers the core modelo set an ordinary taxpayer encounters — the IRPF Renta and pago-fraccionado modelos (100 / 130 / 131), the corporate IS modelos (200 / 202), the IVA modelos (303 / 390), the retención modelos and their annual companions (111 / 190, 115 / 180), the operaciones modelos (349 / 347), and the attribution-entity informational Modelo 184. Per-entity / per-regime expansion to the remaining registered modelos is intentionally deferred, marked at _SEED_COVERAGE_NOTICE.

class ApplicabilityVerdict(*values)[source]

Bases: StrEnum

Whether a modelo applies to a taxpayer, derived from its model.

Variables:
  • APPLICABLE – The declared taxpayer model triggers this modelo.

  • NOT_APPLICABLE – The declared taxpayer model positively excludes this modelo (e.g. a landlord has no Modelo 130 obligation; a sociedad limitada files no Modelo 100).

  • ATTRIBUTION_PASS_THROUGH – The profile is an attribution entity and the modelo is a cuota self-assessment (Modelo 100 / 130 / 200 / 202). The entity runs no IS and no IRPF cuota of its own — the régimen de atribución de rentas (LIRPF Title X Section 2) attributes the income to the members, who file the substantive tax. The honest answer to “what is my cuota” is “none — the income is taxed in the members’ returns”. Distinct from NOT_APPLICABLE, which means the taxpayer files a different cuota.

  • INCOMPLETE – The taxpayer model is not declared in enough detail to decide, or the entity form is recognised-but-unsupported. The engine refuses to guess — the operator must declare their taxpayer type first.

APPLICABLE
NOT_APPLICABLE
ATTRIBUTION_PASS_THROUGH
INCOMPLETE
class ModeloApplicability(**data)[source]

Bases: BaseModel

The derived applicability of one modelo for one taxpayer profile.

Variables:
  • modelo – The AEAT modelo identifier.

  • verdict – The ApplicabilityVerdict derived from the taxpayer model.

  • reason – Operator-facing prose explaining the verdict. An INCOMPLETE verdict carries one of two distinct rationales: the “declare your taxpayer type first” guidance when the taxpayer model is undeclared, or a “no rule derived yet” notice when the modelo has no seed rule (the latter is not a statement about the operator’s profile).

  • legal_refs – Scoped registry citation keys (law-slug:art-N) grounding the rule, each resolvable against the registry legal/*.toml tables. Always at least one entry — applicability is regulatory data and must be grounded (.claude/rules/aeat-calculation-grounding.md). For an INCOMPLETE verdict the refs ground the concept being asked about (the LIRPF / LIS taxpayer definitions) so the operator still sees a citation.

Parameters:
modelo: ModeloId
verdict: ApplicabilityVerdict
reason: _OperatorReason
legal_refs: tuple[LegalRefId, ...]
property applicable: bool

Return whether the modelo positively applies.

Only ApplicabilityVerdict.APPLICABLE is a confident yes. NOT_APPLICABLE, ATTRIBUTION_PASS_THROUGH and INCOMPLETE all yield False — the operative views must not surface an obligation the engine cannot positively justify. An attribution entity owes no cuota self-assessment, so a pass-through verdict is not an applicable obligation.

class ModeloApplicabilityRule(**data)[source]

Bases: BaseModel

A single registry-grounded modelo-applicability rule.

A rule answers, for one modelo, the question “does the declared taxpayer model trigger this modelo?”. The predicate is expressed as closed sets over the three taxpayer axes; evaluation never invents legal behaviour beyond what the seed table grounds.

Variables:
  • modelo – The AEAT modelo identifier the rule decides.

  • applicable_entity_types – The EntityType values the modelo applies to. A taxpayer whose entity_type is outside this set gets ApplicabilityVerdict.NOT_APPLICABLE.

  • required_income_categories – For a natural person, the IRPF income categories of which at least one must be declared for the modelo to apply. Empty means the modelo does not gate on income category (it applies to every natural person whose entity_type matches). Non-empty means a natural person without any of these categories gets NOT_APPLICABLE — this is the gate that excludes Modelo 130 for a pure landlord.

  • required_estimation_regimes – The IRPF estimation regimes the modelo applies to. Empty means the modelo does not gate on the estimation regime. Non-empty means a natural person whose irpf_estimation_regime is outside the set gets NOT_APPLICABLE. An undeclared regime resolves to the direct-estimation default: estimación directa is the default IRPF method (LIRPF art. 16; RIRPF art. 32 makes módulos opt-in), so an actividad-económica autónomo who has not explicitly elected módulos owes Modelo 130. This is the axis that splits Modelo 130 (estimación directa) from Modelo 131 (estimación objetiva): the two are mutually exclusive on the regime.

  • applicable_fiscal_residencies – The fiscal residency categories the modelo applies to. Empty means the modelo does not gate on fiscal residency. An undeclared residency is kept on the resident-IRPF default path described by TaxpayerProfile; a declared residency outside this set is a positive exclusion.

  • applicable_iva_regimes – The IVA regimes that positively keep a modelo in scope. Empty means the modelo does not gate on IVA regime. Non-empty means a profile outside those regimes gets NOT_APPLICABLE. This lets Modelo 303 / 390 be driven by the declared IVA obligation instead of borrowing the natural person’s IRPF income-category axis for legal and attribution entities.

  • required_payer_fact – The PayerFact the modelo’s applicability depends on, or None when the modelo does not gate on a payer fact. When set, a profile that positively declares the fact gets APPLICABLE; a profile that does not gets INCOMPLETE — the underlying boolean has no tri-state, so the engine cannot positively justify a NOT_APPLICABLE (see PayerFact).

  • applicable_reason – Operator-facing prose for the APPLICABLE verdict.

  • not_applicable_reason – Operator-facing prose for the NOT_APPLICABLE verdict.

  • cuota_bearingTrue when the modelo is a cuota self-assessment (the IRPF Modelo 100 / 130 or the IS Modelo 200 / 202). A cuota-bearing modelo asked of an attribution entity yields an ApplicabilityVerdict.ATTRIBUTION_PASS_THROUGH verdict rather than a plain NOT_APPLICABLE: the entity runs no cuota of its own, the income is taxed in the members’ returns (corporate-entity ADR §2). An informational modelo (Modelo 184) is not cuota-bearing — it stays a plain NOT_APPLICABLE for the entity types its applicable_entity_types excludes.

  • legal_refs – Scoped registry citation keys (law-slug:art-N) grounding the rule, each resolvable against the registry legal/*.toml tables.

Parameters:
modelo: ModeloId
applicable_entity_types: frozenset[EntityType]
required_income_categories: frozenset[IrpfIncomeCategory]
required_estimation_regimes: frozenset[IrpfEstimationRegime]
applicable_fiscal_residencies: frozenset[FiscalResidency]
applicable_iva_regimes: frozenset[IVARegime]
required_payer_fact: PayerFact | None
applicable_reason: _OperatorReason
not_applicable_reason: _OperatorReason
cuota_bearing: bool
legal_refs: tuple[LegalRefId, ...]
evaluate(profile)[source]

Derive the ModeloApplicability for profile.

Returns an INCOMPLETE verdict when the taxpayer model is not declared in enough detail to decide; an ATTRIBUTION_PASS_THROUGH verdict when the modelo is a cuota self-assessment asked of an attribution entity; otherwise an APPLICABLE / NOT_APPLICABLE verdict derived from the entity-type, income-category, estimation-regime, and payer-fact axes.

Parameters:

profile (TaxpayerProfile) – The TaxpayerProfile to evaluate against this rule.

Return type:

ModeloApplicability

has_applicability_rule(modelo)[source]

Return whether a seed applicability rule exists for modelo.

Return type:

bool

Parameters:

modelo (str)

iter_modelo_applicability_rules()[source]

Return the registry-owned seed ModeloApplicabilityRule instances.

The returned tuple is ordered by modelo id for deterministic audits and tests. Callers receive rule objects, not the mutable module-level dictionary, so the registry rule table remains read-only from the public API.

Return type:

tuple[ModeloApplicabilityRule, ...]

taxpayer_model_is_declared(profile)[source]

Return whether the profile carries a usable taxpayer model.

The taxpayer model is “declared” when the operator has set an entity_type and — for a natural person — at least one IRPF income category. Without these, modelo applicability cannot be derived: the engine must report INCOMPLETE rather than assume autónomo. A legal / attribution entity needs no income category; the entity_type alone selects its tax.

Parameters:

profile (TaxpayerProfile) – The TaxpayerProfile to inspect.

Return type:

bool

derive_tax_route(profile)[source]

Return the tax branch profile routes to — corporate-entity ADR §4.

The routing contract: the entity_type axis selects the tax. A legal-entity profile routes to the Impuesto sobre Sociedades (Modelo 200 / 202); a natural person to the IRPF (Modelo 100 / 130 / 303); an attribution entity to the member pass-through. An undeclared entity_type yields TaxRoute.INCOMPLETE — the engine never runs an IRPF cuota for a company or an IS cuota for an attribution entity, and never defaults a tax for a profile that declared none.

Parameters:

profile (TaxpayerProfile) – The TaxpayerProfile whose entity_type axis selects the tax branch.

Return type:

TaxRoute

Returns:

The TaxRoute branch the profile’s entity_type selects, or TaxRoute.INCOMPLETE when entity_type is undeclared.

derive_modelo_applicability(profile, modelo, *, today=None)[source]

Derive a modelo’s applicability from the taxpayer model.

The verdict is DERIVED from the three-axis TaxpayerProfile model — never assumed. An undeclared taxpayer model yields an explicit ApplicabilityVerdict.INCOMPLETE answer; the engine never reports a confident wrong obligation.

A modelo without a seed rule (the seed covers the core persona set only) is also reported INCOMPLETE so the operator is never told a confident yes/no the registry rules cannot yet justify; the rationale points at the deferred expansion.

Parameters:
  • profile (TaxpayerProfile) – The operator’s three-axis taxpayer model.

  • modelo (str) – The AEAT modelo identifier to decide.

  • today (date | None) – Reference date for the Beckham window check. Defaults to date.today() when None. Pass an explicit date in tests so results are deterministic.

Return type:

ModeloApplicability

Returns:

The ModeloApplicability for modelo and profile.