Groundwork for delivery-order capture. The Claude-vision extractor
(nix-loyalty-backend/src/lib/ocr.ts) previously returned only the platform order number; it now also
returns line items, total and currency, which the eventual draft-in-POS confirm flow needs.
Backend-only, additive (the existing loyalty claim path reads only order number + platform, unaffected). No migration.
Verified against Narong's four real sample screens run through the production model
(claude-haiku-4-5-20251001) with the deployed prompt — 8/8 checks. Customer screenshots
contained real names/phones/addresses, so they are not shown here; the table below is limited to
menu + totals.
Each rule earns its place from a real miss on the samples:
| Gap on the samples | Before | After |
|---|---|---|
| Line items / total not extracted at all | absent | extracted on all 4 |
| Riel (KHR) amount scaled ÷1000 | 6,160៛ → 6.16 | 6160 |
| Currency of a USD order misread | KHR | USD |
| Platform read off a stray logo | E-GetS → "Grab" | egets |
| Product name with a leading count token | risk: qty 2 | "2X Passion honey", qty 1 |
| Order number + platform (live claim path) | worked | still works — no regression |
| Screen | Platform | Currency | Items | Total |
|---|---|---|---|---|
| E-GetS app | egets | USD | 1× 2X Passion honey @2.66 | 3.16 |
| Grab Merchant (#55) | grabfood | USD | 1× Passion Fruit Honey X Iced Honey Lemon Tea @2.66 | 3.31 |
| Grab Merchant (scrolled) | grabfood | USD | 1× Iced Cappucino @1.00 · 1× (Khmer item) @2.50 | 4.15 |
| GrabFood | grabfood | KHR | 1× Iced Phin Coffee @6160 (Sugar 50%, Normal Ice, 30% off) | 6160 |
The samples were camera photos of a screen (glare, angle), not on-device screenshots — which is what production will actually capture. Two residual errors are photograph artifacts a clean screenshot removes, and both are exactly what the human Confirm step exists to catch: a long order number misread under glare, and one Khmer item name that came back plausible-but-wrong. Tuning the prompt to mask these would be the wrong fix. Foodpanda was not among the samples — still unverified.
Extraction (4 samples, production model + deployed prompt, via getOcrProvider()) | 8/8 |
Staging Fly deploy + /health | 200 |
tsc --noEmit | clean |
nix-loyalty-backend staging a48141c (Fly staging v13). No migration. Nothing consumes
items/total yet — the draft→Confirm→cafe.orders flow that uses them is
separate nix-cafe work. Rides the loyalty backend already in the prod-promote bundle.