Files
HealthLog/prisma/migrations/0222_v125_documents_library/migration.sql
T
Marc-André Bombeck 2321ce29db fix(insights): route alert-card compliance through the cadence-aware engine + deep data cleanups
The insights alert cards computed medication compliance with the naive
`schedules.length × days` denominator, so a fully-adherent weekly
injectable read ~14% and fired a false low-compliance alert (and a paused
medication mis-fired the same way). Route the alert path through the
canonical `calculateCompliance` engine like every other compliance
surface — pause eras, schedule revisions and dose windows honoured.

Document confirm: committing a numeric observation against an existing
biomarker silently replaced the stated unit with the catalog's, with no
conversion — a stated "6.1 mmol/L" against a mg/dL marker committed
"6.1 mg/dL". Reject on a unit mismatch (fail closed) so the user
reconciles on the review screen, the same posture as the unit-required
guard.

Wire grip strength, waist (with waist-to-height) and pain into the
briefing feature set and the grounding allow-set so the clinical-depth
signals reach the daily narrative; the mental-health screeners stay
excluded by construction.

Biomarker detail: fall back to a generic description for catalog-less
markers, and keep the heading + description with a retry on the error
state. Collapse the medication External-API and Lifecycle groups by
default. Re-enable the insights scroll-to-top regression and assert the
shipped main-content reset.

Align the EN haemoglobin/haematocrit display names to British spelling.

Drop the redundant `(userId, deletedAt)` index on inbound documents — a
strict prefix of the new three-column index. Trim the dead
MedicalDisclaimer variants to the one live `dataPosture`. Move the
crisis-resources card off the retired amber tint to a calm
destructive-subtle border. Add a registry invariant asserting every
biomarker signal key resolves to a real catalog slug.
2026-06-29 00:37:39 +02:00

92 lines
4.9 KiB
SQL

-- v1.25 — documents library: store-first, optional extraction.
--
-- Inverts the inbound-documents surface from an OCR inbox into a document
-- library. A user can now STORE any file (encrypted at rest) without a
-- configured document-scan provider; extraction becomes an optional action
-- on an already-stored row.
--
-- Schema deltas on `inbound_documents`:
-- * `title` — user-given label, plaintext (mirrors `filename`) so
-- the list can ILIKE-search + ORDER BY. The document
-- BODY stays encrypted; only this short user-authored
-- title is clear-text.
-- * `document_date` — user-set filing date (editable). Distinct from
-- `report_date`, which stays the model-transcribed
-- value.
-- * a `(user_id, deleted_at, document_date)` index for date-grouped browse +
-- range filter (the list always filters `deleted_at IS NULL`).
-- * `inbound_document_kind` gains LAB_RESULT / IMAGING / PRESCRIPTION /
-- INSURANCE / VACCINATION (the existing 3 stay; OTHER stays the fallback).
-- * `inbound_document_status` gains STORED, made the column DEFAULT (the
-- library default — uploaded, no extraction run).
--
-- Additive; no existing row rewritten (the status type-swap maps every
-- existing label to itself). Nullable columns, no backfill: existing rows
-- keep their current status; `title` / `document_date` start NULL and resolve
-- through the display fallback. Idempotent guards so reruns are safe on prod.
--
-- Postgres note: a freshly-added enum value cannot be USED (e.g. as a column
-- DEFAULT) in the same transaction it is added in. Because Prisma runs each
-- migration inside a transaction, STORED cannot be added with `ALTER TYPE ...
-- ADD VALUE` and then set as the default in the same file. The status enum is
-- therefore recreated (RENAME + CREATE with the full value set + column
-- type-swap + SET DEFAULT): a type CREATEd in the current transaction MAY
-- have its values used in that same transaction, which makes the default
-- change safe. The `kind` additions are not used in this migration, so the
-- cheap `ADD VALUE IF NOT EXISTS` form is used for them.
--
-- Reversibility (down):
-- DROP INDEX IF EXISTS "inbound_documents_user_id_document_date_idx";
-- ALTER TABLE "inbound_documents" DROP COLUMN IF EXISTS "document_date";
-- ALTER TABLE "inbound_documents" DROP COLUMN IF EXISTS "title";
-- -- (added enum values are inert if unused; a roll-back leaves them
-- -- harmless rather than rewriting the type back.)
-- AlterTable: user-given title + user-set filing date (both nullable).
ALTER TABLE "inbound_documents" ADD COLUMN IF NOT EXISTS "title" TEXT;
ALTER TABLE "inbound_documents" ADD COLUMN IF NOT EXISTS "document_date" TIMESTAMP(3);
-- AlterEnum: richer document-kind taxonomy (additive; not used in this
-- migration, so the new values are safe to add in-place).
ALTER TYPE "inbound_document_kind" ADD VALUE IF NOT EXISTS 'LAB_RESULT';
ALTER TYPE "inbound_document_kind" ADD VALUE IF NOT EXISTS 'IMAGING';
ALTER TYPE "inbound_document_kind" ADD VALUE IF NOT EXISTS 'PRESCRIPTION';
ALTER TYPE "inbound_document_kind" ADD VALUE IF NOT EXISTS 'INSURANCE';
ALTER TYPE "inbound_document_kind" ADD VALUE IF NOT EXISTS 'VACCINATION';
-- AlterEnum: add STORED and make it the column default. Recreate the type so
-- the new value can be USED as the default in this same transaction (a
-- same-transaction CREATE TYPE permits same-transaction use of its values).
-- Guarded on STORED-absence so a rerun is a no-op.
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'inbound_document_status' AND e.enumlabel = 'STORED'
) THEN
ALTER TYPE "inbound_document_status" RENAME TO "inbound_document_status_old";
CREATE TYPE "inbound_document_status" AS ENUM (
'STORED', 'EXTRACTING', 'EXTRACTED', 'FAILED', 'CONFIRMED', 'DISCARDED'
);
ALTER TABLE "inbound_documents" ALTER COLUMN "status" DROP DEFAULT;
ALTER TABLE "inbound_documents"
ALTER COLUMN "status" TYPE "inbound_document_status"
USING ("status"::text::"inbound_document_status");
ALTER TABLE "inbound_documents" ALTER COLUMN "status" SET DEFAULT 'STORED';
DROP TYPE "inbound_document_status_old";
END IF;
END $$;
-- CreateIndex: date-grouped browse + document_date range filter. The list
-- always filters `deleted_at IS NULL` and sorts by `document_date`, so
-- deleted_at leads the sort column to keep the scan covered.
CREATE INDEX IF NOT EXISTS "inbound_documents_user_id_deleted_at_document_date_idx"
ON "inbound_documents"("user_id", "deleted_at", "document_date");
-- DropIndex: the new `(user_id, deleted_at, document_date)` index above is a
-- superset of the plain `(user_id, deleted_at)` index, so the 2-column index is
-- redundant for read planning and only adds write/maintenance cost. Drop it.
DROP INDEX IF EXISTS "inbound_documents_user_id_deleted_at_idx";