feat(cortex): no-show casus — intent flow, brief-rescript en document artifact

Cortex handelt een no-show af vanuit één chatcommando: afspraak
annuleren (declarabiliteits-nudge), en de openstaande concept-
huisartsbrief wordt via LLM herschreven en ter review aangeboden
in een document artifact (human-in-the-loop, PATCH dispatch zet
status op verzendklaar).

- API-routes: context, rescript, cancel, dispatch
- NoShowDocumentBlock: review/edit UI met origineel-vergelijk
- Mock-data voor concept huisartsbrief
- PRD, FO, bouwplan en epics in docs/intent/noshow-case/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
colinislit
2026-07-09 23:14:55 +02:00
parent d08db12765
commit 924988dd15
14 changed files with 3022 additions and 0 deletions

View File

@@ -0,0 +1,299 @@
# NS.E5 — Integration & Demo Polish
**Casus:** Cortex No Show Afhandeling
**Epic doel:** De volledige end-to-end flow werkend krijgen, edge cases afvangen, en de demo valideren aan de succescriteria uit het PRD.
**Geschatte tijd:** ~2 uur
**Afhankelijkheden:** NS.E1 t/m NS.E4 volledig afgerond
---
## Context
Na NS.E1NS.E4 zijn alle individuele onderdelen gebouwd:
- Intent wordt herkend (E1)
- Nudges triggeren en lopen sequentieel (E2)
- API routes werken (E3)
- Artifact component toont de brief (E4)
Dit epic zorgt dat alles correct samenwerkt, fouten netjes worden afgevangen, en de demo vloeiend loopt.
---
## NS.E5.S1 — Volledige `chat-panel.tsx` integratie valideren
**Bestand:** `components/cortex/chat/chat-panel.tsx`
### Checklist: zijn alle functies aanwezig en correct verbonden?
Na NS.E2 en NS.E4 moeten de volgende functies bestaan in `ChatPanel`:
| Functie | Aangemaakt in | Doet |
|---|---|---|
| `handleNoShowCancelStep` | NS.E2.S3 | Cancel API → context check → nudge 2 of done |
| `handleNoShowRescriptStep` | NS.E4.S3 | Rescript API → openArtifact |
| `handleAcceptNudge` (uitgebreid) | NS.E2.S3 | Brancht op `trigger.intent` en `suggestion.rationale` |
### Verbinding `handleAcceptNudge` → `handleNoShowRescriptStep`
Controleer dat de stap-5 check correct is geïmplementeerd:
```typescript
const handleAcceptNudge = useCallback(async (
suggestionId: string,
suggestion: ChatMessageType['nudge']
) => {
acceptSuggestion(suggestionId);
if (!suggestion) return;
// Stap 2→3: declarabiliteitscheck geaccepteerd → annuleer + check brief
if (suggestion.trigger.intent === 'register_no_show') {
await handleNoShowCancelStep(suggestion);
return;
}
// Stap 4→5: brief-check geaccepteerd → herschrijf brief
if (suggestion.suggestion.rationale === 'noshow-brief-check') {
await handleNoShowRescriptStep();
return;
}
// Generieke flow voor alle andere nudges
const artifact = routeIntentToArtifact(
suggestion.suggestion.intent,
suggestion.suggestion.entities,
0.9
);
if (artifact) {
openArtifact({ type: artifact.type, prefill: artifact.prefill, title: artifact.title });
}
}, [
acceptSuggestion,
openArtifact,
handleNoShowCancelStep,
handleNoShowRescriptStep,
]);
```
**Belangrijk:** `handleNoShowCancelStep` en `handleNoShowRescriptStep` moeten in de `useCallback` dependency array staan. Als ze er niet in staan, kan je stale closures krijgen.
### `onSend` handler: no-show nudge trigger
Controleer dat de `onDone` callback de no-show nudge triggert. Dit blok moet aanwezig zijn in de `onDone`:
```typescript
// No-show nudge trigger na register_no_show classificatie
if (parsed.action?.intent === 'register_no_show' && isFeatureEnabled('CORTEX_NUDGE')) {
const suggestions = evaluateNudge({
intent: 'register_no_show',
actionId: crypto.randomUUID(),
entities: parsed.action.entities,
content: message,
});
suggestions.forEach((suggestion) => {
addChatMessage({
type: 'nudge',
content: suggestion.suggestion.message,
nudge: suggestion,
});
});
}
```
### Done criteria
- Volledige flow doorloopbaar zonder errors in de browser console
- `[ChatPanel]` log berichten verschijnen op de juiste momenten
- Geen `TypeError` of `undefined is not a function` errors
---
## NS.E5.S2 — Loading states en dubbele submits voorkomen
**Bestand:** `components/cortex/chat/chat-panel.tsx` + `stores/cortex-store.ts`
### Probleem
Tussen de API calls (cancel → context → rescript) zit latentie. Als de gebruiker snel klikt, kunnen er dubbele calls ontstaan. Ook moet de UI duidelijk maken dat er iets "bezig" is.
### Oplossing A — `isNoShowProcessing` flag in store
Voeg toe aan store (in `CortexStore` interface, `initialState`, en actions):
```typescript
// In interface:
isNoShowProcessing: boolean;
// In initialState:
isNoShowProcessing: false,
// Actions:
setNoShowProcessing: (processing: boolean) => void;
```
Implementatie:
```typescript
setNoShowProcessing: (processing) =>
set({ isNoShowProcessing: processing }, false, 'setNoShowProcessing'),
```
### Oplossing B — Gebruik in `handleNoShowCancelStep` en `handleNoShowRescriptStep`
Wrap elke async functie:
```typescript
const handleNoShowCancelStep = useCallback(async (...) => {
const { isNoShowProcessing } = useCortexStore.getState();
if (isNoShowProcessing) return; // Dubbele klik preventie
setNoShowProcessing(true);
try {
// ... bestaande logica
} finally {
setNoShowProcessing(false);
}
}, [..., setNoShowProcessing]);
```
### Oplossing C — Nudge knop disablen tijdens processing
In `NudgeChatMessage` is de accept/dismiss knop al aanwezig. We moeten de accept knop disablen tijdens `isNoShowProcessing`:
**Bestand:** `components/cortex/chat/nudge-chat-message.tsx`
Voeg toe:
```typescript
const isNoShowProcessing = useCortexStore((s) => s.isNoShowProcessing);
```
Voeg `disabled={isNoShowProcessing}` toe aan de accept-knop als de nudge een no-show nudge is (`suggestion.trigger.intent === 'register_no_show'` of `suggestion.suggestion.rationale === 'noshow-brief-check'`).
### Done criteria
- Snel twee keer klikken op `[Ja, annuleer]` triggert de API maar één keer
- Knop is visueel disabled tijdens verwerking
- Na voltooiing is de knop weer actief (relevant voor de "Nee" knop)
---
## NS.E5.S3 — Cleanup bij unmount
**Bestand:** `components/cortex/chat/chat-panel.tsx`
Als de gebruiker weg navigeert tijdens een lopende no-show flow, moet de state worden opgeruimd.
Voeg toe in `ChatPanel`:
```typescript
const resetNoShowFlow = useCortexStore((s) => s.resetNoShowFlow);
// Cleanup bij unmount
useEffect(() => {
return () => {
// Reset no-show flow als component unmount tijdens actieve flow
const { noShowFlow } = useCortexStore.getState();
if (noShowFlow.step !== 'idle' && noShowFlow.step !== 'done') {
resetNoShowFlow();
}
};
}, [resetNoShowFlow]);
```
### Done criteria
- Navigeren weg van het dashboard tijdens een actieve no-show flow reset de state
- Bij terugkeren: flow begint opnieuw (geen "stuck" state)
---
## NS.E5.S4 — Demo Validatie Checklist
Doorloop de volledige flow handmatig en vink elk punt af:
### Classificatie
- [ ] Input `"patiënt is niet verschenen"``register_no_show` intent herkend (zichtbaar in console)
- [ ] Input `"no show vandaag"``register_no_show` intent herkend
- [ ] Input `"cliënt was er niet"` → AI classificeert als `register_no_show`
- [ ] Input `"patiënt niet verschenen volgende week"` → escaleert naar AI (relative_time)
### Nudge 1 — Declarabiliteitscheck
- [ ] Na no-show input: nudge bubble verschijnt met declarabiliteits-waarschuwing
- [ ] Nudge heeft prioriteit badge `high` (rode kleur)
- [ ] `[Nee]` klik: nudge verdwijnt, geen verdere actie, flow eindigt
- [ ] `[Ja, annuleer]` klik: loading state actief (knop disabled)
### Stap 3 — Annulering
- [ ] Cancel API aangeroepen: `POST /api/cortex/noshow/cancel` → 200 response
- [ ] Context API aangeroepen: `GET /api/cortex/noshow/context``hasConceptBrief: true`
- [ ] Chat toont "Bezig met annuleren..." tijdens verwerking
- [ ] Na voltooiing: nudge 2 verschijnt met brief-vraag
### Nudge 2 — Brief check
- [ ] Nudge 2 toont correcte message over conceptbrief
- [ ] `[Nee]` klik: nudge verdwijnt, chat bevestigt annulering
- [ ] `[Ja, pas brief aan]` klik: rescript API aangeroepen
### Stap 4 — Rescript
- [ ] Rescript API aangeroepen: `POST /api/cortex/noshow/rescript` → herschreven inhoud
- [ ] Artifact paneel opent met `NoShowDocumentBlock`
- [ ] Chat toont "Huisartsbrief is aangepast. Kijk of je hiermee akkoord bent."
- [ ] Herschreven brief is NIET alleen de originele tekst met "no show" erachter geplakt
### Stap 5 — Accordering
- [ ] Textarea is bewerkbaar — gebruiker kan aanpassingen maken
- [ ] "Originele brief bekijken" toggle werkt
- [ ] `[Akkoord & Verzendklaar]` roept dispatch API aan
- [ ] Na dispatch: knop verandert naar "✓ Verzendklaar"
- [ ] Chat ontvangt bevestigingsbericht
### Technische criteria
- [ ] Volledige flow doorloopbaar in < 30 seconden (excl. LLM latency ~3-5s)
- [ ] Geen schermwissels nodig gedurende de hele flow
- [ ] `pnpm build` slaagt zonder errors
- [ ] `pnpm lint` slaagt zonder errors
- [ ] Geen rode console errors in de browser
### Edge cases
- [ ] Offline/netwerk fout tijdens cancel → error message in chat, flow herstelbaar
- [ ] LLM fout in rescript → originele brief getoond + gele waarschuwingsbalk
- [ ] Dubbele klik op `[Ja, annuleer]` → slechts één API call
---
## NS.E5.S5 — Commit strategie
Commitvolgorde die logisch werkt voor code review:
```
feat(cortex): NS.E1 — register_no_show intent type + reflex patterns
feat(cortex): NS.E2.S1 — no-show flow state in cortex store
feat(cortex): NS.E2.S2 — nudge rule: declarabiliteitscheck
feat(cortex): NS.E2.S3 — chat panel: no-show nudge trigger + handlers
feat(cortex): NS.E3 — no-show API routes (cancel, context, rescript, dispatch)
feat(cortex): NS.E4.S1 — NoShowDocumentBlock component
feat(cortex): NS.E4.S2 — artifact-container: register_no_show case
feat(cortex): NS.E4.S3 — chat panel: rescript step + artifact koppeling
feat(cortex): NS.E5 — loading states, cleanup, demo polish
```
Elke commit moet `pnpm build` laten slagen.
---
## Overzicht gewijzigde bestanden (totaal)
| Bestand | Wijziging |
|---|---|
| `lib/cortex/types.ts` | `register_no_show` in union + BLOCK_CONFIGS |
| `lib/cortex/reflex-classifier.ts` | Patronen voor register_no_show |
| `app/api/cortex/chat/route.ts` | System prompt uitgebreid |
| `lib/cortex/nudge.ts` | Nudge rule 1 toegevoegd |
| `stores/cortex-store.ts` | NoShowFlowState + actions + isNoShowProcessing |
| `lib/cortex/mock-data/noshow.ts` | **Nieuw** |
| `app/api/cortex/noshow/cancel/route.ts` | **Nieuw** |
| `app/api/cortex/noshow/context/route.ts` | **Nieuw** |
| `app/api/cortex/noshow/rescript/route.ts` | **Nieuw** |
| `app/api/cortex/noshow/dispatch/route.ts` | **Nieuw** |
| `components/cortex/blocks/noshow-document-block.tsx` | **Nieuw** |
| `components/cortex/artifacts/artifact-container.tsx` | Case + import + getArtifactTitle |
| `components/cortex/chat/chat-panel.tsx` | Nudge trigger + handlers + cleanup |
| `components/cortex/chat/nudge-chat-message.tsx` | isNoShowProcessing check |
**Totaal: 14 bestanden** — 5 nieuw, 9 aangepast