cadrumo.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 plainNOT_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 (noentity_typeand, 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). The engine routing contract does not treat 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/cadrumo/_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; a modelo with no
seed rule is reported INCOMPLETE with a rationale that says so.
- class ApplicabilityVerdict(*values)[source]¶
Bases:
StrEnumWhether 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:
BaseModelThe derived applicability of one modelo for one taxpayer profile.
- Variables:
modelo – The AEAT modelo identifier.
verdict – The
ApplicabilityVerdictderived from the taxpayer model.reason – Operator-facing prose explaining the verdict. An
INCOMPLETEverdict 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 registrylegal/*.tomltables. Always at least one entry — applicability is regulatory data and must be grounded (.claude/rules/aeat-calculation-grounding.md). For anINCOMPLETEverdict the refs ground the concept being asked about (the LIRPF / LIS taxpayer definitions) so the operator still sees a citation.
- Parameters:
data (Any)
- modelo: ModeloId¶
- verdict: ApplicabilityVerdict¶
- reason: _OperatorReason¶
- legal_refs: tuple[LegalRefId, ...]¶
- failure: RegistryFailureClassification | None¶
Domain facts for a boundary to project when applicability is incomplete.
- property applicable: bool¶
Return whether the modelo positively applies.
Only
ApplicabilityVerdict.APPLICABLEis a confident yes.NOT_APPLICABLE,ATTRIBUTION_PASS_THROUGHandINCOMPLETEall yieldFalse— 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:
BaseModelA 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
EntityTypevalues the modelo applies to. A taxpayer whoseentity_typeis outside this set getsApplicabilityVerdict.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_typematches). Non-empty means a natural person without any of these categories getsNOT_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_regimeis outside the set getsNOT_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
PayerFactValuethe modelo’s applicability depends on, orNonewhen the modelo does not gate on a payer fact. When set, a profile that positively declares the fact getsAPPLICABLE; a profile that does not getsINCOMPLETE— the underlying boolean has no tri-state, so the engine cannot positively justify aNOT_APPLICABLE(seePayerFactValue).applicable_reason – Operator-facing prose for the
APPLICABLEverdict.not_applicable_reason – Operator-facing prose for the
NOT_APPLICABLEverdict.cuota_bearing –
Truewhen 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 anApplicabilityVerdict.ATTRIBUTION_PASS_THROUGHverdict rather than a plainNOT_APPLICABLE: the entity runs no cuota of its own, the income is taxed in the members’ returns. An informational modelo (Modelo 184) is not cuota-bearing — it stays a plainNOT_APPLICABLEfor the entity types itsapplicable_entity_typesexcludes.legal_refs – Scoped registry citation keys (
law-slug:art-N) grounding the rule, each resolvable against the registrylegal/*.tomltables.
- Parameters:
data (Any)
- 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: PayerFactValue | None¶
- applicable_reason: _OperatorReason¶
- not_applicable_reason: _OperatorReason¶
- cuota_bearing: bool¶
- legal_refs: tuple[LegalRefId, ...]¶
- evaluate(profile)[source]¶
Derive the
ModeloApplicabilityforprofile.Returns an
INCOMPLETEverdict when the taxpayer model is not declared in enough detail to decide; anATTRIBUTION_PASS_THROUGHverdict when the modelo is a cuota self-assessment asked of an attribution entity; otherwise anAPPLICABLE/NOT_APPLICABLEverdict derived from the entity-type, income-category, estimation-regime, and payer-fact axes.- Parameters:
profile (
TaxpayerProfile) – TheTaxpayerProfileto evaluate against this rule.- Return type:
- hydrate_applicability_rule(modelo, fragment)[source]¶
Hydrate a registry-authored applicability fragment into the runtime rule.
The loader boundary for the
applicabilityschema family: every free-form TOML string onfragmentis resolved here to its typed registry token (orPayerFactValue), never left as a raw string for a downstream branch to compare against. An unknown token raisesRegistryValidationErrornaming the offending rule and the underlying enum-coercion error, mirroring the coercion-boundary shapeRevisionReviewStatusFieldand its siblings already use.- Parameters:
modelo (
Modelo) – The modelo the owning revision belongs to; the fragment carries no self-referentialmodelofield to avoid a value that could silently diverge from the revision it is nested in.fragment (
ApplicabilityRuleDefinition) – The validatedApplicabilityRuleDefinitionto hydrate.
- Return type:
- Returns:
The equivalent
ModeloApplicabilityRule.- Raises:
RegistryValidationError – A field names a token not declared by the selected facts registry.
- MODELO_APPLICABILITY_RULES: Mapping[str, ModeloApplicabilityRule]¶
Seed modelo-applicability rules — core persona coverage.
A modelo absent from this table has no derived rule yet: its applicability is reported
ApplicabilityVerdict.INCOMPLETEwith a rationale naming the deferred expansion, never a confident guess.
- REGISTRY_RESOLVED_APPLICABILITY_MODELOS: frozenset[Modelo]¶
Modelo ids whose applicability rule is authored in the registry rather than declared as a Python literal in
MODELO_APPLICABILITY_RULESbelow. 303 and 390 stay literal – their authoring trees are owned by the export-fragment-generator-authority campaign, not unplaced by omission.Membership is reached two ways, and the difference matters when reading a rule’s provenance. Most entries arrived by MIGRATION: every revision of each was hydration-verified equal to the literal it replaces, through the real loader, before it was added here and the literal deleted in the same commit. Modelo 840 arrived by AUTHORING instead – it never had a literal to be verified against, and its rule is grounded directly on TRLRHL arts. 78, 82 and 90 with real-profile verdicts asserted in
test_modelo_840_applicability. A migrated entry’s guarantee is equivalence; an authored entry’s is its citations and its tests, and no count is stated here because a tally of either goes stale on the next entry.This is the single declaration of the mixed-surface state
MODELO_APPLICABILITY_RULESis now in: these modelos resolve from the registry, 303 and 390 still resolve from the literal table. The literal table retires outright once the export-fragment campaign closes those two trees and they are migrated the same way; this module then stops authoring applicability data at all – it only reads it.
- resolve_applicability_rule_from_authority(authority, modelo)[source]¶
Resolve one modelo’s applicability rule from an already-loaded authority.
Reads the UNVALIDATED
ModeloDefinition(authority.modelo(...)), notvalidate_modelo/snapshot: applicability derivation is a pervasive, taxpayer-facing read on every profile view, and coupling its availability to full business-rule validation or the review-status filing gate would make an unrelated validation defect elsewhere in the tree break every taxpayer’s applicability answer. The fragment was already validated once, when the registry authority was built and published.This is a deliberate asymmetry with
RegistrySnapshot, which carries a same-shaped projection for every OTHER schema family: applicability answers “is this modelo due, and to whom” – the floor rung of the authority-grade ladder, scheduling reach, not filing authority.RegistrySnapshotis a filing-context projection one rung up. Resolving applicability without filing-grade review is correct per that ladder, not a gate dodged; coupling it to snapshot construction would wrongly tie a floor-rung fact to filing authority it does not need.Applicability content is uniform across a modelo’s declared revisions today (the migrator authors the identical rule into every one), so any revision carrying the family answers the question – the first one found is used.
Split out from
_resolve_registry_applicability_rule()so the real logic takes its authority as a parameter and is testable against a scratchValidatedRegistryAuthoritywithout touching the bundled tree or monkeypatching anything; the production wrapper leases the indexed generation only when a caller has not already supplied an operation.- Raises:
RegistryValidationError – No declared revision carries an
applicabilityrule, or the rule fails to hydrate.- Return type:
- Parameters:
authority (ValidatedRegistryAuthority)
modelo (Modelo)
- resolve_applicability_rule_from_operation(operation, modelo)[source]¶
Resolve one modelo’s applicability rule from a generation-pinned directory.
- Return type:
- Parameters:
operation (PinnedAuthorityOperation)
modelo (Modelo)
- iter_modelo_applicability_rules()[source]¶
Return every registry-resolved or seed-literal
ModeloApplicabilityRule.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:
- modelo_requires_iva_regime(modelo)[source]¶
Return whether the modelo’s applicability rule depends on IVA regime.
Calendar completeness is a consumer of the same applicability predicate that determines whether a taxpayer can have the modelo obligation. Keep that classification here, where registry-resolved and seed rules already meet, rather than maintaining a second calendar or core-constants set.
- 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_typeand — for a natural person — at least one IRPF income category. Without these, modelo applicability cannot be derived: the engine must reportINCOMPLETErather than assume autónomo. A legal / attribution entity needs no income category; theentity_typealone selects its tax.- Parameters:
profile (
TaxpayerProfile) – TheTaxpayerProfileto inspect.- Return type:
- derive_tax_route(profile)[source]¶
Return the tax branch
profileroutes to.The routing contract: the
entity_typeaxis 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 undeclaredentity_typeyieldsTaxRoute.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) – TheTaxpayerProfilewhoseentity_typeaxis selects the tax branch.- Return type:
- Returns:
The
TaxRoutebranch the profile’sentity_typeselects, orTaxRoute.INCOMPLETEwhenentity_typeis undeclared.
- derive_modelo_applicability(profile, modelo, *, today=None, authority=None, operation=None)[source]¶
Derive a modelo’s applicability from the taxpayer model.
The verdict is DERIVED from the three-axis
TaxpayerProfilemodel — never assumed. An undeclared taxpayer model yields an explicitApplicabilityVerdict.INCOMPLETEanswer; 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
INCOMPLETEso 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 the Europe/Madrid civil date (today_madrid()) whenNone— the six-year window is a Spanish-calendar boundary. Pass an explicit date in tests so results are deterministic.authority (
ValidatedRegistryAuthority|None) – Already-resolved validated authority to reuse. Omitting it preserves the standalone fingerprint-bounded bundled-tree lookup.operation (
PinnedAuthorityOperation|None) – Already-pinned indexed operation to reuse for point-loaded applicability facts. Omitting it leases the bundled indexed generation for this call.
- Return type:
- Returns:
The
ModeloApplicabilityformodeloandprofile.
Core types:
ValidatedRegistryAuthority.
- derive_taxpayer_files_economic_activity(profile)[source]¶
Whether the taxpayer files actividad-económica pagos fraccionados (130/131).
Reads the
TaxpayerProfileincome-category declarations.Truewhen the profile declares actividad-económica income;Falsewhen it declares income categories that exclude it (a salaried/rental-only filer never files 130/131);Nonewhen income categories are undeclared (fail-closed: the 130/131 dependency stays enforced). LIRPF art. 99 / RIRPF art. 109.- Return type:
- Parameters:
profile (TaxpayerProfile)