feat(vision): add VisionClient Node adapter (advisory, fail-soft, opt-in)
Node-side adapter to the apps/vision ANPR microservice (localhost HTTP: POST /analyze
with snapshot bytes, GET /health), returning a normalised VisionResult or null. Enforces
"advisory, never sole authority" at the boundary: opt-in (VISION_ENABLED, default off),
fail-soft (any error/timeout/unreachable → null, never throws into the lane → ticket
fallback), and re-applies the confidence floor (VISION_MIN_CONFIDENCE) on top of the
service's own low_confidence flag. Per-request AbortController timeout so a slow call
can't hang the barrier. Constructed in server.ts.
Verified: fail-soft (disabled/unreachable → null, no throw) and live end-to-end (Node
client → running fast_alpr service → AA558EE 0.999, region=Albania). NOT yet wired into
the read bus — the opt-in snapshot→DeviceReadEvent{kind:"plate"} trigger is the next
step. Build + lint green. Updates opencv-anpr-service (adapter gap marked done).
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -165,15 +165,19 @@ the read is given** — and the answer splits by role:
|
||||
is NOT built**. So plate-as-identity is convenience + evidence, never the lone reason a paid barrier
|
||||
opens. Consistent with "advisory, never sole authority" above.
|
||||
|
||||
**Gaps before it's actually consumed (capable ≠ wired):** (1) the Node→service **`VisionClient`**
|
||||
adapter (localhost HTTP, behind the [[device-adapter-pattern]] interface) — the real integration work;
|
||||
(2) **trigger wiring** — snapshots today fire *after* a barrier opens (evidence); plate-as-identity
|
||||
needs a snapshot *before* the decision, on a **per-camera opt-in** lane (open item below); (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 source feeding the existing `kind:"plate"` path — not as sole authority — and
|
||||
Job 2 is still required for the anti-spoofing value.** Next concrete step is the `VisionClient` adapter
|
||||
+ the opt-in trigger, not more model work.
|
||||
**Gaps before it's actually consumed (capable ≠ wired):** (1) ✅ **DONE — the Node→service
|
||||
`VisionClient`** adapter (`apps/server/src/vision-client.ts`, localhost HTTP to `/analyze` + `/health`)
|
||||
now exists: **opt-in** (`VISION_ENABLED`, default off), **fail-soft** (any error/timeout/unreachable →
|
||||
`null`, never throws into the lane → ticket-path fallback), and **re-applies the confidence floor**
|
||||
(`VISION_MIN_CONFIDENCE`) so a low read is flagged advisory. Constructed in `server.ts`; verified
|
||||
end-to-end against the live service (Node → `AA558EE` 0.999, `region=Albania`). Still NOT wired into the
|
||||
read bus. (2) **trigger wiring** — snapshots today fire *after* a barrier opens (evidence);
|
||||
plate-as-identity needs a snapshot *before* the decision, on a **per-camera opt-in** lane → emit
|
||||
`DeviceReadEvent{kind:"plate"}` (open item below). (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 source feeding the existing `kind:"plate"` path
|
||||
— not as sole authority — and Job 2 is still required for the anti-spoofing value.** With the adapter
|
||||
done, the next concrete step is the **opt-in snapshot→read trigger**, not more model work.
|
||||
|
||||
## Open
|
||||
|
||||
|
||||
@@ -924,3 +924,7 @@ Benchmarked fast-alpr's four candidate fast-plate-ocr models via the FULL pipeli
|
||||
## [2026-06-19] query | Vision service fitness for entry/exit flows — advisory YES, sole-authority NO
|
||||
|
||||
Q: is the scaffolded ANPR service worthy to consume in entry/exit flows? Assessment recorded in [[opencv-anpr-service]] ("Fitness for the entry/exit flows"). Benchmark settled ACCURACY (0.99+ clean AL plates); "worthy" turns on AUTHORITY. Split verdict: (✅) worthy NOW as an ADVISORY identity source (Job 1) — the flows are ALREADY built for a plate (kind:"plate" read is first-class: exit-flow signs source:"lpr"; subscription-flow matches read plate vs subscriptionPlates), so the service just produces the plate string → DeviceReadEvent{kind:"plate"} on the existing read bus; no flow rewrite. Worthy for hands-free subscriber open + evidence enrichment. (⚠️) NOT worthy as SOLE AUTHORITY to open a TRANSIENT barrier: a plate ≠ payment (would be an unpaid-exit bypass; min_confidence floor → ticket/manual fallback is the guard) and plate-spoofing (printed plate, different car) needs Job 2 vehicle-verification which is NOT built. Gaps before consuming: (1) the Node VisionClient adapter (real integration work), (2) trigger wiring — snapshots fire AFTER open today (evidence); plate-as-identity needs a snapshot BEFORE the decision on a per-camera opt-in lane, (3) field accuracy unknown (re-tune threshold on on-site captures), (4) weight-provenance check. Next step: VisionClient adapter + opt-in trigger, not more model work. (Scaffolding VisionClient next.)
|
||||
|
||||
## [2026-06-19] feat | VisionClient Node adapter (apps/server/src/vision-client.ts)
|
||||
|
||||
Scaffolded the Node-side adapter to the host vision microservice per the fitness assessment. VisionClient calls apps/vision over localhost HTTP (POST /analyze with snapshot Buffer bytes, GET /health), returning a normalised/camelCased VisionResult (best plate + all plates + lowConfidence + modelVersion + tookMs) or null. THREE guardrails enforce "advisory, never sole authority" at the boundary: (1) OPT-IN — VISION_ENABLED (default OFF), so the appliance runs with no vision service; (2) FAIL-SOFT — disabled/unreachable/timeout/non-2xx/bad-body all resolve to null and NEVER throw into the entry/exit path (→ ticket/manual fallback, never strand a car); (3) CONFIDENCE FLOOR re-applied (VISION_MIN_CONFIDENCE) on top of the service's own low_confidence flag. Per-request AbortController timeout (VISION_TIMEOUT_MS, default 1500ms) so a slow call can't hang the lane. Constructed in server.ts (logs when enabled). VERIFIED: fail-soft (disabled→null, unreachable→null no-throw) and LIVE end-to-end (Node client → running fast_alpr service → AA558EE 0.999 region=Albania, camelCased). NOT yet wired into the read bus — the opt-in snapshot-before-decision trigger that emits DeviceReadEvent{kind:"plate"} is the next deliberate step. Build+lint green. Updated [[opencv-anpr-service]] (gap 1 marked done).
|
||||
|
||||
Reference in New Issue
Block a user