aeat.domain.iva._saturation module¶
Grounded saturation primitives for the aeat.domain.iva subpackage.
Two reusable primitives that let a caller turn a selected
IvaCategory and a transaction gross into the regulated euro
substrate (rate, taxable base, IVA amount) without ever guessing a
number:
resolve_category_rate()maps anIvaCategoryto itsIvaRateKindand looks the applicable rate up viaaeat.domain.iva.lookup_rate(), returning it as a decimal fraction (Decimal("0.21")) wrapped in a typedIvaRateResolution. Domestic general / reduced / super-reduced derive a positive rate; domestic zero and exempt derive0; every category with no simple derivable positive domestic rate (intra-community, export, reverse-charge, recargo, import, régimen simplificado, no-sujeta, erroneous, unknown) returns aderivable=Falseresolution carrying an explicit operator-facing reason — never a fabricated rate.split_gross_at_rate()performs the inverse split of a gross at a rate fraction into(taxable_base, iva_amount)quantised with the AEAT-mandatedaeat.core.money.round_to_cents()(ROUND_HALF_UP).
The split formula is the canonical inverse of an IVA-inclusive gross:
base = round_to_cents(gross / (1 + rate)) and
iva = round_to_cents(gross - base). Quantising the base first and
deriving the IVA as the remainder guarantees base + iva == gross to
the cent regardless of the rounding residual, which is exactly the
invariant the aeat.domain.transactions.Transaction model
enforces.
Authority: 2026-06-04-llm-ledger-classification-adr. The rate values
are grounded in registry/aeat/iva/rates.toml (Spain general 21 /
reduced 10 / super-reduced 4 / zero 0; LIVA art. 90/91, year-scoped).
- class IvaRateResolution(**data)[source]¶
Bases:
BaseModelTyped outcome of resolving an
IvaCategoryto a rate fraction.A resolution is self-documenting: it never silently substitutes a fabricated rate for a category the system cannot ground. When
derivableisTruetheratecarries the applicable rate as a decimal fraction in[0, 1](e.g.Decimal("0.21");Decimal("0")for zero-rated and exempt categories). WhenderivableisFalsetherateisNoneandreasonstates why the operator must complete the field.- Variables:
category – The
IvaCategorythat was resolved.derivable –
Truewhen a Spanish domestic rate fraction was derived,Falsewhen the category has no simple derivable domestic rate and the operator must complete it.rate – The applicable IVA rate as a decimal fraction in
[0, 1]whenderivable, elseNone.rate_kind – The
IvaRateKindthe category maps to whenderivable, elseNone.reason – An operator-facing explanation present only when the rate is not derivable; the empty string otherwise.
- Parameters:
category (IvaCategory)
derivable (bool)
rate (Decimal | None)
rate_kind (IvaRateKind | None)
reason (str)
- category: IvaCategory¶
- derivable: bool¶
- rate: Decimal | None¶
- rate_kind: IvaRateKind | None¶
- reason: str¶
- resolve_category_rate(category, *, on_date)[source]¶
Resolve an
IvaCategoryto its Spanish IVA rate fraction.Maps
categoryto itsIvaRateKindand looks the applicable rate up viaaeat.domain.iva.lookup_rate()forEUMemberState.ESonon_date, returning the percentage as a decimal fraction (IvaRateRecord.pct / 100). Domestic general / reduced / super-reduced derive a positive fraction; domestic zero and exempt deriveDecimal("0"). Every category with no simple derivable positive domestic rate returns aderivable=Falseresolution carrying an operator-facing reason — the system never guesses a rate for those.- Parameters:
category (
IvaCategory) – The selected IVA category to resolve.on_date (
date) – The effective date used to resolve the registry rate.
- Return type:
- Returns:
A typed
IvaRateResolution.derivableisTruewith aratefraction for domestic categories;Falsewith areasonfor every non-derivable category.- Raises:
IvaRateNotFoundError – If a domestic category maps to a rate kind that the registry has no record for on
on_date(a registry gap, not a category-shape problem).
- split_gross_at_rate(gross, rate)[source]¶
Inverse-split an IVA-inclusive gross into
(taxable_base, iva_amount).Computes the IVA-exclusive base and the IVA charged from an IVA-inclusive gross at
rate(a decimal fraction, e.g.Decimal("0.21")), quantising with the AEAT-mandatedaeat.core.money.round_to_cents()(ROUND_HALF_UP). The base is quantised first and the IVA is taken as the quantised remainder (gross - base), sotaxable_base + iva_amount == grossholds to the cent regardless of the rounding residual.A
rateof0(zero-rated or exempt) yields the whole gross as the base and a zero IVA amount.- Parameters:
- Return type:
- Returns:
A
(taxable_base, iva_amount)tuple, each quantised to euro cents, whose sum equalsround_to_cents(gross)to the cent.