Files
triqura-ecd/docs/swift/cortex-architectuur-analyse.md
2026-07-09 23:16:28 +02:00

74 KiB
Raw Blame History

Cortex Architectuuranalyse — Van Prototype naar Productie

Type: Architectuurrapport Scope: Cortex drie-laags AI-systeem + EPD-integratie Status: Analyse & aanbevelingen Datum: 2026-02-14 Doelgroep: Technisch team, AI-systemen voor vervolganalyse


1. Inleiding

1.1 Doel van dit rapport

Dit rapport beschrijft het huidige Cortex-systeem (as-is) en doet concrete voorstellen voor een productiewaardige architectuur (to-be). Het is bedoeld als referentiedocument voor verdere analyse en iteratieve verbetering.

1.2 Scope

  • Cortex drie-laags architectuur (Reflex, Orchestrator, Nudge)
  • Intent-classificatiesysteem
  • Integratie met EPD-modules (API's, state management, UI-flow)
  • Beknopte enterprise-eisen (NEN 7510, audit, RBAC)

Buiten scope: Volledig datamodel, autorisatiemodel, FHIR-integratie. Deze worden als afhankelijkheden benoemd maar niet uitgewerkt.

1.3 Ontwerpprincipes

De aanbevelingen in dit rapport volgen deze principes:

  1. Single Responsibility — Elk onderdeel heeft precies één doel
  2. Single Source of Truth — Geen dubbele definities; één canonieke bron
  3. Graceful Degradation — Het systeem blijft functioneel als onderdelen falen
  4. Minimale complexiteit — Geen abstracties voor hypothetische toekomstige eisen
  5. Observability by design — Elk onderdeel is meetbaar en debugbaar

2. Concept

2.1 Probleemstelling

Zorgverleners in de GGZ besteden een significant deel van hun werktijd aan administratie: rapportages schrijven, afspraken beheren, overdrachten voorbereiden, en door dossiers navigeren. Deze handelingen onderbreken het primaire proces — directe patiëntenzorg — en vereisen constante contextwisselingen tussen klinisch denken en systeembediening.

Het kernprobleem is niet dat deze taken bestaan, maar dat ze gestructureerde input vereisen (formulieren, knoppen, menustructuren) terwijl de zorgverlener in ongestructureerde taal denkt en communiceert. De kloof tussen "Jan heeft zijn medicatie geweigerd" en het invullen van het juiste formulier in de juiste categorie voor de juiste patiënt is waar productiviteit verloren gaat.

2.2 Visie

Cortex is de intelligente tussenschakel tussen natuurlijke taal en gestructureerde EPD-acties. Het stelt zorgverleners in staat om in hun eigen woorden te communiceren — gesproken of getypt — en vertaalt dit naar de juiste actie in het juiste systeem, met de juiste context.

De kernbelofte:

Zeg wat je wilt doen, niet hoe het systeem het verwacht.

Cortex is geen chatbot en geen zoekmachine. Het is een command interface dat drie dingen doet:

  1. Begrijpen — Wat bedoelt de gebruiker? (intent + entities)
  2. Uitvoeren — De juiste actie starten met de juiste voorvulling
  3. Voorstellen — Proactief vervolgacties suggereren op basis van klinische protocollen

2.3 Ontwerpfilosofie

Snelheid boven volledigheid

70% van de commando's in een EPD is routine: "notitie jan medicatie", "agenda vandaag", "overdracht". Deze moeten in milliseconden verwerkt worden, niet in seconden. AI wordt alleen ingezet wanneer lokale verwerking niet volstaat. Dit is een bewuste keuze: optimaliseer voor de veelvoorkomende gevallen, investeer AI-capaciteit in de uitzonderingen.

Vertrouwen door transparantie

Zorgverleners moeten het systeem kunnen vertrouwen. Dat betekent:

  • Geen automatische opslag zonder bevestiging
  • Altijd tonen wat het systeem begrepen heeft (prefilled formulier, niet een weggeschreven record)
  • Bij twijfel: vragen, niet raden
  • Bevestiging vereisen bij destructieve acties (annuleren, verwijderen)

Klinisch relevante intelligentie

De AI-component is geen generieke assistent. Cortex kent het domein: diensten (nacht/ochtend/middag/avond), rapportagecategorieën (medicatie/ADL/gedrag/incident/observatie), klinische protocollen (wondzorg, medicatie-evaluatie). Deze domeinkennis maakt het verschil tussen een nuttige suggestie en ruis.

2.4 Gebruiksmodel

┌──────────────────────────────────────────────────────────────────┐
│                    Werkdag van een verpleegkundige                │
│                                                                  │
│  07:00  Start dienst                                             │
│         └─ "Wat moet ik weten?" → Overdracht met AI-samenvatting │
│                                                                  │
│  08:30  Medicatieronde                                           │
│         └─ "Jan medicatie gegeven" → Dagnotitie, categorie auto  │
│         └─ "Marie weigert medicatie" → Dagnotitie + incident?    │
│                                                                  │
│  10:00  Intake nieuw patiënt                                     │
│         └─ "Wat moet ik nog doen?" → Intake checklist            │
│         └─ "Ga naar risico" → Navigatie naar risicotaxatie       │
│                                                                  │
│  12:00  Afsprakenbeheer                                          │
│         └─ "Afspraken vanmiddag" → Agendaoverzicht               │
│         └─ "Verzet Jan naar 15:00" → Afspraak wijzigen           │
│                                                                  │
│  14:30  Wondverzorging                                           │
│         └─ "Notitie jan wondverzorging uitgevoerd" → Dagnotitie  │
│         └─ 🔔 Nudge: "Wondcontrole inplannen over 3 dagen?"     │
│                                                                  │
│  16:45  Einde dienst                                             │
│         └─ "Overdracht" → Overdracht genereren                   │
└──────────────────────────────────────────────────────────────────┘

3. Architectuur — Conceptueel

3.1 Het drie-laags model

De architectuur is geïnspireerd op hoe het menselijk zenuwstelsel informatie verwerkt:

Laag Analogie Functie Wanneer
Layer 1: Reflex Ruggenmerg Directe, snelle respons op herkenbare patronen Altijd (eerste poging)
Layer 2: Orchestrator Hersenen Bewuste analyse van complexe, ambigue input Bij twijfel of complexiteit
Layer 3: Nudge Geweten Proactieve suggestie op basis van professionele kennis Na afgeronde actie

Dit model heeft twee architecturele voordelen:

1. Kostenefficiëntie: AI API-calls zijn duur en traag. Door 70%+ lokaal af te handelen worden kosten en latency gedrukt. De AI wordt alleen aangesproken wanneer de waarde evident is.

2. Resilience: Als de AI-service onbeschikbaar is, blijft Layer 1 volledig functioneel. Het systeem degradeert graceful van "intelligent" naar "snel en betrouwbaar" in plaats van volledig te falen.

3.2 De intent als architecturaal primitief

Het centrale concept in Cortex is de intent: een gestructureerde representatie van wat de gebruiker wilt bereiken.

Gebruikersinput: "Notitie jan medicatie gegeven"
                          │
                          ▼
                 ┌─────────────────┐
                 │     Intent      │
                 ├─────────────────┤
                 │ type: dagnotitie│
                 │ confidence: 0.95│
                 │ entities:       │
                 │   patient: jan  │
                 │   category: med │
                 │   content: ...  │
                 └─────────────────┘
                          │
                          ▼
                 ┌─────────────────┐
                 │     Actie       │
                 │  Open formulier │
                 │  met prefill    │
                 └─────────────────┘

Een intent bestaat uit drie delen:

  1. Type — Wát de gebruiker wil (dagnotitie, zoeken, overdracht, etc.)
  2. Confidence — Hoe zeker het systeem is van deze interpretatie (0.01.0)
  3. Entities — De relevante parameters (patiëntnaam, categorie, datum, etc.)

De confidence bepaalt het systeemgedrag:

Confidence Gedrag
≥ 0.9 Direct uitvoeren (formulier openen met prefill)
0.70.9 Uitvoeren met bevestigingsvraag
0.50.7 Verduidelijkingsvraag stellen
< 0.5 "Ik begrijp het niet, kun je het anders zeggen?"

3.3 IntentChain — Meervoudige acties

Gebruikers combineren regelmatig acties in één zin: "Annuleer de afspraak van Jan en maak een notitie." Het systeem modelleert dit als een IntentChain: een geordende reeks intents die sequentieel worden uitgevoerd.

Input: "Annuleer jan en maak notitie griep"
                    │
                    ▼
           ┌─────────────────┐
           │   IntentChain   │
           ├─────────────────┤
           │ Action 1:       │
           │   cancel_appt   │ ──▶ Uitvoeren (met bevestiging)
           │   {patient: jan}│
           │                 │
           │ Action 2:       │
           │   dagnotitie    │ ──▶ Wacht op Action 1, dan uitvoeren
           │   {patient: jan,│
           │    content: ..} │
           └─────────────────┘

Multi-intent is architectureel ondersteund maar beperkt tot sequentiële uitvoering. Parallelle uitvoering is bewust niet geïmplementeerd: de gebruiker verwacht een logische volgorde.

3.4 Nudge — Protocol-gedreven suggesties

De Nudge-laag is conceptueel anders dan Layer 1 en 2. Waar die lagen reactief zijn (gebruiker vraagt, systeem handelt), is Layer 3 proactief: het systeem stelt iets voor dat de gebruiker niet gevraagd heeft maar dat klinisch relevant is.

                   ┌───────────────────────────────┐
                   │   Afgeronde actie              │
                   │   dagnotitie: wondverzorging   │
                   └───────────────┬───────────────┘
                                   │
                                   ▼
                   ┌───────────────────────────────┐
                   │   Protocol Rule Engine         │
                   │                               │
                   │   Regel: "wondzorg-controle"  │
                   │   Trigger: dagnotitie +       │
                   │            content ~ "wond"   │
                   │   ──────────────────────────  │
                   │   Match! → Suggestie genereren│
                   └───────────────┬───────────────┘
                                   │
                                   ▼
                   ┌───────────────────────────────┐
                   │   Nudge Suggestie              │
                   │                               │
                   │   "Wondcontrole inplannen     │
                   │    over 3 dagen?"             │
                   │                               │
                   │   Protocol: V&VN Wondzorg §4.2│
                   │   [Accepteren] [Verwerpen]    │
                   └───────────────────────────────┘

De klinische onderbouwing (protocolnaam, paragraafverwijzing, rationale) is essentieel: het maakt de suggestie controleerbaar en vertrouwenswaardig. De zorgverlener beslist altijd zelf.

3.5 Anatomie van een Intent — De bouwsteen

Een intent is niet alleen een classificatieresultaat. Het is een end-to-end bouwsteen die door 8 lagen van het systeem snijdt. Elk nieuw intent raakt al deze lagen; de kwaliteit van het systeem hangt af van de consistentie ertussen.

┌─────────────────────────────────────────────────────────────────┐
│                  ANATOMIE VAN EEN INTENT                         │
│                                                                 │
│  ┌─ 1. TYPE DEFINITIE ─────────────────────────────────────┐   │
│  │  CortexIntent union type + ExtractedEntities             │   │
│  │  Bron: lib/cortex/types.ts                               │   │
│  └──────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│  ┌─ 2. REFLEX PATRONEN ────────────────────────────────────┐   │
│  │  Regex-patronen met gewichten (1.0 = exact, 0.7 = zwak)  │   │
│  │  Bron: lib/cortex/reflex-classifier.ts                    │   │
│  └──────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│  ┌─ 3. AI PROMPTBESCHRIJVING ──────────────────────────────┐   │
│  │  Intent beschrijving + triggers + entity definitie        │   │
│  │  + voorbeelddialoog met verwacht JSON-resultaat           │   │
│  │  Bron: app/api/cortex/chat/route.ts (system prompt)      │   │
│  └──────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│  ┌─ 4. VALIDATIESCHEMA ───────────────────────────────────┐    │
│  │  Zod-schema voor intent + entities in AI response        │    │
│  │  Bron: lib/cortex/action-parser.ts                       │    │
│  └──────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│  ┌─ 5. ACTIE-ROUTING ─────────────────────────────────────┐    │
│  │  Intent → artifact mapping + entity requirements check   │    │
│  │  Bepaalt of er een artifact opent of navigatie plaatsvindt│   │
│  │  Bron: lib/cortex/action-parser.ts                       │    │
│  └──────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│  ┌─ 6. API ROUTE (optioneel) ─────────────────────────────┐    │
│  │  Server-side data ophalen voor het block                 │    │
│  │  Bron: app/api/cortex/[domain]/route.ts                  │    │
│  └──────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│  ┌─ 7. BLOCK COMPONENT ──────────────────────────────────┐     │
│  │  React component die het intent visualiseert             │     │
│  │  Ontvangt prefill data uit entities                      │     │
│  │  Bron: components/cortex/blocks/[intent]-block.tsx       │     │
│  └──────────────────────────────────────────────────────────┘   │
│                           ↓                                     │
│  ┌─ 8. ARTIFACT CONTAINER ────────────────────────────────┐    │
│  │  Registratie in de artifact renderer (import, case, titel)│   │
│  │  Bron: components/cortex/artifacts/artifact-container.tsx │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Drie intenttypen

Niet elk intent heeft alle 8 lagen nodig. Er zijn drie typen:

Type Beschrijving Lagen nodig Voorbeeld
Query Block Toont data (read-only) Alle 8 risico_query, diagnose_query
Action Block Formulier voor data-invoer Alle 8 dagnotitie, create_appointment
Navigatie Intent Navigeert naar een pagina 15 (geen block/artifact) intake_navigeer

Entity-schema per intent

Elk intent definieert welke entities het verwacht:

Veld Beschrijving Voorbeeld
required Moet aanwezig zijn voor uitvoering patientName bij dagnotitie
optional Verrijkt het resultaat als aanwezig category bij dagnotitie
prefill Wordt doorgegeven aan het block als voorvulling Alle entities

Wanneer required entities ontbreken en confidence < 0.7, stelt het systeem een verduidelijkingsvraag ("Met welke patiënt?"). Wanneer ze aanwezig zijn en confidence >= 0.9, opent het direct het artifact met prefill.

Block lifecycle

Een block doorloopt vier states:

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│  Empty   │────▶│ Loading  │────▶│  Data    │────▶│  Error   │
│ (no ctx) │     │ (fetch)  │     │ (render) │     │ (retry?) │
└──────────┘     └──────────┘     └──────────┘     └──────────┘

Elke state heeft een standaard UI-component (BlockEmpty, BlockLoading, BlockError) zodat blocks consistent gedrag vertonen.

3.6 Relatie met het EPD

Cortex is geen losstaand systeem maar een laag bovenop het EPD die bestaande modules aanstuurt:

┌─────────────────────────────────────────────────────────┐
│                     CORTEX LAAG                          │
│   Natuurlijke taal → Intent → Actie → Nudge             │
└──────────┬──────────┬──────────┬──────────┬─────────────┘
           │          │          │          │
           ▼          ▼          ▼          ▼
┌────────────┐ ┌──────────┐ ┌────────┐ ┌─────────┐
│ Rapportage │ │  Agenda  │ │Dossier │ │Overdracht│
│  module    │ │  module  │ │ module │ │  module  │
└────────────┘ └──────────┘ └────────┘ └─────────┘
           │          │          │          │
           ▼          ▼          ▼          ▼
┌─────────────────────────────────────────────────────────┐
│                   DATA LAAG (Supabase)                   │
│   reports │ encounters │ patients │ conditions │ ...     │
└─────────────────────────────────────────────────────────┘

Cortex kent de EPD-modules niet direct. Het produceert intents met entities, en de UI-laag vertaalt deze naar de juiste module-interactie (formulier openen, navigeren, data ophalen). Dit houdt Cortex ontkoppeld van de specifieke EPD-implementatie.

3.7 Referentiekaders — Positionering in het bredere landschap

Cortex gebruikt concepten die herkenbaar zijn in bestaande NLU- en AI-architecturen, maar combineert ze op een domeinspecifieke manier. Deze sectie positioneert Cortex ten opzichte van gangbare patronen, zodat een architect de vertaling kan maken naar bekende referentiekaders.

3.7.1 NLU Pipelines (Rasa, Dialogflow, LUIS)

Klassieke NLU-systemen volgen een vaste pipeline: tokenize → featurize → classify → extract entities. Cortex wijkt hier op twee punten af:

Aspect Klassieke NLU Cortex
Classificatie Eén model (ML of rule-based) Twee lagen: Reflex (regex) + Orchestrator (LLM)
Entity-extractie Geïntegreerd in pipeline (NER) Gescheiden: Reflex classificeert alleen, Orchestrator extraheert
Training Vereist gelabelde dataset + trainingsloop Geen training: regex-patronen + few-shot prompting
Deployment Model artifact deployen Geen model artifact; patronen in code, LLM via API

Mapping voor een architect:

  • Cortex Reflex Arc ≈ Rule-based intent classifier (vergelijkbaar met Rasa's RegexFeaturizer + FallbackClassifier)
  • Cortex Orchestrator ≈ LLM-as-NLU (vergelijkbaar met Dialogflow CX's generative fallback, maar als primaire escalatielaag)
  • Cortex IntentChain ≈ Multi-intent parsing (vergelijkbaar met Rasa's OR-intents of Dialogflow's composite entities, maar sequentieel uitgevoerd)

Bewuste afwijking: Cortex gebruikt geen getraind ML-model. Dit is een bewuste keuze: in een GGZ-context is de vocabulary beperkt en stabiel (medische termen, vaste werkprocessen), waardoor regex-patronen hoge accuracy bereiken zonder de overhead van modeltraining en -onderhoud. Het LLM fungeert als vangnet voor de uitzonderingen.

3.7.2 Agentic Architectures (Tool Use, Function Calling)

Moderne LLM-architecturen gebruiken "tool use" of "function calling": het model beslist zelf welke tools het aanroept op basis van de gebruikersvraag. Cortex lijkt hierop maar verschilt fundamenteel:

Aspect Agentic (tool use) Cortex
Beslisser LLM beslist autonoom welke tool Reflex beslist lokaal; LLM alleen bij escalatie
Uitvoering LLM roept tools aan en verwerkt resultaat UI voert acties uit; LLM classificeert alleen
Autonomie Hoog (model plant en voert uit) Laag (mens bevestigt altijd)
Kosten per interactie Elke interactie = API call 70%+ zonder API call

Mapping voor een architect:

  • Cortex intent ≈ een function/tool definitie (naam, parameters, beschrijving)
  • Cortex IntentChain ≈ een tool-use plan (sequence of function calls)
  • Cortex entities ≈ function parameters (geëxtraheerd uit natuurlijke taal)
  • Cortex artifact/block ≈ tool execution UI (maar met menselijke bevestiging ertussen)

Bewuste afwijking: Cortex is expliciet niet agentic. In een zorgcontext is autonome uitvoering onacceptabel — de zorgverlener moet altijd de laatste beslissing nemen. Cortex bereidt acties voor en vult formulieren in, maar schrijft nooit zelfstandig naar het dossier. Dit is een harde eis, geen beperking.

3.7.3 Clinical Decision Support (CDS)

De Nudge-laag (Layer 3) raakt aan Clinical Decision Support — systemen die zorgverleners proactief attenderen op klinisch relevante acties.

Aspect CDS (HL7 CDS Hooks) Cortex Nudge
Trigger Workflow event (order-sign, patient-view) Intent completion (dagnotitie opgeslagen)
Regelformat CQL (Clinical Quality Language) of FHIR PlanDefinition TypeScript conditie-objecten (equals, contains, matches)
Output CDS Card (suggestion, info, warning) NudgeSuggestion (suggestie + protocol referentie)
Standaard HL7 CDS Hooks specificatie Eigen formaat

Mapping voor een architect:

  • Cortex NudgeSuggestion ≈ CDS Hooks Card (suggestion type)
  • Cortex ProtocolMetadata ≈ CDS source (clinical evidence reference)
  • Cortex PROTOCOL_RULES ≈ CDS service rules (maar in code i.p.v. FHIR PlanDefinition)

Pad naar standaardisatie: Voor enterprise-gebruik kan de Nudge-engine evolueren naar CDS Hooks-compatibiliteit. Dit vereist:

  1. Regels uitdrukken als FHIR PlanDefinition resources (i.p.v. TypeScript objecten)
  2. Trigger-events mappen naar CDS Hooks trigger types
  3. Output formatteren als CDS Cards

Dit is niet nodig voor MVP maar bepaalt of de Nudge-laag kan integreren met externe CDS-systemen (bijv. ziekenhuis-CDS die al HL7-compatibel is).

3.7.4 FHIR en SMART on FHIR

Het EPD gebruikt een FHIR-geïnspireerd datamodel (patients, observations, conditions, encounters), maar is geen FHIR-server. Cortex raakt FHIR op twee punten:

1. Entities → FHIR Resources

Geëxtraheerde entities moeten uiteindelijk mappen naar FHIR-resources:

Cortex Entity FHIR Resource Voorbeeld
patientName / patientId Patient Patient/123
category + content Observation of DocumentReference Rapportage opslaan
datetime + appointmentType Appointment Afspraak aanmaken
navigationTarget (diagnose) Condition Diagnoselijst bekijken

2. SMART on FHIR als launch-context

In een enterprise-omgeving wordt Cortex mogelijk gestart vanuit een bestaand EPD via SMART on FHIR launch. De launch-context (welke patiënt, welke practitioner, welke encounter) vervangt dan de huidige CortexContext:

Cortex Context SMART on FHIR equivalent
activePatient Launch context patient
currentView Launch context encounter
shift Geen equivalent (domeinspecifiek)
agendaToday Geen equivalent (custom query)

Implicatie voor architectuur: De CortexContext interface moet zo ontworpen zijn dat hij zowel intern (uit de eigen UI) als extern (uit een SMART launch) gevuld kan worden. Het huidige ontwerp sluit dit niet uit, maar de context-opbouw zit verweven in de store i.p.v. in een aparte context-provider.

3.7.5 Samenvattende positionering

                        Autonomie
                           ▲
                           │
              Agentic      │
              (tool use)   │
                    ●      │
                           │
                           │          Cortex
                           │          Orchestrator
                           │              ●
                           │
                           │      Cortex
                           │      Reflex
               NLU         │        ●
               pipeline ●  │
                           │
              ─────────────┼──────────────────▶ Domeinkennis
              Generiek     │            Zorgspecifiek
                           │
                           │    ● Cortex Nudge
                           │      (≈ CDS)
                           │

Cortex zit in het kwadrant laag-autonoom + hoog-domeinspecifiek. Het gebruikt NLU-concepten (intent, entity, confidence) maar past ze toe in een context waar snelheid (Reflex), veiligheid (geen autonome uitvoering) en klinische relevantie (protocol-gedreven nudges) zwaarder wegen dan modelflexibiliteit.


4. Huidige Implementatie (As-Is)

4.1 Overzicht

De conceptuele architectuur uit hoofdstuk 3 is in het prototype als volgt geïmplementeerd:

Gebruikersinput (tekst/spraak)
        │
        ▼
┌─────────────────────┐
│  Layer 1: Reflex    │  <20ms, regex, lokaal
│  (reflex-classifier)│
└────────┬────────────┘
         │ escalatie bij lage confidence/ambiguïteit/complexiteit
         ▼
┌─────────────────────┐
│  Layer 2: Orchestr. │  Claude Haiku, AI-classificatie
│  (orchestrator)     │
└────────┬────────────┘
         │ na succesvolle actie
         ▼
┌─────────────────────┐
│  Layer 3: Nudge     │  Protocolregels, suggesties
│  (nudge)            │
└─────────────────────┘

4.2 Layer 1: Reflex Arc

Bestand: lib/cortex/reflex-classifier.ts

Doel: Snelle, lokale intentclassificatie via gewogen regex-patronen.

Werking:

  1. Detecteert eerst escalatietriggers (multi-intent, voornaamwoorden, relatieve tijd)
  2. Bij escalatietrigger → direct doorsturen naar Layer 2 met best-guess intent
  3. Zonder trigger → patroonmatching met gewichten (1.0=exact, 0.8=sterk, 0.6=partieel)
  4. Houdt top-2 matches bij voor ambiguïteitdetectie
  5. Escaleert als confidence <0.7 of als top-2 delta <0.1

Huidige intents (11 totaal):

Intent Categorie Patronen
dagnotitie P1 (kritiek) 12 patronen
zoeken P1 10 patronen
overdracht P1 10 patronen
agenda_query P2 10 patronen
create_appointment P2 8 patronen
cancel_appointment P2 6 patronen
reschedule_appointment P2 7 patronen
intake_status P3 (intake) 7 patronen
intake_navigeer P3 10 patronen
risico_query P3 6 patronen
diagnose_query P3 6 patronen

Beperking: Reflex doet geen entity-extractie. Het resultaat bevat altijd entities: {}. Entities worden pas door Layer 2 of de Chat API geëxtraheerd.

4.3 Layer 2: Orchestrator

Bestand: lib/cortex/orchestrator.ts

Doel: AI-gestuurde classificatie voor complexe input via Claude 3.5 Haiku.

Werking:

  1. Ontvangt input + volledige applicatiecontext (actieve patiënt, agenda, recente acties)
  2. Formatteert context naar leesbare prompt
  3. Claude analyseert en retourneert JSON met: actions[], reasoning, needsClarification
  4. Zod-validatie op AI-response met fallback bij parse-fouten
  5. Bouwt IntentChain met één of meer sequentiële acties
  6. Bij falen: fallback naar Reflex-resultaat met confidence gecapped op 0.6

Capabilities:

  • Multi-intent splitting ("annuleer afspraak en maak notitie" → 2 acties)
  • Voornaamwoordresolutie ("hij" → actieve patiënt)
  • Relatieve tijdparsing ("morgen" → concrete datum)
  • Bevestigingsvlaggen voor destructieve acties

Timeout: 5 seconden via Promise.race

4.4 Layer 3: Nudge

Bestand: lib/cortex/nudge.ts

Doel: Proactieve suggesties na afgeronde acties op basis van klinische protocollen.

Werking:

  1. Na voltooiing van een actie wordt evaluateNudge() aangeroepen
  2. Controleert alle protocolregels: intent-match + conditiechecks (AND-logica)
  3. Condities: equals, contains, matches (regex), exists
  4. Genereert NudgeSuggestion met voorgeinvulde entities
  5. Sorteert op prioriteit (high → medium → low)
  6. Suggesties verlopen na 5 minuten (configureerbaar per regel)

Huidige regels (2 MVP):

Regel Trigger Suggestie
Wondzorg-controle dagnotitie + content bevat "wond" Wondcontrole inplannen over 3 dagen
Medicatie-controle dagnotitie + content bevat "medicatie" + "gewijzigd" Medicatie evaluatie over 1 week

4.5 API-integratie

Classify API (/api/cortex/classify)

Hybride endpoint dat Layer 1 en 2 combineert:

  1. Authenticatie via Supabase
  2. Zod-validatie op request (input, context, options)
  3. Feature flag check (CORTEX_V2_ENABLED)
  4. Reflex → eventueel Orchestrator → eventueel Fallback
  5. Retourneert IntentChain + debug-informatie

Chat API (/api/cortex/chat)

Streaming conversatie-endpoint (SSE) met Claude Sonnet:

  • Eigen system prompt met volledige intentbeschrijvingen
  • Conversatiegeschiedenis (max 20 berichten)
  • Genereert JSON action objects in de tekststroom
  • In-memory rate limiting (20 req/min per gebruiker)
  • Eigen entity-extractie via prompt engineering

Overige Cortex APIs

  • /api/cortex/context — Gebruikerscontext ophalen
  • /api/cortex/patients/search — Patiëntzoeken
  • /api/cortex/agenda/* — CRUD voor afspraken
  • /api/cortex/intake/* — Intakequeries

4.6 State Management

Bestand: stores/cortex-store.ts (Zustand)

Eén monolithische store voor alle Cortex-state:

  • Context: activePatient, shift, recentPatients
  • Chat: chatMessages, isStreaming, pendingAction
  • Artifacts: openArtifacts (max 3), activeArtifactId
  • V2 Chain: activeChain, chainHistory
  • Nudge: suggestions (add/accept/dismiss)
  • Clarification: pendingClarification
  • UI: inputValue, isVoiceActive, patientSidebarOpen

4.7 Feature Flags

Bestand: lib/config/feature-flags.ts

Vier flags via environment variables:

  • CORTEX_V2_ENABLED — Drie-laags architectuur aan/uit
  • CORTEX_MULTI_INTENT — Multi-intent detectie
  • CORTEX_NUDGE — Proactieve suggesties
  • CORTEX_LOGGING — Debug logging

In development staan alle flags standaard aan (tenzij expliciet false).


5. Analyse: Sterke punten

5.1 Architecturele beslissingen

Beslissing Toelichting
Drie-laags fallback Graceful degradation: als AI faalt, valt het systeem terug op lokale matching
Gewogen patroonmatching Nuanced confidence via weights i.p.v. binaire matches
Ambiguïteitdetectie Top-2 tracking voorkomt foutieve classificatie bij twijfelgevallen
Zod-validatie op AI output Beschermt tegen onverwachte LLM-responses met gestructureerde fallbacks
IntentChain-model Multi-intent support is architectureel correct opgezet
Protocol-based nudges Klinische onderbouwing via metadata (naam, referentie, rationale)

5.2 Implementatiekwaliteit

  • TypeScript-types zijn doordacht en consistent
  • Escalatielogica is helder: detectie → reden → result
  • Fallback-chain is compleet: AI fout → Reflex result met confidence cap
  • Zod-schemas met .catch() en .passthrough() voor robuuste parsing

6. Analyse: Knelpunten

6.1 K1 — Twee divergente classificatiepaden

Probleem: De Classify API (/api/cortex/classify) en de Chat API (/api/cortex/chat) zijn volledig onafhankelijke implementaties van intentherkenning.

Aspect Classify API Chat API
Classificatie Reflex → Orchestrator Eigen system prompt
Model Claude Haiku Claude Sonnet
Entity-extractie AI (Orchestrator) Prompt engineering
Output IntentChain (gestructureerd) JSON-in-tekststroom
Intents beschreven orchestrator.ts prompt chat/route.ts prompt

Impact: Intentbeschrijvingen worden op twee plekken bijgehouden. Wijzigingen in het ene pad worden niet automatisch doorgevoerd in het andere.

Ernst: Hoog — Dit is de belangrijkste architecturale schuld.

6.2 K2 — Intent-definities op drie plekken

Een intent wordt momenteel op drie locaties gedefinieerd:

  1. types.ts — TypeScript type (CortexIntent union type)
  2. reflex-classifier.ts — Regex-patronen en gewichten
  3. orchestrator.ts — Systemprompt met intentbeschrijvingen en voorbeelden

Een nieuw intent toevoegen vereist wijzigingen op alle drie plekken, plus optioneel de Chat API prompt (vierde plek).

Impact: Hoog risico op inconsistentie bij wijzigingen.

6.3 K3 — Reflex doet geen entity-extractie

Layer 1 retourneert altijd entities: {}. Dit betekent dat zelfs bij een hoge-confidence match ("notitie jan medicatie") geen entities worden doorgegeven aan de UI. De actie wordt geopend zonder prefill-data.

Impact: Gemiste kans op snellere interactie. In 70%+ van de gevallen (Reflex handled) gaat entity-informatie verloren.

6.4 K4 — Nudge-systeem is niet geïntegreerd

De evaluateNudge() functie bestaat maar er is geen duidelijk aanroeppunt in de applicatiecode na actievoltooiing. De integratie tussen actiecompletie (store) en nudge-evaluatie ontbreekt.

Impact: Layer 3 is effectief inactief zonder expliciete aanroep.

6.5 K5 — Monolithische store

De Zustand store bevat ~580 regels en beheert 7 verschillende domeinen (context, chat, artifacts, chains, nudges, clarification, UI). Dit maakt de store moeilijk te testen en te refactoren.

6.6 K6 — Operationele blinde vlekken

  • Logging: Alleen console.log met feature flag; geen gestructureerde logging
  • Metrics: Geen tracking van classificatiesnelheid, hit rates, of fallback-frequentie in productie
  • Rate limiting: In-memory (verloren bij restart), niet geschikt voor multi-instance deployment

6.7 K7 — Orchestrator timeout is niet doorverbonden

De classifyWithTimeout wrapper maakt een AbortController aan, maar verbindt het signal niet met de daadwerkelijke fetch-call in classifyWithOrchestrator. Bij timeout wordt het API-verzoek naar Claude niet afgebroken — het blijft lopen.


7. Doelarchitectuur (To-Be)

7.1 Architectuuroverzicht

┌───────────────────────────────────────────────────────────────────┐
│                     CORTEX COMMAND CENTER                         │
│                                                                   │
│  ┌─────────┐   ┌──────────────────────────────────────────────┐  │
│  │  Input   │──▶│         Classification Pipeline              │  │
│  │ (tekst/  │   │                                              │  │
│  │  spraak) │   │  ┌──────────┐   ┌──────────┐   ┌────────┐  │  │
│  └─────────┘   │  │ Reflex   │──▶│ Orchestr.│──▶│ Entity │  │  │
│                 │  │ (Layer 1)│   │ (Layer 2)│   │ Enrich │  │  │
│                 │  └──────────┘   └──────────┘   └────────┘  │  │
│                 │          ↑                                   │  │
│                 │    Intent Registry (single source)           │  │
│                 └──────────────────┬───────────────────────────┘  │
│                                    │                              │
│                                    ▼                              │
│                 ┌──────────────────────────────────────────────┐  │
│                 │         Action Executor                       │  │
│                 │  Chain uitvoering → API calls → Result        │  │
│                 └──────────────────┬───────────────────────────┘  │
│                                    │                              │
│                                    ▼                              │
│                 ┌──────────────────────────────────────────────┐  │
│                 │         Nudge Evaluator (Layer 3)             │  │
│                 │  Protocol rules → Suggesties                  │  │
│                 └──────────────────────────────────────────────┘  │
│                                                                   │
│  ┌───────────┐  ┌────────────┐  ┌────────────┐  ┌────────────┐  │
│  │   Chat    │  │  Artifact  │  │   Nudge    │  │  Context   │  │
│  │   Store   │  │   Store    │  │   Store    │  │   Store    │  │
│  └───────────┘  └────────────┘  └────────────┘  └────────────┘  │
└───────────────────────────────────────────────────────────────────┘

Kernwijziging: Eén classificatiepipeline als backbone. Chat en Classify gebruiken dezelfde pipeline. De Chat API voegt conversatie-context toe, maar delegeert classificatie.

7.2 Intent Registry — Single Source of Truth

Probleem opgelost: K1, K2

Het centrale concept is een IntentRegistry: één plek waar elk intent volledig is gedefinieerd.

// lib/cortex/intent-registry.ts

interface IntentDefinition {
  /** Uniek ID */
  id: CortexIntent;

  /** Weergavenaam (Nederlands) */
  label: string;

  /** Beschrijving voor AI-prompt (Nederlands) */
  description: string;

  /** Prioriteit: P1 (kritiek), P2 (belangrijk), P3 (overig) */
  priority: 'P1' | 'P2' | 'P3';

  /** Regex-patronen voor Reflex Arc met gewichten */
  reflexPatterns: Array<{ pattern: RegExp; weight: number }>;

  /** Entity-schema voor dit intent */
  entitySchema: {
    required: string[];
    optional: string[];
    extraction: EntityExtractionRule[];
  };

  /** Of dit intent bevestiging vereist */
  requiresConfirmation: boolean;

  /** Voorbeelden voor AI-prompt (input → verwacht resultaat) */
  examples: Array<{ input: string; entities: Record<string, string> }>;

  /** Gekoppeld artifact type */
  artifactType: BlockType | null;

  /** Feature flag (optioneel) */
  featureFlag?: FeatureFlagKey;
}

Voordelen:

  • Eén plek om een intent toe te voegen of te wijzigen
  • Reflex-patronen en AI-promptbeschrijvingen komen uit dezelfde bron
  • Entity-schema's zijn herbruikbaar voor validatie
  • Voorbeelden dienen zowel de AI-prompt als test-cases
  • Feature flags per intent voor granulaire rollout

Afgeleide functies:

  • getReflexPatterns() → genereert patroonmap voor Layer 1
  • buildOrchestratorPrompt() → genereert systemprompt voor Layer 2 vanuit registry
  • buildChatPrompt() → genereert systemprompt voor Chat API vanuit registry
  • getEntitySchema(intent) → retourneert Zod-schema voor entity-validatie

7.3 Layer 1: Reflex Arc (verbeterd)

Probleem opgelost: K3

Input
  │
  ├─ 1. Escalatiedetectie (ongewijzigd)
  │
  ├─ 2. Patroonmatching via IntentRegistry
  │
  └─ 3. [NIEUW] Basis entity-extractie
       │
       └─ EntityExtractionRule's uit registry
          Voorbeeld: patientName via /^notitie\s+(\w+)/

Wijzigingen t.o.v. huidige implementatie:

  1. Patronen laden uit IntentRegistry i.p.v. hardcoded INTENT_PATTERNS object
  2. Basis entity-extractie toevoegen via capture groups in regex-patronen
  3. Patroonmatching optimalisatie: patronen per intent gesorteerd op gewicht (hoogste eerst), met early-exit bij weight 1.0

Entity-extractie voorbeeld:

interface EntityExtractionRule {
  /** Regex met named capture groups */
  pattern: RegExp;
  /** Mapping: capture group naam → entity veld */
  mapping: Record<string, keyof ExtractedEntities>;
}

// Voorbeeld voor dagnotitie:
{
  pattern: /^notitie\s+(?<patient>\w+)\s+(?<cat>medicatie|adl|gedrag)/i,
  mapping: { patient: 'patientName', cat: 'category' }
}

Wat NIET wijzigt: De escalatielogica, confidence thresholds, en ambiguïteitdetectie blijven ongewijzigd. Deze werken goed.

7.4 Layer 2: Orchestrator (verbeterd)

Probleem opgelost: K1, K7

Wijzigingen:

7.4.1 Promptgeneratie uit IntentRegistry

De systemprompt wordt niet meer handmatig bijgehouden maar gegenereerd:

function buildOrchestratorPrompt(registry: IntentDefinition[]): string {
  const intentSection = registry
    .map(intent => `
**${intent.id}** — ${intent.description}
  Entities: ${intent.entitySchema.required.join(', ')} (verplicht), ${intent.entitySchema.optional.join(', ')} (optioneel)
  Voorbeelden: ${intent.examples.map(e => `"${e.input}"`).join(', ')}
    `).join('\n');

  return `${ORCHESTRATOR_BASE_PROMPT}\n\n## Intent Types\n${intentSection}`;
}

De basisprompt (instructies over multi-intent, voornaamwoordresolutie, etc.) blijft statisch. Alleen de intentlijst wordt dynamisch gegenereerd.

7.4.2 AbortController doorverbinden

// Fix: signal doorverbinden naar fetch
const response = await fetch('https://api.anthropic.com/v1/messages', {
  signal,  // AbortController.signal
  // ... rest
});

7.4.3 Circuit breaker (simpel)

In plaats van alleen een timeout, een eenvoudig circuit breaker-patroon:

interface CircuitState {
  failures: number;
  lastFailure: number;
  isOpen: boolean;
}

// Na 3 opeenvolgende fouten: circuit open voor 30 seconden
// Gedurende open circuit: directe fallback naar Reflex zonder API-call

Dit voorkomt dat bij een Claude API-storing elk request 5 seconden wacht op timeout.

7.5 Unified Classification Pipeline

Probleem opgelost: K1

De kern van de verbetering: één pipeline die zowel de Classify als Chat API gebruiken.

┌──────────────────────────────────────────────────────────┐
│                Classification Pipeline                    │
│                                                          │
│  classifyInput(input, context) → ClassificationResult    │
│                                                          │
│  1. Reflex Arc → LocalResult                             │
│  2. if (shouldEscalate) → Orchestrator → AIResult        │
│  3. if (AIResult fails) → Fallback                       │
│  4. Entity enrichment → Final result                     │
│                                                          │
│  Output: { intent, confidence, entities, chain, source } │
└──────────────────────────────────────────────────────────┘
           │                              │
           ▼                              ▼
┌──────────────────┐          ┌──────────────────────┐
│  Classify API    │          │  Chat API            │
│  Retourneert     │          │  Gebruikt result als │
│  pipeline result │          │  context voor LLM    │
│  direct          │          │  conversatie         │
└──────────────────┘          └──────────────────────┘

Chat API wijziging: In plaats van intentherkenning te dupliceren in de systemprompt, stuurt de Chat API eerst de input door de pipeline. Het resultaat wordt meegegeven aan de LLM als context:

Systemprompt (kort): Je bent Cortex Assistent. [tone of voice, regels]

Context: { classificationResult, activePatient, shift }

Opdracht: Formuleer een natuurlijk antwoord en bevestig de actie.

De LLM hoeft niet meer te classificeren — alleen te converseren.

7.6 Layer 3: Nudge (verbeterd)

Probleem opgelost: K4

7.6.1 Integratiepatroon

De integratie loopt via de Action Executor:

// In de action executor (na succesvolle actie):
async function executeAction(action: IntentAction): Promise<void> {
  // ... actie uitvoeren ...

  // Na succes: nudge evalueren
  const nudges = evaluateNudge({
    intent: action.intent,
    actionId: action.id,
    entities: action.entities,
    content: originalInput,
  });

  // Nudges toevoegen aan store
  for (const nudge of nudges) {
    nudgeStore.addSuggestion(nudge);
  }
}

7.6.2 Regelopslag scheiden van engine

┌─────────────────────────────────────────────┐
│              Nudge Engine                     │
│                                              │
│  evaluateNudge(input, rules) → suggestions  │
│                                              │
│  - Puur functioneel: input → output          │
│  - Geen kennis van waar regels vandaan komen │
└──────────────────────┬──────────────────────┘
                       │
            Regels laden uit:
                       │
         ┌─────────────┴─────────────┐
         │                           │
    ┌────▼────┐              ┌───────▼──────┐
    │ Hardcod │              │  Database/   │
    │ (MVP)   │              │  Config (v2) │
    └─────────┘              └──────────────┘

De evaluateNudge functie accepteert regels als parameter i.p.v. het importeren van PROTOCOL_RULES. Dit maakt de engine testbaar en de regelbron vervangbaar.

7.6.3 Feedback loop (post-MVP)

Track per regel:

  • Aantal keer getoond
  • Aantal keer geaccepteerd
  • Aantal keer verworpen
  • Gemiddelde reactietijd

Dit is bewust post-MVP maar het datamodel moet er nu al rekening mee houden door accept/dismiss events op te slaan.

7.7 State Management (opsplitsing)

Probleem opgelost: K5

Splits de monolithische store in domein-specifieke stores:

stores/
├── cortex-context-store.ts   # activePatient, shift, recentPatients
├── cortex-chat-store.ts      # chatMessages, isStreaming, pendingAction
├── cortex-artifact-store.ts  # openArtifacts, activeArtifactId
├── cortex-action-store.ts    # activeChain, chainHistory
└── cortex-nudge-store.ts     # suggestions, accept/dismiss

Koppeling tussen stores: Via Zustand's subscribe mechanisme. Voorbeeld:

// Wanneer een chain completeert, evalueer nudges
cortexActionStore.subscribe(
  state => state.activeChain,
  (chain, previousChain) => {
    if (previousChain && !chain) {
      // Chain is zojuist voltooid
      const completedActions = previousChain.actions.filter(a => a.status === 'success');
      // → evaluateNudge voor elke voltooide actie
    }
  }
);

Elke store blijft klein (<150 regels), testbaar, en verantwoordelijk voor één domein.

7.8 Observability

Probleem opgelost: K6

7.8.1 Gestructureerde logging

Vervang console.log door gestructureerde log-events:

interface CortexLogEvent {
  timestamp: string;
  layer: 'reflex' | 'orchestrator' | 'nudge' | 'pipeline';
  event: string;
  data: Record<string, unknown>;
  durationMs?: number;
  userId?: string;
}

// Voorbeeld:
log({
  layer: 'reflex',
  event: 'classification_complete',
  data: {
    input: input.slice(0, 50),
    intent: result.intent,
    confidence: result.confidence,
    escalated: result.shouldEscalateToAI,
    escalationReason: result.escalationReason,
  },
  durationMs: result.processingTimeMs,
});

7.8.2 Metrics

Minimale set metrics voor productie:

Metric Type Beschrijving
cortex.classify.duration_ms histogram Totale classificatietijd
cortex.reflex.hit_rate counter % dat Reflex zelfstandig afhandelt
cortex.orchestrator.fallback_rate counter % AI-fouten met fallback
cortex.orchestrator.timeout_rate counter % timeouts
cortex.nudge.shown counter Nudges getoond per regel-ID
cortex.nudge.accepted counter Nudges geaccepteerd per regel-ID
cortex.intent.distribution counter Frequentie per intent type

In eerste instantie te loggen naar de bestaande ai_events tabel. Externe monitoring (Datadog, Grafana) als post-MVP.


8. Integratiearchitectuur

8.1 API-structuur (herzien)

/api/cortex/
├── classify/          # POST — Classification Pipeline (bestaand, herzien)
├── chat/              # POST — Streaming chat (herzien: gebruikt pipeline)
├── context/           # GET  — Gebruikerscontext (ongewijzigd)
├── patients/search/   # GET  — Patiëntzoeken (ongewijzigd)
├── agenda/            # GET  — Dagagenda (ongewijzigd)
├── agenda/create/     # POST — Afspraak maken (ongewijzigd)
├── agenda/cancel/     # POST — Afspraak annuleren (ongewijzigd)
├── agenda/reschedule/ # POST — Afspraak verzetten (ongewijzigd)
└── intake/            # GET  — Intakequeries (ongewijzigd)

De primaire wijziging is dat /api/cortex/chat intern de classification pipeline aanroept in plaats van classificatie te dupliceren.

8.2 Dataflow: Input tot Actie

1. Gebruiker typt/spreekt: "Notitie jan medicatie gegeven"
                    │
2. Client stuurt naar: POST /api/cortex/chat
                    │
3. Chat API:        │
   a. classifyInput(input, context)
      → Reflex: intent=dagnotitie, confidence=1.0, entities={patientName:"jan", category:"medicatie"}
      → Geen escalatie nodig
                    │
   b. LLM call met:
      - Systemprompt (tone of voice, regels)
      - Classification result als context
      - Conversatiegeschiedenis
                    │
   c. LLM genereert: "Komt voor elkaar. Jan, medicatie."
      + JSON action block
                    │
4. Client ontvangt SSE stream
                    │
5. Action parsing → pendingAction in store
                    │
6. CommandCenter opent DagnotitieBlock met prefill:
   { patientName: "jan", category: "medicatie", content: "medicatie gegeven" }
                    │
7. Gebruiker bevestigt/past aan → POST /api/reports
                    │
8. Na succes: evaluateNudge()
   → Geen match (content bevat geen "wond" of "medicatie gewijzigd")
   → Geen nudge

8.3 Afhankelijkheden buiten scope

Afhankelijkheid Relatie met Cortex Status
Autorisatiemodel Bepaalt welke intents een gebruiker mag uitvoeren Niet geïmplementeerd
Datamodel (FHIR) Entities moeten mappen naar FHIR-resources Gedeeltelijk
Audit trail Elke Cortex-actie moet gelogd worden Basis via ai_events tabel
Notificatiesysteem Nudges zouden ook via push/email kunnen Niet aanwezig

9. Enterprise-eisen

9.1 Niet-Functionele Eisen (NFR's)

9.1.1 Latency

Scenario Target (P95) Target (P99) Huidige status
Reflex-only classificatie <20ms <50ms Voldoet (regex, lokaal)
Reflex + Orchestrator (AI) <3s <5s ~2-4s (afhankelijk van Claude API)
End-to-end: input → artifact open <500ms (Reflex) / <4s (AI) <1s / <6s Niet gemeten
Chat response (first token) <1s <2s ~1-2s (SSE streaming)
Nudge evaluatie <10ms <20ms Voldoet (lokale regelengine)

Latency budget uitsplitsing (worst case, AI-pad):

Gebruikersinput                    0ms
├─ Reflex classificatie            20ms
├─ Escalatiebeslissing             1ms
├─ Orchestrator API call        3000ms  ← dominante factor
├─ Response parsing + validatie    5ms
├─ Entity enrichment              10ms
├─ Store update + UI render       50ms
└─ Artifact open (data fetch)   200ms
                          Totaal: ~3.3s

Implicatie: De Orchestrator API call bepaalt de E2E latency. Optimalisatie moet zich richten op (1) Reflex hit-rate verhogen zodat minder naar AI geëscaleerd wordt, en (2) circuit breaker zodat bij API-traagheid snel gefallbacked wordt.

9.1.2 Beschikbaarheid

Component Beschikbaarheidseis Failover-strategie
Reflex Arc 99.9% (systeembeschikbaarheid) Geen externe dependency; draait in-process
Orchestrator Best-effort (afhankelijk van AI-provider) Fallback naar Reflex met confidence cap 0.6
Nudge Engine 99.9% (lokale regelengine) Geen externe dependency
Chat API Best-effort Fallback: classificatie werkt nog, conversatie niet
Cortex als geheel 99.9% voor basisclassificatie Drie degradatieniveaus (zie hieronder)

Degradatieniveaus:

Niveau 0: Volledig operationeel
           Reflex + Orchestrator + Nudge + Chat
           Latency: P95 <3s (AI-pad), <500ms (Reflex-pad)

Niveau 1: AI-provider onbeschikbaar
           Reflex + Nudge + Chat (zonder classificatie)
           Impact: Complexe input wordt minder goed begrepen
           Latency: P95 <500ms (alles lokaal)
           Automatische trigger: Circuit breaker na 3 fouten

Niveau 2: Database onbeschikbaar
           Alleen Reflex classificatie (geen data-acties)
           Impact: Intents worden herkend maar niet uitgevoerd
           Gebruikersfeedback: "Classificatie succesvol, maar
           de actie kan momenteel niet worden uitgevoerd"

Niveau 3: Volledig onbeschikbaar
           Cortex command center niet bereikbaar
           Impact: Gebruiker valt terug op traditionele EPD-navigatie
           EPD-modules blijven direct bereikbaar

9.1.3 AI-provider abstractie

Het systeem is momenteel hard gekoppeld aan Anthropic Claude (Haiku voor classificatie, Sonnet voor chat). Dit is een vendor lock-in risico.

Huidige koppelingen:

Koppeling Locatie Impact bij providerwisseling
Model ID (claude-3-5-haiku) orchestrator.ts Aanpassen
Model ID (claude-sonnet-4-5-20250929) chat/route.ts Aanpassen
Anthropic SDK (@anthropic-ai/sdk) Beide API routes Vervangen
Prompt-format (system/user rollen) Orchestrator + Chat Herschrijven (model-afhankelijk)
Response-parsing (JSON uit LLM) orchestrator.ts Herijken (output varieert per model)

Aanbevolen abstractie (minimaal):

interface LLMProvider {
  classify(input: string, systemPrompt: string, context: string): Promise<string>;
  stream(messages: Message[], systemPrompt: string): AsyncIterable<string>;
}

Eén interface, twee implementaties (Anthropic, OpenAI). Geen framework (LangChain, Vercel AI SDK) nodig — de interface is klein genoeg om direct te implementeren.

Prompt-portabiliteit: Prompts zijn niet 1-op-1 overdraagbaar tussen modellen. De systeemprompt (met JSON-outputformat, intentbeschrijvingen, en voorbeelden) is geoptimaliseerd voor Claude. Bij een providerwisseling moeten prompts geherijkt worden. Dit is inherent aan LLM-gebruik en niet volledig te abstraheren.

9.1.4 Schaalbaarheid

Dimensie Huidig ontwerp Enterprise target Bottleneck
Concurrent users Onbeperkt (stateless API) 100-500 per organisatie Rate limiting (nu in-memory)
Intents per seconde Niet gemeten 50-100 req/s AI-provider rate limits
Aantal intents 11 30-50 Reflex patroonmatching O(n×p)
Conversatie-lengte 20 berichten (hardcoded) 20-50 berichten Context window + kosten
Nudge-regels 2 (hardcoded) 20-50 Evaluatietijd O(r×c)

Reflex schaalbaarheid bij meer intents:

Bij 11 intents × ~8 patronen = ~88 regex evaluaties per classificatie. Bij 50 intents × 8 patronen = ~400 evaluaties. Dit blijft <50ms maar kan geoptimaliseerd worden met:

  1. Keyword pre-filter: Eerste woord matchen tegen index → alleen relevante intents evalueren
  2. Prioriteit-gebaseerde volgorde: P1-intents eerst; bij hoge-confidence match, stop
  3. Categorie-groepering: Intents per domein (klinisch, administratief, navigatie) → per categorie evalueren

Deze optimalisaties zijn beschreven in docs/intent/design/architecture-intent-scalability.md.

Rate limiting (enterprise):

In-memory rate limiting (huidige implementatie) verliest state bij restart en werkt niet bij meerdere server-instances. Enterprise-alternatief:

Optie Complexiteit Geschikt voor
Supabase rate limit tabel Laag Single-instance, eenvoudig
Redis (Upstash) Medium Multi-instance, serverless
API Gateway rate limiting Laag Als er al een API gateway is

9.1.5 Data-eigenaarschap en retentie

Datatype Eigenaar Retentie Locatie
Classificatieresultaten Organisatie 90 dagen (analytics) ai_events tabel
Chatberichten Organisatie Sessie-duur (niet persistent) In-memory (client)
AI API calls (input/output) Organisatie + AI-provider Per provider-DPA Anthropic servers (niet persistent bij API)
Nudge-interacties Organisatie 1 jaar (kwaliteitsverbetering) Database
Audit events Organisatie 5 jaar (NEN 7510) Audit-tabel (append-only)

Aandachtspunten:

  • Chatberichten worden niet naar de database geschreven. Bij een enterprise-omgeving met auditplicht moet overwogen worden of chatgeschiedenis persistent moet zijn.
  • AI API calls bevatten potentieel patiëntgegevens in de prompt. Anthropic's data processing agreement garandeert dat API-data niet gebruikt wordt voor training, maar dit moet contractueel geborgd zijn.
  • PII in logs: Alle log-events moeten door een sanitisatiestap gaan die patiëntnamen, BSN, en andere PII verwijdert of hasht voordat ze worden opgeslagen.

9.2 NEN 7510 (Informatiebeveiliging in de zorg)

Eis Huidige status Actie nodig
Toegangsbeveiliging Supabase Auth + RLS Rol-gebaseerde intentfiltering toevoegen
Logging van toegang Gedeeltelijk (ai_events) Uitbreiden naar alle Cortex-acties
Geen PII in logs Niet gegarandeerd Inputsanitisatie in log-events
Encryptie in transit HTTPS (Vercel default) Voldoende
Encryptie at rest Supabase managed Voldoende
Integriteitscontrole Niet aanwezig Checksums op audit-records (append-only tabel)
Incidentregistratie Niet aanwezig Cortex-fouten loggen als beveiligingsincident bij herhaaldelijk falen

9.3 Audit Logging

Elke Cortex-interactie moet gelogd worden:

interface AuditEvent {
  userId: string;
  action: 'classify' | 'chat' | 'nudge_shown' | 'nudge_accepted' | 'action_executed';
  intent?: CortexIntent;
  confidence?: number;
  source?: 'reflex' | 'orchestrator' | 'fallback';
  patientId?: string;
  input?: string;       // Gesanitiseerd (PII verwijderd)
  result?: string;       // Samenvatting, geen volledige response
  timestamp: string;
  sessionId?: string;    // Voor correlatie binnen één gebruikerssessie
  durationMs?: number;   // Verwerkingstijd
  metadata?: Record<string, unknown>;
}

Opslagvereisten:

  • Append-only tabel (geen UPDATE of DELETE)
  • Retentie: minimaal 5 jaar (NEN 7510)
  • Indexen op: userId, patientId, timestamp, action

9.4 Role-Based Access (voorbereiding)

Het intent-systeem moet voorbereid zijn op rolgebaseerde filtering:

interface IntentDefinition {
  // ... bestaande velden ...

  /** Welke rollen mogen dit intent gebruiken */
  allowedRoles?: string[];  // Leeg = iedereen
}

Voorbeeldtoepassing:

Rol Toegestane intents Geblokkeerde intents
Verpleegkundige dagnotitie, overdracht, zoeken, agenda_*
Behandelaar Alle
Stagiair zoeken, agenda_query cancel_appointment, reschedule_appointment
Admin Alle + systeem-intents

Dit hoeft nu niet geïmplementeerd te worden, maar het veld moet gereserveerd zijn in de IntentRegistry. De filtering kan als middleware in de Classification Pipeline worden toegevoegd:

Input → [RBAC filter] → Reflex → Orchestrator → Result
         ↓
         Als intent niet toegestaan:
         → "Je hebt geen toegang tot deze functie"

10. Migratiepad

10.1 Fase 1 — Intent Registry + Reflex entity-extractie

Doel: Single Source of Truth voor intents, betere Reflex output.

  1. Maak lib/cortex/intent-registry.ts met alle 11 intents
  2. Refactor reflex-classifier.ts om patronen uit registry te laden
  3. Voeg basis entity-extractie toe aan Reflex (capture groups)
  4. Genereer Orchestrator-prompt uit registry
  5. Tests: verifieer dat bestaande classificatie niet breekt

Risico: Laag — Refactoring zonder gedragswijziging (behalve entity-extractie toevoeging).

10.2 Fase 2 — Unified Pipeline + Chat integratie

Doel: Eén classificatiepad, Chat API als conversatielaag.

  1. Extract classifyInput() als standalone pipeline-functie
  2. Pas Chat API aan om pipeline te gebruiken
  3. Vereenvoudig Chat systemprompt (geen intentherkenning meer)
  4. Fix AbortController doorverbinding
  5. Voeg circuit breaker toe

Risico: Medium — Chat-gedrag kan subtiel veranderen. Uitgebreid testen met bestaande scenarios.

10.3 Fase 3 — Nudge integratie + Store opsplitsing

Doel: Layer 3 daadwerkelijk actief, state management opgeschoond.

  1. Splits Zustand store in 5 domein-stores
  2. Integreer evaluateNudge() via store subscription na chain completion
  3. Voeg 3-5 klinische regels toe aan nudge systeem
  4. Implementeer nudge tracking (getoond/geaccepteerd/verworpen)

Risico: Laag — Nieuwe functionaliteit, geen breaking changes.

10.4 Fase 4 — Observability + Enterprise

Doel: Productieklare logging en monitoring.

  1. Gestructureerde logging implementeren
  2. Metrics toevoegen aan classification pipeline
  3. Audit events loggen naar database
  4. Rate limiting naar database-backed (Redis of Supabase)
  5. Input sanitisatie voor logs (geen PII)

Risico: Laag — Cross-cutting concerns, geen functionaliteitswijziging.


11. Samenvatting

Kernboodschap

Het huidige prototype heeft een architectureel solide basis (drie-laags model, fallback-strategie, Zod-validatie). De belangrijkste verbeterslag is unificatie: één Intent Registry als bron van waarheid, één classificatiepipeline die zowel directe classificatie als chat bedient, en gesplitste state management voor onderhoudbaarheid.

Prioriteiten

# Wijziging Impact Inspanning
1 Intent Registry (Single Source of Truth) Hoog Medium
2 Unified Classification Pipeline Hoog Medium
3 Reflex entity-extractie Medium Laag
4 Nudge integratie Medium Laag
5 Store opsplitsing Medium Medium
6 Observability Medium Laag
7 Circuit breaker + AbortController fix Laag Laag
8 Enterprise (audit, RBAC-voorbereiding) Laag (nu) Laag

Wat bewust NIET wordt voorgesteld

  • Event bus / pub-sub: Overkill voor single-tenant applicatie; Zustand subscriptions volstaan
  • Microservices: Cortex als apart systeem deployen voegt complexiteit toe zonder voordeel bij deze schaal
  • ML-model voor classificatie: Regex + LLM fallback is effectiever en beter debugbaar dan een custom model
  • Nudge learning via ML: Accept/dismiss tracking is voldoende; automatische regeloptimalisatie is premature complexiteit
  • Multi-tenancy: Buiten scope; de architectuur sluit het niet uit maar optimaliseert er niet voor

12. Appendix — Documentatie-index

Dit rapport is de architecturale toegangspoort tot het Cortex-systeem. Onderstaande index verwijst naar alle gerelateerde documentatie in de codebase, gegroepeerd per fase van het ontwerpproces.

12.1 Strategie & Product

Document Pad Beschrijving
PRD Cortex V2 docs/intent/prd-cortex-v2.md Product Requirements: transformatie van reactief "spraakgestuurd toetsenbord" naar AI Collega met context-awareness en multi-intent
MVP User Stories docs/intent/mvp-userstories-intent-system.md Scope-definitie voor de publieke Cortex-prototype met agency en multi-intent demonstratie
Haalbaarheidsanalyse docs/intent/haalbaarheidsanalyse-cortex-v2.md Conclusie: Cortex V2 haalbaar met 70% bestaande infrastructuur; gefaseerde aanpak
Demo Script docs/intent/demo-script-cortex-v2.md 5-minuten demonstratiescript met environment setup en feature flag configuratie

12.2 Functioneel & Technisch Ontwerp

Document Pad Beschrijving
Functioneel Ontwerp V2 docs/intent/fo-cortex-intent-system-v2.md FO gericht op contextbegrip, meerdere intenties en proactieve ondersteuning
Technisch Ontwerp V2 docs/intent/to-cortex-v2.md Vertaling naar concrete architectuur, techstack, datamodellen en implementatiedetails
Bouwplan Cortex V2 docs/intent/bouwplan-cortex-v2.md Implementatieplan met epic-structuur voor drie-laag architectuur
Review Rapport docs/intent/review-cortex-v2.md Architectuurreview met goedkeuring en aanbevelingen

12.3 Architectuur & Design

Document Pad Beschrijving
Architectuur Cortex V2 docs/intent/design/architecture-cortex-v2.md Uitgebreid architectuurplan: drie-laag model, datamodels, APIs, frontend componenten
Intent Schaalbaarheid docs/intent/design/architecture-intent-scalability.md Schaalbaarheidsstrategieën van 7 naar 35+ intents met performance-optimalisatie
Architectuurvoorstel docs/intent/intent-architecture-v2-proposal.md Consensus PO/UX/Dev voor hybrid leader/follower architectuur
Analyse Intents & Nudges docs/intent/design/analyse-intents-nudges.md Analyse van geïmplementeerde intents met aanbevelingen voor 7 nieuwe nudges en 4 query intents
GGZ Zinnen Dataset docs/intent/design/dataset-ggz-zinnen.md Dataset van GGZ-specifieke zinnen voor training en testing van classificatiesysteem
Mini-EPD Architectuur docs/swift/architectuur.md Overkoepelende architectuur met kernwaarden (70% <20ms, GDPR-compliance)
Architectuuroverzicht docs/architectuur/architectuur-overzicht.md Mini-EPD als AI-gestuurd EPD voor GGZ met spraakinterface

12.4 Intent Implementatie

Document Pad Beschrijving
Intent Overzicht docs/architectuur/intent-overzicht.md Wat intents zijn en hoe gebruikersinvoer wordt geclassificeerd naar acties
Implementatieplan Nieuwe Intents docs/architectuur/implementatieplan-nieuwe-intents.md Stappenplan: 8 aanraakpunten per intent met tijdsindicatie per type

12.5 UX Research

Document Pad Beschrijving
UX Evaluatie Schaalbaarheid docs/intent/ux-evaluation-intent-scalability.md Dialoog PO/UX/klant over problemen van strikte hiërarchie
UX Simulatie Next Level docs/intent/ux-simulation-intent-next-level.md Brainstormsessie: Command-Response → Proactive Assistant met input van zorgprofessionals
UX Analyse Patiëntselectie docs/intent/patient-search/ux-analyse-patient-selectie.md Probleemdefinitie en oplossingen voor inline patiëntselectie-ervaring
Bouwplan Patiëntselectie docs/intent/patient-search/bouwplan-patient-selectie-v1.md @mentions en persistent patient sidebar (19/19 user stories compleet)

12.6 Intake-proces

Document Pad Beschrijving
Intake Process Intents docs/intent/intake-intent-proces/intake-process-intents.md Intake-proces met bijbehorende Cortex intents en UI-componenten
Gap Analyse Intake-Cortex docs/intent/intake-intent-proces/gap-analyse-intake-cortex.md Backend 90% compleet; 26 intake intents en API routes ontbreken volledig
Block Template Pattern docs/intent/intake-intent-proces/block-template-pattern.md Herbruikbaar patroon voor Cortex blocks met loading/success/error states
Shared Components FO/TO docs/intent/intake-intent-proces/fo-to-cortex-shared-components.md Herbruikbare shared components en hooks voor alle Cortex Intake Blocks
Bouwplan Shared Components docs/intent/intake-intent-proces/bouwplan-cortex-shared-components.md Foundation components die door alle 26 intake-intents worden hergebruikt
Intake Blocks MVP FO/TO docs/intent/intake-intent-proces/fo-to-bouwplan-intake-blocks-mvp.md FO/TO/Bouwplan voor 4 meest waardevolle intake blocks
Testplan Intake Blocks docs/intent/intake-intent-proces/testplan-intake-blocks-mvp.md Testplan voor intake_status, risico_query, diagnose_query en intake_navigeer

12.7 Leeswijzer

Voor een architect die het systeem voor het eerst benadert, is de aanbevolen leesvolgorde:

1. Dit rapport (cortex-architectuur-analyse.md)
   └─ Geeft het volledige beeld: concept, architectuur, analyse, doelarchitectuur

2. PRD + Functioneel Ontwerp
   └─ prd-cortex-v2.md → fo-cortex-intent-system-v2.md
   └─ Geeft de product-context en gebruikersverwachtingen

3. Technisch Ontwerp + Architectuur Design
   └─ to-cortex-v2.md → design/architecture-cortex-v2.md
   └─ Geeft de technische verdieping per laag

4. Implementatieplan Nieuwe Intents
   └─ architectuur/implementatieplan-nieuwe-intents.md
   └─ Geeft het hands-on beeld van hoe een intent gebouwd wordt

5. Schaalbaarheid + Analyse
   └─ design/architecture-intent-scalability.md → design/analyse-intents-nudges.md
   └─ Geeft de toekomstvisie voor groei van 11 naar 50+ intents