cadrumo.adapters.outbound.llm.evidence_draft_text module

Semantic invoice-field extraction from a document’s TEXT representation.

Reading a rendered document is two questions, not one. The vision reader once answered them together, handing rasterised pages to a model and taking back typed fields in a single call – so when the result was wrong there was no way to tell a transcription error from a reasoning error, and a perfect transcription obtained by any other means had nowhere to go.

This module is the second stage that made splitting them possible: text in, grounded InvoiceDraft out. Both acquisition lanes now feed it – the deterministic text-layer extractor and LocalVisionDocumentTranscriber, which transcribes and interprets nothing. Splitting it out means the transcription stage can be swapped, measured or replaced independently of the understanding stage, and it makes any already-transcribed text – a PDF text layer, an OCR result, an EN16931 payload rendered back to prose – usable.

Defaults to LOCAL, like the vision reader. This paragraph previously said the opposite – that the reader took whatever provider the caller had configured, because “the sensitive-data posture is owned by the calling flow’s consent gate, not by this primitive”. That reasoning was sound and the premise was false: no such consent gate had been built, while the shipped provider default is a cloud vendor. The two together meant reading a taxpayer’s invoice sent the document’s text off-host by configuration alone, with nothing at the call site expressing that choice.

The default is therefore stated here rather than inherited. An off-host read stays possible – the measurement corpus is public, synthetic and explicitly sanctioned for it – but requires naming BOTH the provider and the model, which makes it a deliberate act at the call site instead of a property of the environment. A named provider with no model refuses, mirroring the vision reader’s guard.

This is a floor, not the consent gate. The gate itself is LLMClient’s per-invocation evidence-consent check, applied at the client’s dispatch choke point where no caller can reach around it by constructing a request directly: this reader marks every request it builds as evidence-derived unless the caller names the public corpus, and an off-host dispatch of a marked request without a per-invocation consent token refuses.

Language-neutral by design. The vision prompt opens “a scanned Spanish invoice”; invoices reaching this path are international, in arbitrary languages and layouts, so the prompt names field concepts and lets the model find them under whatever heading the document uses. What it does not relax is the safety contract: copy printed values verbatim, never calculate or infer, and emit null for anything not printed. Grounded re-validation (ground_extracted_fields()) then re-checks every field against an independent authority, so a fabricated value is dropped even if the prompt fails to prevent it.

See also

InvoiceDraft

Typed draft this reader returns after grounded re-validation.

ground_extracted_fields()

Shared grounded re-validation this reader and the vision reader both use.

transcribe_document_images()

Sibling ACQUISITION stage for evidence carrying no text layer at all. It produces the transcription this reader consumes; it does not read fields.

default_extraction_authority_values(*, operation)[source]

Resolve the authority values for the fallback period.

The one place this package turns “no values were supplied” into values, and it does so by asking the application layer’s compiler rather than by reading any authority itself.

Returns:

The values in force across the current civil year.

Return type:

InvoiceExtractionAuthorityValues

Parameters:

operation (PinnedAuthorityOperation)

build_text_field_extraction_prompt(evidence_text, *, operation, values=None, fields=None)[source]

Build the extraction prompt for evidence_text.

Separate from the transport so the exact instruction a model will receive is directly assertable without dispatching a request.

The instruction half is the SHARED rendered artefact (render_invoice_extraction_prompt()), not a second hand-maintained field list: this reader and the vision reader ask for the same eight fields in the same declared forms, and maintaining that agreement by hand is exactly the drift that lost a correctly-read rate.

Parameters:
  • evidence_text (str) – The document’s text representation.

  • operation (PinnedAuthorityOperation) – The caller-owned generation-pinned authority operation used when resolving fallback authority values.

  • values (InvoiceExtractionAuthorityValues | None) – Regulatory values to enumerate, resolved by the application layer. None resolves the current civil year’s, which is the honest fallback for a document not yet bound to a filing period.

  • fields (Collection[str] | None) – The field names to ask for, or None for every declared field. Passed to the shared compiler unchanged; the selection is validated there against the declaration, so a name this reader does not recognise refuses in the one place that knows the vocabulary rather than being silently dropped on the way.

Returns:

instructions, then the document text under a delimiter that tells the model where its own instructions end.

Return type:

The full prompt

Raises:

PurchaseInvoiceEvidenceInputError – When evidence_text is blank, which would otherwise ask a model to read nothing and invite it to invent an entire invoice.

class TextInvoiceFieldExtractor(*, model=None, provider=LLMProvider.LOCAL, client=None, operation, settings=None, authority_values=None, consent_token=None, public_corpus=False, fields=None)[source]

Bases: object

Read an invoice’s text with a language model into a grounded InvoiceDraft.

