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:
299
docs/intent/noshow-case/epics/NS-E5-integration.md
Normal file
299
docs/intent/noshow-case/epics/NS-E5-integration.md
Normal 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.E1–NS.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
|
||||
Reference in New Issue
Block a user