# NS.E1 — Intent Foundation **Casus:** Cortex No Show Afhandeling **Epic doel:** `register_no_show` herkenbaar maken door het volledige classificatiesysteem — types, reflex patterns, en orchestrator prompt. **Geschatte tijd:** ~1.5 uur **Afhankelijkheden:** Geen (dit is de basis voor alle andere epics) --- ## Context & waarschuwingen ### Dubbele BlockType definitie — kritiek Er zijn **twee** `BlockType` definities in de codebase die allebei bijgewerkt moeten worden: | Bestand | Definitie | |---|---| | `lib/cortex/types.ts` | `Exclude \| 'patient-dashboard'` | | `stores/cortex-store.ts` | `Exclude \| 'fallback' \| 'patient-dashboard'` | De store-versie is breder (includeert `intake_navigeer` en `fallback`). Door `register_no_show` toe te voegen aan `CortexIntent` wordt het automatisch onderdeel van beide `BlockType` afgeleidingen — maar alleen als je ook `BLOCK_CONFIGS` bijwerkt. ### `INTENT_PATTERNS` is strikt getypeerd `reflex-classifier.ts` regel 27: ```typescript const INTENT_PATTERNS: Record, PatternConfig[]> = { ... } ``` Dit betekent: als je `register_no_show` toevoegt aan `CortexIntent`, **eist TypeScript** dat je ook een entry toevoegt aan `INTENT_PATTERNS`. De build faalt anders. Dit is bewust — het voorkomt vergeten patronen. --- ## NS.E1.S1 — CortexIntent type + BLOCK_CONFIGS uitbreiden **Bestand:** `lib/cortex/types.ts` ### Wijziging 1 — CortexIntent union Voeg `'register_no_show'` toe na de intake-intents: ```typescript export type CortexIntent = | 'dagnotitie' | 'zoeken' | 'overdracht' | 'agenda_query' | 'create_appointment' | 'cancel_appointment' | 'reschedule_appointment' | 'intake_status' | 'intake_navigeer' | 'risico_query' | 'diagnose_query' // No Show casus | 'register_no_show' | 'unknown'; ``` ### Wijziging 2 — BLOCK_CONFIGS Voeg een entry toe in `BLOCK_CONFIGS` (het is een `Record` — alle BlockTypes moeten erin): ```typescript register_no_show: { type: 'register_no_show', title: 'No Show Registratie', size: 'md', icon: 'UserX', }, ``` Locatie: onderaan de `BLOCK_CONFIGS` definitie, na `diagnose_query`. ### Done criteria - `pnpm build` slaagt — geen TypeScript errors - `register_no_show` is een geldige waarde voor `CortexIntent` - `BLOCK_CONFIGS['register_no_show']` bestaat --- ## NS.E1.S2 — Reflex Classifier patronen toevoegen **Bestand:** `lib/cortex/reflex-classifier.ts` ### Context `INTENT_PATTERNS` is getypeerd als `Record, PatternConfig[]>`. Na toevoeging van `register_no_show` aan de union **moet** je hier ook een entry toevoegen — anders faalt de build met: ``` Type '{ dagnotitie: ...; ... }' is missing the following properties from type 'Record<...>': register_no_show ``` ### Wijziging — patronen toevoegen Voeg toe aan `INTENT_PATTERNS`, na het `diagnose_query` blok: ```typescript // ========================================================================= // No Show casus // ========================================================================= register_no_show: [ // Exacte no-show varianten { pattern: /no.?show/i, weight: 1.0 }, { pattern: /no show/i, weight: 1.0 }, // "Niet verschenen" varianten { pattern: /niet\s+verschenen/i, weight: 0.95 }, { pattern: /niet\s+gekomen/i, weight: 0.95 }, { pattern: /niet\s+op\s+komen\s+dagen/i, weight: 0.95 }, // "Afwezig" + context { pattern: /afwezig\s+(bij|voor)\s+(de\s+)?afspraak/i, weight: 0.9 }, { pattern: /pati[eë]nt\s+afwezig/i, weight: 0.85 }, // Werkwoordvormen { pattern: /komt?\s+niet\s+(op|naar)/i, weight: 0.85 }, { pattern: /is\s+er\s+niet\s+(geweest)?/i, weight: 0.7 }, ], ``` **Toelichting weights:** - `1.0` — directe "no show" termen, geen ambiguïteit - `0.95` — sterke klinische uitdrukkingen - `0.85–0.9` — contextuele varianten - `0.7` — vaag ("is er niet geweest") — escaleer naar AI bij twijfel ### Escalatie check Controleer of de bestaande escalatiepatronen niet per ongeluk triggeren op no-show input: - `"patiënt niet verschenen"` — bevat geen conjuncties → geen multi_intent - `"patiënt is er niet"` — bevat geen voornaamwoorden die escaleren → OK - `"patiënt niet verschenen morgen"` — bevat `morgen` → triggert `relative_time` escalatie → correct (AI lost dit op) ### Done criteria - `classifyWithReflex("patiënt is niet verschenen")` geeft `{ intent: 'register_no_show', confidence: 0.95, shouldEscalateToAI: false }` - `classifyWithReflex("no show vandaag")` geeft `{ intent: 'register_no_show', confidence: 1.0, shouldEscalateToAI: false }` - `classifyWithReflex("patiënt niet verschenen morgen")` geeft `shouldEscalateToAI: true, escalationReason: 'relative_time'` - `pnpm build` slaagt — TypeScript tevreden met de nieuwe entry --- ## NS.E1.S3 — Orchestrator system prompt uitbreiden **Bestand:** `app/api/cortex/chat/route.ts` ### Context De chat API bouwt een system prompt via `buildSystemPrompt(context)`. Die prompt bevat een opsomming van alle intents met beschrijvingen. De Orchestrator (Layer 2) én de chat AI (Layer 3) gebruiken allebei deze prompt. Zoek de sectie in de prompt waar intents worden opgesomd — het zal er zo uitzien: ``` - dagnotitie: Gebruik wanneer... - zoeken: Gebruik wanneer... ``` ### Wijziging — intent beschrijving toevoegen Voeg toe aan de intent-opsomming in `buildSystemPrompt`: ``` - register_no_show: Gebruik wanneer de zorgverlener aangeeft dat een patiënt niet op de geplande afspraak is verschenen. Signaalwoorden: "no show", "niet verschenen", "niet gekomen", "afwezig bij afspraak", "komt niet op". Entiteiten: geen specifieke entiteiten nodig — de actieve patiënt en huidige context worden gebruikt. ``` ### Done criteria - System prompt in de chat API bevat `register_no_show` met beschrijving - Handmatige test: typ "cliënt was er niet vandaag" in de chat → AI classificeert als `register_no_show` (zichtbaar in console via `[ChatPanel] Action detected:`) - Typ "patiënt heeft de afspraak gemist" → zelfde resultaat --- --- ## NS.E1.S4 — `action-parser.ts` uitbreiden **Bestand:** `lib/cortex/action-parser.ts` ### Context — waarom dit hier hoort `action-parser.ts` bevat vier hardcoded enums en switches die de volledige lijst van geldige intents bevatten. Ze zijn **niet** afgeleid van `CortexIntent` in `types.ts` — ze zijn handmatig gesynchroniseerd. Dit is het meest kritieke gat: als `register_no_show` hier ontbreekt, faalt de Zod validatie silently op de AI response en wordt `parsed.action` altijd `null`. De nudge triggert dan **nooit** — ook niet na alle andere fixes. ### Wijziging 1 — `ActionSchema.intent` enum (regel ~16) Voeg `'register_no_show'` toe aan de `z.enum([...])` lijst: ```typescript intent: z.enum([ 'dagnotitie', 'zoeken', 'overdracht', 'agenda_query', 'create_appointment', 'cancel_appointment', 'reschedule_appointment', 'intake_status', 'intake_navigeer', 'risico_query', 'diagnose_query', // No Show casus 'register_no_show', 'unknown', ]), ``` ### Wijziging 2 — `ActionSchema.artifact.type` enum (regel ~77) Voeg `'register_no_show'` toe aan de artifact type enum: ```typescript type: z.enum([ 'dagnotitie', 'zoeken', 'overdracht', 'agenda_query', 'create_appointment', 'cancel_appointment', 'reschedule_appointment', 'intake_status', 'risico_query', 'diagnose_query', 'fallback', 'patient-dashboard', // No Show casus 'register_no_show', ]), ``` ### Wijziging 3 — `routeIntentToArtifact` switch De switch heeft een `default: return null`. Voeg een expliciete case toe **vóór** de `default`, na het `intake_navigeer` blok: ```typescript case 'register_no_show': // Artifact wordt geopend via de no-show handler in chat-panel.tsx, // niet via de generieke routing. Geef null terug zodat de handler // de controle houdt. return null; ``` **Toelichting:** We returnen bewust `null` — het artifact voor no-show wordt door `handleNoShowRescriptStep` geopend met volledige prefill data (documentId, content, originalContent) die pas beschikbaar is ná de rescript API call. De generieke router heeft die data niet. ### Wijziging 4 — `getDefaultConfirmationMessage` switch Voeg toe na het `overdracht` case, vóór de `default`: ```typescript case 'register_no_show': return 'Ik registreer de no show en controleer de agenda op declarabiliteit.'; ``` ### Wijziging 5 — `getAcceptButtonText` in `nudge-chat-message.tsx` **Bestand:** `components/cortex/chat/nudge-chat-message.tsx` De `getAcceptButtonText` functie bepaalt de knoptekst op basis van `suggestion.suggestion.intent`: - Nudge 1 heeft `intent: 'cancel_appointment'` → toont al correct **"Ja, annuleren"** - Nudge 2 heeft `intent: 'register_no_show'` → valt op `default: 'Ja, uitvoeren'` Voeg toe in de switch van `getAcceptButtonText`: ```typescript case 'register_no_show': return 'Ja, pas brief aan'; ``` ### Done criteria - `ActionSchema.safeParse({ type: 'action', intent: 'register_no_show', entities: {}, confidence: 0.9 })` geeft `success: true` - `routeIntentToArtifact('register_no_show', {}, 0.9)` geeft `null` terug (geen artifact via generieke router) - `getDefaultConfirmationMessage('register_no_show', {})` geeft correcte Nederlandse zin - Nudge 2 accept-knop toont "Ja, pas brief aan" - `pnpm build` slaagt --- ## Validatie na NS.E1 (alle stories) Run na voltooiing van alle vier stories: ```bash pnpm build pnpm lint ``` Verwacht: geen errors. Als er TypeScript errors zijn over `INTENT_PATTERNS` of `BLOCK_CONFIGS`, controleer dan of beide `Record<>` types volledig zijn bijgewerkt. **Snelle handmatige smoke test:** 1. Open de app (`pnpm dev`) 2. Navigeer naar het Cortex dashboard 3. Typ: `"patiënt niet verschenen"` 4. Verwacht: AI response met intent `register_no_show` zichtbaar in browser console via `[ChatPanel] Action detected: { intent: 'register_no_show', ... }` 5. Verwacht: nudge bubble verschijnt (NS.E2 vereist — maar de action parse moet nu wel slagen)