cadrumo.domain.iva.legend_derivation module¶
Deriving an IVA category from the regime mention the issuer printed.
The narrowest axis of the deterministic classifier, and the only one that needs nothing but the document. Spanish law does not leave the regime of an operation to be inferred: RD 1619/2012 art. 6.1 obliges the issuer to print a fixed mención whenever certain regimes apply, so for those cases the paper states the regime in words the regulation itself sets. Copying a printed phrase is something a reading stage may do; concluding what that phrase declares is something deterministic code may do. Neither step guesses.
Why this axis rather than the full rule table.
classify_iva() already exists and is the single classifier for
this domain; nothing here duplicates or competes with it. That table resolves the
categories that depend on WHO the counterparty is – their tax status and
residency – and customer_tax_status is a profile fact rather than a document
fact, which is exactly why it cannot be derived from evidence alone. The legend
axis needs neither input. It reports only what the issuer themselves declared in
writing, so it never enters that table’s domain and the two cannot silently
disagree.
Three outcomes, and “probably” is not one. A legend is present and consistent with the tax evidence on the page, or it contradicts that evidence, or there is nothing to derive from. No score, no ranking, no numeric confidence anywhere: those exist to let a caller pick a threshold, and there is no honest threshold between “the issuer declared this” and “the issuer did not”.
The absent outcome is the common one, and it must stay that way. The selected registry vocabulary has seven rows and exactly one declares a category, because that is the measured state of the regulation rather than an unfinished table. Most invoices legitimately derive nothing here. The pressure to close that gap with a default – treating an unstated counterparty country as domestic, or a legend-less invoice as ordinary domestic supply – is the restrictive- provision-as-default trap: it silently captures the whole population the provision does not govern. A wrong category is worse than an absent one, because an absent category asks the operator and a wrong one does not.
The exempt case cannot be derived here at all. Art. 6.1.j fixes no phrase for
an exempt operation; it requires a reference to the provision granting the
exemption, so an exempt invoice prints whichever article applies and no canonical
string exists to match. That absence is load-bearing and is documented on
resolve_regime_legends().
See also
RegimeLegendThe statutory mention this module reads, and its declared expectations.
IvaCategoryThe closed catalogue a legend resolves into.
classify_iva()The counterparty-dependent rule table this axis deliberately stays out of.
- class LegendDerivationOutcome(*values)[source]¶
Bases:
StrEnumWhat the legend axis was able to conclude.
Closed, and deliberately without a “probable” member. The axis either has the issuer’s own written declaration or it does not.
- DERIVED¶
A legend declared a category and the printed tax evidence agrees with it.
- CONTRADICTED¶
A legend declared a category the printed tax evidence contradicts.
Reported rather than resolved. The document is internally inconsistent, and picking whichever side looks more likely would erase the one signal that says so. Turning this into an operator-facing blocking finding belongs to the review path, not here.
- ABSENT¶
no legend printed, or one that declares no category.
The expected outcome for most invoices. Six of the seven mandated mentions declare no category, and an invoice printing none at all is entirely ordinary.
- Type:
Nothing to derive from
- class LegendDerivation(**data)[source]¶
Bases:
BaseModelThe legend axis’s conclusion about one document.
- Variables:
outcome – Which of the three states applies.
category – The declared category, set only on
DERIVED. DeliberatelyNoneonCONTRADICTED: the legend named a category, but the page disagrees with itself, and carrying the value anyway would let a caller use it while ignoring the contradiction.legend – The matched mention, on
DERIVEDandCONTRADICTEDalike. Carries theprovisionso a value derived here can cite the article that put the phrase on the page.derived_from – The inputs this conclusion followed from, named so the derivation can be reproduced by hand. Recorded in place of a printed anchor, because a derived value has none.
note – Operator-facing explanation, populated on
CONTRADICTEDto say what disagreed with what.
- Parameters:
outcome (LegendDerivationOutcome)
category (IvaCategory | None)
legend (RegimeLegend | None)
note (str)
- outcome: LegendDerivationOutcome¶
- category: IvaCategory | None¶
- legend: RegimeLegend | None¶
- derived_from: tuple[str, ...]¶
- note: str¶
- index_regime_legends(legends)[source]¶
Index mentions by their normalised form, refusing a table that collides.
Split from the table so the refusal is reachable with a supplied vocabulary rather than only with the shipped one: the collision refusal is what makes accent folding sound, and a check exercisable only against the bundled table is a check nothing proves.
Two mentions that differ only by an accent would become one form under folding, and a lookup that silently kept whichever came first would make declaration order decide a taxpayer’s regime. Refusing the whole table is the only safe answer, because there is no basis for preferring either row.
- Parameters:
legends (
Iterable[RegimeLegend]) – The mentions to index.- Return type:
- Returns:
Each normalised mention mapped to its record.
- Raises:
ValueError – When the vocabulary is empty, a mention normalises to nothing, or two different provisions claim one normalised form.
- match_regime_legend(printed, *, legends)[source]¶
Return the mandated mention printed contains, or
None.Matched under
core.text_fold.fold_printed_phrase(), which folds case, collapses whitespace and folds combining accents away. Nothing beyond that is normalised: a phrase the regulation does not fix is not a mandated mention, and loosening the match to catch PARAPHRASES would derive a regime from words the issuer did not print. Folding an accent is not that loosening – it recovers wording the issuer printed and our reading degraded.Containment rather than equality, because the mention is printed as a line on an invoice among other text, and the reading stage is instructed to copy the phrase but may carry surrounding punctuation with it.
- Parameters:
printed (
str|None) – The phrase transcribed from the document, orNone.legends (
tuple[RegimeLegend,...]) – The dated registry declarations selected by the enclosing operation.
- Return type:
- Returns:
The matching
RegimeLegend, orNonewhen the text is absent, blank, or contains no mandated mention.
- derive_category_from_regime_legend(*, printed_legend, has_repercutido_line, legends)[source]¶
Derive the IVA category the issuer declared in writing, if any.
The whole axis. A mention is matched against the closed statutory vocabulary; if it declares a category, the declaration is checked against the tax evidence the page actually carries, and only an agreeing pair derives a value.
The check is possible because
RegimeLegendalready states per mention whether an invoice printing it should carry a repercutido rate and cuota. A “inversión del sujeto pasivo” invoice charges no Spanish IVA, so a repercutido line beside that mention means one of the two is wrong – and which one is not decidable from the page. Reporting the disagreement keeps that visible; averaging over it would not.- Parameters:
printed_legend (
str|None) – The mention transcribed from the document, orNone.has_repercutido_line (
bool) – Whether the document carries a repercutido rate and cuota. Passed in as an already-established fact rather than recomputed here: what counts as a printed tax line is the reading stage’s business, and re-deciding it here would be a second authority on it.legends (
tuple[RegimeLegend,...]) – The dated registry declarations selected by the enclosing operation.
- Returns:
One of the three outcomes, carrying exactly what that outcome establishes.
- Return type: