Description
The EquipChain platform requires data aggregation endpoints to power dashboards, analytics portals, and reporting tools. These endpoints will summarize meter reading data, contract interactions, and system metrics into meaningful time-series aggregates: daily summaries, monthly summaries, and custom date-range rollups.
The aggregation system must support: Daily Summaries — total readings per meter per day, average/min/max values, number of readings, and total active time; Monthly Summaries — same aggregations rolled up to monthly granularity; Custom Date Range — user-specified start and end dates with configurable interval grouping (hourly, daily, weekly, monthly); Cross-Meter Aggregations — fleet-wide summaries (total readings across all meters, average across fleet, top/bottom performers); Comparison Periods — ability to compare current period vs previous period (e.g., this month vs last month, this month vs same month last year).
Each aggregation endpoint should support filtering by meter ID, device group, timezone (for correct day boundaries), and aggregation type (count, sum, avg, min, max, p50, p95). Results should be cached (using Issue #12's Redis cache) with a TTL appropriate to the aggregation granularity (daily: 1 hour, monthly: 1 day). The aggregation engine should be efficient, processing data in-memory for MVP and designed to swap in database aggregation queries later.
Technical Context & Impact
Step-by-Step Implementation Guide
- Create Aggregation Service: Write
src/services/aggregator.js exporting functions: aggregateReadings(readings, { startDate, endDate, granularity, meters, aggregationType }). Implement grouping by time buckets (using date-fns or manual date math). Support count, sum, avg, min, max, p50, p95 aggregation types.
- Create Comparison Logic: Extend aggregator with
comparePeriods(currentPeriodData, previousPeriodData) returning delta and percentage change for each metric. Support previous period and year-over-year comparison modes.
- Create Analytics Routes: Write
src/routes/analytics.js with GET /api/analytics/daily-summary (query params: startDate, endDate, meterIds, timezone), GET /api/analytics/monthly-summary, GET /api/analytics/custom-range, GET /api/analytics/fleet-summary. Apply Zod validation schemas. Apply caching middleware with appropriate TTL.
- Create Validation Schemas: Write
src/schemas/analytics.schema.js with schemas for each analytics endpoint: validate dates are valid ISO strings, meterIds is an optional array of strings, granularity is one of ['hour', 'day', 'week', 'month'], timezone is a valid IANA timezone string.
- Populate Sample Data: Create a seed script
scripts/seed-readings.js that generates sample meter reading data for testing analytics. Store in the in-memory store.
- Write Tests: Create
tests/unit/aggregator.test.js testing aggregation logic with synthetic data: verify daily summaries match expected values, test empty periods, test meter filtering. Create tests/integration/analytics.test.js testing the full request/response cycle.
Verification & Testing Steps
- Run the seed script to populate sample readings data spanning 90 days across 3 meters.
- Call
GET /api/analytics/daily-summary?startDate=2026-01-01&endDate=2026-01-31 — verify the response contains 31 daily entries with correct counts, sums, and averages.
- Call
GET /api/analytics/monthly-summary?startDate=2026-01-01&endDate=2026-06-01 — verify 6 monthly entries with correct rollups.
- Call
GET /api/analytics/fleet-summary — verify fleet-wide totals and averages across all meters.
- Call
GET /api/analytics/custom-range?startDate=2026-01-01&endDate=2026-01-07&granularity=hour — verify hourly breakdown within the date range.
- Add comparison:
GET /api/analytics/daily-summary?compareWith=previous_period — verify response includes current and previous period data with delta/percentage fields.
Description
The EquipChain platform requires data aggregation endpoints to power dashboards, analytics portals, and reporting tools. These endpoints will summarize meter reading data, contract interactions, and system metrics into meaningful time-series aggregates: daily summaries, monthly summaries, and custom date-range rollups.
The aggregation system must support: Daily Summaries — total readings per meter per day, average/min/max values, number of readings, and total active time; Monthly Summaries — same aggregations rolled up to monthly granularity; Custom Date Range — user-specified start and end dates with configurable interval grouping (hourly, daily, weekly, monthly); Cross-Meter Aggregations — fleet-wide summaries (total readings across all meters, average across fleet, top/bottom performers); Comparison Periods — ability to compare current period vs previous period (e.g., this month vs last month, this month vs same month last year).
Each aggregation endpoint should support filtering by meter ID, device group, timezone (for correct day boundaries), and aggregation type (count, sum, avg, min, max, p50, p95). Results should be cached (using Issue #12's Redis cache) with a TTL appropriate to the aggregation granularity (daily: 1 hour, monthly: 1 day). The aggregation engine should be efficient, processing data in-memory for MVP and designed to swap in database aggregation queries later.
Technical Context & Impact
src/services/aggregator.jscontaining aggregation logic. Newsrc/routes/analytics.jswith endpoints under/api/analytics/. Newsrc/schemas/analytics.schema.jsfor validation.Step-by-Step Implementation Guide
src/services/aggregator.jsexporting functions:aggregateReadings(readings, { startDate, endDate, granularity, meters, aggregationType }). Implement grouping by time buckets (usingdate-fnsor manual date math). Supportcount,sum,avg,min,max,p50,p95aggregation types.comparePeriods(currentPeriodData, previousPeriodData)returning delta and percentage change for each metric. Support previous period and year-over-year comparison modes.src/routes/analytics.jswithGET /api/analytics/daily-summary(query params: startDate, endDate, meterIds, timezone),GET /api/analytics/monthly-summary,GET /api/analytics/custom-range,GET /api/analytics/fleet-summary. Apply Zod validation schemas. Apply caching middleware with appropriate TTL.src/schemas/analytics.schema.jswith schemas for each analytics endpoint: validate dates are valid ISO strings, meterIds is an optional array of strings, granularity is one of ['hour', 'day', 'week', 'month'], timezone is a valid IANA timezone string.scripts/seed-readings.jsthat generates sample meter reading data for testing analytics. Store in the in-memory store.tests/unit/aggregator.test.jstesting aggregation logic with synthetic data: verify daily summaries match expected values, test empty periods, test meter filtering. Createtests/integration/analytics.test.jstesting the full request/response cycle.Verification & Testing Steps
GET /api/analytics/daily-summary?startDate=2026-01-01&endDate=2026-01-31— verify the response contains 31 daily entries with correct counts, sums, and averages.GET /api/analytics/monthly-summary?startDate=2026-01-01&endDate=2026-06-01— verify 6 monthly entries with correct rollups.GET /api/analytics/fleet-summary— verify fleet-wide totals and averages across all meters.GET /api/analytics/custom-range?startDate=2026-01-01&endDate=2026-01-07&granularity=hour— verify hourly breakdown within the date range.GET /api/analytics/daily-summary?compareWith=previous_period— verify response includes current and previous period data with delta/percentage fields.