# 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
