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
| Piece | Where |
|---|---|
| Producer call | src/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 builder | build_textract_cost_intent (same module) — pure, unit-provable |
| Rate as data | src/domains/vendor_cost_event/textract_rates.py — versioned per-feature-set micro-USD table + TEXTRACT_RATE_CARD_VERSION |
| Feature lockstep | ANALYSIS_FEATURE_TYPES — one constant feeds both the StartDocumentAnalysis call and the rate lookup |
3. The event
| Field | Value |
|---|---|
idempotency_key | textract:<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 · unit | Provider-reported processed page count (DocumentMetadata.Pages) · pages |
event_kind | estimate — pages x effective SKU rate; never described as an AWS invoice receipt. Monthly reconciliation appends a linked adjustment for any variance. |
rate_card_version | textract-ap-south-1-2026-07 — the rate is frozen data, not a worker literal |
metadata | region + feature_set only (allowlist, D-12) — no extracted text, no document metadata |
| attribution | firm_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
| Claim | Test |
|---|---|
| One stable provider-job key across retry/pagination re-drive | tests/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 rate | TestEstimateShape::test_cost_is_pages_times_effective_sku_rate |
| Metadata allowlist compliance | TestEstimateShape::test_metadata_is_allowlist_compliant_and_content_free |
| Failed later package keeps the cost | test_failed_projection_still_leaves_the_recorded_cost + the PARTIAL_SUCCESS cost assertion |