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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user