cadrumo.domain.iva.classification module¶
Generic IVA classification mechanics (issuer / customer / kind / direction).
Layered on top of the cadrumo.domain.iva substrate (the
cadrumo.domain.iva.schema.IvaCategory enum, cadrumo.domain.iva.schema.IvaRateRecord
records, and cadrumo.domain.iva.lookup.lookup_rate()), this module adds the
classification axes needed to tag a transaction deterministically based on
the parties’ tax residency, the customer’s IVA status, the transaction kind,
and the invoice direction.
The legal decision table is registry-owned. This module supplies only the typed criteria/result records and the first-match evaluator that consumes a registry-projected sequence of rule definitions. No legal rows, labels, priorities, or fallback treatment are authored here.
Examples
>>> from datetime import date
>>> from . import (
... EUMemberState,
... InvoiceKind,
... TransactionKind,
... IvaRateKind,
... IvaInvoiceClassificationCriteria,
... customer_tax_status_alias,
... territorial_scope_alias,
... require_eu_member_state,
... classify_iva,
... )
>>> criteria = IvaInvoiceClassificationCriteria(
... transaction_date=date(2025, 6, 15),
... issuer_residency=territorial_scope_alias("mainland"),
... customer_residency=territorial_scope_alias("eu_member"),
... customer_identification_state=require_eu_member_state("DE"),
... customer_tax_status=customer_tax_status_alias("b2b_registered"),
... kind=registry_transaction_kind,
... direction=InvoiceKind.ISSUED,
... )
>>> classify_iva(criteria, rules=registry_rules).category
'intra_community_supply'
- class IvaTerritorialScope(value, *, _registry_validated=False)[source]¶
Bases:
_RegistryProjectedTokenRegistry-projected territorial scope token for an invoice party.
- class PartyFact(*values)[source]¶
Bases:
StrEnumThe two legally distinct facts a rule may need about a party.
They were one output before they were two, and the conflation had a direction. A printed foreign IVA prefix was read as decisive EVIDENCE OF PLACE, so a German-identified entity actually established in Spain resolved silently to a registry-projected EU-member token and fed the table as settled fact — while the mirror case, a non-resident holding a Spanish registration, was correctly refused. Every Member State registers non-residents on the same terms Spain does, so the two are one situation seen from two sides; splitting the fact restores the symmetry at the principle instead of patching it at one rung.
Naming them lets a branch DECLARE which it consumes, so an operator is asked only for what the branch it lands on actually turns on. The domestic and territorial rules need the place and not the identification. The intra-community families need the identification, and need the place only narrowly beside it — not to say which Member State a party belongs to, which is the conflation, but to place the supply in the peninsula and keep the Spanish territories out of “otro Estado miembro”. A row that reads a residency is not thereby reading it as a registration.
- Variables:
IVA_IDENTIFICATION_STATE – The Member State under whose IVA identification the party operates, carried as
cadrumo.domain.iva.schema.EUMemberState. Registration evidence settles it decisively, because registration is precisely what it asserts.TERRITORIAL_ESTABLISHMENT – Where the party has its sede de actividad económica or an establecimiento permanente (Ley 37/1992 arts. 69-70), carried as
IvaTerritorialScope. NO registration evidences it, foreign or Spanish.
- IVA_IDENTIFICATION_STATE¶
- TERRITORIAL_ESTABLISHMENT¶
- class InvoiceKind(*values)[source]¶
Bases:
StrEnumWhether the autónomo issued or received the invoice.
Single canonical enum spanning both the substrate classifier (
IvaInvoiceClassificationCriteria.direction) and ledger / invoice records (Invoice.kind). Replaces the prior split betweenInvoiceDirection(substrate) andInvoiceKind(invoices) that carried identical semantics with mismatched lowercase / uppercase string values. Values are lowercase to align with TOML registry selectors (invoice_direction = "issued").- Variables:
ISSUED – The autónomo is the issuer (sale).
RECEIVED – The autónomo is the recipient (purchase).
- ISSUED¶
- RECEIVED¶
- class CustomerTaxStatus(value, *, _registry_validated=False)[source]¶
Bases:
_RegistryProjectedTokenRegistry-projected customer IVA-status token.
- class IvaClassificationCatalogue(territorial_scopes, customer_tax_statuses, territorial_aliases, customer_status_aliases)[source]¶
Bases:
objectTyped projection of the 0083 territorial/status vocabularies.
- Parameters:
territorial_scopes (tuple[IvaTerritorialScope, ...])
customer_tax_statuses (tuple[CustomerTaxStatus, ...])
territorial_aliases (Mapping[str, IvaTerritorialScope])
customer_status_aliases (Mapping[str, CustomerTaxStatus])
-
territorial_scopes:
tuple[IvaTerritorialScope,...]¶
-
customer_tax_statuses:
tuple[CustomerTaxStatus,...]¶
-
territorial_aliases:
Mapping[str,IvaTerritorialScope]¶
-
customer_status_aliases:
Mapping[str,CustomerTaxStatus]¶
- property territorial_scope_set: frozenset[IvaTerritorialScope]¶
Return all territorial tokens declared by the selected fact.
- property customer_tax_status_set: frozenset[CustomerTaxStatus]¶
Return all customer-status tokens declared by the selected fact.
- require_territorial_scope(value)[source]¶
Validate and return one registry-projected territorial token.
- Return type:
- Parameters:
value (object)
- require_customer_tax_status(value)[source]¶
Validate and return one registry-projected customer-status token.
- Return type:
- Parameters:
value (object)
- territorial_scope_alias(alias)[source]¶
Return a named territorial alias from the selected fact.
- Return type:
- Parameters:
alias (str)
- class TransactionKindDefinition(token, description, legal_refs=None, supply_nature=None, oss_regime=None, default_rate_kind=None)[source]¶
Bases:
objectOne registry-declared transaction-kind token and its semantics.
- Parameters:
-
token:
TransactionKind¶
- class TransactionKindCatalogue(definitions, common_semantics, union_scheme_semantics)[source]¶
Bases:
objectTyped projection of the dated IVA classification kind vocabulary.
- Parameters:
definitions (tuple[TransactionKindDefinition, ...])
common_semantics (str)
union_scheme_semantics (str)
-
definitions:
tuple[TransactionKindDefinition,...]¶
- property all_kinds: frozenset[TransactionKind]¶
Return every registry-declared transaction-kind token.
- require(value)[source]¶
Validate one opaque token against this registry projection.
- Return type:
- Parameters:
value (object)
- for_supply_nature(supply_nature)[source]¶
Return the unique kind projection for a registry supply-nature token.
- Return type:
- Parameters:
supply_nature (str)
- resolve_iva_classification_catalogue(effective_date=None, *, operation)[source]¶
Resolve the territorial and customer-status vocabulary from fact 0083.
- Return type:
- Parameters:
effective_date (date | None)
operation (PinnedAuthorityOperation)
- require_iva_territorial_scope(value, *, effective_date=None, operation)[source]¶
Return a territorial scope only when 0083 declares it.
- Return type:
- Parameters:
value (object)
effective_date (date | None)
operation (PinnedAuthorityOperation)
- require_customer_tax_status(value, *, effective_date=None, operation)[source]¶
Return a customer status only when 0083 declares it.
- Return type:
- Parameters:
value (object)
effective_date (date | None)
operation (PinnedAuthorityOperation)
- territorial_scope_alias(alias, *, effective_date=None, operation)[source]¶
Return one named territorial projection from fact 0083.
- Return type:
- Parameters:
alias (str)
effective_date (date | None)
operation (PinnedAuthorityOperation)
- customer_tax_status_alias(alias, *, effective_date=None, operation)[source]¶
Return one named customer-status projection from fact 0083.
- Return type:
- Parameters:
alias (str)
effective_date (date | None)
operation (PinnedAuthorityOperation)
- resolve_transaction_kind_catalogue(effective_date, *, operation)[source]¶
Resolve all transaction-kind membership through the 0083 fact query.
- Return type:
- Parameters:
effective_date (date)
operation (PinnedAuthorityOperation)
- require_transaction_kind(value, *, effective_date, operation)[source]¶
Return one registry-declared transaction-kind token or refuse it.
- Return type:
- Parameters:
value (object)
effective_date (date)
operation (PinnedAuthorityOperation)
- domestic_rate_tier_is_required(*, issuer_residency, customer_residency, kind, transaction_date=None, customer_tax_status=None, art_69_dos_service=None, operation)[source]¶
Return whether any registry row this operation can reach derives its category from a rate tier.
Asked of the registry-projected rows themselves rather than of a second list of exempt kinds or territories: the operation needs a tier exactly when its first matching row is a
rate_categories[rate_tier]row. A still-open customer status is a union over every status, so an open status demands the tier rather than excusing it, and the direction is unioned the same way. Identification is probed absent: a row that needs one cannot then preempt a later rate-tier row, which errs toward asking.- Return type:
- Parameters:
issuer_residency (IvaTerritorialScope)
customer_residency (IvaTerritorialScope)
kind (TransactionKind)
transaction_date (date | None)
customer_tax_status (CustomerTaxStatus | None)
art_69_dos_service (IvaArt69DosService | None)
operation (PinnedAuthorityOperation)
- class IvaInvoiceClassificationCriteria(**data)[source]¶
Bases:
_IvaStrictFrozenInput record for
classify_iva().Carries every axis the closed decision table inspects. The record is strict and frozen so it can be used as a dict key in upstream caches.
The two party facts are carried separately and are never derived from each other (
PartyFact). The residency fields are the TERRITORIAL ESTABLISHMENT fact; the identification-state fields are the IVA IDENTIFICATION STATE fact. A party may hold a German identification while being established in Spain, or a Spanish one while established abroad — both are ordinary, and a model that could not express them forced the reader to pick one meaning for a value that had two.- Variables:
transaction_date – When the supply takes place.
issuer_residency – Where the issuer is ESTABLISHED — its sede or establecimiento permanente under Ley 37/1992 arts. 69-70. Never a statement about where it is registered. The field name keeps the role label; the type carries the territorial framing.
customer_residency – The same for the customer.
customer_tax_status – Customer’s IVA status.
kind – Kind of supply.
direction –
ISSUEDorRECEIVED.issuer_identification_state – The
cadrumo.domain.iva.schema.EUMemberStateunder whose IVA identification the issuer operates, where established. Optional independently ofissuer_residency: an EU establishment does not supply an identification and an identification does not supply an establishment, so demanding one because of the other would be the conflationPartyFactexists to end. Branches that need it declare so, and the producer demands it only for those.customer_identification_state – The same for the customer.
rate_tier – Explicit rate-tier axis for ES-to-ES domestic rules.
- Parameters:
data (Any)
- transaction_date: date¶
- issuer_residency: IvaTerritorialScope¶
- customer_residency: IvaTerritorialScope¶
- customer_tax_status: CustomerTaxStatus¶
- kind: TransactionKind¶
- direction: InvoiceKind¶
- issuer_identification_state: EUMemberState | None¶
- customer_identification_state: EUMemberState | None¶
- art_69_dos_service: IvaArt69DosService | None¶
- rate_tier: IvaRateKind | None¶
- class IvaClassificationResult(**data)[source]¶
Bases:
_IvaStrictFrozenOutput record returned by
classify_iva().Exposes the matched
cadrumo.domain.iva.schema.IvaCategory, the resolvedcadrumo.domain.iva.schema.IvaRateRecord(orNonefor rate-irrelevant categories), a reverse-charge flag, the matched rule identifier, and any free-form note the resolver emits (typically used for fall-through documentation).- Variables:
category – Resolved IVA category.
rate – Applicable
cadrumo.domain.iva.schema.IvaRateRecord, when relevant.requires_reverse_charge –
Truewhen the rule triggers inversión del sujeto pasivo.matched_rule_id – Stable rule identifier (e.g. supplied by the selected registry revision.
notes – Free-form explanatory note.
consumes_party_facts – Which
PartyFactvalues the matched branch actually turns on. This is how a producer assembling the criteria learns what to demand without holding a second copy of the law: it asks the table which facts the branch consumes rather than hand-writing a rule about the territorial scopes, which is the duplication the lazy-requirement mechanism already refuses elsewhere. Defaults to BOTH facts, so a row or result that forgets to declare demands everything — the fail-toward-asking direction, and the one where forgetting costs a question rather than a silent classification on a fact nobody supplied.place_of_supply –
The registry row that grounds the matched rule’s placement in the operation’s filing year — which provision locates the operation, and whether that provision fixes the nature of the supply.
classify_iva()always stamps it;Nonesurvives only on a result assembled by hand, and therefore means “no placement was resolved”, never “the resolution failed”. A lookup that fails raises out ofclassify_iva()instead of arriving here as an absence.Read the nature off
supply_nature: aNonethere is the third state, and it is a statement about the statute — the cited provisions are silent on whether goods or services were supplied. It is never an unfilled row.What this carries is registry-declared authority for the rule the table matched. It is not an AEAT ruling on the specific operation, and a consumer must not present it as one.
- Parameters:
data (Any)
- category: IvaCategory¶
- rate: IvaRateRecord | None¶
- requires_reverse_charge: bool¶
- matched_rule_id: str¶
- notes: str¶
- consumes_party_facts: frozenset[PartyFact]¶
- exemption_article: IvaExemptionArticle | None¶
- place_of_supply: IvaPlaceOfSupplyRule | None¶
- domestic_categories_by_rate_kind(mapping=None)[source]¶
Expose a registry-projected rate/category mapping as a read-only view.
- Return type:
- Parameters:
mapping (Mapping[IvaRateKind, IvaCategory] | None)
- rate_kind_for_domestic_category(category, mapping=None)[source]¶
Resolve a domestic category through a caller-supplied registry mapping.
- Return type:
- Parameters:
category (IvaCategory)
mapping (Mapping[IvaRateKind, IvaCategory] | None)
- class IvaClassificationRule(rule_id, predicate, category=None, description='', consumes=frozenset({}), requires_reverse_charge=False)[source]¶
Bases:
NamedTupleOne registry-projected classification row consumed by the evaluator.
- Parameters:
rule_id (str)
predicate (Callable[[IvaInvoiceClassificationCriteria], bool])
category (IvaCategory | None)
description (str)
requires_reverse_charge (bool)
-
predicate:
Callable[[IvaInvoiceClassificationCriteria],bool]¶ Alias for field number 1
-
category:
IvaCategory|None¶ Alias for field number 2
- class IvaClassificationInputs(rules, rate_categories, rate_territories)[source]¶
Bases:
objectComplete typed projection consumed by the IVA classifier.
The fact registry stores rule predicates as a small declarative language. This record is the only boundary that turns that language into executable predicates and rate/category mappings; the evaluator itself remains a generic first-match consumer.
- Parameters:
rules (tuple[IvaClassificationRule, ...])
rate_categories (Mapping[IvaRateKind, IvaCategory])
rate_territories (frozenset[IvaTerritorialScope])
-
rules:
tuple[IvaClassificationRule,...]¶
-
rate_categories:
Mapping[IvaRateKind,IvaCategory]¶
-
rate_territories:
frozenset[IvaTerritorialScope]¶
- matches_nothing(criteria)[source]¶
Predicate of the registry’s terminal no_match row, which reads no axis.
- Return type:
- Parameters:
criteria (IvaInvoiceClassificationCriteria)
- resolve_iva_classification_inputs(*, effective_date, operation)[source]¶
Project the dated classification fact into typed evaluator inputs.
- Return type:
- Parameters:
effective_date (date)
operation (PinnedAuthorityOperation)
- classify_iva(criteria, *, rules=None, rate_categories=None, rate_territories=None, operation)[source]¶
Evaluate registry-projected rows in their supplied order.
This function intentionally has no module-owned table or fallthrough row.
rulesand therate_categories/rate_territoriesmappings are registry projections; when omitted they are projected fromoperationfor the criteria’s transaction date.- Return type:
- Parameters:
criteria (IvaInvoiceClassificationCriteria)
rules (Iterable[IvaClassificationRule] | None)
rate_categories (Mapping[IvaRateKind, IvaCategory] | None)
rate_territories (frozenset[IvaTerritorialScope] | None)
operation (PinnedAuthorityOperation)