feat: migrate clients module to patients + add docs
This commit is contained in:
13
docs/archive/manifesto.md
Normal file
13
docs/archive/manifesto.md
Normal file
@@ -0,0 +1,13 @@
|
||||
"Software is eating the world, but AI is going to eat software"
|
||||
Jensen Huang, CEO van Nvidia, zei dit tijdens zijn keynote op GTC in maart 2024. Hij bouwde voort op Marc Andreessen's beroemde uitspraak uit 2011 over hoe software alle sectoren opslokt. Huang's voorspelling: AI zal nu software zelf transformeren - automatiseren, genereren, vervangen.
|
||||
Nu, in 2025, zien we het gebeuren. En bijna niemand heeft het door.
|
||||
Het traditionele SaaS-model is simpel: één gebruiker = één licentie. Logisch in een wereld waar elke gebruiker ongeveer evenveel waarde uit software haalt. Maar ook beperkend - je groei is gekoppeld aan het aantal medewerkers bij je klant. Dit model heeft de software-industrie 20 jaar gedomineerd. Vendors optimaliseren voor meer seats, meer modules, meer lock-in. Klanten betalen voor potentieel, niet voor werkelijke waarde.
|
||||
Maar er gebeurt iets fundamenteels. AI maakt het mogelijk om software te genereren in plaats van te configureren. Niet meer kiezen uit wat bestaat, maar bouwen wat je nodig hebt. McKinsey noemt het "Software on Demand" - adaptieve diensten die via natuurlijke taal ontstaan. De implicaties zijn enorm.
|
||||
Waar traditionele implementaties 6-12 maanden duren, bouw je met AI een werkende applicatie in 4 weken. Niet omdat AI magisch is, maar omdat je 80% van de standaard-code niet meer hoeft te schrijven. De kostenbasis verschuift compleet - van €100k per jaar naar €50 per maand. Geen armies van consultants. Geen jarenlange developmenttrajecten. Infrastructuur die schaalt met gebruik. AI die de heavy lifting doet. En het belangrijkste: je bezit de code. Je controleert de roadmap. Aanpassing nodig? Dagen, geen kwartalen.
|
||||
Dit is geen toekomstmuziek. Ik zie het nu al gebeuren - startups die complete workflows bouwen in de tijd dat enterprises nog aan het onderhandelen zijn over licenties. De vraag is niet óf dit de norm wordt, maar wanneer.
|
||||
Voor software vendors is dit existentieel. Hun hele businessmodel - recurring revenue op basis van seats - verdampt als klanten hun eigen oplossingen kunnen bouwen. Voor enterprises opent dit ongekende mogelijkheden. Software die past bij hoe je werkt, niet andersom. Innovatie in weken, niet jaren.
|
||||
We staan nog aan het begin van deze shift. De grote vraag wordt: wie durft eerst? Wie accepteert dat de SAP-implementatie van 5 jaar geleden misschien wel de laatste traditionele software-aankoop was?
|
||||
Tijd voor een experiment. Ik ga live bouwen hoe ver je komt met moderne AI-tools. Een EPD als testcase - daar ligt mijn ervaring, daar ken ik de pijn. Van niets naar een werkende applicatie in 4 weken, voor de maandelijkse kosten van één SaaS-licentie.
|
||||
De AI Speedrun. Volg de voortgang. Doe suggesties. Kijk mee hoe het nieuwe development er in de praktijk uitziet.
|
||||
Want de beste manier om de toekomst te voorspellen, is hem bouwen.
|
||||
Week 1 start vandaag.
|
||||
180
docs/archive/migrations/20241115000003_test_rls_policies.sql
Normal file
180
docs/archive/migrations/20241115000003_test_rls_policies.sql
Normal file
@@ -0,0 +1,180 @@
|
||||
-- ================================================
|
||||
-- RLS Policy Tests
|
||||
-- Created: 2024-11-15
|
||||
-- Epic: E2 - Database & Auth
|
||||
-- Story: E2.S2 - RLS policies implementeren
|
||||
-- ================================================
|
||||
-- This file contains test queries to verify RLS policies
|
||||
-- Run these queries manually to verify RLS is working correctly
|
||||
-- ================================================
|
||||
|
||||
-- ================================================
|
||||
-- TEST 1: Verify RLS is enabled on all tables
|
||||
-- ================================================
|
||||
-- Expected: All tables should have rowsecurity = true
|
||||
|
||||
SELECT
|
||||
tablename,
|
||||
rowsecurity as rls_enabled
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
ORDER BY tablename;
|
||||
|
||||
-- Expected output:
|
||||
-- ai_events | true
|
||||
-- clients | true
|
||||
-- intake_notes | true
|
||||
-- problem_profiles | true
|
||||
-- treatment_plans | true
|
||||
|
||||
-- ================================================
|
||||
-- TEST 2: Check all RLS policies exist
|
||||
-- ================================================
|
||||
-- Expected: Each table should have 4 policies (SELECT, INSERT, UPDATE, DELETE)
|
||||
-- except ai_events which has only 2 (SELECT, INSERT)
|
||||
|
||||
SELECT
|
||||
tablename,
|
||||
COUNT(*) as policy_count,
|
||||
STRING_AGG(cmd, ', ' ORDER BY cmd) as commands
|
||||
FROM pg_policies
|
||||
WHERE schemaname = 'public'
|
||||
GROUP BY tablename
|
||||
ORDER BY tablename;
|
||||
|
||||
-- Expected output:
|
||||
-- ai_events | 2 | INSERT, SELECT
|
||||
-- clients | 4 | DELETE, INSERT, SELECT, UPDATE
|
||||
-- intake_notes | 4 | DELETE, INSERT, SELECT, UPDATE
|
||||
-- problem_profiles | 4 | DELETE, INSERT, SELECT, UPDATE
|
||||
-- treatment_plans | 4 | DELETE, INSERT, SELECT, UPDATE
|
||||
|
||||
-- ================================================
|
||||
-- TEST 3: Verify policy predicates use auth.uid()
|
||||
-- ================================================
|
||||
-- Expected: All policies should check auth.uid() IS NOT NULL
|
||||
|
||||
SELECT
|
||||
tablename,
|
||||
policyname,
|
||||
cmd,
|
||||
qual as using_clause,
|
||||
with_check
|
||||
FROM pg_policies
|
||||
WHERE schemaname = 'public'
|
||||
AND qual NOT LIKE '%auth.uid()%'
|
||||
ORDER BY tablename, policyname;
|
||||
|
||||
-- Expected output: Empty (no policies without auth.uid() check)
|
||||
|
||||
-- ================================================
|
||||
-- TEST 4: Simulate authenticated user query
|
||||
-- ================================================
|
||||
-- This test simulates what happens when an authenticated user
|
||||
-- tries to access data. In production, auth.uid() would return
|
||||
-- the user's actual UUID.
|
||||
|
||||
-- Note: These queries will work in the SQL editor when logged in,
|
||||
-- but will fail when run as unauthenticated
|
||||
|
||||
-- Test SELECT permission (should succeed when authenticated)
|
||||
-- SELECT * FROM clients LIMIT 1;
|
||||
|
||||
-- Test INSERT permission (should succeed when authenticated)
|
||||
-- INSERT INTO clients (first_name, last_name, birth_date)
|
||||
-- VALUES ('Test', 'User', '1990-01-01');
|
||||
|
||||
-- Test UPDATE permission (should succeed when authenticated)
|
||||
-- UPDATE clients SET first_name = 'Updated' WHERE id = 'some-uuid';
|
||||
|
||||
-- Test DELETE permission (should succeed when authenticated)
|
||||
-- DELETE FROM clients WHERE id = 'some-uuid';
|
||||
|
||||
-- ================================================
|
||||
-- TEST 5: Verify ai_events immutability
|
||||
-- ================================================
|
||||
-- Expected: ai_events should NOT have UPDATE or DELETE policies
|
||||
-- (except for service role via RLS bypass)
|
||||
|
||||
SELECT
|
||||
tablename,
|
||||
policyname,
|
||||
cmd
|
||||
FROM pg_policies
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename = 'ai_events'
|
||||
AND cmd IN ('UPDATE', 'DELETE')
|
||||
ORDER BY cmd;
|
||||
|
||||
-- Expected output: Empty (no UPDATE or DELETE policies for regular users)
|
||||
|
||||
-- ================================================
|
||||
-- TEST 6: Check foreign key relationships
|
||||
-- ================================================
|
||||
-- Expected: All foreign keys should be properly set up
|
||||
|
||||
SELECT
|
||||
tc.table_name,
|
||||
kcu.column_name,
|
||||
ccu.table_name AS foreign_table_name,
|
||||
ccu.column_name AS foreign_column_name,
|
||||
rc.delete_rule
|
||||
FROM information_schema.table_constraints AS tc
|
||||
JOIN information_schema.key_column_usage AS kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
JOIN information_schema.constraint_column_usage AS ccu
|
||||
ON ccu.constraint_name = tc.constraint_name
|
||||
AND ccu.table_schema = tc.table_schema
|
||||
JOIN information_schema.referential_constraints AS rc
|
||||
ON tc.constraint_name = rc.constraint_name
|
||||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||
AND tc.table_schema = 'public'
|
||||
ORDER BY tc.table_name, kcu.column_name;
|
||||
|
||||
-- Expected output:
|
||||
-- intake_notes | client_id | clients | id | CASCADE
|
||||
-- problem_profiles | client_id | clients | id | CASCADE
|
||||
-- problem_profiles | source_note_id | intake_notes | id | SET NULL
|
||||
-- treatment_plans | client_id | clients | id | CASCADE
|
||||
-- ai_events | client_id | clients | id | SET NULL
|
||||
-- ai_events | note_id | intake_notes | id | SET NULL
|
||||
|
||||
-- ================================================
|
||||
-- PRODUCTION MIGRATION PATH
|
||||
-- ================================================
|
||||
-- When moving to production, enhance policies with org_id filtering:
|
||||
--
|
||||
-- 1. Add org_id column to all tables:
|
||||
-- ALTER TABLE clients ADD COLUMN org_id UUID REFERENCES organizations(id);
|
||||
--
|
||||
-- 2. Update policies to filter by organization:
|
||||
-- CREATE POLICY "Users can view own org clients"
|
||||
-- ON clients
|
||||
-- FOR SELECT
|
||||
-- USING (
|
||||
-- auth.uid() IS NOT NULL AND
|
||||
-- org_id = (SELECT org_id FROM users WHERE id = auth.uid())
|
||||
-- );
|
||||
--
|
||||
-- 3. Add role-based access:
|
||||
-- CREATE POLICY "Admins can view all"
|
||||
-- ON clients
|
||||
-- FOR SELECT
|
||||
-- USING (
|
||||
-- auth.uid() IS NOT NULL AND
|
||||
-- EXISTS (
|
||||
-- SELECT 1 FROM users
|
||||
-- WHERE id = auth.uid() AND role = 'admin'
|
||||
-- )
|
||||
-- );
|
||||
|
||||
-- ================================================
|
||||
-- SECURITY NOTES
|
||||
-- ================================================
|
||||
-- 1. Current policies are MVP-level: all authenticated users can access all data
|
||||
-- 2. In production, add org_id filtering for multi-tenancy
|
||||
-- 3. ai_events table is append-only for regular users (audit trail)
|
||||
-- 4. Service role can bypass RLS for admin operations
|
||||
-- 5. All policies use auth.uid() for security
|
||||
-- 6. Foreign key CASCADE ensures orphaned records are cleaned up
|
||||
266
docs/archive/migrations/20251122-current-db-scheme.sql
Normal file
266
docs/archive/migrations/20251122-current-db-scheme.sql
Normal file
@@ -0,0 +1,266 @@
|
||||
-- WARNING: This schema is for context only and is not meant to be run.
|
||||
-- Table order and constraints may not be valid for execution.
|
||||
|
||||
CREATE TABLE public.ai_events (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
kind text NOT NULL CHECK (kind = ANY (ARRAY['summarize'::text, 'readability'::text, 'extract'::text, 'plan'::text])),
|
||||
client_id uuid,
|
||||
note_id uuid,
|
||||
request jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
response jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
duration_ms integer NOT NULL DEFAULT 0 CHECK (duration_ms >= 0),
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT ai_events_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT ai_events_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id),
|
||||
CONSTRAINT ai_events_note_id_fkey FOREIGN KEY (note_id) REFERENCES public.intake_notes(id)
|
||||
);
|
||||
CREATE TABLE public.care_plans (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
status USER-DEFINED NOT NULL DEFAULT 'draft'::careplan_status,
|
||||
intent text NOT NULL DEFAULT 'plan'::text,
|
||||
category_code text DEFAULT 'ggz-behandelplan'::text,
|
||||
category_display text DEFAULT 'GGZ Behandelplan'::text,
|
||||
title text NOT NULL,
|
||||
description text,
|
||||
patient_id uuid NOT NULL,
|
||||
encounter_id uuid,
|
||||
period_start date,
|
||||
period_end date,
|
||||
created_date timestamp with time zone DEFAULT now(),
|
||||
author_id uuid,
|
||||
contributor_ids ARRAY,
|
||||
care_team_ids ARRAY,
|
||||
addresses_condition_ids ARRAY,
|
||||
goals jsonb DEFAULT '[]'::jsonb,
|
||||
activities jsonb DEFAULT '[]'::jsonb,
|
||||
note text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT care_plans_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT care_plans_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT care_plans_encounter_id_fkey FOREIGN KEY (encounter_id) REFERENCES public.encounters(id),
|
||||
CONSTRAINT care_plans_author_id_fkey FOREIGN KEY (author_id) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.clients (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
first_name text NOT NULL,
|
||||
last_name text NOT NULL,
|
||||
birth_date date NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT clients_pkey PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE public.conditions (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
clinical_status USER-DEFINED NOT NULL DEFAULT 'active'::condition_clinical_status,
|
||||
verification_status USER-DEFINED NOT NULL DEFAULT 'provisional'::condition_verification_status,
|
||||
category text NOT NULL DEFAULT 'encounter-diagnosis'::text,
|
||||
severity_code text,
|
||||
severity_display text,
|
||||
code_system text NOT NULL DEFAULT 'http://hl7.org/fhir/sid/icd-10'::text,
|
||||
code_code text NOT NULL,
|
||||
code_display text NOT NULL,
|
||||
body_site_code text,
|
||||
body_site_display text,
|
||||
patient_id uuid NOT NULL,
|
||||
encounter_id uuid,
|
||||
onset_datetime timestamp with time zone,
|
||||
onset_age integer,
|
||||
abatement_datetime timestamp with time zone,
|
||||
abatement_age integer,
|
||||
recorded_date timestamp with time zone NOT NULL DEFAULT now(),
|
||||
recorder_id uuid,
|
||||
asserter_id uuid,
|
||||
note text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT conditions_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT conditions_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT conditions_encounter_id_fkey FOREIGN KEY (encounter_id) REFERENCES public.encounters(id),
|
||||
CONSTRAINT conditions_recorder_id_fkey FOREIGN KEY (recorder_id) REFERENCES public.practitioners(id),
|
||||
CONSTRAINT conditions_asserter_id_fkey FOREIGN KEY (asserter_id) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.demo_users (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
user_id uuid UNIQUE,
|
||||
access_level text NOT NULL DEFAULT 'read_only'::text CHECK (access_level = ANY (ARRAY['read_only'::text, 'interactive'::text, 'presenter'::text])),
|
||||
expires_at timestamp with time zone DEFAULT (now() + '90 days'::interval),
|
||||
usage_count integer DEFAULT 0,
|
||||
last_login_at timestamp with time zone,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
notes text,
|
||||
CONSTRAINT demo_users_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT demo_users_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id)
|
||||
);
|
||||
CREATE TABLE public.encounters (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
status USER-DEFINED NOT NULL DEFAULT 'planned'::encounter_status,
|
||||
class_code text NOT NULL,
|
||||
class_display text NOT NULL,
|
||||
type_code text NOT NULL,
|
||||
type_display text NOT NULL,
|
||||
priority_code text,
|
||||
priority_display text,
|
||||
patient_id uuid NOT NULL,
|
||||
practitioner_id uuid,
|
||||
organization_id uuid,
|
||||
period_start timestamp with time zone NOT NULL,
|
||||
period_end timestamp with time zone,
|
||||
reason_code ARRAY,
|
||||
reason_display ARRAY,
|
||||
admission_source text,
|
||||
discharge_disposition text,
|
||||
notes text,
|
||||
intake_note_id uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT encounters_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT encounters_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT encounters_practitioner_id_fkey FOREIGN KEY (practitioner_id) REFERENCES public.practitioners(id),
|
||||
CONSTRAINT encounters_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id),
|
||||
CONSTRAINT encounters_intake_note_id_fkey FOREIGN KEY (intake_note_id) REFERENCES public.intake_notes(id)
|
||||
);
|
||||
CREATE TABLE public.intake_notes (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
client_id uuid NOT NULL,
|
||||
title text,
|
||||
tag text CHECK (tag = ANY (ARRAY['Intake'::text, 'Evaluatie'::text, 'Plan'::text])),
|
||||
content_json jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (content_json IS NOT NULL),
|
||||
content_text text,
|
||||
author uuid,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT intake_notes_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT intake_notes_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id)
|
||||
);
|
||||
CREATE TABLE public.observations (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
status USER-DEFINED NOT NULL DEFAULT 'final'::observation_status,
|
||||
category text NOT NULL,
|
||||
code_system text NOT NULL,
|
||||
code_code text NOT NULL,
|
||||
code_display text NOT NULL,
|
||||
patient_id uuid NOT NULL,
|
||||
encounter_id uuid,
|
||||
effective_datetime timestamp with time zone NOT NULL,
|
||||
issued timestamp with time zone DEFAULT now(),
|
||||
performer_id uuid,
|
||||
value_type text NOT NULL,
|
||||
value_quantity_value numeric,
|
||||
value_quantity_unit text,
|
||||
value_quantity_comparator text,
|
||||
value_string text,
|
||||
value_boolean boolean,
|
||||
value_codeable_concept jsonb,
|
||||
interpretation_code text,
|
||||
interpretation_display text,
|
||||
note text,
|
||||
body_site text,
|
||||
method_code text,
|
||||
method_display text,
|
||||
reference_range_low numeric,
|
||||
reference_range_high numeric,
|
||||
reference_range_text text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT observations_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT observations_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT observations_encounter_id_fkey FOREIGN KEY (encounter_id) REFERENCES public.encounters(id),
|
||||
CONSTRAINT observations_performer_id_fkey FOREIGN KEY (performer_id) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.organizations (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier_agb text UNIQUE,
|
||||
identifier_kvk text,
|
||||
name text NOT NULL,
|
||||
alias ARRAY,
|
||||
type_code text DEFAULT 'prov'::text,
|
||||
type_display text DEFAULT 'Healthcare Provider'::text,
|
||||
telecom_phone text,
|
||||
telecom_email text,
|
||||
telecom_website text,
|
||||
address_line ARRAY,
|
||||
address_city text,
|
||||
address_postal_code text,
|
||||
address_country text DEFAULT 'NL'::text,
|
||||
active boolean DEFAULT true,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT organizations_pkey PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE public.patients (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier_bsn text DEFAULT '999999990'::text,
|
||||
identifier_client_number text,
|
||||
name_family text NOT NULL,
|
||||
name_given ARRAY NOT NULL,
|
||||
name_prefix text,
|
||||
name_use text DEFAULT 'official'::text,
|
||||
birth_date date NOT NULL,
|
||||
gender USER-DEFINED NOT NULL,
|
||||
telecom_phone text,
|
||||
telecom_email text,
|
||||
address_line ARRAY,
|
||||
address_city text,
|
||||
address_postal_code text,
|
||||
address_country text DEFAULT 'NL'::text,
|
||||
insurance_company text,
|
||||
insurance_number text,
|
||||
emergency_contact_name text,
|
||||
emergency_contact_relationship text,
|
||||
emergency_contact_phone text,
|
||||
active boolean DEFAULT true,
|
||||
general_practitioner_name text,
|
||||
general_practitioner_agb text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT patients_pkey PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE public.practitioners (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier_big text UNIQUE,
|
||||
identifier_agb text,
|
||||
name_prefix text,
|
||||
name_given ARRAY NOT NULL,
|
||||
name_family text NOT NULL,
|
||||
name_suffix text,
|
||||
qualification ARRAY,
|
||||
telecom_phone text,
|
||||
telecom_email text,
|
||||
active boolean DEFAULT true,
|
||||
user_id uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT practitioners_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT practitioners_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id)
|
||||
);
|
||||
CREATE TABLE public.problem_profiles (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
client_id uuid NOT NULL,
|
||||
category text NOT NULL CHECK (category = ANY (ARRAY['stemming_depressie'::text, 'angst'::text, 'gedrag_impuls'::text, 'middelen_gebruik'::text, 'cognitief'::text, 'context_psychosociaal'::text])),
|
||||
severity text NOT NULL CHECK (severity = ANY (ARRAY['laag'::text, 'middel'::text, 'hoog'::text])),
|
||||
remarks text,
|
||||
source_note_id uuid,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT problem_profiles_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT problem_profiles_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id),
|
||||
CONSTRAINT problem_profiles_source_note_id_fkey FOREIGN KEY (source_note_id) REFERENCES public.intake_notes(id)
|
||||
);
|
||||
CREATE TABLE public.treatment_plans (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
client_id uuid NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1 CHECK (version > 0),
|
||||
status text NOT NULL DEFAULT 'concept'::text CHECK (status = ANY (ARRAY['concept'::text, 'gepubliceerd'::text])),
|
||||
plan jsonb NOT NULL DEFAULT '{"doelen": [], "frequentie": "", "interventies": [], "meetmomenten": []}'::jsonb CHECK (plan ? 'doelen'::text AND plan ? 'interventies'::text AND plan ? 'frequentie'::text AND plan ? 'meetmomenten'::text),
|
||||
created_by uuid,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
published_at timestamp with time zone,
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT treatment_plans_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT treatment_plans_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id)
|
||||
);
|
||||
382
docs/archive/migrations/20251122-supabase-scheme.sql
Normal file
382
docs/archive/migrations/20251122-supabase-scheme.sql
Normal file
@@ -0,0 +1,382 @@
|
||||
-- WARNING: This schema is for context only and is not meant to be run.
|
||||
-- Table order and constraints may not be valid for execution.
|
||||
|
||||
CREATE TABLE public.ai_events (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
kind text NOT NULL CHECK (kind = ANY (ARRAY['summarize'::text, 'readability'::text, 'extract'::text, 'plan'::text])),
|
||||
client_id uuid,
|
||||
note_id uuid,
|
||||
request jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
response jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
duration_ms integer NOT NULL DEFAULT 0 CHECK (duration_ms >= 0),
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT ai_events_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT ai_events_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id),
|
||||
CONSTRAINT ai_events_note_id_fkey FOREIGN KEY (note_id) REFERENCES public.intake_notes(id)
|
||||
);
|
||||
CREATE TABLE public.anamneses (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
intake_id uuid NOT NULL,
|
||||
anamnese_date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
anamnese_type text NOT NULL CHECK (anamnese_type = ANY (ARRAY['psychiatrisch'::text, 'sociaal'::text, 'medisch'::text, 'familie'::text, 'ontwikkeling'::text, 'overig'::text])),
|
||||
content text NOT NULL,
|
||||
notes text,
|
||||
created_by uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT anamneses_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT anamneses_intake_id_fkey FOREIGN KEY (intake_id) REFERENCES public.intakes(id),
|
||||
CONSTRAINT anamneses_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.care_plans (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
status USER-DEFINED NOT NULL DEFAULT 'draft'::careplan_status,
|
||||
intent text NOT NULL DEFAULT 'plan'::text,
|
||||
category_code text DEFAULT 'ggz-behandelplan'::text,
|
||||
category_display text DEFAULT 'GGZ Behandelplan'::text,
|
||||
title text NOT NULL,
|
||||
description text,
|
||||
patient_id uuid NOT NULL,
|
||||
encounter_id uuid,
|
||||
period_start date,
|
||||
period_end date,
|
||||
created_date timestamp with time zone DEFAULT now(),
|
||||
author_id uuid,
|
||||
contributor_ids ARRAY,
|
||||
care_team_ids ARRAY,
|
||||
addresses_condition_ids ARRAY,
|
||||
goals jsonb DEFAULT '[]'::jsonb,
|
||||
activities jsonb DEFAULT '[]'::jsonb,
|
||||
note text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
based_on_intake_id uuid,
|
||||
based_on_anamneses ARRAY,
|
||||
based_on_examinations ARRAY,
|
||||
based_on_risk_assessments ARRAY,
|
||||
CONSTRAINT care_plans_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT care_plans_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT care_plans_encounter_id_fkey FOREIGN KEY (encounter_id) REFERENCES public.encounters(id),
|
||||
CONSTRAINT care_plans_author_id_fkey FOREIGN KEY (author_id) REFERENCES public.practitioners(id),
|
||||
CONSTRAINT care_plans_based_on_intake_id_fkey FOREIGN KEY (based_on_intake_id) REFERENCES public.intakes(id)
|
||||
);
|
||||
CREATE TABLE public.clients (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
first_name text NOT NULL,
|
||||
last_name text NOT NULL,
|
||||
birth_date date NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT clients_pkey PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE public.conditions (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
clinical_status USER-DEFINED NOT NULL DEFAULT 'active'::condition_clinical_status,
|
||||
verification_status USER-DEFINED NOT NULL DEFAULT 'provisional'::condition_verification_status,
|
||||
category text NOT NULL DEFAULT 'encounter-diagnosis'::text,
|
||||
severity_code text,
|
||||
severity_display text,
|
||||
code_system text NOT NULL DEFAULT 'http://hl7.org/fhir/sid/icd-10'::text,
|
||||
code_code text NOT NULL,
|
||||
code_display text NOT NULL,
|
||||
body_site_code text,
|
||||
body_site_display text,
|
||||
patient_id uuid NOT NULL,
|
||||
encounter_id uuid,
|
||||
onset_datetime timestamp with time zone,
|
||||
onset_age integer,
|
||||
abatement_datetime timestamp with time zone,
|
||||
abatement_age integer,
|
||||
recorded_date timestamp with time zone NOT NULL DEFAULT now(),
|
||||
recorder_id uuid,
|
||||
asserter_id uuid,
|
||||
note text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT conditions_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT conditions_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT conditions_encounter_id_fkey FOREIGN KEY (encounter_id) REFERENCES public.encounters(id),
|
||||
CONSTRAINT conditions_recorder_id_fkey FOREIGN KEY (recorder_id) REFERENCES public.practitioners(id),
|
||||
CONSTRAINT conditions_asserter_id_fkey FOREIGN KEY (asserter_id) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.demo_users (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
user_id uuid UNIQUE,
|
||||
access_level text NOT NULL DEFAULT 'read_only'::text CHECK (access_level = ANY (ARRAY['read_only'::text, 'interactive'::text, 'presenter'::text])),
|
||||
expires_at timestamp with time zone DEFAULT (now() + '90 days'::interval),
|
||||
usage_count integer DEFAULT 0,
|
||||
last_login_at timestamp with time zone,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
notes text,
|
||||
CONSTRAINT demo_users_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT demo_users_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id)
|
||||
);
|
||||
CREATE TABLE public.encounters (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
status USER-DEFINED NOT NULL DEFAULT 'planned'::encounter_status,
|
||||
class_code text NOT NULL,
|
||||
class_display text NOT NULL,
|
||||
type_code text NOT NULL,
|
||||
type_display text NOT NULL,
|
||||
priority_code text,
|
||||
priority_display text,
|
||||
patient_id uuid NOT NULL,
|
||||
practitioner_id uuid,
|
||||
organization_id uuid,
|
||||
period_start timestamp with time zone NOT NULL,
|
||||
period_end timestamp with time zone,
|
||||
reason_code ARRAY,
|
||||
reason_display ARRAY,
|
||||
admission_source text,
|
||||
discharge_disposition text,
|
||||
notes text,
|
||||
intake_note_id uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
intake_id uuid,
|
||||
CONSTRAINT encounters_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT encounters_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT encounters_practitioner_id_fkey FOREIGN KEY (practitioner_id) REFERENCES public.practitioners(id),
|
||||
CONSTRAINT encounters_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id),
|
||||
CONSTRAINT encounters_intake_note_id_fkey FOREIGN KEY (intake_note_id) REFERENCES public.intake_notes(id),
|
||||
CONSTRAINT encounters_intake_id_fkey FOREIGN KEY (intake_id) REFERENCES public.intakes(id)
|
||||
);
|
||||
CREATE TABLE public.examinations (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
intake_id uuid NOT NULL,
|
||||
examination_date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
examination_type text NOT NULL CHECK (examination_type = ANY (ARRAY['bloedonderzoek'::text, 'neuropsychologisch'::text, 'psychodiagnostiek'::text, 'iq_test'::text, 'persoonlijkheid'::text, 'medisch'::text, 'overig'::text])),
|
||||
performed_by text,
|
||||
reason text,
|
||||
findings text NOT NULL,
|
||||
document_url text,
|
||||
notes text,
|
||||
created_by uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT examinations_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT examinations_intake_id_fkey FOREIGN KEY (intake_id) REFERENCES public.intakes(id),
|
||||
CONSTRAINT examinations_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.intake_notes (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
client_id uuid NOT NULL,
|
||||
title text,
|
||||
tag text CHECK (tag = ANY (ARRAY['Intake'::text, 'Evaluatie'::text, 'Plan'::text])),
|
||||
content_json jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (content_json IS NOT NULL),
|
||||
content_text text,
|
||||
author uuid,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT intake_notes_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT intake_notes_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id)
|
||||
);
|
||||
CREATE TABLE public.intakes (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
patient_id uuid NOT NULL,
|
||||
title text NOT NULL,
|
||||
department text NOT NULL,
|
||||
psychologist_id uuid,
|
||||
status text NOT NULL DEFAULT 'bezig'::text CHECK (status = ANY (ARRAY['bezig'::text, 'afgerond'::text])),
|
||||
start_date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
end_date date,
|
||||
notes text,
|
||||
kindcheck_data jsonb DEFAULT '{}'::jsonb,
|
||||
treatment_advice jsonb DEFAULT '{}'::jsonb,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT intakes_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT intakes_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT intakes_psychologist_id_fkey FOREIGN KEY (psychologist_id) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.observations (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier text DEFAULT (gen_random_uuid())::text UNIQUE,
|
||||
status USER-DEFINED NOT NULL DEFAULT 'final'::observation_status,
|
||||
category text NOT NULL,
|
||||
code_system text NOT NULL,
|
||||
code_code text NOT NULL,
|
||||
code_display text NOT NULL,
|
||||
patient_id uuid NOT NULL,
|
||||
encounter_id uuid,
|
||||
effective_datetime timestamp with time zone NOT NULL,
|
||||
issued timestamp with time zone DEFAULT now(),
|
||||
performer_id uuid,
|
||||
value_type text NOT NULL,
|
||||
value_quantity_value numeric,
|
||||
value_quantity_unit text,
|
||||
value_quantity_comparator text,
|
||||
value_string text,
|
||||
value_boolean boolean,
|
||||
value_codeable_concept jsonb,
|
||||
interpretation_code text,
|
||||
interpretation_display text,
|
||||
note text,
|
||||
body_site text,
|
||||
method_code text,
|
||||
method_display text,
|
||||
reference_range_low numeric,
|
||||
reference_range_high numeric,
|
||||
reference_range_text text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT observations_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT observations_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT observations_encounter_id_fkey FOREIGN KEY (encounter_id) REFERENCES public.encounters(id),
|
||||
CONSTRAINT observations_performer_id_fkey FOREIGN KEY (performer_id) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.organizations (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier_agb text UNIQUE,
|
||||
identifier_kvk text,
|
||||
name text NOT NULL,
|
||||
alias ARRAY,
|
||||
type_code text DEFAULT 'prov'::text,
|
||||
type_display text DEFAULT 'Healthcare Provider'::text,
|
||||
telecom_phone text,
|
||||
telecom_email text,
|
||||
telecom_website text,
|
||||
address_line ARRAY,
|
||||
address_city text,
|
||||
address_postal_code text,
|
||||
address_country text DEFAULT 'NL'::text,
|
||||
active boolean DEFAULT true,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT organizations_pkey PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE public.patients (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier_bsn text DEFAULT '999999990'::text,
|
||||
identifier_client_number text,
|
||||
name_family text NOT NULL,
|
||||
name_given ARRAY NOT NULL,
|
||||
name_prefix text,
|
||||
name_use text DEFAULT 'official'::text,
|
||||
birth_date date NOT NULL,
|
||||
gender USER-DEFINED NOT NULL,
|
||||
telecom_phone text,
|
||||
telecom_email text,
|
||||
address_line ARRAY,
|
||||
address_city text,
|
||||
address_postal_code text,
|
||||
address_country text DEFAULT 'NL'::text,
|
||||
insurance_company text,
|
||||
insurance_number text,
|
||||
emergency_contact_name text,
|
||||
emergency_contact_relationship text,
|
||||
emergency_contact_phone text,
|
||||
active boolean DEFAULT true,
|
||||
general_practitioner_name text,
|
||||
general_practitioner_agb text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
status USER-DEFINED DEFAULT 'planned'::episode_status,
|
||||
is_john_doe boolean DEFAULT false,
|
||||
CONSTRAINT patients_pkey PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE public.practitioners (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
identifier_big text UNIQUE,
|
||||
identifier_agb text,
|
||||
name_prefix text,
|
||||
name_given ARRAY NOT NULL,
|
||||
name_family text NOT NULL,
|
||||
name_suffix text,
|
||||
qualification ARRAY,
|
||||
telecom_phone text,
|
||||
telecom_email text,
|
||||
active boolean DEFAULT true,
|
||||
user_id uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT practitioners_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT practitioners_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id)
|
||||
);
|
||||
CREATE TABLE public.problem_profiles (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
client_id uuid NOT NULL,
|
||||
category text NOT NULL CHECK (category = ANY (ARRAY['stemming_depressie'::text, 'angst'::text, 'gedrag_impuls'::text, 'middelen_gebruik'::text, 'cognitief'::text, 'context_psychosociaal'::text])),
|
||||
severity text NOT NULL CHECK (severity = ANY (ARRAY['laag'::text, 'middel'::text, 'hoog'::text])),
|
||||
remarks text,
|
||||
source_note_id uuid,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT problem_profiles_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT problem_profiles_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id),
|
||||
CONSTRAINT problem_profiles_source_note_id_fkey FOREIGN KEY (source_note_id) REFERENCES public.intake_notes(id)
|
||||
);
|
||||
CREATE TABLE public.risk_assessments (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
intake_id uuid NOT NULL,
|
||||
assessment_date date NOT NULL DEFAULT CURRENT_DATE,
|
||||
risk_type text NOT NULL CHECK (risk_type = ANY (ARRAY['suicidaliteit'::text, 'agressie'::text, 'zelfverwaarlozing'::text, 'middelenmisbruik'::text, 'verward_gedrag'::text, 'overig'::text])),
|
||||
risk_level text NOT NULL CHECK (risk_level = ANY (ARRAY['laag'::text, 'gemiddeld'::text, 'hoog'::text, 'zeer_hoog'::text])),
|
||||
rationale text NOT NULL,
|
||||
measures text,
|
||||
evaluation_date date,
|
||||
notes text,
|
||||
created_by uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT risk_assessments_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT risk_assessments_intake_id_fkey FOREIGN KEY (intake_id) REFERENCES public.intakes(id),
|
||||
CONSTRAINT risk_assessments_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.screening_activities (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
screening_id uuid NOT NULL,
|
||||
activity_text text NOT NULL,
|
||||
created_by uuid,
|
||||
created_by_name text,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT screening_activities_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT screening_activities_screening_id_fkey FOREIGN KEY (screening_id) REFERENCES public.screenings(id),
|
||||
CONSTRAINT screening_activities_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.screening_documents (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
screening_id uuid NOT NULL,
|
||||
file_name text NOT NULL,
|
||||
file_type text,
|
||||
file_size integer,
|
||||
file_path text,
|
||||
document_type text CHECK (document_type = ANY (ARRAY['verwijsbrief'::text, 'verhuisbericht'::text, 'indicatie'::text, 'overig'::text])),
|
||||
uploaded_by uuid,
|
||||
uploaded_by_name text,
|
||||
uploaded_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT screening_documents_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT screening_documents_screening_id_fkey FOREIGN KEY (screening_id) REFERENCES public.screenings(id),
|
||||
CONSTRAINT screening_documents_uploaded_by_fkey FOREIGN KEY (uploaded_by) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.screenings (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
patient_id uuid NOT NULL,
|
||||
request_for_help text,
|
||||
decision text CHECK (decision = ANY (ARRAY['geschikt'::text, 'niet_geschikt'::text])),
|
||||
decision_department text,
|
||||
decision_notes text,
|
||||
decision_date timestamp with time zone,
|
||||
decision_by uuid,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now(),
|
||||
CONSTRAINT screenings_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT screenings_patient_id_fkey FOREIGN KEY (patient_id) REFERENCES public.patients(id),
|
||||
CONSTRAINT screenings_decision_by_fkey FOREIGN KEY (decision_by) REFERENCES public.practitioners(id)
|
||||
);
|
||||
CREATE TABLE public.treatment_plans (
|
||||
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
||||
client_id uuid NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1 CHECK (version > 0),
|
||||
status text NOT NULL DEFAULT 'concept'::text CHECK (status = ANY (ARRAY['concept'::text, 'gepubliceerd'::text])),
|
||||
plan jsonb NOT NULL DEFAULT '{"doelen": [], "frequentie": "", "interventies": [], "meetmomenten": []}'::jsonb CHECK (plan ? 'doelen'::text AND plan ? 'interventies'::text AND plan ? 'frequentie'::text AND plan ? 'meetmomenten'::text),
|
||||
created_by uuid,
|
||||
created_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
published_at timestamp with time zone,
|
||||
updated_at timestamp with time zone NOT NULL DEFAULT now(),
|
||||
CONSTRAINT treatment_plans_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT treatment_plans_client_id_fkey FOREIGN KEY (client_id) REFERENCES public.clients(id)
|
||||
);
|
||||
29
docs/archive/migrations/README.md
Normal file
29
docs/archive/migrations/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Archived Migrations & Schema Snapshots
|
||||
|
||||
Deze directory bevat migrations en schema snapshots die niet meer actief gebruikt worden als migrations, maar behouden blijven voor referentie.
|
||||
|
||||
## Schema Snapshots
|
||||
|
||||
Deze bestanden zijn **niet bedoeld om uit te voeren** als migrations, maar dienen als documentatie van het huidige database schema:
|
||||
|
||||
- **`20251122-current-db-scheme.sql`** - Snapshot van database schema (2025-11-22)
|
||||
- **`20251122-supabase-scheme.sql`** - Actueel database schema snapshot (2025-11-22)
|
||||
|
||||
⚠️ **Waarschuwing:** Deze bestanden bevatten een WARNING dat ze niet uitgevoerd moeten worden. Ze zijn alleen voor documentatie/referentie doeleinden.
|
||||
|
||||
## Test Files
|
||||
|
||||
- **`20241115000003_test_rls_policies.sql`** - Test queries voor RLS policies verificatie (niet een echte migration)
|
||||
|
||||
## Waarom gearchiveerd?
|
||||
|
||||
Deze bestanden zijn gearchiveerd omdat:
|
||||
1. Ze geen echte migrations zijn (snapshots/test files)
|
||||
2. Ze niet uitgevoerd moeten worden door Supabase CLI
|
||||
3. Ze alleen voor documentatie/referentie dienen
|
||||
4. Ze de migrations directory vervuilen
|
||||
|
||||
## Actieve Migrations
|
||||
|
||||
Actieve migrations staan in `/supabase/migrations/` en worden uitgevoerd door Supabase CLI.
|
||||
|
||||
1058
docs/archive/schemas/20241121_fhir_ggz_schema.sql
Normal file
1058
docs/archive/schemas/20241121_fhir_ggz_schema.sql
Normal file
File diff suppressed because it is too large
Load Diff
704
docs/archive/schemas/20241121_pragmatic_fhir_schema.sql
Normal file
704
docs/archive/schemas/20241121_pragmatic_fhir_schema.sql
Normal file
@@ -0,0 +1,704 @@
|
||||
-- ============================================================================
|
||||
-- PRAGMATIC FHIR GGZ EPD SCHEMA
|
||||
-- ============================================================================
|
||||
-- Created: 2024-11-21
|
||||
-- Version: Pragmatic v2.0
|
||||
-- Description: Simplified FHIR schema for prototype focused on data interoperability
|
||||
--
|
||||
-- Differences from full schema (20241121_fhir_ggz_schema.sql):
|
||||
-- - Only 7 tables (6 FHIR resources + organizations)
|
||||
-- - Goals embedded in care_plans.goals JSONB (not separate table)
|
||||
-- - Activities embedded in care_plans.activities JSONB (not separate table)
|
||||
-- - No medications, consents, flags, documents tables
|
||||
-- - BSN placeholders (no encryption for demo)
|
||||
-- - Keeps existing tables: clients, intake_notes, treatment_plans, ai_events
|
||||
-- ============================================================================
|
||||
|
||||
-- ============================================================================
|
||||
-- ENABLE EXTENSIONS
|
||||
-- ============================================================================
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Note: pgcrypto not needed for pragmatic version (no BSN encryption)
|
||||
|
||||
-- ============================================================================
|
||||
-- ENUM TYPES (for type safety)
|
||||
-- ============================================================================
|
||||
|
||||
-- FHIR Gender
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE gender_type AS ENUM ('male', 'female', 'other', 'unknown');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- FHIR Encounter Status
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE encounter_status AS ENUM (
|
||||
'planned', 'in-progress', 'on-hold', 'completed',
|
||||
'cancelled', 'entered-in-error', 'unknown'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- FHIR Condition Clinical Status
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE condition_clinical_status AS ENUM (
|
||||
'active', 'recurrence', 'relapse', 'inactive',
|
||||
'remission', 'resolved', 'unknown'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- FHIR Condition Verification Status
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE condition_verification_status AS ENUM (
|
||||
'unconfirmed', 'provisional', 'differential',
|
||||
'confirmed', 'refuted', 'entered-in-error'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- FHIR Observation Status
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE observation_status AS ENUM (
|
||||
'registered', 'preliminary', 'final', 'amended',
|
||||
'corrected', 'cancelled', 'entered-in-error', 'unknown'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- FHIR CarePlan Status
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE careplan_status AS ENUM (
|
||||
'draft', 'active', 'on-hold', 'revoked',
|
||||
'completed', 'entered-in-error', 'unknown'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: practitioners (FHIR: Practitioner)
|
||||
-- Behandelaren/professionals
|
||||
-- ============================================================================
|
||||
CREATE TABLE IF NOT EXISTS practitioners (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- FHIR Practitioner fields
|
||||
identifier_big TEXT UNIQUE, -- BIG-nummer (optioneel)
|
||||
identifier_agb TEXT, -- AGB-code
|
||||
|
||||
-- Name (HumanName)
|
||||
name_prefix TEXT, -- "Drs.", "Dr."
|
||||
name_given TEXT[] NOT NULL, -- Voornamen
|
||||
name_family TEXT NOT NULL, -- Achternaam
|
||||
name_suffix TEXT, -- "PhD", "MSc"
|
||||
|
||||
-- Qualification
|
||||
qualification TEXT[], -- ["GZ-psycholoog", "Psychotherapeut"]
|
||||
|
||||
-- Contact
|
||||
telecom_phone TEXT,
|
||||
telecom_email TEXT,
|
||||
|
||||
-- Active
|
||||
active BOOLEAN DEFAULT true,
|
||||
|
||||
-- Link to auth user
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE practitioners IS 'FHIR: Practitioner - Behandelaren en zorgprofessionals';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: organizations (FHIR: Organization)
|
||||
-- GGZ-instellingen
|
||||
-- ============================================================================
|
||||
CREATE TABLE IF NOT EXISTS organizations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- FHIR Organization fields
|
||||
identifier_agb TEXT UNIQUE, -- AGB-code instelling
|
||||
identifier_kvk TEXT, -- KVK-nummer
|
||||
|
||||
-- Name
|
||||
name TEXT NOT NULL,
|
||||
alias TEXT[], -- Alternative names
|
||||
|
||||
-- Type
|
||||
type_code TEXT DEFAULT 'prov', -- healthcare provider
|
||||
type_display TEXT DEFAULT 'Healthcare Provider',
|
||||
|
||||
-- Contact
|
||||
telecom_phone TEXT,
|
||||
telecom_email TEXT,
|
||||
telecom_website TEXT,
|
||||
|
||||
-- Address
|
||||
address_line TEXT[],
|
||||
address_city TEXT,
|
||||
address_postal_code TEXT,
|
||||
address_country TEXT DEFAULT 'NL',
|
||||
|
||||
-- Active
|
||||
active BOOLEAN DEFAULT true,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE organizations IS 'FHIR: Organization - GGZ-instellingen';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: patients (FHIR: Patient / ZIB: Patient)
|
||||
-- Cliënten/patiënten
|
||||
-- ============================================================================
|
||||
CREATE TABLE IF NOT EXISTS patients (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- FHIR Patient.identifier
|
||||
-- PRAGMATIC: No encryption, use placeholder BSN for demo
|
||||
identifier_bsn TEXT DEFAULT '999999990', -- Placeholder BSN
|
||||
identifier_client_number TEXT, -- Interne cliëntnummer
|
||||
|
||||
-- FHIR Patient.name (HumanName)
|
||||
name_family TEXT NOT NULL, -- Achternaam
|
||||
name_given TEXT[] NOT NULL, -- Voornamen array
|
||||
name_prefix TEXT, -- Voorvoegsel (van, de, etc)
|
||||
name_use TEXT DEFAULT 'official', -- official, maiden, nickname
|
||||
|
||||
-- FHIR Patient.birthDate
|
||||
birth_date DATE NOT NULL,
|
||||
|
||||
-- FHIR Patient.gender
|
||||
gender gender_type NOT NULL,
|
||||
|
||||
-- FHIR Patient.telecom (ContactPoint)
|
||||
telecom_phone TEXT,
|
||||
telecom_email TEXT,
|
||||
|
||||
-- FHIR Patient.address (Address)
|
||||
address_line TEXT[], -- Straat + huisnummer
|
||||
address_city TEXT,
|
||||
address_postal_code TEXT,
|
||||
address_country TEXT DEFAULT 'NL',
|
||||
|
||||
-- Insurance (ZIB: Payer)
|
||||
insurance_company TEXT, -- Zorgverzekeraar
|
||||
insurance_number TEXT, -- Polisnummer
|
||||
|
||||
-- FHIR Patient.contact (naasten)
|
||||
emergency_contact_name TEXT,
|
||||
emergency_contact_relationship TEXT,
|
||||
emergency_contact_phone TEXT,
|
||||
|
||||
-- FHIR Patient.active
|
||||
active BOOLEAN DEFAULT true,
|
||||
|
||||
-- FHIR Patient.generalPractitioner (huisarts)
|
||||
general_practitioner_name TEXT,
|
||||
general_practitioner_agb TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE patients IS 'FHIR: Patient / ZIB: Patient - Cliënten/patiënten (pragmatic version with placeholder BSN)';
|
||||
COMMENT ON COLUMN patients.identifier_bsn IS 'Placeholder BSN for demo (not encrypted in pragmatic version)';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: encounters (FHIR: Encounter / ZIB: Contact)
|
||||
-- Contactmomenten (intake, behandelsessie, etc)
|
||||
-- ============================================================================
|
||||
CREATE TABLE IF NOT EXISTS encounters (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- FHIR Encounter.identifier
|
||||
identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT,
|
||||
|
||||
-- FHIR Encounter.status
|
||||
status encounter_status NOT NULL DEFAULT 'planned',
|
||||
|
||||
-- FHIR Encounter.class
|
||||
class_code TEXT NOT NULL, -- AMB (ambulatory), IMP (inpatient), EMER (emergency), VR (virtual)
|
||||
class_display TEXT NOT NULL,
|
||||
|
||||
-- FHIR Encounter.type
|
||||
type_code TEXT NOT NULL, -- intake, diagnostiek, behandeling, follow-up, crisis
|
||||
type_display TEXT NOT NULL,
|
||||
|
||||
-- FHIR Encounter.priority
|
||||
priority_code TEXT, -- routine, urgent, emergency
|
||||
priority_display TEXT,
|
||||
|
||||
-- FHIR Encounter.subject (patient)
|
||||
patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL,
|
||||
|
||||
-- FHIR Encounter.participant (behandelaar)
|
||||
practitioner_id UUID REFERENCES practitioners(id),
|
||||
|
||||
-- FHIR Encounter.serviceProvider (instelling)
|
||||
organization_id UUID REFERENCES organizations(id),
|
||||
|
||||
-- FHIR Encounter.period
|
||||
period_start TIMESTAMPTZ NOT NULL,
|
||||
period_end TIMESTAMPTZ,
|
||||
|
||||
-- FHIR Encounter.reasonCode
|
||||
reason_code TEXT[], -- DSM-5 codes, SNOMED codes
|
||||
reason_display TEXT[], -- Human-readable reason
|
||||
|
||||
-- FHIR Encounter.hospitalization (indien opname)
|
||||
admission_source TEXT,
|
||||
discharge_disposition TEXT,
|
||||
|
||||
-- Free text notes
|
||||
notes TEXT,
|
||||
|
||||
-- Link to intake_notes (existing table)
|
||||
intake_note_id UUID REFERENCES intake_notes(id),
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE encounters IS 'FHIR: Encounter / ZIB: Contact - Contactmomenten (intake, behandeling, etc)';
|
||||
COMMENT ON COLUMN encounters.intake_note_id IS 'Link to existing intake_notes table for backwards compatibility';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: conditions (FHIR: Condition / ZIB: Problem)
|
||||
-- DSM-5 diagnoses en problemlijst
|
||||
-- ============================================================================
|
||||
CREATE TABLE IF NOT EXISTS conditions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- FHIR Condition.identifier
|
||||
identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT,
|
||||
|
||||
-- FHIR Condition.clinicalStatus
|
||||
clinical_status condition_clinical_status NOT NULL DEFAULT 'active',
|
||||
|
||||
-- FHIR Condition.verificationStatus
|
||||
verification_status condition_verification_status NOT NULL DEFAULT 'provisional',
|
||||
|
||||
-- FHIR Condition.category
|
||||
category TEXT NOT NULL DEFAULT 'encounter-diagnosis', -- of 'problem-list-item'
|
||||
|
||||
-- FHIR Condition.severity
|
||||
severity_code TEXT, -- mild, moderate, severe
|
||||
severity_display TEXT,
|
||||
|
||||
-- FHIR Condition.code (DSM-5 / ICD-10)
|
||||
code_system TEXT NOT NULL DEFAULT 'http://hl7.org/fhir/sid/icd-10',
|
||||
code_code TEXT NOT NULL, -- "F32.2", "F41.1"
|
||||
code_display TEXT NOT NULL, -- "Depressieve episode, ernstig"
|
||||
|
||||
-- FHIR Condition.bodySite (indien relevant)
|
||||
body_site_code TEXT,
|
||||
body_site_display TEXT,
|
||||
|
||||
-- FHIR Condition.subject (patient)
|
||||
patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL,
|
||||
|
||||
-- FHIR Condition.encounter (wanneer gesteld)
|
||||
encounter_id UUID REFERENCES encounters(id),
|
||||
|
||||
-- FHIR Condition.onsetDateTime / abatementDateTime
|
||||
onset_datetime TIMESTAMPTZ,
|
||||
onset_age INTEGER, -- Leeftijd bij ontstaan (optioneel)
|
||||
abatement_datetime TIMESTAMPTZ,
|
||||
abatement_age INTEGER,
|
||||
|
||||
-- FHIR Condition.recordedDate
|
||||
recorded_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- FHIR Condition.recorder (wie legde vast)
|
||||
recorder_id UUID REFERENCES practitioners(id),
|
||||
|
||||
-- FHIR Condition.asserter (wie stelde diagnose)
|
||||
asserter_id UUID REFERENCES practitioners(id),
|
||||
|
||||
-- FHIR Condition.note
|
||||
note TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE conditions IS 'FHIR: Condition / ZIB: Problem - DSM-5 diagnoses en problemlijst';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: observations (FHIR: Observation)
|
||||
-- ROM-scores, risico's, klachten, metingen
|
||||
-- ============================================================================
|
||||
CREATE TABLE IF NOT EXISTS observations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- FHIR Observation.identifier
|
||||
identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT,
|
||||
|
||||
-- FHIR Observation.status
|
||||
status observation_status NOT NULL DEFAULT 'final',
|
||||
|
||||
-- FHIR Observation.category
|
||||
category TEXT NOT NULL, -- vital-signs, social-history, exam, survey, therapy
|
||||
|
||||
-- FHIR Observation.code (wat werd geobserveerd)
|
||||
code_system TEXT NOT NULL, -- SNOMED, LOINC, custom
|
||||
code_code TEXT NOT NULL,
|
||||
code_display TEXT NOT NULL,
|
||||
|
||||
-- FHIR Observation.subject (patient)
|
||||
patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL,
|
||||
|
||||
-- FHIR Observation.encounter
|
||||
encounter_id UUID REFERENCES encounters(id),
|
||||
|
||||
-- FHIR Observation.effectiveDateTime
|
||||
effective_datetime TIMESTAMPTZ NOT NULL,
|
||||
|
||||
-- FHIR Observation.issued
|
||||
issued TIMESTAMPTZ DEFAULT NOW(),
|
||||
|
||||
-- FHIR Observation.performer (wie deed observatie)
|
||||
performer_id UUID REFERENCES practitioners(id),
|
||||
|
||||
-- FHIR Observation.value[x] (polymorf!)
|
||||
value_type TEXT NOT NULL, -- quantity, string, boolean, codeableConcept
|
||||
value_quantity_value NUMERIC,
|
||||
value_quantity_unit TEXT,
|
||||
value_quantity_comparator TEXT, -- <, <=, >=, >
|
||||
value_string TEXT,
|
||||
value_boolean BOOLEAN,
|
||||
value_codeable_concept JSONB, -- {system, code, display}
|
||||
|
||||
-- FHIR Observation.interpretation
|
||||
interpretation_code TEXT, -- H (high), L (low), N (normal)
|
||||
interpretation_display TEXT,
|
||||
|
||||
-- FHIR Observation.note
|
||||
note TEXT,
|
||||
|
||||
-- FHIR Observation.bodySite
|
||||
body_site TEXT,
|
||||
|
||||
-- FHIR Observation.method
|
||||
method_code TEXT,
|
||||
method_display TEXT,
|
||||
|
||||
-- Reference range (normaalwaarden)
|
||||
reference_range_low NUMERIC,
|
||||
reference_range_high NUMERIC,
|
||||
reference_range_text TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE observations IS 'FHIR: Observation - ROM-scores, risico-inschattingen, metingen';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: care_plans (FHIR: CarePlan)
|
||||
-- Behandelplannen met embedded goals en activities
|
||||
-- ============================================================================
|
||||
CREATE TABLE IF NOT EXISTS care_plans (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- FHIR CarePlan.identifier
|
||||
identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT,
|
||||
|
||||
-- FHIR CarePlan.status
|
||||
status careplan_status NOT NULL DEFAULT 'draft',
|
||||
|
||||
-- FHIR CarePlan.intent
|
||||
intent TEXT NOT NULL DEFAULT 'plan', -- proposal, plan, order, option
|
||||
|
||||
-- FHIR CarePlan.category
|
||||
category_code TEXT DEFAULT 'ggz-behandelplan',
|
||||
category_display TEXT DEFAULT 'GGZ Behandelplan',
|
||||
|
||||
-- FHIR CarePlan.title
|
||||
title TEXT NOT NULL,
|
||||
|
||||
-- FHIR CarePlan.description
|
||||
description TEXT,
|
||||
|
||||
-- FHIR CarePlan.subject (patient)
|
||||
patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL,
|
||||
|
||||
-- FHIR CarePlan.encounter (intake waar uit voortkomt)
|
||||
encounter_id UUID REFERENCES encounters(id),
|
||||
|
||||
-- FHIR CarePlan.period
|
||||
period_start DATE,
|
||||
period_end DATE,
|
||||
|
||||
-- FHIR CarePlan.created
|
||||
created_date TIMESTAMPTZ DEFAULT NOW(),
|
||||
|
||||
-- FHIR CarePlan.author (regiebehandelaar)
|
||||
author_id UUID REFERENCES practitioners(id),
|
||||
|
||||
-- FHIR CarePlan.contributor
|
||||
contributor_ids UUID[], -- Array van practitioner IDs
|
||||
|
||||
-- FHIR CarePlan.careTeam
|
||||
care_team_ids UUID[], -- Array van practitioner IDs
|
||||
|
||||
-- FHIR CarePlan.addresses (welke diagnoses)
|
||||
addresses_condition_ids UUID[], -- Array van condition IDs
|
||||
|
||||
-- ========================================================================
|
||||
-- PRAGMATIC APPROACH: EMBEDDED GOALS AND ACTIVITIES
|
||||
-- ========================================================================
|
||||
-- FHIR CarePlan.goal (behandeldoelen als JSONB array)
|
||||
goals JSONB DEFAULT '[]'::jsonb,
|
||||
-- Structure: [
|
||||
-- {
|
||||
-- "description": {"text": "PHQ-9 score < 10"},
|
||||
-- "target": [{
|
||||
-- "measure": {"coding": [{"system": "...", "code": "44249-1"}]},
|
||||
-- "detailQuantity": {"value": 10, "comparator": "<"},
|
||||
-- "dueDate": "2024-06-30"
|
||||
-- }]
|
||||
-- }
|
||||
-- ]
|
||||
|
||||
-- FHIR CarePlan.activity (behandelactiviteiten als JSONB array)
|
||||
activities JSONB DEFAULT '[]'::jsonb,
|
||||
-- Structure: [
|
||||
-- {
|
||||
-- "detail": {
|
||||
-- "code": {"text": "Cognitieve gedragstherapie"},
|
||||
-- "status": "in-progress",
|
||||
-- "scheduledTiming": {"repeat": {"frequency": 1, "period": 1, "periodUnit": "wk"}},
|
||||
-- "performer": ["practitioner-id"],
|
||||
-- "description": "Individuele CGT sessies, 12 weken",
|
||||
-- "location": "Polikliniek"
|
||||
-- }
|
||||
-- }
|
||||
-- ]
|
||||
-- ========================================================================
|
||||
|
||||
-- FHIR CarePlan.note
|
||||
note TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE care_plans IS 'FHIR: CarePlan - Behandelplannen met embedded goals en activities (pragmatic approach)';
|
||||
COMMENT ON COLUMN care_plans.goals IS 'FHIR Goal structures embedded as JSONB array (pragmatic: no separate table)';
|
||||
COMMENT ON COLUMN care_plans.activities IS 'FHIR CarePlan.activity structures embedded as JSONB array (pragmatic: no separate table)';
|
||||
|
||||
-- ============================================================================
|
||||
-- INDEXES (voor performance)
|
||||
-- ============================================================================
|
||||
|
||||
-- Practitioners
|
||||
CREATE INDEX IF NOT EXISTS idx_practitioners_user_id ON practitioners(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_practitioners_active ON practitioners(active);
|
||||
|
||||
-- Patients
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_bsn ON patients(identifier_bsn);
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_active ON patients(active);
|
||||
CREATE INDEX IF NOT EXISTS idx_patients_client_number ON patients(identifier_client_number);
|
||||
|
||||
-- Encounters
|
||||
CREATE INDEX IF NOT EXISTS idx_encounters_patient_id ON encounters(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_encounters_practitioner_id ON encounters(practitioner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_encounters_status ON encounters(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_encounters_period_start ON encounters(period_start DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_encounters_intake_note_id ON encounters(intake_note_id);
|
||||
|
||||
-- Conditions
|
||||
CREATE INDEX IF NOT EXISTS idx_conditions_patient_id ON conditions(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conditions_encounter_id ON conditions(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conditions_clinical_status ON conditions(clinical_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_conditions_code ON conditions(code_code);
|
||||
|
||||
-- Observations
|
||||
CREATE INDEX IF NOT EXISTS idx_observations_patient_id ON observations(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_observations_encounter_id ON observations(encounter_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_observations_category ON observations(category);
|
||||
CREATE INDEX IF NOT EXISTS idx_observations_effective_datetime ON observations(effective_datetime DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_observations_code ON observations(code_code);
|
||||
|
||||
-- Care Plans
|
||||
CREATE INDEX IF NOT EXISTS idx_care_plans_patient_id ON care_plans(patient_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_care_plans_status ON care_plans(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_care_plans_author_id ON care_plans(author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_care_plans_encounter_id ON care_plans(encounter_id);
|
||||
|
||||
-- GIN indexes for JSONB columns (for efficient querying)
|
||||
CREATE INDEX IF NOT EXISTS idx_care_plans_goals_gin ON care_plans USING GIN (goals);
|
||||
CREATE INDEX IF NOT EXISTS idx_care_plans_activities_gin ON care_plans USING GIN (activities);
|
||||
|
||||
-- ============================================================================
|
||||
-- ROW LEVEL SECURITY (RLS) - basis setup
|
||||
-- ============================================================================
|
||||
|
||||
-- Enable RLS on all tables
|
||||
ALTER TABLE practitioners ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE encounters ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE conditions ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE observations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE care_plans ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Voor MVP: authenticated users kunnen alles zien (later verfijnen)
|
||||
|
||||
-- Practitioners: can read/update their own record
|
||||
DROP POLICY IF EXISTS "Practitioners can view own record" ON practitioners;
|
||||
CREATE POLICY "Practitioners can view own record" ON practitioners
|
||||
FOR SELECT USING (user_id = auth.uid());
|
||||
|
||||
DROP POLICY IF EXISTS "Practitioners can update own record" ON practitioners;
|
||||
CREATE POLICY "Practitioners can update own record" ON practitioners
|
||||
FOR UPDATE USING (user_id = auth.uid());
|
||||
|
||||
-- Patients: authenticated users can view/create/update
|
||||
DROP POLICY IF EXISTS "Authenticated users can view patients" ON patients;
|
||||
CREATE POLICY "Authenticated users can view patients" ON patients
|
||||
FOR SELECT USING (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert patients" ON patients;
|
||||
CREATE POLICY "Authenticated users can insert patients" ON patients
|
||||
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can update patients" ON patients;
|
||||
CREATE POLICY "Authenticated users can update patients" ON patients
|
||||
FOR UPDATE USING (auth.role() = 'authenticated');
|
||||
|
||||
-- Encounters: authenticated users can view/create/update
|
||||
DROP POLICY IF EXISTS "Authenticated users can view encounters" ON encounters;
|
||||
CREATE POLICY "Authenticated users can view encounters" ON encounters
|
||||
FOR SELECT USING (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert encounters" ON encounters;
|
||||
CREATE POLICY "Authenticated users can insert encounters" ON encounters
|
||||
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can update encounters" ON encounters;
|
||||
CREATE POLICY "Authenticated users can update encounters" ON encounters
|
||||
FOR UPDATE USING (auth.role() = 'authenticated');
|
||||
|
||||
-- Conditions: authenticated users can view/create/update
|
||||
DROP POLICY IF EXISTS "Authenticated users can view conditions" ON conditions;
|
||||
CREATE POLICY "Authenticated users can view conditions" ON conditions
|
||||
FOR SELECT USING (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert conditions" ON conditions;
|
||||
CREATE POLICY "Authenticated users can insert conditions" ON conditions
|
||||
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can update conditions" ON conditions;
|
||||
CREATE POLICY "Authenticated users can update conditions" ON conditions
|
||||
FOR UPDATE USING (auth.role() = 'authenticated');
|
||||
|
||||
-- Observations: authenticated users can view/create
|
||||
DROP POLICY IF EXISTS "Authenticated users can view observations" ON observations;
|
||||
CREATE POLICY "Authenticated users can view observations" ON observations
|
||||
FOR SELECT USING (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert observations" ON observations;
|
||||
CREATE POLICY "Authenticated users can insert observations" ON observations
|
||||
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
|
||||
|
||||
-- Care Plans: authenticated users can view/create/update
|
||||
DROP POLICY IF EXISTS "Authenticated users can view care plans" ON care_plans;
|
||||
CREATE POLICY "Authenticated users can view care plans" ON care_plans
|
||||
FOR SELECT USING (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can insert care plans" ON care_plans;
|
||||
CREATE POLICY "Authenticated users can insert care plans" ON care_plans
|
||||
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
|
||||
|
||||
DROP POLICY IF EXISTS "Authenticated users can update care plans" ON care_plans;
|
||||
CREATE POLICY "Authenticated users can update care plans" ON care_plans
|
||||
FOR UPDATE USING (auth.role() = 'authenticated');
|
||||
|
||||
-- ============================================================================
|
||||
-- FUNCTIONS - updated_at trigger
|
||||
-- ============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Apply trigger to all tables with updated_at
|
||||
DROP TRIGGER IF EXISTS set_updated_at ON practitioners;
|
||||
CREATE TRIGGER set_updated_at BEFORE UPDATE ON practitioners
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS set_updated_at ON organizations;
|
||||
CREATE TRIGGER set_updated_at BEFORE UPDATE ON organizations
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS set_updated_at ON patients;
|
||||
CREATE TRIGGER set_updated_at BEFORE UPDATE ON patients
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS set_updated_at ON encounters;
|
||||
CREATE TRIGGER set_updated_at BEFORE UPDATE ON encounters
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS set_updated_at ON conditions;
|
||||
CREATE TRIGGER set_updated_at BEFORE UPDATE ON conditions
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
DROP TRIGGER IF EXISTS set_updated_at ON care_plans;
|
||||
CREATE TRIGGER set_updated_at BEFORE UPDATE ON care_plans
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- ============================================================================
|
||||
-- SEED DATA (demo/development)
|
||||
-- ============================================================================
|
||||
|
||||
-- Default organization
|
||||
INSERT INTO organizations (id, name, identifier_agb, active)
|
||||
VALUES (
|
||||
'00000000-0000-0000-0000-000000000001'::uuid,
|
||||
'Demo GGZ Instelling',
|
||||
'AGB-DEMO-001',
|
||||
true
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ============================================================================
|
||||
-- SUMMARY
|
||||
-- ============================================================================
|
||||
-- ✅ Created 7 tables: practitioners, organizations, patients, encounters, conditions, observations, care_plans
|
||||
-- ✅ Goals and activities embedded in care_plans JSONB (pragmatic approach)
|
||||
-- ✅ No medications, consents, flags, documents tables (out of scope)
|
||||
-- ✅ Simplified BSN (placeholder for demo)
|
||||
-- ✅ RLS enabled on all tables
|
||||
-- ✅ Indexes for performance
|
||||
-- ✅ Triggers for updated_at
|
||||
-- ✅ 1 default organization seeded
|
||||
--
|
||||
-- NEXT STEPS:
|
||||
-- 1. Run this migration in Supabase
|
||||
-- 2. Generate TypeScript types: supabase gen types
|
||||
-- 3. Create data migration script: clients → patients, treatment_plans → care_plans
|
||||
-- 4. Seed demo data: practitioners, patients, encounters
|
||||
-- ============================================================================
|
||||
Reference in New Issue
Block a user