cadrumo.application.aggregation.iva_ledger module¶
Repository-backed IVA observation projection from ledger catalogues.
This module classifies bucket-local
TransactionCatalogue rows into typed
IvaLedgerObservation records and
binding-ready totals. The source-mesh resolver in modelo_bindings
then applies the target
ModeloRevision, resolves
ledger_iva_aggregation bindings, and surfaces source diagnostics for ledger
rows that no declared binding consumes.
When the bucket’s cross-period prorrata register resolves an active
general provisional percentage for the filing year, the aggregation result
carries IvaLedgerProrrataApportionment. The binding resolver applies
that percentage only to deducible IVA cuota bindings; bases and output IVA
cuotas stay unapportioned.
The repository-backed entry point requires the application-owned transaction catalogue capability for the active bucket.
See also
prorrata_registerPer-ejercicio carry home for the provisional percentage consumed by the IVA ledger apportionment.
LedgerIvaAggregationSourceResolverSource-mesh adapter that calls this projection and records prorrata apportionment provenance.
test_iva_ledger_prorrata_apportionmentRegression coverage proving the active provisional percentage reduces deducible cuotas without reducing bases.
renta_ledger,renta_income_ledger,renta_gasto_ledgerSibling Renta ledger projections.
- class IvaLedgerAggregationIssueReason(*values)[source]¶
Bases:
StrEnumMachine-readable reasons why a ledger row did not produce IVA observations.
The first five values are shared with
RentaLedgerAggregationIssueReasonthrough_shared_issue_reasonsso cross-ledger telemetry can group upstream filter rejections under one key. The remaining values are IVA-specific.- UNSUPPORTED_DIRECTION¶
- UNSUPPORTED_CURRENCY¶
- UNCLASSIFIED_BUSINESS_STATE¶
- PERSONAL_TRANSACTION¶
- OUTSIDE_PERIOD¶
- MISSING_TAXABLE_BASE¶
- MISSING_IVA_AMOUNT¶
- MISSING_IVA_RATE¶
- UNSUPPORTED_IVA_RATE¶
- IVA_RATE_DATE_OUTSIDE_TABLE_COVERAGE¶
- CUOTA_ON_ZERO_RATED_ROW¶
- NON_ZERO_RATE_ON_ZERO_CUOTA_CATEGORY¶
- NON_ARISING_CATEGORY_FOR_INVOICE_SIDE¶
- MISSING_EUR_TAX_SUBSTRATE¶
- INVALID_PRORRATA_REFERENCE¶
- UNSUPPORTED_IVA_CATEGORY¶
- MISSING_COUNTERPARTY_IDENTIFICATION_STATE¶
- DOMESTIC_IDENTIFICATION_ON_INTRA_COMMUNITY_TRANSACTION¶
- EU_MEMBER_STATE_ON_EXPORT_TRANSACTION¶
- MISSING_COUNTERPARTY_ESTABLISHMENT_ON_EXPORT¶
- CASH_ACCOUNTING_EXCLUDED_CATEGORY¶
- MISSING_DEDUCTION_CLASSIFICATION¶
- class IvaLedgerAggregationIssue(**data)[source]¶
Bases:
BaseModelTraceable exclusion emitted while projecting IVA ledger observations.
- Parameters:
data (Any)
- transaction_id: str¶
- reason: IvaLedgerAggregationIssueReason¶
- detail: IssueDetail¶
- class ProrrataLedgerReference(**data)[source]¶
Bases:
BaseModelBucket-local ledger row pointer to a legal IVA prorrata reference.
- Parameters:
data (Any)
- transaction_id: TransactionId¶
- transaction_date: date¶
- reference: ProrrataReference¶
- base_amount: Decimal¶
- input_iva_amount: Decimal¶
- class IvaLedgerSectorApportionment(**data)[source]¶
Bases:
BaseModelPer-sector prorrata apportionment for a sectores-diferenciados bucket.
Under LIVA arts. 9.1.c / 101 a taxpayer with differentiated sectors applies the deduction regime separately per sector. Each declared sector carries its own provisional
percentageand its ownregime(a sector may run general while another runs especial); the sector-aware binding resolver applies THIS sector’s apportionment to every deducible cuota whose observation carries the matchingsector_id.See also
SectorDefinitionOperator-declared sector this apportionment resolves for.
- Parameters:
data (Any)
- sector_id: str¶
- percentage: Decimal¶
- regime: ProrrataRegisterRegime¶
- class IvaLedgerProrrataApportionment(**data)[source]¶
Bases:
BaseModelProrrata percentage applied to deducible ledger IVA cuotas.
Under
regime == GENERAL(LIVA art. 104) the singlepercentageis applied to every deducible cuota binding. Underregime == ESPECIAL(LIVA art. 106)percentageis the general percentage that applies only to the COMMON-use inputs; exclusively-deductible inputs deduct in full and exclusively-non-deductible inputs deduct nothing, routed per the observation’sinput_classification.When
sector_apportionmentsis non-empty (LIVA arts. 9.1.c / 101), every sector-owned input carries one exact declared sector. Cross-sector common use is represented only by the registry-declared mixed-use token; a bare missing sector never defaults to common. Emptysector_apportionmentsis the whole-entity register.See also
ProrrataProvisionalProvenanceRegulated source of the provisional percentage carried on this apportionment.
resolve_iva_ledger_binding_values()Applies the percentage after registry selector resolution.
- Parameters:
data (Any)
- percentage: Decimal¶
- provenance: ProrrataProvisionalProvenance¶
- regime: ProrrataRegisterRegime¶
- source_observation_ref: str | None¶
- authorisation_reference: str | None¶
- sector_apportionments: tuple[IvaLedgerSectorApportionment, ...]¶
- class IvaDifferentiatedDeductionContribution(**data)[source]¶
Bases:
BaseModelImmutable canonical apportioned contribution for one sector/source family.
- Parameters:
data (Any)
- sector_id: str¶
- deduction_fact_kind: IvaDeductionFactKind¶
- source_ledger_ids: tuple[str, ...]¶
- base_amount: Decimal¶
- deducible_iva_amount: Decimal¶
- class AnnualDeducibleTotalsByRegime(**data)[source]¶
Bases:
BaseModelThe ejercicio’s whole-year deducible IVA cuota under both prorrata regimes.
The settlement input to the LIVA art. 103.Dos.2.º mandatory-especial check (
build_prorrata_especial_mandatory_advisory): art. 103.Dos.2.º makes prorrata especial obligatory when the deducción under the general regime exceeds the deducción under the especial regime by ten percent or more.deduction_under_generalis mechanically derivable for any bucket (art. 104 applies one whole-entity percentage), so it is always honest; the especial total (art. 106 per-input classification) is honest only when the register regime is ESPECIAL, or when every deducible soportado row of the ejercicio carries a declaredinput_classification—unclassified_deducible_countrecords how many deducible soportado observations are still unclassified, so the caller can decide whether the especial total is honestly computable or the filer must first classify.See also
compute_annual_deducible_totals_by_regime()Builds this record from one annual observation aggregation and two apportionment passes.
- Parameters:
data (Any)
- deduction_under_general: Decimal¶
- deduction_under_especial: Decimal¶
- unclassified_deducible_count: int¶
- regime: ProrrataRegisterRegime¶
- class IvaLedgerAggregation(**data)[source]¶
Bases:
BaseModelIVA observations produced from one bucket-local transaction catalogue.
out_of_window_summaryis only populated by repository-backed date partitions. Full-catalogue aggregation continues to emit row-level issues because every transaction is already loaded for classification.- Parameters:
data (Any)
- period: Period¶
- observations: Sequence[IvaLedgerObservation]¶
- prorrata_references: Sequence[ProrrataLedgerReference]¶
- prorrata_apportionment: IvaLedgerProrrataApportionment | None¶
- issues: Sequence[IvaLedgerAggregationIssue]¶
- out_of_window_summary: OutOfWindowTransactionSummary | None¶
- art_104_tres_excluded_ledger_ids: tuple[str, ...]¶
- aggregate_iva_ledger_observations_from_repositories(*, bucket_id, period, prorrata_register_repository, transaction_repository, investment_asset_register=None, investment_asset_profile_id=None, operation)[source]¶
Load the bucket-local transaction catalogue and project IVA observations.
Returns an
IvaLedgerAggregation.- Parameters:
bucket_id (
str) – The profile bucket every supplied repository must be scoped to; a divergent repository bucket is refused, not silently used.period (
Period) – The filing period whose transactions are projected.prorrata_register_repository (
ProrrataRegisterRepositoryProtocol) – The canonical register repository read for the active general-prorrata provisional percentage. Required, so no caller can fall back to an implicitly constructed register whose bucket nobody checked.transaction_repository (
TransactionCatalogueRepositoryProtocol) – Required bucket-bound catalogue capability.investment_asset_register (
BienesInversionIvaRegister|None) – Bienes-inversion authority, mandatory whenevertransaction_repositoryis injected.investment_asset_profile_id (
str|None) – Profile owning that register.operation (
PinnedAuthorityOperation) – Existing generation-pinned authority operation owned by the enclosing workflow.
- Return type:
- aggregate_iva_ledger_observations(transactions, *, period, ledger_profile_id, investment_asset_register=None, investment_asset_profile_id=None, prorrata_apportionment=None, operation)[source]¶
Project classified ledger transaction tax facts into an
IvaLedgerAggregation.- Parameters:
transactions (
TransactionCatalogue) – TheTransactionCataloguesupplying active ledger entries.ledger_profile_id (
str) – Profile that owns the ledger facts.investment_asset_register (
BienesInversionIvaRegister|None) – Explicit Bienes register authority.investment_asset_profile_id (
str|None) – Profile that owns the Bienes register.prorrata_apportionment (
IvaLedgerProrrataApportionment|None) – Optional active general-prorrata percentage to apply later to deducible IVA cuota binding values.operation (
PinnedAuthorityOperation) – Existing generation-pinned authority operation owned by the enclosing workflow.
- Return type:
- resolve_iva_ledger_binding_values(revision, observations, *, prorrata_apportionment=None, operation)[source]¶
Resolve IVA ledger bindings, applying general-prorrata to deducible cuotas only.
- Parameters:
revision (
ModeloRevision) – TheModeloRevisionwhose IVA ledger bindings are resolved.observations (
Iterable[IvaLedgerObservation]) – TypedIvaLedgerObservationrows to aggregate.prorrata_apportionment (
IvaLedgerProrrataApportionment|None) – OptionalIvaLedgerProrrataApportionmentapplied only to deducible cuota bindings.operation (
PinnedAuthorityOperation) – Caller-owned pinned authority operation for governed IVA binding projections.
- Return type:
Under
regime == GENERALthe single provisional percentage multiplies every deducible cuota binding (LIVA art. 104). Underregime == ESPECIALthe deducible cuota is routed per the observation’sinput_classification(LIVA art. 106.Uno: exclusively-deductible 100%, exclusively-non-deductible 0%, common at the general percentage) by_apply_especial_apportionment(); the general-regime code path is unchanged.See also
resolve_ledger_iva_aggregation_binding_values()Registry selector resolver that produces the unapportioned binding values before this wrapper applies prorrata.
ProrrataRegisterEntrySource record for the active provisional percentage represented by
IvaLedgerProrrataApportionment.
- resolve_iva_differentiated_deduction_contributions(revision, observations, *, apportionment, operation)[source]¶
Expose the canonical sector apportionment as immutable per-kind outputs.
Core types:
ModeloRevision.- Return type:
- Parameters:
revision (ModeloRevision)
observations (Iterable[IvaLedgerObservation])
apportionment (IvaLedgerProrrataApportionment)
operation (PinnedAuthorityOperation)
- compute_annual_deducible_totals_by_regime(*, bucket_id, ejercicio, revision, prorrata_register_repository, transaction_repository, operation)[source]¶
Compute the ejercicio’s deducible IVA cuota under both prorrata regimes.
The plumbing for the LIVA art. 103.Dos.2.º mandatory-especial settlement check. Aggregates the ejercicio’s annual IVA observations ONCE (
aggregate_iva_ledger_observations_from_repositories()over the canonical0AannualPeriod), then resolvesresolve_iva_ledger_binding_values()TWICE over the same observations — once with a GENERAL-stamped and once with an ESPECIAL-stampedIvaLedgerProrrataApportionmentat the register’s resolved percentage — and sums the deducible-cuota binding ids under each. One aggregation, two apportionment passes through the one canonical resolver (aeat-calculation-aggregation); no second aggregation implementation is introduced.Returns
Nonewhen no register apportionment resolves for the ejercicio (prorrata inapplicable), when the register is sectorized (LIVA arts. 9.1.c / 101 — the art-103.Dos.2.º comparison composes per sector, a named v1 deferral), or when the revision declares no deducible cuota bindings. A negative deducible total (an adjustment-heavy degenerate case the art-103.Dos.2 comparison is undefined over) also returnsNoneso the check stays silent rather than crashing on a non-comparable input.- Parameters:
bucket_id (
str) – Active bucket whose annual ledger and prorrata register are read.ejercicio (
int) – The filing year whose annual deducible totals are computed.revision (
ModeloRevision) – The targetModeloRevisionwhose deducible cuota bindings are summed.transaction_repository (
TransactionCatalogueRepositoryProtocol) – Required bucket-bound catalogue capability.prorrata_register_repository (
ProrrataRegisterRepositoryProtocol) – Canonical register repository for the same bucket as the transaction catalogue.operation (
PinnedAuthorityOperation) – Caller-owned pinned authority operation retained through the annual aggregation.
- Return type:
See also
AnnualDeducibleTotalsByRegimeThe frozen record returned.
build_prorrata_especial_mandatory_advisory()Consumes the two totals to build the mandatory-especial advisory.
- validate_intracom_export_counterparty(*, transaction_id, category, counterparty_country, eu_member_state, identification_state, effective_date=None, operation)[source]¶
Return a gate issue when the counterparty/category coupling is violated.
Rules:
INTRA_COMMUNITY_SUPPLYrequires a non-ESidentification_state.Export and export-assimilated categories require the counterparty to be POSITIVELY established in a third country, and must carry no
EUMemberState.
The two rules read DIFFERENT facts, deliberately. Ley 37/1992 art. 25 exempts on the acquirer holding an IVA identification assigned by another Member State and says nothing about where it has its sede, so the intra-community rule reads identification and establishment does not enter it at all. The export rule is the one genuinely about place – an export leaves the Union – so it keeps reading establishment.
Absent identification refuses rather than falling back to
eu_member_state. That fallback is precisely the defect this shape replaced: it read an address fact as a registration fact, over-declaring a Spanish-established acquirer holding a German IVA number and silently under-declaring a German-established acquirer purchasing under a Spanish NIF-IVA.- Return type:
- Parameters:
transaction_id (str)
category (IvaCategory)
counterparty_country (str | None)
eu_member_state (EUMemberState | None)
identification_state (EUMemberState | None)
effective_date (date | None)
operation (PinnedAuthorityOperation)
- validate_iva_ledger_counterparty_category(transaction, *, operation)[source]¶
Return the D5 counterparty/category gate
IvaLedgerAggregationIssuefor a ledger transaction.- Return type:
- Parameters:
transaction (Transaction)
operation (PinnedAuthorityOperation)
- business_proportionality_for(transaction)[source]¶
- Return type:
- Parameters:
transaction (Transaction)
- has_converted_non_eur_amount(transaction)[source]¶
- Return type:
- Parameters:
transaction (Transaction)
- missing_tax_fact_reason(transaction)[source]¶
- Return type:
- Parameters:
transaction (Transaction)
- IVA_LEDGER_MISSING_FACT_REASONS: Final[frozenset[IvaLedgerAggregationIssueReason]]¶
Every reason
iva_ledger_missing_fact_reasons()can emit, derived.
- IVA_LEDGER_COUNTERPARTY_GATE_REASONS: Final[frozenset[IvaLedgerAggregationIssueReason]]¶
Every reason
validate_iva_ledger_counterparty_category()can emit.Declared rather than derived because the three branches read three different counterparty facts and collapsing them into one table would erase the legal distinction between identification and establishment the gate turns on. A behavioural gate exercises the real screen across the category matrix and asserts the observed emissions equal this set, so the declaration is pinned to behaviour rather than trusted.
- iva_ledger_missing_fact_reasons(transaction)[source]¶
Return missing IVA fact reasons for a transaction without projecting it.
Each element is an
IvaLedgerAggregationIssueReasondescribing one absent required tax fact.- Return type:
- Parameters:
transaction (Transaction)
- missing_tax_fact_detail(reason)[source]¶
- Return type:
- Parameters:
reason (IvaLedgerAggregationIssueReason)
- iva_rate_kind_for(rate, *, on_date, operation)[source]¶
Return the tier a declared rate belongs to, or
Noneif it is not one.Delegates to
rate_kinds_for_declared_rate(), the registry’s own value-to-tier direction. This previously iterated the tiers and called the tier-to-value lookup once each, comparing percentages – a simulation of the inverse that holds only while the mapping is one-to-one per date. Spain’s 2024 temporary food rates broke that: 2 % and 4 % were both correct super-reducido rates, and the simulation found neither for a 2 % row.When a rate matches more than one tier the FIRST registered match wins, and the ambiguity is real rather than a defect – the tiers genuinely share that rate on that date, and no bundled AEAT surface carries the goods axis that would separate them. Callers that must report the rate itself carry it separately on the observation.
- Return type:
- Parameters:
rate (Decimal)
on_date (date)
operation (PinnedAuthorityOperation)