- Add architecture overview, implementation plan, and intent overview - Add intake intent process specs (gap analyse, bouwplan, testplan) - Add swift architecture specs and visualization prompts - Remove obsolete aispeedrun-manifesto template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
19 KiB
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:
- Voeg intent toe aan
CortexIntenttype:
export type CortexIntent =
| 'dagnotitie'
| 'zoeken'
// ... bestaande intents
| 'nieuwe_intent' // ← NIEUW
| 'unknown';
- Voeg block config toe aan
BLOCK_CONFIGS(alleen als er een block is):
export const BLOCK_CONFIGS: Record<BlockType, BlockConfig> = {
// ... bestaande configs
nieuwe_intent: {
type: 'nieuwe_intent',
title: 'Nieuwe Intent Titel',
size: 'md', // 'sm' | 'md' | 'lg' | 'full'
icon: 'IconName', // Lucide icon naam
},
};
- Voeg eventuele nieuwe entities toe aan
ExtractedEntities:
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:
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:
- Voeg intent toe aan de system prompt (in
buildSystemPrompt()functie):
// 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
- Voeg een voorbeeld toe:
### 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:
- Voeg intent toe aan
ActionSchema:
const ActionSchema = z.object({
type: z.literal('action'),
intent: z.enum([
'dagnotitie',
'zoeken',
// ... bestaande intents
'nieuwe_intent', // ← NIEUW
'unknown',
]),
// ... rest van schema
});
- Voeg artifact type toe (als er een block is):
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():
export function routeIntentToArtifact(
intent: CortexIntent,
entities: Record<string, any>,
confidence: number
): { type: BlockType; prefill: Record<string, any>; 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:
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:
// 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()retourneertnull, nietundefined- Zod's
.optional()verwachtundefined - Gebruik altijd
|| undefinedbij optionele params
Stap 7: Block Component
Bestand: components/cortex/blocks/nieuwe-intent-block.tsx
Wat te doen:
'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<NieuweIntentData>({
endpoint: '/api/cortex/nieuwe',
params: { patientId: patientId || '' },
enabled: Boolean(patientId),
});
// State: Geen context
if (!patientId) {
return (
<BlockContainer title={config.title} size={config.size}>
<BlockEmpty
icon={IconName}
message="Selecteer eerst een patiënt"
/>
</BlockContainer>
);
}
// State: Loading
if (isLoading) {
return (
<BlockContainer title={config.title} size={config.size}>
<BlockLoading message="Data laden..." />
</BlockContainer>
);
}
// State: Error
if (error) {
return (
<BlockContainer title={config.title} size={config.size}>
<BlockError message={error} onRetry={refetch} />
</BlockContainer>
);
}
// State: Data
return (
<BlockContainer title={config.title} size={config.size}>
<div className="space-y-4">
{/* Render je data hier */}
{data?.items.map((item) => (
<div key={item.id}>
{/* Item content */}
</div>
))}
</div>
</BlockContainer>
);
}
Shared components beschikbaar:
BlockContainer— Wrapper met header en close buttonBlockLoading— Spinner met messageBlockError— Foutmelding met retry buttonBlockEmpty— Lege state met icon en actieBlockSection— Sectie met headerBlockItem— Lijst-item met badgeBlockFooter— Footer met acties
Stap 8: Artifact Container Updaten
Bestand: components/cortex/artifacts/artifact-container.tsx
Wat te doen (3 plekken):
- Import toevoegen:
import { NieuweIntentBlock } from '../blocks/nieuwe-intent-block';
- Render case toevoegen:
function renderArtifactBlock(artifact: Artifact) {
switch (artifact.type) {
// ... bestaande cases
case 'nieuwe_intent':
return <NieuweIntentBlock key={artifact.id} prefill={artifact.prefill} />;
}
}
- Titel toevoegen:
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:
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 typelib/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 promptapp/api/cortex/chat/route.ts— Voorbeeld met JSON
Validatie & Routing
lib/cortex/action-parser.ts— ActionSchema intent enumlib/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:
|| undefinedvoor optionele params!
UI Component
components/cortex/blocks/[intent]-block.tsx— Block componentcomponents/cortex/artifacts/artifact-container.tsx— Importcomponents/cortex/artifacts/artifact-container.tsx— Render casecomponents/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:
// Dit faalt!
const param = searchParams.get('optionalParam'); // Returns null
Oplossing:
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
// CortexIntent
| 'kindcheck_query'
// BLOCK_CONFIGS
kindcheck_query: {
type: 'kindcheck_query',
title: 'Kindcheck',
size: 'md',
icon: 'Baby',
},
Stap 2: reflex-classifier.ts
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
// 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
// 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
// app/api/cortex/intake/kindcheck/route.ts
// ... (vergelijkbaar met risico/route.ts)
Stap 7: Block component
// components/cortex/blocks/kindcheck-block.tsx
// ... (vergelijkbaar met risico-block.tsx)
Stap 8: artifact-container.tsx
import { KindcheckBlock } from '../blocks/kindcheck-block';
// render case
case 'kindcheck_query':
return <KindcheckBlock key={artifact.id} prefill={artifact.prefill} />;
// 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.