feat(setup): "Test ANPR" probe on ANPR-enabled cameras

Adds a bottom-of-modal "Test ANPR" button (shown only when a camera's
Plate recognition opt-in is checked) that captures a live snapshot off
the camera and runs it through the vision service, reporting the plate
read + confidence + elapsed time, or which stage failed.

- New POST /api/setup/test-anpr: builds the camera from the unsaved
  config (no DB write/device change, like /test), captures a snapshot,
  runs vision.analyze. Fail-soft like the runtime path (snapshot.ts):
  camera/vision failures are reported results, never a 500.
- Thread the existing VisionClient into setupRoutes; add an isCamera()
  type guard to @parking/devices.
- Web: testAnpr() client + AnprTestResult; button, hint, result line.
- i18n keys in sq + en (Catalog parity).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-22 00:02:31 +02:00
parent 66c1291578
commit 742653aefb
7 changed files with 193 additions and 8 deletions
+10
View File
@@ -344,6 +344,16 @@ export const en: Catalog = {
anpr: "Plate recognition (ANPR)",
anprHint:
"Enable to scan plates on this camera: the vision service reads the plate from a snapshot and feeds it as a read (advisory only — it never opens a barrier on its own). Requires the vision service running.",
testAnpr: "Test ANPR",
anprTesting: "Testing ANPR…",
testAnprHint:
"Takes a live snapshot from this camera and tries to read a plate, reporting the result and the time it took. Point a plate at the camera first.",
anprOk: "✓ Read plate {{plate}} — {{confidence}}% confidence, {{ms}} ms",
anprLowConfidence: "(low confidence — advisory only)",
anprTookMs: "{{ms}} ms",
"anprFail.vision-disabled": "Vision service is disabled — enable it (VISION_ENABLED) to test ANPR.",
"anprFail.snapshot-failed": "Couldn't take a snapshot from the camera (offline or unreachable).",
"anprFail.no-plate": "No plate found in the snapshot.",
whichBarrier: "Which barrier does this device serve?",
controller: "Controller",
choose: "Choose…",
+10
View File
@@ -354,6 +354,16 @@ export const sq = {
anpr: "Njohja e targave (ANPR)",
anprHint:
"Aktivizo që ky aparat të skanojë targat: shërbimi i vizionit lexon targën nga pamja dhe e dërgon si lexim (vetëm këshillues — nuk hap vetë barrierën). Kërkon shërbimin e vizionit aktiv.",
testAnpr: "Testo ANPR",
anprTesting: "Duke testuar ANPR…",
testAnprHint:
"Merr një pamje të drejtpërdrejtë nga kjo kamerë dhe përpiqet të lexojë një targë, duke raportuar rezultatin dhe kohën e nevojshme. Vendos një targë para kamerës më parë.",
anprOk: "✓ Targa u lexua {{plate}} — {{confidence}}% besueshmëri, {{ms}} ms",
anprLowConfidence: "(besueshmëri e ulët — vetëm këshillues)",
anprTookMs: "{{ms}} ms",
"anprFail.vision-disabled": "Shërbimi i vizionit është çaktivizuar — aktivizoje (VISION_ENABLED) për ta testuar ANPR.",
"anprFail.snapshot-failed": "Nuk u mor dot pamje nga kamera (jashtë linje ose e paarritshme).",
"anprFail.no-plate": "Nuk u gjet asnjë targë në pamje.",
// Binding picker.
whichBarrier: "Cilën barrierë shërben kjo pajisje?",
controller: "Kontrolluesi",