cadrumo.application.ledger.confirmed_field_resolution module

Resolving one confirmed field from the operator’s statement and the document.

Extraction is best-effort, so every field a confirm persists may be corrected. The layering rule is named once here rather than restated per field (operator_value_or_reading()): an explicit operator value outranks the reading, and a field that quietly inverted the order would prefer a misread document over the person confirming it. The three fields that do NOT simply layer – currency, counterparty name, invoice date – each carry their own resolver stating why, and a field neither side supplies refuses through refuse_an_absent_confirmed_field() rather than reaching the catalogue empty.

Two resolutions read the document’s own per-rate breakdown rather than a single flat triple, and both are skipped the moment the operator restates any of the base, rate or cuota (operator_restated_the_amounts()), because their figures are then the authority and two authorities on one set of amounts is exactly the condition that must not arise:

  • domestic_rate_tier_from_the_document() answers which domestic rate tier the document charged, against the rates in force on the invoice’s own issue date, and DECLINES rather than approximating whenever the document does not settle it unambiguously.

  • confirmed_lines_from_the_document() preserves WHICH part of the base carried which rate, which Modelo 303 needs because it sums cuota devengada per tier.

Nothing here persists anything or reaches a model; every function is a pure resolution over the draft plus the operator’s arguments.

operator_value_or_reading(supplied, read)[source]

Return the operator’s value when they supplied one, else the document’s.

The layering rule every confirmable field follows, named once rather than restated per field. Extraction is best-effort, so an explicit operator value always outranks the reading; a field that quietly inverted the order would prefer a misread document over the person confirming it.

The reading carries its own type, so a field the document may not have recovered layers to a result that is still absent rather than one this function has quietly declared present. Refusing that absence belongs to refuse_an_absent_confirmed_field() at the field that requires it; a reading the draft does carry outright keeps its narrowed type through the layering.

Return type:

Union[TypeVar(SuppliedT), TypeVar(ReadT)]

Parameters:
  • supplied (SuppliedT | None)

  • read (ReadT)

refuse_an_absent_confirmed_field(*, field)[source]

Refuse a required field neither the operator nor the document states.

Stated as a refusal rather than as a value-returning guard so the absence is settled where it is proven: the caller tests its own optional value and every path beyond that test carries a present one, which a checker verifies rather than takes on trust. A guard that answers with a value instead reads as present to its caller while its own signature still admits absence, and that gap is exactly where an absent taxable base or invoice number travels on into a filing-bound record.

The field is recorded in the verdict, so a surface can tell the operator which one to state instead of reporting an unnamed missing field.

Return type:

NoReturn

Parameters:

field (str)

confirmed_currency(supplied, read)[source]

Return the ISO-4217 code the invoice is minted in.

Same override-on-extraction layering as every other field: an explicit operator value wins, else the currency actually printed on the document, else euro. Preferring the extracted code over the euro default is what stops a foreign-currency invoice being minted at its face value in euro. Falsy rather than None handling is deliberate here – an empty printed code carries no more information than an absent one.

Return type:

str

Parameters:
  • supplied (str | None)

  • read (str | None)

confirmed_counterparty_name(supplied, read)[source]

Return the counterparty display name, refusing when neither side states one.

Unlike the tax id there is no extraction heuristic strong enough to stand alone, so a document naming nobody plus an operator supplying nothing is a refusal rather than an empty name reaching the catalogue.

Return type:

str

Parameters:
  • supplied (str | None)

  • read (str | None)

resolve_confirmed_invoice_date(invoice_date, draft)[source]
Return type:

date

Parameters:
resolve_invoice_class(draft, *, invoice_class, rectifies_invoice_number)[source]

Resolve Facturae class, preserving explicit operator and rectification facts.

Return type:

InvoiceClass

Parameters:
operator_restated_the_amounts(*, taxable_base, iva_rate, iva_amount)[source]

Return whether the operator restated the invoice totals themselves.

Any one of the three is a statement about the WHOLE invoice, which is why this single predicate gates both the rate tier the establishment ladder is handed and the per-rate line split. Two disagreeing authorities on the same figures is exactly the condition it exists to prevent, so the two decisions must never drift onto separately-derived answers.

Return type:

bool

