From 40ffa90dac048349bdd8c38756c07222e2ac05b1 Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Fri, 26 Jun 2026 08:11:17 +0200 Subject: [PATCH] =?UTF-8?q?fix(vision):=20self-heal=20local=20real=20ANPR?= =?UTF-8?q?=20=E2=80=94=20dev=20scripts=20sync=20the=20alpr=20extra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dev box runs vision as bare `uv run uvicorn`, and a plain uv run/uv sync re-resolves the venv to the lockfile DEFAULTS, stripping fast-alpr/onnxruntime. So after any `pnpm dev` real ANPR silently degraded to "snapshot, no plate" (diagnosed 2026-06-25: real reads through 06-22, venv frozen lean since 06-19, no other env with fast_alpr). The BOOTH was never affected — it runs the Docker image, which bakes `uv sync --frozen --extra alpr` at build (immutable, weights pre-warmed); a booth ModuleNotFoundError is a STALE image (fix: booth.sh update). Vision package.json dev/start/recognize now run `uv sync --extra alpr &&` first so pnpm dev is self-healing; added a dev:stub escape hatch for a lean run. Documented in wiki/decisions/vision-service-packaging.md ("Two runtimes, one fragile") + a log entry. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V --- apps/vision/package.json | 8 ++++--- wiki/decisions/vision-service-packaging.md | 28 +++++++++++++++++++++- wiki/log.md | 15 ++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/apps/vision/package.json b/apps/vision/package.json index 0e03cc4..63cdaa4 100644 --- a/apps/vision/package.json +++ b/apps/vision/package.json @@ -3,14 +3,16 @@ "version": "0.0.0", "private": true, "//": "Thin shim so this Python service is a first-class node in the Turbo task graph (it is NOT a JS package — deps are managed by uv/pyproject.toml). Each script shells to Python tooling. See wiki/decisions/vision-service-packaging.md.", + "//alpr": "DEV self-heals real ANPR: `dev`/`start` run `uv sync --extra alpr` FIRST, because a plain `uv run` re-resolves the venv to the lockfile DEFAULTS and STRIPS fast-alpr (the cause of silent 'snapshot but no plate' after a prior pnpm dev). Syncing the extra here guarantees the recognizer survives every run. Use `dev:stub` for a lean, model-free local run. The BOOTH is unaffected — it runs the Docker image, which bakes `--extra alpr` at build (see Dockerfile + docker-compose.prod.yml).", "scripts": { - "dev": "uv run uvicorn vision_service.app:app --reload --host 0.0.0.0 --port 8089", - "start": "uv run uvicorn vision_service.app:app --host 0.0.0.0 --port 8089", + "dev": "uv sync --extra alpr && uv run uvicorn vision_service.app:app --reload --host 0.0.0.0 --port 8089", + "dev:stub": "uv run uvicorn vision_service.app:app --reload --host 0.0.0.0 --port 8089", + "start": "uv sync --extra alpr && uv run uvicorn vision_service.app:app --host 0.0.0.0 --port 8089", "lint": "uv run ruff check .", "format": "uv run ruff format .", "typecheck": "uv run mypy vision_service", "test": "uv run pytest -q", - "recognize": "uv run python -m vision_service.cli", + "recognize": "uv sync --extra alpr && uv run python -m vision_service.cli", "build": "echo 'no build step (Python service; models fetched at deploy)'" } } diff --git a/wiki/decisions/vision-service-packaging.md b/wiki/decisions/vision-service-packaging.md index 4bf7a82..7221ddd 100644 --- a/wiki/decisions/vision-service-packaging.md +++ b/wiki/decisions/vision-service-packaging.md @@ -2,7 +2,7 @@ type: decision tags: [parking, decisions, vision, anpr, monorepo, packaging] sources: [] -updated: 2026-06-19 +updated: 2026-06-25 status: settled --- @@ -114,3 +114,29 @@ The skeleton is **built and wired** (no recognizer models yet): > **Resolved 2026-06-22 → [[container-deployment]]:** the vision service now ships as the > `parking-vision` Docker image (uv base, `--extra alpr`), model weights **pre-warmed into the image > layer** at build (offline-first), and runs under **docker-compose** (base + per-env override). + +## Two runtimes, one fragile (the `uv run` strips-the-extra trap) — 2026-06-25 + +Real ANPR runs **completely differently on the two machines**, and only the dev path was fragile: + +- **Booth (deployment) = the Docker image.** The `Dockerfile` runs `uv sync --frozen --extra alpr` + at build, so fast-alpr/onnxruntime are **baked into an immutable image layer** and the weights are + pre-warmed in. `docker-compose.prod.yml` forces `VISION_RECOGNIZER=fast_alpr`. Nothing at runtime + re-resolves the venv → **the booth's real ANPR cannot silently degrade.** (A booth + `ModuleNotFoundError: fast_alpr` is a STALE image, not this bug — fix with `booth.sh update` to pull + the current image.) +- **Dev machine = bare `uv run uvicorn …`** against `apps/vision/.venv`. **This is the trap:** a plain + `uv run` (or `uv sync` with no `--extra alpr`) re-resolves the venv to the lockfile **defaults** and + **REMOVES** the alpr stack — leaving the model weights orphaned in `~/.cache/open-image-models` but + no recognizer in the venv. So a dev box that ran real ANPR (weights downloaded, plate reads + recorded) silently degrades to "**snapshot captured but no plate**" after the next `pnpm dev`. This + exactly explains a gap observed 2026-06-25: real reads on 06-22, then nothing — the venv (frozen + since 06-19, lean) had been stripped, while the Docker/compose work (06-23) was an innocent + coincidence, not the cause. + +**Fix (2026-06-25):** the vision `package.json` `dev`/`start`/`recognize` scripts now run +`uv sync --extra alpr &&` FIRST, so `pnpm dev` is **self-healing** — the recognizer survives every +run. A `dev:stub` script is the lean, model-free escape hatch. The booth (Docker) is untouched. +**Implication:** local real-ANPR and booth real-ANPR are now both reliable; CI/light contributors who +don't want the heavy stack use `dev:stub` or run the suite (tests are stub-mode, offline). See +[[opencv-anpr-service]]. diff --git a/wiki/log.md b/wiki/log.md index f874586..ef9fc21 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -1621,3 +1621,18 @@ passes `-v` (would wipe the signed [[append-only-event-chain|ledger]] volume); ` short-circuit before any Docker/.env requirement. Verified: prod `config` renders Caddy:80 + internal server + pinned images + `fast_alpr`; dev `config` renders `:dev` images + `stub` + published ports. Documented in [[container-deployment]] ("Booth operator wrapper"). + +## [2026-06-25] fix | Local ANPR silently degraded — `uv run` strips the alpr extra +Diagnosed via the live DB (read-only `VACUUM INTO` copy) why entry `26799912337` recorded a snapshot +but no plate: the dev box's vision service was running **stub**, and earlier real ANPR had stopped. +Root cause (NOT the Docker/compose work, which was an innocent coincidence): the dev machine runs vision +as **bare `uv run uvicorn`** against `apps/vision/.venv`, and a plain `uv run`/`uv sync` re-resolves the +venv to the lockfile **defaults**, **stripping** fast-alpr/onnxruntime — so after any `pnpm dev` the +recognizer vanishes (weights orphaned in `~/.cache`, no module in the venv) and ANPR silently becomes +"snapshot, no plate". Evidence: 28 real reads through 06-22 (yolo-v9 model, ~99% conf), venv frozen lean +since 06-19, no other env with fast_alpr on the box. **The BOOTH was never affected** — it runs the +Docker image, which bakes `uv sync --frozen --extra alpr` at build (immutable, weights pre-warmed); a +booth `ModuleNotFoundError` is a STALE image (fix: `booth.sh update`). **Fix:** vision `package.json` +`dev`/`start`/`recognize` now `uv sync --extra alpr &&` first (self-healing), `.env` set to `fast_alpr`, ++ a `dev:stub` escape hatch. Restored real ANPR locally (`/health` → `fast_alpr` ready, model loaded from +cache, no download). Documented in [[vision-service-packaging]] ("Two runtimes, one fragile").