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:
StrEnumWhat 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:
BaseModelWhat the document settled about direction, and what it was read from.
- Variables:
kind – The direction the document supports, or
Nonefor every outcome butDirectionDerivationOutcome.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_kindenvelope where the review surface already reads a basis from.
- Parameters:
data (Any)
- kind: InvoiceKind | None¶
- outcome: DirectionDerivationOutcome¶
- basis: str¶
- 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, orNonewhen it was not supplied.NoneyieldsDirectionDerivationOutcome.NO_FILER_IDENTIFIERrather 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:
- 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”.