feat(invoice-inbox): receipt-aware extraction + OCR pipeline fixes (#1331)

Receipts and invoices were extracted through one invoice-shaped prompt
with no document classification. The extractor now also returns
documentKind, payment method (+ card last4), purchaseTime,
merchantCategory and legibility, validated with .catch(null) so a
hallucinated label degrades to unknown instead of sinking the parse.
The FieldsRail shows type and payment method above the editable fields.

Pipeline fixes, all verified against real failure paths:
- PDFs >3 pages: extract from a pdf-lib slice of the first 3 pages
  instead of skipping entirely (issue #553 gate); truncation recorded
  in extracted_data.pages and shown in the UI.
- Oversized images (>4 MB, over Bedrock's 5 MB cap): downscale to
  <=2000px JPEG via sharp before base64, instead of erroring to an
  empty result.
- HEIC/HEIF: attempt sharp transcode to JPEG; when libvips lacks HEIF
  (prebuilt binaries), fall through to today's behavior but show an
  explicit hint instead of silently blank fields.
- Oresavrundning: prompt rule + totals.roundingAmount so receipt totals
  reconcile with subtotal+VAT for exact-amount transaction matching.
- retry-extraction overwrites extracted_data wholesale: a confirm now
  guards against silently destroying manual field edits.

Deliberately NOT added: retry-on-transient-Bedrock-error; the SDK
already retries twice by default (maxRetries=2).

Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-05 14:20:40 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent b1f71912a2
commit 3b3adf96c7
9 changed files with 466 additions and 26 deletions
+3
View File
@@ -769,3 +769,6 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-03] kompletteringsregel_20 with a positive basis and zero acquisition cohorts is refused rather than computed: reducing over an empty cohort set would claim a full write-off the cohort evidence does not support (IL 18 kap. 17 §), so such periods require manual review instead of an automatic deduction.
[2026-08-04] Balance-sheet synthetic result = complement of classes 1-2 (class 0/9/null rows included), not classes 3-8: a resultatavslut posted to 2099 without zeroing class 3-8 then self-cancels inside the residual instead of double-counting equity; mirrors balansrapport's residual definition (#1333)
[2026-08-04] v1 settings PATCH writes directly (no staging), following the v1 customers precedent: REST callers are already scope-gated, staging is an MCP segregation-of-duties concept.
[2026-08-01] OCR quick-fix "bounded retry on transient Bedrock errors" dropped: @anthropic-ai/bedrock-sdk already retries 429/5xx twice by default (maxRetries=2, not overridden in extract-invoice-fields.ts); duplicating it would triple worst-case latency for no reliability gain.
[2026-08-01] Page-count gate (issue #553) changed from skip-extraction to slice-first-3-pages via pdf-lib: invoice data sits on page 1, and a >3-page supplier invoice getting ZERO fields was the worse failure mode. too_many_pages skip remains only for unsliceable (encrypted/malformed) PDFs; truncation recorded in extracted_data.pages. Side effect: client_opt_out now outranks too_many_pages in skip-reason priority (an opted-out caller never extracts regardless of length).
[2026-08-01] Extraction classification fields (documentKind/payment/merchantCategory/legibility) validate with .catch(null) instead of strict enums: a hallucinated label must degrade to unknown, not sink the whole document parse; amounts keep strict parsing on purpose. HEIC handling = attempt sharp transcode at runtime, fall through to today's empty-extraction when libvips lacks HEIF (prebuilt binaries exclude it for patent reasons), with a UI hint replacing the silence.