# 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<BlockType, BlockConfig> = {
  // ... 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<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:**
```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<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 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 <NieuweIntentBlock key={artifact.id} prefill={artifact.prefill} />;
  }
}
```

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 <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.*
