* feat(invoice-inbox): AI extraction via Bedrock + editable fields
Two big changes that go together:
1. Replace the regex extractor with Claude Sonnet 4.6 via AWS Bedrock.
The PDF or image is sent directly to the model — no unpdf, no DOM
stubs, no worker bundling. Sonnet handles English receipts (Anthropic,
AWS, Stripe), USD/EUR currency symbols, scanned PDFs and image
receipts, and Subtotal vs Total disambiguation that the regex layer
couldn't. Output is JSON, validated with Zod; anything that doesn't
parse falls back to an empty result so the inbox row still lands.
2. Make the extracted fields editable inline in the workspace. Each
field is an Input bound to local draft state with debounced
auto-save (800ms) to a new PATCH /items/:id/fields route. The route
refuses edits once the item is converted to a supplier invoice. The
parent updates both the selected-item view and the list rail when a
field saves so the summary stays in sync.
Drops `unpdf`, adds `@anthropic-ai/bedrock-sdk`. Requires three env vars
in Vercel prod (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION) —
not yet set; when missing, the extractor logs a warning and returns
empty so the upload still works.
All 43 invoice-inbox unit tests pass. tsc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(invoice-inbox): loading state + #415 review fixes
Loading state for AI extraction:
- Optimistic placeholder row inserted into the workspace list the moment
an upload starts. Shows the file name + a "Tolkar dokument med AI…"
spinner instead of timestamp/amount.
- Document preview pane shows a centered loader + same caption while the
placeholder is selected.
- Fields rail shows 6 skeleton inputs + the same caption.
- Action buttons are hidden until the real row arrives.
- Placeholder is removed on success or failure (toast on error).
PR #415 review follow-ups (Greptile + Swedish compliance bot):
- Bump max_tokens 1500 → 4096 so multi-line invoices don't silently
truncate mid-JSON and land with all-null fields.
- Tighten NullableDate to range-checked regex + Date.parse refine —
rejects 2026-13-45 / 2026-02-30 with a clean 400 instead of a 500.
- Make vatRate representation consistent: percent integer (25, 12, 6, 0)
for both lineItems[].vatRate AND vatBreakdown[].rate. Previously the
AI was instructed to emit decimals for one and integers for the other.
- EditableFieldsList re-seeds drafts when the parent passes a new data
snapshot, but only on fields where the local draft still matches the
previous server value — so a server-normalised currency upper-cases
cleanly without clobbering an in-progress edit.
- 409 conflict (item already linked to a supplier invoice) shows the
server's specific Swedish message ("Posten är låst") instead of the
generic "Kunde inte spara".
Out of scope (separate tickets if needed): live updates for
email-arrived items, AWS_SESSION_TOKEN plumbing, 6 % livsmedel rate
transition validation, server-side totals consistency check.
All 43 invoice-inbox unit tests pass. tsc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(invoice-inbox): tighten extraction schema per compliance bot review
Three follow-ups from the Swedish compliance bot's second pass on #415:
1. lineItems[].vatRate and vatBreakdown[].rate now refine to 0–100,
blocking AI hallucinations like 5000% or negative rates while still
accepting non-Swedish rates (UK 20, DE 19) since gnubok stores foreign
invoices for reference. The strict Swedish [0, 6, 12, 25] allowlist is
not enforced here on purpose — that check belongs in the supplier-
invoice-creation step where the data hits the ledger.
2. accountSuggestion is now coerced to null at parse time via .transform,
eliminating the brief intermediate window where a hallucinated string
could appear in the parsed object before the post-validation .map()
nulled it. Removes the redundant .map() afterwards.
3. PATCH /items/:id/fields currency now requires ISO 4217 format
(^[A-Z]{3}$). Previously accepted any 3–8 char string, which would
flow into supplier-invoice creation and produce a faktura with an
invalid currency.
Test fixture vatRate updated 0.25 → 25 to match the percent-integer
convention introduced in the previous commit.
All 43 invoice-inbox unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>