feat(anpr): poll snapshots until a confident plate, so auto-exit works

The ANPR bridge took ONE snapshot at the camera's vehicle-alarm instant — but the
alarm fires as the car APPROACHES, so that frame's plate is small/blurry/half-in-
frame and ANPR returns a low-confidence misread ('111'@0.20). The manual test reads
the SAME car at ~100% because by then it's STOPPED at the barrier, well-framed. So
subscriber auto-exit silently never fired (read below the 0.85 floor → ignored).

Fix (the car-stops-at-the-barrier insight): the bridge now PULLS A FRESH FRAME every
ANPR_POLL_MS (1000) and re-runs ANPR until one clears VISION_ENTRY_MIN_CONFIDENCE, or
ANPR_POLL_WINDOW_MS (8000) elapses (drove off / non-subscriber → give up cleanly).
- One loop per camera (#polling set) — the camera's ~1Hz alarm re-fires JOIN the
  running loop instead of spawning N concurrent loops.
- Fresh camera.captureSnapshot each tick, NOT captureSnapshotShared (its 1.5s TTL
  would re-serve the same bad approach frame).
- Camera-level debounce stamp moved to AFTER a successful emit (suppresses re-fires
  for ANPR_DEBOUNCE_MS once we've acted), not before the loop.

VERIFIED on hardware (DS-2CD1047G3H-LIU exit lane): 7 garbage approach frames →
AA890XX@0.999 at the barrier → signed vehicle_exit. Still advisory + fail-soft; a
barrier never opens on a low-confidence read. anpr-entry.test.ts +1 (poll
escalation low→low→high); 169 server tests green. Documented in
lane-presence-and-anpr-entry + the lpr-camera camera-fault writeup.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-27 22:54:49 +02:00
parent e4a17efd97
commit f77ed11782
5 changed files with 225 additions and 16 deletions
+36
View File
@@ -1717,3 +1717,39 @@ Gotchas that bit us (now in [[appliance-provisioning]] §7 + gotchas 7–11): `c
(blank registry account → `no basic auth credentials`); user-mode + `/etc/komodo` root_directory →
`Permission denied`; config key is **`core_address`** singular. [[appliance-provisioning]] §6 split:
§6 = engine, §7 = Komodo deploy (PRIMARY) with §7c manual `booth.sh` break-glass.
## [2026-06-27] query | "Subscribers auto-enter but don't auto-exit" → NOT a bug; G3H main-stream snapshot is structurally dead
Investigated via a read-only VACUUM copy of the dev DB. Caca subscriber's ledger: clean
entry/exit pairs until ~16:38, then 6 entries + 0 exits. Traced to the **exit camera 10.0.10.13
(DS-2CD1047G3H-LIU)** producing only 2 reads ever (vs 60 on the entry cam) — every exit-direction
snapshot after 16:38 was **HTTP 503 "device busy"**, so the ANPR exit read never got a frame →
no `emitRead` → no auto-exit. The subscription-flow exit logic, camera→relay binding (relay 2 =
exit, anpr on), and `stream: "2"` config were all **correct**. Direct hardware re-probe after a
camera reboot + closing web connections: **sub (102) 10/10 rapid → 200 (~50 ms)**, **main (101)
3/3 → 503 in ~20 ms (instant reject)**. So main-stream snapshots are **structurally unavailable**
on this model (sub mandatory), and the 503 storm was **connection-slot exhaustion from manual
main-stream testing** holding the camera's slots (reboot clears). Recorded in the
the `g3h-main-stream-snapshot-503` LLM memory + a 2026-06-27 sharper-finding note in [[lpr-camera]]
("503 Device Busy"). No code changed — diagnosis only.
## [2026-06-27] query | "Auto-exit" RESOLVED — a 4-layer CAMERA fault on the G3H, never our code
Continuation of the above. Drove the full diagnosis to ground using a **dumb HTTP sink**
(`scratch-camera-sink.py`) the exit camera's Alarm Server was pointed at, to capture the verbatim
push. Every assumption about *our* code was wrong; all real causes were camera-side on the
**DS-2CD1047G3H-LIU** (`10.0.10.13`):
(1) 503s were mostly **manual main-stream testing** (main 101 = instant 503 structurally; sub 102 =
perfect) + connection-slot exhaustion (reboot clears) — NOT a retry/flow bug.
(2) **Blocker #1:** the camera never POSTed at all (0 alarms ever vs 1126 on the entry cam); its
**Diagnose dump showed a CORRUPT config DB** (`Main Db is broken`/`db_restore failed`/`reset cfg`) —
factory-reset fixed it.
(3) Gateway/DNS was a **red herring** (working cam had none; broken cam had both).
(4) **Blocker #2:** post-reset it pushed **plain `VMD` with no target** → backend gates on
`target=="vehicle"` (`hikvision-alarm.ts` reads `<targetType>`/`<detectionTarget>`/`<objectType>`)
→ ignored. Enabling the AcuSense **Vehicle target filter** made the push carry
`<targetType>vehicle</targetType>` (confirmed live on a drive-through). "VMD/Motion" is the right
event for this class — it's the *target filter* that matters.
Flagged an **observability gap**: a camera with `alarmPushEnabled=true` and 0 pushes ever should be
a surfaced status (cf. the reader-liveness fix). Recorded in the `g3h-anpr-push-gotchas` memory + a
new troubleshooting section in [[lpr-camera]]. No code changed — diagnosis + camera reconfig only.