feat(analytics): statistics period summary KPIs #83

Closed
opened 2026-09-24 23:23:38 +00:00 by gabogg · 1 comment
Owner

Route

GET /api/statistics/periods/{granularity}/{start}/summary

Returns the KPI strip for one closed period, including the comparisons the deck shows.

Fields needed by the mockup

  • All periods: visitors; vs previous period (always); vs same period last year (only when last year has data, otherwise omitted); highest peak people inside (value and time); open-window mean dwell; weekend share (week, month).
  • Day: visitors vs the same weekday last week (always) and vs the same day last year (only when last year has data; otherwise omitted, not zero); vs usual same weekday (mean of the previous 4 same weekdays); busiest hour; top entrance.
  • Week: daily average; busiest day.
  • Month: daily average (and previous month's); best day; highest peak inside. No year-to-date figure (yearly scope is out).
  • Each comparison returns null with a reason when its reference period has no data.
  • Quality: gap-estimated days, Data Trust and Cycle Completeness summary.

Context

Part of the statistics deck redesign, draft RFC in #80 (docs/architecture/rfc-statistics-deck-display-model.md; mockup linked there). Period selection was decided in #81 (closed): a period is identified by granularity (day / week / month) and its first business day; see the glossary terms Complete Period and Partial Period. A year period is out of scope.

Shared constraints (all statistics-deck routes)

  • Periods are closed business periods in facility time (daily_reset_time boundaries, app/facility_time.py); an in-progress cycle is never returned.
  • Only counted cameras contribute (_COUNTED_CAMERA_JOIN / _COUNTED_CAMERA_FILTER); quarantined events never do.
  • Every period-level figure carries its data quality: gap-estimated days (ingestion anomaly ledger), Data Trust and Cycle Completeness for the cycles involved, so the deck can mark estimates.
  • Dwell means the open-window Mean Dwell (ADR 0006), not the retired definitions.
  • Access: any logged-in user, read-only (require_auth). Deck routes live in a separate /api/statistics/ router so admin-only /api/analytics/ routes can't leak into the deck.
  • Controllers stay thin; aggregation lives in analytics_service, SQL in repositories.

Triage decisions (2026-09-24)

  • Route: GET /api/statistics/periods/{granularity}/{start}/summary.
  • A comparison with no reference data returns null plus a reason code (for example NO_DATA_PREVIOUS_PERIOD); the deck shows "—" with the reason. Last-year comparisons are omitted when last year has no data.
  • "vs usual weekday" uses the Usual Weekday Baseline (glossary; defined in #88).
  • Weekend Share counts Saturday and Sunday business days.
  • Partial periods: figures cover only days with data; the response carries covered_days / business_days.
## Route `GET /api/statistics/periods/{granularity}/{start}/summary` Returns the KPI strip for one closed period, including the comparisons the deck shows. ## Fields needed by the mockup - **All periods:** visitors; vs previous period (always); vs same period last year (only when last year has data, otherwise omitted); highest peak people inside (value and time); open-window mean dwell; weekend share (week, month). - **Day:** visitors vs the same weekday last week (always) and vs the same day last year (only when last year has data; otherwise omitted, not zero); vs usual same weekday (mean of the previous 4 same weekdays); busiest hour; top entrance. - **Week:** daily average; busiest day. - **Month:** daily average (and previous month's); best day; highest peak inside. No year-to-date figure (yearly scope is out). - Each comparison returns `null` with a reason when its reference period has no data. - Quality: gap-estimated days, Data Trust and Cycle Completeness summary. ## Context Part of the statistics deck redesign, draft RFC in #80 (`docs/architecture/rfc-statistics-deck-display-model.md`; mockup linked there). Period selection was decided in #81 (closed): a period is identified by `granularity` (`day` / `week` / `month`) and its first business day; see the glossary terms **Complete Period** and **Partial Period**. A year period is out of scope. ## Shared constraints (all statistics-deck routes) - Periods are **closed** business periods in facility time (`daily_reset_time` boundaries, `app/facility_time.py`); an in-progress cycle is never returned. - Only **counted cameras** contribute (`_COUNTED_CAMERA_JOIN` / `_COUNTED_CAMERA_FILTER`); quarantined events never do. - Every period-level figure carries its data quality: gap-estimated days (ingestion anomaly ledger), **Data Trust** and **Cycle Completeness** for the cycles involved, so the deck can mark estimates. - Dwell means the open-window **Mean Dwell** (ADR 0006), not the retired definitions. - Access: any logged-in user, read-only (`require_auth`). Deck routes live in a separate `/api/statistics/` router so admin-only `/api/analytics/` routes can't leak into the deck. - Controllers stay thin; aggregation lives in `analytics_service`, SQL in repositories. ## Triage decisions (2026-09-24) - Route: `GET /api/statistics/periods/{granularity}/{start}/summary`. - A comparison with no reference data returns `null` plus a reason code (for example `NO_DATA_PREVIOUS_PERIOD`); the deck shows "—" with the reason. Last-year comparisons are omitted when last year has no data. - "vs usual weekday" uses the **Usual Weekday Baseline** (glossary; defined in #88). - **Weekend Share** counts Saturday and Sunday business days. - Partial periods: figures cover only days with data; the response carries `covered_days` / `business_days`.
Author
Owner

Implemented by #92, merged into master as cf20ef2 after two review passes (Standards + Spec). Non-blocking follow-ups are tracked in #96–#100. Closing; this unblocks #80.

🤖 Generated with Claude Code

Implemented by #92, merged into `master` as cf20ef2 after two review passes (Standards + Spec). Non-blocking follow-ups are tracked in #96–#100. Closing; this unblocks #80. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gabogg/hikcentral#83
No description provided.