perf(medications): one compliance round trip and a memoised formatter hot path

Intl.DateTimeFormat instances are memoised per locale, timezone and
options signature — construction dominated the 365-day ledger build and
the hot path drops about fivefold. A batched GET /api/medications/compliance
serves every card from one request through the same per-medication cache
cells the detail page reads, replacing the per-card fan-out; the list
response stays untouched so the due line never waits on a ledger build.
The medications and compliance buckets serve stale-while-revalidate,
interactive writes evict while background bulk sync marks stale, the
per-id route defers ownership and schedule reads until a cache miss,
and the card body reserves constant slots for the status and cycle
lines so late data stops shifting the layout.
This commit is contained in:
Marc-André Bombeck committed 2026-06-11 21:17:17 +02:00
1 parent 23f7c74c04
commit b346e78cc7
36 files changed
+1453 -500

No files matched your search

+132 -72
View File
@@ -2366,6 +2366,27 @@ paths:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/medications/compliance:
get:
tags:
- Medications
summary: Batched adherence read for every medication of the caller
description: Returns one compact adherence row per medication the caller owns (active + paused), ordered by `createdAt
DESC` — the single round trip the medication cards consume instead of fanning out one
`/api/medications/{id}/compliance` request per card. Each row carries the 7-/30-day summaries and the
cadence-scaled display block; the per-day grid stays on the per-medication endpoint. Pure computation — no
writes. Served through the same per-medication server cache as the per-id read, so the two endpoints warm each
other.
responses:
"200":
description: One adherence row per medication.
content:
application/json:
schema:
$ref: "#/components/schemas/ListMedicationComplianceResponse"
"401": *a1
"422": *a3
"429": *a4
/api/medications/{id}/compliance:
get:
tags:
@@ -8897,11 +8918,13 @@ components:
additionalProperties: false
description: One expected-vs-actual dose slot for the cadence timeline chart. `status` is one of `taken | skipped |
missed | pending | future` and drives the chip colour.
GetMedicationComplianceResponse:
ListMedicationComplianceResponse:
type: object
properties:
data:
$ref: "#/components/schemas/MedicationComplianceResponse"
type: array
items:
$ref: "#/components/schemas/MedicationComplianceSummaryEntry"
error:
type: "null"
meta:
@@ -8914,33 +8937,26 @@ components:
- data
- error
additionalProperties: false
MedicationComplianceResponse:
MedicationComplianceSummaryEntry:
type: object
properties:
medicationId:
type: string
compliance7:
$ref: "#/components/schemas/ComplianceResult"
compliance30:
$ref: "#/components/schemas/ComplianceResult"
dailyCompliance:
type: object
propertyNames:
type: string
additionalProperties:
$ref: "#/components/schemas/DailyComplianceEntry"
description: Flat per-day map keyed `YYYY-MM-DD` in the user timezone, one entry per day for up to 90 days back, clamped
to the medication's `createdAt` (so a recently-created med has fewer entries). No weekly/monthly collapse —
this is the raw daily grid.
complianceDisplay:
$ref: "#/components/schemas/ComplianceDisplay"
required:
- medicationId
- compliance7
- compliance30
- dailyCompliance
- complianceDisplay
additionalProperties: false
description: Adherence read for a single medication. `compliance30` is the authoritative 30-day taken-vs-expected
summary; `dailyCompliance` is the per-day grid for the history glyph track. The graded raw→week→month→year
series used elsewhere for AI prompts does NOT apply here — this response is never downsampled.
description: "Compact per-medication adherence row for the batched card read: the 7-/30-day summaries plus the
cadence-scaled display block. The per-day `dailyCompliance` grid is NOT on this shape — read the per-medication
`/compliance` endpoint for the history glyph track."
ComplianceResult:
type: object
properties:
@@ -8984,62 +9000,6 @@ components:
description: Rolling-window adherence summary. `compliance30` is the authoritative 'last 30 days, taken vs expected'
read — clients should display `rate` and use `totalExpected` as the denominator rather than re-deriving it from
the daily map.
DailyComplianceEntry:
type: object
properties:
expected:
type: integer
minimum: 0
maximum: 9007199254740991
description: Engine-computed due-slot count for the day. Equals `expectedCount`; kept for existing consumers.
expectedCount:
type: integer
minimum: 0
maximum: 9007199254740991
description: True due-slot count for the day (additive field clients key off so they don't infer due-ness from
`expected`).
due:
type: boolean
description: "`expectedCount > 0`. Paint a per-day glyph as expected/missed ONLY when `due === true`; off-cadence /
pre-creation / PRN days are not misses."
taken:
type: integer
minimum: 0
maximum: 9007199254740991
skipped:
type: integer
minimum: 0
maximum: 9007199254740991
onTime:
type: integer
minimum: 0
maximum: 9007199254740991
description: Doses taken in the on-time band, including the `early` bucket (early counts as compliant).
late:
type: integer
minimum: 0
maximum: 9007199254740991
veryLate:
type: integer
minimum: 0
maximum: 9007199254740991
early:
description: Doses taken before the on-time band's grace start; already folded into `onTime`, surfaced separately for
consumers that differentiate.
type: integer
minimum: 0
maximum: 9007199254740991
required:
- expected
- expectedCount
- due
- taken
- skipped
- onTime
- late
- veryLate
additionalProperties: false
description: Per-day compliance cell with the timing breakdown that drives the history glyph track.
ComplianceDisplay:
type: object
properties:
@@ -9206,6 +9166,106 @@ components:
additionalProperties: false
description: The two-row card block whose windows scale with dosing cadence (dense meds keep 7 / 30 days, sparse meds
step both windows up). NOT the 30-day denominator — read `compliance30.totalExpected` for that.
GetMedicationComplianceResponse:
type: object
properties:
data:
$ref: "#/components/schemas/MedicationComplianceResponse"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
MedicationComplianceResponse:
type: object
properties:
compliance7:
$ref: "#/components/schemas/ComplianceResult"
compliance30:
$ref: "#/components/schemas/ComplianceResult"
dailyCompliance:
type: object
propertyNames:
type: string
additionalProperties:
$ref: "#/components/schemas/DailyComplianceEntry"
description: Flat per-day map keyed `YYYY-MM-DD` in the user timezone, one entry per day for up to 90 days back, clamped
to the medication's `createdAt` (so a recently-created med has fewer entries). No weekly/monthly collapse —
this is the raw daily grid.
complianceDisplay:
$ref: "#/components/schemas/ComplianceDisplay"
required:
- compliance7
- compliance30
- dailyCompliance
- complianceDisplay
additionalProperties: false
description: Adherence read for a single medication. `compliance30` is the authoritative 30-day taken-vs-expected
summary; `dailyCompliance` is the per-day grid for the history glyph track. The graded raw→week→month→year
series used elsewhere for AI prompts does NOT apply here — this response is never downsampled.
DailyComplianceEntry:
type: object
properties:
expected:
type: integer
minimum: 0
maximum: 9007199254740991
description: Engine-computed due-slot count for the day. Equals `expectedCount`; kept for existing consumers.
expectedCount:
type: integer
minimum: 0
maximum: 9007199254740991
description: True due-slot count for the day (additive field clients key off so they don't infer due-ness from
`expected`).
due:
type: boolean
description: "`expectedCount > 0`. Paint a per-day glyph as expected/missed ONLY when `due === true`; off-cadence /
pre-creation / PRN days are not misses."
taken:
type: integer
minimum: 0
maximum: 9007199254740991
skipped:
type: integer
minimum: 0
maximum: 9007199254740991
onTime:
type: integer
minimum: 0
maximum: 9007199254740991
description: Doses taken in the on-time band, including the `early` bucket (early counts as compliant).
late:
type: integer
minimum: 0
maximum: 9007199254740991
veryLate:
type: integer
minimum: 0
maximum: 9007199254740991
early:
description: Doses taken before the on-time band's grace start; already folded into `onTime`, surfaced separately for
consumers that differentiate.
type: integer
minimum: 0
maximum: 9007199254740991
required:
- expected
- expectedCount
- due
- taken
- skipped
- onTime
- late
- veryLate
additionalProperties: false
description: Per-day compliance cell with the timing breakdown that drives the history glyph track.
ListScheduleRevisionsResponse:
type: object
properties: