Record what the live system actually does, measured not assumed
Production had 1,211 events accepted and six recognised customers from the office cameras - the first time the whole chain has carried a real person, and the project had never been able to claim it. Repeat sightings score 0.44-0.72, a distribution the match threshold sits clearly below, on the head-height camera this file has recommended since August. fraction_below_gate is still 0.59, so the visit count is a floor and the report says so beside it. The face-image chain was exercised on production as a shop PC does it - upload URL, PUT to object storage, anonymous read refused 403. Every server link holds; the only reason a customer has no photo is app.store_faces being false by default, which is a data-protection decision rather than a gap. Sixteen mobile-API checks pass as a staff account. Three apparent bugs were test errors and are written down so nobody re-files them, along with the one field name a caller could guess wrong (site_token). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
4
API.md
4
API.md
@@ -1126,3 +1126,7 @@ a user session is refused.
|
||||
|
||||
A web or mobile client never calls them. They are listed here so nobody wonders
|
||||
what they are.
|
||||
|
||||
One field name, because it is the only one in the product that is easy to guess
|
||||
wrong: `POST /api/agent/enrol` takes **`site_token`** (the installation code),
|
||||
not `code`, plus an optional `device`. Verified against production.
|
||||
|
||||
75
CLAUDE.md
75
CLAUDE.md
@@ -2151,6 +2151,81 @@ correctly calls them one person. Tests that need several different people use
|
||||
`distinctFace(i)`, which is orthogonal per index.
|
||||
|
||||
|
||||
## It recognises people, and that is now measured rather than assumed
|
||||
|
||||
Verified against **production** on 2026-09-24, reading what the live system had
|
||||
already recorded from the office cameras on 15-19 September. Until this, every
|
||||
claim about recognition rested on the August engine tests; the whole chain -
|
||||
camera to engine to agent to broker to server to a screen - had never once
|
||||
carried a real person.
|
||||
|
||||
```
|
||||
6 customers, 36 visits, 1,211 events accepted, 0 dropped, 0 duplicates
|
||||
Visitor 1 15 visits Visitor 3 8 visits Visitor 5 7 visits
|
||||
same-person similarity on returning matches: 0.437 - 0.716
|
||||
attributes travelling with each arrival: age 37 (spread 2), gender Male
|
||||
```
|
||||
|
||||
Those similarities are the point. The August measurement on this site said
|
||||
`match_threshold: 0.42` sat *inside* the same-person distribution for the
|
||||
overhead camera and no threshold could separate a person from themselves. On
|
||||
`cam2` the same code now returns 0.54, 0.64, 0.68, 0.72 for repeat sightings of
|
||||
the same people - a distribution the threshold sits clearly below. The camera
|
||||
that produced them is the open-office one at roughly head height, which is
|
||||
precisely the fix this file has recommended since August.
|
||||
|
||||
What is still true and unflattering: `fraction_below_gate` on that site is
|
||||
**0.59**, reported with `worst_site` beside it. Well over half the faces those
|
||||
two cameras see are still too poor to enrol. The footfall figure of 36 visits
|
||||
is therefore a floor, not a count, and the report says so in the same response -
|
||||
which is the entire reason that number travels with the one it qualifies.
|
||||
|
||||
### The image chain works; the engine is simply not capturing
|
||||
|
||||
Exercised on production exactly as a shop PC does it, end to end:
|
||||
|
||||
```
|
||||
enrolment code -> agent token 200
|
||||
POST /api/agent/upload-url 200 behavision/v2/<client>/<site>/2026/09/24/<random>.jpg
|
||||
PUT <presigned url> 200 JPEG in DigitalOcean Spaces
|
||||
anonymous GET of that same object 403 private, as the signature demands
|
||||
staff GET /api/visitors/{id}/image 404 "No photo was captured for this visit."
|
||||
```
|
||||
|
||||
Every server-side link holds: the key is minted by the server and scoped to one
|
||||
site, the ACL is inside the signature, and an unauthenticated read is refused.
|
||||
The 404 at the end is not a fault - it is `app.store_faces: false`, the shipped
|
||||
default, so the engine never wrote a crop for the agent to upload. Turning
|
||||
images on is one setting on the shop PC and a deliberate change to what the
|
||||
system is under GDPR and India's DPDP, which is why it is off until somebody
|
||||
decides otherwise rather than on until somebody notices.
|
||||
|
||||
### The mobile API, walked as a phone would
|
||||
|
||||
Sixteen checks against production as a **staff** account: login, arrivals feed
|
||||
with cursor, arrivals carrying `visitor_id` and `site_slug`, customer search,
|
||||
customer history, photo endpoint, profile write, purchase, device list, token
|
||||
rotation (the retired access token correctly 401s), team read allowed, invite
|
||||
refused 403, admin surface 404, SSE stream delivering rows, and Loya answering.
|
||||
All pass.
|
||||
|
||||
Three things that looked like product bugs and were not, recorded so the next
|
||||
person does not re-file them:
|
||||
|
||||
- `POST /api/purchases` takes `items` as a **list of strings**, not a count.
|
||||
API.md had it right; the test was wrong.
|
||||
- `new` and `returning` live **inside each bucket** of a footfall report, not at
|
||||
the top level. `total`, `visits`, `fraction_below_gate` and `worst_site` are
|
||||
the top-level fields.
|
||||
- `?range=7d` is not a parameter. Reports take `from` / `to` / `bucket` / `site`,
|
||||
and an unknown query parameter is silently ignored - so an invented one
|
||||
returns the default 30-day window rather than an error. That hazard is
|
||||
already recorded above for `site` vs `site_id`; it applies here too.
|
||||
|
||||
`POST /api/agent/enrol` takes **`site_token`**, not `code` - the only field name
|
||||
in the product a caller could reasonably guess wrong, and it is a route no
|
||||
client app should ever call.
|
||||
|
||||
## Setting up on a new machine
|
||||
|
||||
1. Copy the `Behavision` folder **including `.env`** (gitignored, holds
|
||||
|
||||
Reference in New Issue
Block a user