mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(medications): a skipped dose leaves the dashboard tile's denominator
The two compliance engines disagreed about a deliberate skip. The dose-history ledger (`tallyComplianceFromLedger`, behind the medication card, the dose history, the doctor report and the Health Score pillar) has excluded a skipped dose from the denominator since v1.15.9 — a skip is a pause, not a miss. The schedule-anchored engine behind the dashboard tile and the adherence storyline built its denominator from the raw expected-slot count with no skip term, so it counted the same slot as expected-and-missed. A twice-daily day with one dose taken and one skipped read 100 % on the card and 50 % on the tile. The skip term now lands in the engine, not at the call sites: the day's expected count drops the doses the user skipped, capped at the minted slots, and a day whose every expected dose was skipped falls out of the buckets the same way it falls out of the ledger denominator. The schedule anchoring, the per-day buckets and the duplicate-log cap are untouched — only the skip treatment moved. `skipped` outranks `autoMissed`, mirroring `reconstructDoseHistory`, so a dose the auto-miss cron marked forgotten still counts against the rate. A new parity test feeds one fixture (twice daily, one taken, one skipped, one missed) to both engines and pins them to the same figure, so the class cannot come back through either side; both new cases were confirmed to fail against the previous behaviour. The tile-engine suite gains the corrected figure.
This commit is contained in:
1 parent
297395372b
commit
3e063f48ae
3 files changed
+272
-9
No files matched your search
@@ -704,6 +704,49 @@ describe("v1.15.9 — schedule-anchored compliance buckets (BUG #1)", () => {
|
||||
if (day.scheduled > 0) expect(day.taken).toBe(day.scheduled);
|
||||
}
|
||||
});
|
||||
|
||||
it("a deliberately skipped dose leaves the denominator — a skip is a pause, not a miss, so the tile matches the ledger rate", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
const twiceDaily = activeDailyMed(new Date(Date.now() - 60 * 86_400_000));
|
||||
twiceDaily.schedules[0].timesOfDay = ["08:00", "20:00"];
|
||||
vi.mocked(prisma.medication.findMany).mockResolvedValue([
|
||||
twiceDaily,
|
||||
] as never);
|
||||
|
||||
// Yesterday: the morning dose taken, the evening dose deliberately
|
||||
// skipped. The skipped slot must drop out of `scheduled` entirely, so the
|
||||
// day reads 1 of 1 (100%) — the figure the medication card, the dose
|
||||
// history and the doctor report already showed. Counting it as
|
||||
// expected-and-missed made the same day read 1 of 2 (50%) here.
|
||||
vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue([
|
||||
{
|
||||
medicationId: "m1",
|
||||
scheduledFor: new Date("2026-06-09T08:00:00.000Z"),
|
||||
takenAt: new Date("2026-06-09T08:05:00.000Z"),
|
||||
skipped: false,
|
||||
autoMissed: false,
|
||||
},
|
||||
{
|
||||
medicationId: "m1",
|
||||
scheduledFor: new Date("2026-06-09T20:00:00.000Z"),
|
||||
takenAt: null,
|
||||
skipped: true,
|
||||
autoMissed: false,
|
||||
},
|
||||
] as never);
|
||||
|
||||
const res = await GET(
|
||||
new NextRequest(
|
||||
"http://localhost/api/medications/intake?scope=compliance&days=7",
|
||||
),
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as {
|
||||
data: Array<{ date: string; scheduled: number; taken: number }>;
|
||||
};
|
||||
const skipDay = body.data.find((d) => d.date === "2026-06-09");
|
||||
expect(skipDay).toEqual({ date: "2026-06-09", scheduled: 1, taken: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("v1.4.43 W6 — multi-issue 422 envelope", () => {
|
||||
|
||||
Reference in new issue
Block a user