Files
HealthLog/prisma/migrations/0046_glp1_dose_history/migration.sql
T
Marc-André Bombeck 17b8d8d2c2 chore(release): v1.4.25
Largest feature delta in the v1.4.x line. Insights expands into seven dedicated metric sub-pages; GLP-1 medication tracking lands end-to-end (drug-level chart with Research Mode acknowledgment, EMA titration ladder, cadence + compliance, side-effect taxonomy, pen/vial inventory); cross-source priority becomes a two-axis resolver; per-user timezone threads through ten analytics surfaces; Withings activity + sleep v2 sync; onboarding rebuilt as a nested-route wizard; Personal Records ship end-to-end; Health Score provenance accordion; Coach runs native first-party prompts across six locales with a 1800-assertion refusal-probe matrix; OpenAPI drift gate hard-fails; multi-arch GHCR image. Migrations 0043 through 0060 are additive and forward-only. PROMPT_VERSION 4.24.0 → 4.25.0 with GROUND RULE 9 (refuses dose recommendations) and GROUND RULE 15 (refuses drug-level estimates with MDR + MDCG 2021-24 cites).

See CHANGELOG.md for the full per-section delta.
2026-05-14 23:49:32 +02:00

128 lines
5.2 KiB
SQL

-- v1.4.25 W4d — GLP-1 full integration (research §5).
--
-- Adds the foundation for first-class GLP-1 receptor agonist support
-- (Mounjaro, Ozempic, Wegovy, Zepbound, Trulicity, Saxenda, Rybelsus,
-- compounded semaglutide/tirzepatide). Holistic landing — schema +
-- snapshot + Coach guardrails + UI variants — happens together so the
-- feature surfaces all at once instead of trickling in across releases.
--
-- Design principle: every change is additive. Existing medications keep
-- working untouched. The new columns default to NULL / GENERIC so the
-- previous read paths read the same shape they did before the migration.
--
-- Shapes:
-- - MedicationCategory enum (GENERIC default; GLP1 turns on the
-- specialist code paths). Future categories — INSULIN, BIOLOGIC,
-- FERTILITY — drop into this enum.
-- - Medication.treatment_class column with GENERIC default. The
-- existing `medication_categories` side-table (BLOOD_PRESSURE /
-- VITAMIN / ...) tracks the clinical taxonomy and stays put — it
-- is a different concept (clinical category) and the UI keeps
-- reading it under the existing `category` slot. The new column
-- models "medication kind that needs special handling" so the
-- GLP-1 surfaces (card variant, dashboard tile, Coach snapshot)
-- have a single boolean-ish discriminator.
-- - Medication.doses_per_unit — nullable integer; only meaningful for
-- pen / vial meds (Mounjaro KwikPen = 4 doses, Ozempic 0.25/0.5 mg
-- = 4 doses, Wegovy single-dose pen = 1 dose, compounded vials
-- vary). NULL = inventory tracking is off for this medication.
-- - MedicationDoseChange — append-only titration history. The
-- current dose is "latest row by effective_from". Existing
-- Medication.dose remains the convenience pointer; this table is
-- the history of record.
-- - InjectionSite enum (8 zones — front-of-body view: 4 abdomen
-- quadrants, 2 thighs, 2 upper arms). The body-map picker maps each
-- zone to a clickable region.
-- - MedicationIntakeEvent.injection_site — nullable; only populated
-- for GLP-1 intake events. Existing rows stay NULL and read
-- untouched.
-- - MedicationInventoryEvent — append-only inventory log. Positive
-- delta = stock added (purchase), negative = consumed (one per
-- intake event when doses_per_unit is set). Reason is a free-text
-- audit slot ("purchased", "consumed", "expired", "damaged").
--
-- Forward-only, additive, idempotent. No backfill: every new column
-- carries a sensible NULL/default, and the read paths handle the
-- pre-migration shape verbatim.
-- 1. MedicationCategory enum.
DO $$ BEGIN
CREATE TYPE "medication_category" AS ENUM ('GENERIC', 'GLP1');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- 2. Medication.treatment_class + Medication.doses_per_unit columns.
ALTER TABLE "medications"
ADD COLUMN IF NOT EXISTS "treatment_class" "medication_category" NOT NULL DEFAULT 'GENERIC';
ALTER TABLE "medications"
ADD COLUMN IF NOT EXISTS "doses_per_unit" INTEGER;
CREATE INDEX IF NOT EXISTS "medications_treatment_class_idx" ON "medications" ("treatment_class");
-- 3. InjectionSite enum + MedicationIntakeEvent.injection_site column.
DO $$ BEGIN
CREATE TYPE "injection_site" AS ENUM (
'ABDOMEN_LEFT',
'ABDOMEN_RIGHT',
'ABDOMEN_UPPER_LEFT',
'ABDOMEN_UPPER_RIGHT',
'THIGH_LEFT',
'THIGH_RIGHT',
'UPPER_ARM_LEFT',
'UPPER_ARM_RIGHT'
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
ALTER TABLE "medication_intake_events"
ADD COLUMN IF NOT EXISTS "injection_site" "injection_site";
-- 4. MedicationDoseChange — titration history.
CREATE TABLE IF NOT EXISTS "medication_dose_changes" (
"id" TEXT NOT NULL,
"medication_id" TEXT NOT NULL,
"effective_from" TIMESTAMP(3) NOT NULL,
"dose_value" DOUBLE PRECISION NOT NULL,
"dose_unit" TEXT NOT NULL,
"note" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "medication_dose_changes_pkey" PRIMARY KEY ("id")
);
DO $$ BEGIN
ALTER TABLE "medication_dose_changes"
ADD CONSTRAINT "medication_dose_changes_medication_id_fkey"
FOREIGN KEY ("medication_id") REFERENCES "medications"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
CREATE INDEX IF NOT EXISTS "medication_dose_changes_medication_id_effective_from_idx"
ON "medication_dose_changes" ("medication_id", "effective_from");
-- 5. MedicationInventoryEvent — append-only inventory ledger.
CREATE TABLE IF NOT EXISTS "medication_inventory_events" (
"id" TEXT NOT NULL,
"medication_id" TEXT NOT NULL,
"delta" INTEGER NOT NULL,
"reason" TEXT NOT NULL,
"occurred_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "medication_inventory_events_pkey" PRIMARY KEY ("id")
);
DO $$ BEGIN
ALTER TABLE "medication_inventory_events"
ADD CONSTRAINT "medication_inventory_events_medication_id_fkey"
FOREIGN KEY ("medication_id") REFERENCES "medications"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
CREATE INDEX IF NOT EXISTS "medication_inventory_events_medication_id_occurred_at_idx"
ON "medication_inventory_events" ("medication_id", "occurred_at");