Parameters:
  • model (str | None) – Optional model identifier; None uses the configured LOCAL text model. Required when provider is not LOCAL, because the only default that exists names a local model no vendor serves.

  • provider (LLMProvider) – Which provider carries the read. Defaults to LOCAL so a taxpayer’s document does not leave the host by default; naming a cloud provider is a deliberate call-site act.

  • client (LLMClient | None) – Injected LLMClient (dependency injection for tests); default-constructed against the resolved settings otherwise.

  • operation (PinnedAuthorityOperation) – Caller-owned generation-pinned authority operation retained for authority resolution and field grounding.

  • settings (Settings | None) – Injected settings; defaults to load_settings().

  • authority_values (InvoiceExtractionAuthorityValues | None) – The regulatory values the compiled prompt enumerates, resolved by resolve_invoice_extraction_authority_values(). Taken as resolved DATA rather than as a period this reader would look up for itself: the rates are the application layer’s to determine, and a reader that resolves its own has quietly become a second consumer of the calculation authorities. None falls back to the current civil year’s values.

  • consent_token (EvidenceConsentToken | None) – Per-invocation off-host consent proof, minted through mint_evidence_consent_token(). Required only for an off-host read of real evidence; None is correct for every on-host read.

  • public_corpus (bool) – Whether the text handed to this reader comes from the public, synthetic measurement corpus rather than from a taxpayer’s document. Defaults to False – the fail-closed direction, so a caller that says nothing gets the gate. Naming the corpus is the deliberate act, not naming the evidence.

  • fields (Collection[str] | None) – The field names each read asks for, or None for every declared field. Held for the reader’s lifetime rather than passed per read, because it describes the SHAPE of the call this reader makes, and a shape that varied between two reads of one corpus would make those reads incomparable. A measurement comparing call shapes builds one reader per shape.

property decided_by: str

transport, reader, model, and the rates compiled in.

Distinct from the vision stamp so a persisted record always says which reader produced the fields. The model segment is configured when the caller pinned nothing, because the effective model is then the active provider’s default rather than a fact this object holds.

The trailing rate-provenance token answers the question the model name cannot: the prompt now enumerates registry-resolved rates, so which rates were in force for this read is part of how the figure was reached.

The transport segment is DERIVED from the provider actually used, matching the vision reader. It was previously absent, so this stamp could not say whether a read had left the host – and a model name does not answer that, because a vendor model identifier reveals the vendor only to a reader who already knows the catalogue. A consent withdrawal enumerates cloud-derived artefacts BY this segment, so a stamp that omits it makes a withdrawal silently incomplete: the artefact that most needs re-deriving is the one the survey cannot see.

Type:

Provenance stamp

extract(*, transcription)[source]

Read the acquisition-stage transcription and return the grounded draft.

Takes the typed stage-1 artefact rather than a bare string, which is what makes this the second stage of a pipeline instead of a helper that happens to accept text. A string carries no answer to “who read these characters off the document”, and that answer is not decoration: it is the ORIGIN stamped on every value this reader proposes, and the record of whether an independent reader produced the text its anchors are later checked against.

Parameters:

transcription (DocumentTranscription) – The acquisition-stage transcription, printed forms intact.

Returns:

Every field the model reported AND that passed grounded re-validation; everything else is None.

Return type:

InvoiceDraft

extract_invoice_fields_from_text(transcription, *, operation, model=None, settings=None, authority_values=None)[source]

Convenience wrapper: build a TextInvoiceFieldExtractor and extract.

This is the entry point the evidence router calls, and it takes no provider argument. The provider is therefore pinned LOCAL, and the pin is written out below rather than inherited from the extractor’s default: a pin held by NOT passing an argument is the weakest possible statement of a confidentiality property, because widening this signature with a pass-through provider would open the off-host route for every router call without a single diff line that looks like a confidentiality change.

The pin is documentation, not the boundary. Widening this signature cannot actually send a taxpayer’s document off-host, because the extractor marks every request it builds as evidence-derived and LLMClient’s per-invocation evidence-consent check refuses a marked request at any off-host provider without a per-invocation consent token. That is the property to preserve if this signature ever changes: the refusal lives below this function, not in it.

Parameters:
  • transcription (DocumentTranscription) – The acquisition-stage transcription to read.

  • operation (PinnedAuthorityOperation) – The caller-owned generation-pinned authority operation used for authority resolution and field grounding.

  • model (str | None) – Optional model override.

  • settings (Settings | None) – Optional resolved settings override.

  • authority_values (InvoiceExtractionAuthorityValues | None) – Application-resolved regulatory values for the prompt. The routing caller resolves these ONCE per document and passes them here, so a document read through several stages is read under one set of rates rather than one per prompt construction.

Returns:

The grounded, best-effort extracted fields.

Return type:

InvoiceDraft