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
|
||||
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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user