[data-veracity] Portal attribution is fabricated: group deltas are split evenly across member cameras #28
Labels
No labels
blocked
bug
enhancement
high-priority
low-priority
needs-info
needs-triage
ready-for-agent
ready-for-human
referenced
research
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gabogg/hikcentral#28
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Artemis returns passenger counts per resource group, not per camera. The sync invents a per-camera breakdown by integer division:
Every member camera of a group therefore receives the same share of that group's traffic, plus at most one remainder unit. Over a full cycle each camera in an n-camera group converges to exactly
1/nof the group total, regardless of how many people actually walked through it.The deck's Portal Traversal Attribution panel (present in all three horizons —
RFC-ARCH-2026-004§4.1.4, §4.2.4, §4.3.4) rendersflow_share_pctper portal. With this ingestion those percentages are an artifact of integer division, not a measurement. A three-camera group will always read 33.3% / 33.3% / 33.3%.It is also mis-directed, not just evenly smeared
Membership of
in_cams/out_camsis decided by keyword matching on the camera name:ACCESOis the ordinary Spanish word for a portal, and in this facility the portals are named exactly that —ACCESO NORTE,ACCESO SUR(the RFC's own examples). Both are classifiedENTRANCEand land only inin_cams.ESTACIONAMIENTOmatches nothing, falls through toBIDIRECTIONAL, and lands in both lists (:1825-1826).Result for a group
{ACCESO NORTE, ACCESO SUR, ESTACIONAMIENTO}:out_cams == [ESTACIONAMIENTO], so 100% of facility egress is attributed to the parking camera, while the two main doors are recorded as entrance-only. The portal table then shows two portals with zero exits and one portal carrying every exit in the mall — none of which happened.Worse: a group with no bidirectional or exit-named camera
If keyword matching leaves
out_camsempty, events are written withcamera_index_code = <resource group code>. See the separate orphan-camera-code issue — that path is a runaway.KPIs corrupted
Portal Attribution (Day / Week / Month),
flow_share_pct,is_asymmetric_anomaly, theNATURAL_INGRESS_PORTAL/NATURAL_EGRESS_PORTALtopology described inCONTEXT.md§3, and any per-zone breakdown built onzone_name.Suggested fix
counting_cameras.direction_typeis already a persisted, operator-editable column — make it authoritative and have the name heuristic only seed a suggestion at discovery time, flagged for confirmation./people/advance/...byresourceIndexCode). If it does, ingest per camera and delete the division entirely.flow_share_pctat camera granularity rather than publishing a computed-looking number that carries no information.Needs confirmation
Whether per-camera passenger flow is available on the deployed HikCentral version. That single answer decides between fix (2) and fix (3), and fix (3) changes the RFC.
Blocks #14 Candidate 2, and affects a shipped view
Two additions to scope, raised while cross-checking against #14:
This blocks #14 Candidate 2 (Deepen the Backend Telemetry Streaming Seam). That candidate proposes streaming atomic
PassageFluxVector (camera, direction, count, timestamp). Thecamerafield has no real signal behind it until this issue is resolved, so the answer here — per-camera passenger flow available upstream, or not — decides whether Candidate 2 ships asPassageFluxVectoror asGroupFluxVector. See #14 (comment).This is already visible in production, not just in the proposed stats deck. The delivered
CommandDeckAdapterDirectional Flux Stream Feed renders per-camera>>> IN/<<< OUTlines fed by therecent_eventpayload broadcast atapp/services/occupancy_service.py:540-552, whosecamera_namecomes straight from the even split. With this facility's portal naming (ACCESO NORTE,ACCESO SUR→ENTRANCE;ESTACIONAMIENTO→BIDIRECTIONAL),out_camscollapses to the parking camera alone — so every<<< OUTline currently scrolling in the operator feed is attributed to it.Suggest prioritising this above the rest of the #26–#36 set on the strength of (2).
✅ Design settled (grilling session)
The
needs-infois answered: the Artemis catalog exposes no per-camera passenger-flow endpoint — only group-level (people/resourceGroupRealTimeCount,people/advance/resourceGroupList) and a spatial camera heatmap (people/statisticsHeatMapByTime). Per-camera attribution is therefore not measurable on this HikCentral. Fix (2) is impossible; we take fix (3) + fix (1).Settled spec
flow_share_pctacross resource groups; list member cameras as metadata with no per-camera percentage. This removes the fabricated even-split (base_in = delta_in // num_in,:1903-1909) and changes the RFC (§4.1.4 / §4.2.4 / §4.3.4).direction_type(fix 1):counting_cameras.direction_type(database.py:230, operator-editable) becomes the source of truth forin_cams/out_cams. The name heuristic (infer_camera_direction_and_zone,:341) only seeds a suggestion at discovery, written withneeds_confirmation. The sync never re-derives direction from the camera name (kills theACCESO-as-entrance-only misdirection that put 100% of egress on the parking camera). Existing rows backfilledneeds_confirmation = truefor a one-time operator review.CONTEXT.md§3 and the RFC.Deliverables
Blocked by #28).Acceptance criteria (supersede "Needs confirmation")
flow_share_pctat resource-group granularity; no per-camera %.direction_typeauthoritative forin_cams/out_cams; name heuristic discovery-only withneeds_confirmation; existing rows backfilledneeds_confirmation = true.CONTEXT.md§3 + RFC document per-camera-group counting.{ACCESO NORTE, ACCESO SUR, ESTACIONAMIENTO}with operator-set directions attributes egress correctly, not 100% to the parking camera.Re-tagged
ready-for-agent.Unblocks #51 (orphan camera code) — that cleanup is
Blocked by #28and must land after this.gabogg referenced this issue2026-09-22 15:22:42 +00:00
gabogg referenced this issue2026-09-22 16:31:06 +00:00
⚠️ Amendment to the settled spec — fix (1) is superseded
From the grilling session of 2026-09-22, which scoped this work into two PRs. The fix (3) half of the settled spec (group-level attribution, panel relabel, no per-camera %) is unchanged. This amends fix (1) only.
What the settled spec said
Why that no longer applies
Artemis returns
current_artemis_in/current_artemis_outper group, already split by direction (occupancy_service.py:1898-1899, fromresourceGroupRealTimeCountat:1838). Thein_cams/out_camslists exist for exactly one purpose: deciding which camera rows receive that group's directional delta via the even split.Fix (3) deletes the even split. Once events attribute to the group, there is nothing left for a camera-membership split to decide — so making
direction_typeauthoritative for it is repairing machinery that is being removed. Keeping a correct-but-unused derivation would be speculative generality against a per-camera capability the Artemis catalog says does not exist.What replaces it
in_cams/out_camsderivation (:1814-1832) and its heuristic call (:1819) are deleted, not corrected.direction_typebecomes camera metadata — an operator-maintained label for what a camera physically is — not a counting input.infer_camera_direction_and_zone(:341) stays discovery-only at:422/:484, still writingneeds_confirmation.needs_confirmationdrops from correctness-critical to a display nicety; it is retained, but it is no longer an AC blocker.:1891-1896) becomes a direct group lookup. This is what made #29's runaway possible — an absent camera silently summed to0— so the fix closes that by construction rather than by guard alone.Two further findings from the same session
occupancy_controller.py:365holds a second, independent derivation:in_cams = [c for c in cams if c["direction_type"] in ("ENTRANCE", "BIDIRECTIONAL")] or cams. Sodirection_typehas already been authoritative in the controller while the sync used the name heuristic — the two have diverged for as long as both have existed. It also carries the sameor camsfallback shape that produced the orphan bug. Folded into phase 2.The group was never persisted.
grep -rn "resource_group\|resourceGroupIndexCode" app/db/ app/schemas/returns zero hits — the group→camera map is rebuilt transiently from Artemis each sync and discarded. Group-levelflow_share_pcttherefore requires a schema migration that the original scoping did not identify. That migration is phase 1.Where the work now lives
CONTEXT.md§3 and ADR 0005.Split because emitting group-level events while the aggregates still inner-join
counting_cameraswould drop every event and take all published KPIs to zero between the two merges.The RFC acceptance criteria are dropped:
docs/architecture/rfc-executive-statistics-deck.mdexists only inside draft PR #20 and cannot be edited outside it. ADR 0005 andCONTEXT.mdcarry the decision; PR #20 rebases and reconciles its own §4.x panels.🤖 Generated with Claude Code
gabogg referenced this issue2026-09-23 22:49:06 +00:00