From 9df23c3c87ca6b31f547434c0dda9930aaf77fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 12:04:07 +0200 Subject: [PATCH] chore(analytics): drop unreferenced movingAverage and weeklyAverages exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neither function had any production caller. The chart components in src/components/charts/* implement a local movingAverageByPoints rather than reaching for the lib helper, and weeklyAverages was never imported outside its own test file. detectAnomalies stays — it is reached transitively through summarize() which the analytics, insights, and measurements-series routes all consume. --- .../analytics/__tests__/correlations.test.ts | 60 +---------------- src/lib/analytics/__tests__/trends.test.ts | 22 ------- src/lib/analytics/correlations.ts | 66 ------------------- src/lib/analytics/trends.ts | 27 -------- 4 files changed, 1 insertion(+), 174 deletions(-) diff --git a/src/lib/analytics/__tests__/correlations.test.ts b/src/lib/analytics/__tests__/correlations.test.ts index 12c28fddf..79a2e3cb8 100644 --- a/src/lib/analytics/__tests__/correlations.test.ts +++ b/src/lib/analytics/__tests__/correlations.test.ts @@ -1,10 +1,6 @@ import { describe, it, expect } from "vitest"; import type { DataPoint } from "../trends"; -import { - pairByTimestamp, - pearsonCorrelation, - weeklyAverages, -} from "../correlations"; +import { pairByTimestamp, pearsonCorrelation } from "../correlations"; function makePoints(values: number[], startDaysAgo = 30): DataPoint[] { const now = Date.now(); @@ -107,57 +103,3 @@ describe("pearsonCorrelation", () => { }); }); -describe("weeklyAverages", () => { - it("returns empty for empty input", () => { - expect(weeklyAverages([])).toEqual([]); - }); - - it("groups data points by ISO week", () => { - const data = makePoints( - [70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83], - 14, - ); - const result = weeklyAverages(data); - // 14 days = 2-3 weeks - expect(result.length).toBeGreaterThanOrEqual(2); - expect(result.length).toBeLessThanOrEqual(3); - }); - - it("averages values within same week", () => { - const now = new Date(); - // Find the Monday of current week - const day = now.getDay(); - const monday = new Date(now); - monday.setDate(now.getDate() - day + (day === 0 ? -6 : 1)); - monday.setHours(12, 0, 0, 0); - - const tuesday = new Date(monday); - tuesday.setDate(monday.getDate() + 1); - - const data: DataPoint[] = [ - { date: monday, value: 70 }, - { date: tuesday, value: 80 }, - ]; - const result = weeklyAverages(data); - expect(result).toHaveLength(1); - expect(result[0].value).toBe(75); - }); - - // v3 audit caught a TZ bug: a Sunday-evening Berlin reading (e.g. 22:00 - // local = 21:00 UTC in summer / 21:00 UTC in winter via DST) bucketed - // into next week on UTC servers because `Date.getDay()` was system-local. - // The Berlin-TZ-aware implementation puts the same point into THIS week. - it("buckets Sunday-evening Berlin into the same ISO week as the preceding Monday", () => { - // 2024-08-04 is a Sunday; 22:00 Berlin = 20:00 UTC (CEST). The Monday - // of the same Berlin week is 2024-07-29. - const sundayEveningBerlin = new Date("2024-08-04T20:00:00.000Z"); - const mondayMidday = new Date("2024-07-29T11:00:00.000Z"); - const result = weeklyAverages([ - { date: mondayMidday, value: 70 }, - { date: sundayEveningBerlin, value: 90 }, - ]); - expect(result).toHaveLength(1); - expect(result[0].value).toBe(80); // (70 + 90) / 2 - expect(result[0].date.toISOString()).toBe("2024-07-29T00:00:00.000Z"); - }); -}); diff --git a/src/lib/analytics/__tests__/trends.test.ts b/src/lib/analytics/__tests__/trends.test.ts index 4c295c28c..4bbf9c16c 100644 --- a/src/lib/analytics/__tests__/trends.test.ts +++ b/src/lib/analytics/__tests__/trends.test.ts @@ -1,6 +1,5 @@ import { describe, it, expect } from "vitest"; import { - movingAverage, trendSlope, detectAnomalies, summarize, @@ -15,27 +14,6 @@ function makePoints(values: number[], startDaysAgo = 30): DataPoint[] { })); } -describe("movingAverage", () => { - it("returns empty for empty input", () => { - expect(movingAverage([], 7)).toEqual([]); - }); - - it("calculates moving average over window", () => { - const data = makePoints([70, 71, 72, 73, 74, 75, 76], 7); - const result = movingAverage(data, 3); - expect(result).toHaveLength(7); - // Last point should be avg of last 3 days - expect(result[result.length - 1].value).toBeCloseTo(75, 0); - }); - - it("handles single data point", () => { - const data = makePoints([100], 1); - const result = movingAverage(data, 7); - expect(result).toHaveLength(1); - expect(result[0].value).toBe(100); - }); -}); - describe("trendSlope", () => { it("returns null for insufficient data", () => { expect(trendSlope([], 7)).toBeNull(); diff --git a/src/lib/analytics/correlations.ts b/src/lib/analytics/correlations.ts index 55df438aa..914d5fb11 100644 --- a/src/lib/analytics/correlations.ts +++ b/src/lib/analytics/correlations.ts @@ -105,69 +105,3 @@ export function pearsonCorrelation( return { r, strength, n }; } -const BERLIN_DATE_PARTS = new Intl.DateTimeFormat("en-CA", { - timeZone: "Europe/Berlin", - year: "numeric", - month: "2-digit", - day: "2-digit", - weekday: "short", -}); - -/** - * Aggregate data into weekly averages, ISO-Monday-based, in Europe/Berlin. - * - * v3 audit caught a TZ bug here: the previous implementation used - * `Date.getDay()` / `Date.getDate()` (system local) so on a UTC server, a - * Sunday-evening Berlin reading bucketed into the next week. We now derive - * year/month/day/weekday in Berlin via Intl.DateTimeFormat and rebuild the - * Monday key from there. The returned `date` for each bucket is the Monday - * UTC midnight — sortable, comparable across DST. - */ -export function weeklyAverages(data: DataPoint[]): DataPoint[] { - if (data.length === 0) return []; - - const sorted = [...data].sort((a, b) => a.date.getTime() - b.date.getTime()); - - const weeks = new Map(); - - const WEEKDAY_INDEX: Record = { - Mon: 1, - Tue: 2, - Wed: 3, - Thu: 4, - Fri: 5, - Sat: 6, - Sun: 7, - }; - - for (const point of sorted) { - const parts = BERLIN_DATE_PARTS.formatToParts(point.date); - const yearStr = parts.find((p) => p.type === "year")?.value ?? "1970"; - const monthStr = parts.find((p) => p.type === "month")?.value ?? "01"; - const dayStr = parts.find((p) => p.type === "day")?.value ?? "01"; - const weekdayStr = parts.find((p) => p.type === "weekday")?.value ?? "Mon"; - - const isoWeekday = WEEKDAY_INDEX[weekdayStr] ?? 1; - // Days to subtract to land on Monday of the same ISO week in Berlin. - const offsetDays = isoWeekday - 1; - - const dayUtc = new Date(`${yearStr}-${monthStr}-${dayStr}T00:00:00.000Z`); - const monday = new Date(dayUtc.getTime() - offsetDays * 86_400_000); - const key = monday.toISOString().slice(0, 10); - - const existing = weeks.get(key); - if (existing) { - existing.sum += point.value; - existing.count++; - } else { - weeks.set(key, { sum: point.value, count: 1, date: monday }); - } - } - - return Array.from(weeks.values()) - .sort((a, b) => a.date.getTime() - b.date.getTime()) - .map((w) => ({ - date: w.date, - value: Math.round((w.sum / w.count) * 100) / 100, - })); -} diff --git a/src/lib/analytics/trends.ts b/src/lib/analytics/trends.ts index 581f86e95..c5a57c41a 100644 --- a/src/lib/analytics/trends.ts +++ b/src/lib/analytics/trends.ts @@ -8,33 +8,6 @@ export interface DataPoint { value: number; } -// ── Moving Average ─────────────────────────────────────── - -export function movingAverage( - data: DataPoint[], - windowDays: number, -): DataPoint[] { - if (data.length === 0) return []; - - const sorted = [...data].sort((a, b) => a.date.getTime() - b.date.getTime()); - const result: DataPoint[] = []; - const windowMs = windowDays * 24 * 60 * 60 * 1000; - - for (const point of sorted) { - const windowStart = point.date.getTime() - windowMs; - const windowPoints = sorted.filter( - (p) => - p.date.getTime() > windowStart && - p.date.getTime() <= point.date.getTime(), - ); - const avg = - windowPoints.reduce((sum, p) => sum + p.value, 0) / windowPoints.length; - result.push({ date: point.date, value: Math.round(avg * 100) / 100 }); - } - - return result; -} - // ── Trend Slope ────────────────────────────────────────── export interface TrendSlope {