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
103 lines
3.0 KiB
Python
103 lines
3.0 KiB
Python
# -*- 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",
|
|
)
|