feat(medications): server-authoritative medication-efficacy view + API

Add the migration (0232), Prisma model MedicationEfficacyTarget (persists ONLY
the user's explicit target override; everything else derives each read), and
the server builder buildMedicationEfficacy that composes the target's series,
a before/after-start comparison, the cadence-aware adherence lane (never
recomputed), an optional conservative level-shift note, honest data-floor
states and the retarget options into one strictly-descriptive DTO — no
verdict/score field by construction. Expose GET /api/medications/[id]/efficacy
and PUT /api/medications/[id]/efficacy/target, regenerate the OpenAPI contract.
This commit is contained in:
Marc-André Bombeck committed 2026-07-09 12:45:50 +02:00
1 parent f3762cf0f9
commit 725f69097f
9 files changed
+1629 -2

No files matched your search

+505
View File
@@ -3389,6 +3389,163 @@ paths:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/medications/{id}/efficacy:
get:
tags:
- Medications
summary: Efficacy view for a medication ("Wirkung")
description: "Returns the resolved, strictly-descriptive efficacy DTO: the outcome metric(s)/lab(s) the medication's
class targets, the target series with start/dose-change/pause markers, a before/after-start comparison, the
cadence-aware adherence lane, an optional conservative level-shift note, the data-floor state, and the retarget
options. Association-only — there is no verdict / score field. `eligible:false` (with `reason`) marks a one-shot
or no-target medication whose tab is hidden."
parameters:
- in: path
name: id
schema:
type: string
required: true
responses:
"200":
description: Efficacy view.
content:
application/json:
schema:
$ref: "#/components/schemas/GetMedicationEfficacyResponse"
"401": *a1
"404":
description: Medication not found (or owned by another user).
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/medications/{id}/efficacy/target:
put:
tags:
- Medications
summary: Set or clear the efficacy-target override
description: Persists the user's explicit efficacy target for a medication (the only thing the view stores; everything
else is derived each read). Pin exactly one of `measurementType` / `biomarkerId`, or pass `clear:true` to revert
to the derived target.
parameters:
- in: path
name: id
schema:
type: string
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
clear:
type: boolean
measurementType:
type: string
enum:
- WEIGHT
- BLOOD_PRESSURE_SYS
- BLOOD_PRESSURE_DIA
- PULSE
- BODY_FAT
- SLEEP_DURATION
- ACTIVITY_STEPS
- BLOOD_GLUCOSE
- TOTAL_BODY_WATER
- BONE_MASS
- OXYGEN_SATURATION
- HEART_RATE_VARIABILITY
- RESTING_HEART_RATE
- ACTIVE_ENERGY_BURNED
- FLIGHTS_CLIMBED
- WALKING_RUNNING_DISTANCE
- VO2_MAX
- BODY_TEMPERATURE
- FAT_FREE_MASS
- FAT_MASS
- MUSCLE_MASS
- SKIN_TEMPERATURE
- PULSE_WAVE_VELOCITY
- VASCULAR_AGE
- VISCERAL_FAT
- AUDIO_EXPOSURE_ENV
- AUDIO_EXPOSURE_HEADPHONE
- TIME_IN_DAYLIGHT
- WALKING_STEADINESS
- AUDIO_EXPOSURE_EVENT
- RESPIRATORY_RATE
- BODY_MASS_INDEX
- LEAN_BODY_MASS
- WALKING_HEART_RATE_AVERAGE
- WALKING_ASYMMETRY
- WALKING_DOUBLE_SUPPORT
- WALKING_STEP_LENGTH
- WALKING_SPEED
- CARDIO_RECOVERY
- WRIST_TEMPERATURE
- FALL_COUNT
- SIX_MINUTE_WALK_DISTANCE
- STAIR_ASCENT_SPEED
- STAIR_DESCENT_SPEED
- BREATHING_DISTURBANCES
- IRREGULAR_RHYTHM_NOTIFICATION
- HIGH_HEART_RATE_EVENT
- LOW_HEART_RATE_EVENT
- WALKING_STEADINESS_EVENT
- BREATHING_DISTURBANCE_EVENT
- RECOVERY_SCORE
- STRESS_SCORE
- STRAIN_SCORE
- HRV_RMSSD
- DAY_STRAIN
- WORKOUT_STRAIN
- SLEEP_PERFORMANCE
- SLEEP_EFFICIENCY
- SLEEP_CONSISTENCY
- SLEEP_NEED
- ENERGY_EXPENDITURE_KJ
- AVERAGE_HEART_RATE
- MAX_HEART_RATE
- SLEEP_DISTURBANCE_COUNT
- ANS_CHARGE
- CARDIO_LOAD
- SLEEP_SCORE
- BODY_TEMPERATURE_DEVIATION
- RESILIENCE
- PHQ9_SCORE
- GAD7_SCORE
- GRIP_STRENGTH
- PAIN_NRS
- WAIST_CIRCUMFERENCE
- WAIST_TO_HEIGHT
- WHO5_SCORE
- SCI_SCORE
biomarkerId:
type: string
minLength: 1
maxLength: 64
primary:
type: boolean
responses:
"200":
description: Override set or cleared.
content:
application/json:
schema:
$ref: "#/components/schemas/SetMedicationEfficacyTargetResponse"
"401": *a1
"404":
description: Medication or biomarker not found.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
/api/medications/{id}/schedule-revisions:
get:
tags:
@@ -17712,6 +17869,354 @@ components:
- veryLate
additionalProperties: false
description: Per-day compliance cell with the timing breakdown that drives the history glyph track.
GetMedicationEfficacyResponse:
type: object
properties:
data:
type: object
properties:
medicationId:
type: string
medicationName:
type: string
eligible:
type: boolean
reason:
type: string
enum:
- one_shot
- no_target
startsOn:
anyOf:
- type: string
- type: "null"
resolution:
type: object
properties:
tier:
type: string
enum:
- override
- atc
- name
- none
cls:
anyOf:
- type: string
- type: "null"
required:
- tier
- cls
additionalProperties: false
windowDays:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
minWeeksPerSide:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
markers:
type: object
properties:
start:
anyOf:
- type: string
- type: "null"
startSource:
anyOf:
- type: string
enum:
- startsOn
- firstReading
- type: "null"
doseChanges:
type: array
items:
type: object
properties:
at:
type: string
label:
type: string
required:
- at
- label
additionalProperties: false
pauses:
type: array
items:
type: object
properties:
from:
type: string
to:
anyOf:
- type: string
- type: "null"
required:
- from
- to
additionalProperties: false
required:
- start
- startSource
- doseChanges
- pauses
additionalProperties: false
targets:
type: array
items:
type: object
properties:
kind:
type: string
enum:
- metric
- lab
key:
type: string
label:
type: string
unit:
anyOf:
- type: string
- type: "null"
primary:
type: boolean
referenceBand:
anyOf:
- type: object
properties:
low:
type: number
high:
type: number
required:
- low
- high
additionalProperties: false
- type: "null"
series:
type: array
items:
type: object
properties:
t:
type: string
value:
type: number
status:
type: string
enum:
- in-range
- below
- above
- unknown
required:
- t
- value
additionalProperties: false
beforeAfter:
type: object
properties:
present:
type: boolean
reason:
type: string
enum:
- insufficient_before
- insufficient_after
- no_start
- no_data
before:
type: object
properties:
mean:
type: number
count:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
from:
type: string
to:
type: string
required:
- mean
- count
- from
- to
additionalProperties: false
after:
type: object
properties:
mean:
type: number
count:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
from:
type: string
to:
type: string
required:
- mean
- count
- from
- to
additionalProperties: false
delta:
anyOf:
- type: object
properties:
mean:
type: number
pct:
anyOf:
- type: number
- type: "null"
required:
- mean
- pct
additionalProperties: false
- type: "null"
required:
- present
additionalProperties: false
levelShift:
anyOf:
- type: object
properties:
present:
type: boolean
at:
type: string
nearStart:
type: boolean
required:
- present
additionalProperties: false
- type: "null"
required:
- kind
- key
- label
- unit
- primary
- referenceBand
- series
- beforeAfter
- levelShift
additionalProperties: false
adherence:
type: array
items:
type: object
properties:
date:
type: string
rate:
type: number
taken:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
missed:
type: integer
minimum: -9007199254740991
maximum: 9007199254740991
required:
- date
- rate
- taken
- missed
additionalProperties: false
overrideOptions:
type: object
properties:
metrics:
type: array
items:
type: object
properties:
key:
type: string
label:
type: string
required:
- key
- label
additionalProperties: false
biomarkers:
type: array
items:
type: object
properties:
id:
type: string
label:
type: string
unit:
type: string
required:
- id
- label
- unit
additionalProperties: false
required:
- metrics
- biomarkers
additionalProperties: false
required:
- medicationId
- medicationName
- eligible
- startsOn
- resolution
- windowDays
- minWeeksPerSide
- markers
- targets
- adherence
- overrideOptions
additionalProperties: false
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
SetMedicationEfficacyTargetResponse:
type: object
properties:
data:
type: object
properties:
ok:
type: boolean
cleared:
type: boolean
additionalProperties: false
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
ListScheduleRevisionsResponse:
type: object
properties: