feat(medications): keep a medication as a record without tracking intake

A per-medication trackIntake flag (migration 0351, default on for every
existing and new row). With it off the dose, dates and schedule stay stored,
but nothing derives a due dose from them: the reminder worker, the manual
reminder check, the today projector, the auto-miss pass, the low-stock pass,
the doses card, the today list, the outstanding-dose badge, compliance and
its heatmap, the doctor report, the Coach, the BP-status gate, insights,
targets, achievements, the efficacy view and the MCP schedule tool all skip
it.

The medication wire serves such a medication with schedules: [] and the
stored rows in an additive recordedSchedules field, so a client that arms
local reminders from schedules stays silent without an update. Switching
tracking off or back on archives a schedule revision at that instant, the
untracked stretch with an empty payload, so it never counts as missed and
the projector does not put a dose from before the switch back on the card.

A structural guard freezes the set of schedule readers: each one filters on
the flag, derives from dueSchedules, shapes the wire with scheduleWireFields,
or is allowlisted with the reason it derives nothing due. The flag travels
through backup, restore and the medication CSV export.
This commit is contained in:
Marc-André Bombeck committed 2026-09-25 08:57:10 +02:00
1 parent 700de6d228
commit b4c2706ec9
57 files changed
+1490 -182

No files matched your search

