chore(strip): verwijder behandelplan-module
AI-generatie negeerde de geregistreerde diagnose en ernst — elk plan was generiek (reviewbevinding). Module komt terug bij de rebuild, gekoppeld aan het nieuwe datamodel. - app/epd/patients/[id]/behandelplan/ en /api/behandelplan/ verwijderd - components/behandelplan/ (view, list, forms) verwijderd - lib/ai/behandelplan-prompt.ts, lib/ai/intervention-mapping.ts, lib/types/behandelplan.ts verwijderd - behandelplan-sectie uit patientdashboard, dashboard-API en Cortex patient-dashboard-block - 'Doorzetten naar behandelplan' uit behandeladvies-formulier Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,221 +0,0 @@
|
||||
/**
|
||||
* Behandelplan AI Prompts
|
||||
*
|
||||
* System prompt en user prompt templates voor AI-generatie van behandelplannen
|
||||
*/
|
||||
|
||||
import { type LifeDomainScore, LIFE_DOMAIN_META } from '@/lib/types/leefgebieden';
|
||||
import { type Severity, getInterventionsForCategory, getRecommendedSessionCount, getRecommendedFrequency, getRecommendedFormat } from './intervention-mapping';
|
||||
|
||||
/**
|
||||
* Context voor behandelplan generatie
|
||||
*/
|
||||
export interface PlanContext {
|
||||
patientId: string;
|
||||
intakeNotes: string;
|
||||
dsmCategory: string;
|
||||
severity: Severity;
|
||||
lifeDomains: LifeDomainScore[];
|
||||
extraInstructions?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* System prompt voor Claude
|
||||
* Instructies voor het genereren van een behandelplan
|
||||
*/
|
||||
export const BEHANDELPLAN_SYSTEM_PROMPT = `Je bent een ervaren GGZ-behandelaar die behandelplannen opstelt voor cliënten in de ambulante GGZ.
|
||||
Je maakt SMART doelen die recovery-gericht en evidence-based zijn.
|
||||
|
||||
INSTRUCTIES:
|
||||
1. Genereer 2-4 SMART doelen gebaseerd op de intake en diagnose
|
||||
2. Focus op leefgebieden met prioriteit "hoog"
|
||||
3. Verdeel doelen over minimaal 2 verschillende leefgebieden
|
||||
4. Maak concrete, meetbare doelen (geen vage termen zoals "beter voelen")
|
||||
5. Genereer voor elk doel een B1-taal versie (cliënt-vriendelijk, simpele woorden)
|
||||
6. Kies evidence-based interventies passend bij de DSM-categorie
|
||||
7. Plan sessies gebaseerd op severity niveau
|
||||
8. Voeg een veiligheidsplan toe ALLEEN bij severity "hoog"
|
||||
|
||||
SMART CRITERIA:
|
||||
- Specifiek: Wat precies wil de cliënt bereiken?
|
||||
- Meetbaar: Hoe meten we vooruitgang? (bijv. "3x per week", "score van 3 naar 4")
|
||||
- Acceptabel: Past bij wensen en mogelijkheden cliënt
|
||||
- Realistisch: Haalbaar binnen de behandelperiode
|
||||
- Tijdgebonden: Binnen hoeveel weken?
|
||||
|
||||
B1-TAAL RICHTLIJNEN (cliënt-versie):
|
||||
- Korte zinnen (max 15 woorden)
|
||||
- Alledaagse woorden (geen jargon)
|
||||
- Actieve zinnen ("Ik ga..." niet "Er zal worden...")
|
||||
- Directe aanspreking ("jij" of "je")
|
||||
|
||||
OUTPUT FORMAT:
|
||||
Retourneer ALLEEN valide JSON volgens dit exacte schema (geen extra tekst):
|
||||
{
|
||||
"behandelstructuur": {
|
||||
"duur": "string (bijv. '8 weken')",
|
||||
"frequentie": "string (bijv. 'Wekelijks')",
|
||||
"aantalSessies": number,
|
||||
"vorm": "string (bijv. 'Individueel')"
|
||||
},
|
||||
"doelen": [
|
||||
{
|
||||
"id": "string (uuid)",
|
||||
"title": "string (max 200 tekens, korte beschrijving)",
|
||||
"description": "string (SMART uitwerking, 2-3 zinnen)",
|
||||
"clientVersion": "string (B1-taal versie voor cliënt)",
|
||||
"lifeDomain": "dlv|wonen|werk|sociaal|vrijetijd|financien|gezondheid",
|
||||
"priority": "hoog|middel|laag",
|
||||
"measurability": "string (hoe meten we vooruitgang?)",
|
||||
"timelineWeeks": number,
|
||||
"status": "niet_gestart",
|
||||
"progress": 0
|
||||
}
|
||||
],
|
||||
"interventies": [
|
||||
{
|
||||
"id": "string (uuid)",
|
||||
"name": "string (bijv. 'CGT', 'EMDR')",
|
||||
"description": "string (uitleg interventie)",
|
||||
"rationale": "string (waarom past dit bij deze cliënt?)",
|
||||
"linkedGoalIds": ["string (id van gekoppeld doel)"]
|
||||
}
|
||||
],
|
||||
"sessiePlanning": [
|
||||
{
|
||||
"id": "string (uuid)",
|
||||
"nummer": number,
|
||||
"focus": "string (waar gaat de sessie over?)",
|
||||
"status": "gepland",
|
||||
"gekoppeldeDoelIds": ["string"]
|
||||
}
|
||||
],
|
||||
"evaluatiemomenten": [
|
||||
{
|
||||
"id": "string (uuid)",
|
||||
"type": "tussentijds|eind",
|
||||
"weekNumber": number,
|
||||
"plannedDate": "string (ISO date, mag leeg)",
|
||||
"status": "gepland"
|
||||
}
|
||||
],
|
||||
"veiligheidsplan": null of {
|
||||
"waarschuwingssignalen": ["string (3-5 items)"],
|
||||
"copingStrategieen": ["string (3-5 items)"],
|
||||
"contacten": [
|
||||
{
|
||||
"naam": "string",
|
||||
"rol": "string",
|
||||
"telefoon": "string"
|
||||
}
|
||||
],
|
||||
"restricties": ["string (optioneel)"]
|
||||
}
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Bouw user prompt met context
|
||||
*/
|
||||
export function buildUserPrompt(context: PlanContext): string {
|
||||
// Get high priority domains
|
||||
const highPriorityDomains = context.lifeDomains.filter(d => d.priority === 'hoog');
|
||||
const lowScoreDomains = context.lifeDomains.filter(d => d.baseline <= 2);
|
||||
|
||||
// Get intervention suggestions
|
||||
const suggestedInterventions = getInterventionsForCategory(context.dsmCategory, context.severity);
|
||||
const recommendedSessions = getRecommendedSessionCount(context.dsmCategory, context.severity);
|
||||
const recommendedFrequency = getRecommendedFrequency(context.severity);
|
||||
const recommendedFormat = getRecommendedFormat(context.severity);
|
||||
|
||||
// Build life domains section
|
||||
const lifeDomainLines = context.lifeDomains.map(d => {
|
||||
const meta = LIFE_DOMAIN_META[d.domain];
|
||||
const priorityMarker = d.priority === 'hoog' ? ' [PRIORITEIT]' : '';
|
||||
const lowScoreMarker = d.baseline <= 2 ? ' [LAAG]' : '';
|
||||
return `- ${meta.label} (${d.domain}): ${d.baseline}/5 → doel ${d.target}/5${priorityMarker}${lowScoreMarker}${d.notes ? ` (${d.notes})` : ''}`;
|
||||
}).join('\n');
|
||||
|
||||
// Build intervention suggestions
|
||||
const interventionLines = suggestedInterventions.slice(0, 3).map(i =>
|
||||
`- ${i.name}: ${i.description} (~${i.recommendedSessions} sessies)`
|
||||
).join('\n');
|
||||
|
||||
return `CLIËNT CONTEXT:
|
||||
=================
|
||||
Intake notities:
|
||||
${context.intakeNotes}
|
||||
|
||||
DSM-categorie: ${context.dsmCategory}
|
||||
Severity: ${context.severity}
|
||||
|
||||
LEEFGEBIEDEN ASSESSMENT:
|
||||
========================
|
||||
${lifeDomainLines}
|
||||
|
||||
${highPriorityDomains.length > 0 ? `
|
||||
FOCUS GEBIEDEN (hoge prioriteit):
|
||||
${highPriorityDomains.map(d => `- ${LIFE_DOMAIN_META[d.domain].label}`).join('\n')}
|
||||
` : ''}
|
||||
${lowScoreDomains.length > 0 ? `
|
||||
AANDACHTSPUNTEN (lage scores):
|
||||
${lowScoreDomains.map(d => `- ${LIFE_DOMAIN_META[d.domain].label} (score: ${d.baseline}/5)`).join('\n')}
|
||||
` : ''}
|
||||
|
||||
AANBEVOLEN INTERVENTIES (evidence-based):
|
||||
=========================================
|
||||
${interventionLines}
|
||||
|
||||
AANBEVOLEN BEHANDELSTRUCTUUR:
|
||||
=============================
|
||||
- Aantal sessies: ~${recommendedSessions}
|
||||
- Frequentie: ${recommendedFrequency}
|
||||
- Vorm: ${recommendedFormat}
|
||||
|
||||
${context.extraInstructions ? `
|
||||
EXTRA INSTRUCTIES VAN BEHANDELAAR:
|
||||
==================================
|
||||
${context.extraInstructions}
|
||||
` : ''}
|
||||
|
||||
Genereer nu een compleet behandelplan in JSON formaat.
|
||||
Zorg dat elk doel:
|
||||
1. Gekoppeld is aan een leefgebied met hoge prioriteit of lage score
|
||||
2. Een meetbare indicator heeft
|
||||
3. Een B1-taal versie heeft voor de cliënt
|
||||
4. Realistisch is voor de behandelperiode
|
||||
|
||||
${context.severity === 'hoog' ? 'BELANGRIJK: Voeg ook een veiligheidsplan toe met waarschuwingssignalen, copingstrategieën en contactpersonen.' : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valideer of de context voldoende informatie bevat
|
||||
*/
|
||||
export function validatePlanContext(context: PlanContext): { valid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!context.intakeNotes || context.intakeNotes.length < 50) {
|
||||
errors.push('Intake notities zijn te kort (minimaal 50 tekens)');
|
||||
}
|
||||
|
||||
if (!context.dsmCategory) {
|
||||
errors.push('DSM-categorie ontbreekt');
|
||||
}
|
||||
|
||||
if (!context.severity) {
|
||||
errors.push('Severity niveau ontbreekt');
|
||||
}
|
||||
|
||||
if (!context.lifeDomains || context.lifeDomains.length !== 7) {
|
||||
errors.push('Leefgebieden assessment is incompleet (7 domeinen vereist)');
|
||||
}
|
||||
|
||||
const hasHighPriority = context.lifeDomains?.some(d => d.priority === 'hoog');
|
||||
if (!hasHighPriority) {
|
||||
errors.push('Minimaal 1 leefgebied moet prioriteit "hoog" hebben');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
/**
|
||||
* Evidence-Based Intervention Mapping
|
||||
*
|
||||
* Mapping van DSM-categorieën naar evidence-based interventies
|
||||
* met sessie-aantallen per severity level
|
||||
*/
|
||||
|
||||
export interface InterventionSuggestion {
|
||||
name: string;
|
||||
description: string;
|
||||
sessions: {
|
||||
laag: number;
|
||||
middel: number;
|
||||
hoog: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type Severity = 'laag' | 'middel' | 'hoog';
|
||||
|
||||
/**
|
||||
* DSM categorieën naar interventie mapping
|
||||
* Gebaseerd op richtlijnen GGZ Standaarden
|
||||
*/
|
||||
export const INTERVENTION_MAPPING: Record<string, InterventionSuggestion[]> = {
|
||||
angststoornissen: [
|
||||
{
|
||||
name: 'CGT',
|
||||
description: 'Cognitieve Gedragstherapie gericht op angstreductie door blootstelling en cognitieve herstructurering',
|
||||
sessions: { laag: 8, middel: 10, hoog: 14 },
|
||||
},
|
||||
{
|
||||
name: 'Exposure therapie',
|
||||
description: 'Systematische blootstelling aan angstopwekkende situaties met responspreventie',
|
||||
sessions: { laag: 6, middel: 8, hoog: 12 },
|
||||
},
|
||||
{
|
||||
name: 'ACT',
|
||||
description: 'Acceptance and Commitment Therapy voor psychologische flexibiliteit',
|
||||
sessions: { laag: 8, middel: 10, hoog: 12 },
|
||||
},
|
||||
],
|
||||
stemmingsklachten: [
|
||||
{
|
||||
name: 'CGT',
|
||||
description: 'Cognitieve Gedragstherapie gericht op negatieve denkpatronen en gedragsactivatie',
|
||||
sessions: { laag: 8, middel: 10, hoog: 14 },
|
||||
},
|
||||
{
|
||||
name: 'IPT',
|
||||
description: 'Interpersoonlijke Therapie gericht op relationele patronen en sociale steun',
|
||||
sessions: { laag: 8, middel: 12, hoog: 16 },
|
||||
},
|
||||
{
|
||||
name: 'Gedragsactivatie',
|
||||
description: 'Gestructureerde toename van plezierige en betekenisvolle activiteiten',
|
||||
sessions: { laag: 6, middel: 8, hoog: 10 },
|
||||
},
|
||||
],
|
||||
trauma_ptss: [
|
||||
{
|
||||
name: 'EMDR',
|
||||
description: 'Eye Movement Desensitization and Reprocessing voor traumaverwerking',
|
||||
sessions: { laag: 6, middel: 10, hoog: 16 },
|
||||
},
|
||||
{
|
||||
name: 'Narratieve therapie',
|
||||
description: 'Verwerking door het opbouwen van een coherent traumaverhaal',
|
||||
sessions: { laag: 8, middel: 12, hoog: 16 },
|
||||
},
|
||||
{
|
||||
name: 'CGT trauma-focus',
|
||||
description: 'Trauma-gefocuste CGT met exposure en cognitieve verwerking',
|
||||
sessions: { laag: 8, middel: 12, hoog: 16 },
|
||||
},
|
||||
],
|
||||
persoonlijkheid: [
|
||||
{
|
||||
name: 'Schematherapie',
|
||||
description: 'Langdurige therapie gericht op disfunctionele schemas en copingstijlen',
|
||||
sessions: { laag: 16, middel: 24, hoog: 40 },
|
||||
},
|
||||
{
|
||||
name: 'MBT',
|
||||
description: 'Mentalization-Based Treatment voor emotieregulatie en relaties',
|
||||
sessions: { laag: 16, middel: 24, hoog: 40 },
|
||||
},
|
||||
{
|
||||
name: 'DGT',
|
||||
description: 'Dialectische Gedragstherapie voor emotieregulatie en crisisvaardigheden',
|
||||
sessions: { laag: 16, middel: 24, hoog: 40 },
|
||||
},
|
||||
],
|
||||
verslaving: [
|
||||
{
|
||||
name: 'Motiverende gespreksvoering',
|
||||
description: 'Versterken van intrinsieke motivatie voor gedragsverandering',
|
||||
sessions: { laag: 4, middel: 6, hoog: 8 },
|
||||
},
|
||||
{
|
||||
name: 'CGT verslaving',
|
||||
description: 'Cognitieve Gedragstherapie gericht op craving en terugvalpreventie',
|
||||
sessions: { laag: 8, middel: 12, hoog: 16 },
|
||||
},
|
||||
{
|
||||
name: 'Terugvalpreventie',
|
||||
description: 'Identificeren en managen van risicosituaties en triggers',
|
||||
sessions: { laag: 6, middel: 8, hoog: 12 },
|
||||
},
|
||||
],
|
||||
adhd: [
|
||||
{
|
||||
name: 'Psycho-educatie',
|
||||
description: 'Educatie over ADHD en praktische copingstrategieën',
|
||||
sessions: { laag: 4, middel: 6, hoog: 8 },
|
||||
},
|
||||
{
|
||||
name: 'Coaching/planning',
|
||||
description: 'Structuur en planningsvaardigheden ontwikkelen',
|
||||
sessions: { laag: 6, middel: 8, hoog: 12 },
|
||||
},
|
||||
{
|
||||
name: 'CGT ADHD',
|
||||
description: 'Gedragstherapie gericht op impulscontrole en executieve functies',
|
||||
sessions: { laag: 8, middel: 10, hoog: 14 },
|
||||
},
|
||||
],
|
||||
autisme: [
|
||||
{
|
||||
name: 'Psycho-educatie',
|
||||
description: 'Educatie over autisme en zelfacceptatie',
|
||||
sessions: { laag: 4, middel: 6, hoog: 8 },
|
||||
},
|
||||
{
|
||||
name: 'Sociale vaardigheden',
|
||||
description: 'Training in sociale interactie en communicatie',
|
||||
sessions: { laag: 8, middel: 12, hoog: 16 },
|
||||
},
|
||||
{
|
||||
name: 'Stressmanagement',
|
||||
description: 'Omgaan met prikkels en sensorische overbelasting',
|
||||
sessions: { laag: 6, middel: 8, hoog: 12 },
|
||||
},
|
||||
],
|
||||
overig: [
|
||||
{
|
||||
name: 'Ondersteunende gesprekken',
|
||||
description: 'Steunende begeleiding en reflectie',
|
||||
sessions: { laag: 4, middel: 6, hoog: 8 },
|
||||
},
|
||||
{
|
||||
name: 'Psycho-educatie',
|
||||
description: 'Educatie over klachten en copingstrategieën',
|
||||
sessions: { laag: 4, middel: 6, hoog: 8 },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Haal interventies op voor een DSM categorie
|
||||
*/
|
||||
export function getInterventionsForCategory(
|
||||
dsmCategory: string,
|
||||
severity: Severity
|
||||
): { name: string; description: string; recommendedSessions: number }[] {
|
||||
// Normalize category
|
||||
const normalizedCategory = dsmCategory
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z]/g, '_')
|
||||
.replace(/_+/g, '_');
|
||||
|
||||
// Find matching category
|
||||
const interventions =
|
||||
INTERVENTION_MAPPING[normalizedCategory] ||
|
||||
INTERVENTION_MAPPING['overig'];
|
||||
|
||||
return interventions.map((intervention) => ({
|
||||
name: intervention.name,
|
||||
description: intervention.description,
|
||||
recommendedSessions: intervention.sessions[severity],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Bereken aanbevolen aantal sessies
|
||||
*/
|
||||
export function getRecommendedSessionCount(
|
||||
dsmCategory: string,
|
||||
severity: Severity
|
||||
): number {
|
||||
const interventions = getInterventionsForCategory(dsmCategory, severity);
|
||||
if (interventions.length === 0) return 8;
|
||||
|
||||
// Neem gemiddelde van eerste 2 interventies
|
||||
const topInterventions = interventions.slice(0, 2);
|
||||
const avg =
|
||||
topInterventions.reduce((sum, i) => sum + i.recommendedSessions, 0) /
|
||||
topInterventions.length;
|
||||
|
||||
return Math.round(avg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bepaal behandelvorm op basis van severity
|
||||
*/
|
||||
export function getRecommendedFormat(severity: Severity): string {
|
||||
switch (severity) {
|
||||
case 'laag':
|
||||
return 'Individueel';
|
||||
case 'middel':
|
||||
return 'Individueel';
|
||||
case 'hoog':
|
||||
return 'Individueel + groep (optioneel)';
|
||||
default:
|
||||
return 'Individueel';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bepaal behandelfrequentie op basis van severity
|
||||
*/
|
||||
export function getRecommendedFrequency(severity: Severity): string {
|
||||
switch (severity) {
|
||||
case 'laag':
|
||||
return 'Wekelijks tot tweewekelijks';
|
||||
case 'middel':
|
||||
return 'Wekelijks';
|
||||
case 'hoog':
|
||||
return 'Wekelijks tot 2x per week';
|
||||
default:
|
||||
return 'Wekelijks';
|
||||
}
|
||||
}
|
||||
@@ -1,620 +0,0 @@
|
||||
/**
|
||||
* Behandelplan (Treatment Plan) Types
|
||||
*
|
||||
* Types voor AI-gegenereerde behandelplannen
|
||||
* Gebaseerd op FHIR CarePlan met GGZ-specifieke uitbreidingen
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { type LifeDomain, type LifeDomainScore, LIFE_DOMAINS, PRIORITIES } from './leefgebieden';
|
||||
|
||||
// =============================================================================
|
||||
// STATUS TYPES
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Status van een behandelplan
|
||||
*/
|
||||
export const PLAN_STATUSES = ['concept', 'actief', 'in_evaluatie', 'afgerond', 'gearchiveerd'] as const;
|
||||
export type PlanStatus = typeof PLAN_STATUSES[number];
|
||||
|
||||
/**
|
||||
* Status van een doel
|
||||
*/
|
||||
export const GOAL_STATUSES = ['niet_gestart', 'bezig', 'gehaald', 'bijgesteld'] as const;
|
||||
export type GoalStatus = typeof GOAL_STATUSES[number];
|
||||
|
||||
/**
|
||||
* Type evaluatiemoment
|
||||
*/
|
||||
export const EVALUATION_TYPES = ['tussentijds', 'eind', 'crisis'] as const;
|
||||
export type EvaluationType = typeof EVALUATION_TYPES[number];
|
||||
|
||||
/**
|
||||
* Status evaluatiemoment
|
||||
*/
|
||||
export const EVALUATION_STATUSES = ['gepland', 'afgerond', 'overgeslagen'] as const;
|
||||
export type EvaluationStatus = typeof EVALUATION_STATUSES[number];
|
||||
|
||||
// =============================================================================
|
||||
// CORE TYPES
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Behandelstructuur - algemene parameters van het plan
|
||||
*/
|
||||
export interface Behandelstructuur {
|
||||
duur: string; // bijv. "8 weken"
|
||||
frequentie: string; // bijv. "Wekelijks"
|
||||
aantalSessies: number; // bijv. 8
|
||||
vorm: string; // bijv. "Individueel"
|
||||
}
|
||||
|
||||
/**
|
||||
* SMART Doel
|
||||
*/
|
||||
export interface SmartGoal {
|
||||
id: string;
|
||||
title: string; // Korte beschrijving (1 zin)
|
||||
description: string; // SMART-uitwerking (2-3 zinnen)
|
||||
clientVersion: string; // B1-taal versie voor cliënt
|
||||
lifeDomain: LifeDomain; // Gekoppeld leefgebied
|
||||
priority: 'hoog' | 'middel' | 'laag';
|
||||
measurability: string; // Hoe meten we vooruitgang?
|
||||
timelineWeeks: number; // Binnen X weken
|
||||
status: GoalStatus;
|
||||
progress: number; // 0-100
|
||||
}
|
||||
|
||||
/**
|
||||
* Evidence-based Interventie
|
||||
*/
|
||||
export interface Intervention {
|
||||
id: string;
|
||||
name: string; // bijv. "CGT", "EMDR", "ACT"
|
||||
description: string; // Uitleg van de interventie
|
||||
rationale: string; // Waarom past dit bij deze cliënt?
|
||||
linkedGoalIds: string[]; // Welke doelen worden hiermee benaderd?
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluatiemoment
|
||||
*/
|
||||
export interface Evaluatiemoment {
|
||||
id: string;
|
||||
type: EvaluationType;
|
||||
weekNumber: number;
|
||||
plannedDate: string; // ISO date string
|
||||
actualDate?: string; // Ingevuld na uitvoering
|
||||
status: EvaluationStatus;
|
||||
outcome?: string; // Vrije tekst resultaat
|
||||
lifeDomainUpdates?: LifeDomainScore[]; // Nieuwe scores
|
||||
}
|
||||
|
||||
/**
|
||||
* Veiligheidsplan (alleen bij severity "Hoog")
|
||||
*/
|
||||
export interface Veiligheidsplan {
|
||||
waarschuwingssignalen: string[]; // 3-5 items
|
||||
copingStrategieen: string[]; // 3-5 items
|
||||
contacten: {
|
||||
naam: string;
|
||||
rol: string;
|
||||
telefoon: string;
|
||||
}[];
|
||||
restricties?: string[]; // bijv. "Geen alcohol tijdens behandeling"
|
||||
}
|
||||
|
||||
/**
|
||||
* Sessie in de planning
|
||||
*/
|
||||
export interface Sessie {
|
||||
id: string;
|
||||
nummer: number;
|
||||
focus: string;
|
||||
datum?: string; // ISO date string
|
||||
status: 'gepland' | 'afgerond' | 'no_show' | 'verzet' | 'geannuleerd';
|
||||
gekoppeldeDoelIds: string[];
|
||||
notities?: string;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GENERATED PLAN (AI OUTPUT)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Volledig door AI gegenereerd behandelplan
|
||||
*/
|
||||
export interface GeneratedPlan {
|
||||
behandelstructuur: Behandelstructuur;
|
||||
doelen: SmartGoal[];
|
||||
interventies: Intervention[];
|
||||
sessiePlanning: Sessie[];
|
||||
evaluatiemomenten: Evaluatiemoment[];
|
||||
veiligheidsplan?: Veiligheidsplan; // Alleen bij severity "Hoog"
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// API INPUT/OUTPUT TYPES
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Input voor behandelplan generatie
|
||||
*/
|
||||
export interface GenerateBehandelplanInput {
|
||||
patientId: string;
|
||||
intakeId: string;
|
||||
conditionId?: string; // Optioneel, haalt anders laatste op
|
||||
extraInstructions?: string; // Optionele aanvullende instructies
|
||||
}
|
||||
|
||||
/**
|
||||
* Input voor micro-regeneratie
|
||||
*/
|
||||
export interface RegenerateSectionInput {
|
||||
patientId: string;
|
||||
carePlanId: string;
|
||||
sectionType: 'goal' | 'intervention';
|
||||
sectionId: string;
|
||||
instruction?: string; // Extra instructie voor AI
|
||||
currentPlan: GeneratedPlan; // Context van huidige plan
|
||||
}
|
||||
|
||||
/**
|
||||
* Output van micro-regeneratie
|
||||
*/
|
||||
export interface RegeneratedSection {
|
||||
type: 'goal' | 'intervention';
|
||||
original: SmartGoal | Intervention;
|
||||
regenerated: SmartGoal | Intervention;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ZOD SCHEMAS
|
||||
// =============================================================================
|
||||
|
||||
export const BehandelstructuurSchema = z.object({
|
||||
duur: z.string(),
|
||||
frequentie: z.string(),
|
||||
aantalSessies: z.number().min(1).max(52),
|
||||
vorm: z.string(),
|
||||
});
|
||||
|
||||
export const SmartGoalSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string().min(5).max(200),
|
||||
description: z.string().min(10).max(500),
|
||||
clientVersion: z.string().min(5).max(300),
|
||||
lifeDomain: z.enum(LIFE_DOMAINS),
|
||||
priority: z.enum(PRIORITIES),
|
||||
measurability: z.string().min(5).max(200),
|
||||
timelineWeeks: z.number().min(1).max(52),
|
||||
status: z.enum(GOAL_STATUSES),
|
||||
progress: z.number().min(0).max(100),
|
||||
});
|
||||
|
||||
export const InterventionSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().min(2).max(100),
|
||||
description: z.string().min(10).max(500),
|
||||
rationale: z.string().min(10).max(500),
|
||||
linkedGoalIds: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const EvaluatiemomentSchema = z.object({
|
||||
id: z.string(),
|
||||
type: z.enum(EVALUATION_TYPES),
|
||||
weekNumber: z.number().min(1).max(52),
|
||||
plannedDate: z.string(),
|
||||
actualDate: z.string().optional(),
|
||||
status: z.enum(EVALUATION_STATUSES),
|
||||
outcome: z.string().optional(),
|
||||
lifeDomainUpdates: z.array(z.any()).optional(), // Simplified for now
|
||||
});
|
||||
|
||||
export const VeiligheidsplanSchema = z.object({
|
||||
waarschuwingssignalen: z.array(z.string()).min(1).max(10),
|
||||
copingStrategieen: z.array(z.string()).min(1).max(10),
|
||||
contacten: z.array(z.object({
|
||||
naam: z.string(),
|
||||
rol: z.string(),
|
||||
telefoon: z.string(),
|
||||
})),
|
||||
restricties: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const SessieSchema = z.object({
|
||||
id: z.string(),
|
||||
nummer: z.number().min(1),
|
||||
focus: z.string(),
|
||||
datum: z.string().optional(),
|
||||
status: z.enum(['gepland', 'afgerond', 'no_show', 'verzet', 'geannuleerd']),
|
||||
gekoppeldeDoelIds: z.array(z.string()),
|
||||
notities: z.string().optional(),
|
||||
});
|
||||
|
||||
export const GeneratedPlanSchema = z.object({
|
||||
behandelstructuur: BehandelstructuurSchema,
|
||||
doelen: z.array(SmartGoalSchema).min(1).max(6),
|
||||
interventies: z.array(InterventionSchema).min(1).max(5),
|
||||
sessiePlanning: z.array(SessieSchema),
|
||||
evaluatiemomenten: z.array(EvaluatiemomentSchema).min(1),
|
||||
veiligheidsplan: VeiligheidsplanSchema.optional(),
|
||||
});
|
||||
|
||||
export const GenerateBehandelplanInputSchema = z.object({
|
||||
patientId: z.string().uuid(),
|
||||
intakeId: z.string().uuid(),
|
||||
conditionId: z.string().uuid().optional(),
|
||||
extraInstructions: z.string().max(500).optional(),
|
||||
});
|
||||
|
||||
export const RegenerateSectionInputSchema = z.object({
|
||||
patientId: z.string().uuid(),
|
||||
carePlanId: z.string().uuid(),
|
||||
sectionType: z.enum(['goal', 'intervention']),
|
||||
sectionId: z.string(),
|
||||
instruction: z.string().max(200).optional(),
|
||||
currentPlan: GeneratedPlanSchema,
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Genereer een nieuw UUID-achtig ID
|
||||
*/
|
||||
export function generateId(): string {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maak een nieuw leeg SMART doel
|
||||
*/
|
||||
export function createEmptyGoal(lifeDomain: LifeDomain = 'dlv'): SmartGoal {
|
||||
return {
|
||||
id: generateId(),
|
||||
title: '',
|
||||
description: '',
|
||||
clientVersion: '',
|
||||
lifeDomain,
|
||||
priority: 'middel',
|
||||
measurability: '',
|
||||
timelineWeeks: 8,
|
||||
status: 'niet_gestart',
|
||||
progress: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maak een nieuwe lege interventie
|
||||
*/
|
||||
export function createEmptyIntervention(): Intervention {
|
||||
return {
|
||||
id: generateId(),
|
||||
name: '',
|
||||
description: '',
|
||||
rationale: '',
|
||||
linkedGoalIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Bereken totale voortgang van alle doelen
|
||||
*/
|
||||
export function calculateTotalProgress(goals: SmartGoal[]): number {
|
||||
if (goals.length === 0) return 0;
|
||||
const sum = goals.reduce((acc, goal) => acc + goal.progress, 0);
|
||||
return Math.round(sum / goals.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Krijg doelen per leefgebied
|
||||
*/
|
||||
export function getGoalsByDomain(goals: SmartGoal[]): Record<LifeDomain, SmartGoal[]> {
|
||||
const result = {} as Record<LifeDomain, SmartGoal[]>;
|
||||
for (const domain of LIFE_DOMAINS) {
|
||||
result[domain] = goals.filter((g) => g.lifeDomain === domain);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check of plan klaar is voor publicatie
|
||||
*/
|
||||
export function canPublish(plan: GeneratedPlan): { valid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (plan.doelen.length === 0) {
|
||||
errors.push('Minimaal 1 doel is vereist');
|
||||
}
|
||||
|
||||
if (plan.interventies.length === 0) {
|
||||
errors.push('Minimaal 1 interventie is vereist');
|
||||
}
|
||||
|
||||
if (!plan.behandelstructuur.duur || !plan.behandelstructuur.frequentie) {
|
||||
errors.push('Behandelstructuur moet compleet zijn');
|
||||
}
|
||||
|
||||
if (plan.evaluatiemomenten.length < 2) {
|
||||
errors.push('Minimaal 2 evaluatiemomenten zijn vereist');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Status label voor UI (oude Nederlandse keys - deprecated)
|
||||
*/
|
||||
export const PLAN_STATUS_LABELS: Record<PlanStatus, { label: string; color: string }> = {
|
||||
concept: { label: 'Concept', color: '#60a5fa' }, // blauw
|
||||
actief: { label: 'Actief', color: '#10b981' }, // groen
|
||||
in_evaluatie: { label: 'In evaluatie', color: '#f59e0b' }, // oranje
|
||||
afgerond: { label: 'Afgerond', color: '#6b7280' }, // grijs
|
||||
gearchiveerd: { label: 'Gearchiveerd', color: '#9ca3af' }, // lichtgrijs
|
||||
};
|
||||
|
||||
/**
|
||||
* FHIR status naar Nederlandse UI labels
|
||||
* Database gebruikt FHIR statussen, UI toont Nederlands
|
||||
*/
|
||||
export type FhirCarePlanStatus = 'draft' | 'active' | 'on-hold' | 'completed' | 'revoked' | 'entered-in-error' | 'unknown';
|
||||
|
||||
export const FHIR_STATUS_LABELS: Record<FhirCarePlanStatus, { label: string; color: string }> = {
|
||||
draft: { label: 'Concept', color: '#60a5fa' }, // blauw
|
||||
active: { label: 'Actueel', color: '#10b981' }, // groen
|
||||
'on-hold': { label: 'Gepauzeerd', color: '#f59e0b' }, // oranje
|
||||
completed: { label: 'Definitief', color: '#6b7280' }, // grijs
|
||||
revoked: { label: 'Archief', color: '#9ca3af' }, // lichtgrijs
|
||||
'entered-in-error': { label: 'Verwijderd', color: '#ef4444' }, // rood
|
||||
unknown: { label: 'Onbekend', color: '#9ca3af' }, // lichtgrijs
|
||||
};
|
||||
|
||||
/**
|
||||
* Goal status label voor UI
|
||||
*/
|
||||
export const GOAL_STATUS_LABELS: Record<GoalStatus, { label: string; color: string }> = {
|
||||
niet_gestart: { label: 'Niet gestart', color: '#9ca3af' },
|
||||
bezig: { label: 'Bezig', color: '#3b82f6' },
|
||||
gehaald: { label: 'Gehaald', color: '#10b981' },
|
||||
bijgesteld: { label: 'Bijgesteld', color: '#f59e0b' },
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// FLAT BEHANDELPLAN TYPES (Nieuwe platte structuur)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Embedded interventie binnen een behandeldoel
|
||||
* Simpelere versie zonder linkedGoalIds (want embedded in doel)
|
||||
*/
|
||||
export interface EmbeddedInterventie {
|
||||
id: string;
|
||||
name: string; // bijv. "CGT", "EMDR", "ACT"
|
||||
description: string; // Korte beschrijving van de aanpak
|
||||
}
|
||||
|
||||
/**
|
||||
* Behandeldoel met embedded interventies
|
||||
* Kerntype voor de "platte" behandelplan structuur
|
||||
*/
|
||||
export interface Behandeldoel {
|
||||
id: string;
|
||||
|
||||
// Doel informatie
|
||||
title: string; // Professionele formulering
|
||||
clientVersion: string; // B1-taal versie voor cliënt
|
||||
|
||||
// Classificatie
|
||||
lifeDomain: LifeDomain; // Gekoppeld leefgebied
|
||||
|
||||
// Embedded interventies (KERNVERANDERING - niet meer apart)
|
||||
interventies: EmbeddedInterventie[];
|
||||
|
||||
// Timeline
|
||||
startWeek: number; // Start week (1-52)
|
||||
endWeek: number; // Eind week (1-52)
|
||||
|
||||
// Status & voortgang
|
||||
status: GoalStatus;
|
||||
progress: number; // 0-100
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FLAT BEHANDELPLAN ZOD SCHEMAS
|
||||
// =============================================================================
|
||||
|
||||
export const EmbeddedInterventieSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().min(1).max(100),
|
||||
description: z.string().max(500),
|
||||
});
|
||||
|
||||
export const BehandeldoelSchema = z.object({
|
||||
id: z.string(),
|
||||
title: z.string().min(5).max(200),
|
||||
clientVersion: z.string().min(5).max(300),
|
||||
lifeDomain: z.enum(LIFE_DOMAINS),
|
||||
interventies: z.array(EmbeddedInterventieSchema),
|
||||
startWeek: z.number().min(1).max(52),
|
||||
endWeek: z.number().min(1).max(52),
|
||||
status: z.enum(GOAL_STATUSES),
|
||||
progress: z.number().min(0).max(100),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema voor AI-gegenereerd flat behandelplan
|
||||
*/
|
||||
export const GeneratedPlanFlatSchema = z.object({
|
||||
behandelstructuur: BehandelstructuurSchema,
|
||||
behandeldoelen: z.array(BehandeldoelSchema).min(1).max(6),
|
||||
evaluatiemomenten: z.array(EvaluatiemomentSchema).min(1),
|
||||
veiligheidsplan: VeiligheidsplanSchema.optional(),
|
||||
});
|
||||
|
||||
export type GeneratedPlanFlat = z.infer<typeof GeneratedPlanFlatSchema>;
|
||||
|
||||
// =============================================================================
|
||||
// TRANSFORMATIE FUNCTIES (Oude <-> Nieuwe structuur)
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Transformeer oude structuur (goals + interventions apart) naar flat (behandeldoelen)
|
||||
* Koppelt interventies aan doelen op basis van linkedGoalIds
|
||||
*/
|
||||
export function transformToFlat(
|
||||
goals: SmartGoal[],
|
||||
interventions: Intervention[]
|
||||
): Behandeldoel[] {
|
||||
return goals.map((goal) => {
|
||||
// Vind interventies die aan dit doel gekoppeld zijn
|
||||
const linkedInterventions = interventions
|
||||
.filter((int) => int.linkedGoalIds.includes(goal.id))
|
||||
.map((int) => ({
|
||||
id: int.id,
|
||||
name: int.name,
|
||||
description: int.description,
|
||||
}));
|
||||
|
||||
return {
|
||||
id: goal.id,
|
||||
title: goal.title,
|
||||
clientVersion: goal.clientVersion,
|
||||
lifeDomain: goal.lifeDomain,
|
||||
interventies: linkedInterventions,
|
||||
startWeek: 1, // Default, kan later uit goal.timelineWeeks berekend worden
|
||||
endWeek: goal.timelineWeeks,
|
||||
status: goal.status,
|
||||
progress: goal.progress,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformeer flat structuur terug naar oude structuur (voor backwards compatibility)
|
||||
* Zet embedded interventies om naar aparte array met linkedGoalIds
|
||||
*/
|
||||
export function transformFromFlat(behandeldoelen: Behandeldoel[]): {
|
||||
goals: SmartGoal[];
|
||||
interventions: Intervention[];
|
||||
} {
|
||||
const goals: SmartGoal[] = [];
|
||||
const interventionMap = new Map<string, Intervention>();
|
||||
|
||||
for (const doel of behandeldoelen) {
|
||||
// Maak SmartGoal van Behandeldoel
|
||||
goals.push({
|
||||
id: doel.id,
|
||||
title: doel.title,
|
||||
description: '', // Niet meer gebruikt in flat structuur
|
||||
clientVersion: doel.clientVersion,
|
||||
lifeDomain: doel.lifeDomain,
|
||||
priority: 'middel', // Default
|
||||
measurability: '', // Niet meer gebruikt in flat structuur
|
||||
timelineWeeks: doel.endWeek,
|
||||
status: doel.status,
|
||||
progress: doel.progress,
|
||||
});
|
||||
|
||||
// Verzamel interventies en koppel aan doel
|
||||
for (const int of doel.interventies) {
|
||||
const existing = interventionMap.get(int.id);
|
||||
if (existing) {
|
||||
// Interventie bestaat al, voeg dit doel toe aan linkedGoalIds
|
||||
existing.linkedGoalIds.push(doel.id);
|
||||
} else {
|
||||
// Nieuwe interventie
|
||||
interventionMap.set(int.id, {
|
||||
id: int.id,
|
||||
name: int.name,
|
||||
description: int.description,
|
||||
rationale: '', // Niet meer gebruikt in flat structuur
|
||||
linkedGoalIds: [doel.id],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
goals,
|
||||
interventions: Array.from(interventionMap.values()),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maak een nieuw leeg behandeldoel
|
||||
*/
|
||||
export function createEmptyBehandeldoel(lifeDomain: LifeDomain = 'dlv'): Behandeldoel {
|
||||
return {
|
||||
id: generateId(),
|
||||
title: '',
|
||||
clientVersion: '',
|
||||
lifeDomain,
|
||||
interventies: [],
|
||||
startWeek: 1,
|
||||
endWeek: 8,
|
||||
status: 'niet_gestart',
|
||||
progress: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maak een nieuwe lege embedded interventie
|
||||
*/
|
||||
export function createEmptyEmbeddedInterventie(): EmbeddedInterventie {
|
||||
return {
|
||||
id: generateId(),
|
||||
name: '',
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Bereken totale voortgang van behandeldoelen
|
||||
*/
|
||||
export function calculateBehandeldoelenProgress(doelen: Behandeldoel[]): number {
|
||||
if (doelen.length === 0) return 0;
|
||||
const sum = doelen.reduce((acc, doel) => acc + doel.progress, 0);
|
||||
return Math.round(sum / doelen.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check of flat plan klaar is voor publicatie
|
||||
*/
|
||||
export function canPublishFlat(
|
||||
behandeldoelen: Behandeldoel[],
|
||||
behandelstructuur: Behandelstructuur,
|
||||
evaluatiemomenten: Evaluatiemoment[]
|
||||
): { valid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (behandeldoelen.length === 0) {
|
||||
errors.push('Minimaal 1 behandeldoel is vereist');
|
||||
}
|
||||
|
||||
// Check of elk doel minimaal 1 interventie heeft
|
||||
const doelenZonderInterventie = behandeldoelen.filter(
|
||||
(d) => d.interventies.length === 0
|
||||
);
|
||||
if (doelenZonderInterventie.length > 0) {
|
||||
errors.push('Elk behandeldoel moet minimaal 1 interventie hebben');
|
||||
}
|
||||
|
||||
if (!behandelstructuur.duur || !behandelstructuur.frequentie) {
|
||||
errors.push('Behandelstructuur moet compleet zijn');
|
||||
}
|
||||
|
||||
if (evaluatiemomenten.length < 2) {
|
||||
errors.push('Minimaal 2 evaluatiemomenten zijn vereist');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user