cadrumo.application.ledger.classification_assembly module¶
Assemble the criteria the IVA rule table consumes, or refuse and say what is missing.
The rule table in domain.iva has always been complete and has never been
reachable: its criteria record was constructed nowhere in production — only in
its own docstring example and in tests — so no real document ever met it. This
module is the missing producer.
It refuses far more often than it answers, and that is the deliverable. An unreachable classifier replaced by one that answers on incomplete evidence would be a worse product, not a better one: the gap stops being visible and starts being a number on a filing. So every input the table needs is either established or named as missing, and a refusal lists exactly which authority would settle it.
Two of those authorities are absent by design rather than by omission:
Registration status needs VIES. A printed IVA identifier establishes that someone is acting as a taxable person, not that their number is registered and valid — and the registered status is what triggers the intra-community supply exemption. Inferring it from a printed number would zero-rate a taxable sale on evidence nobody verified.
That refusal is real, and it is also lazy: it is only raised where the answer could change the verdict. The domestic rule consults the customer’s status ONLY to route the three reverse-charge kinds and the exempt immovable supply, none of which a printed goods-or-services reading can produce — so on an ordinary domestic invoice every status reaches the identical category, and demanding one asked the operator a question with no consequence on the commonest document there is. The same table that judges the supply nature judges this, by the same means and under the same fail-toward-asking rule.
Spanish territorial scope needs sub-national evidence. A country code names the State while the IVA territory inside it stays undetermined, and Spain holds three that the law treats differently. The printed postal code is that sub-national evidence — its first two digits are the province — so a Spanish party is now resolved from the country and postal codes together.
Two limits on that join, both deliberate. It is gated on Spain having been NAMED, never on the country resolver merely returning nothing, because five-digit postal codes are not unique to Spain. And an absent or unreadable postal code refuses rather than resolving to the mainland: the peninsula is the majority population, so that default would be invisible in testing while placing Canarian and Ceutan parties inside a territory their operations are not subject to.
What still does not resolve is an ordinary domestic invoice, and the reason is upstream of this module. Establishment evidence reaches it as a printed country code, and a domestic Spanish invoice frequently prints no country at all while its bare tax identifier carries no country prefix. A valid Spanish tax identifier cannot stand in for one: the non-resident CIF leader, the K/L/M identifiers issued to Spaniards abroad and to non-residents, and the NIE series all belong to parties who are not established in Spain — and establishment for IVA is the sede de actividad, not tax registration. So that population refuses here, correctly, until the evidence question is settled.
A party’s IVA identification is a THIRD thing, asked separately and demanded
rarely. Where a party is established and which Member State identifies it are
two facts (PartyFact), and this module resolves them from
different evidence on purpose: the identification from the party’s own printed
IVA number, which settles it decisively because registration is precisely what
it asserts; the establishment from the country and postal evidence, which no
registration can supply on either side. The identification is then demanded only
where the branch the operation reaches declares it consumed — the
intra-community families, whose treatment is reported against a NIF-IVA — so the
foreign goods population resolves with no operator question while a domestic
invoice is never asked for a number its treatment does not turn on.
Both are settleable by an explicit operator assertion, which is the sanctioned path until those authorities exist. An assertion is the operator’s claim, made knowingly; a default would be ours, made silently.
See also
classify_iva()The single rule table. This module produces its input and never duplicates its decisions.
ClassifierInputsThe evidence-and-profile facts this assembly draws on.
- class MissingClassifierInput(**data)[source]¶
Bases:
BaseModelOne input the table needs that the evidence did not establish.
- Variables:
field – The criteria field that could not be filled.
reason – What was tried and why it did not settle the question.
settled_by – The authority or assertion that would settle it. Never empty — a refusal an operator cannot act on is a dead end.
- Parameters:
data (Any)
- field: str¶
- reason: str¶
- settled_by: str¶
- class ClassificationAssembly(**data)[source]¶
Bases:
BaseModelEither the assembled criteria, or every input that stopped it.
- Variables:
criteria – The record the rule table consumes, when every input was established.
Noneotherwise.missing – What stopped it. Empty exactly when
criteriais present.
- Parameters:
data (Any)
- criteria: IvaInvoiceClassificationCriteria | None¶
- missing: tuple[MissingClassifierInput, ...]¶
- names_spain(country_code)[source]¶
Whether the printed country evidence POSITIVELY names Spain.
Asked instead of reading the country resolver’s
Noneas “Spanish”, because that return collapses three different situations: an absent code, a code too malformed to be one, and Spain. Measured against the live resolver, every one ofNone,'','ESP','E1'and'ES'yieldsNone, so a caller branching on it cannot tell “the operator must supply a country” from “the operator must supply a postal code”.That distinction is load-bearing here rather than cosmetic: a postal code is consulted only when Spain was named, so reading absence as Spain would feed a foreign five-digit code to the Spanish province lookup.
Compares against the shipped
SPAIN_COUNTRY_CODEafter the same trim and case fold the resolver applies, and deliberately re-derives no shape check — onlyEScan equal the constant, so the well-formedness question never arises and there is no second copy of it to drift.
- class DeclaredFact(**data)[source]¶
Bases:
BaseModel,GenericOne fact supplied to the criteria, beside who established it.
The value and its attribution travel together because they are one claim. Passing the value alone – which the flat
asserted_*parameters this replaces did – loses WHO said it at the boundary, and the provenance stamp then has to guess or stay silent about a fact the classification stood on.- Variables:
value – The fact itself, in its own closed type.
source – Who established it. Reuses the shipped
ClassifierInputSourcerather than declaring a second source vocabulary: the audit envelope already speaks it, so one spelling flows from this channel throughClassifierInputFactto the stamp. A private enum here would have been a second authority on the one question “who says so”.
- Parameters:
value (T)
source (ClassifierInputSource)
- value: T¶
- source: ClassifierInputSource¶
- class DeclaredFacts(**data)[source]¶
Bases:
BaseModelEvery fact supplied into one classification, whoever established it.
Extended by adding a FIELD, never a second route. This is the whole reason it is a model rather than more keyword parameters: a later stage with a new fact to contribute adds an attribute here and the assembly, the envelope and the stamp carry it without a new channel. A second supply route would fork the attribution the same way the four flat parameters forked it, and the fork is invisible until an auditor asks who said what.
Every field is optional. An absent fact is not a supplied
None: it means nobody established it, which is exactly what the assembly reports as a missing input rather than papering over.- Variables:
supply_nature – What the operation supplies, where established.
customer_tax_status – The customer’s IVA status – the sanctioned way to supply what a VIES consultation would otherwise settle.
issuer_scope – Where the issuer is ESTABLISHED, where a country code cannot settle it. Never supplies the identification state: they are two facts (
PartyFact), and an operator asserting where a party operates from has not thereby said which State registered it.customer_scope – The customer’s establishment, on the same terms.
issuer_identification_state – Which Member State IVA-identifies the issuer, where no IVA number was printed to establish it. Never supplies the establishment, symmetrically.
customer_identification_state – The same for the customer.
stated_category – The IVA treatment the document’s own machine-readable record declares. Supplied as a FACT rather than used as a category in its own right: it is evidence about the operation on the same footing as the country codes beside it, and
resolve_ingestion_iva_category()is the one place it is weighed against what the rule table reaches.
- Parameters:
data (Any)
- supply_nature: DeclaredFact[SupplyNature] | None¶
- customer_tax_status: DeclaredFact[CustomerTaxStatus] | None¶
- issuer_scope: DeclaredFact[IvaTerritorialScope] | None¶
- customer_scope: DeclaredFact[IvaTerritorialScope] | None¶
- issuer_identification_state: DeclaredFact[EUMemberState] | None¶
- customer_identification_state: DeclaredFact[EUMemberState] | None¶
- stated_category: DeclaredFact[IvaCategory] | None¶
- assemble_classification_criteria(*, operation, transaction_date, direction, inputs, declared, issuer_country_code=None, customer_country_code=None, issuer_postal_code=None, customer_postal_code=None, issuer_identifier=None, customer_identifier=None, rate_tier=None)[source]¶
Assemble the rule table’s criteria, or return every input that stopped it.
Accumulates rather than short-circuits: an operator who has to resolve four missing inputs should learn all four at once rather than one per attempt.
- Parameters:
operation (
PinnedAuthorityOperation) – Caller-owned pinned authority operation used for every dated classification and vocabulary projection.transaction_date (
date|None) – When the supply took place.direction (
InvoiceKind) – Issued or received, as the operator settled it at confirm.inputs (
ClassifierInputs) – The evidence-and-profile facts collected for this document.declared (
DeclaredFacts) – The facts supplied into this classification, each carrying who established it. Replaces the flatasserted_*parameters, which could carry a value but not its attribution.issuer_country_code (
str|None) – The issuer’s printed country code, if any.customer_country_code (
str|None) – The customer’s printed country code, if any.issuer_postal_code (
str|None) – The issuer’s printed postal code, if any. Consulted only when the country evidence names Spain, to separate the three Spanish IVA territories a country code cannot tell apart.customer_postal_code (
str|None) – The same for the customer. Asked of each party independently, because an issuer in Las Palmas invoicing a customer in Madrid crosses a territorial boundary one shared code could not express.issuer_identifier (
str|None) – The issuer’s printed tax identifier, if any. Read for the IVA IDENTIFICATION state only — a separate axis from the postal and country evidence above, which answer where the party is.customer_identifier (
str|None) – The same for the customer.rate_tier (
IvaRateKind|None) – The rate tier, required by the criteria model for ES-to-ES domestic operations.
- Returns:
the criteria, or the missing inputs.
- Return type:
- classify_from_assembled_criteria(assembly, *, operation)[source]¶
Run the single rule table over assembled criteria, or return
None.A thin call rather than a second decision surface. Every classification judgement stays in
classify_iva(); this module’s whole contribution is deciding whether the table may be consulted at all.- Return type:
- Parameters:
assembly (ClassificationAssembly)
operation (PinnedAuthorityOperation)
- declared_category_from_document_record(printed_code, *, operation)[source]¶
Read the IVA treatment a document’s own machine-readable record declares.
The one place a category is built from a document token, and it is here rather than beside the reader on purpose: interpreting what the operation’s treatment is belongs to the classification authority, and a reader that minted the value itself was a second authority answering the same question from weaker evidence.
The UNTDID 5305 tax-category code is a fact only a structured reader can recover: it is IN the document and no text or vision reader can supply it. Dropping it is not a missing label but a missing declaration. A domestic reverse charge, an exempt supply and a zero-rated supply all print a base and no cuota, so once the code is gone the record cannot be told apart from an ordinary zero-cuota supply – and the self-assessed output IVA a reverse charge obliges, which Modelo 303 collects in its own inversión del sujeto pasivo tier, is never assessed at all.
A standard-rated supply maps to the empty string rather than a member: the rate itself carries the meaning there and there is no special category to state, so it resolves to
Noneexactly as an absent code does.An unrecognised token also resolves to
Nonerather than raising. The parser only ever writes values from its own closed UNTDID mapping, so a token outside it means that mapping changed shape; refusing the whole confirm over a label the operator can supply themselves would block a filing the rest of the record fully supports.- Parameters:
printed_code (
IvaCategory|str|None) – The tax-category token the document’s record carried.operation (
PinnedAuthorityOperation) – Caller-owned pinned authority operation used to validate and project the category token.
- Return type:
DeclaredFact[IvaCategory]|None- Returns:
The declaration beside its attribution, or
Nonewhen the document states no special category.
- class IvaCategoryResolution(**data)[source]¶
Bases:
BaseModelThe category one confirmed document resolves to, and what established it.
- Variables:
outcome – Which of the five states applies.
category – The resolved treatment.
NoneonCONTRADICTEDandUNRESOLVEDalike – on the same termsLegendDerivationwithholds one, so a caller cannot hold the value while ignoring the conflict that produced it.classified – What the rule table reached from the operation’s own facts, where it could place the operation at all. Carried beside the verdict rather than folded into it, so a contradiction can name both halves of what disagreed.
declared – What the document’s own record declared, where it did.
note – Operator-facing explanation, populated on
CONTRADICTEDto say what disagreed with what.
- Parameters:
data (Any)
- outcome: IvaCategoryOutcome¶
- category: IvaCategory | None¶
- classified: IvaCategory | None¶
- declared: DeclaredFact[IvaCategory] | None¶
- note: str¶
- resolve_ingestion_iva_category(assembly, *, operation, declared, rate_tier=None, counterparty_country_status=None, direction=None)[source]¶
Resolve the IVA category of one document being ingested.
The single production surface that decides an ingested record’s IVA category. Two rival deriving surfaces once sat on the confirm path ahead of the rule table and reached it never: one read the document’s declared tax-category code, the other re-derived a domestic category from the rate. Both are still consulted and neither decides any more – the code arrives as a declared FACT and the rate as the criteria’s own
rate_tieraxis, which is what ruleR05already consults through the mapping the rate surface used to copy.Six outcomes, and “probably” is not one, matching the legend axis this sits beside:
The table places the operation and the code agrees –
CORROBORATED.The table places it and the document declared no code –
CLASSIFIED. A standard-rated supply states no special category because the rate is the meaning, so there is nothing to corroborate against.The document declares a code the table could not reach –
DECLARED. The reverse-charge, exempt and zero-rated population, whose treatments turn on facts a printed page does not carry.They disagree, or the declared code disagrees with the tier charged –
CONTRADICTED, carrying no category. Which half is wrong is not decided here. A wrong category is worse than an absent one, because an absent category asks the operator and a wrong one does not; and the two halves lead to different declarations, so picking silently picks a filing.The table refused, no code was declared, and the document charged a registered Spanish tier –
RATE_INFERRED. The weakest answer, and the one that keeps the commonest document declarable. The table refuses whenever a party’s territory is unestablished, which an ordinary domestic invoice printing no country routinely leaves so; without this branch those records reach the invoice decomposition contract undeclared and the renta income path contributes their bank cash instead of their ingresos íntegros. What carries it is the charged tax rather than a default – Canarias and Ceuta y Melilla levy IGIC and IPSI, not IVA. The outcome names the inference but nothing yet shows it to an operator: it is neither persisted nor emitted, so a record resting on it currently reads exactly like one the rule table placed.Nothing established one –
UNRESOLVED. An honest blank, never a restrictive provision applied as a default.
- Parameters:
assembly (
ClassificationAssembly) – The criteria the rule table consumes, or the inputs that stopped them.declared (
DeclaredFacts) – The facts supplied into this classification, whosestated_categorycarries the document’s own declaration.rate_tier (
IvaRateKind|None) – The tier the document’s lines charged, for corroboration. The SAME value handed toassemble_classification_criteria(), so the two cannot read the document differently.direction (
InvoiceKind|None) – Which side of the invoice the filer is on. Reaches the relief guard, where it says which residency slot the counterparty occupies and therefore which one the catalogue-gap exemption may forgive. Omitted, the exemption forgives nothing.counterparty_country_status (
StatedCountryCodeStatus|None) – What the counterparty’s printed country code turned out to be, orNonewhen none was printed. Consulted only to spare a relief claim whose establishment failed on OUR vocabulary rather than on the document.operation (
PinnedAuthorityOperation) – Caller-owned pinned authority operation retained through the table and category resolution.
- Returns:
the resolved treatment and what established it, or the conflict that stopped it.
- Return type: