diff --git a/src/lib/insights/derived/__tests__/coincident-deviation.test.ts b/src/lib/insights/derived/__tests__/coincident-deviation.test.ts index 5e035a06d..6b08af942 100644 --- a/src/lib/insights/derived/__tests__/coincident-deviation.test.ts +++ b/src/lib/insights/derived/__tests__/coincident-deviation.test.ts @@ -33,6 +33,7 @@ import { computeCoincidentDeviation, classifyDeviation, COINCIDENT_FIRE_THRESHOLD, + sameHoursStanding, } from "../coincident-deviation"; const PROFILE = { ageYears: 40, sex: "MALE" as const }; @@ -351,3 +352,40 @@ describe("glucose — a day in progress is compared with the same hours", () => ).toBeUndefined(); }); }); + +describe("sameHoursStanding", () => { + it("reads an hourly-mean day by its day mean, not by sum / n", () => { + // Twelve dense readings in one hour and one in another: the mean of the + // hours' means is 100, the raw mean is 140. A type on the hourly-mean + // reader carries `dayMean`, and that is the day's value. + const day = (d: string, dayMean: number) => ({ + day: d, + n: 13, + sum: 140 * 13, + dayMean, + }); + const days = [ + ...Array.from({ length: 8 }, (_, i) => + day(`2026-05-${String(20 + i).padStart(2, "0")}`, 100), + ), + day("2026-06-02", 101), + ]; + const standing = sameHoursStanding(days, "2026-06-02", "BLOOD_GLUCOSE"); + expect(standing?.value).toBe(101); + expect(standing?.center).toBe(100); + }); + + it("falls back to sum / n where the reader has no day mean", () => { + const days = [ + ...Array.from({ length: 8 }, (_, i) => ({ + day: `2026-05-${String(20 + i).padStart(2, "0")}`, + n: 2, + sum: 180, + })), + { day: "2026-06-02", n: 1, sum: 88 }, + ]; + const standing = sameHoursStanding(days, "2026-06-02", "BLOOD_GLUCOSE"); + expect(standing?.value).toBe(88); + expect(standing?.center).toBe(90); + }); +}); diff --git a/src/lib/insights/derived/coincident-deviation.ts b/src/lib/insights/derived/coincident-deviation.ts index 5588e3616..235a514fa 100644 --- a/src/lib/insights/derived/coincident-deviation.ts +++ b/src/lib/insights/derived/coincident-deviation.ts @@ -94,20 +94,22 @@ export const SAME_HOURS_MIN_DAYS = 7; * there is nothing like-for-like to compare today with, and no verdict. */ export function sameHoursStanding( - days: readonly { day: string; n: number; sum: number }[], + days: readonly { day: string; n: number; sum: number; dayMean?: number }[], todayKey: string, type: MeasurementType, ): { value: number; low: number; high: number; center: number } | null { + // A type on the hourly-mean reader carries `dayMean` (the mean of its + // hours' means); every other type's day is the mean of its readings. + const value = (d: { n: number; sum: number; dayMean?: number }) => + d.dayMean ?? d.sum / d.n; const today = days.find((d) => d.day === todayKey && d.n > 0); if (!today) return null; - const earlier = days - .filter((d) => d.day < todayKey && d.n > 0) - .map((d) => d.sum / d.n); + const earlier = days.filter((d) => d.day < todayKey && d.n > 0).map(value); if (earlier.length < SAME_HOURS_MIN_DAYS) return null; const band = buildBaselineBand(earlier, type); if (!band) return null; return { - value: today.sum / today.n, + value: value(today), low: band.low, high: band.high, center: band.center,