| Epic | Status | Wat is gedaan |

|---------------------|---------------|---------------------|
  | E0: Foundation      |  Afgerond    | Types + DB migratie |
  | E1: Leefgebieden    |  Afgerond    | 3 componenten       |
  | E2: AI Generatie    |  Nog te doen | -                   |
  | E3: Behandelplan UI |  Nog te doen | -                   |

  Gemaakte bestanden:
  - lib/types/leefgebieden.ts - 7 domeinen met kleuren/emoji's
  - lib/types/behandelplan.ts - SMART doelen, interventies, Zod schemas
  - components/behandelplan/leefgebieden-form.tsx - Intake formulier
  - components/behandelplan/leefgebieden-scores.tsx - Score weergave
  - components/behandelplan/leefgebieden-badge.tsx - Domain badges
  - components/behandelplan/index.ts - Exports
This commit is contained in:
colinislit
2025-12-04 09:09:23 +01:00
parent 8e8a8e6f49
commit 621e1c90f9
22 changed files with 3369 additions and 12 deletions

View File

@@ -0,0 +1,719 @@
# Technisch Ontwerp (TO) — Behandelplan Module
**Projectnaam:** Mini-EPD Prototype - AI Speedrun
**Versie:** v1.0
**Datum:** 03-12-2024
**Auteur:** Colin Lit
---
## 1. Doel en relatie met PRD en FO
**Doel van dit document:**
Dit Technisch Ontwerp beschrijft **hoe** de Behandelplan module technisch wordt gebouwd. Het PRD beschrijft het *wat*, het FO het *hoe functioneel*, en dit TO de *technische implementatie*.
**Gerelateerde documenten:**
- PRD: `prd-behandelplan-v2-final.md`
- FO: `fo-behandelplan-v1.md`
- Implementatieplan: `~/.claude/plans/effervescent-toasting-beaver.md`
**Scope:**
- Foundation first: Types → Database → Components → AI → UI
- Simple JSON API (geen streaming)
- Simpele leefgebieden visualisatie (progress bars, geen radar chart)
---
## 2. Technische Architectuur Overzicht
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ FRONTEND (Next.js 14) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ Behandelplan │ │ Leefgebieden │ │ SMART Doelen │ │
│ │ Page │ │ Components │ │ Components │ │
│ │ (Server Comp) │ │ (Client Comp) │ │ (Client Comp) │ │
│ └────────┬────────┘ └────────┬────────┘ └──────────────┬──────────────┘ │
│ │ │ │ │
│ └────────────────────┴──────────────────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ Server Actions │ │
│ │ (behandelplan/ │ │
│ │ actions.ts) │ │
│ └──────────┬──────────┘ │
└────────────────────────────────────┼────────────────────────────────────────┘
┌────────────────────────────────────┼────────────────────────────────────────┐
│ API ROUTES │
│ ┌─────────────────────────────────▼─────────────────────────────────────┐ │
│ │ /api/behandelplan/generate │ │
│ │ (POST - AI Generation) │ │
│ └─────────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────▼─────────────────────────────────────┐ │
│ │ /api/behandelplan/regenerate-section │ │
│ │ (POST - Micro-regeneration) │ │
│ └─────────────────────────────────┬─────────────────────────────────────┘ │
└────────────────────────────────────┼────────────────────────────────────────┘
┌────────────────────────────────────┼────────────────────────────────────────┐
│ EXTERNAL SERVICES │
│ ┌─────────────────┐ │ ┌─────────────────────┐ │
│ │ Supabase │◄─────────────┴──────────────► Claude API │ │
│ │ (PostgreSQL) │ │ (Anthropic) │ │
│ │ - care_plans │ │ - claude-sonnet │ │
│ │ - patients │ │ - JSON response │ │
│ │ - intakes │ │ │ │
│ └─────────────────┘ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
```
---
## 3. Techstack Selectie
### Bestaande Stack (hergebruiken)
| Component | Technologie | Status | Argumentatie |
|-----------|-------------|--------|--------------|
| Frontend | Next.js 14.2.18 | ✅ Bestaand | React framework, SSR, App Router |
| Backend | Next.js API Routes | ✅ Bestaand | Co-located, TypeScript |
| Database | Supabase PostgreSQL | ✅ Bestaand | RLS, FHIR-compliant schema |
| AI | Claude Sonnet | ✅ Bestaand | API key geconfigureerd |
| Styling | TailwindCSS 3.4 | ✅ Bestaand | Utility-first, shadcn/ui |
| Icons | Lucide React | ✅ Bestaand | Consistent icon set |
| Editor | TipTap | ✅ Bestaand | Rich text editor |
| Validation | Zod | ✅ Bestaand | Schema validation |
### Nieuwe Dependencies
| Component | Technologie | Nodig voor | Alternatief |
|-----------|-------------|------------|-------------|
| Charts | Recharts 2.x | Radar chart (stretch) | ❌ Later toevoegen |
**Conclusie:** Geen nieuwe dependencies nodig voor MVP. Recharts alleen bij stretch goal.
---
## 4. Datamodel
### 4.1 Bestaande Tabellen (hergebruiken)
```sql
-- FHIR CarePlan (hoofdtabel voor behandelplannen)
care_plans (
id UUID PRIMARY KEY,
patient_id UUID REFERENCES patients(id),
title TEXT,
status careplan_status, -- draft | active | completed | revoked
intent TEXT,
goals JSONB, -- Array van doelen
activities JSONB, -- Array van interventies
based_on_intake_id UUID,
based_on_anamneses UUID[],
based_on_examinations UUID[],
based_on_risk_assessments UUID[],
care_team_ids UUID[],
author_id UUID,
period_start DATE,
period_end DATE,
created_at TIMESTAMP,
updated_at TIMESTAMP
)
-- Conditions (diagnoses - input voor AI)
conditions (
id UUID,
patient_id UUID,
code TEXT, -- DSM-5 code
code_system TEXT,
display_text TEXT,
category TEXT,
clinical_status TEXT,
severity TEXT, -- laag | middel | hoog
encounter_id UUID
)
-- Intakes (bron voor AI context)
intakes (
id UUID,
patient_id UUID,
status intake_status,
treatment_advice JSONB,
kindcheck_data JSONB,
notes TEXT
)
```
### 4.2 Nieuwe Velden / Migratie
```sql
-- Migratie: Leefgebieden toevoegen aan intakes
ALTER TABLE intakes
ADD COLUMN life_domains JSONB;
-- Migratie: Behandelplan specifieke velden aan care_plans
ALTER TABLE care_plans
ADD COLUMN version INTEGER DEFAULT 1,
ADD COLUMN published_at TIMESTAMP,
ADD COLUMN behandelstructuur JSONB,
ADD COLUMN evaluatiemomenten JSONB,
ADD COLUMN veiligheidsplan JSONB;
-- Constraint voor versie-beheer
ALTER TABLE care_plans
ADD CONSTRAINT unique_patient_version UNIQUE (patient_id, version);
```
### 4.3 JSONB Structuren
**life_domains (in intakes):**
```typescript
interface LifeDomainScore {
domain: 'dlv' | 'wonen' | 'werk' | 'sociaal' | 'vrijetijd' | 'financien' | 'gezondheid'
baseline: number // 1-5
current: number // 1-5
target: number // 1-5
notes: string
priority: 'laag' | 'middel' | 'hoog'
}
// life_domains: LifeDomainScore[]
```
**goals (in care_plans):**
```typescript
interface SmartGoal {
id: string
title: string
description: string
clientVersion: string // B1-taal
lifeDomain: LifeDomain
priority: 'hoog' | 'middel' | 'laag'
measurability: string
timelineWeeks: number
status: 'niet_gestart' | 'bezig' | 'gehaald' | 'bijgesteld'
progress: number // 0-100
}
```
**activities (in care_plans):**
```typescript
interface Intervention {
id: string
name: string
description: string
rationale: string
linkedGoalIds: string[]
}
```
**behandelstructuur:**
```typescript
interface Behandelstructuur {
duur: string // "8 weken"
frequentie: string // "Wekelijks"
aantalSessies: number // 8
vorm: string // "Individueel"
}
```
**evaluatiemomenten:**
```typescript
interface Evaluatiemoment {
id: string
type: 'tussentijds' | 'eind' | 'crisis'
weekNumber: number
plannedDate: string
actualDate?: string
status: 'gepland' | 'afgerond' | 'overgeslagen'
outcome?: string
lifeDomainUpdates?: LifeDomainScore[]
}
```
### 4.4 ERD
```
patients ─1:N─ intakes ─1:1─ life_domains (JSONB)
│ │
│ └────────── anamneses ─────────┐
│ └────────── examinations ──────┤
│ └────────── risk_assessments ──┤
│ │
└─1:N─ care_plans ────────────────────────────┘
│ (based_on_*)
├── goals (JSONB)
├── activities (JSONB)
├── behandelstructuur (JSONB)
├── evaluatiemomenten (JSONB)
└── veiligheidsplan (JSONB)
└─1:N─ conditions (diagnoses - input voor AI)
```
---
## 5. API Ontwerp
### 5.1 Endpoints Overzicht
| Endpoint | Method | Input | Output | Auth |
|----------|--------|-------|--------|------|
| `/api/behandelplan/generate` | POST | GenerateInput | GeneratedPlan | Required |
| `/api/behandelplan/regenerate-section` | POST | RegenerateInput | RegeneratedSection | Required |
### 5.2 POST /api/behandelplan/generate
**Request:**
```typescript
interface GenerateInput {
patientId: string // UUID
intakeId: string // UUID
conditionId?: string // UUID (optioneel, haalt anders laatste op)
extraInstructions?: string // Optionele aanvullende instructies
}
```
**Response:**
```typescript
interface GeneratedPlan {
behandelstructuur: Behandelstructuur
doelen: SmartGoal[]
interventies: Intervention[]
evaluatiemomenten: Evaluatiemoment[]
veiligheidsplan?: Veiligheidsplan // Alleen bij severity "Hoog"
}
```
**Error Responses:**
- `400`: Validation error (missing fields, invalid UUIDs)
- `401`: Unauthorized
- `404`: Patient/Intake/Condition not found
- `422`: Insufficient data for generation (no intake notes, no diagnosis)
- `500`: AI API error
- `503`: AI service unavailable
### 5.3 POST /api/behandelplan/regenerate-section
**Request:**
```typescript
interface RegenerateInput {
patientId: string
carePlanId: string
sectionType: 'goal' | 'intervention'
sectionId: string
instruction?: string // Extra instructie voor AI
currentPlan: GeneratedPlan // Context van huidige plan
}
```
**Response:**
```typescript
interface RegeneratedSection {
type: 'goal' | 'intervention'
original: SmartGoal | Intervention
regenerated: SmartGoal | Intervention
}
```
### 5.4 Zod Validation Schemas
```typescript
// lib/types/behandelplan.ts
export const GenerateInputSchema = z.object({
patientId: z.string().uuid(),
intakeId: z.string().uuid(),
conditionId: z.string().uuid().optional(),
extraInstructions: z.string().max(500).optional(),
});
export const RegenerateInputSchema = z.object({
patientId: z.string().uuid(),
carePlanId: z.string().uuid(),
sectionType: z.enum(['goal', 'intervention']),
sectionId: z.string().uuid(),
instruction: z.string().max(200).optional(),
currentPlan: GeneratedPlanSchema,
});
```
---
## 6. Security & Compliance
### 6.1 Security Checklist
- [x] **Authentication:** Supabase Auth (bestaand)
- [x] **Authorization:** Row Level Security op care_plans
- [x] **Data Encryption:** At rest (PostgreSQL), in transit (HTTPS)
- [x] **Input Validation:** Zod schemas op alle endpoints
- [ ] **Rate Limiting:** Toe te voegen op AI endpoints (10 req/min)
- [x] **CORS:** Restrictive origins (bestaand)
- [x] **Secrets:** Environment variables (ANTHROPIC_API_KEY)
### 6.2 RLS Policies voor care_plans
```sql
-- Bestaande RLS policy uitbreiden
ALTER TABLE care_plans ENABLE ROW LEVEL SECURITY;
-- Behandelaars kunnen alleen eigen patiënten zien
CREATE POLICY "Users can view care plans for their patients"
ON care_plans FOR SELECT
USING (
auth.uid() IN (
SELECT practitioner_id FROM patient_practitioners
WHERE patient_id = care_plans.patient_id
)
);
-- Behandelaars kunnen care plans maken voor eigen patiënten
CREATE POLICY "Users can create care plans for their patients"
ON care_plans FOR INSERT
WITH CHECK (
auth.uid() = author_id
);
-- Behandelaars kunnen eigen care plans updaten
CREATE POLICY "Users can update their care plans"
ON care_plans FOR UPDATE
USING (auth.uid() = author_id);
```
### 6.3 AVG/GDPR Overwegingen
- **Data minimalisatie:** Alleen noodzakelijke velden in AI prompt
- **Geen BSN/identificerende data naar AI:** Alleen intake notities en scores
- **Audit trail:** Bestaande `ai_events` tabel loggen van AI calls
- **Consent:** AI-gebruik gedekt onder behandelrelatie
---
## 7. AI/LLM Integratie
### 7.1 AI Stack
| Component | Waarde |
|-----------|--------|
| Provider | Anthropic |
| Model | claude-3-5-sonnet-20240620 (of claude-sonnet-4) |
| Library | Native fetch (geen SDK nodig) |
| Caching | Geen (elke generatie is uniek) |
| Fallback | Error message + manual mode optie |
### 7.2 Prompt Template
```typescript
// lib/ai/behandelplan-prompt.ts
export const BEHANDELPLAN_SYSTEM_PROMPT = `
Je bent een ervaren GGZ-behandelaar die behandelplannen opstelt.
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)
5. Genereer voor elk doel een B1-taal versie (cliënt-vriendelijk)
6. Kies evidence-based interventies passend bij de DSM-categorie
7. Plan 8-12 sessies afhankelijk van severity
8. Voeg veiligheidsplan toe alleen bij severity "Hoog"
OUTPUT FORMAT:
Retourneer ALLEEN valide JSON volgens het volgende schema:
{
"behandelstructuur": {
"duur": "8 weken",
"frequentie": "Wekelijks",
"aantalSessies": 8,
"vorm": "Individueel"
},
"doelen": [...],
"interventies": [...],
"evaluatiemomenten": [...],
"veiligheidsplan": null | {...}
}
`;
export function buildUserPrompt(context: PlanContext): string {
return `
CLIËNT CONTEXT:
- Intake notities: ${context.intakeNotes}
- DSM-categorie: ${context.dsmCategory}
- Severity: ${context.severity}
LEEFGEBIEDEN SCORES:
${context.lifeDomains.map(d =>
`- ${d.domain}: ${d.baseline}/5 (prioriteit: ${d.priority})`
).join('\n')}
${context.extraInstructions ? `EXTRA INSTRUCTIES:\n${context.extraInstructions}` : ''}
Genereer nu een behandelplan.
`;
}
```
### 7.3 Evidence-Based Mapping
```typescript
// lib/ai/intervention-mapping.ts
export const INTERVENTION_MAPPING: Record<string, InterventionSuggestion[]> = {
'angststoornissen': [
{ name: 'CGT', sessions: { laag: 8, middel: 10, hoog: 14 } },
{ name: 'Exposure therapie', sessions: { laag: 6, middel: 8, hoog: 12 } },
{ name: 'ACT', sessions: { laag: 8, middel: 10, hoog: 12 } },
],
'stemmingsklachten': [
{ name: 'CGT', sessions: { laag: 8, middel: 10, hoog: 14 } },
{ name: 'IPT', sessions: { laag: 8, middel: 12, hoog: 16 } },
{ name: 'Gedragsactivatie', sessions: { laag: 6, middel: 8, hoog: 10 } },
],
'trauma_ptss': [
{ name: 'EMDR', sessions: { laag: 6, middel: 10, hoog: 16 } },
{ name: 'Narratieve therapie', sessions: { laag: 8, middel: 12, hoog: 16 } },
],
'persoonlijkheid': [
{ name: 'Schematherapie', sessions: { laag: 16, middel: 24, hoog: 40 } },
{ name: 'MBT', sessions: { laag: 16, middel: 24, hoog: 40 } },
],
};
```
### 7.4 API Call Implementatie
```typescript
// app/api/behandelplan/generate/route.ts
export async function POST(request: NextRequest) {
// 1. Validate input
const body = await request.json();
const input = GenerateInputSchema.parse(body);
// 2. Load context from database
const context = await loadPlanContext(input);
// 3. Build prompt
const messages = [
{ role: 'system', content: BEHANDELPLAN_SYSTEM_PROMPT },
{ role: 'user', content: buildUserPrompt(context) },
];
// 4. Call Claude API
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.ANTHROPIC_API_KEY!,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: 'claude-3-5-sonnet-20240620',
max_tokens: 4096,
temperature: 0.3,
messages,
}),
});
// 5. Parse and validate response
const result = await response.json();
const plan = GeneratedPlanSchema.parse(
JSON.parse(result.content[0].text)
);
// 6. Log to ai_events
await logAIEvent('behandelplan_generate', input, plan);
return NextResponse.json(plan);
}
```
---
## 8. Performance & Scalability
### 8.1 Performance Targets
| Metric | Target | Huidige Baseline |
|--------|--------|------------------|
| Page load (FCP) | < 1.5s | ~1s (andere pagina's) |
| API response | < 500ms | ~300ms (intakes) |
| AI generation | < 8s | N/A (nieuw) |
| Auto-save | < 500ms | ~300ms (reports) |
### 8.2 Optimalisaties
**Frontend:**
- Server Components voor initial load (geen client JS voor data)
- Skeleton loaders tijdens AI generatie
- Optimistic updates voor status wijzigingen
**Backend:**
- Parallel database queries voor context loading
- Geen caching van AI responses (elke generatie uniek)
- Connection pooling via Supabase (bestaand)
**AI:**
- Max tokens: 4096 (voldoende voor plan JSON)
- Temperature: 0.3 (consistent maar niet robotisch)
- Retry logic: 2x met exponential backoff
---
## 9. Deployment & CI/CD
### 9.1 Omgevingen (bestaand)
| Omgeving | URL | Database |
|----------|-----|----------|
| Development | localhost:3000 | Local Supabase |
| Preview | Vercel preview | Supabase preview branch |
| Production | [main domain] | Supabase production |
### 9.2 Migratie Workflow (Cloud-only)
```bash
# Geen lokale Supabase instantie - direct naar cloud
# Optie 1: Via Supabase MCP tool
mcp__supabase__apply_migration(name, query)
# Optie 2: Via Supabase CLI
npx supabase db push --linked
# Types genereren na migratie
mcp__supabase__generate_typescript_types
```
**Let op:** Geen `supabase db reset` mogelijk - migraties zijn direct productie.
### 9.3 Deployment Checklist
- [ ] Environment variables in Vercel dashboard
- [ ] Database migraties toegepast
- [ ] TypeScript types gegenereerd (`supabase gen types typescript`)
- [ ] Build succesvol (`pnpm build`)
- [ ] Smoke test op preview environment
---
## 10. Monitoring & Logging
### 10.1 AI Event Logging (bestaand)
```sql
-- Bestaande ai_events tabel
ai_events (
id UUID,
kind TEXT, -- 'behandelplan_generate' | 'behandelplan_regenerate'
request JSONB, -- Input parameters
response JSONB, -- Generated plan
duration_ms INTEGER,
created_at TIMESTAMP
)
```
### 10.2 Metrics te Tracken
| Metric | Doel | Actie bij Overschrijding |
|--------|------|--------------------------|
| AI success rate | > 95% | Check prompts, input validation |
| AI response time p95 | < 8s | Optimize prompt size |
| Error rate | < 2% | Alert + investigate |
---
## 11. Risico's & Technische Mitigatie
| Risico | Impact | Kans | Mitigatie |
|--------|--------|------|-----------|
| AI genereert invalide JSON | Hoog | Middel | Zod validation, retry logic, fallback |
| AI API down/rate limited | Hoog | Laag | Error message, manual mode optie |
| Grote intake teksten (token limit) | Middel | Middel | Truncate/summarize intake eerst |
| Inconsistente B1-taal kwaliteit | Middel | Middel | Post-processing, behandelaar review |
| Performance bij grote plannen | Laag | Laag | Pagination, lazy loading |
---
## 12. Implementatie Volgorde
### Stap 1: Types & Database (2-3 uur)
**Bestanden:**
```
lib/types/
├── behandelplan.ts # Nieuwe types + Zod schemas
└── leefgebieden.ts # Life domain types
supabase/migrations/
└── xxx_add_behandelplan_fields.sql
```
### Stap 2: Leefgebieden Componenten (3-4 uur)
**Bestanden:**
```
components/behandelplan/
├── leefgebieden-form.tsx # Intake formulier (7 sliders)
├── leefgebieden-scores.tsx # Progress bar weergave
└── leefgebieden-badge.tsx # Domain tag/badge
```
### Stap 3: AI Generatie (3-4 uur)
**Bestanden:**
```
lib/ai/
├── behandelplan-prompt.ts # System + user prompts
└── intervention-mapping.ts # Evidence-based mapping
app/api/behandelplan/
├── generate/route.ts # POST endpoint
└── regenerate-section/route.ts # Micro-regeneratie
```
### Stap 4: Behandelplan UI (6-8 uur)
**Bestanden:**
```
app/epd/patients/[id]/behandelplan/
├── page.tsx # Server component (vervang placeholder)
├── actions.ts # Server actions (CRUD)
└── components/
├── behandelplan-view.tsx
├── goals-section.tsx
├── goal-card.tsx
├── interventions-section.tsx
└── generate-button.tsx
```
---
## 13. Bijlagen & Referenties
### Projectdocumenten
- [PRD Behandelplan v2.0](./prd-behandelplan-v2-final.md)
- [FO Behandelplan v1.0](./fo-behandelplan-v1.md)
- [UX Stylesheet](../ux-stylesheet.md)
### Tech Documentatie
- Next.js: https://nextjs.org/docs
- Supabase: https://supabase.com/docs
- Anthropic Claude: https://docs.anthropic.com/claude/reference
### Bestaande Code Referenties
- API pattern: `/app/api/reports/classify/route.ts`
- Server actions: `/app/epd/patients/[id]/intakes/[intakeId]/actions.ts`
- Types pattern: `/lib/types/report.ts`
- AI integration: `/app/api/docs/chat/route.ts`
---
**Document Status:** v1.0 Draft
**Volgende Review:** Na implementatie Stap 1-2
**Eigenaar:** Colin van Zeeland