Story-2547 — Textract Cost from Completed Extraction Receipts

pramaan-functions · RFD-BILL-002 §5.2 · Epic-2469 Phase 3 · builds on Story-2543's ledger seam. Note: the governing issue explicitly exempts walkthrough delivery ("fixture receipt is the proof artifact"); this page ships anyway because the closes-check gate cannot read pramaan-spec.

1. What changed

When the async Textract analysis rail finishes retrieving a job's complete paginated receipt, the worker now appends one platform-private cost event through record_vendor_cost — before, and independent of, the customer-facing projection ("Deep Read"). AWS charged us the moment those pages were processed; the internal book records that truth even when the customer package later fails and bills the customer nothing.

2. The wiring point

PieceWhere
Producer callsrc/workers/document_extraction/async_analysis.py — _complete_loaded_job, immediately after the pagination loop and BEFORE project_vaulted_extraction_record, in its own committed transaction
Intent builderbuild_textract_cost_intent (same module) — pure, unit-provable
Rate as datasrc/domains/vendor_cost_event/textract_rates.py — versioned per-feature-set micro-USD table + TEXTRACT_RATE_CARD_VERSION
Feature lockstepANALYSIS_FEATURE_TYPES — one constant feeds both the StartDocumentAnalysis call and the rate lookup

3. The event

FieldValue
idempotency_keytextract:<provider job id> — keyed on the provider job, so completion redelivery and pagination re-drives dedupe at the ledger's unique constraint; one event per job, ever
quantity · unitProvider-reported processed page count (DocumentMetadata.Pages) · pages
event_kindestimate — pages x effective SKU rate; never described as an AWS invoice receipt. Monthly reconciliation appends a linked adjustment for any variance.
rate_card_versiontextract-ap-south-1-2026-07 — the rate is frozen data, not a worker literal
metadataregion + feature_set only (allowlist, D-12) — no extracted text, no document metadata
attributionfirm_id · matter_id · document_id — identifiers only
Cost survives a failed Deep Read. The cost transaction commits before projection runs, and PARTIAL_SUCCESS receipts (vaulted pages, terminal failed) still record their cost. There is deliberately NO coupling to billing.page_billing_ledger — the customer charge seam is Story-2508's.

4. Proof

ClaimTest
One stable provider-job key across retry/pagination re-drivetests/unit/test_textract_cost_estimate.py::test_retry_and_pagination_redrive_share_one_idempotency_key + resume-cursor assertion in test_async_document_analysis.py; DB dedupe proven at the 2543 seam (tests/integration/test_vendor_cost_ledger.py)
Estimate marking + pages x SKU rateTestEstimateShape::test_cost_is_pages_times_effective_sku_rate
Metadata allowlist complianceTestEstimateShape::test_metadata_is_allowlist_compliant_and_content_free
Failed later package keeps the costtest_failed_projection_still_leaves_the_recorded_cost + the PARTIAL_SUCCESS cost assertion