cadrumo.application.ledger.evidence module¶
Purchase invoice evidence records and the CRUD application service.
aeat app ledger evidence {add|remove|update|view|list} operate over a
PurchaseInvoiceEvidence pydantic record. Audit events are emitted
to a BucketEventHistoryRepository on every mutating verb.
A PurchaseInvoiceEvidence record is the MIDDLE tier of the three-rung
evidence progression, and owns no bytes of its own:
Attachmentowns byte custody. It is strictly content-addressed (attachment_id == sha256of the stored bytes), immutable, and carries no fiscal figures.aeat app ledger attachandaeat app ledger doclinklink one directly to a transaction.PurchaseInvoiceEvidenceis an operator-registered CLAIM ABOUT one such byte payload: a mutable record whose supplier, invoice number, invoice date, and IVA figures are all OPTIONAL, because a scan whose text layer yields nothing is still valid evidence. Itsevidence_idis a metadata digest, not a content digest, so several records may describe one byte payload; the bytes themselves are stored once, as theAttachmentwritten ataddtime and read back throughattachment_id.Invoiceis the CONFIRMED fiscal document, whose counterparty name, tax id, country, totals, currency, and lines are all REQUIRED.aeat app ledger evidence confirmpromotes tier 2 to tier 3 once the operator supplies or accepts those figures.
The tiers are a permissiveness ladder, not three ways of saying one thing: each rung requires strictly more than the one below it, so none can absorb another without either dropping fiscal fields or refusing evidence the tier below legitimately accepts.
File-type scope is restricted to PDF and image inputs. Plaintext, email
body, and Drive-URL evidence sources are out of scope. add refuses
non-PDF/non-image source paths with a typed
PurchaseInvoiceEvidenceInputError.
Persistence is bucket-scoped encrypted secure-object storage. The evidence
catalogue is a PurchaseInvoiceEvidenceDocument and the source bytes
are written through the required LedgerEvidencePorts custody
capability. The resulting content-addressed attachment_id is recorded on
the evidence record; the bytes thereafter live only in secure storage.
source_path is retained as a provenance breadcrumb and is never read for
bytes (sensitive-financial-data-secure-storage-only).
- class MediaKind(*values)[source]¶
Bases:
StrEnumCanonical media-kind values for purchase invoice evidence.
- PDF¶
- IMAGE¶
- class PurchaseInvoiceEvidence(**data)[source]¶
Bases:
BaseModelOne persisted purchase invoice evidence record.
- Parameters:
data (Any)
- evidence_id: str¶
- bucket_id: BucketId¶
- source_path: str¶
- source_sha256: ContentDigest¶
- attachment_id: Hex64Str¶
- media_kind: MediaKind¶
- supplier: str | None¶
- invoice_number: str | None¶
- invoice_date: str | None¶
- taxable_base: NonNegativeDecimal | None¶
- iva_rate: Percentage | None¶
- iva_amount: NonNegativeDecimal | None¶
- notes: str¶
- created_at: datetime¶
- updated_at: datetime¶
- derive_purchase_invoice_evidence_id(*, bucket_id, source_sha256, media_kind, supplier, invoice_number, invoice_date, taxable_base, iva_rate, iva_amount, notes, created_at, disambiguator=0)[source]¶
Return the content-addressed id for a purchase-invoice evidence record.
Mirrors
cadrumo.domain.transactions.models.derive_transaction_id(): the id is a SHA-256 digest (truncated to 16 hex chars, the prior surrogate’s width) over the record’s identifying fields, so it is stable under a frozen-clock replay and directly referenceable as anaeat app ledger evidenceargument, needing no output mask.created_atplus thedisambiguatorordinal preserve the genuine-duplicate case the ledger already supports: two evidence records for the same file must keep distinct ids, so the mint site incrementsdisambiguatoron the rare digest collision (identical fields at an identical coarse-clock instant) rather than colliding.- Return type:
- Parameters:
- derive_keyed_purchase_invoice_evidence_id(*, bucket_id, idempotency_key)[source]¶
Return a CLOCK-FREE evidence id for a caller-supplied idempotency key.
The keyless derivation above deliberately folds
created_atplus a disambiguator, and that is not an oversight to correct: two evidence records for the same file are a legitimate case – the same invoice PDF can be attached twice as two distinct pieces of evidence – and simply dropping the clock would silently collapse them into one. The codified rule anticipates exactly this and supplies the shape: a deliberately-additive verb documents itself as such, while a caller-supplied key provides the guarded path for callers that need one.So this is not “drop the clock”, it is “add the key”. The id derives from the bucket and the key alone, which is what makes a retry at a different instant resolve to the same record.
- class PurchaseInvoiceEvidenceDocument(**data)[source]¶
Bases:
BaseModelEncrypted bucket-local purchase invoice evidence catalogue.
- Parameters:
data (Any)
- bucket_id: BucketId¶
- records: tuple[PurchaseInvoiceEvidence, ...]¶
- class PurchaseInvoiceEvidencePatch(**data)[source]¶
Bases:
BaseModelMutable subset of
PurchaseInvoiceEvidencefields accepted byupdate.Only the fields listed here may be changed after an evidence record is created.
evidence_id,bucket_id,source_path,source_sha256,attachment_id,media_kind, and the timestamp fields are immutable. ANonevalue for any optional field means “leave unchanged”; the service ignoresNoneentries when applying the patch.- Parameters:
data (Any)
- supplier: str | None¶
- invoice_number: str | None¶
- invoice_date: str | None¶
- taxable_base: NonNegativeDecimal | None¶
- iva_rate: Percentage | None¶
- iva_amount: NonNegativeDecimal | None¶
- notes: str | None¶
- class PurchaseInvoiceEvidenceResult(**data)[source]¶
Bases:
BaseModelReturn record from a mutating evidence verb — record plus emitted event id.
- Parameters:
data (Any)
- record: PurchaseInvoiceEvidence¶
- bucket_event_ids: tuple[str, ...]¶
- class PurchaseInvoiceEvidenceService(*, ports)[source]¶
Bases:
objectApplication service for the
aeat app ledger evidenceverb group.- Parameters:
ports (LedgerEvidencePorts)
- add(*, bucket_id, source_path, supplier=None, invoice_number=None, invoice_date=None, taxable_base=None, iva_rate=None, iva_amount=None, notes='', actor='cli', idempotency_key=None)[source]¶
Attach a new purchase invoice evidence file to a bucket (ledger).
Resolves
source_pathfor byte access only, verifies the file exists, infers theMediaKindfrom the extension, copies the file’s bytes through the required attachment-ingestion capability (active bucket) and records the resulting content-addressedattachment_idon the record (the bytes thereafter live only in secure storage). The persistedsource_pathis the argv-faithful path the operator supplied, never the machine-absolutized form, so it is a stable provenance breadcrumb rather than a machine-dependent one. Creates aPurchaseInvoiceEvidencerecord, appends it to the in-memory catalogue, persists the encrypted bucket-local catalogue in secure-object storage, and emits aPURCHASE_INVOICE_EVIDENCE_ATTACHEDaudit event whose content-addressed id derives from the source digest and stable metadata, never the path.- Parameters:
bucket_id (
str) – Ledger bucket the evidence belongs to.source_path (
str|Path) – Local path to a PDF or image file. Astr(the raw operator argv) is echoed onto the record verbatim — separators are preserved exactly, never OS-normalized — so the persisted path and envelope are identical across platforms (a forward-slash relative path stays forward-slash on Windows). APathis accepted for programmatic callers and stringified for the echo. Byte access always resolves the path regardless.supplier (
str|None) – Optional vendor name extracted from the invoice.invoice_number (
str|None) – Optional invoice identifier from the document.invoice_date (
str|None) – Optional issue date string (free-form; typicallyYYYY-MM-DD).taxable_base (
Decimal|None) – Optional net taxable amount (~decimal.Decimal).iva_rate (
Decimal|None) – Optional IVA percentage as a~decimal.Decimal.iva_amount (
Decimal|None) – Optional IVA amount as a~decimal.Decimal.notes (
str) – Operator free-text annotation.actor (
str) – Identifier stamped on the audit event (defaults to"cli").idempotency_key (
str|None) – Caller-supplied retry key. When supplied the record id is derived CLOCK-FREE from it, so a retry at a different instant resolves to the same record: a matching re-add returns the existing record as a guarded no-op with no second bucket event and no re-stamped timestamp, and a same-key re-add whose content differs refuses naming the divergent fields. When omitted the verb stays deliberately ADDITIVE – two attachments of one file are two distinct pieces of evidence, and collapsing them would be its own defect.
- Returns:
Carrying the new record and the emitted audit event id.
- Return type:
- Raises:
PurchaseInvoiceEvidenceInputError – if
source_pathis not a readable file or has an unsupported extension.
- view(*, bucket_id, evidence_id)[source]¶
Return the single evidence record identified by
evidence_id.- Parameters:
- Returns:
The matching record.
- Return type:
- Raises:
PurchaseInvoiceEvidenceNotFoundError – if no record with that id exists in the bucket.
- list_all(*, bucket_id)[source]¶
Return all evidence records for a bucket in append order.
- Parameters:
bucket_id (
str) – Ledger bucket to read.- Returns:
Oldest first. Returns an empty tuple if the bucket has no evidence file yet.
- Return type:
tuple[
PurchaseInvoiceEvidence, …]
- update(*, bucket_id, evidence_id, patch, actor='cli')[source]¶
Apply a partial update to an existing evidence record.
Loads the bucket’s record list, finds the record matching
evidence_id, merges non-Nonefields frompatch, stampsupdated_at, writes the updated list back, and emits aPURCHASE_INVOICE_EVIDENCE_REPLACEDaudit event.- Parameters:
bucket_id (
str) – Ledger bucket containing the record.evidence_id (
str) – Id of the record to update.patch (
PurchaseInvoiceEvidencePatch) –PurchaseInvoiceEvidencePatchcarrying the fields to change. Fields set toNoneare left unchanged.actor (
str) – Identifier stamped on the audit event.
- Returns:
With the updated record and audit event id.
- Return type:
- Raises:
PurchaseInvoiceEvidenceNotFoundError – if no matching record exists.
- remove(*, bucket_id, evidence_id, actor='cli')[source]¶
Remove an evidence record from a bucket.
Loads the bucket catalogue, finds the record, removes it from the in-memory list, persists the updated encrypted bucket-local catalogue in secure-object storage, and emits a
PURCHASE_INVOICE_EVIDENCE_DETACHEDaudit event.- Parameters:
- Returns:
Carrying the removed record and the audit event id.
- Return type:
- Raises:
PurchaseInvoiceEvidenceNotFoundError – if no matching record exists.
- emit_evidence_event(*, event_repository, bucket_id, event_type, evidence_id, actor, occurred_at, payload)¶
Record one evidence transition through the shared emission primitive.
This used to derive the id, build the event, append it and save the catalogue itself – the exact sequence
emit_bucket_event()documents as the one every emitting domain must share. Re-deriving it cost more than duplication: the shared primitive appends through the catalogue’s revision guard, and a bare load-append-save does not, so two evidence attachments landing together discarded one another’s audit entry. Content-addressed events make that loss invisible after the fact – every survivor is intact and the missing one leaves no gap – so the trail still read as complete.The only things this surface fixes are the object kind and the payload version; everything else is the caller’s.
- Return type:
- Parameters:
event_repository (BucketEventHistoryRepositoryProtocol)
bucket_id (str)
event_type (BucketEventType)
evidence_id (str)
actor (str)
occurred_at (datetime)