← All tests

Delivery OCR — line items + total STAGING

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.

What the prompt rewrite fixed

Each rule earns its place from a real miss on the samples:

Gap on the samplesBeforeAfter
Line items / total not extracted at allabsentextracted on all 4
Riel (KHR) amount scaled ÷10006,160៛ → 6.166160
Currency of a USD order misreadKHRUSD
Platform read off a stray logoE-GetS → "Grab"egets
Product name with a leading count tokenrisk: qty 2"2X Passion honey", qty 1
Order number + platform (live claim path)workedstill works — no regression

Per-sample extraction (menu + totals only)

ScreenPlatformCurrencyItemsTotal
E-GetS appegetsUSD1× 2X Passion honey @2.663.16
Grab Merchant (#55)grabfoodUSD1× Passion Fruit Honey X Iced Honey Lemon Tea @2.663.31
Grab Merchant (scrolled)grabfoodUSD1× Iced Cappucino @1.00 · 1× (Khmer item) @2.504.15
GrabFoodgrabfoodKHR1× Iced Phin Coffee @6160 (Sugar 50%, Normal Ice, 30% off)6160

Known residual — camera noise, not the prompt

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.

Checks

Extraction (4 samples, production model + deployed prompt, via getOcrProvider())8/8
Staging Fly deploy + /health200
tsc --noEmitclean

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.