# Implementatieplan — Nieuwe Intents Toevoegen **Versie:** v1.0 **Datum:** 4 februari 2026 **Doelgroep:** Product owners, IT consultants, data scientists --- ## 1. Overzicht Dit document beschrijft het stappenplan voor het toevoegen van een nieuwe intent aan Cortex. Een intent doorloopt **8 aanraakpunten** in de codebase — elk punt moet correct geconfigureerd zijn. ### Tijdsindicatie per Intent Type | Type | Complexiteit | Bestanden | |------|--------------|-----------| | **Query Block** (data tonen) | Laag | 6-7 bestanden | | **Action Block** (data invoeren) | Middel | 7-8 bestanden | | **Navigatie Intent** (geen block) | Laag | 4-5 bestanden | --- ## 2. Beslisboom: Welk Type Intent? ``` ┌─────────────────────────┐ │ Wat moet de intent doen?│ └───────────┬─────────────┘ │ ┌───────────────────────┼───────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Data TONEN │ │ Data INVOEREN │ │ NAVIGEREN │ │ (read-only) │ │ (formulier) │ │ (route) │ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │ │ │ ▼ ▼ ▼ Query Block Action Block Navigation Intent Voorbeelden: Voorbeelden: Voorbeelden: - risico_query - dagnotitie - intake_navigeer - diagnose_query - create_appointment - intake_status ``` --- ## 3. Stappenplan ### Stap 1: Types Definiëren **Bestand:** `lib/cortex/types.ts` **Wat te doen:** 1. Voeg intent toe aan `CortexIntent` type: ```typescript export type CortexIntent = | 'dagnotitie' | 'zoeken' // ... bestaande intents | 'nieuwe_intent' // ← NIEUW | 'unknown'; ``` 2. Voeg block config toe aan `BLOCK_CONFIGS` (alleen als er een block is): ```typescript export const BLOCK_CONFIGS: Record = { // ... bestaande configs nieuwe_intent: { type: 'nieuwe_intent', title: 'Nieuwe Intent Titel', size: 'md', // 'sm' | 'md' | 'lg' | 'full' icon: 'IconName', // Lucide icon naam }, }; ``` 3. Voeg eventuele nieuwe entities toe aan `ExtractedEntities`: ```typescript export interface ExtractedEntities { // ... bestaande entities nieuwVeld?: string; } ``` --- ### Stap 2: Patterns Toevoegen (Reflex Arc) **Bestand:** `lib/cortex/reflex-classifier.ts` **Wat te doen:** Voeg patterns toe aan `INTENT_PATTERNS`: ```typescript const INTENT_PATTERNS: Record<...> = { // ... bestaande patterns nieuwe_intent: [ // Exacte commando's (weight 1.0) { pattern: /^trigger woord/i, weight: 1.0 }, { pattern: /^alternatief commando/i, weight: 1.0 }, // Sterke matches (weight 0.9) { pattern: /^toon\s+(de\s+)?nieuwe/i, weight: 0.9 }, // Partiele matches (weight 0.7-0.8) { pattern: /^nieuwe\b/i, weight: 0.7 }, ], }; ``` **Pattern weight richtlijnen:** | Weight | Wanneer | Voorbeeld | |--------|---------|-----------| | `1.0` | Exacte, unieke trigger | `"risicotaxatie"` | | `0.9-0.95` | Sterke indicator | `"toon risico's"` | | `0.8-0.85` | Goede match | `"bekijk risico"` | | `0.7` | Partiele match | `"risico"` (kan ook andere dingen zijn) | | `< 0.7` | Vermijd | Leidt tot escalatie naar AI | --- ### Stap 3: AI Chat Integratie **Bestand:** `app/api/cortex/chat/route.ts` **Wat te doen:** 1. Voeg intent toe aan de system prompt (in `buildSystemPrompt()` functie): ```typescript // In de intents lijst: - **nieuwe_intent** — Korte beschrijving wat het doet - Triggers: "trigger 1", "trigger 2", "trigger 3" - Entities: veldNaam (type) - Actie: Beschrijf wat er gebeurt ``` 2. Voeg een voorbeeld toe: ```typescript ### Voorbeeld N: Nieuwe Intent **User:** "trigger zin" **AI Response:** "Korte bevestiging van wat je gaat doen. \`\`\`json { "type": "action", "intent": "nieuwe_intent", "entities": { "veldNaam": "waarde" }, "confidence": 0.95, "artifact": { "type": "nieuwe_intent", "prefill": { "veldNaam": "waarde" } } } \`\`\`" ``` **Let op:** De AI leert van voorbeelden. Zorg dat: - Het JSON format exact klopt - De confidence realistisch is (0.85-0.98) - De entities overeenkomen met wat je in types.ts hebt gedefinieerd --- ### Stap 4: Validatie Schema **Bestand:** `lib/cortex/action-parser.ts` **Wat te doen:** 1. Voeg intent toe aan `ActionSchema`: ```typescript const ActionSchema = z.object({ type: z.literal('action'), intent: z.enum([ 'dagnotitie', 'zoeken', // ... bestaande intents 'nieuwe_intent', // ← NIEUW 'unknown', ]), // ... rest van schema }); ``` 2. Voeg artifact type toe (als er een block is): ```typescript artifact: z.object({ type: z.enum([ 'dagnotitie', 'zoeken', // ... bestaande types 'nieuwe_intent', // ← NIEUW 'fallback', ]), prefill: z.record(z.string(), z.any()), }).optional(), ``` --- ### Stap 5: Routing Configureren **Bestand:** `lib/cortex/action-parser.ts` **Wat te doen:** Voeg case toe aan `routeIntentToArtifact()`: ```typescript export function routeIntentToArtifact( intent: CortexIntent, entities: Record, confidence: number ): { type: BlockType; prefill: Record; title: string } | null { // ... bestaande cases case 'nieuwe_intent': // Optioneel: check of vereiste entities aanwezig zijn if (!entities.vereistVeld) { return null; // Triggert clarification vraag } return { type: 'nieuwe_intent', title: 'Nieuwe Intent Titel', prefill: { veldNaam: entities.veldNaam, // ... andere prefill data }, }; } ``` **Voor navigatie intents:** ```typescript case 'nieuwe_navigeer': // Return null → wordt afgehandeld in CommandCenter return null; ``` --- ### Stap 6: API Route (Indien Nodig) **Bestand:** `app/api/cortex/[domain]/route.ts` **Wanneer nodig:** Als de block data moet ophalen van de server. **Wat te doen:** ```typescript // app/api/cortex/nieuwe/route.ts import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; import { createClient } from '@/lib/auth/server'; const QuerySchema = z.object({ patientId: z.string().uuid(), optionalParam: z.string().optional(), // Let op: NIET .nullable() }); export async function GET(request: NextRequest) { const supabase = await createClient(); const { data: { user } } = await supabase.auth.getUser(); if (!user) { return NextResponse.json({ error: 'Niet ingelogd' }, { status: 401 }); } const searchParams = request.nextUrl.searchParams; // BELANGRIJK: Convert null naar undefined voor Zod const params = QuerySchema.safeParse({ patientId: searchParams.get('patientId'), optionalParam: searchParams.get('optionalParam') || undefined, // ← NIET null! }); if (!params.success) { return NextResponse.json({ error: 'Ongeldige parameters' }, { status: 400 }); } // Data ophalen const { data, error } = await supabase .from('tabel_naam') .select('*') .eq('patient_id', params.data.patientId); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } return NextResponse.json({ data }); } ``` **Let op (Lesson Learned):** - `searchParams.get()` retourneert `null`, niet `undefined` - Zod's `.optional()` verwacht `undefined` - Gebruik altijd `|| undefined` bij optionele params --- ### Stap 7: Block Component **Bestand:** `components/cortex/blocks/nieuwe-intent-block.tsx` **Wat te doen:** ```typescript 'use client'; /** * Nieuwe Intent Block * * Block voor [beschrijving]. * Intent: nieuwe_intent */ import { useCortexStore, type BlockPrefillData } from '@/stores/cortex-store'; import { BlockContainer } from './block-container'; import { BlockLoading, BlockError, BlockEmpty } from '../shared/block-states'; import { useBlockData } from '@/lib/cortex/hooks/use-block-data'; import { BLOCK_CONFIGS } from '@/lib/cortex/types'; import { IconName } from 'lucide-react'; interface NieuweIntentBlockProps { prefill?: BlockPrefillData; } interface NieuweIntentData { items: Array<{ id: string; // ... velden }>; } export function NieuweIntentBlock({ prefill }: NieuweIntentBlockProps) { const config = BLOCK_CONFIGS['nieuwe_intent']; const { activePatient } = useCortexStore(); const patientId = prefill?.patientId || activePatient?.id; // Data ophalen const { data, isLoading, error, refetch } = useBlockData({ endpoint: '/api/cortex/nieuwe', params: { patientId: patientId || '' }, enabled: Boolean(patientId), }); // State: Geen context if (!patientId) { return ( ); } // State: Loading if (isLoading) { return ( ); } // State: Error if (error) { return ( ); } // State: Data return (
{/* Render je data hier */} {data?.items.map((item) => (
{/* Item content */}
))}
); } ``` **Shared components beschikbaar:** - `BlockContainer` — Wrapper met header en close button - `BlockLoading` — Spinner met message - `BlockError` — Foutmelding met retry button - `BlockEmpty` — Lege state met icon en actie - `BlockSection` — Sectie met header - `BlockItem` — Lijst-item met badge - `BlockFooter` — Footer met acties --- ### Stap 8: Artifact Container Updaten **Bestand:** `components/cortex/artifacts/artifact-container.tsx` **Wat te doen (3 plekken):** 1. **Import toevoegen:** ```typescript import { NieuweIntentBlock } from '../blocks/nieuwe-intent-block'; ``` 2. **Render case toevoegen:** ```typescript function renderArtifactBlock(artifact: Artifact) { switch (artifact.type) { // ... bestaande cases case 'nieuwe_intent': return ; } } ``` 3. **Titel toevoegen:** ```typescript function getArtifactTitle(type: string): string { switch (type) { // ... bestaande cases case 'nieuwe_intent': return 'Nieuwe Intent Titel'; } } ``` --- ### Stap 9 (Optioneel): Navigatie Handler **Alleen voor navigatie intents (zonder block)** **Bestand:** `components/cortex/command-center/command-center.tsx` **Wat te doen:** In de `useEffect` die `pendingAction` afhandelt: ```typescript useEffect(() => { if (!pendingAction) return; // ... bestaande handlers if (pendingAction.intent === 'nieuwe_navigeer') { const target = pendingAction.entities.navigationTarget; // Navigeer naar juiste pagina router.push(`/epd/patients/${patientId}/path/${target}`); // Feedback tonen toast({ title: 'Navigeren...', description: `Naar ${target}`, }); // Cleanup setPendingAction(null); return; } }, [pendingAction]); ``` --- ## 4. Checklist Gebruik deze checklist bij het toevoegen van een nieuwe intent: ### Voorbereiding - [ ] Intent type bepaald (Query/Action/Navigation) - [ ] Intent naam gekozen (lowercase, underscore) - [ ] Entities gedefinieerd - [ ] Trigger woorden verzameld ### Types & Patterns - [ ] `lib/cortex/types.ts` — CortexIntent type - [ ] `lib/cortex/types.ts` — BLOCK_CONFIGS (als block nodig) - [ ] `lib/cortex/types.ts` — ExtractedEntities (als nieuwe entities) - [ ] `lib/cortex/reflex-classifier.ts` — INTENT_PATTERNS ### AI Integratie - [ ] `app/api/cortex/chat/route.ts` — Intent in system prompt - [ ] `app/api/cortex/chat/route.ts` — Voorbeeld met JSON ### Validatie & Routing - [ ] `lib/cortex/action-parser.ts` — ActionSchema intent enum - [ ] `lib/cortex/action-parser.ts` — ActionSchema artifact type (als block) - [ ] `lib/cortex/action-parser.ts` — routeIntentToArtifact() case ### API (indien nodig) - [ ] `app/api/cortex/[domain]/route.ts` — Nieuwe route - [ ] Query params: `|| undefined` voor optionele params! ### UI Component - [ ] `components/cortex/blocks/[intent]-block.tsx` — Block component - [ ] `components/cortex/artifacts/artifact-container.tsx` — Import - [ ] `components/cortex/artifacts/artifact-container.tsx` — Render case - [ ] `components/cortex/artifacts/artifact-container.tsx` — Titel ### Navigatie (indien van toepassing) - [ ] `components/cortex/command-center/command-center.tsx` — Handler ### Testen - [ ] Lokale classificatie testen (Reflex Arc) - [ ] AI classificatie testen (Chat) - [ ] Block rendering testen - [ ] Error states testen - [ ] Voice input testen --- ## 5. Veelgemaakte Fouten ### Fout 1: Zod + null vs undefined **Probleem:** ```typescript // Dit faalt! const param = searchParams.get('optionalParam'); // Returns null ``` **Oplossing:** ```typescript const param = searchParams.get('optionalParam') || undefined; ``` ### Fout 2: Intent niet in AI prompt **Symptoom:** Chat AI vraagt "Wil je het dossier opzoeken?" ipv de juiste actie. **Oorzaak:** Intent ontbreekt in system prompt. **Oplossing:** Voeg intent + voorbeeld toe aan `buildSystemPrompt()`. ### Fout 3: Block wordt niet gerenderd **Symptoom:** Console log toont "Opening artifact: intent" maar niets verschijnt. **Oorzaak:** Drie plekken in artifact-container.tsx niet bijgewerkt. **Oplossing:** Check import, render case, én titel. ### Fout 4: Escalatie bij elke invoer **Symptoom:** Alles gaat naar AI, zelfs simpele commando's. **Oorzaak:** Pattern weight te laag (< 0.7). **Oplossing:** Verhoog weights of voeg sterkere patterns toe. --- ## 6. Diagram: Bestandenflow ``` ┌────────────────────────────────────────────────────────────────────────┐ │ BESTANDEN PER STAP │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ lib/cortex/ │ │ ├── types.ts ────────────────────────┬─── Stap 1: Types │ │ ├── reflex-classifier.ts ────────────┼─── Stap 2: Patterns │ │ └── action-parser.ts ────────────────┼─── Stap 4-5: Validatie+Route │ │ │ │ │ app/api/cortex/ │ │ │ ├── chat/route.ts ───────────────────┼─── Stap 3: AI Prompt │ │ └── [domain]/route.ts ───────────────┼─── Stap 6: API (optioneel) │ │ │ │ │ components/cortex/ │ │ │ ├── blocks/[intent]-block.tsx ───────┼─── Stap 7: Block Component │ │ ├── artifacts/artifact-container.tsx ┼─── Stap 8: Rendering │ │ └── command-center/command-center.tsx┴─── Stap 9: Navigatie │ │ │ └────────────────────────────────────────────────────────────────────────┘ ``` --- ## 7. Voorbeeld: Nieuwe Intent "kindcheck_query" Stel we willen een intent toevoegen voor "Toon kindcheck status". ### Stap 1: types.ts ```typescript // CortexIntent | 'kindcheck_query' // BLOCK_CONFIGS kindcheck_query: { type: 'kindcheck_query', title: 'Kindcheck', size: 'md', icon: 'Baby', }, ``` ### Stap 2: reflex-classifier.ts ```typescript kindcheck_query: [ { pattern: /^kindcheck/i, weight: 1.0 }, { pattern: /^(toon|bekijk)\s+(de\s+)?kindcheck/i, weight: 0.95 }, { pattern: /^zijn\s+er\s+kinderen/i, weight: 0.9 }, { pattern: /^kinderen\s+in\s+beeld/i, weight: 0.85 }, ], ``` ### Stap 3: chat/route.ts ```typescript // In prompt: - **kindcheck_query** — Kindcheck status opvragen - Triggers: "kindcheck", "zijn er kinderen?", "kinderen in beeld?" - Entities: geen - Actie: Toont kindcheck status block // Voorbeeld: ### Voorbeeld 13: Kindcheck **User:** "kindcheck" **AI Response:** "Ik toon de kindcheck status. \`\`\`json { "type": "action", "intent": "kindcheck_query", "entities": {}, "confidence": 0.98, "artifact": { "type": "kindcheck_query", "prefill": {} } } \`\`\`" ``` ### Stap 4-5: action-parser.ts ```typescript // ActionSchema intent enum 'kindcheck_query', // ActionSchema artifact type 'kindcheck_query', // routeIntentToArtifact case 'kindcheck_query': return { type: 'kindcheck_query', title: 'Kindcheck', prefill: entities, }; ``` ### Stap 6: API route ```typescript // app/api/cortex/intake/kindcheck/route.ts // ... (vergelijkbaar met risico/route.ts) ``` ### Stap 7: Block component ```typescript // components/cortex/blocks/kindcheck-block.tsx // ... (vergelijkbaar met risico-block.tsx) ``` ### Stap 8: artifact-container.tsx ```typescript import { KindcheckBlock } from '../blocks/kindcheck-block'; // render case case 'kindcheck_query': return ; // titel case 'kindcheck_query': return 'Kindcheck'; ``` --- ## 8. Gerelateerde Documentatie | Document | Locatie | |----------|---------| | Intent Overzicht | `docs/architectuur/intent-overzicht.md` | | Block Template Pattern | `docs/intent/intake-intent-proces/block-template-pattern.md` | | Session Log (Lessons Learned) | `docs/intent/intake-intent-proces/session-log-2026-02-03.md` | | Architectuur Overzicht | `docs/architectuur/architectuur-overzicht.md` | --- *Bij vragen of problemen, raadpleeg de session logs voor bekende issues en oplossingen.*