cadrumo.application.ledger.evidence_draft module

Direction-resolved projections over a read invoice draft.

A draft is pre-direction by construction: the readers record what each party’s block said without deciding which of them the filer is. Everything here answers a question that only becomes answerable once the operator states the direction, or once a record has been minted – so these are projections of a draft rather than part of reading one.

Two projections live here, and both exist because a caller recomputing them would be a second authority on one answer:

  • counterparty_draft_side() selects which side of the document is the COUNTERPARTY, totally and with no fall-back to the other side. Every consumer reads the same selection, so a document cannot resolve to the customer for one caller and the supplier for another.

  • printed_total_discrepancy() reports the document’s own printed total disagreeing with the total actually recorded. The printed figure never overwrites the derived one; it is the free cross-check that catches a recargo with nowhere to go, an unread rate resolving to the exempt slot, and a misread base.

Reading a document into a draft lives in invoice_draft_extraction; confirming a reviewed draft into a catalogue record lives in invoice_confirmation.

See also

InvoiceDraft

The pre-direction draft these projections read.

closure_findings()

The document-internal arithmetic check, distinct from the printed-versus-recorded comparison here.

class CounterpartyDraftSide(**data)[source]

Bases: BaseModel

The side of a read document that is the COUNTERPARTY, once direction is known.

A draft is pre-direction by construction: it records what each party’s block said without deciding which of them the filer is. Direction settles that, and settles it the same way for every consumer – which is why this is a record produced once rather than a pair of field lookups each caller repeats.

Variables:
  • tax_id – The counterparty’s identifier as the document printed it, or None when the reader recovered none.

  • name – The counterparty’s stated name, or None.

  • postal_code – The postal code printed in the counterparty’s address, or None.

  • country – The country NAME printed in the counterparty’s address, or None. A name rather than a code, because that is what an address block prints; the match against the bounded vocabulary is the establishment ladder’s own second rung.

  • country_code – The ISO 3166-1 alpha-2 country code a structured record stated for the counterparty, or None. Carried beside the printed NAME rather than instead of it: the two are the same ladder rung reached from different readers, and a document states one or the other, never both.

  • stated_country_token –

    The token the record’s country element carries, verbatim, or None where it stated none. Carried beside the resolved code rather than folded into it, because the two answer different questions and only this one can answer the second: the resolved field is empty both for a document that stated no country AND for one stating a token the bundled vocabulary does not carry, so a consumer asking “did the document state a country” off that field gets the same answer for an unplaceable export and for an invoice with no address block. A consumer deciding whether an unestablished counterparty is the DOCUMENT’s gap or OURS depends on telling those apart.

    Named a TOKEN, and not a country CODE, deliberately. The establishment ladder’s resolved_country_code parameter wants the RESOLVED alpha-2 – which is country_code above, and which is what this object’s one ladder call site correctly passes. Two same-shaped attributes on one object, one safe in that slot and one not, is a swap that type-checks: both are str | None, and feeding an alpha-3 into an alpha-2 parameter on a tax-territory path fails to place the country and reintroduces exactly the silence this field exists to remove. The name is the guard, because nothing else here is.

  • tax_id_field – Which draft field tax_id was taken from. Carried so an operator override is recorded against the reading it displaced rather than against whichever field shares the option’s name.

  • name_field – The same for name.

Parameters:

data (Any)

tax_id: str | None
name: str | None
postal_code: str | None
country: str | None
country_code: str | None
stated_country_token: str | None
tax_id_field: str
name_field: str
counterparty_draft_side(draft, *, kind)[source]

Select the counterparty’s side of a draft from the document’s direction.

On an invoice the filer ISSUED, the counterparty is the customer; on one they RECEIVED, it is the supplier.

The selection is total, with no fall-back to the other side, and that is the load-bearing part. This once read the customer side “if it is set, otherwise the supplier”, and because the text and vision readers cannot populate a customer at all, every issued document silently resolved to the supplier – who, on a document the filer issued, IS the filer. The value is checksum-valid, so every identity check downstream passes it, and it is bound for the Modelo 347 / 349 totals AEAT reconciles against the other party’s own declaration.

Two guards elsewhere do catch that today, but both load the taxpayer profile and both return without refusing when it carries no tax id, so the protection was only ever as present as the profile. Selecting one side and stopping makes the property structural instead: an unread counterparty stays None and is refused as a missing field, naming the override that supplies it, which is the same outcome an operator already gets for any other field the reader could not recover.

Parameters:
  • draft (InvoiceDraft) – The pre-direction reading of the document.

  • kind (InvoiceKind) – Which side of the invoice the filer is on, as the operator settled it at confirm. Never the reader’s suggestion.

Returns:

the selected side, and which draft fields it came from.

Return type:

CounterpartyDraftSide

class PrintedTotalDiscrepancy(**data)[source]

Bases: BaseModel

The document’s printed total disagreeing with the total actually recorded.

The confirm path never persists a model-read or text-read figure as the invoice total: grand_total is DERIVED from the taxable base and the registry-resolved rate slot (build_catalogue_invoice()). That derivation is the correct behaviour and this record does not change it – the printed figure stays an advisory cross-check and never overwrites the derived value, exactly as the evidence-reading discipline requires.

What this record adds is the other half of that same discipline: when the two disagree, say so. A disagreement is never noise, because the derived total is arithmetically fixed at base + cuota; anything the document prints beyond that is a component the record could not represent, or a misread of one it could:

  • A recargo de equivalencia invoice (LIVA art. 161) prints base + cuota + recargo. The recargo has nowhere to go on this path, so the record silently understates the document by exactly that surcharge.

  • An unread rate resolves to the registry-declared exempt slot (iva_rate=None is the base-only slot), minting a zero-cuota invoice whose printed total still shows the cuota that was charged.

  • A misread base propagates into the derived total and diverges from the printed one.

All three are silent under-declarations that the printed total detects for free, having already been read. Discarding it unexamined is what let them through.

Variables:
  • printed_total – The total actually printed on the document, as recovered by the on-host reader.

  • recorded_total – The total derived from the confirmed base and rate slot, i.e. what the persisted invoice carries.

  • difference – printed_total - recorded_total. Positive means the document totals MORE than the record – the under-declaration direction, and the one a recargo produces.

Parameters:

data (Any)

printed_total: Decimal
recorded_total: Decimal
difference: Decimal
printed_total_discrepancy(*, draft, invoice)[source]

Return the printed-vs-recorded total disagreement, or None when they agree.

Compares only when the reader actually recovered a total: a document whose total could not be read grounds no cross-check, and reporting a discrepancy against an absent figure would manufacture an alert out of missing data rather than out of conflicting data.

Parameters:
  • draft (InvoiceDraft) – The extraction the confirmation was based on.

  • invoice (Invoice) – The invoice that was persisted (or matched on a guarded no-op).

Return type:

PrintedTotalDiscrepancy | None

Returns:

PrintedTotalDiscrepancy when the document printed a total that differs from the recorded one, else None.