cadrumo.domain.prorrata_register.register module

Per-ejercicio cross-period IVA prorrata register (LIVA arts. 102-106).

The provisional-to-definitive IVA prorrata lifecycle is inherently cross-year: under prorrata general only the deduction percentage of soportado is deductible in each liquidation period (art. 104.Uno); the percentage provisionally applicable each year is the prior year’s definitive (art. 105.Uno), with the regulated alternatives of an AEAT-authorised provisional (art. 105.Dos) and the inicio-de-actividades proposed percentage (art. 105.Tres via art. 111.Dos); the last liquidation of the year computes the definitive prorrata from the year’s actual operations and regularises the provisional deductions (art. 105.Cuatro).

This module is the CARRY HOME for that lifecycle: a durable per-ejercicio ProrrataRegister, one ProrrataRegisterEntry per (ejercicio, sector) carrying the regime, the provisional percentage in force with its regulated ProrrataProvisionalProvenance, and — once settled — the definitive percentage with the annual volume inputs it derived from. The pure precedence-ladder resolver (resolve_provisional_percentage()) selects the in-force provisional percentage among candidate provenances (authorised/inicio outranking the carried prior definitive) and returns a visible unresolved state rather than any fabricated default — no percentage is ever assumed.

This is a taxpayer-fact store, sibling to domain.bienes_inversion: it holds the per-ejercicio percentages and their provenance, never the regulatory constants. The prorrata compute substrate (domain.iva: compute_prorrata_definitiva_anual, compute_regularizacion_prorrata_anual) is consumed at settlement, not re-implemented here, and this module reads no secure-object store — the seed, in-year apportionment, and settlement write-back live in the application layer.

See also

adapters.persistence.profile.prorrata_register

FINANCIAL secure-object repository that stores the register singleton.

domain.iva

Legal prorrata substrate that computes the definitive percentage from annual volumes and the art. 105.Cuatro regularisation cuota.

domain.bienes_inversion

Sibling per-taxpayer-fact register whose shape this mirrors.

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

Bases: CadrumoError

Raised when a prorrata-register record is structurally invalid.

Parameters:
  • message (str | None)

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

  • translated_message (str | None)

Return type:

None

code
exception ProrrataRegisterValidationError(message=None, *, context=None, translated_message=None)[source]

Bases: ProrrataRegisterError

Raised when a prorrata-register model fails Pydantic validation.

Parameters:
  • message (str | None)

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

  • translated_message (str | None)

Return type:

None

code
PRORRATA_REGISTER_SCHEMA_VERSION

Forward-compatible schema version stamped onto every record in this module.

class ProrrataEspecialTransitionEvidence(**data)[source]

Bases: BaseModel

Evidence for one Modelo 303 prorrata-especial transition.

The voluntary special-prorrata choice and its revocation are filing facts, not inferred descriptions of a register regime. A single discriminated transition keeps their mutual exclusion structural while its reference retains the operator evidence that supports the filing projection.

The enclosing ProrrataRegisterEntry owns the ejercicio and sector, so this child deliberately carries only the closed legal transition and its durable operator-held reference.

Parameters:

data (Any)

kind: _ProrrataEspecialTransitionKind
evidence_reference: str
class SectorDefinition(**data)[source]

Bases: BaseModel

One operator-declared differentiated sector (LIVA arts. 9.1.c / 101).

Strict, frozen, no extra fields. The art. 9.1.c partition of a taxpayer’s activities into differentiated sectors is a legal judgment the ledger cannot infer (which CNAE groups are run, whether their prorrata percentages diverge by more than 50 percentage points, whether a special-regime activity is present), so it is operator-declared: each sector carries a stable sector_id (the key the register entries and the ledger rows reference), the member activity codes it groups, and the SectorDiferenciadoLetra that makes it differentiated. Fail-closed: a register with no sector definitions is a whole-entity register (sector_id = None throughout), the landed cross-period behaviour, never a silently inferred partition.

Variables:
  • sector_id – Stable identifier the register entries and ledger rows reference. Must match the sector_id on the per-sector ProrrataRegisterEntry rows.

  • letra – The SectorDiferenciadoLetra (art. 9.1.c letra a’/b’/c’/d’) on which this sector is differentiated.

  • member_activity_codes – The CNAE / IAE-epígrafe activity codes grouped into this sector. Non-empty: a declared sector groups at least one activity. Recorded for provenance and operator audit; the per-sector routing keys on sector_id, never on these codes.

Parameters:

data (Any)

