Files
HealthLog/prisma/migrations/0171_v1181_illness_episode/migration.sql
T
Marc-André Bombeck 7451e886f1 fix(vorsorge,coach): align reminders + coach chrome with the gold standard
Twice-daily BP protocol (RRULE BYHOUR=7,19) now fires at every clock
hour: byHourTimesOfDay expands BYHOUR into the engine's timesOfDay so
the label, RRULE, and engine output agree instead of collapsing to one
nudge at notifyHour.

Vorsorge surface:
- move create/edit into a ResponsiveSheet with a pinned Cancel/Save
  footer, matching Labs/Illness/Medications
- wire the unused PATCH support: Edit affordance opens the form
  pre-filled; an enable/disable Switch drives the enabled flag
- translate COACH-origin labels (the row stores an i18n key, not prose)
  and render a neutral Coach provenance badge + an until-<date> line
- surface origin + endsOn on the hook type
- pull-to-refresh + always-on rationale documented on the nav entry
- NativeSelect-vs-Radix choice documented as deliberate

Coach:
- expired COACH course-window reminders self-clean: the cron tick
  soft-deletes finished finite cadences instead of leaving dead rows
- accept-dedup gains a partial unique index (user, metric) where
  origin=COACH and not deleted, with a P2002 catch in the route
- per-user rate-limit bucket on the suggestion-action endpoint
- SUGGEST-REMINDER parser keeps prose written after the block
- document POST /api/coach/reminder-suggestions in OpenAPI

Coach chrome:
- HistoryRail hideHeading prop drops the duplicate Conversations
  heading on the page surface
- align the rail-tray strip inset to px-3 sm:px-4 so the dividers line
  up; reword the FAB inset comment

satisfyReminder closes the forward-only TOCTOU via a conditional
updateMany (count===0 no-op). Migration headers corrected to v1.18.1.
2026-06-16 23:11:08 +02:00

204 lines
8.3 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- v1.18.1 — illness / condition journal.
--
-- A CONDITION journal (not a flu log): any illness/condition kept for
-- oneself, with a retrospective lens. Retrospective + correlated, NEVER a
-- predictor/diagnoser. Born-gated by the module system — the gate is
-- app-layer, so there is no enable flag in the schema.
--
-- Tables (the daily timeline mirrors the Cycle three-table shape):
--
-- * `illness_episodes` — one episode. `illness_type` widens beyond an
-- infection log (infection / allergy / injury / mental-health /
-- autoimmune / chronic / other). `illness_lifecycle`
-- (ACUTE / CHRONIC_ONGOING / RECURRING / FLARE) distinguishes a bout from
-- an ongoing chronic condition; CHRONIC_ONGOING carries no resolved_at
-- and is excluded from the recovery-gap math. `parent_condition_id` is a
-- self-FK (ON DELETE SET NULL) so a flare/recurrence threads under one
-- parent condition. `note_encrypted` is a Bytes column (the AES-256-GCM
-- `*Encrypted` convention). Soft-deleted.
--
-- * `illness_day_logs` — one row per day of an episode (mirrors
-- cycle_day_logs): a coarse 0–3 `functional_impact` slider, an optional
-- `fever_c` reading (queryable plaintext, the basal_body_temp_c
-- precedent), an encrypted day note, the day's symptom links. Unique per
-- (episode_id, date). Soft-deleted.
--
-- * `illness_symptoms` — a seeded, icon-bearing, labelKey-i18n catalog
-- (Jackson / WURSS-derived; the cycle_symptoms precedent). Eight
-- high-signal cold/illness symptoms, scored 0–3 on the link.
--
-- * `illness_symptom_links` — the per-day-log join, composite PK
-- (day_log_id, symptom_id), 0–3 severity, cascade on both sides.
--
-- Additive; no existing row touched. The catalog seed uses deterministic
-- ids + ON CONFLICT ("key") DO NOTHING so re-running is a no-op (the
-- cycle/mood-tag seed precedent). Keep in sync with the app-side taxonomy
-- module if either changes.
--
-- Idempotent guards (`IF NOT EXISTS` / `DO $$`) so reruns are safe on prod.
--
-- Reversibility (down):
-- DROP TABLE IF EXISTS "illness_symptom_links";
-- DROP TABLE IF EXISTS "illness_symptoms";
-- DROP TABLE IF EXISTS "illness_day_logs";
-- DROP TABLE IF EXISTS "illness_episodes";
-- DROP TYPE IF EXISTS "illness_lifecycle";
-- DROP TYPE IF EXISTS "illness_type";
-- A roll-back drops the journal wholesale — no other domain depends on it.
-- CreateEnum
DO $$ BEGIN
CREATE TYPE "illness_type" AS ENUM ('INFECTION', 'ALLERGY', 'INJURY', 'MENTAL_HEALTH', 'AUTOIMMUNE', 'CHRONIC', 'OTHER');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- CreateEnum
DO $$ BEGIN
CREATE TYPE "illness_lifecycle" AS ENUM ('ACUTE', 'CHRONIC_ONGOING', 'RECURRING', 'FLARE');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- CreateTable
CREATE TABLE IF NOT EXISTS "illness_episodes" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"label" TEXT NOT NULL,
"type" "illness_type" NOT NULL,
"lifecycle" "illness_lifecycle" NOT NULL DEFAULT 'ACUTE',
"onset_at" TIMESTAMP(3) NOT NULL,
"resolved_at" TIMESTAMP(3),
"parent_condition_id" TEXT,
"note_encrypted" BYTEA,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"deleted_at" TIMESTAMP(3),
CONSTRAINT "illness_episodes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "illness_day_logs" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"episode_id" TEXT NOT NULL,
"date" TEXT NOT NULL,
"functional_impact" INTEGER,
"fever_c" DOUBLE PRECISION,
"note_encrypted" BYTEA,
"tz" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"deleted_at" TIMESTAMP(3),
CONSTRAINT "illness_day_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "illness_symptoms" (
"id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"label_key" TEXT NOT NULL,
"icon" TEXT,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"is_active" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "illness_symptoms_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE IF NOT EXISTS "illness_symptom_links" (
"day_log_id" TEXT NOT NULL,
"symptom_id" TEXT NOT NULL,
"severity" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "illness_symptom_links_pkey" PRIMARY KEY ("day_log_id","symptom_id")
);
-- CreateIndex
CREATE INDEX IF NOT EXISTS "illness_episodes_user_id_deleted_at_idx" ON "illness_episodes"("user_id", "deleted_at");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "illness_episodes_user_id_onset_at_idx" ON "illness_episodes"("user_id", "onset_at");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "illness_episodes_parent_condition_id_idx" ON "illness_episodes"("parent_condition_id");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "illness_day_logs_episode_id_date_key" ON "illness_day_logs"("episode_id", "date");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "illness_day_logs_user_id_date_idx" ON "illness_day_logs"("user_id", "date");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "illness_day_logs_episode_id_idx" ON "illness_day_logs"("episode_id");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "illness_symptoms_key_key" ON "illness_symptoms"("key");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "illness_symptoms_sort_order_idx" ON "illness_symptoms"("sort_order");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "illness_symptom_links_symptom_id_idx" ON "illness_symptom_links"("symptom_id");
-- AddForeignKey
DO $$ BEGIN
ALTER TABLE "illness_episodes" ADD CONSTRAINT "illness_episodes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- AddForeignKey
DO $$ BEGIN
ALTER TABLE "illness_episodes" ADD CONSTRAINT "illness_episodes_parent_condition_id_fkey" FOREIGN KEY ("parent_condition_id") REFERENCES "illness_episodes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- AddForeignKey
DO $$ BEGIN
ALTER TABLE "illness_day_logs" ADD CONSTRAINT "illness_day_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- AddForeignKey
DO $$ BEGIN
ALTER TABLE "illness_day_logs" ADD CONSTRAINT "illness_day_logs_episode_id_fkey" FOREIGN KEY ("episode_id") REFERENCES "illness_episodes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- AddForeignKey
DO $$ BEGIN
ALTER TABLE "illness_symptom_links" ADD CONSTRAINT "illness_symptom_links_day_log_id_fkey" FOREIGN KEY ("day_log_id") REFERENCES "illness_day_logs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- AddForeignKey
DO $$ BEGIN
ALTER TABLE "illness_symptom_links" ADD CONSTRAINT "illness_symptom_links_symptom_id_fkey" FOREIGN KEY ("symptom_id") REFERENCES "illness_symptoms"("id") ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- ─── Seed the default illness-symptom catalog ────────────────────────
-- Jackson/WURSS-derived high-signal cold/illness symptoms, scored 0–3 on
-- the link. Idempotent: deterministic ids + ON CONFLICT ("key") DO NOTHING
-- (the cycle_symptoms seed precedent 0129). Keep in sync with the app-side
-- illness symptom taxonomy if either changes.
INSERT INTO "illness_symptoms" ("id", "key", "label_key", "icon", "sort_order", "is_active") VALUES
('is_runny_nose', 'runny_nose', 'illness.symptom.runnyNose', 'Droplets', 0, true),
('is_stuffy_nose', 'stuffy_nose', 'illness.symptom.stuffyNose', 'Wind', 1, true),
('is_sneezing', 'sneezing', 'illness.symptom.sneezing', 'Waves', 2, true),
('is_sore_throat', 'sore_throat', 'illness.symptom.soreThroat', 'Flame', 3, true),
('is_cough', 'cough', 'illness.symptom.cough', 'Megaphone', 4, true),
('is_headache', 'headache', 'illness.symptom.headache', 'Brain', 5, true),
('is_body_aches', 'body_aches', 'illness.symptom.bodyAches', 'PersonStanding', 6, true),
('is_fatigue', 'fatigue', 'illness.symptom.fatigue', 'BatteryLow', 7, true)
ON CONFLICT ("key") DO NOTHING;