mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
West of UTC the local midnight of the boundary month falls hours after the month's UTC bucket start, so the first rolled-up month went missing and the first quarter was averaged over two of its months. The rollup window is now compared by UTC date at both ends.
105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
/**
|
|
* v1.42 — the timeline's long windows read the rolled-up months from the
|
|
* month the rollup fold boundary falls in, whatever the account's zone.
|
|
* Against Postgres.
|
|
*
|
|
* The rollup tier keys its months by the UTC calendar. West of UTC the local
|
|
* midnight of the boundary month comes hours after that month's UTC start, so
|
|
* a read from the local midnight left the month out: a gap in a month series,
|
|
* and a quarter averaged over two of its three months.
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { getPrismaClient, truncateAllTables } from "./setup";
|
|
|
|
vi.mock("@/lib/db-compat", () => ({
|
|
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
/**
|
|
* Five years before this the fold boundary falls on 2021-10-10, so the first
|
|
* rolled-up month is November 2021 and the first rolled-up quarter Q1 2022.
|
|
*/
|
|
const NOW = new Date("2026-10-09T12:00:00.000Z");
|
|
const TZ = "America/New_York";
|
|
const USER = "timeline-west";
|
|
|
|
beforeEach(async () => {
|
|
await truncateAllTables(getPrismaClient());
|
|
vi.useFakeTimers({ toFake: ["Date"] });
|
|
vi.setSystemTime(NOW);
|
|
await getPrismaClient().user.create({
|
|
data: {
|
|
id: USER,
|
|
username: USER,
|
|
email: `${USER}@example.test`,
|
|
timezone: TZ,
|
|
},
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
/** One DAY rollup row of resting heart rate per `[UTC date, mean, count]`. */
|
|
async function seedDays(days: Array<[string, number, number]>) {
|
|
await getPrismaClient().measurementRollup.createMany({
|
|
data: days.map(([date, mean, count]) => ({
|
|
userId: USER,
|
|
type: "RESTING_HEART_RATE" as const,
|
|
granularity: "DAY" as const,
|
|
bucketStart: new Date(`${date}T00:00:00.000Z`),
|
|
source: "APPLE_HEALTH" as const,
|
|
count,
|
|
mean,
|
|
minValue: mean,
|
|
maxValue: mean,
|
|
sumValue: mean * count,
|
|
})),
|
|
});
|
|
}
|
|
|
|
async function series(bucket: "month" | "quarter") {
|
|
const { loadTimelineSeries } = await import("@/lib/timeline/series");
|
|
const [out] = await loadTimelineSeries({
|
|
userId: USER,
|
|
keys: ["RESTING_HEART_RATE"],
|
|
from: "2016-10-09",
|
|
to: "2026-10-09",
|
|
tz: TZ,
|
|
bucket,
|
|
priorityJson: null,
|
|
now: NOW,
|
|
});
|
|
return out?.points ?? [];
|
|
}
|
|
|
|
describe("timeline rollup months west of UTC", () => {
|
|
it("keeps the first rolled-up month after the fold boundary", async () => {
|
|
await seedDays([
|
|
["2021-11-05", 60, 2],
|
|
["2021-12-05", 62, 2],
|
|
["2022-01-05", 64, 2],
|
|
]);
|
|
const points = await series("month");
|
|
expect(points).toEqual([
|
|
{ t: "2021-11-01", mean: 60, count: 2 },
|
|
{ t: "2021-12-01", mean: 62, count: 2 },
|
|
{ t: "2022-01-01", mean: 64, count: 2 },
|
|
]);
|
|
});
|
|
|
|
it("averages the first rolled-up quarter over all three of its months", async () => {
|
|
await seedDays([
|
|
["2022-01-05", 60, 1],
|
|
["2022-02-05", 63, 1],
|
|
["2022-02-06", 63, 1],
|
|
["2022-03-05", 66, 1],
|
|
]);
|
|
const points = await series("quarter");
|
|
// Each month weighs its days: (60 + 63 + 63 + 66) / 4.
|
|
expect(points).toEqual([{ t: "2022-01-01", mean: 63, count: 4 }]);
|
|
});
|
|
});
|