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>
690 lines
24 KiB
Markdown
690 lines
24 KiB
Markdown
# Bouwplan — Cortex Casus: No Show Afhandeling
|
|
|
|
**Projectnaam:** Cortex — Casus 2 (No Show)
|
|
**Versie:** v1.0
|
|
**Datum:** 2026-03-29
|
|
**Auteur:** Colin Lit / Antigravity
|
|
|
|
---
|
|
|
|
## 1. Doel en Context
|
|
|
|
**Doel:** Een werkende demonstratie bouwen van de no-show afhandelings-flow: van één getypte zin ("patiënt niet verschenen") tot een AI-herschreven, geaccordeerde huisartsbrief — zonder dat de regiebehandelaar een enkel scherm verlaat.
|
|
|
|
**Context:** De V2 Cortex architectuur (three-layer: Reflex → Orchestrator → Nudge) is volledig operationeel. De chat pipeline, artifact store en nudge infrastructure zijn live. Dit bouwplan bouwt de no-show casus **bovenop** die bestaande foundation.
|
|
|
|
**Kernprincipe:**
|
|
> "Eén zin van de zorgverlener vervangt vijf administratieve handelingen."
|
|
|
|
**Beoogd resultaat:** Een demonstreerbare end-to-end flow die:
|
|
- `register_no_show` intent herkent (Reflex + Orchestrator)
|
|
- Declarabiliteitsregel proactief signaleert (Nudge stap 1)
|
|
- Na 1-klik annulering de conceptbrief opspoort (Nudge stap 2)
|
|
- De brief via LLM herschrijft en toont in Artifact (Document Block)
|
|
- Accordering en `ready_for_dispatch` status afhandelt
|
|
|
|
**Referenties:**
|
|
- PRD: `docs/intent/noshow-case/prd-cortex-casus-noshow.md`
|
|
- FO: `docs/intent/noshow-case/fo-cortex-casus-noshow.md`
|
|
- Bestaand bouwplan V2: `docs/intent/bouwplan-cortex-v2.md`
|
|
|
|
---
|
|
|
|
## 2. Dev Quick Start
|
|
|
|
### Codebase oriëntatie
|
|
|
|
```
|
|
lib/cortex/
|
|
├── types.ts # ✅ UITBREIDEN — register_no_show intent + BlockType
|
|
├── reflex-classifier.ts # ✅ UITBREIDEN — no-show patronen toevoegen
|
|
├── nudge.ts # ✅ UITBREIDEN — 2 nieuwe rules toevoegen
|
|
└── noshow-handler.ts # 🆕 NIEUW in E3 — multi-step state machine
|
|
|
|
stores/
|
|
└── cortex-store.ts # ✅ UITBREIDEN — noShowFlow state in E3
|
|
|
|
components/cortex/
|
|
├── blocks/
|
|
│ └── noshow-document-block.tsx # 🆕 NIEUW in E4 — document artifact component
|
|
└── chat/
|
|
└── nudge-chat-message.tsx # ✅ HERGEBRUIK — werkt al met accept/dismiss
|
|
|
|
app/api/cortex/
|
|
└── noshow/
|
|
├── cancel/route.ts # 🆕 NIEUW in E3 — annuleer met cancelled_no_show
|
|
├── context/route.ts # 🆕 NIEUW in E3 — concept brief ophalen
|
|
└── rescript/route.ts # 🆕 NIEUW in E3 — LLM brief herschrijven
|
|
```
|
|
|
|
### Werkwijze per story
|
|
1. Lees story + done criteria
|
|
2. Check bestaande code (zie "Bestaande code" sectie per epic)
|
|
3. Implementeer — kleinste werkende stap
|
|
4. Run `pnpm lint` en `pnpm build`
|
|
5. Commit: `feat(cortex): NS.E{n}.S{n} — <beschrijving>`
|
|
|
|
### Conventies
|
|
- TypeScript strict mode — geen `any`
|
|
- Nederlandse gebruikersteksten, Engelse code/comments
|
|
- Zod voor alle API validatie
|
|
- Mock data voor prototype — geen echte Zorgmail/declaratie koppeling
|
|
- Graceful degradation: als LLM faalt, toon originele brief + foutmelding
|
|
|
|
---
|
|
|
|
## 3. Architecturele Keuzes
|
|
|
|
### 3.1 Multi-step flow: hoe?
|
|
|
|
De no-show flow heeft 5 sequentiële stappen waarbij elke stap afhangt van de vorige keuze. De huidige nudge engine is **single-shot** (trigger → 1 nudge → klaar).
|
|
|
|
**Gekozen aanpak: Lightweight state machine in de store**
|
|
|
|
```
|
|
noShowFlowState: {
|
|
step: 'idle' | 'waiting_cancel' | 'waiting_brief' | 'brief_open' | 'done'
|
|
appointmentId: string | null
|
|
documentId: string | null
|
|
originalBriefContent: string | null
|
|
}
|
|
```
|
|
|
|
Bij elke nudge-accept checkt de `chat-panel` welke `step` actief is en dispatcht de juiste vervolgactie. Dit hergebruikt de bestaande nudge-accept flow zonder een compleet nieuwe state machine framework nodig te hebben.
|
|
|
|
**Niet gekozen:** Een aparte state machine library (XState) — te zwaar voor prototype scope.
|
|
|
|
### 3.2 Document Block: Rich Text Editor?
|
|
|
|
De FO noemt een `rich-text-editor.tsx`, maar die bestaat niet in de codebase. Voor prototype scope is een gewone `<Textarea>` voldoende. De "kwaliteit" van de brief zit in de LLM output, niet in de editor.
|
|
|
|
**Besluit:** `<Textarea>` hergebruiken (zoals in `dagnotitie-block.tsx`), focus op Akkoord-knop en status-update.
|
|
|
|
### 3.3 Mock data strategie
|
|
|
|
Twee mock objecten toevoegen in `lib/cortex/mock-data/noshow.ts`:
|
|
- Een declarabele afspraak voor de actieve patiënt
|
|
- Een concept huisartsbrief gekoppeld aan die patiënt
|
|
|
|
De API routes checken eerst op mock data voordat ze Supabase bevragen — zodat de demo altijd werkt.
|
|
|
|
---
|
|
|
|
## 4. Epics & Stories Overzicht
|
|
|
|
| Epic ID | Titel | Doel | Stories | Geschatte tijd |
|
|
|---------|-------|------|---------|----------------|
|
|
| **NS.E1** | Intent Foundation | Type + classifier | 3 | ~1u |
|
|
| **NS.E2** | Nudge Rules | 2 rules + chained flow | 3 | ~2u |
|
|
| **NS.E3** | No-Show API | Cancel + brief-context + LLM rescript | 4 | ~3u |
|
|
| **NS.E4** | Document Artifact | Block component + Akkoord-knop | 3 | ~2u |
|
|
| **NS.E5** | Integration & Demo | End-to-end flow + polish | 3 | ~2u |
|
|
|
|
**Totaal:** 16 stories, ~10u bouwtijd
|
|
|
|
---
|
|
|
|
## 5. Epics & Stories (Gedetailleerd)
|
|
|
|
---
|
|
|
|
### NS.E1 — Intent Foundation
|
|
|
|
**Doel:** `register_no_show` herkenbaar maken voor het volledige classificatie systeem.
|
|
|
|
**Bestaande code om te lezen voor je begint:**
|
|
- `lib/cortex/types.ts` — `CortexIntent` union type + `BLOCK_CONFIGS`
|
|
- `lib/cortex/reflex-classifier.ts` — patroon hoe andere intents worden gematcht
|
|
|
|
---
|
|
|
|
#### NS.E1.S1 — CortexIntent type uitbreiden
|
|
|
|
**Taak:** `register_no_show` toevoegen aan de type definities.
|
|
|
|
**Aanpassingen:**
|
|
- `lib/cortex/types.ts`:
|
|
- Voeg `'register_no_show'` toe aan `CortexIntent` union
|
|
- Voeg `'register_no_show'` toe aan `BLOCK_CONFIGS` met `{ type: 'register_no_show', title: 'No Show Registratie', size: 'md', icon: 'UserX' }`
|
|
- `BlockType` wordt automatisch meegenomen (afgeleid van `CortexIntent`)
|
|
|
|
**Done criteria:**
|
|
- `pnpm build` slaagt zonder type errors
|
|
- `register_no_show` is een geldige `CortexIntent` en `BlockType`
|
|
|
|
---
|
|
|
|
#### NS.E1.S2 — Reflex Classifier patronen
|
|
|
|
**Taak:** Snelle (< 20ms) lokale patroonherkenning voor no-show input.
|
|
|
|
**Aanpassingen:**
|
|
- `lib/cortex/reflex-classifier.ts` — voeg patronen toe aan de intent-map:
|
|
```
|
|
'register_no_show': [
|
|
/no.?show/i,
|
|
/niet verschenen/i,
|
|
/niet gekomen/i,
|
|
/afwezig.*afspraak/i,
|
|
/no show/i,
|
|
/komt niet op/i,
|
|
]
|
|
```
|
|
- Confidence: `0.85` bij directe match (hoog — weinig ambiguïteit verwacht)
|
|
|
|
**Done criteria:**
|
|
- `classifyWithReflex("patiënt is niet verschenen")` geeft `{ intent: 'register_no_show', confidence: 0.85 }`
|
|
- `classifyWithReflex("no show vandaag")` geeft hetzelfde resultaat
|
|
- Input zonder patroon escaleert correct naar AI
|
|
|
|
---
|
|
|
|
#### NS.E1.S3 — Orchestrator system prompt uitbreiden
|
|
|
|
**Taak:** Orchestrator (Layer 2) informeren over de nieuwe intent.
|
|
|
|
**Aanpassingen:**
|
|
- `lib/cortex/orchestrator.ts` of het `buildSystemPrompt` in `app/api/cortex/chat/route.ts`:
|
|
- Voeg `register_no_show` toe aan de intent-beschrijvingen:
|
|
```
|
|
register_no_show: Gebruik wanneer de zorgverlener aangeeft dat een patiënt
|
|
niet op de afspraak is verschenen. Signaalwoorden: "no show", "niet verschenen",
|
|
"niet gekomen", "afwezig bij afspraak".
|
|
```
|
|
|
|
**Done criteria:**
|
|
- System prompt bevat `register_no_show` met correcte beschrijving
|
|
- AI classificeert "cliënt was er niet" als `register_no_show` (handmatig te testen via chat)
|
|
|
|
---
|
|
|
|
### NS.E2 — Nudge Rules & Chained Flow
|
|
|
|
**Doel:** Twee proactieve nudges aanmaken die sequentieel volgen op de no-show registratie.
|
|
|
|
**Bestaande code om te lezen voor je begint:**
|
|
- `lib/cortex/nudge.ts` — `ProtocolRule` interface + `PROTOCOL_RULES` array + `evaluateNudge()`
|
|
- `components/cortex/chat/nudge-chat-message.tsx` — hoe accept/dismiss werkt
|
|
- `components/cortex/chat/chat-panel.tsx` — hoe `evaluateNudge` wordt aangeroepen na action completion
|
|
- `stores/cortex-store.ts` — `noShowFlowState` is hier **nog niet** — toevoegen in S1
|
|
|
|
---
|
|
|
|
#### NS.E2.S1 — No-show flow state aan store toevoegen
|
|
|
|
**Taak:** Minimale state machine voor de 5-staps flow in de Zustand store.
|
|
|
|
**Aanpassingen:**
|
|
- `stores/cortex-store.ts`:
|
|
```typescript
|
|
// Voeg toe aan state interface:
|
|
noShowFlow: {
|
|
step: 'idle' | 'waiting_cancel' | 'waiting_brief' | 'brief_open' | 'done';
|
|
appointmentId: string | null;
|
|
documentId: string | null;
|
|
originalContent: string | null;
|
|
};
|
|
|
|
// Actions:
|
|
setNoShowStep: (step: NoShowStep) => void;
|
|
setNoShowContext: (ctx: Partial<NoShowFlowContext>) => void;
|
|
resetNoShowFlow: () => void;
|
|
```
|
|
- Initiële waarde: `{ step: 'idle', appointmentId: null, documentId: null, originalContent: null }`
|
|
|
|
**Done criteria:**
|
|
- Store compileert zonder errors
|
|
- `useCorteStore(s => s.noShowFlow)` geeft initiële state terug
|
|
|
|
---
|
|
|
|
#### NS.E2.S2 — Nudge Rule 1: Declarabiliteitscheck
|
|
|
|
**Taak:** Na `register_no_show` intent → nudge die waarschuwt over declarabele afspraak.
|
|
|
|
**Aanpassingen:**
|
|
- `lib/cortex/nudge.ts` — voeg toe aan `PROTOCOL_RULES`:
|
|
```typescript
|
|
{
|
|
id: 'noshow-declarabel-check',
|
|
trigger: {
|
|
intent: 'register_no_show',
|
|
},
|
|
priority: 'high',
|
|
suggestion: {
|
|
intent: 'cancel_appointment',
|
|
message: 'Ik zie een declarabel consult in de agenda. Volgens inkoopvoorwaarden mag deze afspraak NIET gedeclareerd worden. Wil je dat ik deze annuleer als \'No Show\'?',
|
|
prefillEntities: {},
|
|
},
|
|
expiresAfterMs: 5 * 60 * 1000,
|
|
}
|
|
```
|
|
- **Let op:** `priority: 'high'` — deze nudge verschijnt altijd als eerste
|
|
|
|
**Done criteria:**
|
|
- `evaluateNudge({ intent: 'register_no_show', ... })` geeft nudge met id `noshow-declarabel-check` terug
|
|
- Nudge toont correct in chat UI met `[Ja, annuleer]` accept-knop
|
|
|
|
---
|
|
|
|
#### NS.E2.S3 — Nudge Rule 2: Concept Brief Alert + Chained Trigger
|
|
|
|
**Taak:** Na acceptatie van Nudge 1 → check op concept brief → Nudge 2 tonen.
|
|
|
|
**Aanpassingen:**
|
|
|
|
**Stap A — Nudge Rule 2 definiëren** in `lib/cortex/nudge.ts`:
|
|
```typescript
|
|
{
|
|
id: 'noshow-brief-check',
|
|
trigger: {
|
|
intent: 'cancel_appointment',
|
|
conditions: [{ field: 'noShowContext', operator: 'exists', value: true }],
|
|
},
|
|
priority: 'high',
|
|
suggestion: {
|
|
intent: 'register_no_show', // hergebruik als signaal voor brief-stap
|
|
message: 'Afspraak geannuleerd. Er staat nog een concept huisartsbrief klaar. Zal ik daar de No Show in verwerken?',
|
|
prefillEntities: {},
|
|
},
|
|
expiresAfterMs: 5 * 60 * 1000,
|
|
}
|
|
```
|
|
|
|
**Stap B — Chained accept logica** in `components/cortex/chat/chat-panel.tsx`:
|
|
- Bij `acceptSuggestion` van `noshow-declarabel-check`:
|
|
1. `setNoShowStep('waiting_cancel')`
|
|
2. Roep `/api/cortex/noshow/cancel` aan
|
|
3. Roep `/api/cortex/noshow/context` aan (check op conceptbrief)
|
|
4. Als brief gevonden: `setNoShowContext({ documentId, originalContent })` + `evaluateNudge` voor rule 2
|
|
5. `setNoShowStep('waiting_brief')`
|
|
- Bij `acceptSuggestion` van `noshow-brief-check`:
|
|
1. Roep `/api/cortex/noshow/rescript` aan
|
|
2. `openArtifact({ type: 'register_no_show', ... })` met herschreven inhoud
|
|
3. `setNoShowStep('brief_open')`
|
|
|
|
**Done criteria:**
|
|
- Klikken op `[Ja, annuleer]` triggert de cancel API call + toont Nudge 2
|
|
- Klikken op `[Ja, pas brief aan]` triggert rescript API + opent artifact
|
|
- `noShowFlow.step` werkt correct door alle stappen
|
|
|
|
---
|
|
|
|
### NS.E3 — No-Show API Routes
|
|
|
|
**Doel:** Drie backend routes bouwen die de no-show flow ondersteunen.
|
|
|
|
**Bestaande code om te lezen voor je begint:**
|
|
- `app/api/cortex/agenda/cancel/route.ts` — bestaand cancel patroon
|
|
- `app/api/cortex/chat/route.ts` — Anthropic streaming patroon
|
|
- `lib/auth/server.ts` — `createClient()` voor authenticated requests
|
|
|
|
---
|
|
|
|
#### NS.E3.S1 — Mock data aanmaken
|
|
|
|
**Taak:** Statische mock data voor declarabele afspraak en concept huisartsbrief.
|
|
|
|
**Nieuw bestand:** `lib/cortex/mock-data/noshow.ts`
|
|
```typescript
|
|
export const MOCK_NO_SHOW_APPOINTMENT = {
|
|
id: 'mock-appt-noshow-001',
|
|
patientId: 'demo-patient-001', // matcht actieve demo patiënt
|
|
date: new Date().toISOString(),
|
|
type: 'intake_consult',
|
|
is_billable: true,
|
|
status: 'scheduled',
|
|
title: 'Intake Consult',
|
|
};
|
|
|
|
export const MOCK_CONCEPT_BRIEF = {
|
|
id: 'mock-brief-001',
|
|
patientId: 'demo-patient-001',
|
|
type: 'huisartsbrief',
|
|
status: 'concept',
|
|
title: 'Huisartsbrief n.a.v. intake',
|
|
content: `Geachte collega,
|
|
|
|
Hierbij informeer ik u over de intake van uw patiënt die bij ons in zorg is gekomen.
|
|
Tijdens de intake is uitgebreid de hulpvraag besproken en zijn de volgende aandachtspunten
|
|
naar voren gekomen: [...]
|
|
|
|
Het vervolgtraject bestaat uit wekelijkse gesprekken gericht op [...]
|
|
|
|
Met vriendelijke groet,
|
|
[Behandelaar]`,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
```
|
|
|
|
**Done criteria:**
|
|
- Bestand bestaat en exporteert beide mock objecten
|
|
- TypeScript compileert zonder errors
|
|
|
|
---
|
|
|
|
#### NS.E3.S2 — Cancel Route (`/api/cortex/noshow/cancel`)
|
|
|
|
**Taak:** Afspraak annuleren met `cancelled_no_show` status.
|
|
|
|
**Nieuw bestand:** `app/api/cortex/noshow/cancel/route.ts`
|
|
|
|
**Request body (Zod):**
|
|
```typescript
|
|
const CancelNoShowSchema = z.object({
|
|
appointmentId: z.string(),
|
|
patientId: z.string(),
|
|
});
|
|
```
|
|
|
|
**Response:**
|
|
```typescript
|
|
{ success: true, appointmentId: string, newStatus: 'cancelled_no_show' }
|
|
```
|
|
|
|
**Logica:**
|
|
1. Auth check via `createClient()`
|
|
2. Zod validatie
|
|
3. Check op mock data (`MOCK_NO_SHOW_APPOINTMENT.id`) — als match, return mock success
|
|
4. Anders: Supabase update op `encounters` tabel, `status = 'cancelled_no_show'`
|
|
5. Return success response
|
|
|
|
**Done criteria:**
|
|
- `POST /api/cortex/noshow/cancel` met mock appointment ID geeft `{ success: true }` terug
|
|
- Route geeft correcte 400/401 bij ongeldige input/auth
|
|
|
|
---
|
|
|
|
#### NS.E3.S3 — Context Route (`/api/cortex/noshow/context`)
|
|
|
|
**Taak:** Openstaande concept documenten ophalen voor actieve patiënt.
|
|
|
|
**Nieuw bestand:** `app/api/cortex/noshow/context/route.ts`
|
|
|
|
**Query params:** `?patientId=<id>`
|
|
|
|
**Response:**
|
|
```typescript
|
|
{
|
|
hasConceptBrief: boolean;
|
|
document?: {
|
|
id: string;
|
|
title: string;
|
|
content: string;
|
|
type: string;
|
|
};
|
|
}
|
|
```
|
|
|
|
**Logica:**
|
|
1. Auth check
|
|
2. `patientId` param ophalen
|
|
3. Check op mock data — als `patientId` matcht `demo-patient-001`, return mock brief
|
|
4. Anders: Supabase query op `reports` tabel, `type = 'huisartsbrief'` + `status = 'concept'` + `patient_id = patientId` + `deleted_at IS NULL`
|
|
|
|
**Done criteria:**
|
|
- `GET /api/cortex/noshow/context?patientId=demo-patient-001` geeft mock brief terug
|
|
- `hasConceptBrief: false` bij onbekende patiënt
|
|
|
|
---
|
|
|
|
#### NS.E3.S4 — Rescript Route (`/api/cortex/noshow/rescript`)
|
|
|
|
**Taak:** LLM herschrijft de huisartsbrief met de no-show professioneel verwerkt.
|
|
|
|
**Nieuw bestand:** `app/api/cortex/noshow/rescript/route.ts`
|
|
|
|
**Request body (Zod):**
|
|
```typescript
|
|
const RescriptSchema = z.object({
|
|
documentId: z.string(),
|
|
originalContent: z.string(),
|
|
patientId: z.string(),
|
|
});
|
|
```
|
|
|
|
**Response:**
|
|
```typescript
|
|
{ rescriptedContent: string; documentId: string; }
|
|
```
|
|
|
|
**System prompt voor LLM:**
|
|
```
|
|
Je bent een klinisch schrijver in een GGZ instelling. Je taak is een bestaande conceptbrief
|
|
aanpassen zodat de no-show van de patiënt professioneel en contextbewust verwerkt is.
|
|
|
|
Regels:
|
|
- Integreer de no-show in de lopende tekst, voeg het NIET achteraan toe
|
|
- Gebruik formele GGZ-taal ("de patiënt is niet verschenen op het geplande consult")
|
|
- Bewaar alle andere informatie in de brief
|
|
- Voeg een zin toe over herschikken van het vervolggesprek
|
|
- Reageer ALLEEN met de herschreven brieftekst, geen uitleg
|
|
```
|
|
|
|
**Done criteria:**
|
|
- Route roept Anthropic API aan met correct system prompt
|
|
- Herschreven brief is contextbewust (no-show verwerkt, niet achteraan geplakt)
|
|
- Route faalt graceful (geeft originele content terug + foutmelding bij API error)
|
|
|
|
---
|
|
|
|
### NS.E4 — Document Artifact Block
|
|
|
|
**Doel:** Een nieuw block component dat de herschreven brief weergeeft met een Akkoord-knop.
|
|
|
|
**Bestaande code om te lezen voor je begint:**
|
|
- `components/cortex/blocks/dagnotitie-block.tsx` — patroon voor block met textarea
|
|
- `components/cortex/shared/block-footer.tsx` — `primaryAction` / `secondaryAction` props
|
|
- `components/cortex/artifacts/artifact-container.tsx` — `renderArtifactBlock()` switch
|
|
|
|
---
|
|
|
|
#### NS.E4.S1 — NoShowDocumentBlock component
|
|
|
|
**Taak:** Block component voor het weergeven en bewerken van de herschreven brief.
|
|
|
|
**Nieuw bestand:** `components/cortex/blocks/noshow-document-block.tsx`
|
|
|
|
**Props interface:**
|
|
```typescript
|
|
interface NoShowDocumentBlockProps {
|
|
prefill: {
|
|
documentId: string;
|
|
content: string;
|
|
title: string;
|
|
originalContent?: string;
|
|
};
|
|
}
|
|
```
|
|
|
|
**UI Structuur:**
|
|
```
|
|
┌─────────────────────────────────────────────┐
|
|
│ 📄 Huisartsbrief — aangepast door Cortex │
|
|
│ ─────────────────────────────────────────── │
|
|
│ [Textarea met herschreven inhoud] │
|
|
│ (editeerbaar, minimaal 12 regels hoog) │
|
|
│ │
|
|
│ ─────────────────────────────────────────── │
|
|
│ [Origineel bekijken ↕] [Akkoord & Verzendklaar →] │
|
|
└─────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Logica:**
|
|
- State: `content` (editeerbaar), `isSubmitting`, `isDone`
|
|
- "Origineel bekijken" toggle: toont `originalContent` in grijs eronder (collapsible)
|
|
- "Akkoord & Verzendklaar" knop:
|
|
1. `setIsSubmitting(true)`
|
|
2. `PATCH /api/cortex/noshow/dispatch` (S2) met `{ documentId, finalContent: content }`
|
|
3. Bij success: `setIsDone(true)`, `setNoShowStep('done')`, toast "Brief verzendklaar"
|
|
4. Bij fout: toast foutmelding, `setIsSubmitting(false)`
|
|
|
|
**Done criteria:**
|
|
- Component rendert herschreven inhoud in editeerbare textarea
|
|
- "Akkoord" knop triggert dispatch API call
|
|
- Na success toont component bevestiging (geen navigatie nodig)
|
|
|
|
---
|
|
|
|
#### NS.E4.S2 — Dispatch Route (`/api/cortex/noshow/dispatch`)
|
|
|
|
**Taak:** Document status naar `ready_for_dispatch` zetten na accordering.
|
|
|
|
**Nieuw bestand:** `app/api/cortex/noshow/dispatch/route.ts`
|
|
|
|
**Request body (Zod):**
|
|
```typescript
|
|
const DispatchSchema = z.object({
|
|
documentId: z.string(),
|
|
finalContent: z.string(),
|
|
});
|
|
```
|
|
|
|
**Logica:**
|
|
1. Auth check
|
|
2. Als `documentId` matcht mock ID → return success (mock)
|
|
3. Anders: Supabase update op `reports`, `status = 'ready_for_dispatch'`, `content = finalContent`
|
|
|
|
**Done criteria:**
|
|
- `PATCH` met mock document ID geeft `{ success: true, status: 'ready_for_dispatch' }` terug
|
|
|
|
---
|
|
|
|
#### NS.E4.S3 — ArtifactContainer register_no_show toevoegen
|
|
|
|
**Taak:** `renderArtifactBlock()` switch uitbreiden met het nieuwe block.
|
|
|
|
**Aanpassingen:**
|
|
- `components/cortex/artifacts/artifact-container.tsx`:
|
|
```typescript
|
|
case 'register_no_show':
|
|
return (
|
|
<NoShowDocumentBlock
|
|
prefill={artifact.prefill as NoShowDocumentPrefill}
|
|
/>
|
|
);
|
|
```
|
|
- Import toevoegen voor `NoShowDocumentBlock`
|
|
|
|
**Done criteria:**
|
|
- `openArtifact({ type: 'register_no_show', prefill: { documentId, content, title } })` opent het juiste block
|
|
- Geen TypeScript errors in artifact-container
|
|
|
|
---
|
|
|
|
### NS.E5 — Integration & Demo Polish
|
|
|
|
**Doel:** De volledige flow end-to-end werkend maken en demo-klaar polijsten.
|
|
|
|
**Bestaande code om te lezen voor je begint:**
|
|
- `components/cortex/chat/chat-panel.tsx` — de centrale orchestrator voor de hele flow
|
|
- `docs/intent/noshow-case/fo-cortex-casus-noshow.md` — de exacte stap-voor-stap flow
|
|
|
|
---
|
|
|
|
#### NS.E5.S1 — Chat Panel integratie
|
|
|
|
**Taak:** `chat-panel.tsx` uitbreiden met de no-show flow orchestratie.
|
|
|
|
**Aanpassingen in `chat-panel.tsx`:**
|
|
|
|
```typescript
|
|
// Na handleConfirmAction / na evaluateNudge call:
|
|
const handleNudgeAccept = async (suggestion: NudgeSuggestion) => {
|
|
const { noShowFlow, setNoShowStep, setNoShowContext } = useCorteStore.getState();
|
|
|
|
if (suggestion.id === 'noshow-declarabel-check') {
|
|
setNoShowStep('waiting_cancel');
|
|
// Cancel afspraak
|
|
await fetch('/api/cortex/noshow/cancel', { method: 'POST', body: ... });
|
|
// Check concept brief
|
|
const ctx = await fetch(`/api/cortex/noshow/context?patientId=${activePatient?.id}`).then(r => r.json());
|
|
if (ctx.hasConceptBrief) {
|
|
setNoShowContext({ documentId: ctx.document.id, originalContent: ctx.document.content });
|
|
// Trigger nudge 2 als chat message
|
|
addChatMessage({ type: 'nudge', content: BRIEF_NUDGE_MESSAGE, nudge: NOSHOW_BRIEF_NUDGE });
|
|
setNoShowStep('waiting_brief');
|
|
} else {
|
|
setNoShowStep('done');
|
|
addChatMessage({ type: 'assistant', content: 'Afspraak geannuleerd als No Show. Er zijn geen openstaande conceptbrieven gevonden.' });
|
|
}
|
|
}
|
|
|
|
if (suggestion.id === 'noshow-brief-check') {
|
|
setNoShowStep('brief_open');
|
|
const { documentId, originalContent } = noShowFlow;
|
|
const result = await fetch('/api/cortex/noshow/rescript', { method: 'POST', body: ... }).then(r => r.json());
|
|
openArtifact({
|
|
type: 'register_no_show',
|
|
title: 'Huisartsbrief — aangepast',
|
|
prefill: { documentId, content: result.rescriptedContent, title: 'Huisartsbrief', originalContent },
|
|
});
|
|
addChatMessage({ type: 'assistant', content: 'Huisartsbrief is aangepast. Kijk of je hiermee akkoord bent.' });
|
|
}
|
|
};
|
|
```
|
|
|
|
**Done criteria:**
|
|
- Volledige 5-staps flow is doorloopbaar in de browser
|
|
- Elke stap produceert de juiste chat message en UI state
|
|
|
|
---
|
|
|
|
#### NS.E5.S2 — Loading states en foutafhandeling
|
|
|
|
**Taak:** Zorgdragen dat de flow nooit "vastloopt" voor de gebruiker.
|
|
|
|
**Aanpassingen:**
|
|
- Voeg `isNoShowProcessing: boolean` toe aan store
|
|
- `chat-panel.tsx`: toon `<ProcessingIndicator>` tijdens API calls tussen stappen
|
|
- Bij API fout: voeg error chat message toe met Nederlandse foutmelding
|
|
- `resetNoShowFlow()` aanroepen bij component unmount (cleanup)
|
|
|
|
**Done criteria:**
|
|
- Loading indicator zichtbaar tijdens elke API call
|
|
- Bij netwerk fout verschijnt: "Er is iets misgegaan. Probeer het opnieuw."
|
|
- Flow kan opnieuw gestart worden na een fout
|
|
|
|
---
|
|
|
|
#### NS.E5.S3 — Demo script validatie
|
|
|
|
**Taak:** De flow handmatig doorlopen en valideren aan de succescriteria uit het PRD.
|
|
|
|
**Checklist:**
|
|
- [ ] Input "patiënt niet verschenen" → `register_no_show` herkend (Reflex, < 20ms)
|
|
- [ ] Input "cliënt was er niet vandaag" → Orchestrator herkent correct
|
|
- [ ] Nudge 1 verschijnt met juiste declarabiliteits-waarschuwing
|
|
- [ ] Klik `[Ja, annuleer]` → Cancel API succesvol → Nudge 2 verschijnt
|
|
- [ ] Klik `[Ja, pas brief aan]` → Rescript API → Artifact opent met aangepaste brief
|
|
- [ ] Textarea is editeerbaar — aanpassing blijft behouden
|
|
- [ ] Klik `[Akkoord & Verzendklaar]` → Dispatch API → bevestiging in chat
|
|
- [ ] Volledige flow duurt < 30 seconden (excl. LLM latency)
|
|
- [ ] Geen schermwissels nodig gedurende de hele flow
|
|
- [ ] `pnpm build` slaagt zonder errors
|
|
|
|
**Done criteria:** Alle checkboxen afgevinkt = casus NS volledig gereed.
|
|
|
|
---
|
|
|
|
## 6. Risico's en Mitigatie
|
|
|
|
| Risico | Kans | Impact | Mitigatie |
|
|
|--------|------|--------|-----------|
|
|
| LLM rescript kwaliteit onvoldoende | Middel | Hoog | System prompt itereren; originele brief altijd tonen als fallback |
|
|
| Multi-step flow breekt bij snelle klikken | Laag | Middel | `isNoShowProcessing` boolean blokkeert dubbele submits |
|
|
| Mock data matcht niet met actieve demo patiënt | Middel | Hoog | `patientId` hardcoderen in mock als `demo-patient-001` — zelfde als in bestaande demo setup |
|
|
| `ArtifactContainer` switch mist nieuwe BlockType | Laag | Hoog | TypeScript exhaustive check dwingt dit af bij `pnpm build` |
|
|
| Nudge 2 triggert niet als brief niet bestaat | Laag | Laag | Flow eindigt graceful met chat message "geen brief gevonden" |
|
|
|
|
---
|
|
|
|
## 7. Niet in Scope (Prototype)
|
|
|
|
- Echte declaratie-integratie (ZPM/HiX koppeling)
|
|
- Zorgmail verzending van de brief
|
|
- Automatisch inplannen van vervangende afspraak
|
|
- Meerdere conceptbrieven per patiënt (multi-document select)
|
|
- Audit log van de no-show registratie
|
|
- Undo/rollback van de annulering
|