fix(doctor-report): report readings, not day buckets, for a dense type

A type served from day buckets (pulse or glucose past 90 days, and since
the previous change a dense pulse stream on any window) is drawn one point
per day, each the day's mean at the day's start. The summary line counted
those points as measurements, and the FHIR export stamped the last day's
mean as the latest observation: a value and a time nobody measured.

The day-bucket stats now carry when their newest reading was taken
(`latestAt`). `latestReading` returns that reading when it is there, and
the summary line counts each type's readings from its stats. Raw series
are unchanged: their stats count equals their points and their last point
is the newest reading.
This commit is contained in:
Marc-André Bombeck committed 2026-09-27 21:20:24 +02:00
1 parent 3458100ac4
commit 6836830038
6 files changed
+155 -3

No files matched your search

@@ -22,6 +22,7 @@ import { getPrismaClient, truncateAllTables } from "./setup";
import { deriveRestingProxyFromPulse } from "@/lib/analytics/resting-pulse";
import { readRestingPulseProxy } from "@/lib/analytics/resting-pulse-read";
import { collectDoctorReportData } from "@/lib/doctor-report-data";
import { latestReading } from "@/lib/fhir/resources/common";
import { selectionFromLeaves } from "@/lib/report-selection/selection";
import { userDayKey } from "@/lib/tz/format";
import { invalidateUserTimezone } from "@/lib/tz/resolver";
@@ -230,6 +231,16 @@ describe("doctor report inside the raw window", () => {
expect(points.length).toBeLessThanOrEqual(12);
expect(points.length).toBeGreaterThanOrEqual(10);
expect(data.stats.PULSE?.count).toBe(DENSE_ROWS);
// The export's latest observation is the newest reading itself.
const newest = await prisma.measurement.findFirstOrThrow({
where: { userId: DENSE, type: "PULSE" },
orderBy: { measuredAt: "desc" },
select: { value: true, measuredAt: true },
});
expect(latestReading(data, "PULSE")).toEqual({
value: newest.value,
measuredAt: newest.measuredAt.toISOString(),
});
});
it("keeps sparse readings raw", async () => {