fix(cortex): integrate intake intents into full chat + artifact flow
Fixes 4 bugs preventing intake blocks from working: 1. Chat API validation: Add 'nudge' to message type enum, allow empty content for streaming messages 2. AI chat recognition: Add P3 intake intents (intake_status, risico_query, diagnose_query, intake_navigeer) to system prompt with triggers, entities, and JSON examples 3. Artifact rendering: Add intake block imports and switch cases to artifact-container.tsx 4. API 400 error: Convert null to undefined for optional Zod params (searchParams.get returns null, Zod .optional() expects undefined) Also adds: - Testplan for E2E testing (60+ test cases) - Session log with lessons learned and new intent checklist Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,8 +54,8 @@ function checkRateLimit(userId: string): { allowed: boolean; remaining: number;
|
|||||||
// Request validation schemas
|
// Request validation schemas
|
||||||
const ChatMessageSchema = z.object({
|
const ChatMessageSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
type: z.enum(['user', 'assistant', 'system', 'error']),
|
type: z.enum(['user', 'assistant', 'system', 'error', 'nudge']),
|
||||||
content: z.string().min(1),
|
content: z.string(), // Allow empty strings (streaming starts with empty assistant message)
|
||||||
timestamp: z.string().or(z.date()),
|
timestamp: z.string().or(z.date()),
|
||||||
action: z.any().optional(), // ChatAction is optional
|
action: z.any().optional(), // ChatAction is optional
|
||||||
});
|
});
|
||||||
@@ -189,6 +189,29 @@ Je herkent de volgende gebruikersintenties en voert acties uit:
|
|||||||
- Required: identifier
|
- Required: identifier
|
||||||
- Actie: Toon edit form met oude en nieuwe tijd
|
- 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
|
### 2. Verduidelijkingsvragen stellen
|
||||||
|
|
||||||
Als je twijfelt over de intent of belangrijke informatie mist:
|
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
|
## Error Handling
|
||||||
|
|
||||||
### Onbekende intent
|
### Onbekende intent
|
||||||
@@ -482,7 +593,9 @@ Je hebt toegang tot de volgende context:
|
|||||||
"Hmm, snap niet helemaal wat je bedoelt. Probeer bijvoorbeeld:
|
"Hmm, snap niet helemaal wat je bedoelt. Probeer bijvoorbeeld:
|
||||||
- \"Notitie maken voor [patient]\"
|
- \"Notitie maken voor [patient]\"
|
||||||
- \"Zoek [patient]\"
|
- \"Zoek [patient]\"
|
||||||
- \"Maak overdracht\""
|
- \"Maak overdracht\"
|
||||||
|
- \"Wat moet ik nog doen?\" (intake status)
|
||||||
|
- \"Wat zijn de risico's?\" (risicotaxatie)"
|
||||||
|
|
||||||
### Geen patient gevonden
|
### Geen patient gevonden
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export async function GET(request: NextRequest) {
|
|||||||
// Parse and validate query parameters
|
// Parse and validate query parameters
|
||||||
const searchParams = request.nextUrl.searchParams;
|
const searchParams = request.nextUrl.searchParams;
|
||||||
const patientId = searchParams.get('patientId');
|
const patientId = searchParams.get('patientId');
|
||||||
const intakeId = searchParams.get('intakeId');
|
const intakeId = searchParams.get('intakeId') || undefined; // Convert null to undefined for Zod
|
||||||
|
|
||||||
if (!patientId) {
|
if (!patientId) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export async function GET(request: NextRequest) {
|
|||||||
// Parse and validate query parameters
|
// Parse and validate query parameters
|
||||||
const searchParams = request.nextUrl.searchParams;
|
const searchParams = request.nextUrl.searchParams;
|
||||||
const patientId = searchParams.get('patientId');
|
const patientId = searchParams.get('patientId');
|
||||||
const intakeId = searchParams.get('intakeId');
|
const intakeId = searchParams.get('intakeId') || undefined; // Convert null to undefined for Zod
|
||||||
|
|
||||||
if (!patientId) {
|
if (!patientId) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -71,9 +71,12 @@ export async function GET(request: NextRequest) {
|
|||||||
// Parse and validate query parameters
|
// Parse and validate query parameters
|
||||||
const searchParams = request.nextUrl.searchParams;
|
const searchParams = request.nextUrl.searchParams;
|
||||||
const patientId = searchParams.get('patientId');
|
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) {
|
if (!patientId) {
|
||||||
|
console.log('[Intake Status API] Missing patientId');
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Query parameter "patientId" is verplicht' },
|
{ error: 'Query parameter "patientId" is verplicht' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
@@ -85,6 +88,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const errorMessage = validation.error.issues
|
const errorMessage = validation.error.issues
|
||||||
.map((e) => e.message)
|
.map((e) => e.message)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
console.log('[Intake Status API] Validation failed:', errorMessage);
|
||||||
return NextResponse.json({ error: errorMessage }, { status: 400 });
|
return NextResponse.json({ error: errorMessage }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +114,9 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!latestIntake) {
|
if (!latestIntake) {
|
||||||
|
console.log('[Intake Status API] No active intake found for patient:', patientId);
|
||||||
return NextResponse.json(
|
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 }
|
{ status: 404 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import { ZoekenBlock } from '../blocks/zoeken-block';
|
|||||||
import { OverdrachtBlock } from '../blocks/overdracht-block';
|
import { OverdrachtBlock } from '../blocks/overdracht-block';
|
||||||
import { PatientDashboardBlock } from '../blocks/patient-dashboard-block';
|
import { PatientDashboardBlock } from '../blocks/patient-dashboard-block';
|
||||||
import { FallbackPicker } from '../blocks/fallback-picker';
|
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 { APPOINTMENT_TYPES, type AppointmentTypeCode, type LocationClassCode } from '@/app/epd/agenda/types';
|
||||||
import type { Artifact, BlockType } from '@/stores/cortex-store';
|
import type { Artifact, BlockType } from '@/stores/cortex-store';
|
||||||
import { parseRelativeDate, isDateRange } from '@/lib/cortex/date-time-parser';
|
import { parseRelativeDate, isDateRange } from '@/lib/cortex/date-time-parser';
|
||||||
@@ -194,6 +198,13 @@ function renderArtifactBlock(artifact: Artifact, onCloseArtifact: (id: string) =
|
|||||||
return <PatientDashboardBlock key={artifact.id} prefill={artifact.prefill} />;
|
return <PatientDashboardBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
case 'fallback':
|
case 'fallback':
|
||||||
return <FallbackPicker key={artifact.id} originalInput={artifact.prefill?.content} />;
|
return <FallbackPicker key={artifact.id} originalInput={artifact.prefill?.content} />;
|
||||||
|
// Intake MVP blocks
|
||||||
|
case 'intake_status':
|
||||||
|
return <IntakeStatusBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
|
case 'risico_query':
|
||||||
|
return <RisicoBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
|
case 'diagnose_query':
|
||||||
|
return <DiagnoseBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div className="p-4 text-slate-500">
|
<div className="p-4 text-slate-500">
|
||||||
@@ -232,6 +243,13 @@ export function getArtifactTitle(type: BlockType, prefill?: any): string {
|
|||||||
return prefill?.patientName
|
return prefill?.patientName
|
||||||
? `Dashboard - ${prefill.patientName}`
|
? `Dashboard - ${prefill.patientName}`
|
||||||
: 'Patiëntoverzicht';
|
: 'Patiëntoverzicht';
|
||||||
|
// Intake MVP blocks
|
||||||
|
case 'intake_status':
|
||||||
|
return 'Intake Status';
|
||||||
|
case 'risico_query':
|
||||||
|
return 'Risicotaxatie';
|
||||||
|
case 'diagnose_query':
|
||||||
|
return 'Diagnoses';
|
||||||
default:
|
default:
|
||||||
return 'Artifact';
|
return 'Artifact';
|
||||||
}
|
}
|
||||||
|
|||||||
346
docs/intent/intake-intent-proces/session-log-2026-02-03.md
Normal file
346
docs/intent/intake-intent-proces/session-log-2026-02-03.md
Normal file
@@ -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 <IntakeStatusBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
|
case 'risico_query':
|
||||||
|
return <RisicoBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
|
case 'diagnose_query':
|
||||||
|
return <DiagnoseBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 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<ResponseType>({
|
||||||
|
endpoint: '/api/cortex/...',
|
||||||
|
params: { patientId },
|
||||||
|
enabled: hasPatientContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasPatientContext) return <BlockEmpty ... />;
|
||||||
|
if (isLoading) return <BlockLoading ... />;
|
||||||
|
if (error) return <BlockError ... />;
|
||||||
|
|
||||||
|
return <BlockContainer>...</BlockContainer>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 <NieuweIntentBlock key={artifact.id} prefill={artifact.prefill} />;
|
||||||
|
|
||||||
|
// 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)
|
||||||
449
docs/intent/intake-intent-proces/testplan-intake-blocks-mvp.md
Normal file
449
docs/intent/intake-intent-proces/testplan-intake-blocks-mvp.md
Normal file
@@ -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=<UUID>" \
|
||||||
|
-H "Cookie: <session_cookie>"
|
||||||
|
|
||||||
|
# Met expliciete intakeId
|
||||||
|
curl -X GET "http://localhost:3000/api/cortex/intake/status?patientId=<UUID>&intakeId=<UUID>" \
|
||||||
|
-H "Cookie: <session_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=<UUID>" \
|
||||||
|
-H "Cookie: <session_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=<UUID>" \
|
||||||
|
-H "Cookie: <session_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 |
|
||||||
@@ -50,6 +50,10 @@ export async function sendChatMessage(
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({ error: response.statusText }));
|
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');
|
throw new Error(errorData.error || 'API request failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ export function useBlockData<T>({
|
|||||||
setData(result);
|
setData(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const statusCode = (err as any)?.statusCode;
|
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, {
|
const errorInfo = getErrorInfo(err, {
|
||||||
operation: operationName,
|
operation: operationName,
|
||||||
statusCode,
|
statusCode,
|
||||||
|
|||||||
Reference in New Issue
Block a user