feat(medications): make low-stock alert reorder-lead-time aware

The low-stock alert fired when the projected runway dropped below the
user threshold (default 7 days). For a sparse cadence — a weekly
injection's 7-day floor is barely one dose-interval — the warning landed
with about one dose left, too late to reorder before the supply ran out.

Add an optional per-medication reorder lead time plus a user-level
default (0–60 days, default 10) and widen the alert trigger to
max(lowStockRunwayDays, leadDays + cadenceIntervalDays), so the warning
lands before the last dose for any cadence. Because the trigger is a
max() over the user floor it never shrinks anyone's current threshold; a
daily med with no lead keeps its prior boundary.

The push body and the card notice surface concrete dates — supply runs
out around <date>, reorder by <date> — with a last-dose informational
state and the days/units fallback when supply data is thin. Retire the
stale fixed 4-dose count threshold on the GLP-1 details endpoint and
route its card lowStock flag through the same runway evaluation so the
card and the daily cron agree. Surface the lead default in the low-stock
settings card with progressive disclosure; a single medication can
override it.

Additive schema: nullable Medication.reorderLeadDays (migration 0161, no
backfill) and notificationPrefs.medication.reorderLeadDays. OpenAPI
regenerated.
This commit is contained in:
Marc-André Bombeck committed 2026-06-14 20:39:06 +02:00
1 parent 52bf985734
commit 08aa655be4
29 files changed
+1060 -97

No files matched your search

+64
View File
@@ -5481,6 +5481,12 @@ components:
supported fraction for a split pill (¼ / ⅓ / ½ / ⅔ / ¾). Default 1. The intake consumption hook decrements
this many units per taken dose; dose-derived readouts divide unit counts by it.
type: number
reorderLeadDays:
description: Per-medication reorder lead time in days (0–60). The low-stock alert fires the supply early enough to
reorder before the last dose. Omitted = inherit the user-level reorderLeadDays default (10).
type: integer
minimum: 0
maximum: 60
deliveryForm:
type: string
enum:
@@ -5768,6 +5774,14 @@ components:
/ ¾). The intake consumption hook decrements this many units per taken dose; already-stamped intake events
keep their recorded consumption.
type: number
reorderLeadDays:
description: Per-medication reorder lead time in days (0–60). null clears the override and reverts to the user-level
reorderLeadDays default.
anyOf:
- type: integer
minimum: 0
maximum: 60
- type: "null"
deliveryForm:
type: string
enum:
@@ -6167,6 +6181,10 @@ components:
minimum: 1
maximum: 60
- type: "null"
reorderLeadDays:
type: integer
minimum: 0
maximum: 60
mood:
type: object
properties:
@@ -9707,6 +9725,15 @@ components:
description: v1.16.10 — inventory units one dose consumes (e.g. 2 tablets of 2 mg for a 4 mg dose). v1.16.12 — may be a
split-pill fraction (¼ / ⅓ / ½ / ⅔ / ¾); thirds carry as ≈0.3333 / 0.6667. Default 1. The intake consumption
hook decrements this many units per taken dose; dose-derived readouts divide unit counts by it.
reorderLeadDays:
anyOf:
- type: integer
minimum: -9007199254740991
maximum: 9007199254740991
- type: "null"
description: v1.17.0 — optional per-medication reorder lead time in days (0–60). The low-stock alert widens its trigger
by this lead plus one dose-interval so a refill arrives before the last dose. null = inherit the user-level
notificationPrefs.medication.reorderLeadDays default (10).
active:
type: boolean
notificationsEnabled:
@@ -9840,6 +9867,7 @@ components:
- treatmentClass
- dosesPerUnit
- unitsPerDose
- reorderLeadDays
- active
- notificationsEnabled
- liveActivityEnabled
@@ -9925,6 +9953,15 @@ components:
description: v1.16.10 — inventory units one dose consumes (e.g. 2 tablets of 2 mg for a 4 mg dose). v1.16.12 — may be a
split-pill fraction (¼ / ⅓ / ½ / ⅔ / ¾); thirds carry as ≈0.3333 / 0.6667. Default 1. The intake consumption
hook decrements this many units per taken dose; dose-derived readouts divide unit counts by it.
reorderLeadDays:
anyOf:
- type: integer
minimum: -9007199254740991
maximum: 9007199254740991
- type: "null"
description: v1.17.0 — optional per-medication reorder lead time in days (0–60). The low-stock alert widens its trigger
by this lead plus one dose-interval so a refill arrives before the last dose. null = inherit the user-level
notificationPrefs.medication.reorderLeadDays default (10).
active:
type: boolean
notificationsEnabled:
@@ -10025,6 +10062,7 @@ components:
- treatmentClass
- dosesPerUnit
- unitsPerDose
- reorderLeadDays
- active
- notificationsEnabled
- liveActivityEnabled
@@ -12891,10 +12929,18 @@ components:
maximum: 60
- type: "null"
description: Low-stock alert threshold as remaining runway days (1–60). null = alert off. Default 7.
reorderLeadDays:
type: integer
minimum: 0
maximum: 60
description: Reorder lead time in days (0–60) the low-stock alert assumes. The alert fires this lead plus one
dose-interval before the supply runs out so a refill arrives before the last dose. Default 10; a
per-medication reorderLeadDays overrides it.
required:
- clientManaged
- deliveryDefault
- lowStockRunwayDays
- reorderLeadDays
additionalProperties: false
mood:
type: object
@@ -15390,10 +15436,18 @@ components:
- type: "null"
description: Per-user low-stock alert threshold as remaining runway days (1–60). null = the alert is off. Default 7.
Written via PATCH /api/auth/me/notification-prefs.
reorderLeadDays:
type: integer
minimum: 0
maximum: 60
description: v1.17.0 — per-user reorder lead default in days (0–60, default 10). The low-stock alert fires this lead
plus one dose-interval before the supply runs out so a refill arrives before the last dose; a per-medication
reorderLeadDays overrides it. Written via PATCH /api/auth/me/notification-prefs.
required:
- lateMinutes
- missedMinutes
- lowStockRunwayDays
- reorderLeadDays
additionalProperties: false
description: "Medication reminder thresholds: the operator-level late/missed minute marks plus the calling user's
low-stock runway threshold (v1.16.11)."
@@ -15796,6 +15850,15 @@ components:
description: v1.16.10 — inventory units one dose consumes (e.g. 2 tablets of 2 mg for a 4 mg dose). v1.16.12 — may be a
split-pill fraction (¼ / ⅓ / ½ / ⅔ / ¾); thirds carry as ≈0.3333 / 0.6667. Default 1. The intake consumption
hook decrements this many units per taken dose; dose-derived readouts divide unit counts by it.
reorderLeadDays:
anyOf:
- type: integer
minimum: -9007199254740991
maximum: 9007199254740991
- type: "null"
description: v1.17.0 — optional per-medication reorder lead time in days (0–60). The low-stock alert widens its trigger
by this lead plus one dose-interval so a refill arrives before the last dose. null = inherit the user-level
notificationPrefs.medication.reorderLeadDays default (10).
active:
type: boolean
notificationsEnabled:
@@ -15894,6 +15957,7 @@ components:
- treatmentClass
- dosesPerUnit
- unitsPerDose
- reorderLeadDays
- active
- notificationsEnabled
- liveActivityEnabled