Files
triqura-ecd/docs/intent/noshow-case/epics/NS-E1-intent-foundation.md
colinislit 924988dd15 feat(cortex): no-show casus — intent flow, brief-rescript en document artifact
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>
2026-07-09 23:14:55 +02:00

9.9 KiB
Raw Blame History

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 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<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ïteit
  • 0.95 — sterke klinische uitdrukkingen
  • 0.850.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:

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 op default: '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 }) 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:

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)