2025-09-22-server-computed-analytics
ADR: Server-computed Weekly Analytics and Dashboard Endpoints
- Status: Proposed
- Date: 2025-09-22
- Owners: Frontend + Backend teams
Context
The frontend currently derives several analytics at runtime from weekly snapshots and project lists (e.g., Starts/Finishes, Throughput, Active Ratio helpers, moving averages, week-over-week deltas, and month bucketing). This duplication creates:
- Inconsistent definitions across clients and time (ordering, timezone, rounding).
- Fragile logic (e.g., most-recent-first vs ascending sort, partial-week shading heuristics).
- Extra client CPU and multiple requests for basic dashboard KPIs.
Recent live OpenAPI additions already provide avg_wip, max_wip, and sample_count in weekly rollups. We propose extending server output to cover the remaining derived metrics and offer summary endpoints to simplify the frontend.
Decision
Shift key derived analytics from client-side computation to server-side responses. Concretely:
- Enrich
GET /stats/weeklyitems (ascending order) with:
- Identity and ordering:
week_id(e.g.,2025-W38),period_start,period_end,index_asc(0-based),is_partial_week(true if the trailing week isnβt complete at generation time).
- Deltas and derived counts:
starts,finishes,net(starts - finishes),delta_active,delta_backlog(vs previous week end snapshot).
- Throughput helpers:
throughput(alias of finishes),throughput_ma4(4-week moving average),throughput_wow(last/prev relative delta).
- Active ratio helpers:
active_ratio_end,active_ratio_avg, plusactive_ratio_ma4,active_ratio_wow.
- WIP helpers (building on existing fields):
avg_wip,max_wip, pluswip_ma4,wip_wow.
- Progress distribution:
progress_bins(0β100% histogram),progress_weighted_mean.
- Samples:
sample_count.
- Add summary and aligned-series endpoints for dashboards:
GET /dashboard/summaryβ one-hop KPI payload (projects counts, customers, people, shared %, average duration, upcoming/ending soon, missing dates) withgenerated_atandtz.GET /dashboard/sparklinesβ aligned 12-week labels and series foractive_ratio,throughput,wip_avg,wip_max(with MA(4) and WoW), configurablewindowandtz.GET /counts/active-by-monthβ backend month bucketing for last N months withtzandgenerated_at.
All arrays are ordered ascending in time. Default timezone is UTC; clients may request an IANA timezone via tz.
Alternatives Considered
- Keep analytics client-side: simplest for backend, but perpetuates inconsistencies and UI bugs (ordering, partial-week) and increases client complexity.
- GraphQL computed fields: flexible but requires new infra; REST with additive fields meets current needs with lower effort.
- Batch-only endpoints (no helpers): reduces hops but still requires re-implementing helpers client-side.
Consequences
- Pros
- Consistent, audited definitions across clients (one source of truth).
- Less client massaging and fewer round-trips; simpler UI code paths.
- Fewer timezone/order bugs; easier testing via golden payloads.
- Cons
- Backend complexity and compute cost (moving averages, WoW, deltas).
- Requires caching/invalidation strategy and light versioning.
API Changes (summary)
Additive changes to existing WeeklyRollup and three new endpoints. Sketches below (OpenAPI excerpts):
Backward Compatibility
- Changes to
WeeklyRollupare additive; existing consumers remain valid. - New endpoints are optional. Frontend will initially implement a dual-path: use server-provided helpers when available and fall back to current client computation when absent.
- Consider adding a lightweight response header (e.g.,
X-Stats-Version: 2025-09-22) or aversionproperty for debugging/multi-client environments.
Rollout Plan
- Backend implements enriched
WeeklyRollupfields (behind a feature flag if needed) and new endpoints. - Frontend consumes new fields with graceful fallback; add metrics logging when falling back.
- Bake in staging for at least one full week of data to validate rolling metrics.
- Flip default to server-computed in production; remove client computation after 2β4 weeks.
Performance & Caching
- Precompute weekly rollups on schedule (e.g., hourly/daily) and cache responses (ETag/Last-Modified + CDN/local cache with short TTL).
- Moving averages are cheap to compute incrementally; optionally persist them to avoid per-request scanning.
- Summary endpoints are light; cache per
tz.
Security & Privacy
- Payloads contain aggregate counts only; no PII beyond existing entities.
- Rate-limit externally exposed endpoints consistent with current API policy.
Testing Strategy
- Backend unit tests for delta/throughput/ratio helpers and partial-week flag.
- Contract tests (e.g., Schemathesis) against OpenAPI.
- Golden fixtures of 12β16 weeks for reproducible expectations (ascending order required).
- Frontend Vitest toggles: simulate presence/absence of helpers to ensure fallback behavior.
Acceptance Criteria
GET /stats/weeklyreturns ascending weeks withis_partial_weekand the helper fields listed above.GET /dashboard/summaryreturns stable KPIs withgenerated_atandtz.GET /dashboard/sparklinesreturns alignedlabelsand series arrays for requested metrics.GET /counts/active-by-monthreturns month-bucketed active counts with the requestedmonthsandtz.- Frontend renders throughput and WIP KPIs/sparklines without local recomputation when helpers are present.
Notes
- Keep numeric fields as numbers; clients will format units. Return both ratio and count forms where helpful for reuse.
- Prefer UTC default with explicit
tzparameter for deterministic server behavior.