[data-veracity] Peak occupancy is systematically overestimated by intra-timestamp event ordering #30
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#30
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
get_cycle_peak_occupancy_async(app/db/occupancy_repository.py:1006) walks events in time order and records the running maximum:Every event produced by a single poll carries the identical timestamp —
record_counting_event_asyncis called withtimestamp=now(app/services/occupancy_service.py:1909, 1921). So each poll writes a cluster of rows sharing onetimestamp_epoch, andORDER BY timestamp_epoch ASCleaves the order within that cluster unspecified.In practice it is insertion order, and the sync loop writes all IN events before all OUT events (
:1905-1911then:1913-1922). The cumulative walk therefore adds the whole tick's ingress before subtracting any of its egress, creating an artificial local maximum on every single poll.The reported peak is the maximum over ~28,800 such artificial spikes per day, so it is biased upward by roughly one poll's worth of ingress — systematically, never downward.
Why it is much worse than "one poll's worth"
Combine with poll-time stamping (separate issue): after any ingestion gap — network blip, Artemis error, the early-return paths at
:1786and:1845— the entire accumulated backlog is written at the single timestamp of the next successful poll. All of that backlog's ingress is applied before any of its egress. A 30-minute outage over the lunch peak can inflate the reported peak by hundreds of persons, and the peak timestamp is pinned to the recovery moment rather than the real peak.KPIs corrupted
Peak Occupancy (
O_max) and its timestampt_peak— headline KPI in all three horizons. Week "Peak Day" and "Avg Daily Peak". Month "Max Month Peak" and the capacity-utilisation percentage derived from it. The occupancy envelope's maximum in the Day chart.Suggested fix
GROUP BY timestamp_epochproducingSUM(IN) , SUM(OUT), then applycum_in += in; cum_out += outonce per distinct timestamp. This removes the ordering dependence entirely and is also faster.get_bucketed_cycle_flow_async(:1471) already does the correct join and grouping.peak_timestamp_epochalongside the formatted string.CompletePeriodMetricsdeclarespeak_timestamp_epoch: float | Nonebut this method returns onlytimestamp_formatted, so Phase 2 has nothing to populate it with.timestamp_formattedis a bare%I:%M:%S %plocal-time string with no date — unusable as the peak marker for the Week and Month horizons.Being addressed in draft PR #68 together with #26, #31 and #30 (passenger flow ingestion honesty). The PR description lists the settled spec plus the open design points that still need a decision (restart cold start, a floor for the adaptive cap, a shared anomaly/gap ledger table, and how trust rules see reconstructed hours).
gabogg referenced this issue2026-09-24 13:13:51 +00:00
gabogg referenced this issue2026-09-24 13:13:53 +00:00