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,