Behavision: face recognition for retail, edge to head office
Five components that ship as one product:
- behavision/ the recognition engine. RTSP ingest, YuNet detection, IoU
tracking, ArcFace embeddings, a FAISS/SQLite gallery, and a
FastAPI dashboard. Identity is decided once per TRACK from an
average of at least three embeddings, never per frame.
- agent/ the Go edge agent: supervises the engine, holds a durable
spool, and drains it to MQTT. Nothing is acked before the
broker confirms.
- desktop/ the shop PC application (Wails + React + tray).
- server/ the cloud API, MQTT consumer, reports and assistant.
- web/ platform.loyaly.ai, the head-office app, embedded in the
server binary.
The gallery stores 512-float embeddings and timestamps - no images unless
`app.store_faces` is switched on. Those embeddings are biometric personal
data under GDPR and India's DPDP: template inversion reconstructs a
recognisable face from an ArcFace vector, so data/behavision.db is treated
as a biometric database and DELETE /api/visitors/{id} is a real erasure.
CLAUDE.md carries the reasoning behind every non-obvious decision here,
including the ones that were measured and the ones that were wrong first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
This commit is contained in:
102
behavision.spec
Normal file
102
behavision.spec
Normal file
@@ -0,0 +1,102 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
"""PyInstaller spec for the Behavision engine.
|
||||
|
||||
One-folder, not one-file. A onefile build of this is ~200 MB and extracts the
|
||||
whole thing to a temp directory on **every** start, which on a store PC means a
|
||||
multi-second delay and an antivirus scan each time the service restarts.
|
||||
|
||||
Models are NOT bundled. They are ~200 MB on their own and `setup-models`
|
||||
already downloads them with a resumable `.part`-then-rename; bundling them
|
||||
would triple the installer and force a re-sign for a model change. They land in
|
||||
the writable state root (see behavision/paths.py), not next to the code.
|
||||
|
||||
Build:
|
||||
pyinstaller behavision.spec --noconfirm
|
||||
Output:
|
||||
dist/behavision/behavision.exe
|
||||
"""
|
||||
import sys
|
||||
from PyInstaller.utils.hooks import collect_dynamic_libs, collect_submodules
|
||||
|
||||
block_cipher = None
|
||||
|
||||
# The dashboard and the default config are read from disk at runtime, so they
|
||||
# have to travel with the code. They go to the *install* root; anything the app
|
||||
# writes goes to the state root instead.
|
||||
datas = [
|
||||
("behavision/static", "behavision/static"),
|
||||
("config/default.yaml", "config"),
|
||||
]
|
||||
|
||||
# onnxruntime and cv2 load native libraries that PyInstaller's static analysis
|
||||
# cannot see through. Missing these is the classic "works in the venv, dies in
|
||||
# the bundle" failure.
|
||||
binaries = []
|
||||
for pkg in ("onnxruntime", "cv2"):
|
||||
try:
|
||||
binaries += collect_dynamic_libs(pkg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
hiddenimports = [
|
||||
# Imported lazily inside functions, so the graph never sees them.
|
||||
"dotenv",
|
||||
"uvicorn.logging",
|
||||
"uvicorn.loops.auto",
|
||||
"uvicorn.protocols.http.auto",
|
||||
"uvicorn.protocols.websockets.auto",
|
||||
"uvicorn.lifespan.on",
|
||||
]
|
||||
for pkg in ("onnxruntime", "faiss"):
|
||||
try:
|
||||
hiddenimports += collect_submodules(pkg)
|
||||
except Exception:
|
||||
# faiss is optional - the numpy fallback is exact and identical, just
|
||||
# slower, so its absence must not fail the build.
|
||||
pass
|
||||
|
||||
a = Analysis(
|
||||
["behavision/__main__.py"],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
# Nothing here draws a window; matplotlib/tkinter would add ~40 MB of
|
||||
# payload that no code path can reach.
|
||||
excludes=["tkinter", "matplotlib", "PyQt5", "PySide2", "IPython",
|
||||
"notebook", "pytest"],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name="behavision",
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=False, # UPX-packed binaries are a common AV false positive
|
||||
console=True, # the tray app is the GUI; this is the engine
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=False,
|
||||
name="behavision",
|
||||
)
|
||||
Reference in New Issue
Block a user