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
84 lines
3.6 KiB
Python
84 lines
3.6 KiB
Python
"""The installer describes a layout nothing else checks.
|
|
|
|
`tests/test_paths.py` already asserts that every file `behavision.spec` ships
|
|
actually exists, for the reason that a rename otherwise fails only inside the
|
|
bundle - the one place nothing is tested. The installer has the same property
|
|
and one worse: it runs on a machine none of us has, at a customer's counter,
|
|
and its failure mode is a shop PC that installs cleanly and then does nothing.
|
|
|
|
These are cheap string checks on purpose. They cannot prove the package works
|
|
on Windows - only a Windows box can - but they catch the class of mistake that
|
|
would otherwise get that far: a path renamed on one side of the build and not
|
|
the other.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
ISS = (ROOT / "installer" / "behavision.iss").read_text(encoding="utf-8")
|
|
PS1 = (ROOT / "installer" / "build.ps1").read_text(encoding="utf-8")
|
|
GO_DEFAULTS = (ROOT / "agent" / "pkg" / "config" / "config.go").read_text(encoding="utf-8")
|
|
|
|
|
|
def _sources() -> list[str]:
|
|
"""Every `Source:` path the [Files] section installs."""
|
|
return re.findall(r'^Source:\s*"([^"]+)"', ISS, re.MULTILINE)
|
|
|
|
|
|
def test_every_installed_source_is_produced_by_the_build():
|
|
"""A Source: the build never stages makes ISCC fail at compile time — but
|
|
only on the machine that compiles it, which is not this one."""
|
|
staged = {
|
|
r"..\dist\Behavision\Behavision.exe",
|
|
r"..\dist\Behavision\behavision-agent.exe",
|
|
r"..\dist\Behavision\engine\*",
|
|
r"vendor\MicrosoftEdgeWebview2Setup.exe",
|
|
}
|
|
assert set(_sources()) == staged, (
|
|
"installer [Files] and installer/build.ps1 have drifted apart"
|
|
)
|
|
for name in ("Behavision.exe", "behavision-agent.exe", "engine",
|
|
"MicrosoftEdgeWebview2Setup.exe"):
|
|
assert name in PS1, f"build.ps1 does not produce {name}"
|
|
|
|
|
|
def test_the_agent_looks_for_the_engine_where_the_installer_puts_it():
|
|
"""The one coupling between the Go side and the installer.
|
|
|
|
The app resolves `EngineExe` against the directory holding its own
|
|
executable. If the installer puts the engine somewhere else, the app starts,
|
|
shows a healthy window, and never recognises anybody.
|
|
"""
|
|
assert 'filepath.Join("engine", "behavision")' in GO_DEFAULTS
|
|
assert r'DestDir: "{app}\engine"' in ISS
|
|
|
|
|
|
def test_nothing_writable_is_placed_under_program_files():
|
|
"""The install root is read-only for the account the app runs as.
|
|
|
|
Everything written - the biometric database, logs, the camera list, the
|
|
downloaded models - belongs in the state root. An installer that seeds a
|
|
writable file under {app} would work for the administrator who installed it
|
|
and fail for the shop assistant who uses it.
|
|
"""
|
|
assert "{commonappdata}\\Behavision" in ISS
|
|
for src in _sources():
|
|
assert not src.endswith((".db", ".json", ".yaml", ".log")), src
|
|
|
|
|
|
def test_models_are_not_bundled():
|
|
"""~200 MB, downloaded resumably on first run. Bundling them quadruples the
|
|
package and forces a re-sign for a model change."""
|
|
assert not any(s.endswith((".onnx", ".caffemodel")) for s in _sources())
|
|
assert "setup-models" in ISS
|
|
|
|
|
|
def test_the_app_is_offered_at_startup_and_not_installed_as_a_service():
|
|
"""A service runs in session 0 and cannot draw a tray icon — Windows
|
|
session isolation, not a library limitation. Since the product is "the user
|
|
starts and stops it from the tray", autostart is a per-user Run entry."""
|
|
assert "{userstartup}" in ISS
|
|
assert "sc.exe" not in ISS and "nssm" not in ISS.lower()
|