Cortex handelt een no-show af vanuit één chatcommando: afspraak annuleren (declarabiliteits-nudge), en de openstaande concept- huisartsbrief wordt via LLM herschreven en ter review aangeboden in een document artifact (human-in-the-loop, PATCH dispatch zet status op verzendklaar). - API-routes: context, rescript, cancel, dispatch - NoShowDocumentBlock: review/edit UI met origineel-vergelijk - Mock-data voor concept huisartsbrief - PRD, FO, bouwplan en epics in docs/intent/noshow-case/ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.9 KiB
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<CortexIntent, 'unknown' | 'intake_navigeer'> | 'patient-dashboard' |
stores/cortex-store.ts |
Exclude<CortexIntent, 'unknown'> | '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:
const INTENT_PATTERNS: Record<Exclude<CortexIntent, 'unknown'>, 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:
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<BlockType, BlockConfig> — alle BlockTypes moeten erin):
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 buildslaagt — geen TypeScript errorsregister_no_showis een geldige waarde voorCortexIntentBLOCK_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<Exclude<CortexIntent, 'unknown'>, 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:
// =========================================================================
// 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ïteit0.95— sterke klinische uitdrukkingen0.85–0.9— contextuele varianten0.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"— bevatmorgen→ triggertrelative_timeescalatie → 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")geeftshouldEscalateToAI: true, escalationReason: 'relative_time'pnpm buildslaagt — 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_showmet 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:
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:
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:
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:
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 opdefault: 'Ja, uitvoeren'
Voeg toe in de switch van getAcceptButtonText:
case 'register_no_show':
return 'Ja, pas brief aan';
Done criteria
ActionSchema.safeParse({ type: 'action', intent: 'register_no_show', entities: {}, confidence: 0.9 })geeftsuccess: truerouteIntentToArtifact('register_no_show', {}, 0.9)geeftnullterug (geen artifact via generieke router)getDefaultConfirmationMessage('register_no_show', {})geeft correcte Nederlandse zin- Nudge 2 accept-knop toont "Ja, pas brief aan"
pnpm buildslaagt
Validatie na NS.E1 (alle stories)
Run na voltooiing van alle vier stories:
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:
- Open de app (
pnpm dev) - Navigeer naar het Cortex dashboard
- Typ:
"patiënt niet verschenen" - Verwacht: AI response met intent
register_no_showzichtbaar in browser console via[ChatPanel] Action detected: { intent: 'register_no_show', ... } - Verwacht: nudge bubble verschijnt (NS.E2 vereist — maar de action parse moet nu wel slagen)