mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(vaccinations): re-anchor a booster on the logged dose's own day
Logging the next dose satisfies a matching booster reminder with the dose date, a calendar date stored at UTC midnight. The reminder engine reads that instant in the person's zone, where west of UTC it is the evening before, so the next booster came due a day early. The first booster's anchor was already counted from the start of the dose day in the person's zone; the satisfy path now hands the engine the same instant.
This commit is contained in:
1 parent
f76e5cdafd
commit
46906916d8
4 files changed
+81
-3
No files matched your search
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Logging the next dose re-anchors a booster reminder on the dose date. The
|
||||
* dose date is a calendar date stored at UTC midnight, which is the evening
|
||||
* before west of UTC, so the next booster came due a day early there.
|
||||
*/
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { Prisma } from "@/generated/prisma/client";
|
||||
import { satisfyBoostersForDose } from "@/lib/vaccinations/satisfy-matching";
|
||||
import { shiftDateKey, userDayKey } from "@/lib/tz/format";
|
||||
import { startOfLocalDayKey } from "@/lib/tz/local-day";
|
||||
|
||||
const TZ = "America/New_York";
|
||||
|
||||
function fakeTx() {
|
||||
const updates: Array<Record<string, unknown>> = [];
|
||||
const reminder = {
|
||||
id: "reminder-1",
|
||||
userId: "u1",
|
||||
vaccinationAntigen: "tetanus",
|
||||
intervalDays: 3650,
|
||||
rrule: null,
|
||||
anchorDate: new Date("2016-03-10T05:00:00.000Z"),
|
||||
notifyHour: 9,
|
||||
nextDueAt: new Date("2026-03-07T14:00:00.000Z"),
|
||||
lastSatisfiedAt: null,
|
||||
lastSkippedAt: null,
|
||||
createdAt: new Date("2016-03-10T05:00:00.000Z"),
|
||||
endsOn: null,
|
||||
};
|
||||
const tx = {
|
||||
measurementReminder: {
|
||||
findMany: vi.fn(async () => [reminder]),
|
||||
updateMany: vi.fn(async ({ data }: { data: Record<string, unknown> }) => {
|
||||
updates.push(data);
|
||||
return { count: 1 };
|
||||
}),
|
||||
},
|
||||
measurementReminderEvent: { create: vi.fn(async () => ({})) },
|
||||
};
|
||||
return { tx: tx as unknown as Prisma.TransactionClient, updates };
|
||||
}
|
||||
|
||||
describe("satisfyBoostersForDose", () => {
|
||||
it("counts the next interval from the dose's own date west of UTC", async () => {
|
||||
const { tx, updates } = fakeTx();
|
||||
await satisfyBoostersForDose(
|
||||
tx,
|
||||
"u1",
|
||||
{ antigenSlug: "tetanus" },
|
||||
new Date("2026-03-10T00:00:00.000Z"),
|
||||
TZ,
|
||||
);
|
||||
// Ten years of days after 10 March, on the person's calendar.
|
||||
expect(userDayKey(updates[0].nextDueAt as Date, TZ)).toBe(
|
||||
shiftDateKey("2026-03-10", 3650),
|
||||
);
|
||||
expect(updates[0].lastSatisfiedAt).toEqual(
|
||||
startOfLocalDayKey("2026-03-10", TZ),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -52,6 +52,8 @@ import {
|
||||
satisfyReminder,
|
||||
type SatisfiableReminder,
|
||||
} from "@/lib/measurement-reminders/satisfy";
|
||||
import { dateOnlyKey } from "@/lib/tz/date-only";
|
||||
import { startOfLocalDayKey } from "@/lib/tz/local-day";
|
||||
import {
|
||||
componentsForDose,
|
||||
loadCustomVaccineLookup,
|
||||
@@ -136,6 +138,10 @@ export async function satisfyBoostersForDose(
|
||||
return byAntigen !== 0 ? byAntigen : a.id.localeCompare(b.id);
|
||||
});
|
||||
|
||||
// The dose date is a calendar date at UTC midnight, the evening before west
|
||||
// of UTC; the reminder engine reads an instant in the person's zone, so it
|
||||
// gets the start of that day there, as the booster mint does.
|
||||
const satisfiedAt = startOfLocalDayKey(dateOnlyKey(occurredAt), timezone);
|
||||
const satisfiedReminderIds: string[] = [];
|
||||
for (const reminder of ordered) {
|
||||
const row: SatisfiableReminder = {
|
||||
@@ -160,7 +166,7 @@ export async function satisfyBoostersForDose(
|
||||
tx as unknown as PrismaClient,
|
||||
row,
|
||||
timezone,
|
||||
occurredAt,
|
||||
satisfiedAt,
|
||||
"vaccination",
|
||||
);
|
||||
if (result.satisfied) satisfiedReminderIds.push(reminder.id);
|
||||
|
||||
Reference in new issue
Block a user