Wire the lane camera's vehicle event into the gated subscription flow: on a vehicle/active push from an opt-in (config.anpr) camera, AnprBridge pulls a fresh snapshot, runs ANPR, applies a stricter entry confidence floor, debounces, and — matching the plate to a subscription BEFORE emitting — emits a kind:"plate" read. The existing ReadDispatcher -> SubscriptionFlow then signs the entry/exit and opens the barrier. A plate is never the sole authority: it routes through the same gate (active/window/blocklist/car-count) as any credential. Fail-soft, fire-and-forget, subscriber-only by construction. Field-verified end to end (plate AA504LX opened the entry barrier and appended a signed vehicle_entry). Add an admin master switch (site_config.anpr_entry_enabled, default ON) in Site Settings that disables ONLY the barrier-driving bridge; advisory snapshot-ANPR and lane busy/free are unaffected. Read live per event, so toggling takes effect with no restart. Migration 0013 (additive ALTER ADD COLUMN, default 1). - New: apps/server/src/anpr-entry.ts (AnprBridge) + tests (9) - hikvision-alarm.ts hands vehicle detections to the bridge (fire-and-forget) + wiring tests (3) - server.ts reorders the read flows above the hik-alarm registration - snapshot.ts exports buildCamera for reuse - env: VISION_ENTRY_MIN_CONFIDENCE (0.85), ANPR_DEBOUNCE_MS (12000) - site route + SiteSettings checkbox + i18n (sq/en parity) - wiki: lane-presence-and-anpr-entry / lpr-camera / index / log -> BUILT Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -13,8 +13,8 @@ Two related things a camera's vehicle detection feeds, worked out over a long fi
|
||||
detection area" conclusion):
|
||||
|
||||
1. **Lane busy/free** — BUILT. An advisory barrier light on the booth.
|
||||
2. **ANPR subscriber entry** — PLANNED. A subscriber's plate, read from the lane camera, drives
|
||||
their entry/exit through the EXISTING [[subscription]] flow. The "bridge" below.
|
||||
2. **ANPR subscriber entry** — BUILT (2026-06-22). A subscriber's plate, read from the lane camera,
|
||||
drives their entry/exit through the EXISTING [[subscription]] flow. The "bridge" below.
|
||||
|
||||
The camera (Hik `DS-2CD1043G2-LIU`) only emits `VMD` events with `eventState=active` and a
|
||||
`targetType` of `vehicle`/`human` — a coarse **presence** signal, never an identity. Everything
|
||||
@@ -46,11 +46,24 @@ nothing** (never blocks a ticket or opens a barrier; the standing rule).
|
||||
also clears promptly after departure.
|
||||
- A `both`-direction camera marks both lanes. Pushed over the existing `/api/ws` (kind `lane-status`).
|
||||
|
||||
## 2. ANPR subscriber entry — THE BRIDGE (PLANNED, not built)
|
||||
## 2. ANPR subscriber entry — THE BRIDGE (BUILT 2026-06-22)
|
||||
|
||||
> **"Bridge" = a HANDLER FUNCTION in `apps/server` (≈40 lines, e.g. `anpr-entry.ts`). NOT a new
|
||||
> **"Bridge" = a HANDLER CLASS in `apps/server/src/anpr-entry.ts` (`AnprBridge`). NOT a new
|
||||
> service / container / app.** It is in-process glue that calls things that ALREADY exist.
|
||||
|
||||
**As built:** `hikvision-alarm.ts`, on a `vehicle`/non-`inactive` push from an `anpr`-opted-in
|
||||
camera, hands the deviceId to `AnprBridge.onVehicleDetected()` (fire-and-forget, never awaited on the
|
||||
camera's 200). The bridge: debounce (camera-level, pre-snapshot) → `captureSnapshot` (fresh pull, via
|
||||
the reused `snapshot.ts buildCamera`) → `vision.analyze` → entry confidence floor
|
||||
(`VISION_ENTRY_MIN_CONFIDENCE`, 0.85) → normalize plate → **`subscriptionFlow.match()` (match BEFORE
|
||||
emit)** → if a subscriber, `deviceEvents.emitRead({kind:"plate"})`; if not, record an advisory
|
||||
`anpr-skip` device_event and stop. The existing `onRead → ReadDispatcher → SubscriptionFlow.run()`
|
||||
then does the gated entry/exit + barrier open. Constructed in `server.ts` (the flows were reordered
|
||||
above the hik-alarm registration so the bridge can take `subscriptionFlow`). Fail-soft throughout —
|
||||
any snapshot/vision error degrades to the subscriber's card/QR, never throws into the push handler.
|
||||
Two new env knobs: `VISION_ENTRY_MIN_CONFIDENCE` (0.85), `ANPR_DEBOUNCE_MS` (12_000). Covered by
|
||||
`anpr-entry.test.ts` (7) + `hikvision-alarm.test.ts` wiring (3).
|
||||
|
||||
The goal (narrowed deliberately — see Rejected below): **a subscriber's plate, read by the lane
|
||||
camera, admits them through the same gated flow a QR/card scan uses.** Scope was cut to subscribers
|
||||
ONLY — no queue segmentation, no per-car tracking, no make/model, no ticket-button gating.
|
||||
@@ -64,7 +77,7 @@ Almost everything already exists; the bridge is the one missing wire:
|
||||
| Read the plate | ✅ [[opencv-anpr-service]] `/analyze` (~50 ms on the DEV PC; appliance TBD) |
|
||||
| Match a plate → subscriber | ✅ `subscription-flow.ts` `match()` + `subscription_plates` (`via:"plate"`) |
|
||||
| Plate read → gated entry/exit | ✅ `read-dispatch.ts` + SubscriptionFlow (active/window/blocklist/car-count) |
|
||||
| **Emit the plate onto the read bus** | ❌ **the bridge** — today the snapshot ANPR only RECORDS the plate as advisory telemetry; it does NOT `emitRead`. hik-alarm.ts literally says "NOT a DeviceReadEvent yet". |
|
||||
| **Emit the plate onto the read bus** | ✅ `anpr-entry.ts` (`AnprBridge`) — on a vehicle push from an `anpr` camera it snapshots → analyzes → matches a subscriber → `emitRead({kind:"plate"})`. (Built 2026-06-22.) |
|
||||
|
||||
**The bridge logic:** on a camera `vehicle`/`active` event from an **opt-in** camera (`config.anpr`),
|
||||
snapshot → `vision.analyze` → if a plate clears a **HIGH** confidence floor → **debounce** → emit
|
||||
|
||||
@@ -84,9 +84,10 @@ Center**, then **Alarm Settings → Alarm Server**, makes the camera **HTTP-POST
|
||||
or open anything. A plate read is **advisory, never the sole reason** a barrier opens
|
||||
([[append-only-event-chain]], [[opencv-anpr-service]]). Two consumers were since designed off this
|
||||
same vehicle event — see **[[lane-presence-and-anpr-entry]]**: (a) BUILT — advisory lane busy/free
|
||||
booth lights; (b) PLANNED — the ANPR "bridge" that snapshots → ANPR → emits a `kind:"plate"` read
|
||||
for a SUBSCRIBER match through the existing gated flow (a small `apps/server` handler, not a
|
||||
service). If the camera ever emits its own `<plateNumber>` we'd use it directly; this `DS-2CD1043G2`
|
||||
booth lights; (b) BUILT (2026-06-22) — the ANPR "bridge" (`anpr-entry.ts`) that snapshots → ANPR →
|
||||
emits a `kind:"plate"` read for a SUBSCRIBER match through the existing gated flow (a small
|
||||
`apps/server` handler class, not a service). If the camera ever emits its own `<plateNumber>` we'd
|
||||
use it directly; this `DS-2CD1043G2`
|
||||
does not, so the server pulls the frame and hands it to the [[opencv-anpr-service|vision service]].
|
||||
|
||||
### Gotchas learned the hard way (2026-06-22 field session)
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ Counts: 4 sources · 19 entities · 45 concepts · 7 decision records.
|
||||
- [[soft-delete]] — BUILT: accidental admin deletes of master data (users/roles/subs/plans/tariffs) are soft (deleted_at) + recoverable from a recycle bin; auto-purge after N days; signed ledger out of scope.
|
||||
- [[subscription]] — recurring plan (e.g. 10,000 ALL/month); RF/QR or plate identity, car-count + max-concurrent, host-in-loop; short-circuits payment. (Renamed from "permit"; time-of-day windows noted, deferred.)
|
||||
- [[opencv-anpr-service]] — host-side vision microservice: ANPR (plate identity) + vehicle verification (anti-plate-spoofing witness); fast-alpr (MIT, YOLOv9+CCT/ONNX) the evaluated recognizer baseline.
|
||||
- [[lane-presence-and-anpr-entry]] — camera vehicle detection → (BUILT) advisory lane busy/free booth lights + (PLANNED) the ANPR "bridge": a subscriber's plate read at the lane admits them via the existing gated subscription flow. Measured camera limits; rejected the queue-tracking/livestream ideas.
|
||||
- [[lane-presence-and-anpr-entry]] — camera vehicle detection → (BUILT) advisory lane busy/free booth lights + (BUILT) the ANPR "bridge" (`anpr-entry.ts`): a subscriber's plate read at the lane admits them via the existing gated subscription flow (match-before-emit; subscriber-only). Measured camera limits; rejected the queue-tracking/livestream ideas.
|
||||
- [[blocklist]] — barred plates/cards refused at entry (never at exit); signed, attributed.
|
||||
|
||||
## Concepts — frontend / operator UI
|
||||
|
||||
+15
@@ -1431,3 +1431,18 @@ tracking/make-model (needs a vehicle detector the plate-only vision lacks + appl
|
||||
measure on the dev PC). Vision checked: fast_alpr live, ~50ms/frame on DEV PC (appliance TBD —
|
||||
booth-PC test ~2026-06-23). New page [[lane-presence-and-anpr-entry]]; updated [[lpr-camera]],
|
||||
[[subscription]], index.
|
||||
|
||||
## [2026-06-22] build | ANPR subscriber-entry "bridge" — BUILT
|
||||
Built the bridge planned in the previous entry: `apps/server/src/anpr-entry.ts` (`AnprBridge`). On a
|
||||
vehicle/non-`inactive` push from an `anpr`-opted-in camera, `hikvision-alarm.ts` hands the deviceId
|
||||
to the bridge (fire-and-forget, never awaited on the camera's 200). The bridge debounces
|
||||
(camera-level, pre-snapshot), pulls a FRESH snapshot (reused `snapshot.ts buildCamera`), runs
|
||||
`vision.analyze`, applies a stricter entry floor (`VISION_ENTRY_MIN_CONFIDENCE`=0.85), then — the key
|
||||
safety choice settled with the user — MATCHES the plate to a subscription BEFORE emitting: a
|
||||
subscriber → `emitRead{kind:"plate"}` (→ existing `ReadDispatcher`→gated `SubscriptionFlow`); a
|
||||
non-subscriber → advisory `anpr-skip` device_event, nothing emitted (so a random/printed plate never
|
||||
reaches the transient plate-as-ticket exit path). Fail-soft throughout. `server.ts` reordered so the
|
||||
read flows are constructed before the hik-alarm registration. New env: `VISION_ENTRY_MIN_CONFIDENCE`,
|
||||
`ANPR_DEBOUNCE_MS`. Tests: `anpr-entry.test.ts` (7) + `hikvision-alarm.test.ts` wiring (3); full
|
||||
server suite 130 green, monorepo build+lint green. Flipped [[lane-presence-and-anpr-entry]] §2 +
|
||||
table row PLANNED->BUILT; updated [[lpr-camera]]. STILL OPEN: booth-PC ANPR latency (~2026-06-23).
|
||||
|
||||
Reference in New Issue
Block a user