Skip to content

v1.37.10 — one drug, a different dose at each time - #770

Merged
MBombeck merged 13 commits into
mainfrom
release/v1.37.10
Aug 10, 2026
Merged

v1.37.10 — one drug, a different dose at each time#770
MBombeck merged 13 commits into
mainfrom
release/v1.37.10

Conversation

@MBombeck

Copy link
Copy Markdown
Owner

Assembled off origin/main. Additive only: one nullable column, no drops, no restructuring.

One drug, a different dose at each time

The reporter's case (@Limbolindi, #767): a "1-0.5-0.5" split forced two separate drug entries whose stocks never added up, and an occasional extra dose became a third phantom entry.

Most of the shape already existed: a Medication owns N MedicationSchedule children, each already with its own time and its own dose override, and inventory is already shared per medicationId. The one true gap was that units-consumed-per-dose lived at the medication level, so inventory could not draw a different tablet count for a half-dose. This adds that.

  • MedicationSchedule.unitsPerDose Decimal? (migration 0332_schedule_units_per_dose, one nullable ADD COLUMN, no backfill). NULL inherits Medication.unitsPerDose, so every existing medication counts down exactly as before.
  • The inventory consume hook binds a taken dose to its slot by wall-clock time and draws that slot's units, falling back to the medication level when a slot leaves it unset. Past intakes stay frozen by the existing exactly-once consume stamp.
  • The create wizard sets a per-slot dose and unit count, so the split is one medication with three times. An as-needed extra dose is a per-schedule PRN slot on the same medication, drawing from the same box, not a third entry.
  • schedules[].unitsPerDose on the API is additive and NULL-inherits, so an older iOS client that never sends it behaves exactly as today. The column carries through backup and restore. Both med cards show the per-slot amount identically.

Verification

  • Watched-red proven twice: the per-slot consume (mutating the hook to read only the medication-level units turns the "noon draws 0.5" integration test red), and the wizard round-trip.
  • typecheck, lint, openapi:check (in sync; the diff is the version line plus the additive property), test (21116 passed), test:integration (per-slot consume + backup round trip against testcontainers Postgres), build: all green.
  • Independent review ran the unit suite, the full integration suite, and a direct probe of the resolver across time zones. Verdict: ship. Two fail-safe edge findings recorded as a non-blocking follow-up: the slot matcher keys on wall-clock time only (a same-time collision or a drifted timestamp degrades to the medication-level units, never a wrong medication), and the low-stock forecast still estimates from the medication-level figure while the actual decrement is correct per slot.

Carry-forward

Existing single-schedule medications are untouched. A user who already made the two-drug workaround is not auto-merged (two ids, separate stock and history); new entries get the per-slot behaviour, and the manual path is to recreate as one drug. A guided merge could be a later follow-up.

Add a nullable units_per_dose to medication_schedules so a split-dose
plan can consume a different unit count at different times of day (a
whole tablet in the morning, a half at noon) on ONE medication instead
of two. NULL inherits the medication-level units_per_dose, so every
existing row keeps its current behaviour. Migration 0332 adds one
nullable Decimal(10,4) column with no backfill.
The inventory consume hook counted the medication-level units_per_dose
for every dose, so a split plan could not decrement a whole tablet in
the morning and a half at noon. Resolve the units from the taken
intake's own slot: match its scheduled wall-clock time in the user's
zone to a schedule and read that schedule's units_per_dose, inheriting
the medication level on NULL or no match. A medication whose schedules
all leave the column NULL resolves exactly as before. The batch-import
path binds each dose by its scheduled anchor the same way.
Add schedules[].unitsPerDose to the create/update contract with the
same whole-number-or-curated-fraction validation as the medication
level, built into the Prisma data field-by-field on both the create
and the schedule-replace path. Unwrap the nullable Decimal to a JSON
number at every medication read/write response through one shared
serializer so the wire shape cannot drift, keeping NULL as NULL to
mean inherit-the-medication-level.
Give each parallel schedule its own optional dose and units-per-dose
input on the times step, so a split plan (a whole tablet in the
morning, a half at noon) is ONE medication with three schedules
instead of two drugs that double-count. Leaving both blank inherits
the medication-level values, so a single-dose plan is unchanged. An
as-needed slot that sits alongside scheduled ones now rides the same
medication as a PRN schedule rather than flipping the whole-medication
as-needed flag. New keys land in all six locales.
Surface the per-schedule units-per-dose beside the per-slot dose the
card already prints, identically on the standard and the GLP-1 card,
so a split plan reads at a glance which time takes a half. Muted
addendum, no width movement.
…store

The schedule exporter enumerates columns, so the new per-schedule
units-per-dose needs adding by hand: export it as a Decimal string in
the disaster-recovery payload, accept it in the wire schema, and write
it back on restore (NULL stays NULL to inherit). Extend the round-trip
test to prove a half-tablet slot survives export and re-import.
…trip

Integration: a medication with a morning slot at 1 unit and a noon slot
at 0.5 draws the right count for each taken dose against a real
Postgres. Unit: a per-slot dose + units set in the wizard survives
create and round-trips on edit-hydrate.
The consume hook now loads the user's zone, the schedules, and the
intake's scheduled anchor to resolve per-slot units, so the fake
client grows the schedule read and the batch scheduledFor lookup. The
Italian per-dose card label reworded so it no longer echoes English.
@MBombeck
MBombeck merged commit b81e392 into main Aug 10, 2026
23 checks passed
@MBombeck
MBombeck deleted the release/v1.37.10 branch August 10, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant