Files
HealthLog/prisma/migrations/0054_personal_record/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

51 lines
2.7 KiB
SQL

-- v1.4.25 W8d — PersonalRecord schema (no detection worker yet).
--
-- Schema lands now so the v1.5 iOS-Swift app can build its query path
-- against a real GET /api/personal-records route from day one. The
-- worker that populates the rows (sweep on insert / nightly job) is
-- explicitly out of v1.4.25 scope — see the deferred section in the
-- W8d phase report. v1.4.25 ships the bare list endpoint that returns
-- whatever rows the DB carries (initially empty for every user).
--
-- Direction enum (MAX | MIN) is stored on the row rather than derived
-- at read time so the future worker doesn't re-resolve a per-metric
-- lookup on every query. Some metric types deliberately carry NULL
-- direction in the helper (BP, glucose, weight, ambiguous-direction
-- metrics) — those never produce a PersonalRecord row at all, so the
-- column stays NOT NULL for the metrics where a record is actually
-- defined.
--
-- Composite uniqueness on (userId, metricType, metricSlot, achievedAt)
-- ensures the detection worker is idempotent — re-running it never
-- inserts a duplicate. A multi-source tie (e.g. iPhone + Apple Watch
-- both report 12_348 steps for the same day) is broken by including
-- `source` so each provenance retains its row; the display layer
-- runs the existing pickCanonicalSourceRows() helper to surface one.
CREATE TYPE "personal_record_direction" AS ENUM ('MAX', 'MIN');
CREATE TABLE "personal_records" (
"id" TEXT PRIMARY KEY,
"user_id" TEXT NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
"metric_type" "measurement_type" NOT NULL,
-- Sport-specific slot for workout-driven PRs (e.g. "running_5km_time").
-- NULL for measurement-driven PRs that don't need a sport dimension.
"metric_slot" TEXT,
"direction" "personal_record_direction" NOT NULL,
"value" DOUBLE PRECISION NOT NULL,
"unit" TEXT NOT NULL,
"achieved_at" TIMESTAMP(3) NOT NULL,
-- FK to the Measurement row that achieved the record. SET NULL on
-- delete so a PR survives the deletion of the originating
-- measurement (the PR is the historical fact, not a derived view).
"source_measurement_id" TEXT REFERENCES "measurements"("id") ON DELETE SET NULL,
"source" "measurement_source" NOT NULL DEFAULT 'MANUAL',
"external_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX "personal_records_dedup_key"
ON "personal_records" ("user_id", "metric_type", "metric_slot", "achieved_at");
CREATE INDEX "personal_records_user_metric_idx"
ON "personal_records" ("user_id", "metric_type", "value");