diff --git a/app/api/cortex/chat/route.ts b/app/api/cortex/chat/route.ts
index 97f5931..7e0d576 100644
--- a/app/api/cortex/chat/route.ts
+++ b/app/api/cortex/chat/route.ts
@@ -54,8 +54,8 @@ function checkRateLimit(userId: string): { allowed: boolean; remaining: number;
// Request validation schemas
const ChatMessageSchema = z.object({
id: z.string(),
- type: z.enum(['user', 'assistant', 'system', 'error']),
- content: z.string().min(1),
+ type: z.enum(['user', 'assistant', 'system', 'error', 'nudge']),
+ content: z.string(), // Allow empty strings (streaming starts with empty assistant message)
timestamp: z.string().or(z.date()),
action: z.any().optional(), // ChatAction is optional
});
@@ -189,6 +189,29 @@ Je herkent de volgende gebruikersintenties en voert acties uit:
- Required: identifier
- Actie: Toon edit form met oude en nieuwe tijd
+**P3 Intents (Intake - MVP):**
+
+- **intake_status** — Intake voortgang checklist
+ - Triggers: "wat moet ik nog doen", "wat moet ik nog invullen", "is de intake compleet", "intake checklist", "intake status"
+ - Entities: geen (gebruikt actieve patient context)
+ - Actie: Toon IntakeStatusBlock met completion percentage en secties
+
+- **risico_query** — Risicotaxatie opvragen
+ - Triggers: "wat zijn de risico's", "risico's", "toon risico", "risicotaxatie"
+ - Entities: geen (gebruikt actieve patient context)
+ - Actie: Toon RisicoBlock met risico's en severity levels
+
+- **diagnose_query** — Diagnoses opvragen
+ - Triggers: "welke diagnoses", "wat is de diagnose", "toon diagnoses", "diagnose"
+ - Entities: geen (gebruikt actieve patient context)
+ - Actie: Toon DiagnoseBlock met ICD-10 codes
+
+- **intake_navigeer** — Navigeer naar intake sectie
+ - Triggers: "ga naar risico", "ga naar diagnose", "ga naar kindcheck", "ga naar anamnese", "open risicotaxatie", "naar behandeladvies"
+ - Entities: navigationTarget (risk/diagnosis/kindcheck/anamnese/contacts/examination/rom/behandeladvies)
+ - Required: navigationTarget, actieve patient
+ - Actie: Navigeer naar de intake sectie in het EPD
+
### 2. Verduidelijkingsvragen stellen
Als je twijfelt over de intent of belangrijke informatie mist:
@@ -475,6 +498,94 @@ Je hebt toegang tot de volgende context:
}
\`\`\`"
+### Voorbeeld 9: Intake status
+
+**User:**
+"Wat moet ik nog doen?"
+
+**AI Response:**
+"Even kijken naar de intake status.
+
+\`\`\`json
+{
+ "type": "action",
+ "intent": "intake_status",
+ "entities": {},
+ "confidence": 0.98,
+ "artifact": {
+ "type": "intake_status",
+ "prefill": {}
+ }
+}
+\`\`\`"
+
+### Voorbeeld 10: Risico's opvragen
+
+**User:**
+"Wat zijn de risico's?"
+
+**AI Response:**
+"Hier zijn de risico's.
+
+\`\`\`json
+{
+ "type": "action",
+ "intent": "risico_query",
+ "entities": {},
+ "confidence": 0.98,
+ "artifact": {
+ "type": "risico_query",
+ "prefill": {}
+ }
+}
+\`\`\`"
+
+### Voorbeeld 11: Diagnoses opvragen
+
+**User:**
+"Welke diagnoses?"
+
+**AI Response:**
+"De diagnoses:
+
+\`\`\`json
+{
+ "type": "action",
+ "intent": "diagnose_query",
+ "entities": {},
+ "confidence": 0.98,
+ "artifact": {
+ "type": "diagnose_query",
+ "prefill": {}
+ }
+}
+\`\`\`"
+
+### Voorbeeld 12: Navigeren naar intake sectie
+
+**User:**
+"Ga naar risico"
+
+**AI Response:**
+"Ik open de risicotaxatie.
+
+\`\`\`json
+{
+ "type": "action",
+ "intent": "intake_navigeer",
+ "entities": {
+ "navigationTarget": "risk"
+ },
+ "confidence": 0.98,
+ "artifact": {
+ "type": "intake_navigeer",
+ "prefill": {
+ "navigationTarget": "risk"
+ }
+ }
+}
+\`\`\`"
+
## Error Handling
### Onbekende intent
@@ -482,7 +593,9 @@ Je hebt toegang tot de volgende context:
"Hmm, snap niet helemaal wat je bedoelt. Probeer bijvoorbeeld:
- \"Notitie maken voor [patient]\"
- \"Zoek [patient]\"
-- \"Maak overdracht\""
+- \"Maak overdracht\"
+- \"Wat moet ik nog doen?\" (intake status)
+- \"Wat zijn de risico's?\" (risicotaxatie)"
### Geen patient gevonden
diff --git a/app/api/cortex/intake/diagnose/route.ts b/app/api/cortex/intake/diagnose/route.ts
index 95b1fae..911630b 100644
--- a/app/api/cortex/intake/diagnose/route.ts
+++ b/app/api/cortex/intake/diagnose/route.ts
@@ -74,7 +74,7 @@ export async function GET(request: NextRequest) {
// Parse and validate query parameters
const searchParams = request.nextUrl.searchParams;
const patientId = searchParams.get('patientId');
- const intakeId = searchParams.get('intakeId');
+ const intakeId = searchParams.get('intakeId') || undefined; // Convert null to undefined for Zod
if (!patientId) {
return NextResponse.json(
diff --git a/app/api/cortex/intake/risico/route.ts b/app/api/cortex/intake/risico/route.ts
index 9a8c42c..c8c89a2 100644
--- a/app/api/cortex/intake/risico/route.ts
+++ b/app/api/cortex/intake/risico/route.ts
@@ -93,7 +93,7 @@ export async function GET(request: NextRequest) {
// Parse and validate query parameters
const searchParams = request.nextUrl.searchParams;
const patientId = searchParams.get('patientId');
- const intakeId = searchParams.get('intakeId');
+ const intakeId = searchParams.get('intakeId') || undefined; // Convert null to undefined for Zod
if (!patientId) {
return NextResponse.json(
diff --git a/app/api/cortex/intake/status/route.ts b/app/api/cortex/intake/status/route.ts
index a5a532d..1f12e7c 100644
--- a/app/api/cortex/intake/status/route.ts
+++ b/app/api/cortex/intake/status/route.ts
@@ -71,9 +71,12 @@ export async function GET(request: NextRequest) {
// Parse and validate query parameters
const searchParams = request.nextUrl.searchParams;
const patientId = searchParams.get('patientId');
- const intakeId = searchParams.get('intakeId');
+ const intakeId = searchParams.get('intakeId') || undefined; // Convert null to undefined for Zod
+
+ console.log('[Intake Status API] Request params:', { patientId, intakeId });
if (!patientId) {
+ console.log('[Intake Status API] Missing patientId');
return NextResponse.json(
{ error: 'Query parameter "patientId" is verplicht' },
{ status: 400 }
@@ -85,6 +88,7 @@ export async function GET(request: NextRequest) {
const errorMessage = validation.error.issues
.map((e) => e.message)
.join(', ');
+ console.log('[Intake Status API] Validation failed:', errorMessage);
return NextResponse.json({ error: errorMessage }, { status: 400 });
}
@@ -110,8 +114,9 @@ export async function GET(request: NextRequest) {
}
if (!latestIntake) {
+ console.log('[Intake Status API] No active intake found for patient:', patientId);
return NextResponse.json(
- { error: 'Geen actieve intake gevonden voor deze patiënt' },
+ { error: 'Geen actieve intake gevonden voor deze patiënt. Start eerst een intake.' },
{ status: 404 }
);
}
diff --git a/components/cortex/artifacts/artifact-container.tsx b/components/cortex/artifacts/artifact-container.tsx
index ef88fd6..211d759 100644
--- a/components/cortex/artifacts/artifact-container.tsx
+++ b/components/cortex/artifacts/artifact-container.tsx
@@ -18,6 +18,10 @@ import { ZoekenBlock } from '../blocks/zoeken-block';
import { OverdrachtBlock } from '../blocks/overdracht-block';
import { PatientDashboardBlock } from '../blocks/patient-dashboard-block';
import { FallbackPicker } from '../blocks/fallback-picker';
+// Intake MVP blocks
+import { IntakeStatusBlock } from '../blocks/intake-status-block';
+import { RisicoBlock } from '../blocks/risico-block';
+import { DiagnoseBlock } from '../blocks/diagnose-block';
import { APPOINTMENT_TYPES, type AppointmentTypeCode, type LocationClassCode } from '@/app/epd/agenda/types';
import type { Artifact, BlockType } from '@/stores/cortex-store';
import { parseRelativeDate, isDateRange } from '@/lib/cortex/date-time-parser';
@@ -194,6 +198,13 @@ function renderArtifactBlock(artifact: Artifact, onCloseArtifact: (id: string) =
return ;
case 'fallback':
return ;
+ // Intake MVP blocks
+ case 'intake_status':
+ return ;
+ case 'risico_query':
+ return ;
+ case 'diagnose_query':
+ return ;
default:
return (
@@ -232,6 +243,13 @@ export function getArtifactTitle(type: BlockType, prefill?: any): string {
return prefill?.patientName
? `Dashboard - ${prefill.patientName}`
: 'Patiëntoverzicht';
+ // Intake MVP blocks
+ case 'intake_status':
+ return 'Intake Status';
+ case 'risico_query':
+ return 'Risicotaxatie';
+ case 'diagnose_query':
+ return 'Diagnoses';
default:
return 'Artifact';
}
diff --git a/docs/intent/intake-intent-proces/session-log-2026-02-03.md b/docs/intent/intake-intent-proces/session-log-2026-02-03.md
new file mode 100644
index 0000000..8401929
--- /dev/null
+++ b/docs/intent/intake-intent-proces/session-log-2026-02-03.md
@@ -0,0 +1,346 @@
+# Session Log — 03-02-2026
+
+**Project:** Cortex Intake Blocks MVP
+**Sessie:** Testing, Bugfixes & Integration
+**Tijd:** Avond
+
+---
+
+## Samenvatting
+
+Deze sessie richtte zich op het volledig werkend krijgen van de Intake Blocks MVP:
+1. Testplan maken
+2. Meerdere kritieke bugs oplossen
+3. Intake intents integreren in de volledige flow
+
+**Eindresultaat:** ✅ Alle 4 intake intents werken nu volledig
+
+---
+
+## Bugs Opgelost
+
+### Bug 1: Chat API Validatiefout
+
+**Symptoom:** `Chat API error: Error: Validatiefout`
+
+**Oorzaak:** Twee issues in Zod schema:
+1. `nudge` type ontbrak in `ChatMessageSchema.type` enum
+2. `content: z.string().min(1)` blokkeerde lege streaming berichten
+
+**Fix:** `app/api/cortex/chat/route.ts`
+```typescript
+// VOOR:
+type: z.enum(['user', 'assistant', 'system', 'error']),
+content: z.string().min(1),
+
+// NA:
+type: z.enum(['user', 'assistant', 'system', 'error', 'nudge']),
+content: z.string(), // Allow empty
+```
+
+---
+
+### Bug 2: AI Chat herkent intake intents niet
+
+**Symptoom:** Chat AI vraagt "Wil je het dossier opzoeken?" ipv navigeren
+
+**Oorzaak:** Intake intents (`intake_status`, `risico_query`, `diagnose_query`, `intake_navigeer`) ontbraken in de AI system prompt.
+
+**Fix:** `app/api/cortex/chat/route.ts` - System prompt uitgebreid met:
+- P3 Intents sectie met 4 intake intents
+- Triggers en entities per intent
+- 4 voorbeelden (voorbeeld 9-12) met correcte JSON format
+
+---
+
+### Bug 3: Intake blocks worden niet gerenderd
+
+**Symptoom:** Console log toont "Opening artifact: intake_status" maar niets verschijnt
+
+**Oorzaak:** `artifact-container.tsx` miste:
+1. Imports voor intake block components
+2. Switch cases in `renderArtifactBlock()`
+3. Titles in `getArtifactTitle()`
+
+**Fix:** `components/cortex/artifacts/artifact-container.tsx`
+```typescript
+// Imports toegevoegd:
+import { IntakeStatusBlock } from '../blocks/intake-status-block';
+import { RisicoBlock } from '../blocks/risico-block';
+import { DiagnoseBlock } from '../blocks/diagnose-block';
+
+// Switch cases toegevoegd:
+case 'intake_status':
+ return ;
+case 'risico_query':
+ return ;
+case 'diagnose_query':
+ return ;
+```
+
+---
+
+### Bug 4: API retourneert 400 Bad Request
+
+**Symptoom:** `/api/cortex/intake/status?patientId=xxx` → 400 error
+
+**Oorzaak:** `searchParams.get('intakeId')` retourneert `null`, maar Zod `.optional()` verwacht `undefined`.
+
+**Fix:** Alle 3 intake API routes:
+```typescript
+// VOOR:
+const intakeId = searchParams.get('intakeId');
+
+// NA:
+const intakeId = searchParams.get('intakeId') || undefined;
+```
+
+**Bestanden:**
+- `app/api/cortex/intake/status/route.ts`
+- `app/api/cortex/intake/risico/route.ts`
+- `app/api/cortex/intake/diagnose/route.ts`
+
+---
+
+## Lessons Learned
+
+### 1. Zod + URLSearchParams
+`searchParams.get()` retourneert `null`, niet `undefined`. Gebruik altijd `|| undefined` voor optionele Zod parameters.
+
+### 2. AI Chat System Prompt
+Nieuwe intents moeten expliciet in de system prompt staan met:
+- Intent naam
+- Trigger voorbeelden
+- Entities
+- Voorbeeld JSON output
+
+### 3. Artifact Rendering
+Nieuwe blocks vereisen wijzigingen op 3 plekken in `artifact-container.tsx`:
+1. Import statement
+2. Switch case in `renderArtifactBlock()`
+3. Title in `getArtifactTitle()`
+
+### 4. Intent Flow
+Een intent doorloopt meerdere lagen - elke laag moet de intent kennen:
+1. Reflex classifier (patterns)
+2. AI chat (system prompt)
+3. Action parser (routing)
+4. Artifact container (rendering)
+
+---
+
+## Checklist: Nieuwe Intent Toevoegen
+
+### Stap 1: Types & Patterns
+
+**`lib/cortex/types.ts`**
+```typescript
+// 1. Voeg toe aan CortexIntent type
+type CortexIntent =
+ | 'bestaande_intent'
+ | 'nieuwe_intent' // ← nieuw
+ | 'unknown';
+
+// 2. Voeg toe aan BLOCK_CONFIGS (als het een block is)
+nieuwe_intent: {
+ type: 'nieuwe_intent',
+ title: 'Nieuwe Intent',
+ size: 'md',
+ icon: 'IconName',
+},
+```
+
+**`lib/cortex/reflex-classifier.ts`**
+```typescript
+// Voeg patterns toe aan REFLEX_PATTERNS
+nieuwe_intent: [
+ { pattern: /^trigger woorden/i, weight: 1.0 },
+ { pattern: /^alternatieve trigger/i, weight: 0.9 },
+],
+```
+
+### Stap 2: AI Chat Integration
+
+**`app/api/cortex/chat/route.ts`**
+```typescript
+// In buildSystemPrompt(), voeg toe aan intents lijst:
+- **nieuwe_intent** — Beschrijving
+ - Triggers: "woord1", "woord2", "woord3"
+ - Entities: entity1 (type), entity2 (type)
+ - Actie: Wat er gebeurt
+
+// Voeg een voorbeeld toe:
+### Voorbeeld N: Nieuwe Intent
+
+**User:**
+"trigger zin"
+
+**AI Response:**
+"Korte bevestiging.
+
+\`\`\`json
+{
+ "type": "action",
+ "intent": "nieuwe_intent",
+ "entities": { ... },
+ "confidence": 0.98,
+ "artifact": {
+ "type": "nieuwe_intent",
+ "prefill": { ... }
+ }
+}
+\`\`\`"
+```
+
+### Stap 3: Action Parser
+
+**`lib/cortex/action-parser.ts`**
+```typescript
+// 1. Voeg toe aan VALID_INTENTS array
+const VALID_INTENTS = [
+ 'bestaande_intent',
+ 'nieuwe_intent', // ← nieuw
+];
+
+// 2. Voeg toe aan routeIntentToArtifact()
+case 'nieuwe_intent':
+ return {
+ type: 'nieuwe_intent',
+ prefill: { ...entities },
+ };
+```
+
+### Stap 4: API Route (als data nodig is)
+
+**`app/api/cortex/[domain]/route.ts`**
+```typescript
+// Query params validatie - let op null → undefined!
+const optionalParam = searchParams.get('param') || undefined;
+
+// Zod schema
+const QuerySchema = z.object({
+ requiredParam: z.string().uuid(),
+ optionalParam: z.string().optional(), // NOT .nullable()!
+});
+```
+
+### Stap 5: Block Component
+
+**`components/cortex/blocks/nieuwe-intent-block.tsx`**
+```typescript
+export function NieuweIntentBlock({ prefill }: Props) {
+ const { patientId, hasPatientContext } = useIntakeContext(prefill);
+
+ const { data, isLoading, error } = useBlockData({
+ endpoint: '/api/cortex/...',
+ params: { patientId },
+ enabled: hasPatientContext,
+ });
+
+ if (!hasPatientContext) return ;
+ if (isLoading) return ;
+ if (error) return ;
+
+ return ...;
+}
+```
+
+### Stap 6: Artifact Container
+
+**`components/cortex/artifacts/artifact-container.tsx`**
+```typescript
+// 1. Import
+import { NieuweIntentBlock } from '../blocks/nieuwe-intent-block';
+
+// 2. renderArtifactBlock() switch case
+case 'nieuwe_intent':
+ return ;
+
+// 3. getArtifactTitle() switch case
+case 'nieuwe_intent':
+ return 'Nieuwe Intent Titel';
+```
+
+### Stap 7: Navigatie Intent (optioneel)
+
+Als de intent navigeert ipv een block toont:
+
+**`components/cortex/command-center/command-center.tsx`**
+```typescript
+// In useEffect voor pendingAction:
+if (pendingAction.intent === 'nieuwe_navigeer') {
+ // Extract target
+ const target = pendingAction.entities.navigationTarget;
+
+ // Navigate
+ router.push(`/epd/path/${target}`);
+
+ // Toast feedback
+ toast({ title: 'Navigeren...', description: `Naar ${target}` });
+
+ // Clear action
+ setPendingAction(null);
+ return;
+}
+```
+
+---
+
+## Checklist: Nieuwe UI Block
+
+1. **Block component maken**
+ - `components/cortex/blocks/xxx-block.tsx`
+ - Gebruik shared components: `BlockContainer`, `BlockLoading`, `BlockError`, `BlockEmpty`
+ - Gebruik hooks: `useBlockData`, `useIntakeContext`
+
+2. **API route maken** (indien nodig)
+ - `app/api/cortex/[domain]/route.ts`
+ - Let op: `|| undefined` voor optionele params!
+
+3. **Artifact container updaten**
+ - Import toevoegen
+ - Switch case toevoegen
+ - Title toevoegen
+
+4. **Types updaten**
+ - `BLOCK_CONFIGS` in `types.ts`
+
+---
+
+## Gewijzigde Bestanden (Totaal)
+
+| Bestand | Wijziging |
+|---------|-----------|
+| `app/api/cortex/chat/route.ts` | Zod fix + intake intents in prompt |
+| `app/api/cortex/intake/status/route.ts` | null → undefined fix + logging |
+| `app/api/cortex/intake/risico/route.ts` | null → undefined fix |
+| `app/api/cortex/intake/diagnose/route.ts` | null → undefined fix |
+| `lib/cortex/chat-api.ts` | Betere error logging |
+| `lib/cortex/hooks/use-block-data.ts` | Error details logging |
+| `components/cortex/artifacts/artifact-container.tsx` | Intake blocks import + render |
+| `docs/.../testplan-intake-blocks-mvp.md` | Nieuw testplan |
+| `docs/.../session-log-2026-02-03.md` | Deze log |
+
+---
+
+## Eindstatus
+
+| Component | Status |
+|-----------|--------|
+| `intake_status` intent | ✅ Werkt |
+| `risico_query` intent | ✅ Werkt |
+| `diagnose_query` intent | ✅ Werkt |
+| `intake_navigeer` intent | ✅ Werkt |
+| IntakeStatusBlock | ✅ Toont data |
+| RisicoBlock | ✅ Toont data |
+| DiagnoseBlock | ✅ Toont data |
+| Navigatie | ✅ Werkt met toast |
+
+---
+
+## Volgende Stappen
+
+1. [ ] E2E tests uitvoeren volgens testplan
+2. [ ] Performance monitoring (API response times)
+3. [ ] Voice input testen
+4. [ ] Fase 3b intents toevoegen (kindcheck, anamnese, behandeladvies)
diff --git a/docs/intent/intake-intent-proces/testplan-intake-blocks-mvp.md b/docs/intent/intake-intent-proces/testplan-intake-blocks-mvp.md
new file mode 100644
index 0000000..39ae19f
--- /dev/null
+++ b/docs/intent/intake-intent-proces/testplan-intake-blocks-mvp.md
@@ -0,0 +1,449 @@
+# Testplan: Cortex Intake Blocks MVP
+
+**Project:** Cortex Intake Blocks - MVP
+**Versie:** v1.0
+**Datum:** 03-02-2026
+**Status:** E6.S2 - E2E Testing
+
+---
+
+## 1. Overzicht
+
+Dit testplan beschrijft de tests voor de 4 MVP intents:
+- `intake_status` → IntakeStatusBlock
+- `risico_query` → RisicoBlock
+- `diagnose_query` → DiagnoseBlock
+- `intake_navigeer` → Navigatie handler
+
+### Test Niveaus
+
+| Niveau | Type | Tools | Status |
+|--------|------|-------|--------|
+| L1 | Unit Tests | Vitest/Jest | ⏳ |
+| L2 | API Tests | curl/httpie | ✅ Ready |
+| L3 | Component Tests | Browser DevTools | ✅ Ready |
+| L4 | E2E Tests | Manual checklist | ✅ Ready |
+
+---
+
+## 2. Prerequisities
+
+### 2.1 Test Data Vereisten
+
+**Patiënt met actieve intake:**
+- Patiënt ID: `_______________`
+- Intake ID: `_______________`
+- Status: `bezig`
+
+**Intake moet bevatten:**
+- [x] Minimaal 1 contact moment
+- [x] Minimaal 1 anamnese entry
+- [x] Minimaal 1 risico assessment (met variërende levels)
+- [x] Kindcheck ingevuld
+- [x] Minimaal 1 diagnose (primair + secundair)
+- [x] Behandeladvies
+
+### 2.2 Browser Setup
+
+```bash
+# Start development server
+pnpm dev
+
+# Open browser
+open http://localhost:3000/epd/dashboard
+```
+
+### 2.3 Test Account
+
+- Ingelogd als behandelaar
+- Toegang tot testpatiënt
+
+---
+
+## 3. L2: API Tests
+
+### 3.1 Status API
+
+```bash
+# Basis test - met patientId
+curl -X GET "http://localhost:3000/api/cortex/intake/status?patientId=" \
+ -H "Cookie: "
+
+# Met expliciete intakeId
+curl -X GET "http://localhost:3000/api/cortex/intake/status?patientId=&intakeId=" \
+ -H "Cookie: "
+```
+
+**Verwachte response:**
+```json
+{
+ "intakeId": "uuid",
+ "patientId": "uuid",
+ "completionPercentage": 67,
+ "status": "bezig",
+ "sections": [
+ { "id": "contacts", "label": "Contactmomenten", "required": false, "completed": true, "count": 3 },
+ { "id": "anamnese", "label": "Anamnese", "required": true, "completed": true, "count": 1 }
+ ],
+ "completedCount": 6,
+ "totalRequired": 5,
+ "lastUpdated": "2026-02-03T10:00:00Z"
+}
+```
+
+| Test Case | Input | Verwacht | ✓/✗ |
+|-----------|-------|----------|-----|
+| AS-01 | Geldige patientId | 200 + data | |
+| AS-02 | Ontbrekende patientId | 400 error | |
+| AS-03 | Ongeldige UUID | 400 error | |
+| AS-04 | Niet-bestaande patient | 404 "Geen actieve intake" | |
+| AS-05 | Geen auth cookie | 401 error | |
+
+---
+
+### 3.2 Risico API
+
+```bash
+curl -X GET "http://localhost:3000/api/cortex/intake/risico?patientId=" \
+ -H "Cookie: "
+```
+
+**Verwachte response:**
+```json
+{
+ "intakeId": "uuid",
+ "patientId": "uuid",
+ "risks": [
+ {
+ "id": "uuid",
+ "type": "Suïcidaliteit",
+ "level": "matig",
+ "rationale": "...",
+ "measures": "...",
+ "assessmentDate": "2026-01-15",
+ "evaluationDate": "2026-02-15",
+ "notes": "..."
+ }
+ ],
+ "summary": {
+ "total": 3,
+ "highestLevel": "matig",
+ "hasSuicideRisk": true,
+ "hasSelfHarmRisk": false,
+ "hasAggressionRisk": true
+ },
+ "lastUpdated": "2026-02-03T10:00:00Z"
+}
+```
+
+| Test Case | Input | Verwacht | ✓/✗ |
+|-----------|-------|----------|-----|
+| AR-01 | Geldige patientId | 200 + risks array | |
+| AR-02 | Intake zonder risico's | 200 + empty array | |
+| AR-03 | Risico met level "acuut" | highestLevel = "acuut" | |
+| AR-04 | Suicide keyword | hasSuicideRisk = true | |
+| AR-05 | Ontbrekende patientId | 400 error | |
+
+---
+
+### 3.3 Diagnose API
+
+```bash
+curl -X GET "http://localhost:3000/api/cortex/intake/diagnose?patientId=" \
+ -H "Cookie: "
+```
+
+**Verwachte response:**
+```json
+{
+ "intakeId": "uuid",
+ "patientId": "uuid",
+ "diagnoses": [
+ {
+ "id": "uuid",
+ "code": "F32.1",
+ "description": "Depressieve stoornis, matig",
+ "codeSystem": "ICD-10",
+ "clinicalStatus": "active",
+ "severity": "matig",
+ "isPrimary": true,
+ "notes": "...",
+ "recordedDate": "2026-01-15"
+ }
+ ],
+ "summary": {
+ "total": 3,
+ "primaryDiagnosis": { ... },
+ "secondaryCount": 2,
+ "hasActiveConditions": true
+ },
+ "lastUpdated": "2026-02-03T10:00:00Z"
+}
+```
+
+| Test Case | Input | Verwacht | ✓/✗ |
+|-----------|-------|----------|-----|
+| AD-01 | Geldige patientId | 200 + diagnoses | |
+| AD-02 | Intake zonder diagnoses | 200 + empty array | |
+| AD-03 | Primaire diagnose aanwezig | isPrimary = true | |
+| AD-04 | Active status | hasActiveConditions = true | |
+| AD-05 | ICD-10 code aanwezig | codeSystem = "ICD-10" | |
+
+---
+
+## 4. L3: Intent Classification Tests
+
+### 4.1 Reflex Classifier Tests
+
+Open browser console op `/epd/dashboard` en test:
+
+```javascript
+// Test in console (als reflex-classifier beschikbaar is)
+// Of test via Cortex input veld
+```
+
+**Handmatige tests via Cortex input:**
+
+| Test ID | Input | Verwacht Intent | Confidence | ✓/✗ |
+|---------|-------|-----------------|------------|-----|
+| RC-01 | "moet ik nog doen" | intake_status | 1.0 | |
+| RC-02 | "wat moet ik nog invullen" | intake_status | 1.0 | |
+| RC-03 | "is de intake compleet" | intake_status | 0.95 | |
+| RC-04 | "intake checklist" | intake_status | 0.95 | |
+| RC-05 | "intake status" | intake_status | 0.95 | |
+| RC-06 | "wat zijn de risico's" | risico_query | 1.0 | |
+| RC-07 | "risicotaxatie" | risico_query | 0.95 | |
+| RC-08 | "toon risico's" | risico_query | 0.95 | |
+| RC-09 | "welke diagnoses" | diagnose_query | 1.0 | |
+| RC-10 | "wat is de diagnose" | diagnose_query | 0.95 | |
+| RC-11 | "toon diagnoses" | diagnose_query | 0.95 | |
+| RC-12 | "ga naar risico" | intake_navigeer | 1.0 | |
+| RC-13 | "ga naar diagnose" | intake_navigeer | 1.0 | |
+| RC-14 | "naar anamnese" | intake_navigeer | 0.9 | |
+| RC-15 | "open kindcheck" | intake_navigeer | 0.95 | |
+
+### 4.2 Escalation Tests
+
+| Test ID | Input | Verwacht | Reden | ✓/✗ |
+|---------|-------|----------|-------|-----|
+| ES-01 | "risico's en maak notitie" | Escalate | multi_intent | |
+| ES-02 | "diagnoses van hem" | Escalate | needs_context | |
+| ES-03 | "risico morgen bekijken" | Escalate | relative_time | |
+
+---
+
+## 5. L4: E2E Scenario Tests
+
+### 5.1 Scenario: Intake Status Bekijken
+
+**Precondities:**
+- Ingelogd
+- Patiënt met actieve intake geselecteerd in context
+
+**Stappen:**
+
+| Stap | Actie | Verwacht Resultaat | ✓/✗ |
+|------|-------|-------------------|-----|
+| 1 | Open Cortex (⌘K) | Command input verschijnt | |
+| 2 | Type "wat moet ik nog doen" | Input wordt herkend | |
+| 3 | Submit (⌘Enter of Enter) | IntakeStatusBlock opent | |
+| 4 | Wacht op laden | Loading spinner → data | |
+| 5 | Check completion ring | Percentage en kleur correct | |
+| 6 | Check verplichte secties | Gemarkeerd met badge | |
+| 7 | Check openstaande secties | Amber highlighted | |
+| 8 | Klik op sectie | Navigeert naar EPD tab | |
+
+**Verwachte States:**
+
+| State | Trigger | Weergave |
+|-------|---------|----------|
+| Loading | API call actief | Spinner + "Intake status laden..." |
+| Success | Data ontvangen | Completion ring + secties lijst |
+| Error | API fout | Foutmelding + "Opnieuw proberen" |
+| Empty | Geen intake | "Geen intake gevonden" |
+| No Patient | Geen context | "Selecteer eerst een patiënt" |
+
+---
+
+### 5.2 Scenario: Risico's Bekijken
+
+**Stappen:**
+
+| Stap | Actie | Verwacht Resultaat | ✓/✗ |
+|------|-------|-------------------|-----|
+| 1 | Type "wat zijn de risico's" | Input herkend | |
+| 2 | Submit | RisicoBlock opent | |
+| 3 | Check summary card | Totaal + hoogste level | |
+| 4 | Check risk flags | Suïcide/zelfbesch./agressie | |
+| 5 | Check risk items | Level badges correct | |
+| 6 | Klik "Naar risicotaxaties" | Navigeert naar EPD | |
+
+**Level Kleuren:**
+
+| Level | Badge Kleur | Achtergrond |
+|-------|-------------|-------------|
+| laag | Groen | bg-green-50 |
+| matig | Oranje | bg-amber-50 |
+| hoog | Rood (licht) | bg-red-50 |
+| acuut | Rood | bg-red-100 |
+
+---
+
+### 5.3 Scenario: Diagnoses Bekijken
+
+**Stappen:**
+
+| Stap | Actie | Verwacht Resultaat | ✓/✗ |
+|------|-------|-------------------|-----|
+| 1 | Type "welke diagnoses" | Input herkend | |
+| 2 | Submit | DiagnoseBlock opent | |
+| 3 | Check hoofddiagnose | Highlighted card | |
+| 4 | Check ICD-10 code | Code + omschrijving | |
+| 5 | Check nevendiagnoses | Lijst met badges | |
+| 6 | Check status badges | Actief/Remissie/etc | |
+| 7 | Klik "Naar diagnoses" | Navigeert naar EPD | |
+
+---
+
+### 5.4 Scenario: Navigatie naar Intake Tab
+
+**Stappen:**
+
+| Stap | Actie | Verwacht Resultaat | ✓/✗ |
+|------|-------|-------------------|-----|
+| 1 | Selecteer patiënt | Context bar toont naam | |
+| 2 | Type "ga naar risico" | Intent herkend | |
+| 3 | Submit | Toast verschijnt | |
+| 4 | Check toast | "Navigeren... Naar risico sectie" | |
+| 5 | Check URL | /epd/patients/{id}/intakes | |
+
+**Tab Mapping Test:**
+
+| Input | Verwachte Tab | ✓/✗ |
+|-------|---------------|-----|
+| "ga naar risico" | risk | |
+| "ga naar diagnose" | diagnosis | |
+| "ga naar anamnese" | anamnese | |
+| "ga naar kindcheck" | kindcheck | |
+| "ga naar contact" | contacts | |
+| "ga naar rom" | rom | |
+| "ga naar behandeladvies" | behandeladvies | |
+| "ga naar onderzoek" | examination | |
+
+---
+
+### 5.5 Scenario: Error Handling
+
+| Test | Actie | Verwacht | ✓/✗ |
+|------|-------|----------|-----|
+| EH-01 | Geen patiënt + "risico's" | BlockEmpty: "Selecteer eerst een patiënt" | |
+| EH-02 | Geen intake + "status" | BlockEmpty: "Geen intake gevonden" | |
+| EH-03 | Netwerk uit + refetch | BlockError met retry optie | |
+| EH-04 | Geen patiënt + "ga naar risico" | Toast: "Geen patiënt geselecteerd" | |
+
+---
+
+## 6. Voice Input Tests
+
+**Vereist:** Deepgram API key geconfigureerd
+
+| Test | Gesproken Input | Verwacht | ✓/✗ |
+|------|-----------------|----------|-----|
+| V-01 | "Wat moet ik nog doen?" | intake_status | |
+| V-02 | "Wat zijn de risico's?" | risico_query | |
+| V-03 | "Welke diagnoses?" | diagnose_query | |
+| V-04 | "Ga naar risico" | intake_navigeer | |
+| V-05 | "Toon risicotaxatie" | risico_query | |
+
+---
+
+## 7. Edge Cases
+
+### 7.1 Data Edge Cases
+
+| Test | Scenario | Verwacht | ✓/✗ |
+|------|----------|----------|-----|
+| EC-01 | 0 risico's | "Geen specifieke risico's" | |
+| EC-02 | 0 diagnoses | "Geen diagnoses gevonden" | |
+| EC-03 | Geen primaire diagnose | "Geen hoofddiagnose" placeholder | |
+| EC-04 | Alle secties compleet | 100% ring (groen) | |
+| EC-05 | 0 secties compleet | 0% ring (rood) | |
+| EC-06 | Zeer lange diagnose tekst | Truncatie/wrap | |
+
+### 7.2 Input Edge Cases
+
+| Test | Input | Verwacht | ✓/✗ |
+|------|-------|----------|-----|
+| IC-01 | "" (leeg) | Geen actie | |
+| IC-02 | "RISICO'S" (uppercase) | risico_query | |
+| IC-03 | " diagnoses " (spaties) | diagnose_query | |
+| IC-04 | "risico diagnose" (ambiguous) | Escalate of hoogste | |
+
+---
+
+## 8. Performance Tests
+
+| Metric | Target | Acceptabel | Meting | ✓/✗ |
+|--------|--------|------------|--------|-----|
+| Reflex classification | <20ms | <50ms | ___ms | |
+| API /status | <500ms | <2s | ___ms | |
+| API /risico | <500ms | <2s | ___ms | |
+| API /diagnose | <500ms | <2s | ___ms | |
+| Block render | <100ms | <300ms | ___ms | |
+
+---
+
+## 9. Test Resultaten Samenvatting
+
+### API Tests
+- [ ] AS-01 t/m AS-05: ___/5 passed
+- [ ] AR-01 t/m AR-05: ___/5 passed
+- [ ] AD-01 t/m AD-05: ___/5 passed
+
+### Intent Classification
+- [ ] RC-01 t/m RC-15: ___/15 passed
+- [ ] ES-01 t/m ES-03: ___/3 passed
+
+### E2E Scenarios
+- [ ] Intake Status: ___/8 stappen OK
+- [ ] Risico's: ___/6 stappen OK
+- [ ] Diagnoses: ___/7 stappen OK
+- [ ] Navigatie: ___/5 stappen OK + ___/8 tabs OK
+
+### Edge Cases
+- [ ] Data: ___/6 passed
+- [ ] Input: ___/4 passed
+
+### Voice Input
+- [ ] V-01 t/m V-05: ___/5 passed
+
+### Performance
+- [ ] Alle metrics binnen target: Ja/Nee
+
+---
+
+## 10. Bekende Issues / Opmerkingen
+
+| # | Issue | Prioriteit | Status |
+|---|-------|------------|--------|
+| 1 | | | |
+| 2 | | | |
+| 3 | | | |
+
+---
+
+## 11. Sign-off
+
+| Rol | Naam | Datum | Akkoord |
+|-----|------|-------|---------|
+| Tester | | | |
+| Developer | | | |
+| Product Owner | | | |
+
+---
+
+## Versiehistorie
+
+| Versie | Datum | Auteur | Wijziging |
+|--------|-------|--------|-----------|
+| v1.0 | 03-02-2026 | Colin Lit | Initieel testplan |
diff --git a/lib/cortex/chat-api.ts b/lib/cortex/chat-api.ts
index 443bab5..56d8316 100644
--- a/lib/cortex/chat-api.ts
+++ b/lib/cortex/chat-api.ts
@@ -50,6 +50,10 @@ export async function sendChatMessage(
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: response.statusText }));
+ // Log validation details for debugging
+ if (errorData.details) {
+ console.error('Validation details:', errorData.details);
+ }
throw new Error(errorData.error || 'API request failed');
}
diff --git a/lib/cortex/hooks/use-block-data.ts b/lib/cortex/hooks/use-block-data.ts
index c963cf9..4762c0b 100644
--- a/lib/cortex/hooks/use-block-data.ts
+++ b/lib/cortex/hooks/use-block-data.ts
@@ -95,6 +95,14 @@ export function useBlockData({
setData(result);
} catch (err) {
const statusCode = (err as any)?.statusCode;
+ const details = (err as any)?.details;
+ console.error('[useBlockData] Error:', {
+ operation: operationName,
+ statusCode,
+ message: (err as Error)?.message,
+ details,
+ });
+
const errorInfo = getErrorInfo(err, {
operation: operationName,
statusCode,