Parameters:
domestic_rate_tier_from_the_document(draft, *, invoice_date)[source]

Return the domestic rate tier the document’s own lines charged, or None.

A tier, not a category. This resolution used to end in a domestic IvaCategory, which made it a second classifier sitting ahead of the rule table and reaching it never – and it reached that category through domestic_categories_by_rate_kind(), the exact mapping the table’s own R05 rule consults. Stopping at the tier keeps every one of the declines below and hands the answer to the table as a criteria axis, so the mapping is applied once, where the law is.

rate_kinds_for_declared_rate() answers which tier a declared rate WAS on a given date, against the registered rate records; it returns a tuple because that question can legitimately have more than one answer, so a caller detects ambiguity instead of picking one.

The date is load-bearing and is the invoice’s own issue date, not today’s. A tier’s rate changes by statute, so resolving a 2024 document against today’s table would answer about a rate it was never charged at.

Declines, rather than approximating, in three cases:

  • More than one rate. One invoice carries one category field and a two-tier document has two answers. Picking either declares part of the base under a rate it was not charged at, and picking by size is an invention. Which tier a multi-rate invoice takes is a modelling decision this resolution does not make.

  • A recargo de equivalencia. The rate resolves cleanly, but a supply carrying a recargo may belong to the ordinary domestic tier or to the recargo category, and the decomposition contract accepts BOTH – so a wrong pick would be caught nowhere downstream. That is exactly the shape that must not be guessed.

  • An unregistered or ambiguous rate. A rate that was not a registered Spanish rate on the issue date is a real refusal rather than a lookup failure, and a rate matching two tiers is the ambiguity the tuple exists to surface.

Declining is visible: the criteria carry no tier, the rule table refuses the domestic branch that needs one, and the resolution reports the operation unresolved. A guess would not be.

Parameters:
  • draft (InvoiceDraft) – The re-run extraction being confirmed.

  • invoice_date (date) – The resolved issue date the rate must be read against.

Return type:

IvaRateKind | None

Returns:

The resolved IvaRateKind, or None when the document does not settle it unambiguously.

rate_tier_the_document_charged(draft, *, invoice_date, operator_restated_amounts)[source]

Return the domestic rate tier to hand the establishment ladder, or None.

Resolved here and handed in, rather than left for the classification apparatus to re-read: which tier a document charged is the reading stage’s business, and re-deciding it inside the classifier would be a second authority on the same lines. Skipped when the operator restated the amounts, for the same reason the per-rate split is – their figures are the authority, not the reader’s – and skipped when no date resolves, because the tier is only meaningful against the rates in force on a given day.

Return type:

IvaRateKind | None

Parameters:
confirmed_lines_from_the_document(*, draft, invoice_number, devengo_date, taxable_base, iva_rate, iva_amount, operator_overrode_the_amounts)[source]

Build the confirmed lines from what the document itself declared.

Returns None when nothing better than the writer’s own base-times-rate derivation is available, which is the correct outcome for a text or vision reader: those recover printed totals, not a tax breakdown.

The per-rate breakdown is used whenever the document states one, at ANY length. A single entry is not the harmless case it looks like: the structured readers populate the breakdown and never the draft’s flat iva_rate, so a one-rate structured document reached the writer with no rate at all and resolved to the base-only EXEMPT slot – minting a zero-cuota invoice out of a document that plainly charged one. Reading the breakdown at length one is what recovers that rate; reading it at length two or more is additionally what preserves WHICH part of the base carried which rate, since Modelo 303 sums cuota devengada per tier.

Parameters:
  • draft (InvoiceDraft) – The re-run extraction being confirmed.

  • invoice_number (str) – Resolved invoice number, used to label the lines.

  • devengo_date (date) – Explicit date selecting the IVA rate fact.

  • taxable_base (Decimal) – Resolved taxable base the lines must sum back to.

  • iva_rate (Decimal | None) – Resolved IVA percentage, or None.

  • iva_amount (Decimal | None) – The operator-supplied printed cuota, or None.

  • operator_overrode_the_amounts (bool) – Whether the operator restated any of the base, rate or cuota.

Return type:

tuple[InvoiceLine, ...] | None

Returns:

The lines to hand the writer, or None to let it derive one line.