sector_id: str
letra: _SectorDiferenciadoLetra
member_activity_codes: tuple[str, ...]
class ProrrataActivityRow(**data)[source]

Bases: BaseModel

One canonical Modelo 303 per-activity prorrata filing row.

The five official DP30305 rows are taxpayer facts, not copies of the register’s global percentage or five families of numbered scalar inputs. activity_id gives a durable operator identity while slot records the reviewed fixed-row projection (1 through 5). The registry owns the revision-specific boxes and byte geometry; this child owns only the typed row facts and their evidence reference.

Parameters:

data (Any)

ejercicio: FilingYear
activity_id: str
slot: int
cnae_code: str
operaciones_total: Decimal
operaciones_con_derecho: Decimal
prorrata_type: _ProrrataActivityRowType
percentage: Decimal
evidence_reference: str
class ProrrataRegisterEntry(**data)[source]

Bases: BaseModel

One (ejercicio, sector) entry in the cross-period prorrata register.

Strict, frozen, no extra fields. Carries the regime in force, the provisional percentage with its regulated provenance (and, for the referenced provenances, its authorisation/proposal reference), and — populated only at settlement — the definitive percentage with the annual volume inputs it derived from. The provisional, referenced, and settlement field groups each travel together (present or absent as a unit), enforced by _validate_field_coupling().

Variables:
  • ejercicio – Filing year the entry covers.

  • regime – ProrrataRegisterRegime in force for the ejercicio.

  • especial_transition – The ProrrataEspecialTransitionEvidence for an art. 103.Dos option or its revocation evidenced in this ejercicio, or None when the regime merely continues. Required rather than defaulted: a regime is durable state, so an absent transition must be an explicit declaration and never an omission.

  • sector_id – Sector identifier for a sectores-diferenciados register, or None for the whole-entity register. Present from birth so sectores land without migration; the per-sector compute is deferred.

  • interrupted – The art. 105.Cinco “sin operaciones” marker — True when the taxpayer (or the differentiated sector) performed no operations during the ejercicio. Distinct from the ninguna regime (an active year under no prorrata): an interrupted year is inactive. An interrupted entry carries no provisional or definitive percentage and no volume inputs; the three-active-years seed walk skips it. The register thereby retains a truthful active/inactive history.

  • provisional_percentage – The provisional deduction percentage (0-100) in force during the year’s liquidations (art. 104.Uno + 105.Uno), or None when no percentage has resolved yet (never a fabricated default).

  • provisional_provenance – The ProrrataProvisionalProvenance the provisional percentage came from. Present iff provisional_percentage is present.

  • authorisation_reference – The AEAT authorisation (art. 105.Dos) or inicio-de-actividades proposal (art. 105.Tres) reference. Required iff provisional_provenance is a referenced provenance; forbidden otherwise.

  • definitive_percentage – The definitive deduction percentage (0-100) computed at settlement from the annual volumes (art. 105.Cuatro), or None before settlement.

  • definitive_volume_con_derecho – The annual con-derecho operations volume the definitive percentage derived from. Present iff definitive_percentage is present.

  • definitive_volume_sin_derecho – The annual sin-derecho operations volume the definitive percentage derived from. Present iff definitive_percentage is present.

  • source_observation_ref – The prior settlement observation identity a carried_prior_definitiva entry was seeded from, so the register stays cross-checkable against the prior filing. Permitted only for the carried provenance.

  • schema_version – Forward-compatible schema version. "2".

Parameters:

data (Any)

ejercicio: FilingYear
regime: _ProrrataRegisterRegime
especial_transition: ProrrataEspecialTransitionEvidence | None
sector_id: str | None
interrupted: bool
provisional_percentage: Decimal | None
provisional_provenance: _ProrrataProvisionalProvenance | None
authorisation_reference: str | None
definitive_percentage: Decimal | None
definitive_volume_con_derecho: Decimal | None
definitive_volume_sin_derecho: Decimal | None
source_observation_ref: str | None
source_registry_snapshot_refs: tuple[RegistrySnapshotRef, ...]
schema_version: str
class ProrrataProvisionalResolution(**data)[source]

Bases: BaseModel

Outcome of the precedence-ladder resolution of the in-force provisional percentage.

Variables:
  • percentage – The in-force provisional deduction percentage (0-100), or None when the ladder resolved no value (the visible unresolved state — the caller surfaces an advisory, never a silent default).

  • provenance – The winning ProrrataProvisionalProvenance, or None when unresolved.

Parameters:

data (Any)

percentage: Decimal | None
provenance: _ProrrataProvisionalProvenance | None
property resolved: bool

