diff --git a/src/components/medications/__tests__/supply-runway.test.ts b/src/components/medications/__tests__/supply-runway.test.ts index d1c11ad56..77ea502d7 100644 --- a/src/components/medications/__tests__/supply-runway.test.ts +++ b/src/components/medications/__tests__/supply-runway.test.ts @@ -68,6 +68,43 @@ describe("estimateDailyDoseCount", () => { ).toBeCloseTo(1 / 30); }); + it("treats a once-weekly RRULE as one dose per 7 days", () => { + // FREQ=WEEKLY;BYDAY=MO is the modern once-weekly injection encoding; + // it must NOT fall through to the daysPerWeek=7 legacy branch. + expect( + estimateDailyDoseCount([ + schedule({ timesOfDay: ["08:00"], rrule: "FREQ=WEEKLY;BYDAY=MO" }), + ]), + ).toBeCloseTo(1 / 7); + }); + + it("halves a bi-weekly RRULE via INTERVAL=2 (one dose per 14 days)", () => { + expect( + estimateDailyDoseCount([ + schedule({ + timesOfDay: ["08:00"], + rrule: "FREQ=WEEKLY;INTERVAL=2;BYDAY=MO", + }), + ]), + ).toBeCloseTo(1 / 14); + }); + + it("counts each BYDAY pick on a weekly RRULE (Mon+Thu → 2 per 7 days)", () => { + expect( + estimateDailyDoseCount([ + schedule({ timesOfDay: ["08:00"], rrule: "FREQ=WEEKLY;BYDAY=MO,TH" }), + ]), + ).toBeCloseTo(2 / 7); + }); + + it("defaults a weekly RRULE with no BYDAY to one pick per week", () => { + expect( + estimateDailyDoseCount([ + schedule({ timesOfDay: ["08:00"], rrule: "FREQ=WEEKLY" }), + ]), + ).toBeCloseTo(1 / 7); + }); + it("sums across multiple schedules", () => { expect( estimateDailyDoseCount([ diff --git a/src/components/medications/detail/supply-runway.ts b/src/components/medications/detail/supply-runway.ts index b4972e946..bbc36a58f 100644 --- a/src/components/medications/detail/supply-runway.ts +++ b/src/components/medications/detail/supply-runway.ts @@ -46,6 +46,25 @@ export function estimateDailyDoseCount(schedules: RunwaySchedule[]): number { perDay += times / 365; continue; } + // FREQ=WEEKLY;BYDAY=…;INTERVAL=… is the modern weekly encoding (the + // create path stores the cadence on the rrule and leaves daysOfWeek + // empty). Honour the BYDAY pick count and the week INTERVAL: a + // once-weekly injection is one dose per 7 days, a bi-weekly one per + // 14. Without this branch a weekly rrule fell through to the legacy + // daysOfWeek fallback below with daysPerWeek=7, over-estimating the + // rate ~7× (≈14× bi-weekly) and firing low-stock alerts far too early. + if (/FREQ=WEEKLY/.test(rrule)) { + const byday = /BYDAY=([^;]+)/.exec(rrule); + const bydayCount = + byday && byday[1].length > 0 ? byday[1].split(",").length : 1; + const intervalMatch = /INTERVAL=(\d+)/.exec(rrule); + const interval = + intervalMatch && Number(intervalMatch[1]) >= 1 + ? Number(intervalMatch[1]) + : 1; + perDay += (times * bydayCount) / (7 * interval); + continue; + } const { daysOfWeek, intervalWeeks } = parseScheduleRecurrence(s.daysOfWeek); const daysPerWeek = daysOfWeek.length > 0 ? daysOfWeek.length : 7; const weeks = intervalWeeks >= 1 ? intervalWeeks : 1;