feat(vision): surface the recognized plate in the booth UI

The ANPR plate was saved (device_events kind:"read") but had no UI. Extend
GET /api/snapshots/by-identity/:identity to also return plates[] (plate, confidence,
region, direction, snapshotId, at) for that session, and render each as a cyan
"Plate: AA558EE 100%" chip in the SnapshotStrip — so it shows in both the booth
event-detail modal and the pay modal, beside the evidence photo, no separate screen.
Deduped by plate+direction; session:read gated; i18n sq+en.

Verified: by-identity returns plates[] for a seeded read (200, AA558EE 0.999 Albania
entry). Build + lint green.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-19 17:23:32 +02:00
parent ecaaefd899
commit 9ec644811a
7 changed files with 100 additions and 5 deletions
+5
View File
@@ -186,6 +186,11 @@ records nothing, a vision failure never delays or changes the open, and the plat
access decision (the flow already decided). *Verified end-to-end:* a simulated entry snapshot on an
`anpr` camera → stored the snapshot for the session AND recorded `{identity:"TICKET-…", plate:"AA558EE",
confidence:0.999, region:"Albania", snapshotId:…}`.
**Viewing it:** `GET /api/snapshots/by-identity/:identity` now also returns `plates[]` (the
`kind:"read"` reads for that session), and the **`SnapshotStrip`** renders each as a cyan
"Plate: AA558EE 100%" chip above the images — so the recognized plate shows in the **booth
event-detail modal AND the pay modal** beside the evidence photo, with no separate screen.
(3) **field-accuracy** unknown — re-benchmark/tune
the threshold on real on-site captures (angle/night/dirt). (4) the **weight-provenance** check (open).
**Bottom line: consume it as a gated advisory identity record off the entry/exit snapshot — not as sole
+4
View File
@@ -944,3 +944,7 @@ Made the vision service genuinely configurable (was env-only). THREE additions:
## [2026-06-19] refactor | ANPR rides the entry/exit snapshot (replaces polling VisionReader)
Reworked the ANPR TRIGGER per the real design goal: when a transient pushes the button or a subscriber passes QR/RFID, the entry/exit fires and takes the evidence snapshot — THAT is the moment to recognize the plate, off the SAME image, tied to the SAME session. So snapshotAsync now takes the VisionClient and, after storing each snapshot from an opt-in (config.anpr) camera, runs ANPR on shot.bytes and records the plate against the session identity (device_events kind:"read" with plate/confidence/region/snapshotId/source:"entry-exit-snapshot"). One image serves both evidence + plate extraction; recognition fires ONLY on a real entry/exit — NO POLLING. The flows (entry/exit/subscription) now take an optional VisionClient and pass it through; server.ts wires it. REMOVED the polling VisionReader (vision-reader.ts deleted) + VISION_POLL_MS/VISION_DEDUPE_MS env. Advisory + fire-and-forget: low-confidence/no-plate records nothing, a vision failure never delays/changes the open, the plate does NOT feed the access decision (the flow already decided) — it's a record ("session X entered on plate AA558EE"). VERIFIED e2e: a simulated entry snapshot on an anpr camera (live fast_alpr) stored the snapshot for session TICKET-SG-1 AND recorded {identity:TICKET-SG-1, plate:AA558EE, confidence:0.999, region:Albania, snapshotId:…}. Build+lint green. Updated [[opencv-anpr-service]] (trigger section + Configuration, polling refs removed) + both .env.example.
## [2026-06-19] feat | Surface recognized plate in the booth UI (SnapshotStrip)
Made the ANPR plate VIEWABLE (it was saved but had no UI). Extended GET /api/snapshots/by-identity/:identity to also query device_events kind:"read" for that identity and return plates[] (plate, confidence, region, direction, snapshotId, at) alongside the existing snapshots + failures. The SnapshotStrip now renders each recognized plate as a cyan "Plate: AA558EE 100%" chip above the images (deduped by plate+direction; title shows region + time) — so it appears in BOTH the booth event-detail modal and the pay modal, beside the evidence photo, no separate screen. session:read gated (same as snapshots). i18n pay.plate sq+en. VERIFIED: by-identity returns plates[] for a seeded read (status 200, {plate:AA558EE, confidence:0.999, region:Albania, direction:entry, snapshotId}). Build+lint green. Updated [[opencv-anpr-service]].