The injection_site column on intake events was read across several
surfaces but never written, so site tracking was effectively dead. Wire
it end-to-end as a per-medication opt-in, gated on an INJECTION delivery
form and default off.
- Schema: add medications.track_injection_sites + allowed_injection_sites
and users.global_excluded_injection_sites (migrations 0099, 0100;
additive, defaulted, no backfill).
- Intake write path: accept an optional injectionSite on the per-med,
status-toggle, and bulk intake routes. Persist it only on a taken
injection with tracking enabled, validated against the medication's
effective allowed set (per-med allowed sites minus the user-level
global exclusion, deny wins) — a disallowed site returns 422 (the bulk
route skips the entry).
- Rotation: extend the least-recently-used recommender with an allowed
filter; the picker disables sites outside the effective set.
- UI: per-medication tracking toggle + allowed-sites editor in the dose
wizard step, a skippable post-dose site prompt on both medication
cards, and a Settings card for the user-level global exclusion.
- Contract: injectionSite is additive on the intake request; OpenAPI
regenerated. New GET/PATCH /api/auth/me/injection-site-prefs.
The site capture is always optional and never blocks logging a dose.