Files
triqura-ecd/docs/architectuur/implementatieplan-nieuwe-intents.md
colinislit af88ac9446 docs: add architecture and intake intent documentation
- 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>
2026-02-04 19:26:14 +01:00

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:

  1. Voeg intent toe aan CortexIntent type:
export type CortexIntent =
  | 'dagnotitie'
  | 'zoeken'
  // ... bestaande intents
  | 'nieuwe_intent'  // ← NIEUW
  | 'unknown';
  1. 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
  },
};
  1. 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:

  1. 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
  1. 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:

  1. 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
});
  1. 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() 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:

'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:
import { NieuweIntentBlock } from '../blocks/nieuwe-intent-block';
  1. Render case toevoegen:
function renderArtifactBlock(artifact: Artifact) {
  switch (artifact.type) {
    // ... bestaande cases

    case 'nieuwe_intent':
      return <NieuweIntentBlock key={artifact.id} prefill={artifact.prefill} />;
  }
}
  1. 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 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:

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