Files
triqura-ecd/supabase/migrations/20260720000004_create_intake_treatment_advice_domain.sql
colinislit ea4c0c836f feat(db): technisch datamodel instroom/intake/behandeladvies (SQL-migraties)
Vertaalt de FCO-IM-feitenrondes en logische modellen naar 37 nieuwe
PostgreSQL-tabellen, verdeeld over vier migraties:

- create_reference_data: één generieke value_list_item-tabel (spelregel
  3.1 #1) i.p.v. ~30 losse waardelijst-tabellen, met alle startwaarden uit
  model-aanmelding.md/model-instroom.md/model-intake-behandeladvies.md.
- create_person_client_domain: person, address, contact_detail, client,
  client_relation, referrer, practice_organization,
  client_general_practitioner, insurance, consent, client_portal_account
  (model-aanmelding.md §2.1-2.9, 2.14, 2.16). BSN als bsn_hash (SHA-256),
  conform de platform-privacyregel — geen raw BSN, geen reversibele
  encryptie.
- create_referral_domain: 20 entiteiten uit model-instroom.md (referral_
  request t/m episode_team_involvement), inclusief de vervangt-constraints
  als partial unique index (voorkomt vertakking) en de XOR-check op
  clinical_care_episode tussen acceptatiebesluit en Wvggz-mandaat.
- create_intake_treatment_advice_domain: 5 entiteiten uit
  model-intake-behandeladvies.md.

Scope: dit zijn nieuwe, additieve tabellen naast de bestaande prototype-
tabellen (patients, screenings, intakes, patients.is_john_doe,
intakes.kindcheck_data) — die tabellen zijn bewust niet aangeraakt.
Wiring van de live app op dit schema, en migratie van prototype-data, is
een aparte, nog te nemen beslissing. Migraties zijn nog niet toegepast op
een database.

decision_authority_id (care_acceptance_decision) is tijdelijk nullable
zonder FK — het doeltabel volgt uit de bevoegdheidsronde (B16). Perifere
waardelijst-kolommen zijn TEXT met een verwijzing naar de bedoelde lijst in
een comment, geen harde FK naar value_list_item — applicatievalidatie (Zod)
dekt die laag; business-kritieke uitkomsten (decision_outcome,
treatment_advice_outcome, mandate_type) hebben wel een CHECK-constraint.
2026-07-20 08:57:12 +02:00

179 lines
11 KiB
SQL

-- ============================================================================
-- INTAKE / TREATMENT ADVICE DOMAIN
-- ============================================================================
-- Created: 2026-07-20
-- Source: docs/datamodel/deelmodellen/model-intake-behandeladvies.md (5
-- entiteiten), docs/datamodel/feitenmodellen/feitenmodel-intake-behandeladvies.md,
-- besluiten B31-B34.
--
-- clinical_intake_assessment hangs off clinical_care_episode (not off
-- referral_case) — it starts at some point after the acceptance decision,
-- possibly after a wait-list period (status = 'gepland' covers that).
-- ============================================================================
-- ============================================================================
-- clinical_intake_assessment
-- ============================================================================
CREATE TABLE clinical_intake_assessment (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
clinical_care_episode_id UUID NOT NULL REFERENCES clinical_care_episode(id) ON DELETE RESTRICT,
initiation_reason TEXT NOT NULL, -- waardelijst intake_initiation_reason
department TEXT NOT NULL, -- waardelijst department
status TEXT NOT NULL DEFAULT 'gepland', -- waardelijst intake_status
planned_at TIMESTAMPTZ NOT NULL,
started_at TIMESTAMPTZ,
ended_at TIMESTAMPTZ,
abort_reason TEXT, -- waardelijst intake_abort_reason
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT clinical_intake_assessment_status_check CHECK (status IN ('gepland', 'bezig', 'afgerond', 'afgebroken')),
CONSTRAINT clinical_intake_assessment_started_required CHECK (
status NOT IN ('bezig', 'afgerond', 'afgebroken') OR started_at IS NOT NULL
),
CONSTRAINT clinical_intake_assessment_ended_required CHECK (
status NOT IN ('afgerond', 'afgebroken') OR ended_at IS NOT NULL
),
CONSTRAINT clinical_intake_assessment_abort_reason_required CHECK (
status <> 'afgebroken' OR abort_reason IS NOT NULL
)
);
CREATE INDEX idx_clinical_intake_assessment_episode ON clinical_intake_assessment(clinical_care_episode_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_clinical_intake_assessment_status ON clinical_intake_assessment(status) WHERE deleted_at IS NULL;
COMMENT ON TABLE clinical_intake_assessment IS 'model-intake-behandeladvies.md §1.1. Start ná het acceptatiebesluit (B31), niet gelijktijdig met episode-ontstaan — planned_at dekt een eventuele intakewachtlijst-periode. "Maximaal één lopende intake per episode" is een aan/uit-zetbare bedrijfsregel, geen schemabeperking.';
ALTER TABLE clinical_intake_assessment ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON clinical_intake_assessment FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_clinical_intake_assessment_updated_at BEFORE UPDATE ON clinical_intake_assessment FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- intake_contact
-- ============================================================================
CREATE TABLE intake_contact (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
clinical_intake_assessment_id UUID NOT NULL REFERENCES clinical_intake_assessment(id) ON DELETE CASCADE,
occurred_at TIMESTAMPTZ NOT NULL,
contact_type TEXT NOT NULL, -- waardelijst intake_contact_type
performed_by UUID NOT NULL REFERENCES practitioners(id),
notes TEXT,
planned_duration_minutes INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT intake_contact_duration_non_negative CHECK (planned_duration_minutes IS NULL OR planned_duration_minutes >= 0)
);
CREATE INDEX idx_intake_contact_assessment ON intake_contact(clinical_intake_assessment_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE intake_contact IS 'model-intake-behandeladvies.md §1.2. Exclusief aan één clinical_intake_assessment; generalisatie naar een bredere consult-/afspraakstructuur is de agenda-ronde.';
ALTER TABLE intake_contact ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON intake_contact FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_intake_contact_updated_at BEFORE UPDATE ON intake_contact FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- child_safety_check
-- ============================================================================
CREATE TABLE child_safety_check (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
clinical_intake_assessment_id UUID NOT NULL REFERENCES clinical_intake_assessment(id) ON DELETE CASCADE,
performed_at TIMESTAMPTZ NOT NULL,
performed_by UUID NOT NULL REFERENCES practitioners(id),
responsible_for_minors BOOLEAN NOT NULL,
minor_count INTEGER,
minor_ages TEXT, -- vrije tekst, bewust geen kindrecords (dataminimalisatie)
safety_concern BOOLEAN NOT NULL,
safety_concern_notes TEXT,
action_taken BOOLEAN NOT NULL,
action_taken_notes TEXT,
pregnancy BOOLEAN NOT NULL, -- vierde vlag, te verifiëren tegen KNMG-meldcode (B34)
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT child_safety_check_safety_notes_required CHECK (NOT safety_concern OR safety_concern_notes IS NOT NULL),
CONSTRAINT child_safety_check_action_notes_required CHECK (NOT action_taken OR action_taken_notes IS NOT NULL)
);
-- maximaal één per intake
CREATE UNIQUE INDEX idx_child_safety_check_assessment ON child_safety_check(clinical_intake_assessment_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE child_safety_check IS 'model-intake-behandeladvies.md §1.3 (B34). Wet verplichte meldcode / KNMG-kindcheck.';
ALTER TABLE child_safety_check ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON child_safety_check FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_child_safety_check_updated_at BEFORE UPDATE ON child_safety_check FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- ============================================================================
-- intake_mdt_review (minimale, voorlopige hook — zie §6 aldaar)
-- ============================================================================
CREATE TABLE intake_mdt_review (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
occurred_at TIMESTAMPTZ NOT NULL,
participants TEXT NOT NULL, -- vrije tekst; structurering wacht op de MDO-ronde (B14-B15)
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
COMMENT ON TABLE intake_mdt_review IS 'model-intake-behandeladvies.md §1.4. Geen eigen FK naar clinical_intake_assessment — gekoppeld via treatment_advice.mdt_review_id, telt pas zodra hij aan een vastgesteld advies hangt. LKS 4.0: dwingend voor settings 3-8, optioneel setting 2, n.v.t. vrijgevestigden.';
ALTER TABLE intake_mdt_review ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON intake_mdt_review FOR ALL USING (auth.role() = 'authenticated');
-- ============================================================================
-- treatment_advice
-- ============================================================================
CREATE TABLE treatment_advice (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
clinical_intake_assessment_id UUID NOT NULL REFERENCES clinical_intake_assessment(id) ON DELETE RESTRICT,
decided_at TIMESTAMPTZ NOT NULL,
decided_by UUID NOT NULL REFERENCES practitioners(id), -- indicerende regiebehandelaar
outcome TEXT NOT NULL, -- waardelijst treatment_advice_outcome
recommended_care_program TEXT, -- waardelijst care_program
rationale TEXT,
mdt_review_id UUID REFERENCES intake_mdt_review(id),
replaces_advice_id UUID REFERENCES treatment_advice(id),
replacement_reason TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ,
CONSTRAINT treatment_advice_outcome_check CHECK (outcome IN ('in_zorg', 'terugverwijzing', 'doorverwijzing', 'extra_diagnostiek')),
CONSTRAINT treatment_advice_care_program_required CHECK (
outcome <> 'in_zorg' OR recommended_care_program IS NOT NULL
),
CONSTRAINT treatment_advice_rationale_required CHECK (
outcome = 'in_zorg' OR (rationale IS NOT NULL AND length(trim(rationale)) > 0)
),
CONSTRAINT treatment_advice_replacement_reason_required CHECK (
replaces_advice_id IS NULL OR (replacement_reason IS NOT NULL AND length(trim(replacement_reason)) > 0)
),
CONSTRAINT treatment_advice_no_self_replace CHECK (replaces_advice_id <> id)
);
-- vervangt-keten kan nooit vertakken, zelfde patroon als care_acceptance_decision
CREATE UNIQUE INDEX idx_treatment_advice_replaces_unique ON treatment_advice(replaces_advice_id) WHERE replaces_advice_id IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_treatment_advice_assessment ON treatment_advice(clinical_intake_assessment_id) WHERE deleted_at IS NULL;
COMMENT ON TABLE treatment_advice IS 'model-intake-behandeladvies.md §1.5 (B32-B33). Geen apart voorafgaand advies — dit object zelf geeft de vervolgrichting. doorverwijzing/terugverwijzing zijn LKS-genormeerd volgtijdelijk (via replaces_advice_id); extra_diagnostiek is praktijkgefundeerd, niet LKS-genormeerd.';
ALTER TABLE treatment_advice ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON treatment_advice FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_treatment_advice_updated_at BEFORE UPDATE ON treatment_advice FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Een afgeronde intake vereist minimaal één (niet noodzakelijk het laatst
-- vervangen) treatment_advice — afgedwongen op applicatieniveau, niet als
-- schema-constraint (zou een circulaire FK-afhankelijkheid vereisen).
COMMENT ON COLUMN clinical_intake_assessment.status IS 'gepland/bezig/afgerond/afgebroken. Bij afgerond hoort minimaal één treatment_advice (applicatieniveau, geen FK-constraint i.v.m. circulaire afhankelijkheid).';