feat(analytics): daily totals series for a date range #84

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

Route

GET /api/statistics/timeseries/daily?start=YYYY-MM-DD&end=YYYY-MM-DD[&bucket=hour]

One row per business day: visitors (ingress), egress, highest peak inside and its time, open-window mean dwell, weekday, holiday flag, and quality (gap-estimated, Data Trust, Cycle Completeness).

Used by

  • Week: visitors by day, average visit by day; the previous-week and last-year comparison bars come from two more calls over the shifted ranges (or an optional compare=previous,last_year parameter).
  • Month: the month day by day, running total against the previous month (a second call over the previous month), peak people inside per day, average by weekday, weekend share.

Notes

The existing /timeseries/multiday overlays intraday curves for a list of dates; it does not return per-day totals, and calling it for 31 dates is not a substitute.

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)

  • Absorbs #87: bucket=hour returns each business day's 24 hourly visitor counts (04:00 → 04:00) with gap-estimated cells marked. The week heatmap uses it.
  • No compare= parameter: the deck makes separate calls for the previous period and last year.
  • Range limits: at most 62 days for daily rows, 7 days with bucket=hour; longer ranges return VALIDATION_ERROR.
  • Holiday days carry a flag so per-day graphs can mark them.
## Route `GET /api/statistics/timeseries/daily?start=YYYY-MM-DD&end=YYYY-MM-DD[&bucket=hour]` One row per business day: visitors (ingress), egress, highest peak inside and its time, open-window mean dwell, weekday, holiday flag, and quality (gap-estimated, Data Trust, Cycle Completeness). ## Used by - Week: visitors by day, average visit by day; the previous-week and last-year comparison bars come from two more calls over the shifted ranges (or an optional `compare=previous,last_year` parameter). - Month: the month day by day, running total against the previous month (a second call over the previous month), peak people inside per day, average by weekday, weekend share. ## Notes The existing `/timeseries/multiday` overlays intraday curves for a list of dates; it does not return per-day totals, and calling it for 31 dates is not a substitute. ## 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) - Absorbs #87: `bucket=hour` returns each business day's 24 hourly visitor counts (04:00 → 04:00) with gap-estimated cells marked. The week heatmap uses it. - No `compare=` parameter: the deck makes separate calls for the previous period and last year. - Range limits: at most 62 days for daily rows, 7 days with `bucket=hour`; longer ranges return `VALIDATION_ERROR`. - Holiday days carry a flag so per-day graphs can mark them.
Author
Owner

Implemented by #89, merged into master as 3541af5 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 #89, merged into `master` as 3541af5 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#84
No description provided.