cadrumo.application.ledger.document_direction module

Decide from the document which side of the invoice the filer is on.

Direction — did this taxpayer ISSUE the invoice or RECEIVE it — is the axis every downstream informativa turns on. Modelo 347 and Modelo 349 report counterparty totals per direction, so reading a purchase as a sale files it against the wrong party in the wrong return, and both figures are wrong in a way that reconciles internally.

Until this module the answer came from one place only: the operator typed it on the confirm verb. That is a real answer and it stays the decision, but nothing checked it, and the document itself was never asked. The draft has carried a suggested_kind slot for exactly this since it was declared – documented, and surfaced by the review command – with nothing on the reading path writing it.

The question asked is a ROLE question, not an identity one. Whether the document names the filer at all is a different check, already owned: counterparty_is_the_filer() answers “is the recorded counterparty us”, and resolve_counterparty_identity() excludes the filer’s own identifier from counterparty candidacy. Neither says which ROLE the filer occupies, and that is the whole of the direction question: the filer standing in the issuing party’s block means they issued it.

Identity is normalised, attribution is by containment. Two authorities, each asked the question it owns. Whether a printed identifier names the filer goes to same_tax_identifier(), so a document printing B-1234567-4 is recognised against a stored B12345674; a raw text search for the stored form would miss every punctuated print. Whether that value belongs to the party the reader filed it under goes to party_regions() – the same heading partition the address attribution uses – so a value is the issuing party’s because it is printed inside the issuing party’s block, never because it is near its heading.

Autoconsumo needs no carve-out here, and that is why containment was chosen. LIVA art. 9 is the one family where a taxpayer documents an operation directed at themselves, and it has no representation in this codebase. A document placing the filer on BOTH sides is the both-regions case, which yields no derivation and no finding — so this module asserts nothing about autoconsumo rather than asserting something false about it. Comparing the two identity slots alone would have answered ISSUED for such a document on slot order.

It suggests; it never decides. The returned kind is stamped on the draft as a suggestion and the operator’s --kind remains the decision. The cross-check that compares the two, and refuses when they disagree, is the consuming half and lives at the confirm boundary where both are in hand.

See also

party_regions()

The heading partition this reads containment from.

resolve_party_attribution_by_colocation()

The sibling that attributes ADDRESS values across the same partition.

DIRECTION_BY_FILER_ROLE: dict[str, InvoiceKind]

Which direction each party role implies when the FILER occupies it.

The whole derivation, as data. The filer standing where the document names the issuing party means they issued it; standing where it names the recipient means they received it. Keyed on the role strings party_addresses() declares, so a party table that grew a side would surface here as a missing key rather than as a silent default.

class DirectionDerivationOutcome(*values)[source]

Bases: StrEnum

What the document settled about which side the filer is on.

Variables:
  • DERIVED – The filer’s identifier occupies exactly one party’s role and is printed inside that party’s own block. The one outcome carrying a direction.

  • FILER_ON_BOTH_SIDES – The document places the filer in both parties’ roles. No direction follows, and nothing is asserted about why — a transposed read and a genuine self-directed operation look identical from here, and only one of them is a defect.

  • FILER_ABSENT – Neither party’s identifier names the filer. Ordinary for a document that omits the recipient’s NIF, which a factura simplificada may legitimately do.

  • CONTAINMENT_UNCONFIRMED – A party’s slot names the filer, but the document does not print that value inside that party’s own block — it is in the other block, in both, or in neither. The reader’s assignment is exactly what is unverified, so it is not read as the answer.

  • NO_PARTITION – The document states no usable two-party heading partition, so containment cannot be asked at all.

  • NO_FILER_IDENTIFIER – The filer’s own identifier was not supplied. Nothing can be compared, and this is reported rather than folded into FILER_ABSENT — “we did not ask” and “the document does not say” are different facts about different owners.

DERIVED
FILER_ON_BOTH_SIDES
FILER_ABSENT
CONTAINMENT_UNCONFIRMED
NO_PARTITION
NO_FILER_IDENTIFIER
class InvoiceKindDerivation(**data)[source]

Bases: BaseModel

What the document settled about direction, and what it was read from.

Variables:
  • kind – The direction the document supports, or None for every outcome but DirectionDerivationOutcome.DERIVED. Never a default: an outcome that did not settle direction carries no direction.

  • outcome – Which of the six cases this is.

  • basis – The operator-facing sentence naming what was read. Carried rather than rebuilt at the surface so the review command and any later consumer quote one wording, and stamped onto the draft’s suggested_kind envelope where the review surface already reads a basis from.

Parameters:

data (Any)

kind: InvoiceKind | None
outcome: DirectionDerivationOutcome
basis: str
property settled: bool

Return whether the document supports a direction.

derive_invoice_kind_from_filer_role(*, draft, transcription, taxpayer_tax_id)[source]

Return which side of the invoice the document places the filer on.

Deterministic and side-effect free: it reads the draft and the transcription and returns a verdict. It never rewrites a draft value, never re-assigns a party, and never returns a direction it did not read from the document.

Parameters:
  • draft (InvoiceDraft) – The read draft, carrying both parties’ identity values and the role evidence the partition is built from.

  • transcription (DocumentTranscription) – The independently produced document text, line structure intact, which is what the partition is read off.

  • taxpayer_tax_id (str | None) – The filer’s own identifier, or None when it was not supplied. None yields DirectionDerivationOutcome.NO_FILER_IDENTIFIER rather than a guess: without it every identifier on the page is equally a counterparty’s, which is the condition under which a first-match scan produced confident wrong answers.

Return type:

InvoiceKindDerivation

Returns:

The derivation. Exactly one outcome carries a kind; every other reports why the document did not settle direction, so a caller can tell “the document says received” from “the document does not say”.