From 04eb8dd7f8660bcd5796d5c7ccee032c04970001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 26 Sep 2026 22:11:27 +0200 Subject: [PATCH] fix(doctor-report): compare course dates as days in the report zone A medication's start and end dates are calendar dates, but the report compared them against the window's instants. West of UTC the window begins hours after UTC midnight, so a course that ended on the first day was left out, and one that started the day after the window was listed. Both now compare against the window's first and last day in the report zone. --- src/lib/doctor-report/collect.ts | 12 ++++-- .../doctor-report-fixed-window.test.ts | 42 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/lib/doctor-report/collect.ts b/src/lib/doctor-report/collect.ts index 901a4f885..05fa74ec1 100644 --- a/src/lib/doctor-report/collect.ts +++ b/src/lib/doctor-report/collect.ts @@ -61,7 +61,7 @@ import { loadDenseMeasurementBuckets } from "./dense-buckets"; import { summariseDenseBuckets } from "./measurement-series"; import { emptyGlucoseClinical } from "./glucose-panel"; import { buildAdministrationLedger, buildGlp1Block } from "./medications"; -import { validTimezoneOr, DEFAULT_TIMEZONE } from "@/lib/tz/format"; +import { userDayKey, validTimezoneOr, DEFAULT_TIMEZONE } from "@/lib/tz/format"; import { loadAllergies, loadAnamnesis, @@ -148,6 +148,12 @@ export async function collectDoctorReportData( }, }); const reportTz = validTimezoneOr(userProfile?.timezone, DEFAULT_TIMEZONE); + // `startsOn` / `endsOn` are calendar dates (stored as UTC midnight), so + // they compare against the window's first and last day in the report zone, + // not its instants: west of UTC the window starts hours after UTC midnight, + // and a course that ended on the first day read as over before it began. + const firstDay = new Date(`${userDayKey(start, reportTz)}T00:00:00.000Z`); + const lastDay = new Date(`${userDayKey(end, reportTz)}T00:00:00.000Z`); const aggregateDenseTypes = days > DENSE_REPORT_RAW_WINDOW_DAYS; const densePulse = aggregateDenseTypes && !excluded.includes("PULSE"); @@ -202,7 +208,7 @@ export async function collectDoctorReportData( AND: [ { OR: [ - { startsOn: { lte: end } }, + { startsOn: { lte: lastDay } }, { startsOn: null, createdAt: { lte: end } }, { intakeEvents: { @@ -214,7 +220,7 @@ export async function collectDoctorReportData( }, ], }, - { OR: [{ endsOn: null }, { endsOn: { gte: start } }] }, + { OR: [{ endsOn: null }, { endsOn: { gte: firstDay } }] }, ], }, include: { diff --git a/tests/integration/doctor-report-fixed-window.test.ts b/tests/integration/doctor-report-fixed-window.test.ts index 72116145a..e42ceb95c 100644 --- a/tests/integration/doctor-report-fixed-window.test.ts +++ b/tests/integration/doctor-report-fixed-window.test.ts @@ -138,4 +138,46 @@ describe("doctor report with a fixed end date", () => { expect(glp1?.currentDose?.value).toBe(0.25); expect(glp1?.lastInjection?.date).toBe("2026-03-20T08:05:00.000Z"); }); + it("compares a course's start and end dates as days in the report zone", async () => { + // West of UTC the window's instants sit hours after UTC midnight, and the + // course dates are calendar days stored as UTC midnight. + const prisma = getPrismaClient(); + const user = await prisma.user.create({ + data: { + username: "west-window", + email: "west-window@example.test", + timezone: "America/New_York", + }, + }); + const course = (name: string, startsOn: string, endsOn: string | null) => + prisma.medication.create({ + data: { + userId: user.id, + name, + dose: "1 mg", + startsOn: new Date(`${startsOn}T00:00:00.000Z`), + endsOn: endsOn ? new Date(`${endsOn}T00:00:00.000Z`) : null, + createdAt: new Date("2025-06-01T00:00:00.000Z"), + }, + }); + await course("Ended on the first day", "2025-10-01", "2026-01-01"); + await course("Ended the day before", "2025-10-01", "2025-12-31"); + await course("Started on the last day", "2026-03-31", null); + await course("Started the day after", "2026-04-01", null); + + const data = await collectDoctorReportData( + user.id, + { + start: new Date("2026-01-01T05:00:00.000Z"), + end: new Date("2026-04-01T03:59:59.999Z"), + days: 90, + }, + selectionFromLeaves(ALL_LEAF_IDS), + ); + + expect(data.medications?.map((m) => m.name).sort()).toEqual([ + "Ended on the first day", + "Started on the last day", + ]); + }); });