Whether the ladder resolved a percentage.

resolve_provisional_percentage(candidates)[source]

Resolve the in-force provisional percentage among candidate entries by the LIVA art. 105 ladder.

Applies the single declared precedence ladder (_provenance_precedence()): an AEAT-authorised (art. 105.Dos) or inicio-de-actividades (art. 105.Tres) provisional percentage outranks the carried prior definitive (art. 105.Uno). Only candidates that actually carry a provisional percentage participate; a candidate recording a regime but no percentage does not contribute a value. When no candidate carries a percentage the result is the visible unresolved state (both fields None) — never a fabricated default.

Parameters:

candidates (Iterable[ProrrataRegisterEntry]) – Register entries to resolve among. In the normal register this is the (at most one) entry for a (ejercicio, sector) key; the seeding/override recording path supplies several provenance candidates.

Return type:

ProrrataProvisionalResolution

Returns:

A ProrrataProvisionalResolution naming the winning percentage and provenance, or both None when unresolved.

class ProrrataRegister(**data)[source]

Bases: BaseModel

Encrypted JSON document holding the per-ejercicio prorrata register.

Holds one ProrrataRegisterEntry per (ejercicio, sector_id) key; a duplicate key is rejected at construction. The regime and sector axes are present from birth so prorrata especial and sectores diferenciados land without a schema migration (no-legacy-compatibility).

Variables:
  • schema_version – Forward-compatible schema version. "2".

  • entries – Tuple of ProrrataRegisterEntry rows.

  • sector_definitions – The operator-declared differentiated-sector partition (LIVA arts. 9.1.c / 101). Empty for a whole-entity register — the fail-closed default; when non-empty every per-sector ProrrataRegisterEntry sector_id and every sectored ledger row references one of these declared sectors.

  • activity_rows – Canonical, evidence-carrying Modelo 303 per-activity prorrata rows. They are keyed by (ejercicio, activity_id) and retain their fixed official row slot without owning any global prorrata result.

Parameters:

data (Any)

schema_version: str
entries: tuple[ProrrataRegisterEntry, ...]
sector_definitions: tuple[SectorDefinition, ...]
activity_rows: tuple[ProrrataActivityRow, ...]
property is_sectorized: bool

Whether the register declares a differentiated-sector partition.

Fail-closed: False (whole-entity) when no sector definition exists, so a taxpayer with no declared partition keeps the landed cross-period behaviour byte-identical.

sector_ids()[source]

Return the declared sector ids, in declaration order.

Return type:

tuple[str, ...]

sector_definition_for(sector_id)[source]

Return the declared SectorDefinition for sector_id, or None.

Return type:

SectorDefinition | None

Parameters:

sector_id (str)

entries_for_ejercicio(ejercicio)[source]

Return every entry recorded for ejercicio across all sectors.

Return type:

tuple[ProrrataRegisterEntry, ...]

Parameters:

ejercicio (int)

has_complete_current_entry_coverage(ejercicio)[source]

Whether every declared prorrata scope has an explicit current entry.

A whole-entity register has exactly one None sector scope. Once sectors are declared, the common whole-entity scope remains required alongside every differentiated sector: a filing-wide Modelo 303 reduction is valid only when the current ejercicio has exactly one entry for that complete set. It must not turn an absent declaration into NO.

Return type:

bool

Parameters:

ejercicio (int)

activity_rows_for_ejercicio(ejercicio)[source]

Return the year’s canonical activity rows in official fixed-slot order.

Return type:

tuple[ProrrataActivityRow, ...]

Parameters:

ejercicio (int)

requires_activity_rows_for(ejercicio)[source]

Whether the recorded prorrata regime makes the five rows applicable.

Return type:

bool

Parameters:

ejercicio (int)

activity_rows_complete_for(ejercicio)[source]

Whether an applicable year has exactly the five declared official rows.

Return type:

bool

Parameters:

ejercicio (int)

entry_for(ejercicio, *, sector_id=None)[source]

Return the entry for a (ejercicio, sector_id) key, or None when absent.

Return type:

ProrrataRegisterEntry | None

Parameters:
  • ejercicio (int)

  • sector_id (str | None)

resolve_provisional(ejercicio, *, sector_id=None)[source]

Resolve the in-force provisional percentage for a (ejercicio, sector_id) key.

Filters the register to the key’s entry and applies resolve_provisional_percentage(), returning the visible unresolved state when no percentage is recorded.

Return type:

ProrrataProvisionalResolution

Parameters:
  • ejercicio (int)

  • sector_id (str | None)