+73 -4
View File
@@ -23103,6 +23103,13 @@ components:
rates/streaks, but intakes still log (ad-hoc), inventory still consumes, and the history renders. Stays
active indefinitely. Mutually exclusive with `oneShot`."
type: boolean
trackIntake:
description: "v1.39.1 — intake tracking. Default true. When false the medication keeps its dose, dates and schedules as
information, but nothing is ever due: no reminder on any channel, no projected slot, no entry on the today
list or the doses card, and no adherence figure anywhere. Existing intake history stays stored. Switching it
back on resumes from that moment; the time it was off never counts as missed. Omitted on update leaves the
current value."
type: boolean
schedules:
type: array
items:
@@ -23418,6 +23425,13 @@ components:
rates/streaks, but intakes still log (ad-hoc), inventory still consumes, and the history renders. Stays
active indefinitely. Mutually exclusive with `oneShot`."
type: boolean
trackIntake:
description: "v1.39.1 — intake tracking. Default true. When false the medication keeps its dose, dates and schedules as
information, but nothing is ever due: no reminder on any channel, no projected slot, no entry on the today
list or the doses card, and no adherence figure anywhere. Existing intake history stays stored. Switching it
back on resumes from that moment; the time it was off never counts as missed. Omitted on update leaves the
current value."
type: boolean
schedules:
type: array
items:
@@ -33035,6 +33049,8 @@ components:
type: boolean
asNeeded:
type: boolean
trackIntake:
type: boolean
deliveryForm:
type: string
enum:
@@ -42557,6 +42573,13 @@ components:
any schedule entry alongside the flag): it is never due (`nextDueAt` stays null), never reminded, and
excluded from every compliance rate/streak — but intakes still log as ad-hoc rows, inventory still consumes
per `unitsPerDose`, and the history renders. Stays active indefinitely. Mutually exclusive with `oneShot`."
trackIntake:
type: boolean
description: "v1.39.1 — intake tracking. True (the default) for every medication that existed before the field. When
false the medication is kept as a record: nothing is ever due (`nextDueAt` stays null), nothing reminds on
any channel, and it is excluded from every adherence figure. Its stored schedule then travels in
`recordedSchedules` while `schedules` is served empty, so a client that arms reminders from `schedules`
stays silent without an update."
createdAt:
type: string
format: date-time
@@ -42569,6 +42592,14 @@ components:
type: array
items:
$ref: "#/components/schemas/MedicationScheduleOutput"
description: "The schedules in force. Empty for an as-needed medication and (v1.39.1) for a medication with
`trackIntake: false`, whose stored schedule is in `recordedSchedules`."
recordedSchedules:
description: "v1.39.1 — present only when `trackIntake` is false: the stored schedule, kept as information. Nothing
derives a due dose or a reminder from it. Absent on a tracked medication, whose schedule is `schedules`."
type: array
items:
$ref: "#/components/schemas/MedicationScheduleOutput"
category:
$ref: "#/components/schemas/MedicationCategory"
externalSource:
@@ -42644,6 +42675,7 @@ components:
- endsOn
- oneShot
- asNeeded
- trackIntake
- createdAt
- updatedAt
- schedules
@@ -42808,6 +42840,13 @@ components:
any schedule entry alongside the flag): it is never due (`nextDueAt` stays null), never reminded, and
excluded from every compliance rate/streak — but intakes still log as ad-hoc rows, inventory still consumes
per `unitsPerDose`, and the history renders. Stays active indefinitely. Mutually exclusive with `oneShot`."
trackIntake:
type: boolean
description: "v1.39.1 — intake tracking. True (the default) for every medication that existed before the field. When
false the medication is kept as a record: nothing is ever due (`nextDueAt` stays null), nothing reminds on
any channel, and it is excluded from every adherence figure. Its stored schedule then travels in
`recordedSchedules` while `schedules` is served empty, so a client that arms reminders from `schedules`
stays silent without an update."
createdAt:
type: string
format: date-time
@@ -42820,6 +42859,14 @@ components:
type: array
items:
$ref: "#/components/schemas/MedicationScheduleOutput"
description: "The schedules in force. Empty for an as-needed medication and (v1.39.1) for a medication with
`trackIntake: false`, whose stored schedule is in `recordedSchedules`."
recordedSchedules:
description: "v1.39.1 — present only when `trackIntake` is false: the stored schedule, kept as information. Nothing
derives a due dose or a reminder from it. Absent on a tracked medication, whose schedule is `schedules`."
type: array
items:
$ref: "#/components/schemas/MedicationScheduleOutput"
category:
$ref: "#/components/schemas/MedicationCategory"
stockUnitsRemaining:
@@ -42863,6 +42910,7 @@ components:
- endsOn
- oneShot
- asNeeded
- trackIntake
- createdAt
- updatedAt
- schedules
@@ -43615,7 +43663,9 @@ components:
notApplicableReason:
anyOf:
- type: string
const: NO_LOCAL_SCHEDULE
enum:
- NO_LOCAL_SCHEDULE
- INTAKE_NOT_TRACKED
- type: "null"
compliance7:
description: Seven-day adherence summary, or an all-zero compatibility placeholder when applicable is false.
@@ -43874,10 +43924,13 @@ components:
notApplicableReason:
anyOf:
- type: string
const: NO_LOCAL_SCHEDULE
enum:
- NO_LOCAL_SCHEDULE
- INTAKE_NOT_TRACKED
- type: "null"
description: Reason adherence is not applicable. NO_LOCAL_SCHEDULE means the medication has no HealthLog-owned
expected-dose grid.
description: "Reason adherence is not applicable. NO_LOCAL_SCHEDULE means the medication has no HealthLog-owned
expected-dose grid. INTAKE_NOT_TRACKED (v1.39.1) means the medication's intake tracking is switched off
(`trackIntake: false`): its schedule is kept as information and nothing is expected from it."
compliance7:
description: Seven-day adherence summary. When applicable is false, this remains a non-null all-zero compatibility
placeholder for released clients and must not be rendered as a percentage.
@@ -72014,6 +72067,13 @@ components:
any schedule entry alongside the flag): it is never due (`nextDueAt` stays null), never reminded, and
excluded from every compliance rate/streak — but intakes still log as ad-hoc rows, inventory still consumes
per `unitsPerDose`, and the history renders. Stays active indefinitely. Mutually exclusive with `oneShot`."
trackIntake:
type: boolean
description: "v1.39.1 — intake tracking. True (the default) for every medication that existed before the field. When
false the medication is kept as a record: nothing is ever due (`nextDueAt` stays null), nothing reminds on
any channel, and it is excluded from every adherence figure. Its stored schedule then travels in
`recordedSchedules` while `schedules` is served empty, so a client that arms reminders from `schedules`
stays silent without an update."
createdAt:
type: string
format: date-time
@@ -72026,6 +72086,14 @@ components:
type: array
items:
$ref: "#/components/schemas/MedicationScheduleOutput"
description: "The schedules in force. Empty for an as-needed medication and (v1.39.1) for a medication with
`trackIntake: false`, whose stored schedule is in `recordedSchedules`."
recordedSchedules:
description: "v1.39.1 — present only when `trackIntake` is false: the stored schedule, kept as information. Nothing
derives a due dose or a reminder from it. Absent on a tracked medication, whose schedule is `schedules`."
type: array
items:
$ref: "#/components/schemas/MedicationScheduleOutput"
required:
- id
- name
@@ -72046,6 +72114,7 @@ components:
- endsOn
- oneShot
- asNeeded
- trackIntake
- createdAt
- updatedAt
- schedules