fix(medications): attribute doses to slots from before the medication existed

A medication added at 16:05 with doses at 09:00, 14:00 and 21:00 showed its
creation day wrongly in "Last 90 days". The 14:00 dose, recorded afterwards
on its own slot, read as an off-schedule take "due" at 21:00, and a 09:00
row with no time appeared as off-schedule too.

The history route minted slots only from the creation instant, so the 14:00
slot never existed for the dose to land on, while the write path, which
mints the whole day, had filed it there. The 09:00 row was the pending
placeholder the today projector mints for every slot of the day, which the
auto-miss pass stamped a day later; with no slot to bind to it surfaced as
an off-schedule row.

The ledger now mints slots over the requested window and takes the
creation instant as the floor of expected slots: a slot before it appears
only when a recorded dose claims it (a take by its window or its pin, or a
skip), and a pending or auto-missed placeholder on it is dropped. The first
archived schedule era also covers the stretch before the creation, so an
edited schedule behaves the same.

Every adherence surface tallies the same ledger with the same floor, so the
detail rates, the calendar and the ledger bar agree on that day. The doctor
report, the Coach and the adherence chips previously read the slots before
a medication existed as missed whenever their window started earlier; they
now read nothing there.

Refs #1028
This commit is contained in:
Marc-André Bombeck committed 2026-09-24 19:40:27 +02:00
1 parent 9328a4298d
commit fe7cccfd46
19 files changed
+839 -70

No files matched your search

+7 -4
View File
@@ -5984,12 +5984,14 @@ paths:
name: from
schema:
description: Window start (inclusive). Defaults to 90 days before `to`; clamped to a 366-day span floor. Recorded doses
are returned from this instant; expected slots are not minted before the medication's `createdAt`.
are returned from this instant. A slot from before the medication's `createdAt` appears only when a
recorded dose (a take or a skip) claims it; the pending placeholder of such a slot is not returned.
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
description: Window start (inclusive). Defaults to 90 days before `to`; clamped to a 366-day span floor. Recorded doses
are returned from this instant; expected slots are not minted before the medication's `createdAt`.
are returned from this instant. A slot from before the medication's `createdAt` appears only when a recorded
dose (a take or a skip) claims it; the pending placeholder of such a slot is not returned.
- in: query
name: to
schema:
@@ -43263,8 +43265,9 @@ components:
type: integer
minimum: 0
maximum: 9007199254740991
description: "Full denominator over the window: `taken + skipped + missed`. Cadence-aware and clamped to the
medication's `createdAt`."
description: "Full denominator over the window: `taken + skipped + missed`. Cadence-aware. A slot from before the
medication's `createdAt` counts only when a recorded dose (a take or a skip) claims it, so the days before
the medication existed are never missed."
taken:
type: integer
minimum: 0