chore(docs): remove duplicate swift docs (moved to docs/archive/swift)
Remove 39 swift documentation files that were duplicated. Original files are preserved in docs/archive/swift/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,294 +0,0 @@
|
||||
# Swift Project Status Report
|
||||
**Datum:** 27 december 2024
|
||||
**Versie:** v2.5
|
||||
**Status:** 🎯 **86% Complete** - MVP binnen handbereik!
|
||||
|
||||
---
|
||||
|
||||
## 📊 Executive Summary
|
||||
|
||||
| Metric | Waarde | Visualisatie |
|
||||
|--------|--------|--------------|
|
||||
| **Voortgang** | 86% | ██████████████████░░ |
|
||||
| **Story Points** | 62 / 72 SP | +2 SP vandaag |
|
||||
| **Stories Compleet** | 27 / 29 | 93% |
|
||||
| **Epics Compleet** | 4.75 / 5 | Alle major epics done ✅ |
|
||||
| **Remaining Work** | 2 SP (1 story) | E5.S4 alleen |
|
||||
| **Target MVP** | 72 SP | 1-2 uur tot compleet |
|
||||
|
||||
**Conclusie:** Project loopt uitstekend! Alle kernfunctionaliteit is af. Alleen smoke tests nog te doen.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Vandaag Afgerond (27-12-2024)
|
||||
|
||||
### ✅ E5.S2 - Error Handling (2 SP)
|
||||
**Implementatie:**
|
||||
- `lib/swift/error-handler.ts` (301 regels) - Gecentraliseerde error handling
|
||||
- `components/swift/command-center/offline-banner.tsx` - Offline detection
|
||||
- Alle blocks + CommandInput gebruiken nieuwe error handler
|
||||
- User-friendly Nederlandse error messages voor alle HTTP codes (401, 404, 500, etc.)
|
||||
- Retry logic met exponential backoff (max 3 retries)
|
||||
- 30s timeout op alle API calls
|
||||
|
||||
**Impact:** Robuste error handling door hele app. Users krijgen duidelijke feedback bij problemen.
|
||||
|
||||
### ✅ E5.S3 - Keyboard Shortcuts (2 SP)
|
||||
**Implementatie:**
|
||||
- ⌘Enter / Ctrl+Enter quick submit in CommandInput
|
||||
- ⌘Enter / Ctrl+Enter quick save in DagnotatieBlock
|
||||
- Visual hints (⌘↵) op buttons
|
||||
- Geverifieerd: ⌘K, Escape, 1-3 number keys
|
||||
|
||||
**Documentatie:**
|
||||
- `docs/swift/keyboard-shortcuts-reference.md` (200+ regels)
|
||||
- `docs/swift/test-plan-e5-s3-keyboard-shortcuts.md` (350+ regels)
|
||||
|
||||
**Impact:** Power users kunnen nu razendsnel werken zonder muis.
|
||||
|
||||
---
|
||||
|
||||
## 📈 Epic Status Overview
|
||||
|
||||
| Epic | Omschrijving | Stories | SP | Status | % |
|
||||
|------|--------------|---------|----:|--------|---:|
|
||||
| **E0** | Setup & Foundation | 4/4 | 8/8 | ✅ **DONE** | 100% |
|
||||
| **E1** | Command Center | 5/5 | 13/13 | ✅ **DONE** | 100% |
|
||||
| **E2** | Intent Classification | 5/5 | 12/12 | ✅ **DONE** | 100% |
|
||||
| **E3** | P1 Blocks | 7/7 | 23/23 | ✅ **DONE** | 100% |
|
||||
| **E4** | Navigation & Auth | 4/4 | 8/8 | ✅ **DONE** | 100% |
|
||||
| **E5** | Polish & Testing | 3/4 | 6/8 | 🔄 **IN PROGRESS** | 75% |
|
||||
|
||||
### E5 - Polish & Testing Breakdown:
|
||||
- ✅ E5.S1 - Block animaties (2 SP) - DONE 24-12-2024
|
||||
- ✅ E5.S2 - Error handling (2 SP) - **DONE 27-12-2024** 🎉
|
||||
- ✅ E5.S3 - Keyboard shortcuts (2 SP) - **DONE 27-12-2024** 🎉
|
||||
- ⏳ E5.S4 - Smoke tests (2 SP) - TO DO (1-2 uur)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Geïmplementeerde Features
|
||||
|
||||
### Core Functionaliteit (E0-E3)
|
||||
- ✅ Command Center met 4-zone layout
|
||||
- ✅ Voice input met Deepgram streaming + waveform
|
||||
- ✅ Two-tier intent classification (local + AI fallback)
|
||||
- ✅ DagnotatieBlock - patient selectie, 5 categorieën, save to API
|
||||
- ✅ ZoekenBlock - fuzzy search, patient selection
|
||||
- ✅ OverdrachtBlock - AI samenvattingen per patiënt, period selector
|
||||
- ✅ PatientContextCard - auto-display na selectie, context view
|
||||
|
||||
### Navigation & Auth (E4)
|
||||
- ✅ Login met interface preference selector
|
||||
- ✅ Routing naar Swift/Klassiek EPD
|
||||
- ✅ User metadata storage
|
||||
- ✅ FallbackPicker voor onbekende intents
|
||||
|
||||
### Polish & UX (E5)
|
||||
- ✅ Block animaties - slide up/down, 200ms transitions
|
||||
- ✅ Error handling - offline banner, retry logic, NL messages
|
||||
- ✅ Keyboard shortcuts - ⌘K, Escape, ⌘Enter, 1-3
|
||||
- ✅ Loading states, toasts, validation errors
|
||||
- ✅ Responsive design (mobile + desktop)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Code Quality Metrics
|
||||
|
||||
### Lines of Code
|
||||
| Category | Files | LOC (approx) |
|
||||
|----------|-------|-------------:|
|
||||
| Components | 12 | ~2,500 |
|
||||
| Utilities | 5 | ~800 |
|
||||
| Stores | 1 | ~170 |
|
||||
| API Routes | 4 | ~600 |
|
||||
| **Total** | **22+** | **~4,000+** |
|
||||
|
||||
### Test Coverage (E5.S4 nog te doen)
|
||||
- ⏳ Unit tests - Intent classifier
|
||||
- ⏳ Integration tests - API routes
|
||||
- ⏳ Component tests - Blocks
|
||||
- ⏳ E2E tests - Smoke tests
|
||||
|
||||
### Documentation
|
||||
| Document | Status | Lines |
|
||||
|----------|--------|------:|
|
||||
| Bouwplan v2.5 | ✅ Up-to-date | 950+ |
|
||||
| Keyboard Shortcuts Reference | ✅ Complete | 200+ |
|
||||
| Error Handling Test Plan | ✅ Complete | 350+ |
|
||||
| Keyboard Shortcuts Test Plan | ✅ Complete | 350+ |
|
||||
| Test Plan Epic 3 | ✅ Complete | 270+ |
|
||||
| **Total Documentation** | | **2,000+** |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Manual Test Status
|
||||
|
||||
**17/20 test scenarios passed** (85%)
|
||||
|
||||
### Happy Flows (10/10) ✅
|
||||
- ✅ Login + Swift interface selector
|
||||
- ✅ ⌘K focus input
|
||||
- ✅ ⌘Enter quick submit
|
||||
- ✅ "notitie jan medicatie" → DagnotatieBlock
|
||||
- ✅ Dagnotitie save + toast
|
||||
- ✅ ⌘Enter quick save in block
|
||||
- ✅ "zoek marie" → ZoekenBlock
|
||||
- ✅ Patient selectie → PatientContextCard
|
||||
- ✅ "overdracht" → AI samenvatting
|
||||
- ✅ Voice input → transcript
|
||||
|
||||
### Error Scenarios (5/5) ✅
|
||||
- ✅ Onbekende intent → FallbackPicker
|
||||
- ✅ Offline mode → banner + error
|
||||
- ✅ Network error → retry toast
|
||||
- ✅ Validation errors → duidelijke messages
|
||||
- ✅ Geen resultaten → empty state
|
||||
|
||||
### Keyboard Shortcuts (2/2) ✅
|
||||
- ✅ Escape sluit block
|
||||
- ✅ 1-3 FallbackPicker quick select
|
||||
|
||||
### Nog Te Testen (3) - E5.S4
|
||||
- ⏳ End-to-end smoke test
|
||||
- ⏳ Performance check (< 100ms)
|
||||
- ⏳ Cross-browser (Chrome, Safari, Firefox)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Risico's & Issues
|
||||
|
||||
### Huidige Status: **Groen** ✅
|
||||
|
||||
| Risico | Impact | Status |
|
||||
|--------|--------|--------|
|
||||
| Voice accuracy | Middel | ✅ **Gemitigeerd** - Fallback werkt |
|
||||
| Intent misclassificatie | Laag | ✅ **Gemitigeerd** - FallbackPicker |
|
||||
| Performance | Laag | ✅ **Gemitigeerd** - <200ms animaties |
|
||||
| Network errors | Laag | ✅ **Gemitigeerd** - Retry + offline detection |
|
||||
| Scope creep | Laag | ✅ **Onder controle** - P1 alleen |
|
||||
|
||||
### Nieuwe Risico's (Laag)
|
||||
- Demo preparatie → Mitigatie: E5.S4 smoke tests
|
||||
- Cross-browser → Mitigatie: Test in E5.S4
|
||||
- Deployment → Post-MVP activiteit
|
||||
|
||||
**Conclusie:** Geen blockers. Project risico's minimaal.
|
||||
|
||||
---
|
||||
|
||||
## 📅 Volgende Stappen
|
||||
|
||||
### Vandaag/Morgen (1-2 uur)
|
||||
1. **E5.S4 - Smoke Tests** (2 SP)
|
||||
- Run test checklist uit test-plan-epic3.md
|
||||
- Performance check (block open < 100ms)
|
||||
- Cross-browser test (Chrome, Safari, Firefox)
|
||||
- Document resultaten
|
||||
|
||||
2. **🎉 MVP COMPLEET!** (72/72 SP)
|
||||
|
||||
### Na MVP (Week 1)
|
||||
- Demo rehearsal met stakeholders
|
||||
- User feedback sessie
|
||||
- Bug fixes op basis van feedback
|
||||
- Performance optimalisatie indien nodig
|
||||
|
||||
### Toekomstige Uitbreidingen (Backlog)
|
||||
- Diagnostiek Workflow (22 SP) - Voor behandelaars
|
||||
- Meer blocks (P2 scope)
|
||||
- Advanced analytics
|
||||
- Mobile app
|
||||
|
||||
---
|
||||
|
||||
## 💾 Technical Debt
|
||||
|
||||
### Opgelost (E5.S2, E5.S3)
|
||||
- ✅ BlockContainer animaties
|
||||
- ✅ CanvasArea block rendering
|
||||
- ✅ Error handling centralisatie
|
||||
- ✅ Keyboard shortcuts implementatie
|
||||
|
||||
### Nog Te Doen (Post-MVP)
|
||||
- Type duplicatie fix (`lib/swift/types.ts` als single source)
|
||||
- Code splitting optimalisatie
|
||||
- Bundle size analyse
|
||||
- Accessibility audit (WCAG 2.1 AA)
|
||||
|
||||
**Priority:** Laag - Geen blockers voor MVP
|
||||
|
||||
---
|
||||
|
||||
## 📊 Changelog Vandaag
|
||||
|
||||
### v2.5 (27-12-2024)
|
||||
**Nieuwe Features:**
|
||||
- ✅ Error handling met gecentraliseerde utilities
|
||||
- ✅ OfflineBanner component voor offline detection
|
||||
- ✅ safeFetch wrapper met 30s timeout en retry logic
|
||||
- ✅ Nederlandse error messages voor alle HTTP status codes
|
||||
- ✅ ⌘Enter / Ctrl+Enter quick submit shortcuts
|
||||
- ✅ ⌘Enter / Ctrl+Enter quick save in DagnotatieBlock
|
||||
- ✅ Visual hints voor keyboard shortcuts (⌘↵)
|
||||
|
||||
**Documentatie:**
|
||||
- ✅ Error handling test plan (350+ regels)
|
||||
- ✅ Keyboard shortcuts reference (200+ regels)
|
||||
- ✅ Keyboard shortcuts test plan (350+ regels)
|
||||
- ✅ Bouwplan v2.5 update
|
||||
|
||||
**Impact:**
|
||||
- +2 SP voltooid (E5.S2, E5.S3)
|
||||
- 86% → 86% voortgang
|
||||
- 27/29 stories compleet
|
||||
- Robustere error handling
|
||||
- Betere power user experience
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Metrics
|
||||
|
||||
| Metric | Target | Current | Status |
|
||||
|--------|--------|---------|--------|
|
||||
| Story Points | 72 SP | 62 SP | 86% ✅ |
|
||||
| Core Features | 100% | 100% | ✅ Done |
|
||||
| Error Handling | 100% | 100% | ✅ Done |
|
||||
| Keyboard Shortcuts | 100% | 100% | ✅ Done |
|
||||
| Documentation | Complete | Complete | ✅ Done |
|
||||
| Tests | 80%+ | 0% (E5.S4) | ⏳ In Progress |
|
||||
| Demo Ready | Yes | Almost | 🎯 1 story away |
|
||||
|
||||
---
|
||||
|
||||
## 👥 Team & Ownership
|
||||
|
||||
| Role | Name | Verantwoordelijk voor |
|
||||
|------|------|----------------------|
|
||||
| Developer | Claude | Alle implementatie (E0-E5) |
|
||||
| Product Owner | Colin | Requirements, prioriteit |
|
||||
| Architect | Colin | Technische keuzes |
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact & Next Steps
|
||||
|
||||
**Voor Colin:**
|
||||
1. ✅ E5.S2 is compleet - Error handling geïmplementeerd
|
||||
2. ✅ E5.S3 is compleet - Keyboard shortcuts klaar
|
||||
3. ⏳ **Volgende:** E5.S4 - Smoke tests (1-2 uur)
|
||||
4. 🎉 **Na E5.S4:** MVP COMPLEET! Demo-ready.
|
||||
|
||||
**Vragen/Opmerkingen:**
|
||||
- Wil je E5.S4 nu doen of later?
|
||||
- Zijn er specifieke test scenarios die prioriteit hebben?
|
||||
- Deployment naar Vercel planning?
|
||||
|
||||
---
|
||||
|
||||
**Project Status:** 🟢 **EXCELLENT**
|
||||
**Momentum:** 🚀 **HIGH**
|
||||
**Risk Level:** 🟢 **LOW**
|
||||
**MVP Completion:** 🎯 **1-2 HOURS AWAY**
|
||||
|
||||
**Let's finish this! 💪**
|
||||
@@ -1,554 +0,0 @@
|
||||
# Mission Control — Bouwplan Swift v1.0
|
||||
|
||||
**Projectnaam:** Swift — Contextual UI EPD
|
||||
**Versie:** v1.0
|
||||
**Datum:** 23-12-2024
|
||||
**Auteur:** Colin Lit / Development Team
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en context
|
||||
|
||||
### 1.1 Projectdoel
|
||||
|
||||
Swift is een **Contextual UI** interface voor het Mini-EPD systeem. In plaats van navigatie door menu's spreekt of typt de gebruiker een intentie — en het juiste UI-blok verschijnt voorgevuld met relevante data.
|
||||
|
||||
**Kernbelofte:**
|
||||
> Van 12 klikken en 3 minuten naar 1 zin en 15 seconden.
|
||||
|
||||
### 1.2 Business Case
|
||||
|
||||
| Metric | Huidig | Met Swift | Besparing |
|
||||
|--------|--------|-----------|-----------|
|
||||
| Dagnotitie maken | 3-5 min | 15 sec | 95% |
|
||||
| Patiënt zoeken | 1-2 min | 5 sec | 95% |
|
||||
| Overdracht maken | 20-30 min | 5 min | 80% |
|
||||
| Rapportage schrijven | 8-15 min | 2-3 min | 75% |
|
||||
|
||||
**Per verpleegkundige per dag: ~4 uur terug naar zorg**
|
||||
|
||||
### 1.3 Relatie met Documentatie
|
||||
|
||||
| Document | Beschrijft | Locatie |
|
||||
|----------|------------|---------|
|
||||
| PRD | Product visie, requirements | `swift-prd.md` |
|
||||
| FO | Functionele flows, blocks | `swift-fo-ai.md` |
|
||||
| TO | Technische architectuur | `to-swift-v1.md` |
|
||||
| UX | Visuele specificaties | `swift-ux-v2.1.md` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Uitgangspunten
|
||||
|
||||
### 2.1 Technische Stack
|
||||
|
||||
**Bestaand (hergebruiken):**
|
||||
| Component | Technologie | Versie |
|
||||
|-----------|-------------|--------|
|
||||
| Framework | Next.js | 14.2.18 |
|
||||
| UI Library | React | 18.3.1 |
|
||||
| Styling | TailwindCSS | 3.4.18 |
|
||||
| Components | shadcn/ui | - |
|
||||
| Command Palette | cmdk | 1.1.1 |
|
||||
| Animations | framer-motion | 12.23.24 |
|
||||
| Database | Supabase | 2.81.1 |
|
||||
| Speech-to-Text | Deepgram | 4.11.2 |
|
||||
| AI | Claude API | - |
|
||||
| Validation | Zod | 4.1.12 |
|
||||
| Forms | react-hook-form | 7.66.1 |
|
||||
|
||||
**Nieuw toe te voegen:**
|
||||
| Component | Technologie | Versie | Reden |
|
||||
|-----------|-------------|--------|-------|
|
||||
| State Management | Zustand | ^4.5.0 | Lightweight, TypeScript-first |
|
||||
|
||||
### 2.2 Projectkaders
|
||||
|
||||
| Kader | Waarde |
|
||||
|-------|--------|
|
||||
| **Bouwtijd** | 4 weken (4 sprints) |
|
||||
| **Team** | 1 developer |
|
||||
| **Scope** | MVP: P1 blocks (dagnotitie, zoeken, overdracht) |
|
||||
| **Data** | Bestaande Supabase database |
|
||||
| **Doel** | Werkende demo + user testing |
|
||||
|
||||
### 2.3 Programmeer Uitgangspunten
|
||||
|
||||
**Code Quality Principles:**
|
||||
|
||||
- **DRY (Don't Repeat Yourself)**
|
||||
- Herbruikbare block componenten
|
||||
- Centrale intent classificatie logica
|
||||
- Shared hooks voor common patterns
|
||||
|
||||
- **KISS (Keep It Simple, Stupid)**
|
||||
- Local-first intent classificatie (regex)
|
||||
- AI alleen als fallback
|
||||
- Minimale state complexity
|
||||
|
||||
- **SOC (Separation of Concerns)**
|
||||
- UI blocks gescheiden van intent logic
|
||||
- API routes gescheiden van business logic
|
||||
- Store slices per domein
|
||||
|
||||
- **YAGNI (You Aren't Gonna Need It)**
|
||||
- Alleen P1 blocks in MVP
|
||||
- Geen toggle tussen interfaces
|
||||
- Geen advanced analytics in v1
|
||||
|
||||
**Development Practices:**
|
||||
|
||||
```typescript
|
||||
// ✅ Goede structuur voor Swift components
|
||||
components/
|
||||
├── swift/
|
||||
│ ├── command-center/
|
||||
│ │ ├── command-center.tsx // Main container
|
||||
│ │ ├── command-input.tsx // Input component
|
||||
│ │ └── index.ts // Barrel export
|
||||
│ └── blocks/
|
||||
│ ├── dagnotitie-block.tsx
|
||||
│ ├── zoeken-block.tsx
|
||||
│ └── index.ts
|
||||
|
||||
// ✅ Store slice pattern
|
||||
stores/
|
||||
└── swift-store.ts // Single store file
|
||||
|
||||
// ✅ Intent classification
|
||||
lib/
|
||||
└── swift/
|
||||
├── intent-classifier.ts // Local classification
|
||||
├── intent-classifier-ai.ts // AI fallback
|
||||
└── types.ts // Type definitions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Epics & Stories Overzicht
|
||||
|
||||
| Epic ID | Titel | Doel | Status | Stories | Effort |
|
||||
|---------|-------|------|--------|---------|--------|
|
||||
| E0 | Setup & Foundation | Zustand, routing, base layout | ✅ Done | 4 | 8 SP |
|
||||
| E1 | Command Center | Input, voice, context bar | ✅ Done | 5 | 13 SP |
|
||||
| E2 | Intent Classification | Local + AI fallback + wiring | ⏳ In Progress | 5 | 12 SP |
|
||||
| E3 | P1 Blocks | Dagnotitie, Zoeken, Overdracht | ⏳ To Do | 6 | 21 SP |
|
||||
| E4 | Navigation & Auth | Login keuze, routing, preferences | ⏳ To Do | 4 | 8 SP |
|
||||
| E5 | Polish & Testing | Animaties, error handling, tests | ⏳ To Do | 4 | 8 SP |
|
||||
|
||||
**Totaal: 28 stories, 70 story points (31 SP done, 39 SP remaining)**
|
||||
|
||||
**Belangrijk:**
|
||||
- Bouw per epic en per story, niet alles tegelijk
|
||||
- Dependencies installeren: eerst aan Colin melden
|
||||
- Database migraties: eerst aan Colin melden
|
||||
|
||||
---
|
||||
|
||||
## 4. Epics & Stories (Uitwerking)
|
||||
|
||||
### Epic 0 — Setup & Foundation
|
||||
**Epic Doel:** Werkende development omgeving met Zustand store en Swift routing.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E0.S1 | Zustand installeren | `pnpm add zustand` succesvol, import werkt | ✅ | — | 1 |
|
||||
| E0.S2 | Swift store opzetten | `stores/swift-store.ts` met basis state | ✅ | E0.S1 | 2 |
|
||||
| E0.S3 | Swift route aanmaken | `/epd/swift` route met eigen layout | ✅ | E0.S2 | 2 |
|
||||
| E0.S4 | Swift folder structuur | `components/swift/`, `lib/swift/` aangemaakt | ✅ | E0.S3 | 3 |
|
||||
|
||||
**Technical Notes:**
|
||||
```bash
|
||||
# E0.S1: Dependency installatie
|
||||
pnpm add zustand
|
||||
```
|
||||
|
||||
```typescript
|
||||
// E0.S2: Store structuur
|
||||
// stores/swift-store.ts
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
|
||||
interface SwiftStore {
|
||||
// Context
|
||||
activePatient: Patient | null;
|
||||
shift: 'nacht' | 'ochtend' | 'middag' | 'avond';
|
||||
|
||||
// Block state
|
||||
activeBlock: BlockType | null;
|
||||
prefillData: Record<string, unknown>;
|
||||
|
||||
// Actions
|
||||
setActivePatient: (patient: Patient | null) => void;
|
||||
openBlock: (type: BlockType, prefill?: Record<string, unknown>) => void;
|
||||
closeBlock: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 1 — Command Center
|
||||
**Epic Doel:** Werkende command center met tekst en voice input.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E1.S1 | Command Center layout | 4-zone layout (context, canvas, recent, input) | ✅ | E0.S4 | 3 |
|
||||
| E1.S2 | Context Bar | Dienst, patiënt dropdown, user info | ✅ | E1.S1 | 2 |
|
||||
| E1.S3 | Command Input | Tekst input met placeholder, focus state | ✅ | E1.S1 | 2 |
|
||||
| E1.S4 | Voice Input integratie | Deepgram streaming in command input | ✅ | E1.S3 | 3 |
|
||||
| E1.S5 | Recent Strip | Laatste 5 acties als chips | ✅ | E1.S1 | 3 |
|
||||
|
||||
**Technical Notes:**
|
||||
```
|
||||
Command Center Layout:
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Context Bar (48px) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Canvas Area (flex) │
|
||||
│ │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Recent Strip (48px) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Command Input (64px, fixed bottom) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 2 — Intent Classification
|
||||
**Epic Doel:** Two-tier intent classificatie (local + AI fallback).
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E2.S1 | Local classifier | Regex patterns voor P1 intents, <50ms | ✅ | E0.S4 | 3 |
|
||||
| E2.S2 | Entity extraction | Patient naam, categorie uit input | ✅ | E2.S1 | 3 |
|
||||
| E2.S3 | AI fallback | Claude Haiku bij confidence <0.8 | ✅ | E2.S2 | 2 |
|
||||
| E2.S4 | Intent API route | POST /api/intent/classify | ✅ | E2.S3 | 2 |
|
||||
| E2.S5 | Input → Block wiring | CommandInput submit → API → openBlock | ⏳ | E2.S4 | 2 |
|
||||
|
||||
**Technical Notes:**
|
||||
```typescript
|
||||
// E2.S1: Local classifier patterns
|
||||
const INTENT_PATTERNS = {
|
||||
dagnotitie: [
|
||||
/^notitie\s+(\w+)/i,
|
||||
/^(\w+)\s+(medicatie|adl|gedrag|incident)/i,
|
||||
/dagnotitie/i,
|
||||
],
|
||||
zoeken: [
|
||||
/^zoek\s+(\w+)/i,
|
||||
/^wie is\s+(\w+)/i,
|
||||
/^vind\s+(\w+)/i,
|
||||
],
|
||||
overdracht: [
|
||||
/^overdracht/i,
|
||||
/^dienst (klaar|afronden)/i,
|
||||
/^wat moet ik weten/i,
|
||||
],
|
||||
};
|
||||
|
||||
// E2.S5: Input → Block wiring
|
||||
// In CommandInput.handleSubmit:
|
||||
const handleSubmit = async () => {
|
||||
const response = await fetch('/api/intent/classify', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ input: inputValue }),
|
||||
});
|
||||
const { intent, confidence, entities } = await response.json();
|
||||
|
||||
if (intent !== 'unknown' && confidence >= 0.5) {
|
||||
openBlock(intent, entities); // Open juiste block met prefill
|
||||
} else {
|
||||
// Toon FallbackPicker (E4.S4)
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 3 — P1 Blocks
|
||||
**Epic Doel:** Werkende DagnotatieBlock, ZoekenBlock en OverdrachtBlock.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E3.S1 | Block Container | Animatie wrapper, close button, sizes | ⏳ | E1.S1 | 2 |
|
||||
| E3.S2 | DagnotatieBlock | Patient, categorie, tekst, opslaan | ⏳ | E3.S1, E2.S2 | 5 |
|
||||
| E3.S3 | Patient search API | GET /api/patients/search?q= fuzzy search | ⏳ | E0.S4 | 3 |
|
||||
| E3.S4 | ZoekenBlock | Input, resultaten, selectie → store | ⏳ | E3.S1, E3.S3 | 3 |
|
||||
| E3.S5 | PatientContextCard | Na selectie: notities, vitals, diagnose | ⏳ | E3.S4 | 5 |
|
||||
| E3.S6 | OverdrachtBlock | AI samenvatting per patiënt (bestaande API) | ⏳ | E3.S1 | 3 |
|
||||
|
||||
**Technical Notes:**
|
||||
```typescript
|
||||
// E3.S2: DagnotatieBlock prefill
|
||||
interface DagnotitieBlockProps {
|
||||
prefill?: {
|
||||
patientId?: string;
|
||||
patientName?: string;
|
||||
category?: VerpleegkundigCategory;
|
||||
content?: string;
|
||||
};
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
// Gebruikt bestaande POST /api/reports
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 4 — Navigation & Auth
|
||||
**Epic Doel:** Login pagina met interface keuze, routing naar Swift/Klassiek.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E4.S1 | Login form uitbreiden | Interface selector (Swift/Klassiek) | ⏳ | E0.S3 | 2 |
|
||||
| E4.S2 | Preference opslag | user_metadata.preferred_interface | ⏳ | E4.S1 | 2 |
|
||||
| E4.S3 | Redirect middleware | /epd → preference route | ⏳ | E4.S2 | 2 |
|
||||
| E4.S4 | Fallback Picker | Visuele keuze bij lage confidence | ⏳ | E3.S1 | 2 |
|
||||
|
||||
**Technical Notes:**
|
||||
```typescript
|
||||
// E4.S2: Preference in Supabase
|
||||
await supabase.auth.updateUser({
|
||||
data: {
|
||||
preferred_interface: 'swift', // of 'classic'
|
||||
remember_interface_choice: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 5 — Polish & Testing
|
||||
**Epic Doel:** Gepolijste UX met animaties, error handling en tests.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E5.S1 | Block animaties | Slide up/down met framer-motion | ⏳ | E3.S1 | 2 |
|
||||
| E5.S2 | Error handling | Network errors, validation, toasts | ⏳ | E3.S6 | 2 |
|
||||
| E5.S3 | Keyboard shortcuts | Cmd+K focus, Escape close, Enter submit | ⏳ | E1.S3 | 2 |
|
||||
| E5.S4 | Smoke tests | Happy flow tests voor alle P1 blocks | ⏳ | E5.S2 | 2 |
|
||||
|
||||
**Technical Notes:**
|
||||
```typescript
|
||||
// E5.S1: Framer Motion animaties
|
||||
const blockVariants = {
|
||||
hidden: { opacity: 0, y: 20, scale: 0.95 },
|
||||
visible: { opacity: 1, y: 0, scale: 1 },
|
||||
exit: { opacity: 0, y: 20, scale: 0.95 },
|
||||
};
|
||||
|
||||
// E5.S3: Keyboard shortcuts
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
closeBlock();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, []);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Kwaliteit & Testplan
|
||||
|
||||
### 5.1 Test Types
|
||||
|
||||
| Test Type | Scope | Tools | Wanneer |
|
||||
|-----------|-------|-------|---------|
|
||||
| Unit Tests | Intent classifier, entity extraction | Vitest | E2 |
|
||||
| Integration Tests | API endpoints | Vitest + MSW | E2, E3 |
|
||||
| Component Tests | Blocks, Command Center | React Testing Library | E3, E5 |
|
||||
| E2E Tests | Complete flows | Playwright (optioneel) | E5 |
|
||||
| Manual Tests | Demo scenarios | Checklist | E5 |
|
||||
|
||||
### 5.2 Test Coverage Targets
|
||||
|
||||
| Area | Target | Reden |
|
||||
|------|--------|-------|
|
||||
| Intent classifier | 90%+ | Kritiek voor UX |
|
||||
| API routes | 80%+ | Data integrity |
|
||||
| UI components | 60%+ | Belangrijkste flows |
|
||||
|
||||
### 5.3 Manual Test Checklist (MVP Demo)
|
||||
|
||||
**Happy Flows:**
|
||||
- [ ] User kan inloggen en Swift kiezen
|
||||
- [ ] Command input krijgt focus met Cmd+K
|
||||
- [ ] "notitie jan medicatie" → DagnotatieBlock opent met prefill
|
||||
- [ ] Dagnotitie opslaan → toast + block sluit
|
||||
- [ ] "zoek marie" → ZoekenBlock met resultaten
|
||||
- [ ] Patiënt selecteren → PatientContextCard
|
||||
- [ ] "overdracht" → OverdrachtBlock met AI samenvatting
|
||||
- [ ] Voice input → transcript in command input
|
||||
|
||||
**Error Scenarios:**
|
||||
- [ ] Onbekende intent → FallbackPicker
|
||||
- [ ] Network error → toast met retry
|
||||
- [ ] Lege notitie → validation error
|
||||
- [ ] Geen zoekresultaten → "Geen patiënten gevonden"
|
||||
|
||||
---
|
||||
|
||||
## 6. Demo & Presentatieplan
|
||||
|
||||
### 6.1 Demo Scenario
|
||||
|
||||
**Duur:** 10 minuten
|
||||
**Doelgroep:** Zorgprofessionals, management
|
||||
**Locatie:** Live op Vercel
|
||||
|
||||
**Flow:**
|
||||
|
||||
```
|
||||
1. INTRO (1 min)
|
||||
"40% van je tijd gaat naar administratie.
|
||||
Wat als je gewoon kon zeggen wat je wilt?"
|
||||
|
||||
2. CONTRAST (2 min)
|
||||
Klassiek EPD: Dashboard → Patiënten → Jan → Rapportages →
|
||||
Nieuwe → Type → Tekst → Opslaan
|
||||
Swift: "notitie jan medicatie gegeven" → Opslaan
|
||||
|
||||
3. DAGNOTITIE FLOW (2 min)
|
||||
- Typ: "notitie jan medicatie uitgereikt"
|
||||
- Block verschijnt voorgevuld
|
||||
- Één klik: opgeslagen
|
||||
|
||||
4. VOICE DEMO (2 min)
|
||||
- Klik microfoon
|
||||
- Spreek: "marie had een rustige nacht, goed geslapen"
|
||||
- Block verschijnt met transcript
|
||||
|
||||
5. OVERDRACHT (2 min)
|
||||
- "overdracht"
|
||||
- AI genereert samenvatting per patiënt
|
||||
- Toon bronverwijzingen
|
||||
|
||||
6. AFSLUITING (1 min)
|
||||
- Tijdsbesparing recap
|
||||
- Vragen
|
||||
```
|
||||
|
||||
### 6.2 Backup Plan
|
||||
|
||||
| Probleem | Oplossing |
|
||||
|----------|-----------|
|
||||
| Internet issues | Localhost met demo data |
|
||||
| Voice niet werkt | Type-only demo |
|
||||
| AI API down | Pre-cached responses |
|
||||
| Complete failure | Video recording |
|
||||
|
||||
---
|
||||
|
||||
## 7. Risico's & Mitigatie
|
||||
|
||||
| Risico | Kans | Impact | Mitigatie | Owner |
|
||||
|--------|------|--------|-----------|-------|
|
||||
| Voice accuracy NL | Middel | Hoog | Deepgram NL model, fallback naar tekst | Dev |
|
||||
| Intent misclassificatie | Middel | Hoog | Two-tier systeem, FallbackPicker | Dev |
|
||||
| AI latency | Laag | Middel | Local-first, Haiku model | Dev |
|
||||
| User adoption | Middel | Middel | Keuze behouden, geen dwang | Product |
|
||||
| Scope creep | Hoog | Hoog | Strict P1-only, backlog voor rest | Dev |
|
||||
| Performance | Laag | Middel | Code splitting, lazy loading | Dev |
|
||||
|
||||
---
|
||||
|
||||
## 8. Sprint Planning
|
||||
|
||||
### Sprint 1 (Week 1): Foundation
|
||||
- E0: Setup & Foundation (8 SP)
|
||||
- E1.S1-S3: Command Center basics (7 SP)
|
||||
- **Deliverable:** Swift route met command input
|
||||
|
||||
### Sprint 2 (Week 2): Intent & Blocks
|
||||
- E1.S4-S5: Voice + Recent (6 SP)
|
||||
- E2: Intent Classification (10 SP)
|
||||
- **Deliverable:** Werkende intent classificatie
|
||||
|
||||
### Sprint 3 (Week 3): P1 Blocks
|
||||
- E3: Alle P1 blocks (21 SP)
|
||||
- **Deliverable:** DagnotatieBlock, ZoekenBlock, OverdrachtBlock
|
||||
|
||||
### Sprint 4 (Week 4): Polish & Ship
|
||||
- E4: Navigation & Auth (8 SP)
|
||||
- E5: Polish & Testing (8 SP)
|
||||
- **Deliverable:** Demo-ready MVP
|
||||
|
||||
---
|
||||
|
||||
## 9. Definition of Done
|
||||
|
||||
Een story is **Done** wanneer:
|
||||
- [ ] Code geschreven en werkend
|
||||
- [ ] TypeScript types correct
|
||||
- [ ] Component responsive (mobile + desktop)
|
||||
- [ ] Error states afgehandeld
|
||||
- [ ] Toegankelijkheid basics (focus, labels)
|
||||
- [ ] Getest in Chrome + Safari
|
||||
- [ ] PR reviewed (indien team)
|
||||
- [ ] Gemerged naar main
|
||||
|
||||
Een epic is **Done** wanneer:
|
||||
- [ ] Alle stories Done
|
||||
- [ ] Integration test passed
|
||||
- [ ] Demo scenario werkt
|
||||
|
||||
---
|
||||
|
||||
## 10. Referenties
|
||||
|
||||
### Project Documenten
|
||||
- PRD: `docs/swift/swift-prd.md`
|
||||
- FO: `docs/swift/swift-fo-ai.md`
|
||||
- TO: `docs/swift/to-swift-v1.md`
|
||||
- UX: `docs/swift/swift-ux-v2.1.md`
|
||||
|
||||
### Bestaande Code Referenties
|
||||
- Command component: `components/ui/command.tsx`
|
||||
- Speech streaming: `components/speech-recorder-streaming.tsx`
|
||||
- Overdracht API: `app/api/overdracht/generate/route.ts`
|
||||
- Report types: `lib/types/report.ts`
|
||||
|
||||
### External
|
||||
- Zustand: https://zustand-demo.pmnd.rs/
|
||||
- cmdk: https://cmdk.paco.me/
|
||||
- Deepgram: https://developers.deepgram.com/docs
|
||||
- Claude API: https://docs.anthropic.com/
|
||||
|
||||
---
|
||||
|
||||
## 11. Glossary
|
||||
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| Swift | Projectnaam voor Contextual UI EPD |
|
||||
| Command Center | Hoofdscherm met één input |
|
||||
| Block | Ephemeral UI component (dagnotitie, zoeken, etc.) |
|
||||
| Intent | Gebruikersintentie (dagnotitie, zoeken, overdracht) |
|
||||
| Entity | Geëxtraheerde data (patiëntnaam, categorie) |
|
||||
| Prefill | Vooraf ingevulde data in block |
|
||||
| Klassiek EPD | Traditionele menu-gebaseerde interface |
|
||||
| P1 | Prioriteit 1 (MVP scope) |
|
||||
| SP | Story Points (Fibonacci: 1, 2, 3, 5, 8) |
|
||||
|
||||
---
|
||||
|
||||
**Versiehistorie:**
|
||||
|
||||
| Versie | Datum | Auteur | Wijziging |
|
||||
|--------|-------|--------|-----------|
|
||||
| v1.0 | 23-12-2024 | Colin Lit | Initiële versie |
|
||||
| v1.1 | 23-12-2024 | Claude | E0 + E1 voltooid (21 SP) |
|
||||
| v1.2 | 23-12-2024 | Claude | E2.S1 + E2.S2 voltooid (27 SP) |
|
||||
| v1.3 | 23-12-2024 | Claude | E2.S3 AI fallback voltooid (29 SP) |
|
||||
| v1.4 | 23-12-2024 | Claude | E2.S1-S4 voltooid (31 SP) |
|
||||
| v1.5 | 24-12-2024 | Claude | E2.S5 toegevoegd: Input → Block wiring (+2 SP) |
|
||||
@@ -1,971 +0,0 @@
|
||||
# Mission Control — Bouwplan Swift v2.5
|
||||
|
||||
**Projectnaam:** Swift — Contextual UI EPD
|
||||
**Versie:** v2.5
|
||||
**Datum:** 27-12-2024
|
||||
**Auteur:** Colin Lit / Development Team
|
||||
**Status:** 🔄 **86% Complete** - MVP binnen handbereik!
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Project Status Summary
|
||||
|
||||
| Metric | Waarde | Progress |
|
||||
|--------|--------|----------|
|
||||
| **Story Points** | 62 / 72 SP | 86% ██████████████████░░ |
|
||||
| **Epics Compleet** | 4.75 / 5 | E0 ✅ E1 ✅ E2 ✅ E3 ✅ E4 ✅ E5 🔄 |
|
||||
| **Stories Compleet** | 27 / 29 | 93% |
|
||||
| **Remaining Work** | 2 SP (1 story) | E5.S4 only! |
|
||||
| **Estimated Time** | 1-2 uur | Voor MVP compleet |
|
||||
|
||||
**🎉 Laatste sprint! Nog 1 story en de MVP is klaar voor demo!**
|
||||
|
||||
---
|
||||
|
||||
## Changelog v2.5
|
||||
|
||||
> **Belangrijke wijzigingen t.o.v. v2.4:**
|
||||
> - E5.S3 compleet: Keyboard shortcuts geverifieerd en uitgebreid
|
||||
> - ⌘Enter / Ctrl+Enter voor quick submit in CommandInput
|
||||
> - ⌘Enter / Ctrl+Enter voor quick save in DagnotatieBlock
|
||||
> - Visual hints toegevoegd (⌘↵ op buttons)
|
||||
> - Keyboard shortcuts reference document
|
||||
> - Totalen bijgewerkt: 62 SP done (86%), 10 SP remaining
|
||||
|
||||
## Changelog v2.4
|
||||
|
||||
> **Belangrijke wijzigingen t.o.v. v2.3:**
|
||||
> - E5.S2 compleet: Error handling geïmplementeerd met gecentraliseerde utilities
|
||||
> - OfflineBanner component voor offline detection
|
||||
> - safeFetch wrapper met 30s timeout en retry logic
|
||||
> - User-friendly Nederlandse error messages voor alle HTTP status codes
|
||||
> - Retry logic met exponential backoff voor transient errors
|
||||
> - Alle blocks en command input gebruiken nieuwe error handler
|
||||
> - Totalen bijgewerkt: 60 SP done (83%), 12 SP remaining
|
||||
|
||||
## Changelog v2.3
|
||||
|
||||
> **Belangrijke wijzigingen t.o.v. v2.2:**
|
||||
> - E5.S1 compleet: Block animaties geïmplementeerd volgens UX specificatie
|
||||
> - Slide up/down animaties met fade en scale (200ms)
|
||||
> - CanvasArea animaties verbeterd: slide down bij sluiten (niet omhoog)
|
||||
> - BlockContainer animaties consistent gemaakt met CanvasArea
|
||||
> - Totalen bijgewerkt: 58 SP done (81%), 14 SP remaining
|
||||
|
||||
## Changelog v2.2
|
||||
|
||||
> **Belangrijke wijzigingen t.o.v. v2.1:**
|
||||
> - Epic 3 compleet: Alle P1 blocks geïmplementeerd (E3.S0-S6)
|
||||
> - PatientContextCard toegevoegd: Auto-open na patient selectie
|
||||
> - OverdrachtBlock met AI samenvattingen per patiënt
|
||||
> - Patient search API geïmplementeerd
|
||||
> - Technische debt opgelost: BlockContainer animaties, CanvasArea rendering
|
||||
> - Totalen bijgewerkt: 56 SP done (78%), 16 SP remaining
|
||||
|
||||
## Changelog v2.0
|
||||
|
||||
> **Belangrijke wijzigingen t.o.v. v1.5:**
|
||||
> - E3.S0 toegevoegd: CanvasArea block rendering (kritieke blokkade)
|
||||
> - Technische debt sectie toegevoegd (type duplicatie, keyboard shortcuts)
|
||||
> - Diagnostiek Workflow als uitbreiding opgenomen
|
||||
> - Sprint planning aangepast aan huidige voortgang
|
||||
> - Totalen bijgewerkt: 29 stories, 72 SP
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en context
|
||||
|
||||
### 1.1 Projectdoel
|
||||
|
||||
Swift is een **Contextual UI** interface voor het Mini-EPD systeem. In plaats van navigatie door menu's spreekt of typt de gebruiker een intentie — en het juiste UI-blok verschijnt voorgevuld met relevante data.
|
||||
|
||||
**Kernbelofte:**
|
||||
> Van 12 klikken en 3 minuten naar 1 zin en 15 seconden.
|
||||
|
||||
### 1.2 Business Case
|
||||
|
||||
| Metric | Huidig | Met Swift | Besparing |
|
||||
|--------|--------|-----------|-----------|
|
||||
| Dagnotitie maken | 3-5 min | 15 sec | 95% |
|
||||
| Patiënt zoeken | 1-2 min | 5 sec | 95% |
|
||||
| Overdracht maken | 20-30 min | 5 min | 80% |
|
||||
| Rapportage schrijven | 8-15 min | 2-3 min | 75% |
|
||||
|
||||
**Per verpleegkundige per dag: ~4 uur terug naar zorg**
|
||||
|
||||
### 1.3 Relatie met Documentatie
|
||||
|
||||
| Document | Beschrijft | Locatie |
|
||||
|----------|------------|---------|
|
||||
| PRD | Product visie, requirements | `swift-prd.md` |
|
||||
| FO | Functionele flows, blocks | `swift-fo-ai.md` |
|
||||
| TO | Technische architectuur | `to-swift-v1.md` |
|
||||
| UX | Visuele specificaties | `swift-ux-v2.1.md` |
|
||||
| **Diagnostiek FO** | Behandelaar workflow | `swift-fo-diagnostiek-workflow.md` |
|
||||
| **Diagnostiek Bouwplan** | Uitbreiding voor behandelaars | `bouwplan-swift-diagnostiek-workflow.md` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Uitgangspunten
|
||||
|
||||
### 2.1 Technische Stack
|
||||
|
||||
**Bestaand (hergebruiken):**
|
||||
| Component | Technologie | Versie |
|
||||
|-----------|-------------|--------|
|
||||
| Framework | Next.js | 14.2.18 |
|
||||
| UI Library | React | 18.3.1 |
|
||||
| Styling | TailwindCSS | 3.4.18 |
|
||||
| Components | shadcn/ui | - |
|
||||
| Command Palette | cmdk | 1.1.1 |
|
||||
| Animations | framer-motion | 12.23.24 |
|
||||
| Database | Supabase | 2.81.1 |
|
||||
| Speech-to-Text | Deepgram | 4.11.2 |
|
||||
| AI | Claude API | - |
|
||||
| Validation | Zod | 4.1.12 |
|
||||
| Forms | react-hook-form | 7.66.1 |
|
||||
|
||||
**Nieuw toegevoegd:**
|
||||
| Component | Technologie | Versie | Status |
|
||||
|-----------|-------------|--------|--------|
|
||||
| State Management | Zustand | 5.0.9 | ✅ Geïnstalleerd |
|
||||
|
||||
### 2.2 Projectkaders
|
||||
|
||||
| Kader | Waarde |
|
||||
|-------|--------|
|
||||
| **Bouwtijd** | 4 weken (4 sprints) |
|
||||
| **Team** | 1 developer |
|
||||
| **Scope** | MVP: P1 blocks (dagnotitie, zoeken, overdracht) |
|
||||
| **Uitbreiding** | Diagnostiek workflow (optioneel, +22 SP) |
|
||||
| **Data** | Bestaande Supabase database |
|
||||
| **Doel** | Werkende demo + user testing |
|
||||
|
||||
### 2.3 Programmeer Uitgangspunten
|
||||
|
||||
**Code Quality Principles:**
|
||||
|
||||
- **DRY (Don't Repeat Yourself)**
|
||||
- Herbruikbare block componenten
|
||||
- Centrale intent classificatie logica
|
||||
- Shared hooks voor common patterns
|
||||
- **Let op:** Types importeren uit `lib/swift/types.ts`, niet dupliceren
|
||||
|
||||
- **KISS (Keep It Simple, Stupid)**
|
||||
- Local-first intent classificatie (regex)
|
||||
- AI alleen als fallback
|
||||
- Minimale state complexity
|
||||
|
||||
- **SOC (Separation of Concerns)**
|
||||
- UI blocks gescheiden van intent logic
|
||||
- API routes gescheiden van business logic
|
||||
- Store slices per domein
|
||||
|
||||
- **YAGNI (You Aren't Gonna Need It)**
|
||||
- Alleen P1 blocks in MVP
|
||||
- Geen toggle tussen interfaces
|
||||
- Geen advanced analytics in v1
|
||||
|
||||
**Development Practices:**
|
||||
|
||||
```typescript
|
||||
// ✅ Goede structuur voor Swift components
|
||||
components/
|
||||
├── swift/
|
||||
│ ├── command-center/
|
||||
│ │ ├── command-center.tsx // Main container
|
||||
│ │ ├── command-input.tsx // Input component
|
||||
│ │ ├── canvas-area.tsx // Block rendering ← KRITIEK
|
||||
│ │ └── index.ts // Barrel export
|
||||
│ └── blocks/
|
||||
│ ├── block-container.tsx // Wrapper met animaties
|
||||
│ ├── dagnotitie-block.tsx
|
||||
│ ├── zoeken-block.tsx
|
||||
│ ├── overdracht-block.tsx
|
||||
│ └── index.ts
|
||||
|
||||
// ✅ Store importeert types (geen duplicatie)
|
||||
stores/
|
||||
└── swift-store.ts // Importeert uit lib/swift/types.ts
|
||||
|
||||
// ✅ Intent classification
|
||||
lib/
|
||||
└── swift/
|
||||
├── types.ts // SINGLE SOURCE OF TRUTH voor types
|
||||
├── intent-classifier.ts // Local classification
|
||||
├── intent-classifier-ai.ts // AI fallback
|
||||
└── entity-extractor.ts // Entity parsing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Epics & Stories Overzicht
|
||||
|
||||
| Epic ID | Titel | Doel | Status | Stories | Effort |
|
||||
|---------|-------|------|--------|---------|--------|
|
||||
| E0 | Setup & Foundation | Zustand, routing, base layout | ✅ Done | 4 | 8 SP |
|
||||
| E1 | Command Center | Input, voice, context bar | ✅ Done | 5 | 13 SP |
|
||||
| E2 | Intent Classification | Local + AI fallback + wiring | ✅ Done | 5 | 12 SP |
|
||||
| E3 | P1 Blocks | Dagnotitie, Zoeken, Overdracht | ✅ Done | 7 | 23 SP |
|
||||
| E4 | Navigation & Auth | Login keuze, routing, preferences | ✅ Done | 4 | 8 SP |
|
||||
| E5 | Polish & Testing | Animaties, error handling, tests | 🔄 In Progress | 4 | 8 SP |
|
||||
|
||||
**Totaal: 29 stories, 72 story points**
|
||||
|
||||
| Categorie | SP |
|
||||
|-----------|----:|
|
||||
| ✅ Done (E0 + E1 + E2 + E3 + E4 + E5.S1 + E5.S2 + E5.S3) | 62 |
|
||||
| ⏳ Remaining | 10 |
|
||||
|
||||
### Epic Completion Details
|
||||
|
||||
| Epic | Stories | SP | Status | Completion |
|
||||
|------|---------|----:|--------|------------|
|
||||
| **E0: Setup & Foundation** | 4/4 | 8/8 | ✅ **DONE** | 100% ████████████████████ |
|
||||
| **E1: Command Center** | 5/5 | 13/13 | ✅ **DONE** | 100% ████████████████████ |
|
||||
| **E2: Intent Classification** | 5/5 | 12/12 | ✅ **DONE** | 100% ████████████████████ |
|
||||
| **E3: P1 Blocks** | 7/7 | 23/23 | ✅ **DONE** | 100% ████████████████████ |
|
||||
| **E4: Navigation & Auth** | 4/4 | 8/8 | ✅ **DONE** | 100% ████████████████████ |
|
||||
| **E5: Polish & Testing** | 3/4 | 6/8 | 🔄 **IN PROGRESS** | 75% ███████████████░░░░░ |
|
||||
| **TOTAAL** | **27/29** | **62/72** | 🎯 **86%** | ██████████████████░░ |
|
||||
|
||||
**Laatste Sprint:**
|
||||
- ✅ E5.S1 - Block animaties (2 SP)
|
||||
- ✅ E5.S2 - Error handling (2 SP)
|
||||
- ✅ E5.S3 - Keyboard shortcuts (2 SP)
|
||||
- ⏳ E5.S4 - Smoke tests (2 SP) ← **ALLEEN DIT NOG!**
|
||||
|
||||
**Belangrijk:**
|
||||
- Bouw per epic en per story, niet alles tegelijk
|
||||
- Dependencies installeren: eerst aan Colin melden
|
||||
- Database migraties: eerst aan Colin melden
|
||||
|
||||
---
|
||||
|
||||
## 4. Epics & Stories (Uitwerking)
|
||||
|
||||
### Epic 0 — Setup & Foundation ✅ DONE
|
||||
**Epic Doel:** Werkende development omgeving met Zustand store en Swift routing.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E0.S1 | Zustand installeren | `pnpm add zustand` succesvol, import werkt | ✅ | — | 1 |
|
||||
| E0.S2 | Swift store opzetten | `stores/swift-store.ts` met basis state | ✅ | E0.S1 | 2 |
|
||||
| E0.S3 | Swift route aanmaken | `/epd/swift` route met eigen layout | ✅ | E0.S2 | 2 |
|
||||
| E0.S4 | Swift folder structuur | `components/swift/`, `lib/swift/` aangemaakt | ✅ | E0.S3 | 3 |
|
||||
|
||||
---
|
||||
|
||||
### Epic 1 — Command Center ✅ DONE
|
||||
**Epic Doel:** Werkende command center met tekst en voice input.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E1.S1 | Command Center layout | 4-zone layout (context, canvas, recent, input), keyboard shortcuts (⌘K, Escape) | ✅ | E0.S4 | 3 |
|
||||
| E1.S2 | Context Bar | Dienst indicator, patiënt chip, user info | ✅ | E1.S1 | 2 |
|
||||
| E1.S3 | Command Input | Tekst input met placeholder, focus state, send button | ✅ | E1.S1 | 2 |
|
||||
| E1.S4 | Voice Input integratie | Deepgram streaming, waveform visualisatie | ✅ | E1.S3 | 3 |
|
||||
| E1.S5 | Recent Strip | Laatste 5 acties als chips, click-to-repeat | ✅ | E1.S1 | 3 |
|
||||
|
||||
**Technical Notes:**
|
||||
```
|
||||
Command Center Layout:
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Context Bar (48px) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Canvas Area (flex) ← Blocks hier │
|
||||
│ │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Recent Strip (48px) │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Command Input (64px, fixed bottom) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 2 — Intent Classification ✅ DONE
|
||||
**Epic Doel:** Two-tier intent classificatie (local + AI fallback) + wiring naar blocks.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E2.S1 | Local classifier | Regex patterns voor P1 intents, <50ms | ✅ | E0.S4 | 3 |
|
||||
| E2.S2 | Entity extraction | Patient naam, categorie uit input | ✅ | E2.S1 | 3 |
|
||||
| E2.S3 | AI fallback | Claude Haiku bij confidence <0.8 | ✅ | E2.S2 | 2 |
|
||||
| E2.S4 | Intent API route | POST /api/intent/classify met logging | ✅ | E2.S3 | 2 |
|
||||
| E2.S5 | Input → Block wiring | CommandInput.handleSubmit → API → openBlock | ✅ | E2.S4 | 2 |
|
||||
|
||||
**E2.S5 Technical Notes (✅ GEÏMPLEMENTEERD):**
|
||||
```typescript
|
||||
// components/swift/command-center/command-input.tsx
|
||||
// IMPLEMENTATIE:
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!hasValue || isProcessing) return;
|
||||
|
||||
const inputText = inputValue.trim();
|
||||
setIsProcessing(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/intent/classify', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ input: inputText }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ error: 'Onbekende fout' }));
|
||||
throw new Error(errorData.error || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const { intent, confidence, entities } = await response.json();
|
||||
|
||||
if (intent !== 'unknown' && confidence >= 0.5) {
|
||||
openBlock(intent as BlockType, entities);
|
||||
addRecentAction({ intent, label: inputText.slice(0, 50), patientName: entities.patientName });
|
||||
clearInput();
|
||||
} else {
|
||||
// Tijdelijke fallback naar dagnotitie (wordt vervangen door FallbackPicker in E4.S4)
|
||||
openBlock('dagnotitie', { content: inputText });
|
||||
addRecentAction({ intent: 'dagnotitie', label: inputText.slice(0, 50) });
|
||||
clearInput();
|
||||
}
|
||||
} catch (error) {
|
||||
// Error handling met fallback naar dagnotitie
|
||||
openBlock('dagnotitie', { content: inputText });
|
||||
addRecentAction({ intent: 'dagnotitie', label: inputText.slice(0, 50) });
|
||||
clearInput();
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 3 — P1 Blocks ✅ DONE
|
||||
**Epic Doel:** Werkende DagnotatieBlock, ZoekenBlock en OverdrachtBlock.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E3.S0 | CanvasArea block rendering | Switch/case voor block types, prefill doorgeven | ✅ | E2.S5 | 2 |
|
||||
| E3.S1 | Block Container | Animatie wrapper, close button, sizes | ✅ | E1.S1 | 2 |
|
||||
| E3.S2 | DagnotatieBlock | Patient, categorie, tekst, opslaan naar /api/reports | ✅ | E3.S0, E3.S1 | 5 |
|
||||
| E3.S3 | Patient search API | GET /api/patients/search?q= fuzzy search | ✅ | E0.S4 | 3 |
|
||||
| E3.S4 | ZoekenBlock | Input, resultaten, selectie → store | ✅ | E3.S0, E3.S3 | 3 |
|
||||
| E3.S5 | PatientContextCard | Na selectie: notities, vitals, diagnose | ✅ | E3.S4 | 5 |
|
||||
| E3.S6 | OverdrachtBlock | AI samenvatting per patiënt (bestaande API) | ✅ | E3.S0 | 3 |
|
||||
|
||||
|
||||
**E3.S0 Technical Notes (✅ GEÏMPLEMENTEERD):**
|
||||
```typescript
|
||||
// components/swift/command-center/canvas-area.tsx
|
||||
// IMPLEMENTATIE:
|
||||
import { DagnotatieBlock } from '../blocks/dagnotitie-block';
|
||||
import { ZoekenBlock } from '../blocks/zoeken-block';
|
||||
import { OverdrachtBlock } from '../blocks/overdracht-block';
|
||||
import { PatientContextCard } from '../blocks/patient-context-card';
|
||||
|
||||
function renderBlock(activeBlock: BlockType, prefillData: BlockPrefillData) {
|
||||
switch (activeBlock) {
|
||||
case 'dagnotitie':
|
||||
return <DagnotatieBlock prefill={prefillData} />;
|
||||
case 'zoeken':
|
||||
return <ZoekenBlock prefill={prefillData} />;
|
||||
case 'overdracht':
|
||||
return <OverdrachtBlock prefill={prefillData} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// In CanvasArea:
|
||||
{activeBlock ? (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div key={activeBlock} {...blockAnimations}>
|
||||
{renderBlock(activeBlock, prefillData)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
) : activePatient ? (
|
||||
<motion.div key="patient-context" {...blockAnimations}>
|
||||
<PatientContextCard />
|
||||
</motion.div>
|
||||
) : (
|
||||
<EmptyState />
|
||||
)}
|
||||
```
|
||||
|
||||
**E3.S2 Technical Notes (✅ GEÏMPLEMENTEERD):**
|
||||
```typescript
|
||||
// components/swift/blocks/dagnotitie-block.tsx
|
||||
// IMPLEMENTATIE:
|
||||
// 1. Patient search: Debounced search met /api/fhir/Patient?q=...
|
||||
// 2. Category selector: 5 categorie buttons (medicatie, adl, gedrag, incident, observatie)
|
||||
// 3. Tekst input: Textarea met character counter (max 500)
|
||||
// 4. Opslaan: POST /api/reports met type 'verpleegkundig'
|
||||
// 5. Success toast + auto-close block na 500ms
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 4 — Navigation & Auth ✅ DONE
|
||||
**Epic Doel:** Login pagina met interface keuze, routing naar Swift/Klassiek.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E4.S1 | Login form uitbreiden | Interface selector (Swift/Klassiek) | ✅ | E0.S3 | 2 |
|
||||
| E4.S2 | Preference opslag | user_metadata.preferred_interface | ✅ | E4.S1 | 2 |
|
||||
| E4.S3 | Redirect middleware | /epd → preference route | ✅ | E4.S2 | 2 |
|
||||
| E4.S4 | Fallback Picker | Visuele keuze bij lage confidence | ✅ | E3.S0 | 2 |
|
||||
|
||||
---
|
||||
|
||||
### Epic 5 — Polish & Testing 🔄 IN PROGRESS
|
||||
**Epic Doel:** Gepolijste UX met animaties, error handling en tests.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|
||||
|----------|--------------|---------------------|--------|------|----|
|
||||
| E5.S1 | Block animaties | Slide up/down met framer-motion | ✅ | E3.S0 | 2 |
|
||||
| E5.S2 | Error handling | Network errors, validation, toasts | ✅ | E3.S6 | 2 |
|
||||
| E5.S3 | Keyboard shortcuts | Verificatie + ⌘Enter shortcuts | ✅ | E1.S1 | 2 |
|
||||
| E5.S4 | Smoke tests | Happy flow tests voor alle P1 blocks | ⏳ | E5.S2 | 2 |
|
||||
|
||||
**E5.S2 Technical Notes (✅ GEÏMPLEMENTEERD):**
|
||||
```typescript
|
||||
// lib/swift/error-handler.ts
|
||||
// IMPLEMENTATIE: Gecentraliseerde error handling utility
|
||||
|
||||
// 1. Offline detection
|
||||
export function isOffline(): boolean {
|
||||
return typeof navigator !== 'undefined' && !navigator.onLine;
|
||||
}
|
||||
|
||||
// 2. Network error detection
|
||||
export function isNetworkError(error: unknown): boolean {
|
||||
if (error instanceof TypeError) {
|
||||
return error.message.includes('fetch') ||
|
||||
error.message.includes('network') ||
|
||||
error.message.includes('Failed to fetch');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. User-friendly error messages (Dutch)
|
||||
export function getErrorInfo(error: unknown, context?: ErrorContext): ErrorInfo {
|
||||
if (isOffline()) {
|
||||
return {
|
||||
title: 'Geen internetverbinding',
|
||||
description: 'Controleer je internetverbinding en probeer het opnieuw.',
|
||||
retryable: true,
|
||||
};
|
||||
}
|
||||
|
||||
// HTTP status code mapping
|
||||
if (context?.statusCode) {
|
||||
switch (context.statusCode) {
|
||||
case 401: return { title: 'Niet geautoriseerd', ... };
|
||||
case 404: return { title: 'Niet gevonden', ... };
|
||||
case 500: return { title: 'Serverfout', ... };
|
||||
// etc.
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
// 4. Safe fetch wrapper met timeout (30s)
|
||||
export async function safeFetch(
|
||||
url: string,
|
||||
options?: RequestInit,
|
||||
context?: ErrorContext
|
||||
): Promise<Response> {
|
||||
if (isOffline()) throw new Error('Geen internetverbinding');
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: AbortSignal.timeout(30000), // 30s timeout
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await parseErrorResponse(response);
|
||||
throw new Error(errorData.error);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 5. Retry logic met exponential backoff
|
||||
export async function retryFetch<T>(
|
||||
fn: () => Promise<T>,
|
||||
maxRetries: number = 3,
|
||||
delayMs: number = 1000
|
||||
): Promise<T> {
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
const errorInfo = getErrorInfo(error);
|
||||
if (!errorInfo.retryable || attempt === maxRetries - 1) {
|
||||
throw error;
|
||||
}
|
||||
await new Promise(r => setTimeout(r, delayMs * (attempt + 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// components/swift/command-center/offline-banner.tsx
|
||||
// IMPLEMENTATIE: Offline detection banner
|
||||
export function OfflineBanner() {
|
||||
const [isOffline, setIsOffline] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsOffline(!navigator.onLine);
|
||||
window.addEventListener('online', () => setIsOffline(false));
|
||||
window.addEventListener('offline', () => setIsOffline(true));
|
||||
// cleanup
|
||||
}, []);
|
||||
|
||||
if (!isOffline) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed top-0 left-0 right-0 z-[100] bg-amber-500 text-white">
|
||||
<WifiOff /> Geen internetverbinding
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage in blocks:
|
||||
// - safeFetch() voor alle API calls (vervangt raw fetch)
|
||||
// - getErrorInfo() voor user-friendly toast messages
|
||||
// - retryFetch() voor dagnotitie/overdracht save operaties (max 3 retries)
|
||||
```
|
||||
|
||||
**E5.S3 Technical Notes (✅ GEÏMPLEMENTEERD):**
|
||||
```typescript
|
||||
// components/swift/command-center/command-input.tsx
|
||||
// IMPLEMENTATIE: ⌘Enter quick submit
|
||||
|
||||
// Keyboard shortcut: Cmd/Ctrl+Enter to submit
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Cmd/Ctrl+Enter: submit command
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleSubmit]);
|
||||
|
||||
// components/swift/blocks/dagnotitie-block.tsx
|
||||
// IMPLEMENTATIE: ⌘Enter quick save
|
||||
|
||||
// Keyboard shortcut: Cmd/Ctrl+Enter to save
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Cmd/Ctrl+Enter: save dagnotitie
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleSave]);
|
||||
|
||||
// Visual hint op submit button
|
||||
<Button type="submit" title="Opslaan (⌘Enter)">
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Opslaan...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Opslaan
|
||||
<span className="ml-2 text-xs opacity-70 hidden sm:inline">⌘↵</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
```
|
||||
|
||||
**Bestaande shortcuts (E1.S1, E4.S4) geverifieerd:**
|
||||
- **⌘K / Ctrl+K** - Focus command input (CommandCenter)
|
||||
- **Escape** - Close active block (CommandCenter + FallbackPicker)
|
||||
- **1-3** - Quick select in FallbackPicker
|
||||
- **Enter** - Native form submit in CommandInput
|
||||
|
||||
**Nieuwe shortcuts (E5.S3):**
|
||||
- **⌘Enter / Ctrl+Enter** - Quick submit in CommandInput
|
||||
- **⌘Enter / Ctrl+Enter** - Quick save in DagnotatieBlock
|
||||
|
||||
**Documentatie:**
|
||||
- `docs/swift/keyboard-shortcuts-reference.md` - Complete shortcut reference
|
||||
- `docs/swift/test-plan-e5-s3-keyboard-shortcuts.md` - Test plan
|
||||
|
||||
**E5.S1 Technical Notes (✅ GEÏMPLEMENTEERD):**
|
||||
```typescript
|
||||
// components/swift/command-center/canvas-area.tsx
|
||||
// IMPLEMENTATIE volgens UX specificatie (sectie 11.1):
|
||||
// Openen: Slide up + fade in (200ms), Scale: 0.95 → 1.0
|
||||
// Sluiten: Slide down + fade out (200ms), Scale: 1.0 → 0.95
|
||||
const blockAnimations = {
|
||||
initial: { opacity: 0, y: 20, scale: 0.95 },
|
||||
animate: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] },
|
||||
},
|
||||
exit: {
|
||||
opacity: 0,
|
||||
y: 20, // Slide down (niet omhoog)
|
||||
scale: 0.95,
|
||||
transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] },
|
||||
},
|
||||
};
|
||||
|
||||
// AnimatePresence met mode="wait" voor soepele transitions
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{activeBlock && (
|
||||
<motion.div
|
||||
key={activeBlock}
|
||||
initial={blockAnimations.initial}
|
||||
animate={blockAnimations.animate}
|
||||
exit={blockAnimations.exit}
|
||||
>
|
||||
{renderBlock(activeBlock, prefillData)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
```
|
||||
|
||||
**Nota:** Keyboard shortcuts (⌘K focus, Escape close) zijn al geïmplementeerd in E1.S1.
|
||||
E5.S3 is nu verificatie + eventuele uitbreiding (Enter submit, etc.).
|
||||
|
||||
---
|
||||
|
||||
## 5. Technische Debt & Bekende Issues
|
||||
|
||||
### 5.1 Type Duplicatie (Medium Priority)
|
||||
|
||||
**Probleem:**
|
||||
```typescript
|
||||
// lib/swift/types.ts - BRON
|
||||
export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown';
|
||||
|
||||
// stores/swift-store.ts - DUPLICAAT (moet verwijderd worden)
|
||||
export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown';
|
||||
```
|
||||
|
||||
**Impact:** Bij uitbreiding intents (diagnostiek workflow) moet op twee plekken gewijzigd worden.
|
||||
|
||||
**Oplossing:** Verwijder duplicaat uit `swift-store.ts`, importeer uit `lib/swift/types.ts`:
|
||||
```typescript
|
||||
// stores/swift-store.ts
|
||||
import type { SwiftIntent, BlockType, ShiftType } from '@/lib/swift/types';
|
||||
```
|
||||
|
||||
**Wanneer:** Voorafgaand aan E-D1.S1 (diagnostiek intent patterns).
|
||||
|
||||
### 5.2 BlockContainer Animaties (RESOLVED)
|
||||
|
||||
**Probleem:** BlockContainer bestaat maar heeft nog geen framer-motion animaties.
|
||||
|
||||
**Oplossing:** Geïmplementeerd in E3.S1. BlockContainer heeft nu volledige animatie support met framer-motion.
|
||||
|
||||
### 5.3 CanvasArea Placeholder (RESOLVED)
|
||||
|
||||
**Probleem:** CanvasArea toont placeholder tekst i.p.v. blocks.
|
||||
|
||||
**Oplossing:** Geïmplementeerd in E3.S0. CanvasArea heeft nu volledige block rendering met switch/case en animaties.
|
||||
|
||||
---
|
||||
|
||||
## 6. Kwaliteit & Testplan
|
||||
|
||||
### 6.1 Test Types
|
||||
|
||||
| Test Type | Scope | Tools | Wanneer |
|
||||
|-----------|-------|-------|---------|
|
||||
| Unit Tests | Intent classifier, entity extraction | Vitest | E2 |
|
||||
| Integration Tests | API endpoints | Vitest + MSW | E2, E3 |
|
||||
| Component Tests | Blocks, Command Center | React Testing Library | E3, E5 |
|
||||
| E2E Tests | Complete flows | Playwright (optioneel) | E5 |
|
||||
| Manual Tests | Demo scenarios | Checklist | E5 |
|
||||
|
||||
### 6.2 Test Coverage Targets
|
||||
|
||||
| Area | Target | Reden |
|
||||
|------|--------|-------|
|
||||
| Intent classifier | 90%+ | Kritiek voor UX |
|
||||
| API routes | 80%+ | Data integrity |
|
||||
| UI components | 60%+ | Belangrijkste flows |
|
||||
|
||||
### 6.3 Manual Test Checklist (MVP Demo)
|
||||
|
||||
**Status:** 🔄 12/14 scenarios geïmplementeerd (86%)
|
||||
|
||||
**Happy Flows:**
|
||||
- [x] User kan inloggen en Swift kiezen (E4) ✅
|
||||
- [x] Command input krijgt focus met Cmd+K (E1.S1) ✅
|
||||
- [x] ⌘Enter quick submit → block opent (E5.S3) ✅ NEW!
|
||||
- [x] "notitie jan medicatie" → DagnotatieBlock opent met prefill (E3.S2) ✅
|
||||
- [x] Dagnotitie opslaan → toast + block sluit (E3.S2) ✅
|
||||
- [x] ⌘Enter in dagnotitie → quick save (E5.S3) ✅ NEW!
|
||||
- [x] "zoek marie" → ZoekenBlock met resultaten (E3.S4) ✅
|
||||
- [x] Patiënt selecteren → PatientContextCard (E3.S5) ✅
|
||||
- [x] "overdracht" → OverdrachtBlock met AI samenvatting (E3.S6) ✅
|
||||
- [x] Voice input → transcript in command input (E1.S4) ✅
|
||||
|
||||
**Error Scenarios:**
|
||||
- [x] Onbekende intent → FallbackPicker (E4.S4) ✅
|
||||
- [x] Offline mode → banner + error toast (E5.S2) ✅ NEW!
|
||||
- [x] Network error → toast met retry (E5.S2) ✅
|
||||
- [x] Lege notitie → validation error (E3.S2) ✅
|
||||
- [x] Geen zoekresultaten → "Geen patiënten gevonden" (E3.S4) ✅
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
- [x] Escape sluit block (E1.S1) ✅
|
||||
- [x] 1-3 in FallbackPicker → quick select (E4.S4) ✅
|
||||
|
||||
**Te Testen (E5.S4):**
|
||||
- [ ] End-to-end smoke test alle flows
|
||||
- [ ] Performance check (< 100ms block open)
|
||||
- [ ] Cross-browser test (Chrome, Safari, Firefox)
|
||||
|
||||
---
|
||||
|
||||
## 7. Demo & Presentatieplan
|
||||
|
||||
### 7.1 Demo Scenario
|
||||
|
||||
**Duur:** 10 minuten
|
||||
**Doelgroep:** Zorgprofessionals, management
|
||||
**Locatie:** Live op Vercel
|
||||
|
||||
**Flow:**
|
||||
|
||||
```
|
||||
1. INTRO (1 min)
|
||||
"40% van je tijd gaat naar administratie.
|
||||
Wat als je gewoon kon zeggen wat je wilt?"
|
||||
|
||||
2. CONTRAST (2 min)
|
||||
Klassiek EPD: Dashboard → Patiënten → Jan → Rapportages →
|
||||
Nieuwe → Type → Tekst → Opslaan
|
||||
Swift: "notitie jan medicatie gegeven" → Opslaan
|
||||
|
||||
3. DAGNOTITIE FLOW (2 min)
|
||||
- Typ: "notitie jan medicatie uitgereikt"
|
||||
- Block verschijnt voorgevuld
|
||||
- Één klik: opgeslagen
|
||||
|
||||
4. VOICE DEMO (2 min)
|
||||
- Klik microfoon
|
||||
- Spreek: "marie had een rustige nacht, goed geslapen"
|
||||
- Block verschijnt met transcript
|
||||
|
||||
5. OVERDRACHT (2 min)
|
||||
- "overdracht"
|
||||
- AI genereert samenvatting per patiënt
|
||||
- Toon bronverwijzingen
|
||||
|
||||
6. AFSLUITING (1 min)
|
||||
- Tijdsbesparing recap
|
||||
- Vragen
|
||||
```
|
||||
|
||||
### 7.2 Backup Plan
|
||||
|
||||
| Probleem | Oplossing |
|
||||
|----------|-----------|
|
||||
| Internet issues | Localhost met demo data |
|
||||
| Voice niet werkt | Type-only demo |
|
||||
| AI API down | Pre-cached responses |
|
||||
| Complete failure | Video recording |
|
||||
|
||||
---
|
||||
|
||||
## 8. Risico's & Mitigatie
|
||||
|
||||
**Status Update (27-12-2024):** Meeste risico's zijn gemitigeerd! ✅
|
||||
|
||||
| Risico | Kans | Impact | Mitigatie | Status |
|
||||
|--------|------|--------|-----------|--------|
|
||||
| Voice accuracy NL | Middel | Hoog | Deepgram NL model, fallback naar tekst | ✅ **Gemitigeerd** - Voice werkt met transcript fallback |
|
||||
| Intent misclassificatie | Laag ↓ | Middel ↓ | Two-tier systeem, FallbackPicker | ✅ **Gemitigeerd** - FallbackPicker geïmplementeerd (E4.S4) |
|
||||
| AI latency | Laag | Middel | Local-first, Haiku model | ✅ **Gemitigeerd** - Local classifier <50ms |
|
||||
| User adoption | Middel | Middel | Keuze behouden, geen dwang | ✅ **Gemitigeerd** - Interface selector (E4.S1-S3) |
|
||||
| Scope creep | Laag ↓ | Laag ↓ | Strict P1-only, backlog voor rest | ✅ **Onder controle** - P1 scope behouden |
|
||||
| Performance | Laag | Laag | Code splitting, lazy loading, animaties | ✅ **Gemitigeerd** - <200ms transitions (E5.S1) |
|
||||
| Network errors | Laag | Laag | Offline detection, retry logic | ✅ **Gemitigeerd** - Error handler (E5.S2) |
|
||||
| ~~CanvasArea blocking~~ | ~~Hoog~~ | ~~Hoog~~ | ~~E3.S0 prioriteit~~ | ✅ **OPGELOST** - E3.S0 compleet |
|
||||
|
||||
**Nieuwe Risico's:**
|
||||
| Risico | Kans | Impact | Mitigatie | Status |
|
||||
|--------|------|--------|-----------|--------|
|
||||
| Demo preparatie | Laag | Middel | E5.S4 smoke tests, rehearsal | ⏳ **In behandeling** |
|
||||
| Cross-browser issues | Laag | Laag | Test in Chrome/Safari/Firefox | ⏳ **E5.S4** |
|
||||
| Production deployment | Laag | Middel | Vercel deployment check | ⏳ **Post-MVP** |
|
||||
|
||||
**Conclusie:** Project risico's zijn minimaal. MVP is stabiel en klaar voor testing. ✅
|
||||
|
||||
---
|
||||
|
||||
## 9. Sprint Planning (Aangepast)
|
||||
|
||||
### Huidige Status (27-12-2024)
|
||||
- ✅ E0: Setup & Foundation (8 SP) — DONE
|
||||
- ✅ E1: Command Center (13 SP) — DONE
|
||||
- ✅ E2: Intent Classification (12 SP) — DONE
|
||||
- ✅ E3: P1 Blocks (23 SP) — DONE
|
||||
- ✅ E4: Navigation & Auth (8 SP) — DONE
|
||||
- 🔄 E5: Polish & Testing (8 SP) — IN PROGRESS (6/8 SP done)
|
||||
- ✅ E5.S1: Block animaties (2 SP) — DONE
|
||||
- ✅ E5.S2: Error handling (2 SP) — DONE
|
||||
- ✅ E5.S3: Keyboard shortcuts (2 SP) — DONE
|
||||
- ⏳ E5.S4: Smoke tests (2 SP) — TO DO
|
||||
|
||||
**Totaal Done: 62 SP / 72 SP (86%)**
|
||||
|
||||
### Sprint 3 (Voltooid): Core Wiring + Blocks
|
||||
- ✅ E2.S5: Input → Block wiring (2 SP) — DONE
|
||||
- ✅ E3.S0: CanvasArea block rendering (2 SP) — DONE
|
||||
- ✅ E3.S1: Block Container (2 SP) — DONE
|
||||
- ✅ E3.S2: DagnotatieBlock (5 SP) — DONE
|
||||
- **Deliverable:** "notitie jan" → DagnotatieBlock werkt end-to-end ✅
|
||||
|
||||
### Sprint 4 (Voltooid): Remaining Blocks
|
||||
- ✅ E3.S3: Patient search API (3 SP) — DONE
|
||||
- ✅ E3.S4: ZoekenBlock (3 SP) — DONE
|
||||
- ✅ E3.S5: PatientContextCard (5 SP) — DONE
|
||||
- ✅ E3.S6: OverdrachtBlock (3 SP) — DONE
|
||||
- **Deliverable:** Alle P1 blocks werken ✅
|
||||
|
||||
### Sprint 5: Polish & Ship (In Progress)
|
||||
- ✅ E4: Navigation & Auth (8 SP) — DONE
|
||||
- 🔄 E5: Polish & Testing (8 SP) — IN PROGRESS (6/8 SP done)
|
||||
- ✅ E5.S1: Block animaties (2 SP) — DONE
|
||||
- ✅ E5.S2: Error handling (2 SP) — DONE
|
||||
- ✅ E5.S3: Keyboard shortcuts (2 SP) — DONE
|
||||
- ⏳ E5.S4: Smoke tests (2 SP) — TO DO
|
||||
- Technische debt opruimen
|
||||
- **Deliverable:** Demo-ready MVP
|
||||
|
||||
**Laatste 2 SP voor MVP compleet! 🎯**
|
||||
|
||||
---
|
||||
|
||||
## 10. Uitbreidingen (Backlog)
|
||||
|
||||
### Diagnostiek Workflow (22 SP)
|
||||
|
||||
**Status:** Gepland na MVP
|
||||
**Documentatie:** `bouwplan-swift-diagnostiek-workflow.md`
|
||||
|
||||
| Epic | Stories | SP | Vereisten |
|
||||
|------|---------|----:|-----------|
|
||||
| E-D1 | Afspraak & Rapportage | 12 | E3 compleet |
|
||||
| E-D2 | Diagnose Beheer | 10 | E3 compleet |
|
||||
|
||||
**Pre-requisites:**
|
||||
1. Swift MVP compleet (E0-E5)
|
||||
2. Report type "diagnostiek" toevoegen aan REPORT_TYPES
|
||||
3. SwiftIntent uitbreiden met nieuwe types
|
||||
4. Entity extraction voor datum/tijd
|
||||
|
||||
**Zie:** `bouwplan-swift-diagnostiek-workflow-beoordeling.md` voor details.
|
||||
|
||||
---
|
||||
|
||||
## 11. Definition of Done
|
||||
|
||||
Een story is **Done** wanneer:
|
||||
- [ ] Code geschreven en werkend
|
||||
- [ ] TypeScript types correct (geen duplicaten)
|
||||
- [ ] Component responsive (mobile + desktop)
|
||||
- [ ] Error states afgehandeld
|
||||
- [ ] Toegankelijkheid basics (focus, labels)
|
||||
- [ ] Getest in Chrome + Safari
|
||||
- [ ] PR reviewed (indien team)
|
||||
- [ ] Gemerged naar main
|
||||
|
||||
Een epic is **Done** wanneer:
|
||||
- [ ] Alle stories Done
|
||||
- [ ] Integration test passed
|
||||
- [ ] Demo scenario werkt
|
||||
|
||||
---
|
||||
|
||||
## 12. Referenties
|
||||
|
||||
### Project Documenten
|
||||
- PRD: `docs/swift/swift-prd.md`
|
||||
- FO: `docs/swift/swift-fo-ai.md`
|
||||
- TO: `docs/swift/to-swift-v1.md`
|
||||
- UX: `docs/swift/swift-ux-v2.1.md`
|
||||
- Diagnostiek FO: `docs/swift/swift-fo-diagnostiek-workflow.md`
|
||||
- Diagnostiek Bouwplan: `docs/swift/bouwplan-swift-diagnostiek-workflow.md`
|
||||
- Beoordeling: `docs/swift/bouwplan-swift-diagnostiek-workflow-beoordeling.md`
|
||||
|
||||
### Bestaande Code Referenties
|
||||
- Swift Store: `stores/swift-store.ts`
|
||||
- Swift Types: `lib/swift/types.ts` (SINGLE SOURCE)
|
||||
- Intent Classifier: `lib/swift/intent-classifier.ts`
|
||||
- Intent API: `app/api/intent/classify/route.ts`
|
||||
- Command Center: `components/swift/command-center/`
|
||||
- Block Container: `components/swift/blocks/block-container.tsx`
|
||||
|
||||
### Bestaande EPD Code (Hergebruik)
|
||||
- Command component: `components/ui/command.tsx`
|
||||
- Speech streaming: `components/speech-recorder-streaming.tsx`
|
||||
- Overdracht API: `app/api/overdracht/generate/route.ts`
|
||||
- Report types: `lib/types/report.ts`
|
||||
- Reports API: `app/api/reports/route.ts`
|
||||
|
||||
### External
|
||||
- Zustand: https://zustand-demo.pmnd.rs/
|
||||
- cmdk: https://cmdk.paco.me/
|
||||
- Deepgram: https://developers.deepgram.com/docs
|
||||
- Claude API: https://docs.anthropic.com/
|
||||
- Framer Motion: https://www.framer.com/motion/
|
||||
|
||||
---
|
||||
|
||||
## 13. Glossary
|
||||
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| Swift | Projectnaam voor Contextual UI EPD |
|
||||
| Command Center | Hoofdscherm met één input |
|
||||
| Block | Ephemeral UI component (dagnotitie, zoeken, etc.) |
|
||||
| Intent | Gebruikersintentie (dagnotitie, zoeken, overdracht) |
|
||||
| Entity | Geëxtraheerde data (patiëntnaam, categorie) |
|
||||
| Prefill | Vooraf ingevulde data in block |
|
||||
| Klassiek EPD | Traditionele menu-gebaseerde interface |
|
||||
| P1 | Prioriteit 1 (MVP scope) |
|
||||
| SP | Story Points (Fibonacci: 1, 2, 3, 5, 8) |
|
||||
| Wiring | Koppeling tussen componenten (input → API → block) |
|
||||
|
||||
---
|
||||
|
||||
**Versiehistorie:**
|
||||
|
||||
| Versie | Datum | Auteur | Wijziging |
|
||||
|--------|-------|--------|-----------|
|
||||
| v1.0 | 23-12-2024 | Colin Lit | Initiële versie |
|
||||
| v1.1 | 23-12-2024 | Claude | E0 + E1 voltooid (21 SP) |
|
||||
| v1.2 | 23-12-2024 | Claude | E2.S1 + E2.S2 voltooid (27 SP) |
|
||||
| v1.3 | 23-12-2024 | Claude | E2.S3 AI fallback voltooid (29 SP) |
|
||||
| v1.4 | 23-12-2024 | Claude | E2.S1-S4 voltooid (31 SP) |
|
||||
| v1.5 | 24-12-2024 | Claude | E2.S5 toegevoegd: Input → Block wiring (+2 SP) |
|
||||
| **v2.0** | **24-12-2024** | **Claude** | **Major update: E3.S0 toegevoegd, technische debt sectie, diagnostiek workflow referentie, sprint planning aangepast (29 stories, 72 SP)** |
|
||||
| **v2.1** | **24-12-2024** | **Claude** | **E2.S5 voltooid: Input → Block wiring geïmplementeerd, Epic 2 compleet (33 SP done, 46%)** |
|
||||
| **v2.2** | **24-12-2024** | **Claude** | **Epic 3 compleet: Alle P1 blocks geïmplementeerd (E3.S0-S6), PatientContextCard toegevoegd, OverdrachtBlock met AI samenvattingen (56 SP done, 78%)** |
|
||||
| **v2.3** | **24-12-2024** | **Claude** | **E5.S1 compleet: Block animaties geïmplementeerd volgens UX specificatie (slide up/down met fade en scale, 200ms), Epic 4 compleet (58 SP done, 81%)** |
|
||||
| **v2.4** | **27-12-2024** | **Claude** | **E5.S2 compleet: Error handling met gecentraliseerde utilities, OfflineBanner, safeFetch, retry logic, Nederlandse error messages (60 SP done, 83%)** |
|
||||
| **v2.5** | **27-12-2024** | **Claude** | **E5.S3 compleet: Keyboard shortcuts geverifieerd en uitgebreid, ⌘Enter quick submit/save, visual hints, shortcuts reference (62 SP done, 86%)** |
|
||||
@@ -1,173 +0,0 @@
|
||||
# Swift Keyboard Shortcuts Reference
|
||||
|
||||
**Versie:** 1.0
|
||||
**Datum:** 27-12-2024
|
||||
**Status:** E5.S3 - Keyboard Shortcuts Verificatie
|
||||
|
||||
---
|
||||
|
||||
## 🎹 Global Shortcuts (altijd actief)
|
||||
|
||||
| Shortcut | Actie | Context | Status |
|
||||
|----------|-------|---------|--------|
|
||||
| **⌘K** / **Ctrl+K** | Focus command input | Overal | ✅ Werkt |
|
||||
| **Escape** | Sluit actief block | Als block open is | ✅ Werkt |
|
||||
| **⌘Enter** / **Ctrl+Enter** | Quick submit command | Als input focus heeft | ⏳ Toe te voegen |
|
||||
|
||||
---
|
||||
|
||||
## 📝 Command Input Shortcuts
|
||||
|
||||
| Shortcut | Actie | Context | Status |
|
||||
|----------|-------|---------|--------|
|
||||
| **Enter** | Verstuur commando | In command input | ✅ Werkt (native form) |
|
||||
| **⌘Enter** / **Ctrl+Enter** | Verstuur commando | In command input | ⏳ Toe te voegen |
|
||||
| **Escape** | Clear input | In command input (optioneel) | ❌ Niet geïmplementeerd |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 FallbackPicker Shortcuts
|
||||
|
||||
| Shortcut | Actie | Context | Status |
|
||||
|----------|-------|---------|--------|
|
||||
| **1** | Selecteer Dagnotitie | FallbackPicker open | ✅ Werkt |
|
||||
| **2** | Selecteer Zoeken | FallbackPicker open | ✅ Werkt |
|
||||
| **3** | Selecteer Overdracht | FallbackPicker open | ✅ Werkt |
|
||||
| **Escape** | Sluit FallbackPicker | FallbackPicker open | ✅ Werkt |
|
||||
|
||||
---
|
||||
|
||||
## 📋 Block-Specific Shortcuts
|
||||
|
||||
### DagnotatieBlock
|
||||
| Shortcut | Actie | Context | Status |
|
||||
|----------|-------|---------|--------|
|
||||
| **⌘Enter** / **Ctrl+Enter** | Opslaan dagnotitie | In dagnotitie block | ⏳ Toe te voegen |
|
||||
| **Escape** | Sluit block | In dagnotitie block | ✅ Werkt (global) |
|
||||
|
||||
### ZoekenBlock
|
||||
| Shortcut | Actie | Context | Status |
|
||||
|----------|-------|---------|--------|
|
||||
| **Enter** | Selecteer eerste resultaat | In zoek input | ⏳ Optioneel |
|
||||
| **↓ / ↑** | Navigeer door resultaten | In zoek input | ⏳ Optioneel |
|
||||
| **Escape** | Sluit block | In zoeken block | ✅ Werkt (global) |
|
||||
|
||||
### OverdrachtBlock
|
||||
| Shortcut | Actie | Context | Status |
|
||||
|----------|-------|---------|--------|
|
||||
| **Escape** | Sluit block | In overdracht block | ✅ Werkt (global) |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 MVP Scope (E5.S3)
|
||||
|
||||
Voor de MVP implementeren we:
|
||||
|
||||
### ✅ Already Working
|
||||
1. **⌘K / Ctrl+K** - Focus input
|
||||
2. **Escape** - Close block
|
||||
3. **Enter** - Submit form (native)
|
||||
4. **1-3** - FallbackPicker quick select
|
||||
|
||||
### ⏳ To Add
|
||||
1. **⌘Enter / Ctrl+Enter** in CommandInput - Quick submit
|
||||
2. **⌘Enter / Ctrl+Enter** in DagnotatieBlock - Quick save
|
||||
|
||||
### ❌ Out of Scope (Future)
|
||||
1. Arrow key navigation in search results
|
||||
2. Escape to clear input
|
||||
3. Tab for autocomplete
|
||||
4. Vim-style navigation (j/k)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test Checklist
|
||||
|
||||
### Global Shortcuts
|
||||
- [ ] **⌘K**: Press ⌘K → Input krijgt focus
|
||||
- [ ] **⌘K**: Press ⌘K from within block → Input krijgt focus
|
||||
- [ ] **Escape**: Open block → Press Escape → Block sluit
|
||||
- [ ] **Escape**: FallbackPicker open → Press Escape → Picker sluit
|
||||
|
||||
### Command Input
|
||||
- [ ] **Enter**: Typ commando → Press Enter → Commando wordt verstuurd
|
||||
- [ ] **Enter**: Leeg input → Press Enter → Niks gebeurt (validation)
|
||||
- [ ] **⌘Enter**: Typ commando → Press ⌘Enter → Commando wordt verstuurd
|
||||
- [ ] **⌘Enter**: Focus niet in input → Press ⌘Enter → Niks gebeurt
|
||||
|
||||
### FallbackPicker
|
||||
- [ ] **1**: FallbackPicker open → Press 1 → Dagnotitie opent
|
||||
- [ ] **2**: FallbackPicker open → Press 2 → Zoeken opent
|
||||
- [ ] **3**: FallbackPicker open → Press 3 → Overdracht opent
|
||||
- [ ] **Numbers**: In input field → Press 1-3 → Nummer wordt getypt (niet shortcut)
|
||||
|
||||
### DagnotatieBlock
|
||||
- [ ] **⌘Enter**: Vul form in → Press ⌘Enter → Dagnotitie wordt opgeslagen
|
||||
- [ ] **⌘Enter**: Form incomplete → Press ⌘Enter → Validation error
|
||||
- [ ] **Escape**: DagnotatieBlock open → Press Escape → Block sluit
|
||||
|
||||
### Edge Cases
|
||||
- [ ] Multiple shortcuts in rapid succession (⌘K → Escape → Enter)
|
||||
- [ ] Shortcuts werken niet tijdens isProcessing state
|
||||
- [ ] Shortcuts werken op macOS (⌘) en Windows/Linux (Ctrl)
|
||||
- [ ] Shortcuts conflicteren niet met browser defaults
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Notes
|
||||
|
||||
### Cmd vs Ctrl Detection
|
||||
```typescript
|
||||
// Use both metaKey (Cmd on Mac) and ctrlKey (Ctrl on Windows/Linux)
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
// Handle shortcut
|
||||
}
|
||||
```
|
||||
|
||||
### Preventing Default Behavior
|
||||
```typescript
|
||||
// Always preventDefault for custom shortcuts
|
||||
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault(); // Prevent browser's native ⌘K
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Shortcuts
|
||||
```typescript
|
||||
// Only handle when not in input/textarea
|
||||
if (e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement) {
|
||||
return; // Let native behavior handle it
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
- All shortcuts should have visual hints (e.g., "⌘K" label)
|
||||
- Shortcuts should work with screen readers
|
||||
- Focus management must be clear (visible focus ring)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Future Enhancements
|
||||
|
||||
### Phase 2
|
||||
- **Cmd+Shift+K** - Toggle voice input
|
||||
- **Cmd+/** - Show keyboard shortcuts help
|
||||
- **Cmd+P** - Quick patient search
|
||||
- **Cmd+N** - New dagnotitie
|
||||
- **Cmd+O** - Open overdracht
|
||||
|
||||
### Phase 3
|
||||
- Customizable shortcuts (user preferences)
|
||||
- Vim-style modal editing
|
||||
- Search results navigation (arrow keys)
|
||||
- Multi-block shortcuts (Cmd+1, Cmd+2, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- [MDN: KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)
|
||||
- [Keyboard Event Viewer](https://keycode.info/)
|
||||
- [macOS Keyboard Shortcuts Guidelines](https://developer.apple.com/design/human-interface-guidelines/keyboards)
|
||||
@@ -1,675 +0,0 @@
|
||||
# Analyse Ephemeral UI EPD - Multi-Perspectief Review
|
||||
|
||||
**Document:** Analyse PRD Ephemeral UI EPD v1.0
|
||||
**Datum:** december 2024
|
||||
**Reviewers:** Lead Developer, UX Designer, Frontend Developer, Product Owner
|
||||
|
||||
---
|
||||
|
||||
## 1. Lead Developer Perspectief
|
||||
|
||||
### 1.1 Technische Haalbaarheid
|
||||
|
||||
**Verdict:** ✅ Haalbaar met bestaande stack
|
||||
|
||||
De PRD vraagt om functionaliteit die grotendeels al bestaat in de codebase. De technische uitdaging zit niet in de bouwblokken zelf, maar in de orchestratielaag.
|
||||
|
||||
**Wat we al hebben:**
|
||||
| Component | Status | Kwaliteit |
|
||||
|-----------|--------|-----------|
|
||||
| Next.js 14 App Router | ✅ | Production-ready |
|
||||
| Supabase Auth + RLS | ✅ | Volledig geconfigureerd |
|
||||
| Claude API integratie | ✅ | Werkt voor behandelplan + overdracht |
|
||||
| Deepgram STT | ✅ | Streaming + batch transcriptie |
|
||||
| TipTap Rich Text | ✅ | Met voice-insert support |
|
||||
| shadcn/ui + Tailwind | ✅ | 20+ componenten |
|
||||
|
||||
**Wat nieuw gebouwd moet worden:**
|
||||
|
||||
```
|
||||
Prioriteit 1 (Kritiek):
|
||||
├── /api/intent/classify # AI intent classificatie
|
||||
├── /app/command-center # Nieuwe entry point
|
||||
└── stores/commandCenter.ts # State management
|
||||
|
||||
Prioriteit 2 (Bouwblok wrappers):
|
||||
├── components/building-blocks/BlockContainer.tsx
|
||||
├── components/building-blocks/PatientResolver.tsx
|
||||
└── components/building-blocks/EntityExtractor.tsx
|
||||
|
||||
Prioriteit 3 (Nieuwe blokken):
|
||||
├── ZoekenBlock.tsx # Patient search UI
|
||||
└── MetingenBlock.tsx # Vitals input form
|
||||
```
|
||||
|
||||
### 1.2 Architectuurbeslissingen
|
||||
|
||||
**State Management:**
|
||||
Aanbeveling: **Zustand** boven Context API
|
||||
- Command Center heeft complexe state (activeBlock, activePatient, transcript, recentActions)
|
||||
- Zustand is al impliciet beschikbaar via React patterns in codebase
|
||||
- Geen prop drilling, geen provider nesting
|
||||
|
||||
```typescript
|
||||
// Voorgestelde store structuur
|
||||
interface CommandCenterStore {
|
||||
// UI State
|
||||
activeBlock: BlockType | null;
|
||||
isListening: boolean;
|
||||
transcript: string;
|
||||
|
||||
// Context
|
||||
activePatient: Patient | null;
|
||||
recentActions: Action[];
|
||||
shiftInfo: ShiftInfo;
|
||||
|
||||
// Pre-fill data extracted from intent
|
||||
prefillData: Record<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
**Intent Classification:**
|
||||
Aanbeveling: **Streaming response** met confidence threshold
|
||||
|
||||
```typescript
|
||||
// Twee-staps flow
|
||||
1. Quick classification (<100ms): regex + keyword matching
|
||||
2. AI fallback (>100ms): Claude voor ambigue input
|
||||
|
||||
// Voorbeeld
|
||||
"notitie jan" → Quick match: intent=dagnotitie, patient="jan"
|
||||
"ik heb net iets besproken" → AI needed: wat? met wie?
|
||||
```
|
||||
|
||||
**API Design:**
|
||||
```
|
||||
POST /api/intent/classify
|
||||
Input: { text: string, context: { activePatient?, shift? } }
|
||||
Output: { intent, confidence, entities, clarification? }
|
||||
|
||||
GET /api/context
|
||||
Output: { user, shift, patients, pendingItems }
|
||||
```
|
||||
|
||||
### 1.3 Risico's & Mitigaties
|
||||
|
||||
| Risico | Impact | Mitigatie |
|
||||
|--------|--------|-----------|
|
||||
| Intent misclassificatie | Hoog | Fallback naar blok-selector UI |
|
||||
| Latency AI calls | Medium | Local-first matching, AI als fallback |
|
||||
| Entity extraction fout | Medium | "Bedoelde je X?" confirmation flow |
|
||||
| State sync issues | Medium | Optimistic UI + server reconciliation |
|
||||
| Voice in lawaaierige omgeving | Medium | Push-to-talk, geen continuous listening |
|
||||
|
||||
### 1.4 Technische Schuld Risico
|
||||
|
||||
**Laag risico** - We bouwen bovenop bestaande patterns:
|
||||
- Bestaande API routes blijven werken
|
||||
- Bouwblokken zijn wrappers rond bestaande componenten
|
||||
- Geen database schema wijzigingen nodig
|
||||
- Geen breaking changes voor huidige EPD flows
|
||||
|
||||
### 1.5 Aanbeveling
|
||||
|
||||
**Go/No-Go:** ✅ GO
|
||||
|
||||
Start met Command Center + Intent API + 2 blokken (Rapportage, Dagnotitie).
|
||||
Itereer op basis van intent accuracy metrics voordat we alle 8 blokken bouwen.
|
||||
|
||||
---
|
||||
|
||||
## 2. UX Designer Perspectief
|
||||
|
||||
### 2.1 Concept Evaluatie
|
||||
|
||||
**De belofte:** Van 12 klikken naar 1 zin.
|
||||
|
||||
Dit is een fundamentele paradigma-shift. Geen menu's, geen navigatie-leren, geen "waar zit dat ook alweer?" De gebruiker spreekt intentie, het systeem reageert.
|
||||
|
||||
**Sterktes van het concept:**
|
||||
|
||||
1. **Cognitive load reductie** - Zorgverleners hoeven geen mentaal model van het EPD te hebben
|
||||
2. **Context-awareness** - Systeem weet wie je bent, welke dienst, welke patiënten
|
||||
3. **Hands-free potentieel** - Voice-first past bij zorg (handschoenen, hygiëne)
|
||||
4. **Ephemeral = focus** - Alleen wat je nu nodig hebt, geen afleiding
|
||||
|
||||
**Zorgen:**
|
||||
|
||||
1. **Discoverability** - Hoe weet de gebruiker wat mogelijk is?
|
||||
2. **Error recovery** - Wat als het systeem verkeerd begrijpt?
|
||||
3. **Power users** - Willen sommigen toch sneltoetsen/directe toegang?
|
||||
4. **Trust** - "Heeft het systeem mijn notitie wel opgeslagen?"
|
||||
|
||||
### 2.2 Interaction Design Analyse
|
||||
|
||||
**Command Center Flow:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 🎤 Wat wil je doen? │ │
|
||||
│ │ ____________________________________________ │ │
|
||||
│ │ │ │
|
||||
│ │ 💡 Voorbeelden: "notitie voor Jan", "overdracht", │ │
|
||||
│ │ "mijn afspraken", "zoek Marie" │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Context: Ochtend dienst · 8 patiënten · 2 actiepunten │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ [ Actief bouwblok verschijnt hier ] │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Recent: [Jan - Notitie ✓] [Overdracht 14:00] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Kritieke UX Momenten:**
|
||||
|
||||
| Moment | Risico | Design Oplossing |
|
||||
|--------|--------|------------------|
|
||||
| Eerste gebruik | "Wat kan ik zeggen?" | Voorbeelden tonen, onboarding hints |
|
||||
| Ambigue input | Frustratie bij verkeerde interpretatie | "Bedoelde je..." met opties |
|
||||
| Mid-task switch | Context verlies | "Opslaan als concept?" modal |
|
||||
| Na voltooiing | Onzekerheid of het werkte | Duidelijke bevestiging + undo optie |
|
||||
| Geen match | Doodlopende straat | Fallback naar visuele blok-picker |
|
||||
|
||||
### 2.3 Microinteracties
|
||||
|
||||
**Voice Input Feedback:**
|
||||
```
|
||||
[Idle] → Grijze microfoon
|
||||
[Listening] → Pulserende blauwe ring + live transcript
|
||||
[Processing]→ Spinner + "Even denken..."
|
||||
[Matched] → Groene check + smooth transition naar blok
|
||||
[Unclear] → Oranje indicator + clarificatie vraag
|
||||
```
|
||||
|
||||
**Blok Transities:**
|
||||
- **In:** Slide-up met fade (200ms)
|
||||
- **Minimize:** Collapse naar badge in "Recent" row
|
||||
- **Close:** Fade-out met success toast
|
||||
|
||||
**Pre-fill Animatie:**
|
||||
Wanneer systeem velden invult op basis van intent:
|
||||
- Velden highlighten kort (geel flash)
|
||||
- Sequential fill (niet alles tegelijk)
|
||||
- "Automatisch ingevuld" label bij pre-filled velden
|
||||
|
||||
### 2.4 Accessibility Overwegingen
|
||||
|
||||
| Aspect | Vereiste | Implementatie |
|
||||
|--------|----------|---------------|
|
||||
| Keyboard-only | Moet volledig werken zonder voice | Tab navigation, Enter to submit |
|
||||
| Screen readers | Blok-wissels aangekondigd | ARIA live regions |
|
||||
| Motor impairments | Grote touch targets | Min 44x44px buttons |
|
||||
| Cognitieve load | Niet te veel tegelijk | Max 1 actief blok |
|
||||
|
||||
### 2.5 Fallback Strategie
|
||||
|
||||
**De "Noodrem":**
|
||||
Als conversational interface faalt, moet er altijd een visuele fallback zijn.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Ik begreep dat niet helemaal. Wat wil je doen? │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 📝 │ │ 👤 │ │ 📋 │ │ 🔄 │ │
|
||||
│ │Notitie │ │ Intake │ │ Plan │ │Overdracht│ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 🔍 │ │ 📅 │ │ 💊 │ │ 📊 │ │
|
||||
│ │ Zoeken │ │ Agenda │ │Medicatie│ │Metingen │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.6 Aanbeveling
|
||||
|
||||
**Go/No-Go:** ✅ GO met voorwaarden
|
||||
|
||||
1. Bouw de fallback picker EERST - dit is je vangnet
|
||||
2. Investeer in microinteracties - ze maken of breken de "magie"
|
||||
3. User testing na eerste 2 blokken - niet na alle 8
|
||||
4. Metrics verzamelen: intent accuracy, tijd-tot-taak, fallback usage
|
||||
|
||||
---
|
||||
|
||||
## 3. Frontend Developer Perspectief
|
||||
|
||||
### 3.1 Component Architectuur
|
||||
|
||||
**Huidige staat:** Goed gestructureerd, maar bouwblok-specifiek
|
||||
|
||||
We moeten bestaande componenten wrappen om ze "ephemeral" te maken. Dit vereist een abstractielaag.
|
||||
|
||||
**Voorgestelde structuur:**
|
||||
|
||||
```
|
||||
components/
|
||||
├── building-blocks/
|
||||
│ ├── core/
|
||||
│ │ ├── BlockContainer.tsx # Wrapper met header, minimize, close
|
||||
│ │ ├── BlockHeader.tsx # Titel + acties
|
||||
│ │ ├── BlockFooter.tsx # Save/Cancel buttons
|
||||
│ │ └── PrefilledField.tsx # Highlight voor auto-filled
|
||||
│ │
|
||||
│ ├── rapportage/
|
||||
│ │ ├── RapportageBlock.tsx # Wrapper rond ReportComposer
|
||||
│ │ └── RapportageBlock.types.ts
|
||||
│ │
|
||||
│ ├── dagnotitie/
|
||||
│ │ ├── DagnotitieBlock.tsx # Quick entry form
|
||||
│ │ └── DagnotitieBlock.types.ts
|
||||
│ │
|
||||
│ ├── zoeken/
|
||||
│ │ ├── ZoekenBlock.tsx # cmdk-based search
|
||||
│ │ ├── PatientCard.tsx # Search result card
|
||||
│ │ └── ZoekenBlock.types.ts
|
||||
│ │
|
||||
│ └── ... (andere blokken)
|
||||
│
|
||||
├── command-center/
|
||||
│ ├── CommandInput.tsx # Text + voice input
|
||||
│ ├── VoiceIndicator.tsx # Listening state UI
|
||||
│ ├── ContextBar.tsx # Shift info, patient count
|
||||
│ ├── RecentActions.tsx # Minimized blocks
|
||||
│ ├── BlockPicker.tsx # Fallback grid
|
||||
│ └── ClarificationDialog.tsx # "Bedoelde je...?"
|
||||
```
|
||||
|
||||
### 3.2 Hergebruik Analyse
|
||||
|
||||
**Direct herbruikbaar (copy):**
|
||||
```typescript
|
||||
// Volledig herbruikbaar
|
||||
import { RichTextEditor } from '@/components/rich-text-editor'
|
||||
import { SpeechRecorder } from '@/components/speech-recorder'
|
||||
import { Timeline } from '@/components/ui/timeline'
|
||||
import { Command } from '@/components/ui/command' // voor zoeken
|
||||
|
||||
// Bestaande forms met minimale aanpassing
|
||||
ReportComposer → RapportageBlock (wrap + simplify)
|
||||
BehandelplanView → BehandelplanBlock (read-only + edit mode toggle)
|
||||
AgendaCalendar → AgendaBlock (date-filtered view)
|
||||
```
|
||||
|
||||
**Moet gerefactored worden:**
|
||||
```typescript
|
||||
// Te gekoppeld aan page-specifieke logica
|
||||
IntakeTabs → Moet ontkoppeld van [intakeId] routing
|
||||
VitalsBlock → Alleen display, geen input form
|
||||
PatientList → Moet naar PatientCard + search results
|
||||
```
|
||||
|
||||
**Nieuw te bouwen:**
|
||||
```typescript
|
||||
// Helemaal nieuw
|
||||
CommandInput.tsx // ~150 lines
|
||||
BlockContainer.tsx // ~100 lines
|
||||
ZoekenBlock.tsx // ~200 lines
|
||||
MetingenBlock.tsx // ~150 lines
|
||||
DagnotitieBlock.tsx // ~120 lines (simplified from ReportComposer)
|
||||
ClarificationDialog.tsx // ~80 lines
|
||||
```
|
||||
|
||||
### 3.3 State Management Implementatie
|
||||
|
||||
**Keuze: Zustand**
|
||||
|
||||
```typescript
|
||||
// stores/command-center-store.ts
|
||||
import { create } from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
interface CommandCenterState {
|
||||
// Block state
|
||||
activeBlock: BlockType | null
|
||||
blockData: Record<string, unknown>
|
||||
minimizedBlocks: MinimizedBlock[]
|
||||
|
||||
// Context
|
||||
activePatient: Patient | null
|
||||
shiftInfo: ShiftInfo | null
|
||||
|
||||
// Voice
|
||||
isListening: boolean
|
||||
transcript: string
|
||||
interimTranscript: string
|
||||
|
||||
// Recent
|
||||
recentActions: Action[]
|
||||
|
||||
// Actions
|
||||
openBlock: (type: BlockType, prefill?: Record<string, unknown>) => void
|
||||
closeBlock: () => void
|
||||
minimizeBlock: () => void
|
||||
setActivePatient: (patient: Patient | null) => void
|
||||
setTranscript: (text: string) => void
|
||||
addRecentAction: (action: Action) => void
|
||||
}
|
||||
|
||||
export const useCommandCenter = create<CommandCenterState>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
// ... implementation
|
||||
}),
|
||||
{ name: 'command-center' }
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### 3.4 Intent Handling Flow
|
||||
|
||||
```typescript
|
||||
// hooks/use-intent.ts
|
||||
export function useIntent() {
|
||||
const { openBlock, setActivePatient, activePatient } = useCommandCenter()
|
||||
|
||||
const processInput = async (text: string) => {
|
||||
// 1. Quick local match
|
||||
const quickMatch = quickClassify(text)
|
||||
if (quickMatch.confidence > 0.9) {
|
||||
return handleIntent(quickMatch)
|
||||
}
|
||||
|
||||
// 2. AI classification
|
||||
const result = await fetch('/api/intent/classify', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
context: { activePatient }
|
||||
})
|
||||
}).then(r => r.json())
|
||||
|
||||
// 3. Handle result
|
||||
if (result.clarification_needed) {
|
||||
return { type: 'clarify', question: result.clarification_question }
|
||||
}
|
||||
|
||||
if (result.entities.patient_name && !activePatient) {
|
||||
const patient = await resolvePatient(result.entities.patient_name)
|
||||
if (patient.length > 1) {
|
||||
return { type: 'select_patient', options: patient }
|
||||
}
|
||||
setActivePatient(patient[0])
|
||||
}
|
||||
|
||||
openBlock(result.intent, result.entities)
|
||||
return { type: 'success', block: result.intent }
|
||||
}
|
||||
|
||||
return { processInput }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Performance Optimalisatie
|
||||
|
||||
**Bundle splitting:**
|
||||
```typescript
|
||||
// Lazy load blokken
|
||||
const RapportageBlock = dynamic(
|
||||
() => import('@/components/building-blocks/rapportage/RapportageBlock'),
|
||||
{ loading: () => <BlockSkeleton /> }
|
||||
)
|
||||
|
||||
const BehandelplanBlock = dynamic(
|
||||
() => import('@/components/building-blocks/behandelplan/BehandelplanBlock'),
|
||||
{ loading: () => <BlockSkeleton /> }
|
||||
)
|
||||
```
|
||||
|
||||
**Prefetching:**
|
||||
```typescript
|
||||
// Prefetch meest gebruikte blokken bij mount
|
||||
useEffect(() => {
|
||||
import('@/components/building-blocks/rapportage/RapportageBlock')
|
||||
import('@/components/building-blocks/dagnotitie/DagnotitieBlock')
|
||||
}, [])
|
||||
```
|
||||
|
||||
**Voice optimization:**
|
||||
```typescript
|
||||
// Reuse Deepgram connection
|
||||
const deepgramRef = useRef<DeepgramConnection | null>(null)
|
||||
|
||||
// Start listening immediately on mic click (no API call delay)
|
||||
// Token already fetched at mount
|
||||
```
|
||||
|
||||
### 3.6 Testing Strategy
|
||||
|
||||
```typescript
|
||||
// Kritieke test scenarios
|
||||
describe('CommandCenter', () => {
|
||||
it('opens RapportageBlock for "notitie voor jan"')
|
||||
it('asks clarification for ambiguous input')
|
||||
it('pre-fills patient when mentioned by name')
|
||||
it('falls back to BlockPicker on unknown intent')
|
||||
it('minimizes block and shows in recent')
|
||||
it('restores minimized block on click')
|
||||
})
|
||||
|
||||
describe('Intent Classification', () => {
|
||||
it('handles Dutch medical vocabulary')
|
||||
it('extracts patient name from natural speech')
|
||||
it('returns low confidence for gibberish')
|
||||
})
|
||||
```
|
||||
|
||||
### 3.7 Aanbeveling
|
||||
|
||||
**Go/No-Go:** ✅ GO
|
||||
|
||||
**Geschatte effort:**
|
||||
|
||||
| Component | Uren | Complexiteit |
|
||||
|-----------|------|--------------|
|
||||
| CommandCenter layout | 4h | Low |
|
||||
| CommandInput + Voice | 6h | Medium |
|
||||
| BlockContainer abstraction | 4h | Medium |
|
||||
| RapportageBlock wrapper | 3h | Low |
|
||||
| DagnotitieBlock (new) | 5h | Medium |
|
||||
| ZoekenBlock (new) | 6h | Medium |
|
||||
| Intent API route | 4h | Medium |
|
||||
| Zustand store | 3h | Low |
|
||||
| Animations/transitions | 4h | Low |
|
||||
| **Totaal MVP** | **~40h** | - |
|
||||
|
||||
---
|
||||
|
||||
## 4. Product Owner Perspectief
|
||||
|
||||
### 4.1 Business Case Analyse
|
||||
|
||||
**Doelstelling:** Demo-ready voor Nedap gesprek (7 jan)
|
||||
|
||||
Dit is een **showcase project** met twee doelen:
|
||||
1. Thought leadership positionering
|
||||
2. Concrete gespreksstarter voor enterprise partnerships
|
||||
|
||||
**ROI Potentieel:**
|
||||
|
||||
| Metric | Traditioneel EPD | Ephemeral UI | Impact |
|
||||
|--------|------------------|--------------|--------|
|
||||
| Tijd per rapportage | 3-5 min | <1 min | 70% reductie |
|
||||
| Klikken per actie | 8-15 | 1-3 | 80% reductie |
|
||||
| Training tijd | 2-4 dagen | 1 uur | 90% reductie |
|
||||
| Error rate (verkeerde scherm) | 15% | <5% | 70% reductie |
|
||||
|
||||
### 4.2 Scope Prioritering
|
||||
|
||||
**Must Have (Demo MVP):**
|
||||
- [ ] Command Center met text input
|
||||
- [ ] Voice input (Deepgram werkt al)
|
||||
- [ ] 2 werkende blokken: Rapportage + Dagnotitie
|
||||
- [ ] Intent classification (happy path)
|
||||
- [ ] Pre-fill van patient naam
|
||||
- [ ] Visuele fallback (blok picker)
|
||||
|
||||
**Should Have (Demo Enhanced):**
|
||||
- [ ] Overdracht blok met AI samenvatting
|
||||
- [ ] Zoeken blok
|
||||
- [ ] Recent actions tracking
|
||||
- [ ] Context bar (dienst info)
|
||||
- [ ] Animaties en polish
|
||||
|
||||
**Could Have (Post-Demo):**
|
||||
- [ ] Behandelplan blok
|
||||
- [ ] Agenda blok
|
||||
- [ ] Metingen blok
|
||||
- [ ] Intake blok
|
||||
- [ ] Multi-patient context switching
|
||||
|
||||
**Won't Have (v1):**
|
||||
- Offline mode
|
||||
- Mobile native app
|
||||
- Multi-user realtime
|
||||
- FHIR integratie
|
||||
- Full ambient listening
|
||||
|
||||
### 4.3 Risico Assessment
|
||||
|
||||
| Risico | Waarschijnlijkheid | Impact | Mitigatie |
|
||||
|--------|-------------------|--------|-----------|
|
||||
| Demo niet klaar 7 jan | Medium | Hoog | Focus op 2 blokken, polish later |
|
||||
| Intent accuracy <85% | Medium | Hoog | Fallback UI prominent aanwezig |
|
||||
| Voice werkt niet live | Laag | Medium | Tekst input als backup |
|
||||
| Nedap niet geïnteresseerd | Medium | Medium | Parallel outreach naar andere partijen |
|
||||
| "Speeltje" perceptie | Medium | Medium | Focus op tijdsbesparing metrics |
|
||||
|
||||
### 4.4 Stakeholder Waarde
|
||||
|
||||
**Voor Zorgverleners:**
|
||||
- Minder administratie, meer tijd voor zorg
|
||||
- Geen menu-navigatie stress
|
||||
- Voice input tijdens handeling
|
||||
|
||||
**Voor Zorgorganisaties:**
|
||||
- Lagere training kosten
|
||||
- Hogere EPD adoptie
|
||||
- Minder documentatie-fouten
|
||||
|
||||
**Voor IT/Beheer:**
|
||||
- Moderne tech stack (Next.js, Supabase)
|
||||
- AI-first architectuur
|
||||
- Schaalbaar en maintainable
|
||||
|
||||
### 4.5 Demo Scenario's
|
||||
|
||||
**Scenario 1: De Drukke Ochtend (2 min)**
|
||||
```
|
||||
Verpleegkundige start dienst → Command Center opent
|
||||
"Mijn patiënten vandaag" → Overzicht met prioriteiten
|
||||
"Notitie voor Jan de Vries: medicatie uitgereikt, geen bijzonderheden"
|
||||
→ Dagnotitie pre-filled, 1-click save
|
||||
```
|
||||
|
||||
**Scenario 2: Na een Gesprek (1 min)**
|
||||
```
|
||||
Behandelaar na sessie → Voice inspreken
|
||||
"Ik heb net een gesprek gehad met mevrouw Jansen over haar angstklachten"
|
||||
→ Rapportage blok opent, patient pre-filled
|
||||
→ AI structureert de dictatie
|
||||
→ Save
|
||||
```
|
||||
|
||||
**Scenario 3: Overdracht (1 min)**
|
||||
```
|
||||
Dienst eindigt → "Overdracht"
|
||||
→ AI genereert samenvatting van de dag
|
||||
→ Aandachtspunten gemarkeerd
|
||||
→ Klaar voor collega
|
||||
```
|
||||
|
||||
### 4.6 Success Metrics
|
||||
|
||||
**Demo Success:**
|
||||
- [ ] 3 scenario's foutloos uitvoeren
|
||||
- [ ] <5 seconden van input tot blok open
|
||||
- [ ] "Wow" reactie van stakeholders
|
||||
- [ ] Concrete vervolgafspraak
|
||||
|
||||
**Product Success (post-launch):**
|
||||
- Intent accuracy >85%
|
||||
- Fallback usage <20%
|
||||
- Tijd-tot-taak 50% lager dan traditioneel
|
||||
- User satisfaction >4/5
|
||||
|
||||
### 4.7 Go-to-Market
|
||||
|
||||
**Fase 1: Internal Demo (Week 1-2)**
|
||||
- Bouwen MVP
|
||||
- Interne testing
|
||||
|
||||
**Fase 2: Stakeholder Demo (Week 3)**
|
||||
- Nedap presentatie (7 jan)
|
||||
- LinkedIn content (4 posts gepland)
|
||||
|
||||
**Fase 3: Pilot (Q1 2025)**
|
||||
- 1 zorginstelling
|
||||
- Real user feedback
|
||||
- Iteratie op intent accuracy
|
||||
|
||||
**Fase 4: Scale (Q2 2025)**
|
||||
- Meerdere instellingen
|
||||
- Enterprise features
|
||||
- Mogelijke partnership/overname gesprekken
|
||||
|
||||
### 4.8 Aanbeveling
|
||||
|
||||
**Go/No-Go:** ✅ GO
|
||||
|
||||
**Voorwaarden:**
|
||||
1. Scope beperken tot 2-3 blokken voor demo
|
||||
2. Fallback UI is verplicht (geen "het werkt alleen met AI")
|
||||
3. Realistische demo verwachtingen (happy path)
|
||||
4. LinkedIn content parallel voorbereiden
|
||||
|
||||
---
|
||||
|
||||
## 5. Gezamenlijke Conclusie
|
||||
|
||||
### Consensus: ✅ GO
|
||||
|
||||
Alle vier de perspectieven zijn positief, met de volgende gedeelde voorwaarden:
|
||||
|
||||
**Kritieke Succesfactoren:**
|
||||
1. **Fallback first** - Bouw de visuele blok-picker voordat je intent bouwt
|
||||
2. **Scope discipline** - 2 blokken voor demo, niet 8
|
||||
3. **Intent accuracy** - Meet en optimaliseer continue
|
||||
4. **User testing vroeg** - Niet wachten tot alles "af" is
|
||||
|
||||
**Gedeelde Risico's:**
|
||||
- Intent misclassificatie (mitigatie: fallback UI)
|
||||
- Demo deadline druk (mitigatie: scope beperken)
|
||||
- "Speeltje" perceptie (mitigatie: focus op metrics)
|
||||
|
||||
**Next Steps:**
|
||||
1. [ ] Command Center basis layout (Frontend)
|
||||
2. [ ] Intent classification API (Lead Dev)
|
||||
3. [ ] UX mockups voor microinteracties (UX)
|
||||
4. [ ] Demo scenario scripts (PO)
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Decision Log
|
||||
|
||||
| Beslissing | Optie Gekozen | Rationale |
|
||||
|------------|---------------|-----------|
|
||||
| State management | Zustand | Lightweight, geen provider nesting |
|
||||
| Intent approach | Local + AI fallback | Snelheid + accuracy balans |
|
||||
| Voice library | Deepgram (bestaand) | Al geïntegreerd, werkt goed |
|
||||
| Blok architectuur | Wrapper pattern | Maximaal hergebruik bestaande code |
|
||||
| Demo scope | 2-3 blokken | Realistische deadline |
|
||||
|
||||
---
|
||||
|
||||
*Document gegenereerd op basis van PRD analyse en codebase review.*
|
||||
@@ -1,217 +0,0 @@
|
||||
> denk even mee als innovatie-expert en visonair. We hebben met moderne ai-tooling (jij, claude) een epd vrij traditioneel epd gebouwd. maar hoe zou een epd er over 5
|
||||
jaar uit zien?
|
||||
|
||||
● Goede vraag. Laat me meedenken over waar EPD's naartoe gaan.
|
||||
|
||||
Het huidige paradigma: formulieren invullen
|
||||
|
||||
Wat we nu gebouwd hebben (en wat 95% van de EPD's doet):
|
||||
- Mens vult data in → systeem slaat op → mens leest later terug
|
||||
- Gestructureerde formulieren, dropdown menus, verplichte velden
|
||||
- De zorgverlener werkt voor het systeem
|
||||
|
||||
Het paradigma over 5 jaar: ambient intelligence
|
||||
|
||||
1. Van input naar capture
|
||||
|
||||
Geen formulieren meer. Het systeem luistert mee tijdens zorgmomenten:
|
||||
- Gesprekken met patiënten worden real-time getranscribeerd
|
||||
- Computer vision herkent handelingen (ADL, medicatie toediening)
|
||||
- Wearables van patiënten streamen continuous vitals
|
||||
- De AI stelt: "Ik zag dat je mevrouw Jansen hielp met wassen en zij klaagde over pijn in haar heup - klopt dat?"
|
||||
|
||||
2. Van dossier naar co-piloot
|
||||
|
||||
Het EPD is niet meer een archief maar een assistent:
|
||||
- "Meneer de Vries heeft 3 nachten slecht geslapen, zijn medicatie is vorige week aangepast, en zijn dochter belt morgen. Wil je dit bespreken in de overdracht?"
|
||||
- Proactieve alerts: "Dit gedragspatroon lijkt op wat we 6 maanden geleden zagen voor de crisis"
|
||||
- Real-time beslissingsondersteuning tijdens het zorgmoment, niet achteraf
|
||||
|
||||
3. Van silos naar netwerk
|
||||
|
||||
- Alle zorgverleners (huisarts, specialist, thuiszorg, apotheek) zien dezelfde patiënt
|
||||
- AI vertaalt tussen domeinen: "De cardioloog schreef X, dat betekent voor jullie verpleegkundige zorg: Y"
|
||||
- Patiënt heeft eigen toegang en kan toevoegen aan hun dossier
|
||||
|
||||
4. Van reactief naar predictief
|
||||
|
||||
- Machine learning op populatieniveau voorspelt: "Patiënten met dit profiel hebben 40% kans op val binnen 2 weken"
|
||||
- Preventieve interventies worden voorgesteld vóór het incident
|
||||
- Resource planning: "Volgende week verwachten we 3 extra high-care momenten"
|
||||
|
||||
5. Van documentatie naar narratief
|
||||
|
||||
- AI genereert leesbare verhalen in plaats van bullet points
|
||||
- Automatische samenvatting op het juiste niveau (arts krijgt andere view dan familie)
|
||||
- Voice-first interface: "Wat is er gebeurd met mevrouw Jansen deze week?"
|
||||
|
||||
Concrete features die we nu al zouden kunnen bouwen
|
||||
|
||||
| Nu haalbaar | 2-3 jaar | 5 jaar |
|
||||
|--------------------------|--------------------------|-----------------------------|
|
||||
| Voice-to-text rapportage | Ambient transcriptie | Volledig hands-free capture |
|
||||
| AI-samenvattingen | Proactieve alerts | Predictieve interventies |
|
||||
| Gestructureerde data | Cross-systeem integratie | Unified patient graph |
|
||||
| Dashboard views | Conversational interface | AR/spatial computing |
|
||||
|
||||
De fundamentele shift
|
||||
|
||||
Nu: De zorgverlener is een data-entry clerk die ook zorg levert
|
||||
Straks: De zorgverlener levert zorg, het systeem observeert en ondersteunt
|
||||
|
||||
---
|
||||
Welke richting spreekt je aan? Ik kan dieper ingaan op een specifiek aspect, of we kunnen brainstormen over wat we nu al kunnen toevoegen aan dit prototype dat richting die visie beweegt.
|
||||
|
||||
> Hoe ziet dat er dan ongeveer uit? Wat voor forntend heb je dan?
|
||||
|
||||
● Goede vraag. Laat me het concreet maken.
|
||||
|
||||
Het huidige model: "Cockpit met 100 knoppen"
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [Menu] [Patiënten] [Agenda] [Rapportage] [Medicatie] [...] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ┌──────────┐ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Patient │ │ Formulier │ │
|
||||
│ │ Lijst │ │ ┌─────────────────────────────────────┐ │ │
|
||||
│ │ │ │ │ Categorie: [Dropdown ▼] │ │ │
|
||||
│ │ > Jansen │ │ │ Datum: [__/__/____] │ │ │
|
||||
│ │ Vries │ │ │ Tijd: [__:__] │ │ │
|
||||
│ │ Berg │ │ │ Beschrijving: │ │ │
|
||||
│ │ │ │ │ ┌─────────────────────────────────┐ │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ └─────────────────────────────────┘ │ │ │
|
||||
│ │ │ │ │ [Opslaan] [Annuleren] │ │ │
|
||||
│ └──────────┘ └─────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
De zorgverlener moet: navigeren → zoeken → klikken → typen → valideren → opslaan
|
||||
|
||||
---
|
||||
Het toekomstige model: "Intelligente gesprekspartner"
|
||||
|
||||
Primaire interface: Conversationeel + Contextueel
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ Goedemiddag Marieke. Je hebt dienst op afdeling 2. │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 🔴 Mevr. Jansen (K204) - onrustige nacht, 2x geval- │ │
|
||||
│ │ len, dochter belt om 14:00 │ │
|
||||
│ │ │ │
|
||||
│ │ 🟡 Dhr. de Vries (K208) - nieuwe medicatie sinds │ │
|
||||
│ │ gisteren, monitor bijwerkingen │ │
|
||||
│ │ │ │
|
||||
│ │ 🟢 6 andere patiënten - geen bijzonderheden │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 🎤 "Ik kom net bij mevrouw Jansen, ze is wat │ │
|
||||
│ │ rustiger nu maar klaagt over hoofdpijn" │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 💬 Genoteerd. Gezien de valincidenten vannacht en │ │
|
||||
│ │ de hoofdpijn - wil je dat ik de arts informeer? │ │
|
||||
│ │ │ │
|
||||
│ │ [Ja, informeer arts] [Nee, ik monitor zelf] │ │
|
||||
│ │ │ │
|
||||
│ │ 📋 Ik heb alvast toegevoegd: │ │
|
||||
│ │ • Observatie: rustiger, hoofdpijnklacht │ │
|
||||
│ │ • Link naar valincidenten vannacht │ │
|
||||
│ │ • Reminder: bel dochter 14:00 │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
---
|
||||
De shift in UI-paradigma's
|
||||
|
||||
| Aspect | Nu | Straks |
|
||||
|-----------|---------------------------|-------------------------------------------|
|
||||
| Navigatie | Menu's, tabs, breadcrumbs | Context-aware, AI bepaalt wat relevant is |
|
||||
| Input | Formulieren, dropdowns | Voice-first, bevestig/corrigeer |
|
||||
| Schermen | 50+ verschillende views | 2-3 adaptieve views |
|
||||
| Zoeken | Filters, queries | "Wat wil je weten?" |
|
||||
| Actie | Klik-klik-klik-opslaan | Conversatie → bevestiging |
|
||||
|
||||
---
|
||||
Drie hoofdmodi
|
||||
|
||||
1. Ambient modus (tijdens zorgverlening)
|
||||
┌──────────────────────────────┐
|
||||
│ 🎤 Luistert... │
|
||||
│ │
|
||||
│ Mevr. Jansen - K204 │
|
||||
│ ──────────────────────── │
|
||||
│ "...doet pijn als ik │
|
||||
│ zo beweeg..." │
|
||||
│ │
|
||||
│ 📝 Concept: │
|
||||
│ • Pijnklacht bij beweging │
|
||||
│ • Locatie: [vul aan] │
|
||||
│ │
|
||||
│ [Pauzeer] [Corrigeer] │
|
||||
└──────────────────────────────┘
|
||||
Klein, onopvallend, draait op telefoon/tablet/smart display in de kamer.
|
||||
|
||||
2. Overdracht modus (briefing)
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ OVERDRACHT 15:00 │
|
||||
│ │
|
||||
│ "Drie patiënten hebben aandacht nodig deze dienst. │
|
||||
│ Mevrouw Jansen heeft een onrustige nacht gehad met │
|
||||
│ twee valincidenten. Ze is nu rustiger maar klaagt │
|
||||
│ over hoofdpijn. Haar dochter belt om 14:00..." │
|
||||
│ │
|
||||
│ ▶️ 🔊 ●●●○○○○○○○ │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Jansen │ │ Vries │ │ Berg │ │
|
||||
│ │ 🔴 Hoog │ │ 🟡 Mid │ │ 🟢 Laag │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ "Vertel me meer over mevrouw Jansen" │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
Voice-navigatie, visuele ondersteuning, drill-down on demand.
|
||||
|
||||
3. Deep-dive modus (wanneer je écht moet graven)
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🔍 "Toon me alle valincidenten van Jansen afgelopen maand" │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 📊 Analyse: 4 incidenten in 30 dagen (verhoogd risico) │
|
||||
│ │
|
||||
│ Tijdlijn: Correlaties: │
|
||||
│ ────────── ───────────── │
|
||||
│ 12 dec (nacht) → Na slaapmed aanpassing │
|
||||
│ 18 dec (nacht) → Na slaapmed aanpassing │
|
||||
│ 18 dec (nacht) → Na slaapmed aanpassing │
|
||||
│ 8 dec (ochtend) → Lage bloeddruk gemeten │
|
||||
│ │
|
||||
│ 💡 Suggestie: Medicatiereview aanvragen? De incidenten │
|
||||
│ correleren met de slaapmed aanpassing van 10 dec. │
|
||||
│ │
|
||||
│ [Vraag medicatiereview aan] [Meer context] [Exporteer] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
Nog steeds conversationeel, maar met rijke visualisatie.
|
||||
|
||||
---
|
||||
Technisch: wat verandert er?
|
||||
|
||||
| Component | Nu | Straks |
|
||||
|--------------------|--------------------|-------------------------------------|
|
||||
| Frontend framework | React + forms | React + conversational UI + voice |
|
||||
| State | Form state, CRUD | Conversation context, AI memory |
|
||||
| API | REST endpoints | Streaming, real-time, WebSocket |
|
||||
| Database | Relational queries | Vector search + semantic queries |
|
||||
| Auth | Login + RLS | Continuous auth (voice, biometrics) |
|
||||
|
||||
---
|
||||
Wat kunnen we nu bouwen als proof-of-concept?
|
||||
|
||||
1. Conversational rapportage - voice input → AI structureert → bevestig
|
||||
2. Smart overdracht - niet alleen samenvatten, maar prioriteren en adviseren
|
||||
3. Vraag-antwoord interface - "Wat is er gebeurd met Jansen deze week?"
|
||||
@@ -1,512 +0,0 @@
|
||||
# MVP & Prioritering Ephemeral UI EPD
|
||||
|
||||
**Document:** MVP Scope en Implementatie Prioritering
|
||||
**Datum:** december 2024
|
||||
**Status:** Definitief
|
||||
|
||||
---
|
||||
|
||||
## 1. MVP Definitie
|
||||
|
||||
### 1.1 Eén Zin
|
||||
|
||||
> **MVP = Een Command Center waarmee je met tekst of voice een dagnotitie of rapportage maakt, met automatische patient herkenning.**
|
||||
|
||||
### 1.2 MVP Scope
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MVP SCOPE │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ IN SCOPE (Must Have) │
|
||||
│ ───────────────────── │
|
||||
│ ✓ Command Center pagina │
|
||||
│ ✓ Text input met intent herkenning │
|
||||
│ ✓ Voice input (Deepgram - bestaat al) │
|
||||
│ ✓ Dagnotitie blok │
|
||||
│ ✓ Zoeken blok │
|
||||
│ ✓ Patient pre-fill uit input │
|
||||
│ ✓ Fallback blok-picker │
|
||||
│ │
|
||||
│ SHOULD HAVE (Demo Enhanced) │
|
||||
│ ─────────────────────────── │
|
||||
│ ○ Rapportage blok │
|
||||
│ ○ Overdracht blok met AI │
|
||||
│ ○ Recent actions tracking │
|
||||
│ ○ Context bar (dienst info) │
|
||||
│ │
|
||||
│ OUT OF SCOPE (v1) │
|
||||
│ ───────────────── │
|
||||
│ ✗ Behandelplan blok │
|
||||
│ ✗ Intake blok │
|
||||
│ ✗ Agenda blok │
|
||||
│ ✗ Metingen blok │
|
||||
│ ✗ Ambient listening │
|
||||
│ ✗ Multi-user realtime │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.3 Waarom Deze Scope?
|
||||
|
||||
| Keuze | Rationale |
|
||||
|-------|-----------|
|
||||
| Dagnotitie eerst | Hoogste frequentie (20x/dag), simpelste form |
|
||||
| Zoeken als fundament | Elke actie begint met "wie" - zonder zoeken geen pre-fill |
|
||||
| Voice must-have | "Ik draag handschoenen" - zorg context vereist hands-free |
|
||||
| Fallback verplicht | Vertrouwen: nooit een doodlopende straat |
|
||||
| Geen behandelplan | Te complex, lage frequentie, bestaande UI voldoet |
|
||||
|
||||
---
|
||||
|
||||
## 2. Hergebruik Bestaande Code
|
||||
|
||||
### 2.1 Direct Herbruikbaar
|
||||
|
||||
| Component | Locatie | Hergebruik |
|
||||
|-----------|---------|------------|
|
||||
| **Speech Recorder** | `components/speech-recorder.tsx` | 100% - drop-in |
|
||||
| **Deepgram API** | `api/deepgram/transcribe/route.ts` | 100% - werkt |
|
||||
| **Toast System** | `lib/hooks/use-toast.ts` | 100% - feedback |
|
||||
| **Command (cmdk)** | `components/ui/command.tsx` | 90% - zoek basis |
|
||||
| **Dagregistratie Form** | `app/epd/dagregistratie/` | 80% - wrap als blok |
|
||||
| **Report Types** | `lib/types/report.ts` | 100% - validatie |
|
||||
| **Verpleegkundig API** | `api/reports/route.ts` | 100% - save |
|
||||
|
||||
### 2.2 Gedeeltelijk Herbruikbaar
|
||||
|
||||
| Component | Locatie | Aanpassing Nodig |
|
||||
|-----------|---------|------------------|
|
||||
| **Patient List** | `app/epd/verpleegrapportage/` | → PatientCard voor zoeken |
|
||||
| **EPD Layout** | `app/epd/layout.tsx` | → Command Center layout |
|
||||
| **Report Composer** | `app/epd/patients/[id]/rapportage/` | → RapportageBlock wrapper |
|
||||
|
||||
### 2.3 Nieuw te Bouwen
|
||||
|
||||
| Component | Geschatte Effort | Prioriteit |
|
||||
|-----------|------------------|------------|
|
||||
| `/app/command-center/page.tsx` | 4h | P1 |
|
||||
| `/api/intent/classify/route.ts` | 6h | P1 |
|
||||
| `CommandInput.tsx` | 3h | P1 |
|
||||
| `BlockContainer.tsx` | 2h | P1 |
|
||||
| `DagnotitieBlock.tsx` | 3h | P1 |
|
||||
| `ZoekenBlock.tsx` | 4h | P1 |
|
||||
| `FallbackPicker.tsx` | 2h | P1 |
|
||||
| `PatientResolver.ts` | 2h | P1 |
|
||||
| `useCommandCenter.ts` (store) | 3h | P1 |
|
||||
| **Totaal MVP** | **~29h** | - |
|
||||
|
||||
---
|
||||
|
||||
## 3. Prioritering: MoSCoW
|
||||
|
||||
### 3.1 Must Have (Release Blocker)
|
||||
|
||||
```
|
||||
M1. Command Center Layout
|
||||
└── Centrale pagina met input field
|
||||
└── Route: /command-center
|
||||
|
||||
M2. Intent Classification
|
||||
└── API endpoint dat input analyseert
|
||||
└── Returns: { intent, entities, confidence }
|
||||
|
||||
M3. Dagnotitie Blok
|
||||
└── Simpele form: patient, categorie, tekst
|
||||
└── Pre-fill support
|
||||
└── 1-click save
|
||||
|
||||
M4. Zoeken Blok
|
||||
└── Patient search met cmdk
|
||||
└── PatientCard met quick actions
|
||||
└── Set active patient
|
||||
|
||||
M5. Voice Input
|
||||
└── Deepgram integratie in Command input
|
||||
└── Live transcript display
|
||||
|
||||
M6. Fallback Picker
|
||||
└── Grid met blok icons
|
||||
└── Toont bij lage confidence of onbekende intent
|
||||
|
||||
M7. Patient Pre-fill
|
||||
└── Entity extraction uit input
|
||||
└── Patient name → ID resolver
|
||||
```
|
||||
|
||||
### 3.2 Should Have (Demo Value)
|
||||
|
||||
```
|
||||
S1. Rapportage Blok
|
||||
└── Wrapper rond bestaande ReportComposer
|
||||
└── Voice dictation in editor
|
||||
└── AI structurering knop
|
||||
|
||||
S2. Overdracht Blok
|
||||
└── AI samenvatting (API bestaat)
|
||||
└── Multi-patient view
|
||||
└── Bronverwijzingen
|
||||
|
||||
S3. Recent Actions
|
||||
└── Badge strip onder input
|
||||
└── Click to re-open
|
||||
|
||||
S4. Context Bar
|
||||
└── "Ochtend dienst | 8 patiënten"
|
||||
└── Shift awareness
|
||||
```
|
||||
|
||||
### 3.3 Could Have (Nice to Have)
|
||||
|
||||
```
|
||||
C1. Categorie Herkenning
|
||||
└── "medicatie" → Medicatie category
|
||||
└── Keyword mapping
|
||||
|
||||
C2. Animaties
|
||||
└── Block slide-in
|
||||
└── Pre-fill highlight
|
||||
└── Success celebration
|
||||
|
||||
C3. Keyboard Shortcuts
|
||||
└── Cmd+K → focus input
|
||||
└── Enter → submit
|
||||
└── Esc → close block
|
||||
|
||||
C4. Onboarding Hints
|
||||
└── "Probeer: notitie jan"
|
||||
└── First-time user guidance
|
||||
```
|
||||
|
||||
### 3.4 Won't Have (Explicit Out)
|
||||
|
||||
```
|
||||
W1. Behandelplan Blok - te complex, lage frequentie
|
||||
W2. Intake Blok - wizard is complex, 1x/maand
|
||||
W3. Agenda Blok - bestaande werkt, lage urgentie
|
||||
W4. Metingen Blok - lage waarde-perceptie
|
||||
W5. Ambient Listening - v2+ feature
|
||||
W6. Offline Mode - v2+ feature
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementatie Volgorde
|
||||
|
||||
### 4.1 Dependency Graph
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Command Center │
|
||||
│ Layout │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Voice │ │ Intent │ │ Fallback │
|
||||
│ Input │ │ API │ │ Picker │
|
||||
└────┬─────┘ └────┬─────┘ └──────────┘
|
||||
│ │
|
||||
│ ┌───────┴───────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ Zoeken │ │ Dagnotitie │
|
||||
│ Blok │ │ Blok │
|
||||
└──────┬───────┘ └──────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Pre-fill │
|
||||
│ Logic │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
### 4.2 Sprint Planning
|
||||
|
||||
#### Sprint 1: Foundation (Dag 1-3)
|
||||
|
||||
| # | Taak | Afhankelijk van | Output |
|
||||
|---|------|-----------------|--------|
|
||||
| 1.1 | Command Center page layout | - | `/app/command-center/page.tsx` |
|
||||
| 1.2 | CommandInput component | 1.1 | Text input met submit |
|
||||
| 1.3 | BlockContainer wrapper | 1.1 | Generic block frame |
|
||||
| 1.4 | Zustand store setup | - | `useCommandCenter` hook |
|
||||
| 1.5 | Voice integratie | 1.2 | Mic button in input |
|
||||
|
||||
**Deliverable:** Command Center opent, voice werkt, geen blokken nog.
|
||||
|
||||
#### Sprint 2: Intent & Zoeken (Dag 4-6)
|
||||
|
||||
| # | Taak | Afhankelijk van | Output |
|
||||
|---|------|-----------------|--------|
|
||||
| 2.1 | Intent API route | - | `/api/intent/classify` |
|
||||
| 2.2 | Entity extraction | 2.1 | Patient name uit tekst |
|
||||
| 2.3 | Patient search API | - | `/api/patients/search` |
|
||||
| 2.4 | ZoekenBlock | 1.3, 2.3 | Patient cards + select |
|
||||
| 2.5 | Fallback picker | 1.3 | Grid met blok icons |
|
||||
|
||||
**Deliverable:** "zoek jan" werkt, patient selectie mogelijk.
|
||||
|
||||
#### Sprint 3: Dagnotitie Flow (Dag 7-9)
|
||||
|
||||
| # | Taak | Afhankelijk van | Output |
|
||||
|---|------|-----------------|--------|
|
||||
| 3.1 | DagnotitieBlock | 1.3 | Quick entry form |
|
||||
| 3.2 | Pre-fill logic | 2.2, 3.1 | Patient + categorie auto |
|
||||
| 3.3 | Save flow | 3.1 | Toast + recent badge |
|
||||
| 3.4 | Intent → Block routing | 2.1, 3.1 | "notitie jan" → block |
|
||||
|
||||
**Deliverable:** "notitie voor jan: medicatie gegeven" werkt end-to-end.
|
||||
|
||||
#### Sprint 4: Polish & Demo (Dag 10-12)
|
||||
|
||||
| # | Taak | Afhankelijk van | Output |
|
||||
|---|------|-----------------|--------|
|
||||
| 4.1 | RapportageBlock | 1.3 | Wrapper rond composer |
|
||||
| 4.2 | Recent actions strip | 3.3 | Clickable badges |
|
||||
| 4.3 | Animaties | All | Smooth transitions |
|
||||
| 4.4 | Demo scenarios | All | 3 happy paths |
|
||||
| 4.5 | Bug fixes | All | Stability |
|
||||
|
||||
**Deliverable:** Demo-ready voor stakeholders.
|
||||
|
||||
---
|
||||
|
||||
## 5. Technische Beslissingen
|
||||
|
||||
### 5.1 State Management
|
||||
|
||||
**Besluit:** Zustand
|
||||
|
||||
```typescript
|
||||
// stores/command-center.ts
|
||||
interface CommandCenterState {
|
||||
// Active state
|
||||
activeBlock: 'dagnotitie' | 'zoeken' | 'rapportage' | 'overdracht' | null
|
||||
activePatient: Patient | null
|
||||
|
||||
// Input state
|
||||
inputValue: string
|
||||
isListening: boolean
|
||||
transcript: string
|
||||
|
||||
// Pre-fill data
|
||||
prefillData: {
|
||||
patientName?: string
|
||||
category?: string
|
||||
text?: string
|
||||
}
|
||||
|
||||
// History
|
||||
recentActions: Action[]
|
||||
|
||||
// Actions
|
||||
processInput: (text: string) => Promise<void>
|
||||
openBlock: (type: BlockType, prefill?: object) => void
|
||||
closeBlock: () => void
|
||||
setActivePatient: (patient: Patient) => void
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Intent Classification
|
||||
|
||||
**Besluit:** Two-tier approach
|
||||
|
||||
```typescript
|
||||
// Tier 1: Local keyword matching (instant)
|
||||
const quickMatch = (input: string) => {
|
||||
if (/notitie|dagnotitie/i.test(input)) return { intent: 'dagnotitie', confidence: 0.9 }
|
||||
if (/zoek|vind|wie is/i.test(input)) return { intent: 'zoeken', confidence: 0.9 }
|
||||
if (/overdracht|dienst/i.test(input)) return { intent: 'overdracht', confidence: 0.9 }
|
||||
if (/rapport|gesprek/i.test(input)) return { intent: 'rapportage', confidence: 0.85 }
|
||||
return null
|
||||
}
|
||||
|
||||
// Tier 2: Claude API (fallback)
|
||||
const aiClassify = async (input: string) => {
|
||||
// Only called if quickMatch returns null or low confidence
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Patient Resolution
|
||||
|
||||
**Besluit:** Fuzzy search met Supabase
|
||||
|
||||
```typescript
|
||||
// Fuzzy match op naam
|
||||
const searchPatients = async (query: string) => {
|
||||
const { data } = await supabase
|
||||
.from('patients')
|
||||
.select('id, name, birth_date')
|
||||
.ilike('name', `%${query}%`)
|
||||
.limit(5)
|
||||
return data
|
||||
}
|
||||
|
||||
// Als 1 match → auto-select
|
||||
// Als >1 match → toon ZoekenBlock
|
||||
// Als 0 matches → "Geen patient gevonden"
|
||||
```
|
||||
|
||||
### 5.4 Block Architecture
|
||||
|
||||
**Besluit:** Wrapper pattern
|
||||
|
||||
```typescript
|
||||
// Elk blok krijgt dezelfde interface
|
||||
interface BlockProps {
|
||||
prefill?: Record<string, unknown>
|
||||
onComplete: (result: unknown) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
// BlockContainer handelt header, minimize, close
|
||||
<BlockContainer title="Dagnotitie" icon={FileText}>
|
||||
<DagnotitieBlock prefill={prefill} onComplete={handleComplete} />
|
||||
</BlockContainer>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Risico's & Mitigaties
|
||||
|
||||
| Risico | Kans | Impact | Mitigatie |
|
||||
|--------|------|--------|-----------|
|
||||
| Intent accuracy <80% | Medium | Hoog | Fallback picker altijd zichtbaar |
|
||||
| Voice niet goed in NL | Laag | Medium | Deepgram NL model + tekst fallback |
|
||||
| Pre-fill verkeerde patient | Medium | Hoog | Altijd confirmation, nooit blind save |
|
||||
| Latency AI calls | Medium | Medium | Local-first matching, AI als backup |
|
||||
| Demo deadline druk | Medium | Hoog | Scope strict houden, no feature creep |
|
||||
|
||||
---
|
||||
|
||||
## 7. Success Metrics
|
||||
|
||||
### 7.1 MVP Success (Must Hit)
|
||||
|
||||
| Metric | Target | Hoe Meten |
|
||||
|--------|--------|-----------|
|
||||
| "notitie jan" → save | <30 sec | Timestamp logs |
|
||||
| Intent accuracy | >85% | Correct block / total |
|
||||
| Voice transcription | >90% accuracy | Manual review sample |
|
||||
| Fallback usage | <25% | Picker clicks / total |
|
||||
|
||||
### 7.2 Demo Success
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| 3 scenario's foutloos | 100% |
|
||||
| Stakeholder "wow" moment | Ja |
|
||||
| Concrete vervolgafspraak | Ja |
|
||||
|
||||
---
|
||||
|
||||
## 8. Definition of Done
|
||||
|
||||
### 8.1 Per Sprint
|
||||
|
||||
- [ ] Alle taken in sprint completed
|
||||
- [ ] Geen console errors
|
||||
- [ ] Happy path werkt
|
||||
- [ ] Code reviewed
|
||||
|
||||
### 8.2 MVP Done
|
||||
|
||||
- [ ] Command Center bereikbaar op `/command-center`
|
||||
- [ ] Voice input werkt
|
||||
- [ ] "notitie voor [patient]" opent DagnotitieBlock
|
||||
- [ ] "zoek [naam]" toont PatientCards
|
||||
- [ ] Pre-fill werkt voor patient naam
|
||||
- [ ] Fallback picker werkt
|
||||
- [ ] Save + toast feedback werkt
|
||||
- [ ] Geen blocking bugs
|
||||
- [ ] 3 demo scenario's gedocumenteerd
|
||||
|
||||
---
|
||||
|
||||
## 9. Demo Scenario's
|
||||
|
||||
### Scenario 1: Snelle Dagnotitie (30 sec)
|
||||
|
||||
```
|
||||
1. Open Command Center
|
||||
2. Type of spreek: "notitie voor Jan de Vries: medicatie uitgereikt"
|
||||
3. System:
|
||||
- Herkent intent: dagnotitie
|
||||
- Herkent patient: Jan de Vries
|
||||
- Pre-fills categorie: Medicatie
|
||||
- Pre-fills tekst: "medicatie uitgereikt"
|
||||
4. User: Review → Opslaan
|
||||
5. Toast: "Notitie opgeslagen"
|
||||
6. Recent badge verschijnt
|
||||
```
|
||||
|
||||
### Scenario 2: Patient Zoeken + Notitie (45 sec)
|
||||
|
||||
```
|
||||
1. Type: "notitie marie"
|
||||
2. System: Meerdere "Marie" gevonden
|
||||
3. Toont: ZoekenBlock met 3 matches
|
||||
4. User: Selecteert "Marie van den Berg"
|
||||
5. System: Opent DagnotitieBlock met patient ingevuld
|
||||
6. User: Typt notitie → Opslaan
|
||||
```
|
||||
|
||||
### Scenario 3: Voice Flow (40 sec)
|
||||
|
||||
```
|
||||
1. Klik mic button
|
||||
2. Spreek: "Mevrouw Jansen heeft goed gegeten en haar medicatie ingenomen"
|
||||
3. System:
|
||||
- Transcribeert real-time
|
||||
- Herkent: patient = Jansen, categorie = ADL + Medicatie
|
||||
4. Toont: DagnotitieBlock met alles ingevuld
|
||||
5. User: Review → Opslaan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Appendix: File Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── command-center/
|
||||
│ ├── page.tsx # Main Command Center
|
||||
│ └── components/
|
||||
│ ├── command-input.tsx # Text + voice input
|
||||
│ ├── voice-indicator.tsx # Listening state UI
|
||||
│ ├── block-container.tsx # Generic block wrapper
|
||||
│ ├── fallback-picker.tsx # Block selection grid
|
||||
│ └── recent-actions.tsx # Recent badges strip
|
||||
|
||||
├── api/
|
||||
│ └── intent/
|
||||
│ └── classify/
|
||||
│ └── route.ts # Intent classification API
|
||||
|
||||
components/
|
||||
└── building-blocks/
|
||||
├── dagnotitie-block.tsx # Quick entry form
|
||||
├── zoeken-block.tsx # Patient search
|
||||
├── rapportage-block.tsx # Report composer wrapper
|
||||
└── overdracht-block.tsx # Handover summary
|
||||
|
||||
stores/
|
||||
└── command-center.ts # Zustand store
|
||||
|
||||
lib/
|
||||
├── intent/
|
||||
│ ├── classifier.ts # Intent classification logic
|
||||
│ ├── entity-extractor.ts # Extract patient, category
|
||||
│ └── patient-resolver.ts # Name → Patient lookup
|
||||
└── types/
|
||||
└── command-center.ts # TypeScript types
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Dit document is de single source of truth voor MVP scope en prioritering.*
|
||||
@@ -1,430 +0,0 @@
|
||||
# Onderzoeksverslag: UX/UI Patterns voor Ephemeral UI EPD
|
||||
|
||||
**Document:** UX/UI Research & Best Practices
|
||||
**Datum:** december 2024
|
||||
**Status:** Definitief
|
||||
**Onderzoeksmethode:** Desk research, marktanalyse, design pattern analyse
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
Dit onderzoek analyseert de beste UX/UI patterns voor het Ephemeral UI EPD concept. De conclusie is dat een **hybrid approach** de beste keuze is: natural language input gecombineerd met voorgedefinieerde, gestructureerde UI-bouwblokken.
|
||||
|
||||
**Kernbevindingen:**
|
||||
- Ambient Clinical Intelligence is de dominante trend (markt groeit 37% per jaar)
|
||||
- Pure conversational UI is te onvoorspelbaar voor zorg
|
||||
- Command palette pattern (cmdk) is de beste basis voor intent-input
|
||||
- Two-tier intent classification (local + AI fallback) biedt optimale snelheid
|
||||
- Fallback UI is essentieel voor vertrouwen
|
||||
|
||||
---
|
||||
|
||||
## 2. Marktanalyse: Ambient Clinical Intelligence
|
||||
|
||||
### 2.1 Marktomvang & Groei
|
||||
|
||||
| Metric | Waarde | Bron |
|
||||
|--------|--------|------|
|
||||
| Marktwaarde 2024 | $468 miljoen | Nova One Advisor |
|
||||
| Verwacht 2034 | $11,5 miljard | Nova One Advisor |
|
||||
| CAGR | 37,87% | Nova One Advisor |
|
||||
| Clinical documentation share | 18% van markt | Nova One Advisor |
|
||||
| NLP-powered agents | 33% marktaandeel | Nova One Advisor |
|
||||
|
||||
### 2.2 Belangrijkste Spelers
|
||||
|
||||
| Vendor | Product | Aanpak | Adoptie |
|
||||
|--------|---------|--------|---------|
|
||||
| **Microsoft/Nuance** | DAX Copilot | Ambient listening → full note | Stanford, Kaiser |
|
||||
| **Suki AI** | Suki Assistant | Voice commands + dictatie | 72% sneller documentatie |
|
||||
| **Abridge** | Abridge | Ambient → structured notes | Sutter Health, UPMC |
|
||||
| **DeepScribe** | DeepScribe | Specialty-focused (oncologie) | Enterprise |
|
||||
| **Ambience** | AI Scribe | Ambient + Epic integratie | Cleveland Clinic (4000+ artsen) |
|
||||
|
||||
### 2.3 Adoptie bij Grote Zorginstellingen (2024)
|
||||
|
||||
| Instelling | Tool | Schaal | Resultaat |
|
||||
|------------|------|--------|-----------|
|
||||
| Stanford Health | DAX Copilot | Volledig uitgerold | Significante tijdsbesparing |
|
||||
| Cleveland Clinic | Ambience AI Scribe | 4000+ providers | 80+ specialismen |
|
||||
| Sutter Health | Abridge | 100 clinici pilot | April 2024 start |
|
||||
| Kaiser/TPMG | Ambient AI | 10.000 artsen | 303.266 encounters in 10 weken |
|
||||
|
||||
### 2.4 Kernprobleem dat Wordt Opgelost
|
||||
|
||||
> "Artsen besteden tot **4,5 uur per dag** aan EHR data invoer"
|
||||
> — NEJM Catalyst Research
|
||||
|
||||
> "De flow van EHR systemen matcht niet met de workflow van de clinicus, wat frustratie en tijdverlies veroorzaakt"
|
||||
> — Healthcare IT News
|
||||
|
||||
---
|
||||
|
||||
## 3. UX Pattern Analyse
|
||||
|
||||
### 3.1 Ephemeral UI Concept
|
||||
|
||||
**Definitie** (Hilal Koyuncu, ex-Google designer):
|
||||
> "UI die alleen verschijnt wanneer nodig en verdwijnt na gebruik"
|
||||
|
||||
**Kenmerken:**
|
||||
| Eigenschap | Beschrijving |
|
||||
|------------|--------------|
|
||||
| On-demand | Interface materialiseert bij intentie |
|
||||
| Hyper-contextual | Gebouwd voor één gebruiker's momentane doel |
|
||||
| Transient | Vernietigd wanneer doel bereikt is |
|
||||
| Task-optimal | Elk element dient het directe doel |
|
||||
|
||||
**Voordelen:**
|
||||
- Zero learning curve
|
||||
- Infinite scalability
|
||||
- Accessibility by default (past zich aan)
|
||||
|
||||
**Risico's:**
|
||||
> "Constant veranderende UIs kunnen usability problemen veroorzaken. Gebruikers leunen op design standaarden."
|
||||
> — Roger Wong, Generative UI Analysis
|
||||
|
||||
**Mitigatie:** Gebruik voorgedefinieerde bouwblokken, geen AI-gegenereerde UI.
|
||||
|
||||
### 3.2 Command Palette Pattern
|
||||
|
||||
**Oorsprong:** Sublime Text, VS Code (IDE's voor developers)
|
||||
|
||||
**Moderne adoptie:** Superhuman, Linear, Figma, Slack, Raycast, Notion
|
||||
|
||||
**Best Practices (Superhuman):**
|
||||
|
||||
| Principe | Implementatie |
|
||||
|----------|---------------|
|
||||
| Fuzzy search | "jn dvr" matcht "Jan de Vries" |
|
||||
| Recent first | Laatst gebruikte items bovenaan |
|
||||
| Keyboard-first | Enter = bevestigen, Esc = annuleren |
|
||||
| Single source | Alles op één plek |
|
||||
| Contextual | Relevante acties per context |
|
||||
|
||||
**Wanneer gebruiken:**
|
||||
- Producten met veel features
|
||||
- Power users die efficiency waarderen
|
||||
- Keyboard-heavy workflows
|
||||
|
||||
**Libraries:**
|
||||
| Library | Kenmerken |
|
||||
|---------|-----------|
|
||||
| cmdk | Fast, unstyled, React, headless |
|
||||
| Kbar | Portable, extensible |
|
||||
| Kmenu | Animated, accessible |
|
||||
|
||||
### 3.3 Natural Language Interface (NLI) Patterns
|
||||
|
||||
**Definitie:**
|
||||
> "Een platform dat interactie tussen computer en mens mogelijk maakt via natuurlijke taal"
|
||||
|
||||
**Best Practices:**
|
||||
|
||||
| Principe | Beschrijving |
|
||||
|----------|--------------|
|
||||
| Focus op intent | Begrijp wat de gebruiker wil bereiken |
|
||||
| Handle ambiguity | Vraag verduidelijking bij onduidelijkheid |
|
||||
| Define scope | Maak duidelijk wat het systeem kan |
|
||||
| Confidence thresholds | Stel drempels in voor zekerheid |
|
||||
| Balanced training | Voorkom bias in intent herkenning |
|
||||
|
||||
**Reader vs Writer Intents:**
|
||||
- **Reader intent:** Informatie ophalen, geen actie
|
||||
- **Writer intent:** Actie uitvoeren, geen informatie tonen
|
||||
|
||||
**Belangrijke waarschuwing:**
|
||||
> "NLIs presteren het best in nauw gedefinieerde domeinen. Open-ended interactions overschrijden hun capaciteiten."
|
||||
> — Explosion AI
|
||||
|
||||
### 3.4 Voice Interface Patterns
|
||||
|
||||
**Push-to-Talk vs Ambient:**
|
||||
|
||||
| Aspect | Push-to-Talk | Ambient Listening |
|
||||
|--------|--------------|-------------------|
|
||||
| Privacy | Hoog (expliciet) | Laag (altijd aan) |
|
||||
| Nauwkeurigheid | Hoger (gericht) | Lager (ruis) |
|
||||
| Gebruiksgemak | Actie vereist | Hands-free |
|
||||
| Batterij/resources | Laag | Hoog |
|
||||
| Enterprise adoptie | Growing | Dominant (Suki, Abridge) |
|
||||
|
||||
**Aanbeveling voor MVP:** Push-to-talk (privacy, nauwkeurigheid, eenvoudiger te bouwen)
|
||||
|
||||
---
|
||||
|
||||
## 4. Healthcare UX Trends 2024-2025
|
||||
|
||||
### 4.1 Dominante Trends
|
||||
|
||||
| Trend | Beschrijving | Relevantie |
|
||||
|-------|--------------|------------|
|
||||
| **AI-powered documentation** | Ambient listening, auto-notes | Direct relevant |
|
||||
| **Reduced cognitive load** | Minder klikken, minder schermen | Kernprincipe |
|
||||
| **Voice-first input** | Hands-free tijdens zorg | Must-have |
|
||||
| **Personalization** | Interface past zich aan gebruiker aan | Nice-to-have |
|
||||
| **Mobile-responsive** | Werkt op alle devices | Vereist |
|
||||
|
||||
### 4.2 Anti-Patterns (Wat te Vermijden)
|
||||
|
||||
| Anti-pattern | Waarom slecht | Alternatief |
|
||||
|--------------|---------------|-------------|
|
||||
| Modal op modal | Cognitive overload | Max 1 laag diep |
|
||||
| "Weet je het zeker?" | Vertraagt, twijfel zaaien | Undo in plaats van confirm |
|
||||
| Verplichte velden overal | Blokkeert snelle invoer | Alleen essentiële velden |
|
||||
| Loading spinners | Wachten = frustratie | Optimistic UI |
|
||||
| Sessie verlopen | Werk kwijt | Auto-save drafts |
|
||||
|
||||
### 4.3 EHR-Specifieke UX Principes
|
||||
|
||||
**Van Arkenea EHR Interface Guide:**
|
||||
- Streamlined dashboards met customizable workflows
|
||||
- Responsive designs voor verschillende schermgroottes
|
||||
- Enhanced visual hierarchy met betere typografie
|
||||
- Eliminatie van onnodige klikken en context switching
|
||||
- Generous white space
|
||||
|
||||
---
|
||||
|
||||
## 5. Aanbevolen Architectuur
|
||||
|
||||
### 5.1 Hybrid Approach
|
||||
|
||||
De beste UX combineert:
|
||||
1. **Natural language input** (command palette + voice)
|
||||
2. **Structured UI output** (voorgedefinieerde bouwblokken)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ INPUT LAYER: Natural Language │
|
||||
│ ───────────────────────────────────────────────────── │
|
||||
│ "notitie jan: medicatie gegeven" │
|
||||
│ │
|
||||
│ ↓ Intent Classification │
|
||||
│ │
|
||||
│ OUTPUT LAYER: Structured UI │
|
||||
│ ───────────────────────────────────────────────────── │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Patient: [Jan de Vries ▼] ← pre-filled │ │
|
||||
│ │ Categorie: [Medicatie ▼] ← pre-filled │ │
|
||||
│ │ Tekst: medicatie gegeven ← pre-filled │ │
|
||||
│ │ │ │
|
||||
│ │ [Opslaan] [Aanpassen] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Waarom hybrid:**
|
||||
- Natural language voor snelle input
|
||||
- Structured UI voor verificatie en correctie
|
||||
- Best of both worlds: snelheid + controle
|
||||
|
||||
### 5.2 Two-Tier Intent Classification
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ TIER 1: Local Pattern Matching (<50ms) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Input → Intent → Entities │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ "notitie jan" → dagnotitie → patient: jan │
|
||||
│ "zoek marie" → zoeken → query: marie │
|
||||
│ "overdracht" → overdracht → (none) │
|
||||
│ "gesprek met piet" → rapportage → patient: piet │
|
||||
│ │
|
||||
│ Confidence: >0.9 → Direct uitvoeren │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
Als geen match of <0.9 confidence
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ TIER 2: AI Classification (<500ms) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Input: "ik heb net iets besproken" │
|
||||
│ │
|
||||
│ AI Output: │
|
||||
│ { │
|
||||
│ "intent": "rapportage", │
|
||||
│ "confidence": 0.75, │
|
||||
│ "clarification_needed": true, │
|
||||
│ "clarification": "Met welke patiënt?" │
|
||||
│ } │
|
||||
│ │
|
||||
│ 0.7-0.9 → "Bedoelde je...?" met opties │
|
||||
│ <0.7 → Fallback naar visuele picker │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.3 UI Layout Aanbeveling
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ COMMAND CENTER │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 🎤 Wat wil je doen? [Cmd+K] │ │
|
||||
│ │ ________________________________________________│ │
|
||||
│ │ │ │
|
||||
│ │ 💡 notitie jan · zoek marie · overdracht │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Context: Ochtend dienst · 8 patiënten · Dr. Jansen │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ACTIEF BOUWBLOK (ephemeral) │ │
|
||||
│ │ │ │
|
||||
│ │ - Voorgedefinieerde structured form │ │
|
||||
│ │ - Pre-filled velden highlighted (gele flash) │ │
|
||||
│ │ - Minimal required fields │ │
|
||||
│ │ - 1-click save │ │
|
||||
│ │ - Undo beschikbaar (geen confirm dialog) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Recent: [Jan ✓ 14:32] [Marie ✓ 14:28] [Overdracht 14:00] │
|
||||
│ │
|
||||
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
|
||||
│ │ 📝 │ │ 🔍 │ │ 📋 │ │ 🔄 │ FALLBACK │
|
||||
│ │Notitie │ │ Zoeken │ │Rapport │ │Overdr. │ PICKER │
|
||||
│ └────────┘ └────────┘ └────────┘ └────────┘ (altijd) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Differentiatie t.o.v. Markt
|
||||
|
||||
### 6.1 Vergelijking met Bestaande Oplossingen
|
||||
|
||||
| Aspect | Suki/Abridge/DAX | Ephemeral UI EPD |
|
||||
|--------|------------------|------------------|
|
||||
| Luistermodus | Ambient (altijd aan) | Push-to-talk (expliciet) |
|
||||
| Output | Full generated notes | Pre-filled structured forms |
|
||||
| Pricing | Enterprise ($$$) | Open source / self-hosted |
|
||||
| Markt | US healthcare | Nederlandse GGZ |
|
||||
| Scope | Alleen documentatie | Multi-task intent routing |
|
||||
| Verificatie | Post-hoc review | Inline confirmation |
|
||||
| EHR integratie | Epic, Cerner focus | Supabase (eigen) |
|
||||
|
||||
### 6.2 Unique Selling Points
|
||||
|
||||
1. **Intent-based routing** - Niet alleen transcriberen, maar routeren naar juiste bouwblok
|
||||
2. **Pre-fill magic** - Velden automatisch ingevuld, gebruiker bevestigt alleen
|
||||
3. **GGZ vocabulaire** - Getraind op Nederlandse GGZ terminologie
|
||||
4. **Privacy-first** - Push-to-talk, geen ambient listening
|
||||
5. **Open architectuur** - Niet locked-in bij enterprise vendor
|
||||
|
||||
---
|
||||
|
||||
## 7. Implementatie Aanbevelingen
|
||||
|
||||
### 7.1 Technology Stack
|
||||
|
||||
| Component | Aanbeveling | Rationale |
|
||||
|-----------|-------------|-----------|
|
||||
| Command input | cmdk library | Industry standard, headless, accessible |
|
||||
| Voice | Deepgram (bestaand) | Al geïntegreerd, goed Nederlands |
|
||||
| Intent (local) | Regex + keyword matching | <50ms, geen API call |
|
||||
| Intent (AI) | Claude API | Al geïntegreerd, goed Nederlands |
|
||||
| State | Zustand | Lightweight, geen provider nesting |
|
||||
| UI blocks | shadcn/ui (bestaand) | Consistent met rest van app |
|
||||
|
||||
### 7.2 Implementatie Volgorde
|
||||
|
||||
| Fase | Component | Prioriteit |
|
||||
|------|-----------|------------|
|
||||
| 1 | Command Center layout | Must have |
|
||||
| 1 | Fallback picker | Must have |
|
||||
| 1 | Local intent matching | Must have |
|
||||
| 2 | Voice input integratie | Must have |
|
||||
| 2 | Dagnotitie block | Must have |
|
||||
| 2 | Zoeken block | Must have |
|
||||
| 3 | AI intent fallback | Should have |
|
||||
| 3 | Rapportage block | Should have |
|
||||
| 4 | Overdracht block | Could have |
|
||||
| 4 | Animaties & polish | Could have |
|
||||
|
||||
### 7.3 Success Metrics
|
||||
|
||||
| Metric | Target | Meetmethode |
|
||||
|--------|--------|-------------|
|
||||
| Time-to-first-input | <2 sec | Timestamp logging |
|
||||
| Task completion (notitie) | <30 sec | Timestamp logging |
|
||||
| Intent accuracy | >90% | Correct block / total |
|
||||
| Fallback usage | <15% | Picker clicks / total |
|
||||
| Voice adoption | >40% | Voice / total inputs |
|
||||
|
||||
---
|
||||
|
||||
## 8. Bronnen
|
||||
|
||||
### Marktonderzoek
|
||||
- [AI Voice Agents in Healthcare Market](https://www.novaoneadvisor.com/report/ai-voice-agents-in-healthcare-market) - Nova One Advisor
|
||||
- [Healthcare UX/UI Design Trends 2025](https://www.excellentwebworld.com/healthcare-ux-ui-design-trends/) - Excellent WebWorld
|
||||
- [AI and Healthcare UX/UI 2024-2025](https://www.graphitedigital.com/insights/ai-impact-ux-ui-design-healthcare) - Graphite Digital
|
||||
|
||||
### Ambient Clinical Intelligence
|
||||
- [Stanford DAX Implementation](https://med.stanford.edu/news/all-news/2024/03/ambient-listening-notes.html) - Stanford Medicine
|
||||
- [Cleveland Clinic Ambient AI](https://consultqd.clevelandclinic.org/less-typing-more-talking-how-ambient-ai-is-reshaping-clinical-workflow-at-cleveland-clinic) - Cleveland Clinic
|
||||
- [Ambient AI Scribes - NEJM](https://catalyst.nejm.org/doi/full/10.1056/CAT.23.0404) - NEJM Catalyst
|
||||
- [Ambient Listening in Healthcare](https://healthtechmagazine.net/article/2024/08/ambient-listening-in-healthcare-perfcon) - HealthTech Magazine
|
||||
|
||||
### Ephemeral UI
|
||||
- [Ephemeral UI in AI-Generated Interfaces](https://isolutions.medium.com/ephemeral-ui-in-ai-generated-on-demand-interfaces-81dbc8cd4579) - iSolutions
|
||||
- [Generative UI and the Ephemeral Interface](https://rogerwong.me/2025/11/generative-ui-and-the-ephemeral-interface/) - Roger Wong
|
||||
- [Ephemeral Web-Based Applications](https://www.nngroup.com/articles/ephemeral-web-based-applications/) - Nielsen Norman Group
|
||||
- [Future of AI UI/UX: Ephemeral Interfaces](https://hertzfelt.io/blog/the-future-of-ai-ui-ux-ephemeral-interfaces-and-stateless-design-paradigms) - Hertzfelt Labs
|
||||
|
||||
### Command Palette
|
||||
- [Command Palette UX Patterns](https://medium.com/design-bootcamp/command-palette-ux-patterns-1-d6b6e68f30c1) - Alicja Suska
|
||||
- [How to Build a Remarkable Command Palette](https://blog.superhuman.com/how-to-build-a-remarkable-command-palette/) - Superhuman
|
||||
- [Command Palette UI Design](https://mobbin.com/glossary/command-palette) - Mobbin
|
||||
- [Command Palette Resources](https://www.commandpalette.org/) - commandpalette.org
|
||||
|
||||
### Natural Language Interfaces
|
||||
- [5 Principles for Good NLU Design](https://www.voiceflow.com/pathways/5-principles-for-good-natural-language-understanding-nlu-design) - Voiceflow
|
||||
- [Natural Language Interface](https://www.uxtweak.com/ux-glossary/natural-language-interface/) - UXtweak
|
||||
- [A Natural Language UI is Just a UI](https://explosion.ai/blog/natural-user-interface) - Explosion AI
|
||||
|
||||
### Healthcare AI Vendors
|
||||
- [Suki AI](https://www.suki.ai/) - Suki Assistant
|
||||
- [DeepScribe](https://www.deepscribe.ai/resources/best-ai-medical-scribes) - AI Medical Scribes
|
||||
- [Abridge](https://www.trendingaitools.com/ai-tools/abridge/) - Clinical Documentation
|
||||
|
||||
### EHR Design
|
||||
- [EHR Interface Design Guide 2026](https://arkenea.com/blog/ehr-interface/) - Arkenea
|
||||
- [EMR/EHR UI/UX Principles](https://www.purrweb.com/blog/emr-ehr-interface-design/) - Purrweb
|
||||
- [EHR Redesign for Burnout](https://www.healthcareitnews.com/news/pandemic-era-burnout-how-ehr-vendors-are-redesigning-ui-and-ux-battle-stress) - Healthcare IT News
|
||||
|
||||
---
|
||||
|
||||
## 9. Conclusie
|
||||
|
||||
De beste UX/UI voor het Ephemeral UI EPD is een **hybrid approach**:
|
||||
|
||||
1. **Input:** Command palette (cmdk) + push-to-talk voice
|
||||
2. **Processing:** Two-tier intent classification (local-first, AI-fallback)
|
||||
3. **Output:** Voorgedefinieerde, structured UI-bouwblokken met pre-fill
|
||||
4. **Safety net:** Altijd zichtbare fallback picker
|
||||
|
||||
Deze aanpak combineert:
|
||||
- De snelheid van natural language input
|
||||
- De voorspelbaarheid van structured UI
|
||||
- De betrouwbaarheid van voorgedefinieerde componenten
|
||||
- De flexibiliteit van AI-assisted intent recognition
|
||||
|
||||
Het resultaat: een interface die **voelt als magie** ("het begrijpt me!") maar **werkt als een betrouwbaar tool** (geen verrassingen, altijd een uitweg).
|
||||
|
||||
---
|
||||
|
||||
*Onderzoeksverslag gegenereerd op basis van desk research december 2024*
|
||||
@@ -1,607 +0,0 @@
|
||||
# 📄 Product Requirements Document (PRD)
|
||||
|
||||
**Product:** Ephemeral UI EPD - "Het Vergankelijke EPD"
|
||||
**Doel:** AI Speedrun Deel 2 - Demonstreren van context-aware, on-demand interfaces voor GGZ
|
||||
**Versie:** 1.0
|
||||
**Datum:** december 2024
|
||||
|
||||
---
|
||||
|
||||
## 1. Doelstelling
|
||||
|
||||
### Primair doel
|
||||
Bouwen van een **Ephemeral UI EPD**: een systeem waarin de interface niet permanent is, maar **on-demand verschijnt** op basis van wat de gebruiker wil doen. De gebruiker navigeert niet door menu's, maar spreekt of typt een intentie - en het juiste "bouwblok" verschijnt.
|
||||
|
||||
### Het contrast met traditionele EPD's
|
||||
|
||||
| Aspect | Traditioneel EPD | Ephemeral UI EPD |
|
||||
|--------|------------------|------------------|
|
||||
| Navigatie | 47 menu-items, tabbladen, submenu's | Eén input: "wat wil je doen?" |
|
||||
| Interface | Altijd alles zichtbaar | Alleen wat je nu nodig hebt |
|
||||
| Leren | 200-pagina handleiding | Zero learning curve |
|
||||
| Klikken | 12 klikken voor rapportage | 1 zin of voice command |
|
||||
| Context | Gebruiker moet context onthouden | Systeem begrijpt context |
|
||||
|
||||
### Secundair doel
|
||||
- Positioneren als thought leader op het snijvlak van AI en healthcare UX
|
||||
- Demonstreren van Vercel AI SDK Generative UI capabilities
|
||||
- Concrete showcase voor gesprekken met Nedap, Medicore, etc.
|
||||
|
||||
---
|
||||
|
||||
## 2. Wat is Ephemeral UI?
|
||||
|
||||
### Definitie
|
||||
Ephemeral UI ("vergankelijke interface") betekent dat interface-elementen:
|
||||
1. **On-demand verschijnen** - alleen wanneer nodig
|
||||
2. **Context-aware zijn** - weten wie je bent, welke patiënt, welk moment
|
||||
3. **Verdwijnen na gebruik** - geen permanente schermvervuiling
|
||||
4. **Voorgedefinieerd zijn** - geen willekeurig gegenereerde UI, maar geteste bouwblokken
|
||||
|
||||
### Onze interpretatie voor GGZ
|
||||
We bouwen **geen** volledig AI-gegenereerde interfaces (te onvoorspelbaar voor zorg).
|
||||
We bouwen **wel** een set van **voorgedefinieerde UI-bouwblokken** die:
|
||||
- Door AI worden geselecteerd op basis van gebruikersintentie
|
||||
- Automatisch worden gevuld met relevante data
|
||||
- Na voltooiing verdwijnen of minimaliseren
|
||||
|
||||
---
|
||||
|
||||
## 3. Bestaande basis (uit Speedrun 1)
|
||||
|
||||
### Wat we al hebben
|
||||
Uit het Mini-ECD en Overdracht Dashboard:
|
||||
|
||||
**Database (Supabase PostgreSQL):**
|
||||
- `clients` - patiëntgegevens
|
||||
- `intakes` - intakeverslagen
|
||||
- `problem_profiles` - DSM-light classificaties
|
||||
- `treatment_plans` - behandelplannen met versioning
|
||||
- `nursing_logs` - verpleegkundige dagregistraties
|
||||
- `appointments` - afspraken (basis)
|
||||
- `vitals` - vitale functies metingen
|
||||
- `risk_assessments` - risicotaxaties
|
||||
|
||||
**AI Functionaliteit:**
|
||||
- Samenvatten van tekst
|
||||
- Leesbaarheid verbeteren (B1)
|
||||
- Problemen extraheren uit intake
|
||||
- Behandelplan genereren
|
||||
- Overdracht samenvatting genereren
|
||||
|
||||
**Tech Stack:**
|
||||
- Next.js 15 (App Router)
|
||||
- Supabase (Auth + DB)
|
||||
- Claude API (Anthropic)
|
||||
- TailwindCSS + shadcn/ui
|
||||
- TipTap rich text editor
|
||||
- Deepgram (speech-to-text)
|
||||
|
||||
### Wat we hergebruiken
|
||||
- Volledige database schema
|
||||
- Alle bestaande API routes
|
||||
- AI prompts en functionaliteit
|
||||
- Authenticatie en autorisatie
|
||||
- Bestaande UI componenten (als bouwblokken)
|
||||
|
||||
---
|
||||
|
||||
## 4. De Bouwblokken
|
||||
|
||||
### 4.1 Overzicht van UI Bouwblokken
|
||||
|
||||
| # | Bouwblok | Trigger voorbeelden | Data input | Output |
|
||||
|---|----------|---------------------|------------|--------|
|
||||
| 1 | **Rapportage** | "gesprek gehad", "notitie maken" | patient_id, transcript/tekst | Opgeslagen rapportage |
|
||||
| 2 | **Intake** | "nieuwe cliënt", "intake" | basisgegevens, verwijzing | Intake + patient record |
|
||||
| 3 | **Behandelplan** | "behandelplan", "doelen opstellen" | patient_id, diagnose | Treatment plan |
|
||||
| 4 | **Overdracht** | "overdracht", "dienst eindigt" | patient_id[], tijdrange | Samenvatting |
|
||||
| 5 | **Dagnotitie** | "medicatie gegeven", "incident" | patient_id, categorie | Nursing log entry |
|
||||
| 6 | **Zoeken** | "zoek", "wie is", "vind" | zoekterm | Patient card(s) |
|
||||
| 7 | **Agenda** | "afspraken", "planning", "wanneer" | datum, behandelaar | Agenda view |
|
||||
| 8 | **Metingen** | "vitale functies", "meting invoeren" | patient_id, type | Vitals entry |
|
||||
|
||||
### 4.2 Bouwblok Specificaties
|
||||
|
||||
#### Bouwblok 1: Rapportage
|
||||
**Trigger patterns:**
|
||||
- "Ik heb net een gesprek gehad met [naam]"
|
||||
- "Notitie voor [naam]"
|
||||
- "Rapportage maken"
|
||||
|
||||
**UI Componenten:**
|
||||
- Patient selector (indien niet gespecificeerd)
|
||||
- Rich text editor (TipTap)
|
||||
- AI-knoppen: Samenvatten, Structureren, B1-niveau
|
||||
- Tag selector (Gesprek/Observatie/Telefonisch/etc.)
|
||||
- Save + Close actie
|
||||
|
||||
**Pre-fill logica:**
|
||||
- Als patient genoemd: voorselect patient
|
||||
- Als "gesprek": tag = Gesprek
|
||||
- Als transcript meegegeven: vul editor
|
||||
|
||||
**Na voltooiing:**
|
||||
- Opslaan in `intakes` of `nursing_logs`
|
||||
- Toon bevestiging
|
||||
- Minimaliseer naar "Laatste: [titel]" badge
|
||||
- Klaar voor volgende actie
|
||||
|
||||
---
|
||||
|
||||
#### Bouwblok 2: Intake
|
||||
**Trigger patterns:**
|
||||
- "Nieuwe cliënt"
|
||||
- "Intake starten"
|
||||
- "Aanmelding verwerken"
|
||||
|
||||
**UI Componenten:**
|
||||
- Stap 1: Basisgegevens (naam, geboortedatum)
|
||||
- Stap 2: Verwijsgegevens (optioneel)
|
||||
- Stap 3: Intake editor met AI-ondersteuning
|
||||
- Stap 4: AI-suggestie voor probleemprofiel
|
||||
|
||||
**Pre-fill logica:**
|
||||
- Als naam genoemd: vul naam in
|
||||
- Als verwijsbrief geüpload: extract data
|
||||
|
||||
**Na voltooiing:**
|
||||
- Patient aangemaakt
|
||||
- Intake opgeslagen
|
||||
- Probleemprofiel concept aangemaakt
|
||||
- Navigeer naar volgende stap of minimaliseer
|
||||
|
||||
---
|
||||
|
||||
#### Bouwblok 3: Behandelplan
|
||||
**Trigger patterns:**
|
||||
- "Behandelplan opstellen voor [naam]"
|
||||
- "Doelen formuleren"
|
||||
- "Plan maken"
|
||||
|
||||
**UI Componenten:**
|
||||
- Patient context header
|
||||
- Diagnose/probleemprofiel samenvatting
|
||||
- AI-gegenereerd plan (bewerkbaar)
|
||||
- SMART doelen editor
|
||||
- Interventies selector
|
||||
- Versie management (concept/gepubliceerd)
|
||||
|
||||
**Pre-fill logica:**
|
||||
- Laad bestaand probleemprofiel
|
||||
- Laad recente intakes voor context
|
||||
- Genereer voorstel met AI
|
||||
|
||||
**Na voltooiing:**
|
||||
- Plan opgeslagen (concept of gepubliceerd)
|
||||
- Toon bevestiging met versienummer
|
||||
|
||||
---
|
||||
|
||||
#### Bouwblok 4: Overdracht
|
||||
**Trigger patterns:**
|
||||
- "Overdracht maken"
|
||||
- "Dienst eindigt"
|
||||
- "Samenvatting voor collega"
|
||||
|
||||
**UI Componenten:**
|
||||
- Multi-patient selector (of "mijn patiënten vandaag")
|
||||
- Tijdrange selector (afgelopen X uur)
|
||||
- Per patient: collapsible summary
|
||||
- AI-samenvatting met bronverwijzingen
|
||||
- Export/print optie
|
||||
|
||||
**Pre-fill logica:**
|
||||
- Selecteer patiënten van huidige gebruiker
|
||||
- Default tijdrange: afgelopen 8 uur
|
||||
- Laad relevante nursing_logs, vitals, rapportages
|
||||
|
||||
**Na voltooiing:**
|
||||
- Overdracht gemarkeerd als compleet
|
||||
- Optioneel: doorsturen naar collega
|
||||
|
||||
---
|
||||
|
||||
#### Bouwblok 5: Dagnotitie (Quick Entry)
|
||||
**Trigger patterns:**
|
||||
- "Medicatie gegeven aan [naam]"
|
||||
- "Incident bij [naam]"
|
||||
- "[naam] heeft goed gegeten"
|
||||
|
||||
**UI Componenten:**
|
||||
- Minimale form: categorie, tekst, tijd
|
||||
- Categorieën: Medicatie, ADL, Gedrag, Incident, Observatie
|
||||
- Checkbox: "Opnemen in overdracht"
|
||||
- Quick save (enter = opslaan)
|
||||
|
||||
**Pre-fill logica:**
|
||||
- Extract patient uit zin
|
||||
- Extract categorie uit keywords
|
||||
- Tijd = nu (aanpasbaar)
|
||||
|
||||
**Na voltooiing:**
|
||||
- Direct opgeslagen
|
||||
- Toast bevestiging
|
||||
- Klaar voor volgende notitie
|
||||
|
||||
---
|
||||
|
||||
#### Bouwblok 6: Zoeken
|
||||
**Trigger patterns:**
|
||||
- "Zoek [naam]"
|
||||
- "Wie is [naam]"
|
||||
- "Toon patiënt [naam]"
|
||||
|
||||
**UI Componenten:**
|
||||
- Search results cards
|
||||
- Per card: naam, geboortedatum, laatste contact, status
|
||||
- Klik = open patient context
|
||||
- "Geen resultaten" state
|
||||
|
||||
**Na selectie:**
|
||||
- Set active patient context
|
||||
- Toon relevante vervolgacties: "Wat wil je doen met [naam]?"
|
||||
|
||||
---
|
||||
|
||||
#### Bouwblok 7: Agenda
|
||||
**Trigger patterns:**
|
||||
- "Mijn afspraken vandaag"
|
||||
- "Afspraken voor [naam]"
|
||||
- "Planning deze week"
|
||||
|
||||
**UI Componenten:**
|
||||
- Dag/week view toggle
|
||||
- Afsprakenlijst met tijden
|
||||
- Patient naam + type afspraak
|
||||
- Quick actions: afspraak toevoegen
|
||||
|
||||
**Pre-fill logica:**
|
||||
- Default: vandaag, huidige gebruiker
|
||||
- Als patient genoemd: filter op patient
|
||||
|
||||
---
|
||||
|
||||
#### Bouwblok 8: Metingen
|
||||
**Trigger patterns:**
|
||||
- "Bloeddruk invoeren"
|
||||
- "Vitale functies [naam]"
|
||||
- "Gewicht meten"
|
||||
|
||||
**UI Componenten:**
|
||||
- Meting type selector
|
||||
- Waarde input met validatie
|
||||
- Trend indicator (vs vorige meting)
|
||||
- Quick save
|
||||
|
||||
---
|
||||
|
||||
## 5. De Orchestratielaag
|
||||
|
||||
### 5.1 Command Center Interface
|
||||
|
||||
**Het centrale scherm:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ [🎤] Wat wil je doen? [user] │
|
||||
│ _____________________________________________________ │
|
||||
│ │
|
||||
│ Context: Dienst ochtend | 8 patiënten | 3 todo's │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ [Actief Bouwblok verschijnt hier] │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Recent: [Jan - Rapportage] [Overdracht 14:00] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 Intent Classification
|
||||
|
||||
**Flow:**
|
||||
```
|
||||
[User Input: tekst of voice]
|
||||
↓
|
||||
[AI Intent Classifier]
|
||||
↓
|
||||
┌────┴────┐
|
||||
↓ ↓
|
||||
[Intent] [Entities]
|
||||
↓ ↓
|
||||
[Route naar Bouwblok + Pre-fill data]
|
||||
↓
|
||||
[Render Bouwblok Component]
|
||||
```
|
||||
|
||||
**Intent Classification Prompt:**
|
||||
```
|
||||
Je bent een intent classifier voor een GGZ EPD systeem.
|
||||
|
||||
Analyseer de gebruikersinput en return JSON:
|
||||
{
|
||||
"intent": "rapportage|intake|behandelplan|overdracht|dagnotitie|zoeken|agenda|metingen|onbekend",
|
||||
"confidence": 0.0-1.0,
|
||||
"entities": {
|
||||
"patient_name": string | null,
|
||||
"date": string | null,
|
||||
"category": string | null,
|
||||
"action_type": string | null
|
||||
},
|
||||
"clarification_needed": boolean,
|
||||
"clarification_question": string | null
|
||||
}
|
||||
|
||||
Gebruikersinput: "{input}"
|
||||
```
|
||||
|
||||
### 5.3 Context Management
|
||||
|
||||
**Actieve context bevat:**
|
||||
- `current_user`: ingelogde behandelaar
|
||||
- `active_patient`: laatst geselecteerde patiënt (sticky)
|
||||
- `active_shift`: huidige dienst info
|
||||
- `recent_actions`: laatste 5 acties (voor "recent" badge)
|
||||
- `pending_items`: openstaande taken
|
||||
|
||||
**Context wordt gebruikt voor:**
|
||||
- Pre-filling van bouwblokken
|
||||
- Suggesties bij ambigue input
|
||||
- "Bedoelde je [patient X]?" bij onduidelijkheid
|
||||
|
||||
---
|
||||
|
||||
## 6. Voice Interface
|
||||
|
||||
### 6.1 Speech-to-Text Flow
|
||||
```
|
||||
[Mic Button Click]
|
||||
↓
|
||||
[Deepgram Streaming STT]
|
||||
↓
|
||||
[Live Transcription Display]
|
||||
↓
|
||||
[User confirms / auto-submit na pauze]
|
||||
↓
|
||||
[Intent Classification]
|
||||
↓
|
||||
[Bouwblok]
|
||||
```
|
||||
|
||||
### 6.2 Voice Commands
|
||||
- Wake phrase niet nodig (expliciete mic button)
|
||||
- Continu luisteren tijdens bouwblok voor dicteren
|
||||
- "Klaar" of "Opslaan" als voice command
|
||||
|
||||
---
|
||||
|
||||
## 7. Technische Architectuur
|
||||
|
||||
### 7.1 Nieuwe Routes
|
||||
|
||||
```
|
||||
/app
|
||||
/(app)
|
||||
/command-center
|
||||
/page.tsx # Hoofdscherm met input
|
||||
/components/
|
||||
CommandInput.tsx # Tekst + voice input
|
||||
BuildingBlock.tsx # Container voor actief blok
|
||||
ContextBar.tsx # Context info display
|
||||
RecentActions.tsx # Recent items
|
||||
|
||||
/api
|
||||
/intent
|
||||
/classify/route.ts # AI intent classification
|
||||
/context
|
||||
/route.ts # Get/set user context
|
||||
```
|
||||
|
||||
### 7.2 Building Blocks als Components
|
||||
|
||||
```
|
||||
/components/building-blocks/
|
||||
/rapportage/
|
||||
RapportageBlock.tsx
|
||||
RapportageBlock.types.ts
|
||||
/intake/
|
||||
IntakeBlock.tsx
|
||||
IntakeBlock.types.ts
|
||||
/behandelplan/
|
||||
BehandelplanBlock.tsx
|
||||
/overdracht/
|
||||
OverdrachtBlock.tsx
|
||||
/dagnotitie/
|
||||
DagnotitieBlock.tsx
|
||||
/zoeken/
|
||||
ZoekenBlock.tsx
|
||||
/agenda/
|
||||
AgendaBlock.tsx
|
||||
/metingen/
|
||||
MetingenBlock.tsx
|
||||
|
||||
/shared/
|
||||
BlockContainer.tsx # Wrapper met header, minimize, close
|
||||
BlockHeader.tsx
|
||||
PatientSelector.tsx
|
||||
ConfirmationToast.tsx
|
||||
```
|
||||
|
||||
### 7.3 State Management
|
||||
|
||||
```typescript
|
||||
// stores/commandCenterStore.ts
|
||||
interface CommandCenterState {
|
||||
// Active block
|
||||
activeBlock: BlockType | null;
|
||||
blockData: Record<string, any>;
|
||||
|
||||
// Context
|
||||
activePatient: Patient | null;
|
||||
recentActions: Action[];
|
||||
|
||||
// Input
|
||||
inputMode: 'text' | 'voice';
|
||||
isListening: boolean;
|
||||
transcript: string;
|
||||
|
||||
// Actions
|
||||
setActiveBlock: (block: BlockType, data?: any) => void;
|
||||
closeBlock: () => void;
|
||||
setActivePatient: (patient: Patient) => void;
|
||||
addRecentAction: (action: Action) => void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. User Flows
|
||||
|
||||
### 8.1 Happy Path: Rapportage na gesprek
|
||||
|
||||
```
|
||||
1. User opent Command Center
|
||||
2. Ziet: lege input, context "Ochtend dienst, 8 patiënten"
|
||||
3. Spreekt: "Ik heb net een gesprek gehad met Jan de Vries"
|
||||
4. Systeem:
|
||||
- Herkent intent: rapportage
|
||||
- Herkent entity: patient = "Jan de Vries"
|
||||
- Zoekt patient in DB
|
||||
- Opent Rapportage bouwblok met Jan geselecteerd
|
||||
5. User dicteert inhoud gesprek
|
||||
6. Klikt "AI Samenvatten"
|
||||
7. Review en opslaan
|
||||
8. Bouwblok minimaliseert
|
||||
9. Klaar voor volgende actie
|
||||
```
|
||||
|
||||
### 8.2 Ambigue Input
|
||||
|
||||
```
|
||||
1. User typt: "notitie"
|
||||
2. Systeem: confidence < 0.7, geen patient
|
||||
3. Toont: "Voor welke patiënt wil je een notitie maken?"
|
||||
4. User: "Jan"
|
||||
5. Systeem: meerdere "Jan" in DB
|
||||
6. Toont: selector met matches
|
||||
7. User selecteert
|
||||
8. Opent Dagnotitie bouwblok
|
||||
```
|
||||
|
||||
### 8.3 Context Switch
|
||||
|
||||
```
|
||||
1. User werkt in Rapportage voor Jan
|
||||
2. Spreekt: "Wacht, even medicatie noteren voor Piet"
|
||||
3. Systeem:
|
||||
- Herkent nieuwe intent + patient
|
||||
- Vraagt: "Rapportage voor Jan opslaan als concept?"
|
||||
4. User: "Ja"
|
||||
5. Rapportage minimized als "Jan - Concept"
|
||||
6. Dagnotitie opent voor Piet
|
||||
7. Na opslaan: "Terug naar Jan's rapportage?"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Niet in Scope (v1)
|
||||
|
||||
- Volledige voice-only mode (voice is input, niet navigatie)
|
||||
- Multi-user realtime collaboration
|
||||
- Offline mode
|
||||
- Native mobile app (wel responsive web)
|
||||
- Integratie met externe EPD's (later: FHIR)
|
||||
- Volledig geautomatiseerde workflows (user blijft in control)
|
||||
- AI-gegenereerde UI componenten (alleen voorgedefinieerde blokken)
|
||||
|
||||
---
|
||||
|
||||
## 10. Succescriteria
|
||||
|
||||
### Functioneel
|
||||
- [ ] 8 bouwblokken werkend met pre-fill
|
||||
- [ ] Intent classification >85% accuracy op test set
|
||||
- [ ] Voice input werkend met Deepgram
|
||||
- [ ] Context persistence binnen sessie
|
||||
- [ ] Gemiddeld <3 interacties tot taak compleet
|
||||
|
||||
### Performance
|
||||
- [ ] Intent classification <500ms
|
||||
- [ ] Bouwblok render <200ms
|
||||
- [ ] Voice transcription realtime (<100ms latency)
|
||||
|
||||
### UX
|
||||
- [ ] Zero training needed voor basis flows
|
||||
- [ ] "Dit voelt als magie" feedback van test users
|
||||
- [ ] 50% minder klikken vs traditionele navigatie (gemeten)
|
||||
|
||||
### Business
|
||||
- [ ] Demo-ready voor Nedap gesprek (7 jan)
|
||||
- [ ] LinkedIn content: 4 posts over ephemeral UI concept
|
||||
- [ ] Minimaal 2 concrete interesse-uitingen van GGZ partijen
|
||||
|
||||
---
|
||||
|
||||
## 11. Fasering
|
||||
|
||||
### Fase 1: Foundation (Week 1)
|
||||
- [ ] Command Center basis UI
|
||||
- [ ] Intent classification API
|
||||
- [ ] 2 bouwblokken: Rapportage + Dagnotitie
|
||||
- [ ] Context management basis
|
||||
|
||||
### Fase 2: Core Blocks (Week 2)
|
||||
- [ ] Voice input integratie
|
||||
- [ ] Bouwblokken: Zoeken, Overdracht, Behandelplan
|
||||
- [ ] Pre-fill logica uitbreiden
|
||||
- [ ] Recent actions tracking
|
||||
|
||||
### Fase 3: Polish (Week 3)
|
||||
- [ ] Bouwblokken: Intake, Agenda, Metingen
|
||||
- [ ] Animaties en transitions
|
||||
- [ ] Error handling en edge cases
|
||||
- [ ] Performance optimalisatie
|
||||
|
||||
### Fase 4: Demo Ready (Week 4)
|
||||
- [ ] End-to-end testing
|
||||
- [ ] Demo script en scenario's
|
||||
- [ ] Documentation
|
||||
- [ ] LinkedIn content
|
||||
|
||||
---
|
||||
|
||||
## 12. Risico's
|
||||
|
||||
| Risico | Impact | Mitigatie |
|
||||
|--------|--------|-----------|
|
||||
| Intent classification onnauwkeurig | Hoog | Fallback naar handmatige selectie, train op GGZ vocabulaire |
|
||||
| Voice transcription slecht in Nederlands | Middel | Deepgram NL model testen, fallback naar tekst |
|
||||
| Gebruikers missen "overzicht" | Middel | Dashboard/overzicht als alternatieve entry point |
|
||||
| Te veel edge cases | Hoog | Focus op 3-5 happy paths voor demo |
|
||||
| Performance AI calls | Middel | Caching, streaming responses |
|
||||
|
||||
---
|
||||
|
||||
## 13. Appendix: Intent Training Data (voorbeelden)
|
||||
|
||||
```json
|
||||
[
|
||||
{"input": "gesprek gehad met jan de vries", "intent": "rapportage", "entities": {"patient_name": "jan de vries"}},
|
||||
{"input": "notitie maken", "intent": "dagnotitie", "entities": {}},
|
||||
{"input": "nieuwe patient aanmelden", "intent": "intake", "entities": {}},
|
||||
{"input": "overdracht voor de avonddienst", "intent": "overdracht", "entities": {}},
|
||||
{"input": "zoek marie", "intent": "zoeken", "entities": {"patient_name": "marie"}},
|
||||
{"input": "mijn afspraken vandaag", "intent": "agenda", "entities": {"date": "today"}},
|
||||
{"input": "bloeddruk 140/90 bij piet", "intent": "metingen", "entities": {"patient_name": "piet", "measurement_type": "bloeddruk"}},
|
||||
{"input": "behandelplan opstellen", "intent": "behandelplan", "entities": {}}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wijzigingslog
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| 1.0 | dec 2024 | Initiële versie |
|
||||
@@ -1,551 +0,0 @@
|
||||
# Waarde-Analyse Ephemeral UI EPD
|
||||
|
||||
**Document:** Waar ligt de waarde en hoe implementeren?
|
||||
**Datum:** december 2024
|
||||
**Auteurs:** Product Owner, Klant (GGZ Zorginstelling), UX Designer
|
||||
|
||||
---
|
||||
|
||||
## 1. Klant Perspectief: De Zorginstelling
|
||||
|
||||
### 1.1 De Pijn van Vandaag
|
||||
|
||||
**Citaten uit het veld:**
|
||||
|
||||
> "Mijn verpleegkundigen besteden 40% van hun tijd aan administratie. Dat is tijd die niet naar de cliënt gaat."
|
||||
> — Teamleider GGZ
|
||||
|
||||
> "We hebben een EPD met 200 schermen. Niemand kent ze allemaal. Nieuwe medewerkers doen er 3 maanden over om het te leren."
|
||||
> — ICT Manager
|
||||
|
||||
> "Na een crisis-interventie moet ik 20 minuten typen. Terwijl ik eigenlijk bij de volgende cliënt moet zijn."
|
||||
> — SPV'er
|
||||
|
||||
### 1.2 Waar Ligt de Echte Waarde?
|
||||
|
||||
**Waarde = Tijd terug naar de zorg**
|
||||
|
||||
| Activiteit | Nu (minuten) | Ephemeral (minuten) | Besparing |
|
||||
|------------|--------------|---------------------|-----------|
|
||||
| Dagnotitie na ADL | 3-5 | 0.5 | **80%** |
|
||||
| Rapportage na gesprek | 8-15 | 2-3 | **75%** |
|
||||
| Overdracht maken | 20-30 | 5 | **80%** |
|
||||
| Patiënt opzoeken | 1-2 | 0.2 | **85%** |
|
||||
| Navigeren naar juiste scherm | 0.5-1 per actie | 0 | **100%** |
|
||||
|
||||
**Rekenvoorbeeld voor 1 afdeling (10 FTE):**
|
||||
- 10 medewerkers × 8 notities/dag × 3 min besparing = **4 uur/dag terug**
|
||||
- Per jaar: **1000+ uur** extra zorgcontact
|
||||
|
||||
### 1.3 Wat de Klant Wil Zien
|
||||
|
||||
**Primair:**
|
||||
1. **Snelheid** - "Ik zeg iets, het staat erin"
|
||||
2. **Betrouwbaarheid** - "Het begrijpt me, ook in GGZ-taal"
|
||||
3. **Geen gedoe** - "Ik hoef niet na te denken over het systeem"
|
||||
|
||||
**Secundair:**
|
||||
4. Overdracht die zichzelf schrijft
|
||||
5. Minder training voor nieuwe medewerkers
|
||||
6. Voice input (handsfree tijdens zorg)
|
||||
|
||||
**Niet gevraagd maar wel gewaardeerd:**
|
||||
- AI-suggesties ("wil je de arts informeren?")
|
||||
- Proactieve alerts ("let op: 3 valincidenten deze week")
|
||||
|
||||
### 1.4 Waarde Prioritering door Klant
|
||||
|
||||
```
|
||||
████████████████████████████████ HOOGSTE WAARDE
|
||||
█ 1. Snelle dagnotities (voice)
|
||||
█ 2. Rapportage na gesprek
|
||||
█ 3. Automatische overdracht
|
||||
████████████████████████████ HOGE WAARDE
|
||||
█ 4. Patiënt snel vinden
|
||||
█ 5. Context-aware (weet welke dienst)
|
||||
████████████████████ GEMIDDELDE WAARDE
|
||||
█ 6. Behandelplan assistentie
|
||||
█ 7. Agenda integratie
|
||||
████████████ LAGERE WAARDE
|
||||
█ 8. Metingen invoer
|
||||
█ 9. Intake ondersteuning
|
||||
```
|
||||
|
||||
### 1.5 Klant Conclusie
|
||||
|
||||
**Meeste waarde:** De drie "high-frequency, low-complexity" taken:
|
||||
1. **Dagnotitie** - 10-20x per dag per medewerker
|
||||
2. **Rapportage** - 3-5x per dag per behandelaar
|
||||
3. **Overdracht** - 2-3x per dag per afdeling
|
||||
|
||||
**Implementatie advies:** Begin hier. Dit is waar 80% van de tijdwinst zit.
|
||||
|
||||
---
|
||||
|
||||
## 2. Product Owner Perspectief: Waarde vs. Effort
|
||||
|
||||
### 2.1 Value/Effort Matrix
|
||||
|
||||
```
|
||||
HOGE WAARDE
|
||||
│
|
||||
┌───────────────────┼───────────────────┐
|
||||
│ │ │
|
||||
│ QUICK WINS │ BIG BETS │
|
||||
│ ──────────── │ ──────────── │
|
||||
│ • Dagnotitie │ • Intent API │
|
||||
│ • Zoeken │ • Voice flow │
|
||||
│ • Context bar │ • Overdracht AI │
|
||||
│ │ │
|
||||
LAGE ├───────────────────┼───────────────────┤ HOGE
|
||||
EFFORT │ │ EFFORT
|
||||
│ │ │
|
||||
│ FILL-INS │ MONEY PITS │
|
||||
│ ──────────── │ ──────────── │
|
||||
│ • Metingen │ • Intake wizard │
|
||||
│ • Agenda view │ • Behandelplan │
|
||||
│ • Recent badges │ • Full ambient │
|
||||
│ │ │
|
||||
└───────────────────┼───────────────────┘
|
||||
│
|
||||
LAGE WAARDE
|
||||
```
|
||||
|
||||
### 2.2 Waarde Drivers per Bouwblok
|
||||
|
||||
| Bouwblok | Frequentie | Tijdwinst | Effort | **Waarde Score** |
|
||||
|----------|------------|-----------|--------|------------------|
|
||||
| **Dagnotitie** | 20x/dag | 80% | Laag | ⭐⭐⭐⭐⭐ |
|
||||
| **Zoeken** | 15x/dag | 85% | Laag | ⭐⭐⭐⭐⭐ |
|
||||
| **Rapportage** | 5x/dag | 75% | Medium | ⭐⭐⭐⭐ |
|
||||
| **Overdracht** | 2x/dag | 80% | Medium | ⭐⭐⭐⭐ |
|
||||
| **Agenda** | 3x/dag | 50% | Laag | ⭐⭐⭐ |
|
||||
| **Metingen** | 2x/dag | 60% | Laag | ⭐⭐⭐ |
|
||||
| **Behandelplan** | 1x/week | 40% | Hoog | ⭐⭐ |
|
||||
| **Intake** | 1x/maand | 30% | Hoog | ⭐ |
|
||||
|
||||
### 2.3 MVP Definitie op Basis van Waarde
|
||||
|
||||
**MVP = Hoogste waarde, laagste effort**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MVP SCOPE │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ MUST: Command Center + Intent │
|
||||
│ ├── Text input │
|
||||
│ ├── Voice input (Deepgram bestaand) │
|
||||
│ └── Fallback blok-picker │
|
||||
│ │
|
||||
│ MUST: Dagnotitie Blok ⭐⭐⭐⭐⭐ │
|
||||
│ ├── Quick entry form │
|
||||
│ ├── Categorie pre-select uit intent │
|
||||
│ ├── Patient pre-fill │
|
||||
│ └── 1-click save │
|
||||
│ │
|
||||
│ MUST: Zoeken Blok ⭐⭐⭐⭐⭐ │
|
||||
│ ├── Patient search (cmdk) │
|
||||
│ ├── Quick actions per result │
|
||||
│ └── Set active patient │
|
||||
│ │
|
||||
│ SHOULD: Rapportage Blok ⭐⭐⭐⭐ │
|
||||
│ ├── Wrapper rond bestaande ReportComposer │
|
||||
│ ├── Voice dictation │
|
||||
│ └── AI structurering │
|
||||
│ │
|
||||
│ COULD: Overdracht Blok ⭐⭐⭐⭐ │
|
||||
│ └── AI samenvatting (API bestaat al) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.4 Wat NIET in MVP
|
||||
|
||||
| Blok | Reden voor uitstel |
|
||||
|------|-------------------|
|
||||
| Behandelplan | Laag-frequent, hoog-complex, bestaande UI voldoet |
|
||||
| Intake | Zeer laag-frequent, wizard is complex |
|
||||
| Metingen | Lage waarde-perceptie bij klant |
|
||||
| Agenda | Bestaande agenda werkt, lage urgentie |
|
||||
|
||||
### 2.5 Release Strategie
|
||||
|
||||
**Week 1-2: Foundation**
|
||||
- Command Center shell
|
||||
- Intent API (basic)
|
||||
- Dagnotitie blok
|
||||
|
||||
**Week 3: Core Value**
|
||||
- Zoeken blok
|
||||
- Rapportage blok
|
||||
- Voice refinement
|
||||
|
||||
**Week 4: Demo Polish**
|
||||
- Overdracht blok
|
||||
- Animaties
|
||||
- Demo scenario's
|
||||
|
||||
**Post-Demo: Iterate**
|
||||
- Metrics verzamelen
|
||||
- Intent accuracy verbeteren
|
||||
- Overige blokken op basis van feedback
|
||||
|
||||
---
|
||||
|
||||
## 3. UX Designer Perspectief: Waarde in de Interactie
|
||||
|
||||
### 3.1 Waar Ontstaat Waarde in de UX?
|
||||
|
||||
**Waarde = Friction verwijderen**
|
||||
|
||||
De grootste UX-waarde zit niet in features, maar in het **elimineren van stappen**:
|
||||
|
||||
```
|
||||
TRADITIONEEL EPD:
|
||||
Login → Dashboard → Menu → Submenu → Patiënten → Zoeken →
|
||||
Selecteer → Menu → Rapportage → Type selecteren → Formulier →
|
||||
Invullen → Validatie fixen → Opslaan → Bevestiging
|
||||
|
||||
= 14 stappen, 12+ klikken, 3-5 minuten
|
||||
|
||||
EPHEMERAL UI:
|
||||
Command Center → "Notitie voor Jan" → Invullen → Opslaan
|
||||
|
||||
= 4 stappen, 2 klikken, 30 seconden
|
||||
```
|
||||
|
||||
### 3.2 De Vijf Waarde-Momenten
|
||||
|
||||
**Moment 1: De Eerste Seconde**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ Goedemiddag. Wat wil je doen? │
|
||||
│ ___________________________________________________ │
|
||||
│ │
|
||||
│ 💡 "notitie jan", "overdracht", "zoek marie" │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
WAARDE: Geen keuze-stress. Geen menu's. Eén vraag.
|
||||
```
|
||||
|
||||
**Moment 2: De Herkenning**
|
||||
```
|
||||
User: "notitie voor Jan"
|
||||
|
||||
System:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 📝 Dagnotitie voor Jan de Vries │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ Categorie: [ADL ▼] Tijd: [14:32] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ _ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Opslaan] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
WAARDE: "Het begreep me!" Patient al ingevuld. Direct typen.
|
||||
```
|
||||
|
||||
**Moment 3: De Voice Flow**
|
||||
```
|
||||
User klikt 🎤
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🔴 Luistert... │
|
||||
│ │
|
||||
│ "Mevrouw heeft goed gegeten, medicatie ingenomen, │
|
||||
│ was wat onrustig vanmorgen maar nu stabiel" │
|
||||
│ │
|
||||
│ [Stop] [Opnieuw] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
WAARDE: Handen vrij. Praten is sneller dan typen.
|
||||
Natuurlijke taal, geen formulier-denken.
|
||||
```
|
||||
|
||||
**Moment 4: De Bevestiging**
|
||||
```
|
||||
Na opslaan:
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ✓ Notitie opgeslagen [Ongedaan] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Recent: [Jan - Notitie ✓ 14:32]
|
||||
|
||||
WAARDE: Zekerheid. "Het staat erin." Undo als vangnet.
|
||||
```
|
||||
|
||||
**Moment 5: De Volgende Actie**
|
||||
```
|
||||
System: Klaar. Wat nu?
|
||||
___________________________________________________
|
||||
|
||||
Suggestie: Je hebt nog 2 patiënten met openstaande notities
|
||||
|
||||
WAARDE: Flow behouden. Niet terug naar start.
|
||||
Proactief helpen.
|
||||
```
|
||||
|
||||
### 3.3 Waarde-Killers (Anti-Patterns)
|
||||
|
||||
| Anti-Pattern | Waarom Slecht | Oplossing |
|
||||
|--------------|---------------|-----------|
|
||||
| "Weet je het zeker?" dialoog | Vertraagt, twijfel zaaien | Undo in plaats van confirm |
|
||||
| Verplichte velden | Blokkeert snelle invoer | Alleen patient verplicht |
|
||||
| Modal op modal | Cognitive overload | Max 1 laag diep |
|
||||
| Laden... spinner | Wachten = frustratie | Optimistic UI |
|
||||
| "Sessie verlopen" | Werk kwijt | Auto-save drafts |
|
||||
|
||||
### 3.4 Implementatie: Waarde-First Design
|
||||
|
||||
**Principe 1: Progressive Disclosure**
|
||||
```
|
||||
Stap 1: Minimaal formulier (alleen tekst)
|
||||
↓
|
||||
Stap 2: Optionele details (categorie, tijd) - collapsed
|
||||
↓
|
||||
Stap 3: Geavanceerd (tags, links) - hidden by default
|
||||
```
|
||||
|
||||
**Principe 2: Defaults die Kloppen**
|
||||
```typescript
|
||||
// Pre-fill logica
|
||||
const defaults = {
|
||||
patient: context.activePatient || extractFromIntent(input),
|
||||
category: inferCategory(input), // "medicatie" → Medicatie
|
||||
time: new Date(), // Nu
|
||||
includeInHandover: true, // Standaard aan
|
||||
}
|
||||
```
|
||||
|
||||
**Principe 3: Keyboard-First, Voice-Enhanced**
|
||||
```
|
||||
Enter = Opslaan (als er tekst is)
|
||||
Escape = Sluiten (met draft save)
|
||||
Tab = Volgende veld
|
||||
Cmd+K = Terug naar Command input
|
||||
Spacebar = Start/stop voice (in input)
|
||||
```
|
||||
|
||||
**Principe 4: Feedback Loops**
|
||||
```
|
||||
Input → Instant echo (wat het systeem hoorde)
|
||||
Processing→ Subtle indicator (geen blocking spinner)
|
||||
Success → Toast + sound + Recent update
|
||||
Error → Inline, niet modal, met fix-suggestie
|
||||
```
|
||||
|
||||
### 3.5 Waarde Meten
|
||||
|
||||
**Metrics die waarde bewijzen:**
|
||||
|
||||
| Metric | Target | Hoe Meten |
|
||||
|--------|--------|-----------|
|
||||
| Time-to-first-input | <2 sec | Timestamp command → blok open |
|
||||
| Task completion time | <30 sec (notitie) | Blok open → save |
|
||||
| Intent accuracy | >90% | Correct blok / totaal attempts |
|
||||
| Voice adoption | >40% | Voice inputs / totaal inputs |
|
||||
| Fallback usage | <15% | Blok-picker clicks / totaal |
|
||||
| Error rate | <5% | Failed saves / totaal saves |
|
||||
|
||||
### 3.6 UX Implementatie Prioriteit
|
||||
|
||||
```
|
||||
WEEK 1: Core Interaction
|
||||
├── Command input component
|
||||
├── Voice indicator states
|
||||
├── Block container met animaties
|
||||
└── Success/error feedback
|
||||
|
||||
WEEK 2: Waarde-Blokken
|
||||
├── Dagnotitie (minimalist form)
|
||||
├── Zoeken (cmdk + patient cards)
|
||||
└── Pre-fill animations
|
||||
|
||||
WEEK 3: Polish
|
||||
├── Microinteracties
|
||||
├── Keyboard shortcuts
|
||||
├── Fallback blok-picker
|
||||
└── Onboarding hints
|
||||
|
||||
WEEK 4: Demo Ready
|
||||
├── Happy path perfectioneren
|
||||
├── Edge case handling
|
||||
├── Performance tuning
|
||||
└── Demo scenario walkthroughs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Gezamenlijke Waarde-Conclusie
|
||||
|
||||
### 4.1 Waar Ligt de Meeste Waarde?
|
||||
|
||||
**Top 3 Waarde-Dragers:**
|
||||
|
||||
| # | Feature | Waarde Reden |
|
||||
|---|---------|--------------|
|
||||
| 1 | **Voice Dagnotitie** | Hoogste frequentie (20x/dag), grootste tijdwinst (80%), laagste effort |
|
||||
| 2 | **Instant Zoeken** | Elimineert navigatie volledig, elke actie begint met "wie" |
|
||||
| 3 | **Smart Pre-fill** | "Het systeem begrijpt me" - emotionele waarde + tijdwinst |
|
||||
|
||||
**Waarde Piramide:**
|
||||
|
||||
```
|
||||
▲
|
||||
/│\
|
||||
/ │ \
|
||||
/ │ \
|
||||
/ │ \ DELIGHT
|
||||
/ AI │ \ "Het systeem denkt mee"
|
||||
/ suggestie \
|
||||
/──────────────\
|
||||
/ \
|
||||
/ Pre-fill \ SATISFACTION
|
||||
/ Voice input \ "Het begrijpt me"
|
||||
/ Snelle feedback \
|
||||
/────────────────────────\
|
||||
/ \
|
||||
/ Intent herkenning \ BASIC
|
||||
/ Blok openen \ "Het werkt"
|
||||
/ Opslaan lukt \
|
||||
/──────────────────────────────────\
|
||||
```
|
||||
|
||||
### 4.2 Implementatie Volgorde op Basis van Waarde
|
||||
|
||||
```
|
||||
FASE 1: "Het werkt" (Basic)
|
||||
─────────────────────────
|
||||
• Command Center layout
|
||||
• Text input → Intent → Blok openen
|
||||
• Dagnotitie blok (simpel form)
|
||||
• Opslaan + bevestiging
|
||||
|
||||
FASE 2: "Het begrijpt me" (Satisfaction)
|
||||
────────────────────────────────────────
|
||||
• Voice input integratie
|
||||
• Patient pre-fill uit intent
|
||||
• Categorie herkenning
|
||||
• Zoeken blok
|
||||
|
||||
FASE 3: "Het denkt mee" (Delight)
|
||||
─────────────────────────────────
|
||||
• Rapportage met AI structurering
|
||||
• Overdracht met AI samenvatting
|
||||
• Suggesties ("wil je ook...?")
|
||||
• Context-aware hints
|
||||
```
|
||||
|
||||
### 4.3 Concrete Implementatie Aanbevelingen
|
||||
|
||||
**1. Start met Dagnotitie, niet Rapportage**
|
||||
|
||||
*Waarom:*
|
||||
- Dagnotitie is simpeler (1 tekstveld)
|
||||
- Hogere frequentie = sneller feedback
|
||||
- Sneller "waarde-bewijs" voor stakeholders
|
||||
- Rapportage kan als "upgrade" komen
|
||||
|
||||
**2. Bouw Zoeken als Fundament**
|
||||
|
||||
```typescript
|
||||
// Zoeken is de basis voor alles
|
||||
"notitie jan" → Zoek Jan → Open Dagnotitie
|
||||
"gesprek met jan" → Zoek Jan → Open Rapportage
|
||||
"overdracht jan" → Zoek Jan → Open Overdracht
|
||||
|
||||
// Zonder goede zoek = geen pre-fill = geen waarde
|
||||
```
|
||||
|
||||
**3. Voice is Must-Have, niet Nice-to-Have**
|
||||
|
||||
*Klant citaat:*
|
||||
> "Ik draag handschoenen, ik heb net iemand gewassen,
|
||||
> ik kan niet gaan typen. Voice is geen luxe."
|
||||
|
||||
*Implementatie:*
|
||||
- Voice input in Command Center (dag 1)
|
||||
- Voice in Dagnotitie tekstveld (dag 1)
|
||||
- Deepgram werkt al - alleen UI koppelen
|
||||
|
||||
**4. Pre-fill is de "Magie"**
|
||||
|
||||
```typescript
|
||||
// Dit is het WOW-moment
|
||||
Input: "Jan heeft medicatie gehad"
|
||||
|
||||
Resultaat:
|
||||
├── Patient: Jan de Vries (auto-selected)
|
||||
├── Categorie: Medicatie (auto-selected)
|
||||
├── Tekst: "heeft medicatie gehad" (pre-filled)
|
||||
└── Tijd: 14:32 (current time)
|
||||
|
||||
// User hoeft alleen: review → save
|
||||
```
|
||||
|
||||
**5. Fallback = Vertrouwen**
|
||||
|
||||
```
|
||||
Als intent mislukt:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Ik begreep dat niet helemaal. │
|
||||
│ │
|
||||
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
|
||||
│ │ 📝 │ │ 🔍 │ │ 📋 │ │ 🔄 │ │
|
||||
│ │Notitie │ │ Zoeken │ │Rapport │ │Overdr. │ │
|
||||
│ └────────┘ └────────┘ └────────┘ └────────┘ │
|
||||
│ │
|
||||
│ Of probeer opnieuw: ____________________________ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
// Nooit een doodlopende straat
|
||||
```
|
||||
|
||||
### 4.4 Success Criteria (Waarde-Based)
|
||||
|
||||
| Stakeholder | Success = |
|
||||
|-------------|-----------|
|
||||
| **Klant** | "Mijn team is 30 min/dag sneller klaar met admin" |
|
||||
| **Zorgverlener** | "Ik hoef niet meer na te denken over het systeem" |
|
||||
| **PO** | "Demo leidt tot concrete vervolgafspraak" |
|
||||
| **UX** | "Users raken de fallback-picker zelden aan" |
|
||||
|
||||
---
|
||||
|
||||
## 5. Actieplan
|
||||
|
||||
### Week 1: Foundation + Eerste Waarde
|
||||
- [ ] Command Center layout
|
||||
- [ ] Dagnotitie blok (simpel)
|
||||
- [ ] Basic intent classification
|
||||
- [ ] Voice input in Command
|
||||
|
||||
### Week 2: Core Waarde
|
||||
- [ ] Zoeken blok (cmdk)
|
||||
- [ ] Patient pre-fill
|
||||
- [ ] Categorie herkenning
|
||||
- [ ] Fallback blok-picker
|
||||
|
||||
### Week 3: Waarde Uitbreiden
|
||||
- [ ] Rapportage blok
|
||||
- [ ] AI structurering
|
||||
- [ ] Overdracht blok
|
||||
- [ ] Microinteracties
|
||||
|
||||
### Week 4: Demo + Metrics
|
||||
- [ ] Demo scenario's perfectioneren
|
||||
- [ ] Waarde-metrics implementeren
|
||||
- [ ] LinkedIn content
|
||||
- [ ] Stakeholder presentatie
|
||||
|
||||
---
|
||||
|
||||
*De meeste waarde zit in de eenvoudigste dingen: snel een notitie maken,
|
||||
snel iemand vinden, en het gevoel dat het systeem je begrijpt.*
|
||||
@@ -1,987 +0,0 @@
|
||||
# 🧩 Functioneel Ontwerp (FO) — Swift: Contextual UI EPD
|
||||
|
||||
**Projectnaam:** Swift — Contextual UI EPD
|
||||
**Versie:** v1.0
|
||||
**Datum:** 23-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en relatie met het PRD
|
||||
|
||||
🎯 **Doel van dit document:**
|
||||
Dit Functioneel Ontwerp beschrijft **hoe** Swift uit het PRD functioneel werkt — wat de gebruiker ziet, doet en ervaart. Waar het PRD het concept en de architectuur beschrijft, laat dit FO zien hoe elke interactie in de praktijk werkt.
|
||||
|
||||
📘 **Relatie met andere documenten:**
|
||||
- **PRD:** `nextgen-epd-prd-ephemeral-ui-epd.md` — Wat en waarom
|
||||
- **UX/UI:** `ux-ui-design-ephemeral-ui-epd-v2.1.md` — Visuele specificaties
|
||||
- **Taken analyse:** `taken-en-vragen-analyse.md` — Frequentie en prioritering
|
||||
|
||||
**Kernprincipe:**
|
||||
> De gebruiker navigeert niet door menu's. De gebruiker spreekt of typt een intentie — en het juiste bouwblok verschijnt, voorgevuld met relevante data.
|
||||
|
||||
---
|
||||
|
||||
## 2. Overzicht van de belangrijkste onderdelen
|
||||
|
||||
🎯 **Doel:** Overzicht van alle modules en hun relaties.
|
||||
|
||||
### 2.1 Systeemcomponenten
|
||||
|
||||
| # | Component | Beschrijving | Type |
|
||||
|---|-----------|--------------|------|
|
||||
| 1 | **Command Center** | Hoofdscherm met één input | Scherm |
|
||||
| 2 | **Context Bar** | Dienst, patiënt, user info | UI Zone |
|
||||
| 3 | **Canvas Area** | Waar blocks verschijnen | UI Zone |
|
||||
| 4 | **Recent Strip** | Laatste acties quick access | UI Zone |
|
||||
| 5 | **Command Input** | Tekst + voice input | UI Zone |
|
||||
| 6 | **Intent Engine** | Classificeert gebruikersinput | Backend |
|
||||
| 7 | **Context Manager** | Beheert sessie context | Backend |
|
||||
|
||||
### 2.2 Bouwblokken (Blocks)
|
||||
|
||||
| Prio | Block | Functie | Trigger voorbeelden |
|
||||
|------|-------|---------|---------------------|
|
||||
| P1 | **DagnotatieBlock** | Snelle notitie invoer | "notitie jan medicatie" |
|
||||
| P1 | **ZoekenBlock** | Patiënt zoeken | "zoek marie" |
|
||||
| P1 | **PatientContextCard** | Patiënt overzicht | Na zoeken / selectie |
|
||||
| P1 | **OverdrachtBlock** | Dienst overdracht | "overdracht maken" |
|
||||
| P2 | **RapportageBlock** | Behandelrapportage | "gesprek gehad met jan" |
|
||||
| P2 | **AgendaBlock** | Afspraken | "mijn afspraken" |
|
||||
| P2 | **MetingenBlock** | Vitale functies | "bloeddruk invoeren" |
|
||||
| P3 | **IntakeWizard** | Nieuwe patiënt intake | "nieuwe intake" |
|
||||
| P3 | **BehandelplanBlock** | Behandelplan | "behandelplan jan" |
|
||||
| P3 | **RisicoBlock** | Risicotaxatie | "risico jan" |
|
||||
| P3 | **ContactenBlock** | Contactpersonen | "contacten jan" |
|
||||
|
||||
### 2.3 Systeem Blocks
|
||||
|
||||
| Block | Functie | Trigger |
|
||||
|-------|---------|---------|
|
||||
| **HelpBlock** | Hulp en voorbeelden | "help", "wat kan ik" |
|
||||
| **FallbackPicker** | Visuele keuze bij onduidelijkheid | Lage confidence |
|
||||
|
||||
---
|
||||
|
||||
## 3. User Stories
|
||||
|
||||
🎯 **Doel:** Beschrijven wat gebruikers moeten kunnen doen, vanuit hun perspectief.
|
||||
|
||||
### 3.1 P1: Kritieke Stories (MVP Week 1-2)
|
||||
|
||||
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|
||||
|----|-----|--------------|------------------|------|
|
||||
| US-01 | Verpleegkundige | Dagnotitie maken door te spreken/typen | Registratie in 15 sec ipv 5 min | 🔴 P1 |
|
||||
| US-02 | Verpleegkundige | Patiënt zoeken op naam | Direct vinden zonder navigatie | 🔴 P1 |
|
||||
| US-03 | Verpleegkundige | Patiënt context zien na selectie | Alles relevante in één oogopslag | 🔴 P1 |
|
||||
| US-04 | Verpleegkundige | Overdracht maken einde dienst | Complete overdracht in 5 min | 🔴 P1 |
|
||||
| US-05 | Alle gebruikers | Weten wat het systeem kan | "help" toont mogelijkheden | 🔴 P1 |
|
||||
| US-06 | Alle gebruikers | Kiezen als systeem niet begrijpt | Visuele fallback picker | 🔴 P1 |
|
||||
|
||||
### 3.2 P2: Belangrijke Stories (MVP Week 3-4)
|
||||
|
||||
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|
||||
|----|-----|--------------|------------------|------|
|
||||
| US-07 | Behandelaar | Rapportage schrijven na gesprek | Dicteren + AI samenvatting | 🟡 P2 |
|
||||
| US-08 | Behandelaar | Agenda bekijken | Afspraken vandaag/week | 🟡 P2 |
|
||||
| US-09 | Verpleegkundige | Meting invoeren | Vitals met trend indicator | 🟡 P2 |
|
||||
| US-10 | Behandelaar | Diagnose/plan van patiënt zien | Via PatientContextCard | 🟡 P2 |
|
||||
|
||||
### 3.3 P3: Post-MVP Stories
|
||||
|
||||
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|
||||
|----|-----|--------------|------------------|------|
|
||||
| US-11 | Behandelaar | Nieuwe intake starten | Wizard begeleidt proces | 🟢 P3 |
|
||||
| US-12 | Behandelaar | Behandelplan maken/bewerken | AI-gegenereerd, bewerkbaar | 🟢 P3 |
|
||||
| US-13 | Verpleegkundige | Risicotaxatie invullen | Gestructureerd formulier | 🟢 P3 |
|
||||
| US-14 | Behandelaar | Contactpersonen beheren | Noodcontact, familie | 🟢 P3 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Functionele werking per onderdeel
|
||||
|
||||
🎯 **Doel:** Per component beschrijven wat de gebruiker kan doen en wat het systeem doet.
|
||||
|
||||
### 4.1 Command Center (Hoofdscherm)
|
||||
|
||||
**Beschrijving:**
|
||||
Het enige scherm van de applicatie. Geen sidebar, geen menu's. Alles gebeurt via één input.
|
||||
|
||||
**Layout:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ [Context Bar: Dienst | Patiënt dropdown | User] 48px │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [Active Block Area] │
|
||||
│ Blocks verschijnen hier │
|
||||
│ Centered, responsive width │
|
||||
│ Flex │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ [Recent Strip: laatste acties als badges] 48px │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ [Command Input: 🎤 Typ of spreek wat je wilt doen...] 64px │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Gedrag:**
|
||||
- Bij laden: Command Input heeft focus
|
||||
- Keyboard shortcut `⌘K` focust input vanaf elke plek
|
||||
- Blocks verschijnen met fade+scale animatie
|
||||
- Slechts één block actief tegelijk (of gestapeld met z-index)
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Context Bar
|
||||
|
||||
**Functie:** Toont essentiële context: welke dienst, welke patiënt actief, wie ingelogd.
|
||||
|
||||
**Elementen:**
|
||||
|
||||
| Element | Locatie | Gedrag |
|
||||
|---------|---------|--------|
|
||||
| Dienst indicator | Links | "🌅 Ochtend \| 8 ptn" — kleur per shift |
|
||||
| Actieve patiënt | Midden-rechts | Dropdown voor quick-switch |
|
||||
| User | Rechts | Initialen, klik → logout |
|
||||
|
||||
**Dienst bepaling:**
|
||||
|
||||
| Dienst | Tijd | Kleur | Icon |
|
||||
|--------|------|-------|------|
|
||||
| Ochtend | 07:00-15:00 | Amber #F59E0B | 🌅 |
|
||||
| Middag | 15:00-23:00 | Blue #3B82F6 | 🌤️ |
|
||||
| Nacht | 23:00-07:00 | Indigo #6366F1 | 🌙 |
|
||||
|
||||
**Patiënt Dropdown:**
|
||||
- Toont huidige selectie (of "Geen patiënt")
|
||||
- Recent bekeken patiënten (max 5)
|
||||
- Zoek optie onderaan
|
||||
- "Clear" optie om selectie te wissen
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Command Input
|
||||
|
||||
**Functie:** Het hart van de interface — tekst én voice input.
|
||||
|
||||
**States:**
|
||||
|
||||
| State | Weergave | Trigger |
|
||||
|-------|----------|---------|
|
||||
| Default | "🎤 Typ of spreek wat je wilt doen..." | — |
|
||||
| Typing | Cursor, getypte tekst | Keyboard input |
|
||||
| Listening | 🔴 + waveform + live transcript | Mic click of spatie (bij leeg) |
|
||||
| Processing | ⏳ spinner + "Even kijken..." | Na submit |
|
||||
|
||||
**Acties:**
|
||||
|
||||
| Input | Actie |
|
||||
|-------|-------|
|
||||
| `Enter` | Submit naar Intent Engine |
|
||||
| `Escape` | Clear input / close block |
|
||||
| `↑` | Vorige command (history) |
|
||||
| `Space` (leeg) | Start voice |
|
||||
| Mic click | Toggle voice recording |
|
||||
|
||||
**Voice Flow:**
|
||||
1. User klikt mic of drukt spatie (bij lege input)
|
||||
2. Deepgram start streaming transcription
|
||||
3. Live transcript verschijnt in input
|
||||
4. Pauze detectie (1.5 sec stilte) → auto-submit
|
||||
5. Of user klikt "Stop" → submit
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Intent Engine
|
||||
|
||||
**Functie:** Classificeert gebruikersinput naar intent + entities.
|
||||
|
||||
**Input:**
|
||||
```typescript
|
||||
{
|
||||
text: string; // "notitie jan medicatie gegeven"
|
||||
context: {
|
||||
activePatient?: Patient;
|
||||
currentShift: 'ochtend' | 'middag' | 'nacht';
|
||||
recentPatients: Patient[];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```typescript
|
||||
{
|
||||
intent: IntentType; // 'dagnotitie'
|
||||
confidence: number; // 0.94
|
||||
entities: {
|
||||
patientName?: string; // "jan"
|
||||
patientId?: string; // "uuid-123" (als gevonden)
|
||||
category?: string; // "medicatie"
|
||||
content?: string; // "gegeven"
|
||||
date?: string;
|
||||
};
|
||||
clarificationNeeded: boolean;
|
||||
clarificationQuestion?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Intent Types:**
|
||||
|
||||
| Intent | Block | Confidence drempel |
|
||||
|--------|-------|-------------------|
|
||||
| `dagnotitie` | DagnotatieBlock | 0.7 |
|
||||
| `zoeken` | ZoekenBlock | 0.7 |
|
||||
| `overdracht` | OverdrachtBlock | 0.8 |
|
||||
| `rapportage` | RapportageBlock | 0.7 |
|
||||
| `agenda` | AgendaBlock | 0.7 |
|
||||
| `metingen` | MetingenBlock | 0.7 |
|
||||
| `intake` | IntakeWizard | 0.8 |
|
||||
| `behandelplan` | BehandelplanBlock | 0.8 |
|
||||
| `risico` | RisicoBlock | 0.8 |
|
||||
| `patient_info` | PatientContextCard | 0.7 |
|
||||
| `help` | HelpBlock | 0.9 |
|
||||
| `onbekend` | FallbackPicker | < 0.5 |
|
||||
|
||||
**Clarification Flow:**
|
||||
- Confidence < drempel → vraag om verduidelijking
|
||||
- Meerdere patient matches → toon selector
|
||||
- Geen patient bij patient-required intent → vraag welke patient
|
||||
|
||||
---
|
||||
|
||||
### 4.5 DagnotatieBlock
|
||||
|
||||
**Functie:** Snelle notitie invoer — meest gebruikte actie (10-20x per dag).
|
||||
|
||||
**Trigger patterns:**
|
||||
- "notitie [patient]"
|
||||
- "[patient] medicatie gegeven"
|
||||
- "incident bij [patient]"
|
||||
- "[patient] heeft goed gegeten"
|
||||
|
||||
**Pre-fill logica:**
|
||||
|
||||
| Extracted | Pre-fill |
|
||||
|-----------|----------|
|
||||
| patient_name → match | Patient selector |
|
||||
| "medicatie" keyword | Category = Medicatie |
|
||||
| "gegeten", "ADL" | Category = ADL |
|
||||
| "incident", "agressie" | Category = Incident |
|
||||
| Overige tekst | Notitie veld |
|
||||
|
||||
**Form velden:**
|
||||
|
||||
| Veld | Type | Verplicht | Default |
|
||||
|------|------|-----------|---------|
|
||||
| Patient | Dropdown + search | Ja | Pre-filled of activePatient |
|
||||
| Categorie | Button group | Ja | Extracted of Algemeen |
|
||||
| Notitie | Textarea | Ja | Extracted content |
|
||||
| Tijd | Time picker | Ja | Nu |
|
||||
| In overdracht | Checkbox | Nee | false |
|
||||
|
||||
**Categorieën:**
|
||||
|
||||
| Categorie | Icon | Kleur | Keyboard |
|
||||
|-----------|------|-------|----------|
|
||||
| Medicatie | 💊 | Amber | 1 |
|
||||
| ADL | 🍽️ | Green | 2 |
|
||||
| Observatie | 👁️ | Blue | 3 |
|
||||
| Incident | ⚠️ | Red | 4 |
|
||||
| Algemeen | 💬 | Gray | 5 |
|
||||
|
||||
**Acties:**
|
||||
|
||||
| Knop | Actie | Keyboard |
|
||||
|------|-------|----------|
|
||||
| Opslaan | POST naar API, sluit block | `⌘Enter` |
|
||||
| Annuleren | Sluit block zonder opslaan | `Escape` |
|
||||
|
||||
**Na opslaan:**
|
||||
1. Toast: "✓ Notitie opgeslagen"
|
||||
2. Block verdwijnt (200ms animatie)
|
||||
3. Recent strip: badge "[📝 Jan-Med]"
|
||||
4. Input krijgt focus voor volgende actie
|
||||
|
||||
**API:**
|
||||
```
|
||||
POST /api/reports
|
||||
Body: {
|
||||
patient_id: string,
|
||||
type: 'nursing_log',
|
||||
category: string,
|
||||
content: string,
|
||||
timestamp: datetime,
|
||||
include_in_handover: boolean
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.6 ZoekenBlock
|
||||
|
||||
**Functie:** Patiënt zoeken en selecteren.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "zoek [naam]"
|
||||
- "wie is [naam]"
|
||||
- "vind [naam]"
|
||||
|
||||
**Gedrag:**
|
||||
1. Block opent met zoekterm pre-filled
|
||||
2. Live search (debounced 300ms)
|
||||
3. Resultaten tonen met relevante info
|
||||
4. Klik of Enter selecteert patiënt
|
||||
|
||||
**Resultaat card bevat:**
|
||||
- Naam (highlighted match)
|
||||
- Leeftijd
|
||||
- Kamer/locatie
|
||||
- Laatste activiteit ("2 uur geleden")
|
||||
- Alert badge indien aanwezig
|
||||
|
||||
**Na selectie:**
|
||||
1. ZoekenBlock sluit
|
||||
2. PatientContextCard opent automatisch
|
||||
3. Context Bar update: patient dropdown toont selectie
|
||||
4. Recent strip: badge "[🔍 Jan]"
|
||||
|
||||
**Empty state:**
|
||||
"Geen patiënten gevonden voor '[zoekterm]'"
|
||||
|
||||
**API:**
|
||||
```
|
||||
GET /api/patients/search?q={zoekterm}
|
||||
Response: Patient[] met relevance score
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.7 PatientContextCard
|
||||
|
||||
**Functie:** Compact overzicht van alles relevant voor één patiënt.
|
||||
|
||||
**Trigger:**
|
||||
- Automatisch na patiënt selectie
|
||||
- "info [patient]", "dossier [patient]"
|
||||
- Klik op patiënt in dropdown
|
||||
|
||||
**Secties (adaptive - alleen tonen als data aanwezig):**
|
||||
|
||||
| Sectie | Data | Bron |
|
||||
|--------|------|------|
|
||||
| Header | Naam, leeftijd, kamer, opnamedatum | patients |
|
||||
| Alert | Actieve alerts/risico's | risk_assessments |
|
||||
| Laatste notities | 3 meest recente | reports |
|
||||
| Vitals | Vandaag gemeten | vitals |
|
||||
| Diagnose | Hoofddiagnose + ernst | conditions |
|
||||
| Behandelplan | Status, sessie X/Y, volgende eval | care_plans |
|
||||
| Contacten | Primaire contact | contacts |
|
||||
|
||||
**Quick Actions (onderaan):**
|
||||
```
|
||||
[📝 Notitie] [📋 Rapport] [📊 Meting] [📄 Plan] [📞 Contact]
|
||||
```
|
||||
|
||||
Klik op quick action → opent betreffende block met patient pre-filled.
|
||||
|
||||
**API:**
|
||||
```
|
||||
GET /api/patients/{id}/context
|
||||
Response: {
|
||||
patient: Patient,
|
||||
alerts: Alert[],
|
||||
recentNotes: Report[],
|
||||
vitals: Vital[],
|
||||
diagnosis: Condition,
|
||||
carePlan: CarePlan,
|
||||
contacts: Contact[]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.8 OverdrachtBlock
|
||||
|
||||
**Functie:** Multi-patiënt overdracht met AI-samenvattingen.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "overdracht maken"
|
||||
- "dienst klaar"
|
||||
- "samenvatting voor collega"
|
||||
|
||||
**Proactive trigger:**
|
||||
- 30 minuten voor dienst einde → suggestie banner
|
||||
|
||||
**Layout:**
|
||||
- Header: "Overdracht [Ochtend] → [Middag]" + tijdrange
|
||||
- Accordion per patiënt (expanded als alert aanwezig)
|
||||
- AI samenvatting per patiënt
|
||||
- Bronverwijzingen naar originele notities
|
||||
- Footer: Kopieer / E-mail / Afronden
|
||||
|
||||
**Per patiënt accordion:**
|
||||
|
||||
| Element | Beschrijving |
|
||||
|---------|--------------|
|
||||
| Header | Naam + alert badge |
|
||||
| Summary | AI-gegenereerde samenvatting |
|
||||
| Bronnen | "📎 3 notities" — klik opent details |
|
||||
| Bewerken | Inline edit van samenvatting |
|
||||
|
||||
**AI Samenvatting:**
|
||||
- Gegenereerd op basis van: nursing_logs, vitals, rapportages
|
||||
- Tijdrange: huidige dienst (default 8 uur)
|
||||
- Max 3 zinnen per patiënt
|
||||
- Aandachtspunten prominent
|
||||
|
||||
**Acties:**
|
||||
|
||||
| Knop | Actie |
|
||||
|------|-------|
|
||||
| Kopieer alles | Alle samenvattingen naar clipboard |
|
||||
| E-mail | Open mail client met content |
|
||||
| Afronden | Markeer overdracht compleet |
|
||||
|
||||
**API:**
|
||||
```
|
||||
POST /api/overdracht/generate
|
||||
Body: {
|
||||
patientIds: string[],
|
||||
shiftStart: datetime,
|
||||
shiftEnd: datetime
|
||||
}
|
||||
Response: {
|
||||
summaries: { patientId: string, summary: string, sources: Source[] }[]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.9 RapportageBlock
|
||||
|
||||
**Functie:** Uitgebreide behandelrapportage met rich text en AI.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "rapportage"
|
||||
- "gesprek gehad met [patient]"
|
||||
- "behandelgesprek"
|
||||
|
||||
**Form velden:**
|
||||
|
||||
| Veld | Type | Default |
|
||||
|------|------|---------|
|
||||
| Patient | Dropdown | Pre-filled of activePatient |
|
||||
| Type | Button group | Gesprek |
|
||||
| Inhoud | Rich text (TipTap) | Leeg of transcript |
|
||||
| Datum/tijd | DateTime | Nu |
|
||||
|
||||
**Rapportage types:**
|
||||
- Gesprek
|
||||
- Evaluatie
|
||||
- Telefonisch
|
||||
- Consult
|
||||
- Familie
|
||||
|
||||
**Rich text toolbar:**
|
||||
- Bold, Italic
|
||||
- Bullet list, Numbered list
|
||||
- Quote
|
||||
- H1, H2
|
||||
- 🎤 Dicteer knop
|
||||
|
||||
**AI acties:**
|
||||
|
||||
| Actie | Beschrijving | Output |
|
||||
|-------|--------------|--------|
|
||||
| ✨ Samenvatten | Bullets van kernpunten | Zijpaneel |
|
||||
| 📖 B1-niveau | Herschrijf leesbaar | Zijpaneel |
|
||||
| 🔍 Problemen | Extraheer klinische issues | Zijpaneel |
|
||||
|
||||
**AI Zijpaneel:**
|
||||
- Verschijnt rechts van editor
|
||||
- Preview van AI output
|
||||
- Knoppen: Invoegen, Kopiëren, Verwerp
|
||||
|
||||
**API:**
|
||||
```
|
||||
POST /api/reports
|
||||
Body: {
|
||||
patient_id: string,
|
||||
type: 'rapportage',
|
||||
subtype: string,
|
||||
content: string (HTML),
|
||||
timestamp: datetime
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.10 AgendaBlock
|
||||
|
||||
**Functie:** Afspraken overzicht en beheer.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "agenda"
|
||||
- "afspraken vandaag"
|
||||
- "wie zie ik deze week"
|
||||
- "afspraken [patient]"
|
||||
|
||||
**Layout:**
|
||||
- Navigatie: [◀ Gisteren] [Vandaag] [Morgen ▶]
|
||||
- View toggle: [Dag] [Week]
|
||||
- Lijst van afspraken met tijden
|
||||
|
||||
**Per afspraak:**
|
||||
- Tijd (09:00 - 09:50)
|
||||
- Patiëntnaam
|
||||
- Type afspraak
|
||||
- Quick actions: [Open dossier] [Notitie]
|
||||
|
||||
**Acties:**
|
||||
- Klik op afspraak → open PatientContextCard
|
||||
- [+ Nieuwe afspraak] → AfspraakBlock (P4)
|
||||
|
||||
**API:**
|
||||
```
|
||||
GET /api/appointments?date={date}&practitionerId={id}
|
||||
Response: Appointment[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.11 MetingenBlock
|
||||
|
||||
**Functie:** Vitale functies invoeren.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "bloeddruk invoeren"
|
||||
- "meting [patient]"
|
||||
- "vitals"
|
||||
- "temperatuur"
|
||||
|
||||
**Form velden:**
|
||||
|
||||
| Veld | Type | Validatie |
|
||||
|------|------|-----------|
|
||||
| Patient | Dropdown | Verplicht |
|
||||
| Type | Button group | Verplicht |
|
||||
| Waarde(s) | Number input(s) | Per type |
|
||||
| Tijd | DateTime | Default: nu |
|
||||
| Opmerking | Textarea | Optioneel |
|
||||
|
||||
**Meting types:**
|
||||
|
||||
| Type | Velden | Eenheid |
|
||||
|------|--------|---------|
|
||||
| Bloeddruk | Systolisch, Diastolisch | mmHg |
|
||||
| Pols | BPM | /min |
|
||||
| Temperatuur | Temp | °C |
|
||||
| Gewicht | Kg | kg |
|
||||
| Glucose | mmol/L | mmol/L |
|
||||
| Saturatie | % | SpO2 |
|
||||
|
||||
**Trend indicator:**
|
||||
Na invoer: vergelijk met vorige meting
|
||||
- ↑ Hoger dan vorige
|
||||
- ↓ Lager dan vorige
|
||||
- → Gelijk
|
||||
|
||||
**API:**
|
||||
```
|
||||
POST /api/vitals
|
||||
Body: {
|
||||
patient_id: string,
|
||||
type: string,
|
||||
values: Record<string, number>,
|
||||
timestamp: datetime,
|
||||
notes?: string
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.12 HelpBlock
|
||||
|
||||
**Functie:** Toont wat het systeem kan.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "help"
|
||||
- "wat kan ik doen"
|
||||
- "hoe werkt dit"
|
||||
|
||||
**Inhoud:**
|
||||
Gegroepeerde voorbeelden per categorie:
|
||||
- 📝 Notities: "notitie jan medicatie"
|
||||
- 🔍 Zoeken: "zoek marie"
|
||||
- 🔄 Overdracht: "overdracht maken"
|
||||
- 📋 Rapportage: "gesprek gehad"
|
||||
- 📅 Agenda: "afspraken vandaag"
|
||||
- etc.
|
||||
|
||||
---
|
||||
|
||||
### 4.13 FallbackPicker
|
||||
|
||||
**Functie:** Visuele keuze wanneer intent onduidelijk is.
|
||||
|
||||
**Trigger:**
|
||||
- Intent confidence < 0.5
|
||||
- Intent = 'onbekend'
|
||||
|
||||
**Layout:**
|
||||
Grid van 8-10 opties met icon + label.
|
||||
Keyboard shortcuts 1-9 voor snelle selectie.
|
||||
|
||||
**Opties:**
|
||||
```
|
||||
[📝 Notitie] [🔍 Zoeken] [🔄 Overdracht] [📋 Rapport]
|
||||
[📅 Agenda] [📊 Meting] [👤 Dossier] [📄 Intake]
|
||||
[⚠️ Risico] [💡 Help]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.14 Recent Strip
|
||||
|
||||
**Functie:** Quick access naar recente acties.
|
||||
|
||||
**Gedrag:**
|
||||
- Max 5 badges zichtbaar
|
||||
- Nieuwste links
|
||||
- Horizontal scroll voor meer
|
||||
- Klik = heropen context
|
||||
|
||||
**Badge types:**
|
||||
|
||||
| Type | Icon | Kleur | Klik actie |
|
||||
|------|------|-------|------------|
|
||||
| Dagnotitie | 📝 | Category kleur | Open PatientContextCard |
|
||||
| Zoeken | 🔍 | Gray | Open PatientContextCard |
|
||||
| Overdracht | 🔄 | Blue | Open OverdrachtBlock |
|
||||
| Rapportage | 📋 | Green | Open RapportageBlock |
|
||||
|
||||
---
|
||||
|
||||
## 5. UI-overzicht (visuele structuur)
|
||||
|
||||
🎯 **Doel:** Globale schermopbouw en component hiërarchie.
|
||||
|
||||
### 5.1 Hoofdlayout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🌅 Ochtend | 8 ptn Jan de Vries ▼ 👤 SV │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ CONTEXT BAR (48px) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ACTIVE BLOCK │ │
|
||||
│ │ (Small: 480px) │ │
|
||||
│ │ (Medium: 640px) │ │
|
||||
│ │ (Large: 900px) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────┘ │
|
||||
│ │
|
||||
│ CANVAS AREA (flex) │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ Recent: [📝 Jan-Med] [🔄 Overdracht] [🔍 Marie] │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ RECENT STRIP (48px) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🎤 Typ of spreek wat je wilt doen... ⌘K │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ COMMAND INPUT (64px) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 Block Sizes
|
||||
|
||||
| Size | Max-width | Use case |
|
||||
|------|-----------|----------|
|
||||
| Small | 480px | Quick entry (notitie, zoeken, meting) |
|
||||
| Medium | 640px | Forms (rapportage, behandelplan) |
|
||||
| Large | 900px | Overzichten (overdracht, agenda) |
|
||||
| XLarge | 1100px | Wizards (intake) |
|
||||
|
||||
### 5.3 Block Container
|
||||
|
||||
Alle blocks gebruiken dezelfde wrapper:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ [Icon] [Title] [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ BLOCK CONTENT │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ [Cancel] [Save] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Interacties met AI (functionele beschrijving)
|
||||
|
||||
🎯 **Doel:** Waar AI in de flow voorkomt en wat de gebruiker ziet.
|
||||
|
||||
### 6.1 Intent Classification
|
||||
|
||||
| Locatie | Trigger | Input | Output |
|
||||
|---------|---------|-------|--------|
|
||||
| Command Input | Submit | Tekst/transcript | Intent + entities + confidence |
|
||||
|
||||
### 6.2 Content AI
|
||||
|
||||
| Locatie | AI-actie | Trigger | Output |
|
||||
|---------|----------|---------|--------|
|
||||
| RapportageBlock | Samenvatten | Klik knop | Bullets in zijpaneel |
|
||||
| RapportageBlock | B1-niveau | Klik knop | Herschreven tekst |
|
||||
| RapportageBlock | Extract problemen | Klik knop | Categorie + severity |
|
||||
| OverdrachtBlock | Genereer samenvatting | Per patiënt accordion | 3-zin summary |
|
||||
| BehandelplanBlock | Genereer plan | Klik knop | Doelen + interventies |
|
||||
| IntakeWizard | Suggestie diagnose | Na anamnese stap | ICD-10 code + rationale |
|
||||
|
||||
### 6.3 AI Response Handling
|
||||
|
||||
**Alle AI outputs:**
|
||||
1. Tonen in dedicated preview area (niet direct in form)
|
||||
2. User moet expliciet accepteren/invoegen
|
||||
3. Bewerken altijd mogelijk
|
||||
4. Annuleren zonder gevolgen
|
||||
|
||||
**Loading state:**
|
||||
- Skeleton loader in output area
|
||||
- "AI denkt na..." indicator
|
||||
- Non-blocking (user kan annuleren)
|
||||
|
||||
**Error handling:**
|
||||
- "Kon niet verwerken. Probeer opnieuw."
|
||||
- Retry knop
|
||||
- Fallback naar handmatige invoer
|
||||
|
||||
---
|
||||
|
||||
## 7. Proactieve Triggers
|
||||
|
||||
🎯 **Doel:** Wanneer het systeem zelf UI toont zonder expliciete vraag.
|
||||
|
||||
| Trigger | Conditie | Actie |
|
||||
|---------|----------|-------|
|
||||
| Dienst start | Login of shift wissel | Suggestie: OverdrachtLezenBlock |
|
||||
| Dienst einde | 30 min voor einde | Banner: "Overdracht maken?" |
|
||||
| Patiënt geselecteerd | Na zoeken/klik | Auto-open PatientContextCard |
|
||||
| Afspraak nadert | 15 min voor afspraak | Suggestie: patiënt openen |
|
||||
| Alert aanwezig | Patiënt met actief risico | Alert banner in PatientContextCard |
|
||||
|
||||
**Suggestie Banner:**
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ 🕐 Dienst eindigt over 30 min. Overdracht maken? │
|
||||
│ [Ja] [Later] [×] │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Niet-blokkerend (user kan negeren)
|
||||
- "Later" = herinner over 15 min
|
||||
- "×" = niet meer voor deze dienst
|
||||
|
||||
---
|
||||
|
||||
## 8. States & Error Handling
|
||||
|
||||
### 8.1 Block States
|
||||
|
||||
| State | Weergave |
|
||||
|-------|----------|
|
||||
| Loading | Skeleton loader |
|
||||
| Ready | Form/content |
|
||||
| Submitting | Disabled + spinner |
|
||||
| Success | Toast + close |
|
||||
| Error | Inline error message |
|
||||
|
||||
### 8.2 Network Errors
|
||||
|
||||
| Situatie | Gedrag |
|
||||
|----------|--------|
|
||||
| API timeout | "Verbinding verbroken. Probeer opnieuw." + retry |
|
||||
| 401 Unauthorized | Redirect naar login |
|
||||
| 500 Server error | "Er ging iets mis. Probeer later opnieuw." |
|
||||
| Offline | Banner bovenin: "Geen internetverbinding" |
|
||||
|
||||
### 8.3 Validation Errors
|
||||
|
||||
| Veld | Validatie | Foutmelding |
|
||||
|------|-----------|-------------|
|
||||
| Patient | Verplicht | "Selecteer een patiënt" |
|
||||
| Notitie | Min 5 karakters | "Voer een notitie in" |
|
||||
| Bloeddruk | 50-250 / 30-150 | "Waarde buiten bereik" |
|
||||
|
||||
---
|
||||
|
||||
## 9. Gebruikersrollen en rechten
|
||||
|
||||
🎯 **Doel:** Welke rollen toegang hebben tot welke onderdelen.
|
||||
|
||||
| Rol | Toegang | Beperkingen |
|
||||
|-----|---------|-------------|
|
||||
| Verpleegkundige | Dagnotitie, Zoeken, Overdracht, Metingen | Geen behandelplan bewerken |
|
||||
| Behandelaar | Alle blocks | Alleen eigen patiënten bewerken |
|
||||
| Psychiater | Alle blocks + diagnose | Alleen eigen patiënten |
|
||||
| Demo-user | Alle blocks (fictieve data) | Alleen lezen |
|
||||
|
||||
---
|
||||
|
||||
## 10. Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Actie | Scope |
|
||||
|----------|-------|-------|
|
||||
| `⌘K` | Focus command input | Global |
|
||||
| `Escape` | Sluit actieve block | Block open |
|
||||
| `Enter` | Submit form | Form focused |
|
||||
| `⌘Enter` | Opslaan | In form |
|
||||
| `1-5` | Selecteer categorie | DagnotatieBlock |
|
||||
| `1-9` | Selecteer optie | FallbackPicker |
|
||||
| `↑` `↓` | Navigeer resultaten | ZoekenBlock |
|
||||
| `Space` | Start voice (lege input) | Command Input |
|
||||
|
||||
---
|
||||
|
||||
## 11. Navigatie & Toegang
|
||||
|
||||
🎯 **Doel:** Beschrijven hoe gebruikers Swift bereiken en kiezen.
|
||||
|
||||
### 11.1 User Journey naar Swift
|
||||
|
||||
```
|
||||
LANDINGSPAGINA (/)
|
||||
│
|
||||
│ Storytelling + Demo
|
||||
│
|
||||
▼
|
||||
LOGIN PAGINA (/login)
|
||||
│
|
||||
│ ┌─────────────────────────────────────┐
|
||||
│ │ Kies je werkwijze: │
|
||||
│ │ │
|
||||
│ │ ○ ✨ Swift — Spreek of typ │
|
||||
│ │ ○ 📋 Klassiek — Menu's en forms │
|
||||
│ │ │
|
||||
│ │ ☐ Onthoud mijn keuze │
|
||||
│ └─────────────────────────────────────┘
|
||||
│
|
||||
├──────────────────┬───────────────────┐
|
||||
▼ ▼ │
|
||||
/epd/swift /epd/dashboard │
|
||||
(Command Center) (Klassiek EPD) │
|
||||
│
|
||||
│
|
||||
GEEN TOGGLE ◄──────────┘
|
||||
binnen interface
|
||||
```
|
||||
|
||||
### 11.2 Landingspagina Elementen
|
||||
|
||||
De landingspagina introduceert Swift via storytelling:
|
||||
|
||||
| Element | Functie |
|
||||
|---------|---------|
|
||||
| **Hero** | Probleem: "40% admin tijd" → Oplossing: "1 zin, klaar" |
|
||||
| **Interactive Demo** | Probeer Swift zonder login |
|
||||
| **Side-by-side** | 12 klikken klassiek vs 1 zin Swift |
|
||||
| **Video** | Demo van dagnotitie flow |
|
||||
| **CTA** | "Aan de slag" → /login |
|
||||
|
||||
### 11.3 Login Pagina met Interface Keuze
|
||||
|
||||
De login pagina bevat een interface selector:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ [Bestaand login formulier - email, wachtwoord] │
|
||||
│ │
|
||||
│ ───────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ Kies je werkwijze: │
|
||||
│ │
|
||||
│ ┌───────────────────────┐ ┌───────────────────────┐ │
|
||||
│ │ ✨ Swift │ │ 📋 Klassiek │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ Spreek of typ wat je │ │ Vertrouwde menu's │ │
|
||||
│ │ wilt — het systeem │ │ en formulieren │ │
|
||||
│ │ begrijpt │ │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ "notitie jan med" → │ │ Dashboard → Patient │ │
|
||||
│ │ klaar in 15 sec │ │ → Tab → Form → Save │ │
|
||||
│ └───────────────────────┘ └───────────────────────┘ │
|
||||
│ │
|
||||
│ ☐ Onthoud mijn keuze │
|
||||
│ │
|
||||
│ [Inloggen] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 11.4 Redirect Gedrag
|
||||
|
||||
| Situatie | Gedrag |
|
||||
|----------|--------|
|
||||
| Nieuwe user, geen preference | Toon keuze op login pagina |
|
||||
| User met preference + "onthoud" | Direct door naar gekozen interface |
|
||||
| User naar `/epd` | Redirect naar preference of dashboard |
|
||||
| Uitgelogd | Terug naar login met keuze optie |
|
||||
|
||||
### 11.5 Geen Toggle in Interface
|
||||
|
||||
Er is **geen mogelijkheid** om binnen Swift of Klassiek EPD te wisselen naar de andere interface:
|
||||
|
||||
- Geen toggle button in Swift
|
||||
- Geen toggle button in Klassiek EPD
|
||||
- Om te wisselen: uitloggen → opnieuw inloggen → andere keuze
|
||||
|
||||
**Rationale:**
|
||||
- Voorkomt verwarring over "waar ben ik"
|
||||
- Gebruiker committed aan één ervaring
|
||||
- Admin kan preference aanpassen indien nodig
|
||||
|
||||
### 11.6 Admin Beheer
|
||||
|
||||
Alleen admins kunnen de interface preference van andere users wijzigen:
|
||||
|
||||
| Actie | Wie | Waar |
|
||||
|-------|-----|------|
|
||||
| Eigen preference instellen | Alle users | Login pagina |
|
||||
| Preference andere user wijzigen | Admin | Admin panel |
|
||||
| Geforceerd naar Swift/Klassiek | Admin | Admin panel |
|
||||
|
||||
---
|
||||
|
||||
## 12. Bijlagen & Referenties
|
||||
|
||||
**Projectdocumenten:**
|
||||
- PRD: `swift-prd.md`
|
||||
- UX/UI: `swift-ux-v2.1.md`
|
||||
- TO: `to-swift-v1.md`
|
||||
- Taken analyse: `taken-en-vragen-analyse.md`
|
||||
|
||||
**Bestaande documentatie:**
|
||||
- Database schema: Supabase migrations
|
||||
- Bestaande FO overdracht: `fo-overdracht-dashboard-v1_1.md` (archive)
|
||||
|
||||
---
|
||||
|
||||
## Wijzigingslog
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| 1.0 | 23-12-2024 | Initiële versie op basis van PRD en UX/UI v2.1 |
|
||||
| 1.1 | 23-12-2024 | Hernoemd naar Swift, toegevoegd: Navigatie & Toegang sectie |
|
||||
@@ -1,725 +0,0 @@
|
||||
# 🧩 Functioneel Ontwerp (FO) — Swift: Diagnostiek Workflow
|
||||
|
||||
**Projectnaam:** Swift — Contextual UI EPD
|
||||
**Versie:** v1.0
|
||||
**Datum:** 23-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en relatie met het PRD
|
||||
|
||||
🎯 **Doel van dit document:**
|
||||
Dit Functioneel Ontwerp beschrijft **hoe** de diagnostiek workflow in Swift functioneel werkt — van het plannen van een diagnostiek-afspraak tot het stellen en bijstellen van diagnoses. Dit document focust specifiek op de complete flow die behandelaars doorlopen tijdens een diagnostiek-traject.
|
||||
|
||||
📘 **Relatie met andere documenten:**
|
||||
- **PRD:** `swift-prd.md` — Product visie en requirements
|
||||
- **FO Algemeen:** `swift-fo-ai.md` — Algemene Swift functionaliteit
|
||||
- **Bouwplan:** `bouwplan-swift-v1.md` — Technische implementatie planning
|
||||
- **UX/UI:** `swift-ux-v2.1.md` — Visuele specificaties
|
||||
|
||||
**Kernprincipe:**
|
||||
> Een behandelaar kan een volledig diagnostiek-traject doorlopen via natuurlijke taal: van afspraak plannen, via rapportage schrijven, tot diagnose stellen — alles in één vloeiende flow zonder menu-navigatie.
|
||||
|
||||
---
|
||||
|
||||
## 2. Overzicht van de belangrijkste onderdelen
|
||||
|
||||
🎯 **Doel:** Overzicht van de modules en blocks binnen de diagnostiek workflow.
|
||||
|
||||
### 2.1 Workflow Componenten
|
||||
|
||||
| # | Component | Beschrijving | Type |
|
||||
|---|-----------|--------------|------|
|
||||
| 1 | **AfspraakBlock** | Diagnostiek-afspraak plannen | Block |
|
||||
| 2 | **RapportageBlock** | Verslag schrijven na afspraak | Block |
|
||||
| 3 | **DiagnoseBlock** | Overzicht van diagnoses | Block |
|
||||
| 4 | **DiagnoseFormBlock** | Diagnose aanmaken/bijstellen | Block |
|
||||
|
||||
### 2.2 Workflow Flow
|
||||
|
||||
```
|
||||
Afspraak Plannen → Rapportage Schrijven → Diagnose Bekijken → Diagnose Aanmaken/Bijstellen
|
||||
```
|
||||
|
||||
### 2.3 Relatie met Bestaande Blocks
|
||||
|
||||
| Block | Relatie met Diagnostiek Workflow |
|
||||
|-------|----------------------------------|
|
||||
| **ZoekenBlock** | Wordt gebruikt voor patiëntselectie |
|
||||
| **PatientContextCard** | Toont actieve diagnoses in overzicht |
|
||||
| **AgendaBlock** | Toont geplande diagnostiek-afspraken |
|
||||
|
||||
---
|
||||
|
||||
## 3. User Stories
|
||||
|
||||
🎯 **Doel:** Beschrijven wat behandelaars moeten kunnen doen tijdens een diagnostiek-traject.
|
||||
|
||||
### 3.1 Diagnostiek Workflow Stories
|
||||
|
||||
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|
||||
|----|-----|--------------|------------------|------|
|
||||
| US-D01 | Behandelaar | Diagnostiek-afspraak plannen via spraak/tekst | Afspraak in < 30 sec zonder menu-navigatie | 🟡 P2 |
|
||||
| US-D02 | Behandelaar | Rapportage schrijven en koppelen aan afspraak | Verslag automatisch gekoppeld aan encounter | 🟡 P2 |
|
||||
| US-D03 | Behandelaar | Alle diagnoses van patiënt bekijken | Overzicht in één oogopslag | 🟡 P2 |
|
||||
| US-D04 | Behandelaar | Nieuwe diagnose toevoegen met ICD-10 zoeker | Diagnose toegevoegd met correcte code | 🟡 P2 |
|
||||
| US-D05 | Behandelaar | Bestaande diagnose bijstellen (status, ernst) | Wijzigingen direct zichtbaar | 🟡 P2 |
|
||||
|
||||
### 3.2 User Story Details
|
||||
|
||||
**US-D01: Diagnostiek-afspraak plannen**
|
||||
> Als behandelaar wil ik een diagnostiek-afspraak kunnen plannen door te zeggen "afspraak diagnostiek met Jan morgen 10:00" zodat ik snel kan plannen zonder door menu's te navigeren.
|
||||
|
||||
**US-D02: Rapportage koppelen aan afspraak**
|
||||
> Als behandelaar wil ik een rapportage kunnen schrijven die automatisch gekoppeld wordt aan de diagnostiek-afspraak zodat ik niet handmatig hoef te koppelen.
|
||||
|
||||
**US-D03: Diagnoses bekijken**
|
||||
> Als behandelaar wil ik alle diagnoses van een patiënt kunnen bekijken door te zeggen "diagnose Jan" zodat ik snel een overzicht heb zonder te navigeren.
|
||||
|
||||
**US-D04: Diagnose toevoegen**
|
||||
> Als behandelaar wil ik een nieuwe diagnose kunnen toevoegen met een ICD-10 zoeker zodat ik de juiste code kan vinden zonder handmatig te zoeken.
|
||||
|
||||
**US-D05: Diagnose bijstellen**
|
||||
> Als behandelaar wil ik een bestaande diagnose kunnen bijstellen (status, ernst) zodat ik diagnoses kan actualiseren na behandeling.
|
||||
|
||||
---
|
||||
|
||||
## 4. Functionele werking per onderdeel
|
||||
|
||||
🎯 **Doel:** Per component beschrijven wat de gebruiker kan doen en wat het systeem doet.
|
||||
|
||||
### 4.1 AfspraakBlock
|
||||
|
||||
**Functie:** Diagnostiek-afspraak plannen via natuurlijke taal.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "afspraak diagnostiek met [patient] [datum] [tijd]"
|
||||
- "plan diagnostiek voor [patient] morgen"
|
||||
- "afspraak [patient] volgende week dinsdag 10:00"
|
||||
|
||||
**Pre-fill logica:**
|
||||
|
||||
| Extracted | Pre-fill |
|
||||
|-----------|----------|
|
||||
| patient_name → match | Patiënt selector |
|
||||
| "diagnostiek" keyword | Type = Diagnostiek |
|
||||
| "morgen", "vandaag", datum | Datum picker |
|
||||
| Tijd (10:00, 14:30) | Starttijd |
|
||||
| Geen tijd | Default 09:00 |
|
||||
|
||||
**Form velden:**
|
||||
|
||||
| Veld | Type | Verplicht | Default |
|
||||
|------|------|-----------|---------|
|
||||
| Patiënt | Dropdown + search | Ja | Pre-filled of ZoekenBlock |
|
||||
| Datum | Date picker | Ja | Pre-filled of vandaag |
|
||||
| Starttijd | Time picker | Ja | Pre-filled of 09:00 |
|
||||
| Eindtijd | Time picker | Nee | Starttijd + 1 uur |
|
||||
| Type | Dropdown | Ja | Diagnostiek (pre-selected) |
|
||||
| Locatie | Dropdown | Nee | AMB |
|
||||
|
||||
**Afspraak types:**
|
||||
- Diagnostiek
|
||||
- Behandeling
|
||||
- Evaluatie
|
||||
- Consult
|
||||
- Overig
|
||||
|
||||
**Acties:**
|
||||
|
||||
| Knop | Actie | Keyboard |
|
||||
|------|-------|----------|
|
||||
| Opslaan | POST naar API, sluit block, retourneert encounter_id | `⌘Enter` |
|
||||
| Annuleren | Sluit block zonder opslaan | `Escape` |
|
||||
|
||||
**Na opslaan:**
|
||||
1. Toast: "✓ Afspraak diagnostiek met Jan de Vries aangemaakt voor morgen 10:00"
|
||||
2. Block verdwijnt (200ms animatie)
|
||||
3. Recent strip: badge "[📅 Jan - Diagnostiek]"
|
||||
4. Encounter_id wordt opgeslagen in context voor volgende stap
|
||||
|
||||
**API:**
|
||||
```
|
||||
POST /api/appointments
|
||||
Body: {
|
||||
patient_id: string,
|
||||
period_start: datetime,
|
||||
period_end: datetime,
|
||||
type_code: 'diagnostiek',
|
||||
class_code: 'AMB',
|
||||
notes?: string
|
||||
}
|
||||
Response: {
|
||||
id: string (encounter_id),
|
||||
success: boolean
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 RapportageBlock (Uitgebreid)
|
||||
|
||||
**Functie:** Verslag schrijven na diagnostiek-afspraak, gekoppeld aan encounter.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "rapportage diagnostiek gesprek met [patient]"
|
||||
- "verslag van diagnostiek afspraak [patient]"
|
||||
- "rapportage [patient]" (als recente diagnostiek-afspraak bestaat)
|
||||
|
||||
**Pre-fill logica:**
|
||||
|
||||
| Extracted | Pre-fill |
|
||||
|-----------|----------|
|
||||
| patient_name → match | Patiënt selector |
|
||||
| "diagnostiek" keyword | Type = Diagnostiek |
|
||||
| Recente encounter (diagnostiek) | Encounter_id gekoppeld |
|
||||
| Geen encounter | Geen koppeling, suggestie tonen |
|
||||
|
||||
**Form velden:**
|
||||
|
||||
| Veld | Type | Verplicht | Default |
|
||||
|------|------|-----------|---------|
|
||||
| Patiënt | Dropdown | Ja | Pre-filled |
|
||||
| Gekoppeld aan | Link naar encounter | Nee | Recente diagnostiek-afspraak |
|
||||
| Type | Button group | Ja | Diagnostiek (pre-selected) |
|
||||
| Inhoud | Rich text editor | Ja | Leeg |
|
||||
| Datum/tijd | DateTime | Ja | Nu |
|
||||
|
||||
**Rapportage types:**
|
||||
- Diagnostiek
|
||||
- Gesprek
|
||||
- Evaluatie
|
||||
- Telefonisch
|
||||
- Consult
|
||||
|
||||
**AI acties:**
|
||||
|
||||
| Actie | Beschrijving | Output |
|
||||
|-------|--------------|--------|
|
||||
| ✨ Samenvatten | Bullets van kernpunten | Zijpaneel |
|
||||
| 📖 B1-niveau | Herschrijf leesbaar | Zijpaneel |
|
||||
| 🔍 Problemen | Extraheer klinische issues | Zijpaneel |
|
||||
|
||||
**Acties:**
|
||||
|
||||
| Knop | Actie | Keyboard |
|
||||
|------|-------|----------|
|
||||
| Opslaan | POST naar API met encounter_id, sluit block | `⌘Enter` |
|
||||
| Annuleren | Sluit block zonder opslaan | `Escape` |
|
||||
|
||||
**Na opslaan:**
|
||||
1. Toast: "✓ Rapportage opgeslagen en gekoppeld aan afspraak"
|
||||
2. Block verdwijnt
|
||||
3. Recent strip: badge "[📋 Jan - Diagnostiek]"
|
||||
|
||||
**API:**
|
||||
```
|
||||
POST /api/reports
|
||||
Body: {
|
||||
patient_id: string,
|
||||
encounter_id: string, // Nieuwe: koppeling aan afspraak
|
||||
type: 'diagnostiek',
|
||||
content: string (HTML),
|
||||
timestamp: datetime
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3 DiagnoseBlock
|
||||
|
||||
**Functie:** Overzicht van alle diagnoses van een patiënt.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "diagnose [patient]"
|
||||
- "diagnoses van [patient]"
|
||||
- "wat zijn de diagnoses van [patient]"
|
||||
- Klik op "Diagnoses" in PatientContextCard
|
||||
|
||||
**Layout:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🏥 Diagnoses van Jan de Vries [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Filter: [Actief ✓] [Inactief] [Alle] │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ F41.1 Gegeneraliseerde angststoornis │ │
|
||||
│ │ Status: Actief | Ernst: Matig │ │
|
||||
│ │ Toegevoegd: 15 nov 2024 | Intake: Intake 1 │ │
|
||||
│ │ [Bewerken] [Details] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ F32.1 Depressieve stoornis │ │
|
||||
│ │ Status: Actief | Ernst: Mild │ │
|
||||
│ │ Toegevoegd: 20 dec 2024 | Intake: Intake 2 │ │
|
||||
│ │ [Bewerken] [Details] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [+ Nieuwe diagnose toevoegen] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Gedrag:**
|
||||
- Toont lijst met diagnoses, gesorteerd op datum (nieuwste eerst)
|
||||
- Filter tabs: Actief / Inactief / Alle
|
||||
- Per diagnose: code, omschrijving, status, ernst, datum, intake
|
||||
- Klik op diagnose → DiagnoseFormBlock (edit mode)
|
||||
- Klik "Nieuwe diagnose" → DiagnoseFormBlock (create mode)
|
||||
|
||||
**Empty state:**
|
||||
"Geen diagnoses gevonden voor [patient]. [+ Nieuwe diagnose toevoegen]"
|
||||
|
||||
**API:**
|
||||
```
|
||||
GET /api/diagnoses/:patientId
|
||||
Response: {
|
||||
diagnoses: [
|
||||
{
|
||||
id: string,
|
||||
code_code: string,
|
||||
code_display: string,
|
||||
clinical_status: 'active' | 'inactive' | 'resolved',
|
||||
severity_display: string,
|
||||
recorded_date: datetime,
|
||||
encounter_id: string,
|
||||
intake?: {
|
||||
id: string,
|
||||
title: string
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 DiagnoseFormBlock
|
||||
|
||||
**Functie:** Diagnose aanmaken of bijstellen met ICD-10 zoeker.
|
||||
|
||||
**Trigger patterns:**
|
||||
- "diagnose toevoegen [patient] [ICD-10 code]"
|
||||
- "diagnose wijzigen [patient]"
|
||||
- "diagnose bijstellen [patient]"
|
||||
- Klik op diagnose in DiagnoseBlock
|
||||
- Klik "Nieuwe diagnose" in DiagnoseBlock
|
||||
|
||||
**Pre-fill logica (create mode):**
|
||||
|
||||
| Extracted | Pre-fill |
|
||||
|-----------|----------|
|
||||
| patient_name → match | Patiënt selector (read-only) |
|
||||
| ICD-10 code (F41.1) | Code + omschrijving via zoeker |
|
||||
| Geen code | ICD-10 zoeker open |
|
||||
|
||||
**Pre-fill logica (edit mode):**
|
||||
|
||||
| Veld | Pre-fill |
|
||||
|------|----------|
|
||||
| Patiënt | Read-only, huidige waarde |
|
||||
| ICD-10 code | Huidige code |
|
||||
| Omschrijving | Huidige omschrijving |
|
||||
| Type | Huidige type (hoofd/neven) |
|
||||
| Status | Huidige status |
|
||||
| Ernst | Huidige ernst |
|
||||
| Intake | Huidige intake koppeling |
|
||||
| Toelichting | Huidige toelichting |
|
||||
|
||||
**Form velden:**
|
||||
|
||||
| Veld | Type | Verplicht | Default |
|
||||
|------|------|-----------|---------|
|
||||
| Patiënt | Read-only | Ja | Pre-filled |
|
||||
| ICD-10 zoeker | Search + dropdown | Ja | Leeg of pre-filled |
|
||||
| Code | Text (read-only na selectie) | Ja | Uit zoeker |
|
||||
| Omschrijving | Text (read-only na selectie) | Ja | Uit zoeker |
|
||||
| Type | Radio buttons | Ja | Hoofddiagnose |
|
||||
| Status | Dropdown | Ja | Actief |
|
||||
| Ernst | Dropdown | Nee | Geen |
|
||||
| Intake koppeling | Dropdown | Nee | Geen |
|
||||
| Toelichting | Textarea | Nee | Leeg |
|
||||
|
||||
**ICD-10 zoeker gedrag:**
|
||||
- Fuzzy search tijdens typen (minimaal 2 karakters)
|
||||
- Resultaten dropdown met code + omschrijving
|
||||
- Bij selectie: code en omschrijving worden ingevuld
|
||||
- Zoek op code (F41.1) of omschrijving (angst)
|
||||
|
||||
**Status opties:**
|
||||
- Actief
|
||||
- Inactief
|
||||
- Resolved
|
||||
- Remission
|
||||
- Recurrence
|
||||
- Relapse
|
||||
|
||||
**Ernst opties:**
|
||||
- Geen
|
||||
- Mild
|
||||
- Matig
|
||||
- Ernstig
|
||||
|
||||
**Type opties:**
|
||||
- Hoofddiagnose
|
||||
- Nevendiagnose
|
||||
|
||||
**Acties:**
|
||||
|
||||
| Knop | Actie | Keyboard |
|
||||
|------|-------|----------|
|
||||
| Opslaan | POST/PATCH naar API, sluit block | `⌘Enter` |
|
||||
| Annuleren | Sluit block zonder opslaan | `Escape` |
|
||||
| Verwijderen | Bevestigingsdialog → soft delete | - |
|
||||
|
||||
**Na opslaan:**
|
||||
1. Toast: "✓ Diagnose F41.1 - Gegeneraliseerde angststoornis toegevoegd" (of "bijgewerkt")
|
||||
2. Block verdwijnt
|
||||
3. DiagnoseBlock wordt automatisch getoond met nieuwe/bijgewerkte diagnose
|
||||
|
||||
**API:**
|
||||
```
|
||||
POST /api/diagnoses
|
||||
Body: {
|
||||
patient_id: string,
|
||||
encounter_id?: string,
|
||||
code_code: string,
|
||||
code_display: string,
|
||||
code_system: 'ICD-10',
|
||||
clinical_status: 'active' | 'inactive' | 'resolved',
|
||||
severity_display?: string,
|
||||
category: 'primary-diagnosis' | 'encounter-diagnosis',
|
||||
note?: string
|
||||
}
|
||||
|
||||
PATCH /api/diagnoses/:id
|
||||
Body: {
|
||||
code_code?: string,
|
||||
code_display?: string,
|
||||
clinical_status?: string,
|
||||
severity_display?: string,
|
||||
category?: string,
|
||||
note?: string
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. UI-overzicht (visuele structuur)
|
||||
|
||||
🎯 **Doel:** Globale schermopbouw voor diagnostiek workflow blocks.
|
||||
|
||||
### 5.1 AfspraakBlock Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 📅 Nieuwe Afspraak [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Patiënt * │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ Jan de Vries ✓ │ │
|
||||
│ │ 59 jaar • Kamer 12B [← Auto] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Datum * Van * Tot │
|
||||
│ ┌─────────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 2024-12-24 │ │ 10:00 │ │ 11:00 │ │
|
||||
│ └─────────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ Type afspraak * │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [Diagnostiek ▼] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Locatie │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [AMB ▼] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Notities (optioneel) │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Annuleren] [Opslaan] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 RapportageBlock Layout (met encounter koppeling)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 📋 Rapportage [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Patiënt: Jan de Vries [Wijzig] │
|
||||
│ │
|
||||
│ Gekoppeld aan: Afspraak diagnostiek - 24 dec 2024 10:00 │
|
||||
│ │
|
||||
│ Type │
|
||||
│ [Diagnostiek ✓] [Gesprek] [Evaluatie] [Telefonisch] │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [B] [I] [•] [1.] ["] 🎤 Dicteer │ │
|
||||
│ ├───────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ Verslag van diagnostiek gesprek... │ │
|
||||
│ │ │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ AI-acties │
|
||||
│ [✨ Samenvatten] [📖 B1-niveau] [🔍 Problemen extraheren] │
|
||||
│ │
|
||||
│ [Annuleren] [Opslaan] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.3 DiagnoseBlock Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🏥 Diagnoses van Jan de Vries [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Filter: [Actief ✓] [Inactief] [Alle] │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ F41.1 Gegeneraliseerde angststoornis │ │
|
||||
│ │ Status: Actief | Ernst: Matig │ │
|
||||
│ │ Toegevoegd: 15 nov 2024 | Intake: Intake 1 │ │
|
||||
│ │ │ │
|
||||
│ │ [Bewerken] [Details] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ F32.1 Depressieve stoornis │ │
|
||||
│ │ Status: Actief | Ernst: Mild │ │
|
||||
│ │ Toegevoegd: 20 dec 2024 | Intake: Intake 2 │ │
|
||||
│ │ │ │
|
||||
│ │ [Bewerken] [Details] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [+ Nieuwe diagnose toevoegen] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.4 DiagnoseFormBlock Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🏥 Nieuwe Diagnose [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Patiënt: Jan de Vries (read-only) │
|
||||
│ │
|
||||
│ ICD-10 Code * │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ 🔍 Zoek ICD-10 code of omschrijving... │ │
|
||||
│ │ │ │
|
||||
│ │ Resultaten: │ │
|
||||
│ │ • F41.1 Gegeneraliseerde angststoornis │ │
|
||||
│ │ • F41.0 Paniekstoornis │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Code: F41.1 (read-only na selectie) │
|
||||
│ Omschrijving: Gegeneraliseerde angststoornis (read-only) │
|
||||
│ │
|
||||
│ Type * │
|
||||
│ ○ Hoofddiagnose ● Nevendiagnose │
|
||||
│ │
|
||||
│ Status * │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [Actief ▼] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Ernst │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [Geen ▼] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Intake koppeling │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ [Geen ▼] │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Toelichting (optioneel) │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Annuleren] [Opslaan] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Interacties met AI (functionele beschrijving)
|
||||
|
||||
🎯 **Doel:** Waar AI in de diagnostiek workflow voorkomt.
|
||||
|
||||
| Locatie | AI-actie | Trigger | Output |
|
||||
|---------|----------|---------|--------|
|
||||
| RapportageBlock | Samenvatten | Klik knop "✨ Samenvatten" | Bullets van kernpunten in zijpaneel |
|
||||
| RapportageBlock | B1-niveau | Klik knop "📖 B1-niveau" | Herschreven tekst in zijpaneel |
|
||||
| RapportageBlock | Extract problemen | Klik knop "🔍 Problemen" | Gestructureerde lijst met categorie + severity |
|
||||
| DiagnoseFormBlock | ICD-10 suggestie | Typ in zoeker | Fuzzy search resultaten met relevante codes |
|
||||
|
||||
**AI Response Handling:**
|
||||
- Alle AI outputs tonen in dedicated preview area (zijpaneel)
|
||||
- Gebruiker moet expliciet accepteren/invoegen
|
||||
- Bewerken altijd mogelijk
|
||||
- Annuleren zonder gevolgen
|
||||
|
||||
---
|
||||
|
||||
## 7. Complete Workflow Flow
|
||||
|
||||
🎯 **Doel:** Stap-voor-stap beschrijving van de complete diagnostiek workflow.
|
||||
|
||||
### Flow 1: Van Afspraak tot Diagnose (Happy Path)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STAP 1: AFSPRAAK PLANNEN │
|
||||
│ │
|
||||
│ Behandelaar: "afspraak diagnostiek met Jan morgen 10:00" │
|
||||
│ → AfspraakBlock verschijnt met pre-fill │
|
||||
│ → Behandelaar controleert → klikt Opslaan │
|
||||
│ → Encounter aangemaakt, encounter_id opgeslagen │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STAP 2: RAPPORTAGE SCHRIJVEN (na afspraak) │
|
||||
│ │
|
||||
│ Behandelaar: "rapportage diagnostiek gesprek met Jan" │
|
||||
│ → RapportageBlock verschijnt │
|
||||
│ → Encounter_id automatisch gekoppeld │
|
||||
│ → Behandelaar schrijft/dicteert verslag │
|
||||
│ → Optioneel: AI samenvatten │
|
||||
│ → Klikt Opslaan → Rapportage gekoppeld aan encounter │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STAP 3: DIAGNOSE BEKIJKEN │
|
||||
│ │
|
||||
│ Behandelaar: "diagnose Jan" │
|
||||
│ → DiagnoseBlock verschijnt met lijst diagnoses │
|
||||
│ → Filter: Actief (default) │
|
||||
│ → Behandelaar ziet overzicht │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ STAP 4: DIAGNOSE TOEVOEGEN │
|
||||
│ │
|
||||
│ Behandelaar: "diagnose toevoegen Jan F41.1" │
|
||||
│ → DiagnoseFormBlock verschijnt │
|
||||
│ → ICD-10 code F41.1 pre-filled │
|
||||
│ → Behandelaar vult type, status, ernst in │
|
||||
│ → Koppelt aan intake (optioneel) │
|
||||
│ → Klikt Opslaan → Diagnose toegevoegd │
|
||||
│ → DiagnoseBlock wordt getoond met nieuwe diagnose │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Flow 2: Diagnose Bijstellen
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Behandelaar: "diagnose wijzigen Jan" │
|
||||
│ → DiagnoseBlock verschijnt │
|
||||
│ → Behandelaar klikt op diagnose F41.1 │
|
||||
│ → DiagnoseFormBlock verschijnt (edit mode) │
|
||||
│ → Alle velden pre-filled met huidige waarden │
|
||||
│ → Behandelaar wijzigt status: Actief → Resolved │
|
||||
│ → Behandelaar wijzigt ernst: Matig → Mild │
|
||||
│ → Voegt toelichting toe │
|
||||
│ → Klikt Opslaan → Diagnose bijgewerkt │
|
||||
│ → DiagnoseBlock wordt getoond met bijgewerkte diagnose │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Edge Cases & Alternatieve Flows
|
||||
|
||||
### Edge Case 1: Meerdere patiënten metzelfde naam
|
||||
- **Situatie:** Input "diagnose Jan" → meerdere matches
|
||||
- **Gedrag:** ZoekenBlock verschijnt met resultaten
|
||||
- **Actie:** Behandelaar selecteert juiste patiënt
|
||||
- **Vervolg:** DiagnoseBlock voor geselecteerde patiënt
|
||||
|
||||
### Edge Case 2: Geen recente afspraak voor rapportage
|
||||
- **Situatie:** Input "rapportage Jan" → geen encounter gevonden
|
||||
- **Gedrag:** RapportageBlock opent zonder encounter-koppeling
|
||||
- **Suggestie:** "Geen recente diagnostiek-afspraak gevonden. Wil je een afspraak koppelen?"
|
||||
- **Actie:** Optioneel AfspraakBlock openen
|
||||
|
||||
### Edge Case 3: ICD-10 code niet gevonden
|
||||
- **Situatie:** Input "diagnose toevoegen Jan F99.9" → code bestaat niet
|
||||
- **Gedrag:** Validatie fout: "ICD-10 code F99.9 niet gevonden"
|
||||
- **Actie:** ICD-10 zoeker blijft open voor correctie
|
||||
|
||||
### Edge Case 4: Diagnose al bestaat
|
||||
- **Situatie:** Input "diagnose toevoegen Jan F41.1" → diagnose bestaat al
|
||||
- **Gedrag:** Waarschuwing: "Diagnose F41.1 bestaat al. Wil je deze bijwerken?"
|
||||
- **Actie:** Optioneel DiagnoseFormBlock openen in edit mode
|
||||
|
||||
### Edge Case 5: Onvolledige input
|
||||
- **Situatie:** Input "afspraak diagnostiek" (geen patiënt/datum)
|
||||
- **Gedrag:** Systeem vraagt om ontbrekende informatie
|
||||
- **Actie:** ZoekenBlock voor patiënt, datum/tijd picker voor planning
|
||||
|
||||
---
|
||||
|
||||
## 9. Success Criteria
|
||||
|
||||
De diagnostiek workflow is succesvol wanneer:
|
||||
|
||||
1. ✅ Behandelaar kan diagnostiek-afspraak plannen in < 30 seconden
|
||||
2. ✅ Rapportage kan worden geschreven en automatisch gekoppeld aan afspraak
|
||||
3. ✅ Alle diagnoses van een patiënt zijn in één overzicht zichtbaar
|
||||
4. ✅ Nieuwe diagnose kan worden toegevoegd met ICD-10 zoeker
|
||||
5. ✅ Bestaande diagnose kan worden bijgewerkt (status, ernst, etc.)
|
||||
6. ✅ Alle acties zijn traceerbaar (wie, wanneer, wat)
|
||||
7. ✅ Workflow kan volledig worden doorlopen zonder menu-navigatie
|
||||
|
||||
---
|
||||
|
||||
## 10. Bijlagen & Referenties
|
||||
|
||||
**Projectdocumenten:**
|
||||
- PRD: `swift-prd.md`
|
||||
- FO Algemeen: `swift-fo-ai.md`
|
||||
- Bouwplan: `bouwplan-swift-v1.md`
|
||||
- UX/UI: `swift-ux-v2.1.md`
|
||||
- Taken analyse: `taken-en-vragen-analyse.md`
|
||||
|
||||
**Bestaande Code Referenties:**
|
||||
- Diagnose pagina: `app/epd/patients/[id]/diagnose/page.tsx`
|
||||
- Diagnose actions: `app/epd/patients/[id]/diagnose/actions.ts`
|
||||
- Appointment modal: `app/epd/agenda/components/appointment-modal.tsx`
|
||||
- Rapportage workspace: `app/epd/patients/[id]/rapportage/components/rapportage-workspace-v2.tsx`
|
||||
|
||||
**Database Schema:**
|
||||
- `conditions` tabel voor diagnoses
|
||||
- `encounters` tabel voor afspraken
|
||||
- `reports` tabel voor rapportages
|
||||
|
||||
---
|
||||
|
||||
## Wijzigingslog
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| 1.0 | 23-12-2024 | Initiële versie - Diagnostiek workflow beschrijving |
|
||||
|
||||
@@ -1,632 +0,0 @@
|
||||
# 📄 Product Requirements Document (PRD) — Contextual UI EPD
|
||||
|
||||
**Projectnaam:** Contextual UI EPD — "Het Slimme EPD"
|
||||
**Versie:** v1.0
|
||||
**Datum:** 23-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Doelstelling
|
||||
|
||||
🎯 **Doel van deze sectie:** Beschrijf waarom dit product of prototype wordt gebouwd en wat het beoogde resultaat is.
|
||||
|
||||
### Primair doel
|
||||
|
||||
Uitbreiden van het bestaande Speedrun EPD met **Contextual UI**: een systeem waarin voorgedefinieerde interface-componenten **automatisch verschijnen op basis van context** — wie je bent, welke patiënt actief is, welk moment van de dag het is, en wat je probeert te doen.
|
||||
|
||||
### Het kernprincipe
|
||||
|
||||
> **"Pre-built components, smart triggers"**
|
||||
>
|
||||
> We genereren geen UI on-the-fly (te onvoorspelbaar voor zorg), maar tonen **geteste bouwblokken op het juiste moment**. De AI zit in het *bepalen wanneer* en het *vullen van content*, niet in het genereren van interface-elementen.
|
||||
|
||||
### Het contrast met traditionele EPD's
|
||||
|
||||
| Aspect | Traditioneel EPD | Contextual UI EPD |
|
||||
|--------|------------------|-------------------|
|
||||
| Navigatie | 47 menu-items, tabbladen, submenu's | Context bepaalt wat je ziet |
|
||||
| Interface | Altijd alles zichtbaar | Alleen wat nu relevant is |
|
||||
| Timing | Gebruiker zoekt zelf | Systeem toont proactief |
|
||||
| Klikken | 12 klikken voor rapportage | 1 zin of automatisch |
|
||||
| Context | Gebruiker moet onthouden | Systeem begrijpt situatie |
|
||||
|
||||
### Secundaire doelen
|
||||
|
||||
- Demonstreren van "intelligent interface" concept voor gesprekken met Nedap, Medicore, etc.
|
||||
- LinkedIn content over next-gen EPD interfaces
|
||||
- Technische showcase van context-aware React componenten
|
||||
- Valideren of "proactieve UI" gewaardeerd wordt door zorgprofessionals
|
||||
|
||||
### Relatie met Speedrun EPD
|
||||
|
||||
Dit is **geen nieuw product** maar een uitbreiding op de bestaande codebase:
|
||||
- Hergebruik van alle database schemas
|
||||
- Hergebruik van bestaande API routes en AI-functionaliteit
|
||||
- Hergebruik van UI componenten (speech recorder, editors, etc.)
|
||||
- Toevoeging van Context Engine en Trigger System
|
||||
|
||||
---
|
||||
|
||||
## 2. Doelgroep
|
||||
|
||||
🎯 **Doel:** Schets wie de eindgebruikers, stakeholders en testers zijn.
|
||||
|
||||
### Primaire gebruikers
|
||||
|
||||
| Rol | Behoeften | Pijnpunten vandaag |
|
||||
|-----|-----------|-------------------|
|
||||
| **Verpleegkundige** | Snelle notities tussen zorgmomenten | 40% tijd aan administratie, zoeken naar juiste scherm |
|
||||
| **SPV/Behandelaar** | Rapportage na gesprek, overdracht | 20 min typen na crisis-interventie |
|
||||
| **Teamleider** | Overzicht, overdracht ontvangen | Informatie verspreid over schermen |
|
||||
|
||||
### Secundaire stakeholders
|
||||
|
||||
| Rol | Interesse |
|
||||
|-----|-----------|
|
||||
| **ICT Manager** | Minder training nodig, snellere adoptie |
|
||||
| **Product Owner (demo)** | AI-toegevoegde waarde zien |
|
||||
| **Developer (inspiratie)** | Context-aware UI patterns leren |
|
||||
|
||||
### Gebruikerscontext
|
||||
|
||||
De interface moet werken in situaties waar:
|
||||
- Handen bezet zijn (handschoenen, zorghandelingen)
|
||||
- Tijd schaars is (tussen patiënten door)
|
||||
- Concentratie elders ligt (na emotioneel gesprek)
|
||||
- Meerdere patiënten tegelijk aandacht vragen
|
||||
|
||||
---
|
||||
|
||||
## 3. Kernfunctionaliteiten (MVP-scope)
|
||||
|
||||
🎯 **Doel:** Afbakenen van de minimale werkende functies.
|
||||
|
||||
### 3.1 Architectuur: De Drie Lagen
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ LAAG 1: CONTEXT ENGINE │
|
||||
│ Houdt bij: gebruiker, patiënt, tijd, dienst, recente │
|
||||
│ acties. Zustand store + Supabase realtime. │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ LAAG 2: TRIGGER SYSTEM │
|
||||
│ Rules engine die bepaalt wanneer welk component │
|
||||
│ verschijnt. Combinatie van tijd, events, en intent. │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ LAAG 3: PRE-BUILT COMPONENTS │
|
||||
│ Geteste UI-blokken die door triggers worden getoond │
|
||||
│ en automatisch worden gevuld met relevante data. │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 MVP Componenten
|
||||
|
||||
| # | Component | Trigger | Data/AI | Prioriteit |
|
||||
|---|-----------|---------|---------|------------|
|
||||
| 1 | **Command Input** | Altijd zichtbaar (centraal) | Voice + text | Must |
|
||||
| 2 | **DagnotatieBlock** | Intent "notitie" of voice | Pre-fill patiënt, categorie | Must |
|
||||
| 3 | **ZoekenBlock** | Intent "zoek" of patiëntnaam | Fuzzy search results | Must |
|
||||
| 4 | **PatientContextCard** | Patiënt geselecteerd | Laatste rapportages, alerts | Must |
|
||||
| 5 | **RapportageBlock** | Intent "gesprek" of "rapportage" | AI-samenvatting optie | Should |
|
||||
| 6 | **OverdrachtPanel** | Tijd = einde dienst OF intent | AI-samenvatting per patiënt | Should |
|
||||
| 7 | **AgendaContextCard** | Afspraak binnen 15 min | Patiënt + laatste contact | Could |
|
||||
| 8 | **FallbackPicker** | Lage intent confidence | Grid met alle opties | Must |
|
||||
|
||||
### 3.3 Context Engine Specificatie
|
||||
|
||||
```typescript
|
||||
interface ContextState {
|
||||
// Gebruiker
|
||||
currentUser: {
|
||||
id: string;
|
||||
name: string;
|
||||
role: 'verpleegkundige' | 'behandelaar' | 'teamleider';
|
||||
};
|
||||
|
||||
// Dienst
|
||||
currentShift: {
|
||||
type: 'ochtend' | 'middag' | 'avond' | 'nacht';
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
patients: string[]; // IDs van toegewezen patiënten
|
||||
};
|
||||
|
||||
// Actieve patiënt (sticky)
|
||||
activePatient: {
|
||||
id: string;
|
||||
name: string;
|
||||
lastContact: Date;
|
||||
alerts: Alert[];
|
||||
} | null;
|
||||
|
||||
// Recente acties
|
||||
recentActions: Action[]; // Laatste 5-10
|
||||
|
||||
// Tijd-gevoelige context
|
||||
upcomingAppointment: Appointment | null; // Binnen 30 min
|
||||
shiftEndingSoon: boolean; // < 1 uur tot einde
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Trigger Rules Specificatie
|
||||
|
||||
| Trigger Type | Conditie | Actie | Prioriteit |
|
||||
|--------------|----------|-------|------------|
|
||||
| **Intent** | Voice/text input geclassificeerd | Open bijbehorend block | 1 (hoogste) |
|
||||
| **Patient Select** | Patiënt aangeklikt/gevonden | Toon PatientContextCard | 2 |
|
||||
| **Time: Appointment** | Afspraak binnen 15 min | Toon AgendaContextCard | 3 |
|
||||
| **Time: Shift End** | < 1 uur tot einde dienst | Suggestie OverdrachtPanel | 4 |
|
||||
| **Fallback** | Intent confidence < 0.7 | Toon FallbackPicker | 5 (laagste) |
|
||||
|
||||
### 3.5 Intent Classification
|
||||
|
||||
**Two-tier approach voor snelheid:**
|
||||
|
||||
```typescript
|
||||
// Tier 1: Local keyword matching (< 10ms)
|
||||
const quickMatch = (input: string): IntentResult | null => {
|
||||
const patterns = [
|
||||
{ regex: /notitie|dagnotitie|noteren/i, intent: 'dagnotitie' },
|
||||
{ regex: /zoek|vind|wie is/i, intent: 'zoeken' },
|
||||
{ regex: /overdracht|dienst\s*(eindigt|klaar)/i, intent: 'overdracht' },
|
||||
{ regex: /rapport|gesprek|consult/i, intent: 'rapportage' },
|
||||
{ regex: /afspraken?|agenda|planning/i, intent: 'agenda' },
|
||||
];
|
||||
// ... matching logic
|
||||
};
|
||||
|
||||
// Tier 2: Claude API (fallback, < 500ms)
|
||||
const aiClassify = async (input: string): Promise<IntentResult> => {
|
||||
// Alleen aangeroepen als quickMatch null of low confidence
|
||||
};
|
||||
```
|
||||
|
||||
**Entity Extraction:**
|
||||
|
||||
Naast intent ook extraheren:
|
||||
- `patient_name`: "Jan de Vries", "mevrouw Jansen"
|
||||
- `category`: "medicatie", "ADL", "incident"
|
||||
- `time_reference`: "vandaag", "gisteren", "afgelopen dienst"
|
||||
|
||||
---
|
||||
|
||||
## 4. Gebruikersflows (Demo- of MVP-flows)
|
||||
|
||||
🎯 **Doel:** Laten zien hoe de gebruiker stap-voor-stap door het systeem gaat.
|
||||
|
||||
### Flow 1: Snelle Dagnotitie via Voice (30 seconden)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 1. Gebruiker opent Command Center │
|
||||
│ → Ziet: lege input, context "Ochtend | 8 patiënten" │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 2. Spreekt: "Notitie Jan de Vries medicatie gegeven" │
|
||||
│ → Deepgram transcribeert real-time │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 3. System analyseert: │
|
||||
│ → Intent: dagnotitie (confidence 0.95) │
|
||||
│ → Patient: "Jan de Vries" → ID lookup │
|
||||
│ → Category: "Medicatie" │
|
||||
│ → Text: "medicatie gegeven" │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 4. DagnotatieBlock verschijnt: │
|
||||
│ → Patient: Jan de Vries ✓ (pre-filled) │
|
||||
│ → Categorie: Medicatie ✓ (pre-filled) │
|
||||
│ → Tekst: "medicatie gegeven" ✓ (pre-filled) │
|
||||
│ → [Opslaan] knop highlighted │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 5. Gebruiker: review → klik Opslaan │
|
||||
│ → Toast: "Notitie opgeslagen" │
|
||||
│ → Block verdwijnt │
|
||||
│ → Recent badge: "Jan - Medicatie" verschijnt │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Flow 2: Patiënt Opzoeken + Context (45 seconden)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 1. Gebruiker typt: "Jan" │
|
||||
│ → Intent: zoeken │
|
||||
│ → Query: "Jan" │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 2. ZoekenBlock verschijnt met matches: │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ Jan de Vries │ 15-03-1965 │ Laatst: 2u │ │
|
||||
│ │ Jan Bakker │ 22-08-1978 │ Laatst: 1d │ │
|
||||
│ │ Jantine Smit │ 04-11-1990 │ Laatst: 3d │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 3. Gebruiker klikt "Jan de Vries" │
|
||||
│ → activePatient wordt gezet │
|
||||
│ → ZoekenBlock sluit │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 4. PatientContextCard verschijnt: │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ JAN DE VRIES (59) │ │
|
||||
│ │ ───────────────────────────────────────── │ │
|
||||
│ │ Laatste contact: 2 uur geleden │ │
|
||||
│ │ │ │
|
||||
│ │ Recente notities: │ │
|
||||
│ │ • 09:15 Medicatie uitgereikt │ │
|
||||
│ │ • Gisteren: Goed gesprek over ontslag │ │
|
||||
│ │ │ │
|
||||
│ │ ⚠️ Let op: 2 valincidenten deze week │ │
|
||||
│ │ │ │
|
||||
│ │ [Notitie] [Rapportage] [Behandelplan] │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 5. Gebruiker klikt [Notitie] │
|
||||
│ → DagnotatieBlock opent met Jan pre-filled │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Flow 3: Automatische Overdracht Suggestie (proactief)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Context: Het is 15:15, dienst eindigt om 16:00 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 1. System detecteert: shiftEndingSoon = true │
|
||||
│ → Subtiele banner verschijnt: │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ 🕐 Dienst eindigt over 45 min │ │
|
||||
│ │ Wil je alvast de overdracht voorbereiden? │ │
|
||||
│ │ [Start overdracht] [Later] │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 2. Gebruiker klikt [Start overdracht] │
|
||||
│ → OverdrachtPanel opent │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 3. OverdrachtPanel toont: │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ OVERDRACHT OCHTEND → MIDDAG │ │
|
||||
│ │ 08:00 - 16:00 | 8 patiënten │ │
|
||||
│ │ ───────────────────────────────────────── │ │
|
||||
│ │ │ │
|
||||
│ │ ▼ Jan de Vries │ │
|
||||
│ │ AI-samenvatting: Rustige ochtend, medicatie│ │
|
||||
│ │ uitgereikt zonder problemen. Let op val- │ │
|
||||
│ │ risico bij toiletbezoek. │ │
|
||||
│ │ [Bronnen: 3 notities] │ │
|
||||
│ │ │ │
|
||||
│ │ ▼ Marie van den Berg │ │
|
||||
│ │ AI-samenvatting: ... │ │
|
||||
│ │ │ │
|
||||
│ │ [Kopieer alles] [Verstuur naar collega] │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Flow 4: Ambigue Input met Fallback
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 1. Gebruiker typt: "medicatie" │
|
||||
│ → Intent: onduidelijk (dagnotitie? metingen?) │
|
||||
│ → Confidence: 0.5 │
|
||||
│ → Geen patiënt gespecificeerd │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 2. FallbackPicker verschijnt: │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ Wat wil je doen? │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ 📝 │ │ 🔍 │ │ 📋 │ │ │
|
||||
│ │ │ Notitie │ │ Zoeken │ │ Rapport │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ 🔄 │ │ 📅 │ │ 💊 │ │ │
|
||||
│ │ │Overdracht│ │ Agenda │ │ Meting │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ 3. Gebruiker klikt [Notitie] │
|
||||
│ → DagnotatieBlock opent │
|
||||
│ → "medicatie" ingevuld als tekst │
|
||||
│ → Vraagt om patiënt te selecteren │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Niet in Scope
|
||||
|
||||
🎯 **Doel:** Duidelijk maken wat (nog) niet wordt gebouwd.
|
||||
|
||||
### Expliciet uitgesloten (v1)
|
||||
|
||||
| Feature | Reden |
|
||||
|---------|-------|
|
||||
| **Volledig voice-only navigatie** | Voice is input, niet navigatie. Te foutgevoelig. |
|
||||
| **AI-gegenereerde UI componenten** | Onvoorspelbaar, niet geschikt voor zorg. Pre-built only. |
|
||||
| **Ambient listening** | Privacy concerns, v2+ feature |
|
||||
| **Multi-user realtime** | Complexiteit, niet nodig voor demo |
|
||||
| **Offline mode** | Complexiteit, later toevoegen |
|
||||
| **Behandelplan block** | Te complex, lage frequentie, bestaande UI voldoet |
|
||||
| **Intake block** | Wizard is complex, 1x/maand, lage waarde voor MVP |
|
||||
| **Metingen block** | Lage waarde-perceptie bij stakeholders |
|
||||
| **Native mobile app** | Responsive web is voldoende |
|
||||
| **FHIR/externe EPD integratie** | Post-MVP, apart project |
|
||||
|
||||
### Bewust versimpeld (v1)
|
||||
|
||||
| Aspect | Versimpeling |
|
||||
|--------|--------------|
|
||||
| **Diensten** | Hardcoded tijden (08-16, 16-23, 23-08) |
|
||||
| **Patiënt-toewijzing** | Alle patiënten zichtbaar, geen restricties |
|
||||
| **Alerts** | Alleen handmatig toegevoegd, geen automatische detectie |
|
||||
|
||||
---
|
||||
|
||||
## 6. Succescriteria
|
||||
|
||||
🎯 **Doel:** Objectieve meetlat voor een geslaagde oplevering.
|
||||
|
||||
### Functionele criteria
|
||||
|
||||
| Criterium | Target | Meetmethode |
|
||||
|-----------|--------|-------------|
|
||||
| Intent classification accuracy | > 85% | Test set van 50 voorbeelden |
|
||||
| Voice transcription accuracy | > 90% | Handmatige review sample |
|
||||
| Pre-fill correctheid | > 90% | Juiste patiënt/categorie |
|
||||
| Fallback usage | < 25% | Picker clicks / totaal |
|
||||
| "Notitie Jan medicatie" → save | < 30 sec | Timestamp logging |
|
||||
|
||||
### Performance criteria
|
||||
|
||||
| Criterium | Target |
|
||||
|-----------|--------|
|
||||
| Intent classification | < 500ms |
|
||||
| Block render | < 200ms |
|
||||
| Voice transcription latency | < 100ms |
|
||||
| PatientContextCard laden | < 300ms |
|
||||
|
||||
### UX criteria
|
||||
|
||||
| Criterium | Target |
|
||||
|-----------|--------|
|
||||
| Klikken tot taak compleet | Gemiddeld < 3 |
|
||||
| Training nodig | Zero (intuïtief) |
|
||||
| "Dit voelt als magie" feedback | Minimaal 1 test user |
|
||||
|
||||
### Business criteria
|
||||
|
||||
| Criterium | Target |
|
||||
|-----------|--------|
|
||||
| Demo-ready | Ja, 3 scenario's foutloos |
|
||||
| LinkedIn content | 2+ posts over concept |
|
||||
| Stakeholder interesse | Minimaal 1 concrete vervolgvraag |
|
||||
|
||||
---
|
||||
|
||||
## 7. Risico's & Mitigatie
|
||||
|
||||
🎯 **Doel:** Risico's vroeg signaleren en plannen hoe ermee om te gaan.
|
||||
|
||||
| Risico | Kans | Impact | Mitigatie |
|
||||
|--------|------|--------|-----------|
|
||||
| **Intent classification onnauwkeurig** | Medium | Hoog | Fallback picker altijd beschikbaar, local-first matching |
|
||||
| **Voice niet goed in Nederlands** | Laag | Medium | Deepgram NL model, tekst fallback altijd mogelijk |
|
||||
| **Pre-fill verkeerde patiënt** | Medium | Hoog | Altijd confirmation tonen, nooit blind opslaan |
|
||||
| **Context engine te complex** | Medium | Medium | Minimale context in v1, uitbreiden in v2 |
|
||||
| **Gebruikers missen "overzicht"** | Medium | Medium | Bestaande patiëntenlijst als alternatieve entry |
|
||||
| **Performance AI calls** | Medium | Medium | Local-first matching, streaming responses, caching |
|
||||
| **Scope creep** | Hoog | Hoog | Strikte MVP scope, parking lot voor ideeën |
|
||||
| **Demo deadline druk** | Medium | Hoog | Focus op 3 happy paths, polish later |
|
||||
|
||||
---
|
||||
|
||||
## 8. Roadmap / Vervolg (Post-MVP)
|
||||
|
||||
🎯 **Doel:** Richting geven aan toekomstige uitbreidingen.
|
||||
|
||||
### Fase 2: Extended Blocks (Post-MVP)
|
||||
|
||||
- **AgendaBlock** - Afspraken beheren, context bij naderende afspraak
|
||||
- **BehandelplanBlock** - Wrapper rond bestaande functionaliteit
|
||||
- **MetingenBlock** - Vitale functies invoer met trends
|
||||
|
||||
### Fase 3: Smart Triggers
|
||||
|
||||
- **Proactieve alerts** - "3 valincidenten, wil je een risico-analyse?"
|
||||
- **Behandelplan reminder** - "Plan verloopt over 2 weken"
|
||||
- **Automatische categorisatie** - ML-model voor notitie-types
|
||||
|
||||
### Fase 4: Team Features
|
||||
|
||||
- **Shift handover** - Gestructureerde overdracht workflow
|
||||
- **Team dashboard** - Overzicht alle patiënten, alerts
|
||||
- **Notificaties** - Push bij urgente updates
|
||||
|
||||
### Fase 5: Integraties
|
||||
|
||||
- **FHIR export** - Standaard zorgdata uitwisseling
|
||||
- **Externe EPD sync** - Koppeling met Nedap, PinkRoccade
|
||||
- **Calendar sync** - Google/Outlook afspraken importeren
|
||||
|
||||
---
|
||||
|
||||
## 9. Technische Architectuur (Overzicht)
|
||||
|
||||
### Nieuwe Routes
|
||||
|
||||
```
|
||||
/app
|
||||
/(app)
|
||||
/command-center
|
||||
/page.tsx # Hoofdscherm
|
||||
/components/
|
||||
CommandInput.tsx # Text + voice input
|
||||
BlockContainer.tsx # Generic block wrapper
|
||||
FallbackPicker.tsx # Block selection grid
|
||||
RecentActions.tsx # Recent badges
|
||||
|
||||
/api
|
||||
/intent
|
||||
/classify/route.ts # Intent classification
|
||||
/context
|
||||
/route.ts # Get/set user context
|
||||
/patients
|
||||
/search/route.ts # Fuzzy patient search
|
||||
```
|
||||
|
||||
### Building Blocks
|
||||
|
||||
```
|
||||
/components/building-blocks/
|
||||
/dagnotitie/
|
||||
DagnotatieBlock.tsx
|
||||
/zoeken/
|
||||
ZoekenBlock.tsx
|
||||
PatientCard.tsx
|
||||
/rapportage/
|
||||
RapportageBlock.tsx
|
||||
/overdracht/
|
||||
OverdrachtPanel.tsx
|
||||
/context/
|
||||
PatientContextCard.tsx
|
||||
AgendaContextCard.tsx
|
||||
/shared/
|
||||
BlockContainer.tsx
|
||||
BlockHeader.tsx
|
||||
```
|
||||
|
||||
### State Management (Zustand)
|
||||
|
||||
```typescript
|
||||
// stores/context-store.ts
|
||||
interface ContextStore {
|
||||
// User & shift
|
||||
currentUser: User;
|
||||
currentShift: Shift;
|
||||
|
||||
// Active patient (sticky)
|
||||
activePatient: Patient | null;
|
||||
setActivePatient: (patient: Patient | null) => void;
|
||||
|
||||
// Recent actions
|
||||
recentActions: Action[];
|
||||
addRecentAction: (action: Action) => void;
|
||||
|
||||
// Time-based context
|
||||
upcomingAppointment: Appointment | null;
|
||||
shiftEndingSoon: boolean;
|
||||
}
|
||||
|
||||
// stores/command-center-store.ts
|
||||
interface CommandCenterStore {
|
||||
// Active block
|
||||
activeBlock: BlockType | null;
|
||||
blockPrefill: Record<string, unknown>;
|
||||
|
||||
// Input state
|
||||
inputValue: string;
|
||||
isListening: boolean;
|
||||
transcript: string;
|
||||
|
||||
// Actions
|
||||
processInput: (text: string) => Promise<void>;
|
||||
openBlock: (type: BlockType, prefill?: object) => void;
|
||||
closeBlock: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Fasering & Sprint Planning
|
||||
|
||||
### Sprint 1: Foundation (Dag 1-2)
|
||||
|
||||
| Taak | Output | Uren |
|
||||
|------|--------|------|
|
||||
| Command Center page layout | `/app/command-center/page.tsx` | 2 |
|
||||
| CommandInput component | Text + submit | 2 |
|
||||
| Voice integratie | Mic button, Deepgram | 2 |
|
||||
| Context store setup | Zustand store | 2 |
|
||||
| BlockContainer wrapper | Generic frame | 1 |
|
||||
|
||||
**Deliverable:** Command Center opent, voice werkt, geen blocks nog.
|
||||
|
||||
### Sprint 2: Intent & Zoeken (Dag 3-4)
|
||||
|
||||
| Taak | Output | Uren |
|
||||
|------|--------|------|
|
||||
| Intent API route | `/api/intent/classify` | 3 |
|
||||
| Local keyword matching | Quick patterns | 2 |
|
||||
| Entity extraction | Patient name uit tekst | 2 |
|
||||
| ZoekenBlock | Patient cards + select | 3 |
|
||||
| FallbackPicker | Grid met icons | 2 |
|
||||
|
||||
**Deliverable:** "zoek jan" werkt, patient selectie mogelijk.
|
||||
|
||||
### Sprint 3: Dagnotitie Flow (Dag 5-6)
|
||||
|
||||
| Taak | Output | Uren |
|
||||
|------|--------|------|
|
||||
| DagnotatieBlock | Quick entry form | 3 |
|
||||
| Pre-fill logic | Patient + categorie | 2 |
|
||||
| Save flow + toast | Feedback | 1 |
|
||||
| PatientContextCard | Na selectie tonen | 3 |
|
||||
| Recent actions strip | Badges | 2 |
|
||||
|
||||
**Deliverable:** "notitie jan medicatie" werkt end-to-end.
|
||||
|
||||
### Sprint 4: Polish & Demo (Dag 7-8)
|
||||
|
||||
| Taak | Output | Uren |
|
||||
|------|--------|------|
|
||||
| RapportageBlock | Wrapper rond composer | 3 |
|
||||
| OverdrachtPanel | AI-samenvatting | 4 |
|
||||
| Animaties | Transitions | 2 |
|
||||
| Demo scenario's | 3 happy paths | 2 |
|
||||
| Bug fixes | Stability | 3 |
|
||||
|
||||
**Deliverable:** Demo-ready voor stakeholders.
|
||||
|
||||
### Totaal: ~42 uur over 8 dagen
|
||||
|
||||
---
|
||||
|
||||
## 11. Bijlagen & Referenties
|
||||
|
||||
🎯 **Doel:** Bronnen koppelen voor context en consistentie.
|
||||
|
||||
### Gerelateerde documenten
|
||||
|
||||
| Document | Locatie | Beschrijving |
|
||||
|----------|---------|--------------|
|
||||
| Ephemeral UI Research | `Ephemeral_UI_for_Healthcare...md` | Achtergrond contextual interfaces |
|
||||
| PRD Ephemeral UI EPD | `nextgen-epd-prd-ephemeral-ui-epd.md` | Originele visie document |
|
||||
| MVP Prioritering | `nextgen-epd-mvp-prioritering-ephemeral-ui.md` | Scope beslissingen |
|
||||
| Waarde-analyse | `nextgen-epd-waarde-analyse-ephemeral-ui.md` | Klant & PO perspectief |
|
||||
| FO Mini-ECD v2 | `fo-mini-ecd-v2.md` | Bestaande EPD functionaliteit |
|
||||
| TO Mini-ECD | `to-mini-ecd-v1_2.md` | Technische basis |
|
||||
|
||||
### Bestaande codebase (hergebruik)
|
||||
|
||||
| Component | Locatie | Hergebruik |
|
||||
|-----------|---------|------------|
|
||||
| Speech Recorder | `components/speech-recorder.tsx` | 100% |
|
||||
| Deepgram API | `api/deepgram/transcribe/route.ts` | 100% |
|
||||
| Toast System | `lib/hooks/use-toast.ts` | 100% |
|
||||
| Command (cmdk) | `components/ui/command.tsx` | 90% |
|
||||
| Dagregistratie Form | `app/epd/dagregistratie/` | 80% |
|
||||
| Report Types | `lib/types/report.ts` | 100% |
|
||||
|
||||
---
|
||||
|
||||
## Wijzigingslog
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| 1.0 | 23-12-2024 | Initiële versie |
|
||||
@@ -1,829 +0,0 @@
|
||||
# 🎨 UX/UI Design Document — Swift: Diagnostiek Workflow
|
||||
|
||||
**Projectnaam:** Swift — Diagnostiek Workflow
|
||||
**Versie:** v1.0
|
||||
**Datum:** 23-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Visie: Behandelaar Workflow via Natuurlijke Taal
|
||||
|
||||
### 1.1 Het Probleem voor Behandelaars
|
||||
|
||||
```
|
||||
TRADITIONEEL EPD - DIAGNOSTIEK WORKFLOW
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ Menu → Patiënten → Jan → Agenda → Nieuwe Afspraak → │
|
||||
│ Type: Diagnostiek → Datum/Tijd → Opslaan │
|
||||
│ │
|
||||
│ Menu → Patiënten → Jan → Rapportages → Nieuw → │
|
||||
│ Type: Diagnostiek → Koppel Afspraak → Zoek → Selecteer → │
|
||||
│ Schrijf verslag → Opslaan │
|
||||
│ │
|
||||
│ Menu → Patiënten → Jan → Diagnoses → Nieuw → │
|
||||
│ Zoek ICD-10 → Selecteer → Vul formulier → Opslaan │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Resultaat: 15-20 klikken, 10-15 minuten per diagnostiek-traject
|
||||
```
|
||||
|
||||
### 1.2 De Swift Oplossing
|
||||
|
||||
```
|
||||
SWIFT - DIAGNOSTIEK WORKFLOW
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ "afspraak diagnostiek met jan morgen 10:00" │
|
||||
│ → AfspraakBlock verschijnt voorgevuld │
|
||||
│ → Opslaan (1 klik) │
|
||||
│ │
|
||||
│ "rapportage diagnostiek gesprek met jan" │
|
||||
│ → RapportageBlock verschijnt met encounter koppeling │
|
||||
│ → Schrijf/dicteer → Opslaan (1 klik) │
|
||||
│ │
|
||||
│ "diagnose toevoegen jan F41.1" │
|
||||
│ → DiagnoseFormBlock verschijnt met ICD-10 pre-filled │
|
||||
│ → Vul aan → Opslaan (1 klik) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Resultaat: 3 zinnen, 3 klikken, 3-5 minuten per traject
|
||||
```
|
||||
|
||||
### 1.3 Core Design Principles (Diagnostiek Workflow)
|
||||
|
||||
| Principe | Betekenis voor Diagnostiek |
|
||||
|----------|---------------------------|
|
||||
| **Conversational** | "afspraak diagnostiek jan morgen 10:00" werkt direct |
|
||||
| **Contextual** | Systeem onthoudt encounter_id tussen stappen |
|
||||
| **Ephemeral** | Blocks verschijnen wanneer nodig, verdwijnen na opslaan |
|
||||
| **Complete Flow** | Van afspraak tot diagnose in één vloeiende flow |
|
||||
|
||||
---
|
||||
|
||||
## 2. Intent Mapping voor Diagnostiek Workflow
|
||||
|
||||
### 2.1 Diagnostiek Intents
|
||||
|
||||
| Intent | Trigger patterns | Block | Prio | Freq |
|
||||
|--------|-----------------|-------|------|------|
|
||||
| `afspraak_maken` | "afspraak diagnostiek met [patient] [datum] [tijd]"<br>"plan diagnostiek voor [patient] morgen"<br>"afspraak [patient] volgende week dinsdag 10:00" | `AfspraakBlock` | 🟡 P2 | 2-3x/week |
|
||||
| `rapportage` | "rapportage diagnostiek gesprek met [patient]"<br>"verslag van diagnostiek afspraak [patient]"<br>"rapportage [patient]" (als recente afspraak) | `RapportageBlock` | 🟡 P2 | 3-5x/week |
|
||||
| `diagnose_bekijken` | "diagnose [patient]"<br>"diagnoses van [patient]"<br>"wat zijn de diagnoses van [patient]" | `DiagnoseBlock` | 🟡 P2 | 3-5x/week |
|
||||
| `diagnose_toevoegen` | "diagnose toevoegen [patient] [ICD-10]"<br>"[patient] heeft [ICD-10]"<br>"diagnose [patient] [ICD-10]" | `DiagnoseFormBlock` | 🟡 P2 | 1-2x/week |
|
||||
| `diagnose_wijzigen` | "diagnose wijzigen [patient]"<br>"diagnose bijstellen [patient]" | `DiagnoseFormBlock` (edit) | 🟡 P2 | 1x/week |
|
||||
|
||||
### 2.2 Context-Triggered UI (Proactief)
|
||||
|
||||
| Trigger | Conditie | Wat verschijnt | Prio |
|
||||
|---------|----------|----------------|------|
|
||||
| **Recente afspraak** | Na afspraak opslaan | Suggestie: "Rapportage schrijven?" | 🟡 P2 |
|
||||
| **Rapportage zonder diagnose** | Na rapportage opslaan | Suggestie: "Diagnose toevoegen?" | 🟡 P2 |
|
||||
| **Diagnose verouderd** | Diagnose > 6 maanden oud | Suggestie: "Diagnose bijwerken?" | 🟢 P3 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Screen Architecture
|
||||
|
||||
### 3.1 Diagnostiek Blocks in Swift Layout
|
||||
|
||||
Alle diagnostiek blocks verschijnen in de **Canvas Area** van Swift:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🕐 Ochtend | 8 ptn Jan de Vries ▼ 👤 SV │
|
||||
│ CONTEXT BAR │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ DIAGNOSTIEK BLOCK │ │
|
||||
│ │ (AfspraakBlock / │ │
|
||||
│ │ RapportageBlock / │ │
|
||||
│ │ DiagnoseBlock / │ │
|
||||
│ │ DiagnoseFormBlock) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────┘ │
|
||||
│ │
|
||||
│ CANVAS AREA │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Recent: [📅 Jan-Diag] [📋 Jan-Rapp] [🏥 Jan-Diag] │
|
||||
│ RECENT STRIP │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 🎤 Typ of spreek wat je wilt doen... ⌘K │
|
||||
│ COMMAND INPUT │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 Block Sizes voor Diagnostiek
|
||||
|
||||
| Block | Size | Max-width | Reden |
|
||||
|-------|------|-----------|-------|
|
||||
| AfspraakBlock | Medium | 640px | Form met meerdere velden |
|
||||
| RapportageBlock | Large | 900px | Rich text editor + AI acties |
|
||||
| DiagnoseBlock | Medium | 640px | Overzicht lijst |
|
||||
| DiagnoseFormBlock | Medium | 640px | Form met ICD-10 zoeker |
|
||||
|
||||
---
|
||||
|
||||
## 4. Component Specifications
|
||||
|
||||
### 4.1 AfspraakBlock
|
||||
|
||||
**Functie:** Diagnostiek-afspraak plannen met pre-fill vanuit intent.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 📅 Nieuwe Afspraak [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Patiënt * │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Jan de Vries ✓ │ │
|
||||
│ │ 59 jaar • Kamer 12B [← Auto] │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Datum * Van * Tot │
|
||||
│ ┌─────────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 2024-12-24 │ │ 10:00 │ │ 11:00 │ │
|
||||
│ └─────────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ Type afspraak * │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ [Diagnostiek ▼] │ │
|
||||
│ │ • Diagnostiek │ │
|
||||
│ │ • Behandeling │ │
|
||||
│ │ • Evaluatie │ │
|
||||
│ │ • Consult │ │
|
||||
│ │ • Overig │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Locatie │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ [AMB ▼] │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Notities (optioneel) │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Annuleren] [💾 Opslaan] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Pre-fill Indicatoren:**
|
||||
- Patiënt naam: **Bold** + checkmark
|
||||
- Datum: **Highlighted** achtergrond
|
||||
- Tijd: **Highlighted** achtergrond
|
||||
- Type: **Pre-selected** in dropdown
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
- `⌘Enter` / `Ctrl+Enter`: Opslaan
|
||||
- `Escape`: Annuleren
|
||||
- `Tab`: Navigeer tussen velden
|
||||
|
||||
**Na Opslaan:**
|
||||
1. Toast: "✓ Afspraak diagnostiek met Jan de Vries aangemaakt voor morgen 10:00"
|
||||
2. Block verdwijnt (200ms slide-down animatie)
|
||||
3. Recent strip: badge "[📅 Jan - Diagnostiek]"
|
||||
4. Encounter_id opgeslagen in Swift store voor volgende stap
|
||||
|
||||
---
|
||||
|
||||
### 4.2 RapportageBlock (Uitgebreid)
|
||||
|
||||
**Functie:** Verslag schrijven na diagnostiek-afspraak met encounter koppeling.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 📋 Rapportage [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Patiënt: Jan de Vries [Wijzig] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🔗 Gekoppeld aan: Afspraak diagnostiek │ │
|
||||
│ │ 24 dec 2024 10:00 - 11:00 │ │
|
||||
│ │ [Ontkoppelen] │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Type │
|
||||
│ [Diagnostiek ✓] [Gesprek] [Evaluatie] [Telefonisch] [Consult]│
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ [B] [I] [•] [1.] ["] 🎤 Dicteer │ │
|
||||
│ ├─────────────────────────────────────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ │ Verslag van diagnostiek gesprek met Jan de Vries... │ │
|
||||
│ │ │ │
|
||||
│ │ [Rich text editor met formatting toolbar] │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ AI-acties │
|
||||
│ [✨ Samenvatten] [📖 B1-niveau] [🔍 Problemen extraheren] │
|
||||
│ │
|
||||
│ [Annuleren] [💾 Opslaan] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Encounter Koppeling:**
|
||||
- **Zichtbaar:** Als recente diagnostiek-afspraak bestaat
|
||||
- **Styling:** Link badge met encounter details
|
||||
- **Actie:** Klik "Ontkoppelen" om koppeling te verwijderen
|
||||
- **Empty state:** "Geen recente diagnostiek-afspraak gevonden. [+ Koppel afspraak]"
|
||||
|
||||
**AI Acties (Zijpaneel):**
|
||||
- **✨ Samenvatten:** Bullets van kernpunten
|
||||
- **📖 B1-niveau:** Herschreven tekst (leesbaar voor patiënt)
|
||||
- **🔍 Problemen:** Gestructureerde lijst met categorie + severity
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
- `⌘Enter` / `Ctrl+Enter`: Opslaan
|
||||
- `Escape`: Annuleren
|
||||
- `⌘B` / `Ctrl+B`: Bold
|
||||
- `⌘I` / `Ctrl+I`: Italic
|
||||
- `Space` (leeg): Start dicteer
|
||||
|
||||
**Na Opslaan:**
|
||||
1. Toast: "✓ Rapportage opgeslagen en gekoppeld aan afspraak"
|
||||
2. Block verdwijnt
|
||||
3. Recent strip: badge "[📋 Jan - Diagnostiek]"
|
||||
4. Suggestie: "Diagnose toevoegen?" (als nog geen diagnose)
|
||||
|
||||
---
|
||||
|
||||
### 4.3 DiagnoseBlock
|
||||
|
||||
**Functie:** Overzicht van alle diagnoses van een patiënt.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🏥 Diagnoses van Jan de Vries [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Filter: [Actief ✓] [Inactief] [Alle] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ F41.1 Gegeneraliseerde angststoornis │ │
|
||||
│ │ Status: ● Actief | Ernst: Matig │ │
|
||||
│ │ Toegevoegd: 15 nov 2024 | Intake: Intake 1 │ │
|
||||
│ │ │ │
|
||||
│ │ [✏️ Bewerken] [📄 Details] │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ F32.1 Depressieve stoornis │ │
|
||||
│ │ Status: ● Actief | Ernst: Mild │ │
|
||||
│ │ Toegevoegd: 20 dec 2024 | Intake: Intake 2 │ │
|
||||
│ │ │ │
|
||||
│ │ [✏️ Bewerken] [📄 Details] │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ F41.0 Paniekstoornis │ │
|
||||
│ │ Status: ○ Inactief | Ernst: - │ │
|
||||
│ │ Toegevoegd: 10 sep 2024 | Intake: Intake 1 │ │
|
||||
│ │ │ │
|
||||
│ │ [✏️ Bewerken] [📄 Details] │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [+ Nieuwe diagnose toevoegen] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Filter Tabs:**
|
||||
- **Actief:** Alleen diagnoses met status "active" (default)
|
||||
- **Inactief:** Alleen diagnoses met status "inactive" of "resolved"
|
||||
- **Alle:** Alle diagnoses
|
||||
|
||||
**Diagnose Card:**
|
||||
- **Code:** ICD-10 code (F41.1) in monospace font
|
||||
- **Omschrijving:** Volledige naam in bold
|
||||
- **Status:** Dot indicator (● = actief, ○ = inactief)
|
||||
- **Ernst:** Badge met kleur (Mild = groen, Matig = geel, Ernstig = rood)
|
||||
- **Metadata:** Datum + Intake link
|
||||
|
||||
**Empty State:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Geen diagnoses gevonden voor Jan de Vries │
|
||||
│ │
|
||||
│ [+ Nieuwe diagnose toevoegen] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
- `Escape`: Sluit block
|
||||
- `Enter` (op diagnose): Open DiagnoseFormBlock (edit mode)
|
||||
- `N`: Nieuwe diagnose toevoegen
|
||||
|
||||
---
|
||||
|
||||
### 4.4 DiagnoseFormBlock
|
||||
|
||||
**Functie:** Diagnose aanmaken of bijstellen met ICD-10 zoeker.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🏥 Nieuwe Diagnose [−] [×] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Patiënt: Jan de Vries (read-only) │
|
||||
│ │
|
||||
│ ICD-10 Code * │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🔍 Zoek ICD-10 code of omschrijving... │ │
|
||||
│ │ │ │
|
||||
│ │ Resultaten (bij typen): │ │
|
||||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ • F41.1 Gegeneraliseerde angststoornis │ │ │
|
||||
│ │ │ • F41.0 Paniekstoornis │ │ │
|
||||
│ │ │ • F41.2 Gemengde angst- en depressieve stoornis│ │ │
|
||||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Code: F41.1 (read-only na selectie) │
|
||||
│ Omschrijving: Gegeneraliseerde angststoornis (read-only) │
|
||||
│ │
|
||||
│ Type * │
|
||||
│ ○ Hoofddiagnose ● Nevendiagnose │
|
||||
│ │
|
||||
│ Status * │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ [Actief ▼] │ │
|
||||
│ │ • Actief │ │
|
||||
│ │ • Inactief │ │
|
||||
│ │ • Resolved │ │
|
||||
│ │ • Remission │ │
|
||||
│ │ • Recurrence │ │
|
||||
│ │ • Relapse │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Ernst │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ [Geen ▼] │ │
|
||||
│ │ • Geen │ │
|
||||
│ │ • Mild │ │
|
||||
│ │ • Matig │ │
|
||||
│ │ • Ernstig │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Intake koppeling (optioneel) │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ [Geen ▼] │ │
|
||||
│ │ • Geen │ │
|
||||
│ │ • Intake 1 - 15 nov 2024 │ │
|
||||
│ │ • Intake 2 - 20 dec 2024 │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Toelichting (optioneel) │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Annuleren] [💾 Opslaan] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**ICD-10 Zoeker Gedrag:**
|
||||
- **Trigger:** Typ minimaal 2 karakters
|
||||
- **Search:** Fuzzy search op code (F41.1) of omschrijving (angst)
|
||||
- **Results:** Dropdown met max 10 resultaten
|
||||
- **Selectie:** Klik op resultaat → code + omschrijving worden ingevuld (read-only)
|
||||
- **Pre-fill:** Als ICD-10 code in intent → automatisch zoeken en invullen
|
||||
|
||||
**Status Kleuren:**
|
||||
- **Actief:** Groen dot (●)
|
||||
- **Inactief:** Grijs dot (○)
|
||||
- **Resolved:** Blauw dot (●)
|
||||
|
||||
**Ernst Badges:**
|
||||
- **Geen:** Geen badge
|
||||
- **Mild:** Groen badge
|
||||
- **Matig:** Geel badge
|
||||
- **Ernstig:** Rood badge
|
||||
|
||||
**Edit Mode:**
|
||||
- Alle velden pre-filled met huidige waarden
|
||||
- Patiënt read-only
|
||||
- Code + omschrijving read-only (wijzig via zoeker)
|
||||
- Status, ernst, type, toelichting bewerkbaar
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
- `⌘Enter` / `Ctrl+Enter`: Opslaan
|
||||
- `Escape`: Annuleren
|
||||
- `Tab`: Navigeer tussen velden
|
||||
- `↑` `↓` (in zoeker): Navigeer resultaten
|
||||
- `Enter` (in zoeker): Selecteer resultaat
|
||||
|
||||
**Na Opslaan:**
|
||||
1. Toast: "✓ Diagnose F41.1 - Gegeneraliseerde angststoornis toegevoegd"
|
||||
2. Block verdwijnt
|
||||
3. DiagnoseBlock wordt automatisch getoond met nieuwe/bijgewerkte diagnose
|
||||
|
||||
---
|
||||
|
||||
## 5. Interaction Flows
|
||||
|
||||
### 5.1 Happy Path: Complete Diagnostiek Workflow (3-5 minuten)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ STAP 1: AFSPRAAK PLANNEN (30 sec) │
|
||||
│ │
|
||||
│ Behandelaar: "afspraak diagnostiek met jan morgen 10:00" │
|
||||
│ → AfspraakBlock verschijnt met pre-fill │
|
||||
│ ✓ Patiënt: Jan de Vries [highlighted] │
|
||||
│ ✓ Datum: morgen [highlighted] │
|
||||
│ ✓ Tijd: 10:00 [highlighted] │
|
||||
│ ✓ Type: Diagnostiek [pre-selected] │
|
||||
│ → Behandelaar review → klikt Opslaan │
|
||||
│ → Toast: "✓ Afspraak aangemaakt" │
|
||||
│ → Encounter_id opgeslagen │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ STAP 2: RAPPORTAGE SCHRIJVEN (2 min) │
|
||||
│ │
|
||||
│ Behandelaar: "rapportage diagnostiek gesprek met jan" │
|
||||
│ → RapportageBlock verschijnt │
|
||||
│ ✓ Patiënt: Jan de Vries │
|
||||
│ ✓ Gekoppeld aan: Afspraak diagnostiek - morgen 10:00 │
|
||||
│ ✓ Type: Diagnostiek [pre-selected] │
|
||||
│ → Behandelaar schrijft/dicteert verslag │
|
||||
│ → Optioneel: AI samenvatten │
|
||||
│ → Klikt Opslaan │
|
||||
│ → Toast: "✓ Rapportage gekoppeld aan afspraak" │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ STAP 3: DIAGNOSE BEKIJKEN (30 sec) │
|
||||
│ │
|
||||
│ Behandelaar: "diagnose jan" │
|
||||
│ → DiagnoseBlock verschijnt met overzicht │
|
||||
│ → Filter: Actief (default) │
|
||||
│ → Behandelaar ziet bestaande diagnoses │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ STAP 4: DIAGNOSE TOEVOEGEN (1 min) │
|
||||
│ │
|
||||
│ Behandelaar: "diagnose toevoegen jan F41.1" │
|
||||
│ → DiagnoseFormBlock verschijnt │
|
||||
│ ✓ Patiënt: Jan de Vries [read-only] │
|
||||
│ ✓ ICD-10: F41.1 [pre-filled] │
|
||||
│ ✓ Omschrijving: Gegeneraliseerde angststoornis [pre-filled] │
|
||||
│ → Behandelaar vult type, status, ernst in │
|
||||
│ → Koppelt aan intake (optioneel) │
|
||||
│ → Klikt Opslaan │
|
||||
│ → Toast: "✓ Diagnose toegevoegd" │
|
||||
│ → DiagnoseBlock wordt getoond met nieuwe diagnose │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
|
||||
TOTAAL: ~4 minuten (was: 10-15 minuten)
|
||||
```
|
||||
|
||||
### 5.2 Diagnose Bijstellen Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Behandelaar: "diagnose wijzigen jan" │
|
||||
│ → DiagnoseBlock verschijnt │
|
||||
│ → Behandelaar klikt op diagnose F41.1 │
|
||||
│ → DiagnoseFormBlock verschijnt (edit mode) │
|
||||
│ ✓ Alle velden pre-filled met huidige waarden │
|
||||
│ → Behandelaar wijzigt: │
|
||||
│ - Status: Actief → Resolved │
|
||||
│ - Ernst: Matig → Mild │
|
||||
│ - Toelichting: "Na behandeling verbeterd" │
|
||||
│ → Klikt Opslaan │
|
||||
│ → Toast: "✓ Diagnose bijgewerkt" │
|
||||
│ → DiagnoseBlock wordt getoond met bijgewerkte diagnose │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.3 Proactieve Suggesties Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Na Afspraak opslaan: │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ 💡 Rapportage schrijven voor deze afspraak? │ │
|
||||
│ │ [Ja] [Later] [×] │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Na Rapportage opslaan (zonder diagnose): │
|
||||
│ ┌───────────────────────────────────────────────────────────┐ │
|
||||
│ │ 💡 Diagnose toevoegen voor Jan de Vries? │ │
|
||||
│ │ [Ja] [Later] [×] │ │
|
||||
│ └───────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Visual Design Tokens
|
||||
|
||||
### 6.1 Block Colors (Dark Theme - Swift Context)
|
||||
|
||||
| Element | Hex | Gebruik |
|
||||
|---------|-----|---------|
|
||||
| Block Background | `#1E293B` | Block achtergrond |
|
||||
| Block Border | `#334155` | Block rand |
|
||||
| Text Primary | `#F1F5F9` | Hoofdtekst |
|
||||
| Text Secondary | `#94A3B8` | Subtekst |
|
||||
| Accent | `#3B82F6` | Primaire acties |
|
||||
| Success | `#10B981` | Bevestigingen |
|
||||
| Warning | `#F59E0B` | Waarschuwingen |
|
||||
| Error | `#EF4444` | Fouten |
|
||||
|
||||
### 6.2 Status Colors
|
||||
|
||||
| Status | Dot | Badge | Hex |
|
||||
|--------|-----|-------|-----|
|
||||
| Actief | ● | - | `#10B981` |
|
||||
| Inactief | ○ | - | `#64748B` |
|
||||
| Resolved | ● | - | `#3B82F6` |
|
||||
|
||||
### 6.3 Ernst Badges
|
||||
|
||||
| Ernst | Badge | Hex |
|
||||
|-------|-------|-----|
|
||||
| Geen | - | - |
|
||||
| Mild | Badge | `#10B981` |
|
||||
| Matig | Badge | `#F59E0B` |
|
||||
| Ernstig | Badge | `#EF4444` |
|
||||
|
||||
### 6.4 ICD-10 Code Styling
|
||||
|
||||
| Element | Font | Size | Color |
|
||||
|---------|------|------|-------|
|
||||
| Code | Monospace | 14px | `#F1F5F9` |
|
||||
| Omschrijving | Sans-serif | 16px | `#F1F5F9` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Keyboard Navigation
|
||||
|
||||
### 7.1 AfspraakBlock
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `⌘Enter` / `Ctrl+Enter` | Opslaan |
|
||||
| `Escape` | Annuleren |
|
||||
| `Tab` | Navigeer tussen velden |
|
||||
| `↑` `↓` | Navigeer dropdown opties |
|
||||
|
||||
### 7.2 RapportageBlock
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `⌘Enter` / `Ctrl+Enter` | Opslaan |
|
||||
| `Escape` | Annuleren |
|
||||
| `⌘B` / `Ctrl+B` | Bold |
|
||||
| `⌘I` / `Ctrl+I` | Italic |
|
||||
| `Space` (leeg) | Start dicteer |
|
||||
|
||||
### 7.3 DiagnoseBlock
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `Escape` | Sluit block |
|
||||
| `Enter` (op diagnose) | Open DiagnoseFormBlock (edit) |
|
||||
| `N` | Nieuwe diagnose |
|
||||
| `↑` `↓` | Navigeer diagnoses |
|
||||
|
||||
### 7.4 DiagnoseFormBlock
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `⌘Enter` / `Ctrl+Enter` | Opslaan |
|
||||
| `Escape` | Annuleren |
|
||||
| `Tab` | Navigeer tussen velden |
|
||||
| `↑` `↓` (in zoeker) | Navigeer resultaten |
|
||||
| `Enter` (in zoeker) | Selecteer resultaat |
|
||||
|
||||
---
|
||||
|
||||
## 8. Error States & Edge Cases
|
||||
|
||||
### 8.1 AfspraakBlock Errors
|
||||
|
||||
**Geen patiënt gevonden:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ Patiënt "Jan" niet gevonden │
|
||||
│ │
|
||||
│ [🔍 Zoek patiënt] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Ongeldige datum:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Datum * │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ morgen ❌ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ ⚠️ Ongeldige datum. Gebruik formaat: DD-MM-YYYY │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 8.2 RapportageBlock Errors
|
||||
|
||||
**Geen encounter gevonden:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Geen recente diagnostiek-afspraak gevonden │
|
||||
│ │
|
||||
│ [+ Koppel afspraak] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Lege rapportage:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ Rapportage mag niet leeg zijn │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 8.3 DiagnoseFormBlock Errors
|
||||
|
||||
**ICD-10 code niet gevonden:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ICD-10 Code * │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ F99.9 ❌ │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ ⚠️ ICD-10 code F99.9 niet gevonden │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Diagnose bestaat al:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ⚠️ Diagnose F41.1 bestaat al voor Jan de Vries │
|
||||
│ │
|
||||
│ [✏️ Bestaande diagnose bewerken] [✕ Annuleren] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Responsive Design
|
||||
|
||||
### 9.1 Mobile (< 768px)
|
||||
|
||||
**AfspraakBlock:**
|
||||
- Full-width block
|
||||
- Stacked form velden
|
||||
- Date/time pickers full-width
|
||||
- Bottom sheet voor dropdowns
|
||||
|
||||
**RapportageBlock:**
|
||||
- Full-width block
|
||||
- Rich text editor full-width
|
||||
- AI acties als buttons onder editor
|
||||
- Bottom sheet voor encounter koppeling
|
||||
|
||||
**DiagnoseBlock:**
|
||||
- Full-width block
|
||||
- Diagnose cards stacked
|
||||
- Filter tabs als chips
|
||||
- Swipe to edit
|
||||
|
||||
**DiagnoseFormBlock:**
|
||||
- Full-width block
|
||||
- Form velden stacked
|
||||
- ICD-10 zoeker full-width
|
||||
- Bottom sheet voor dropdowns
|
||||
|
||||
### 9.2 Tablet (768px - 1024px)
|
||||
|
||||
- Blocks blijven medium/large size
|
||||
- Form velden kunnen naast elkaar (waar logisch)
|
||||
- Dropdowns blijven inline
|
||||
|
||||
---
|
||||
|
||||
## 10. Accessibility
|
||||
|
||||
### 10.1 Screen Reader Support
|
||||
|
||||
- Alle form velden hebben labels
|
||||
- Status changes worden aangekondigd
|
||||
- Error messages zijn toegankelijk
|
||||
- Keyboard navigation volledig ondersteund
|
||||
|
||||
### 10.2 Focus Management
|
||||
|
||||
- Focus blijft in block na openen
|
||||
- Focus naar eerste veld bij nieuwe block
|
||||
- Focus naar error veld bij validatie fout
|
||||
- Focus naar recent strip na sluiten
|
||||
|
||||
### 10.3 Color Contrast
|
||||
|
||||
- Alle tekst voldoet aan WCAG AA (4.5:1)
|
||||
- Status indicators hebben tekst labels
|
||||
- Error states hebben icon + tekst
|
||||
|
||||
---
|
||||
|
||||
## 11. Animation & Transitions
|
||||
|
||||
### 11.1 Block Animations
|
||||
|
||||
**Openen:**
|
||||
- Slide up + fade in (200ms)
|
||||
- Scale: 0.95 → 1.0
|
||||
|
||||
**Sluiten:**
|
||||
- Slide down + fade out (200ms)
|
||||
- Scale: 1.0 → 0.95
|
||||
|
||||
**Pre-fill Highlight:**
|
||||
- Pulse animatie (2x) bij pre-filled velden
|
||||
- Duration: 600ms
|
||||
|
||||
### 11.2 Toast Notifications
|
||||
|
||||
- Slide in from bottom (300ms)
|
||||
- Auto-dismiss na 3 seconden
|
||||
- Hover: pause auto-dismiss
|
||||
|
||||
---
|
||||
|
||||
## 12. Component Checklist
|
||||
|
||||
### 🟡 P2: Diagnostiek Workflow Blocks
|
||||
|
||||
- [ ] AfspraakBlock
|
||||
- [ ] Pre-fill vanuit intent
|
||||
- [ ] Encounter_id teruggeven
|
||||
- [ ] Dark theme styling
|
||||
- [ ] Keyboard shortcuts
|
||||
- [ ] Error states
|
||||
|
||||
- [ ] RapportageBlock (uitgebreid)
|
||||
- [ ] Encounter koppeling
|
||||
- [ ] Type "diagnostiek"
|
||||
- [ ] Rich text editor
|
||||
- [ ] AI acties
|
||||
- [ ] Dark theme styling
|
||||
|
||||
- [ ] DiagnoseBlock
|
||||
- [ ] Overzicht diagnoses
|
||||
- [ ] Filter tabs (Actief/Inactief/Alle)
|
||||
- [ ] Diagnose cards
|
||||
- [ ] Empty state
|
||||
- [ ] Dark theme styling
|
||||
|
||||
- [ ] DiagnoseFormBlock
|
||||
- [ ] ICD-10 zoeker (fuzzy search)
|
||||
- [ ] Pre-fill vanuit intent
|
||||
- [ ] Edit mode
|
||||
- [ ] Status + ernst selectie
|
||||
- [ ] Dark theme styling
|
||||
|
||||
---
|
||||
|
||||
## 13. Summary: De Transformatie voor Behandelaars
|
||||
|
||||
```
|
||||
VAN: NAAR:
|
||||
────────────────────────────────────────────────────────────────
|
||||
|
||||
15-20 klikken per traject → 3 zinnen + 3 klikken
|
||||
10-15 minuten per traject → 3-5 minuten
|
||||
Menu navigatie → Natuurlijke taal
|
||||
Handmatige koppeling → Automatische koppeling
|
||||
ICD-10 handmatig zoeken → Fuzzy search + pre-fill
|
||||
Vergeten rapportage → Proactieve suggesties
|
||||
|
||||
TIJDSBESPARING:
|
||||
─────────────────────────────────────────────────────────────────
|
||||
Afspraak plannen: 2-3 min → 30 sec (85% sneller)
|
||||
Rapportage schrijven: 5-8 min → 2 min (75% sneller)
|
||||
Diagnose toevoegen: 3-5 min → 1 min (80% sneller)
|
||||
|
||||
Per behandelaar per week: ~2 uur terug naar patiëntcontact
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wijzigingslog
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| 1.0 | 23-12-2024 | Initiële versie - UX/UI specificaties voor diagnostiek workflow |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,486 +0,0 @@
|
||||
# Taken & Vragen Analyse: EPD Functionaliteit
|
||||
|
||||
**Document:** Wat moet de nieuwe UI kunnen?
|
||||
**Datum:** december 2024
|
||||
**Doel:** Inventarisatie van alle taken en vragen die het EPD beantwoordt
|
||||
|
||||
---
|
||||
|
||||
## 1. Overzicht Huidige EPD Modules
|
||||
|
||||
Op basis van de codebase analyse:
|
||||
|
||||
```
|
||||
/epd
|
||||
├── dashboard/ # Overzicht
|
||||
├── patients/ # Patiëntbeheer
|
||||
│ ├── [id]/
|
||||
│ │ ├── basisgegevens/ # NAW, contactgegevens
|
||||
│ │ ├── intakes/ # Intake trajecten
|
||||
│ │ │ └── [intakeId]/
|
||||
│ │ │ ├── anamnese/ # Voorgeschiedenis
|
||||
│ │ │ ├── examination/ # Onderzoek
|
||||
│ │ │ ├── diagnosis/ # Diagnose (ICD-10)
|
||||
│ │ │ ├── risk/ # Risicotaxatie
|
||||
│ │ │ ├── kindcheck/ # Kindcheck
|
||||
│ │ │ ├── contacts/ # Contactpersonen
|
||||
│ │ │ ├── behandeladvies/# Behandeladvies
|
||||
│ │ │ └── rom/ # ROM vragenlijsten
|
||||
│ │ ├── diagnose/ # Diagnosebeheer
|
||||
│ │ ├── behandelplan/ # Behandelplan
|
||||
│ │ ├── rapportage/ # Rapportages
|
||||
│ │ └── screening/ # Screening
|
||||
├── verpleegrapportage/ # Verpleegkundig overzicht
|
||||
│ ├── overdracht/ # Overdrachtsrapportage
|
||||
│ └── rapportage/ # Dagrapportage per patiënt
|
||||
├── agenda/ # Afspraken
|
||||
├── clients/ # Client overzicht
|
||||
└── reports/ # Rapportages overzicht
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Categorieën van Vragen
|
||||
|
||||
### 2.1 ZOEKEN & VINDEN (Wie/Wat/Waar)
|
||||
|
||||
| Vraag | Huidige UI | Data |
|
||||
|-------|------------|------|
|
||||
| Wie is [naam]? | patients/ → zoeken | patients |
|
||||
| Waar is [patiënt] opgenomen? | patients/[id] | encounters |
|
||||
| Welke diagnoses heeft [patiënt]? | patients/[id]/diagnose | conditions |
|
||||
| Wat is het behandelplan van [patiënt]? | patients/[id]/behandelplan | care_plans |
|
||||
| Welke medicatie gebruikt [patiënt]? | - (nog niet) | - |
|
||||
| Wie zijn de contactpersonen van [patiënt]? | intakes/[id]/contacts | contacts |
|
||||
| Wat is de risicoscore van [patiënt]? | intakes/[id]/risk | risk_assessments |
|
||||
|
||||
### 2.2 RAPPORTEREN & DOCUMENTEREN (Vastleggen)
|
||||
|
||||
| Taak | Huidige UI | Data |
|
||||
|------|------------|------|
|
||||
| Dagnotitie maken | verpleegrapportage/rapportage | reports (type: verpleegkundig) |
|
||||
| Rapportage schrijven | patients/[id]/rapportage | reports |
|
||||
| Intake vastleggen | patients/[id]/intakes/new | intakes |
|
||||
| Anamnese invullen | intakes/[id]/anamnese | anamneses |
|
||||
| Onderzoek documenteren | intakes/[id]/examination | examinations |
|
||||
| Diagnose toevoegen | patients/[id]/diagnose | conditions |
|
||||
| Behandeladvies schrijven | intakes/[id]/behandeladvies | - |
|
||||
| Risicotaxatie invullen | intakes/[id]/risk | risk_assessments |
|
||||
| Kindcheck uitvoeren | intakes/[id]/kindcheck | - |
|
||||
|
||||
### 2.3 SAMENVATTEN & OVERDRAGEN (Communiceren)
|
||||
|
||||
| Taak | Huidige UI | Data |
|
||||
|------|------------|------|
|
||||
| Overdracht maken | verpleegrapportage/overdracht | AI samenvatting |
|
||||
| Samenvatting van vandaag | - | reports (shift_date) |
|
||||
| Wat is er gebeurd met [patiënt]? | verpleegrapportage/rapportage | reports, vitals |
|
||||
| Aandachtspunten voor collega | overdracht | include_in_handover |
|
||||
|
||||
### 2.4 PLANNEN & ORGANISEREN (Agenda)
|
||||
|
||||
| Taak | Huidige UI | Data |
|
||||
|------|------------|------|
|
||||
| Mijn afspraken vandaag | agenda/ | appointments |
|
||||
| Afspraken van [patiënt] | - | appointments |
|
||||
| Afspraak inplannen | agenda/ (FullCalendar) | appointments |
|
||||
| Wie zie ik deze week? | agenda/ | appointments |
|
||||
|
||||
### 2.5 BEHANDELEN & VOLGEN (Zorgpad)
|
||||
|
||||
| Taak | Huidige UI | Data |
|
||||
|------|------------|------|
|
||||
| Behandelplan opstellen | patients/[id]/behandelplan | care_plans |
|
||||
| Doelen formuleren | behandelplan | care_plans.goals |
|
||||
| Voortgang evalueren | - | - |
|
||||
| ROM afnemen | intakes/[id]/rom | - |
|
||||
|
||||
---
|
||||
|
||||
## 3. Frequentie & Prioriteit Matrix
|
||||
|
||||
### 3.1 Hoogfrequent (meerdere keren per dag)
|
||||
|
||||
| Taak | Freq/dag | Huidige klikken | Ephemeral target |
|
||||
|------|----------|-----------------|------------------|
|
||||
| **Dagnotitie maken** | 10-20x | 5-8 | 1 zin |
|
||||
| **Patiënt zoeken** | 15-20x | 3-5 | 1 zin |
|
||||
| **Laatste notities bekijken** | 10-15x | 4-6 | 1 zin |
|
||||
| **Overdracht maken** | 2-3x | 8-12 | 1 zin |
|
||||
|
||||
### 3.2 Middenfrequent (dagelijks)
|
||||
|
||||
| Taak | Freq/dag | Huidige klikken | Ephemeral target |
|
||||
|------|----------|-----------------|------------------|
|
||||
| **Rapportage schrijven** | 3-5x | 6-10 | 1 zin + dicteren |
|
||||
| **Diagnose bekijken** | 3-5x | 4-6 | 1 zin |
|
||||
| **Behandelplan raadplegen** | 2-3x | 4-6 | 1 zin |
|
||||
| **Afspraken bekijken** | 2-3x | 3-4 | 1 zin |
|
||||
|
||||
### 3.3 Laagfrequent (wekelijks/maandelijks)
|
||||
|
||||
| Taak | Frequentie | Huidige klikken | Ephemeral target |
|
||||
|------|------------|-----------------|------------------|
|
||||
| **Intake starten** | 1x/week | 10-15 | Wizard |
|
||||
| **Behandelplan maken** | 1x/maand | 15-20 | Wizard + AI |
|
||||
| **Diagnose toevoegen** | 1x/week | 6-8 | 1 zin |
|
||||
| **Risicotaxatie** | 1x/maand | 10-15 | Wizard |
|
||||
|
||||
---
|
||||
|
||||
## 4. Mapping naar Ephemeral UI Bouwblokken
|
||||
|
||||
### 4.1 Must Have (MVP)
|
||||
|
||||
| Bouwblok | Beantwoordt vragen | Prioriteit |
|
||||
|----------|-------------------|------------|
|
||||
| **Dagnotitie** | "notitie voor jan: medicatie gegeven" | P1 |
|
||||
| **Zoeken** | "wie is jan", "zoek marie" | P1 |
|
||||
| **Overdracht** | "overdracht maken", "samenvatting dienst" | P1 |
|
||||
|
||||
### 4.2 Should Have
|
||||
|
||||
| Bouwblok | Beantwoordt vragen | Prioriteit |
|
||||
|----------|-------------------|------------|
|
||||
| **Rapportage** | "gesprek gehad met jan", "rapportage maken" | P2 |
|
||||
| **Patiënt Info** | "diagnoses van jan", "behandelplan jan" | P2 |
|
||||
| **Agenda** | "mijn afspraken", "wanneer zie ik jan" | P2 |
|
||||
|
||||
### 4.3 Could Have
|
||||
|
||||
| Bouwblok | Beantwoordt vragen | Prioriteit |
|
||||
|----------|-------------------|------------|
|
||||
| **Behandelplan** | "plan opstellen voor jan" | P3 |
|
||||
| **Diagnose** | "diagnose toevoegen: F41.1" | P3 |
|
||||
| **Risico** | "risicotaxatie jan" | P3 |
|
||||
| **Intake** | "nieuwe intake starten" | P3 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Intent Classificatie Mapping
|
||||
|
||||
### 5.1 Schrijf-intents (Writer)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTENT: dagnotitie │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Trigger patterns: │
|
||||
│ • "notitie [patient]" │
|
||||
│ • "dagnotitie" │
|
||||
│ • "[patient] medicatie gegeven" │
|
||||
│ • "[patient] heeft gegeten" │
|
||||
│ • "incident bij [patient]" │
|
||||
│ │
|
||||
│ Entities: │
|
||||
│ • patient_name: string │
|
||||
│ • category: medicatie|adl|gedrag|incident|observatie │
|
||||
│ • content: string (optioneel) │
|
||||
│ │
|
||||
│ Pre-fill: │
|
||||
│ • Patient selector │
|
||||
│ • Category dropdown │
|
||||
│ • Tekstveld │
|
||||
│ • include_in_handover checkbox │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTENT: rapportage │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Trigger patterns: │
|
||||
│ • "rapportage [patient]" │
|
||||
│ • "gesprek gehad met [patient]" │
|
||||
│ • "verslag maken" │
|
||||
│ • "sessie met [patient]" │
|
||||
│ │
|
||||
│ Entities: │
|
||||
│ • patient_name: string │
|
||||
│ • report_type: voortgang|observatie|contact|crisis │
|
||||
│ │
|
||||
│ Pre-fill: │
|
||||
│ • Patient selector │
|
||||
│ • Type dropdown │
|
||||
│ • Rich text editor │
|
||||
│ • AI structureren knop │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTENT: diagnose_toevoegen │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Trigger patterns: │
|
||||
│ • "diagnose [patient]: [code]" │
|
||||
│ • "diagnose toevoegen" │
|
||||
│ • "[patient] heeft [diagnose]" │
|
||||
│ │
|
||||
│ Entities: │
|
||||
│ • patient_name: string │
|
||||
│ • icd10_code: string (optioneel) │
|
||||
│ • diagnosis_text: string (optioneel) │
|
||||
│ │
|
||||
│ Pre-fill: │
|
||||
│ • Patient selector │
|
||||
│ • ICD-10 zoeken combobox │
|
||||
│ • Clinical status │
|
||||
│ • Severity │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 Lees-intents (Reader)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTENT: zoeken │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Trigger patterns: │
|
||||
│ • "zoek [naam]" │
|
||||
│ • "wie is [naam]" │
|
||||
│ • "vind [naam]" │
|
||||
│ • "[naam]" (als geen andere intent matcht) │
|
||||
│ │
|
||||
│ Entities: │
|
||||
│ • search_query: string │
|
||||
│ │
|
||||
│ Output: │
|
||||
│ • PatientCards met quick actions │
|
||||
│ • Selectie → set active patient │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTENT: patient_info │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Trigger patterns: │
|
||||
│ • "diagnoses van [patient]" │
|
||||
│ • "behandelplan [patient]" │
|
||||
│ • "info [patient]" │
|
||||
│ • "dossier [patient]" │
|
||||
│ • "risico's [patient]" │
|
||||
│ │
|
||||
│ Entities: │
|
||||
│ • patient_name: string │
|
||||
│ • info_type: diagnoses|behandelplan|risico|alles │
|
||||
│ │
|
||||
│ Output: │
|
||||
│ • Collapsible info cards │
|
||||
│ • Quick actions (bewerken, toevoegen) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTENT: overdracht │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Trigger patterns: │
|
||||
│ • "overdracht" │
|
||||
│ • "overdracht maken" │
|
||||
│ • "samenvatting dienst" │
|
||||
│ • "wat is er gebeurd vandaag" │
|
||||
│ │
|
||||
│ Entities: │
|
||||
│ • time_range: afgelopen 8 uur (default) │
|
||||
│ • patient_filter: string[] (optioneel) │
|
||||
│ │
|
||||
│ Output: │
|
||||
│ • AI-samenvatting per patiënt │
|
||||
│ • Bronverwijzingen │
|
||||
│ • Aandachtspunten │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ INTENT: agenda │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Trigger patterns: │
|
||||
│ • "mijn afspraken" │
|
||||
│ • "afspraken vandaag" │
|
||||
│ • "wanneer zie ik [patient]" │
|
||||
│ • "planning deze week" │
|
||||
│ │
|
||||
│ Entities: │
|
||||
│ • date_range: vandaag|deze week|datum │
|
||||
│ • patient_name: string (optioneel) │
|
||||
│ │
|
||||
│ Output: │
|
||||
│ • Afsprakenlijst │
|
||||
│ • Quick action: nieuwe afspraak │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Data Requirements per Intent
|
||||
|
||||
### 6.1 Database Tabellen per Bouwblok
|
||||
|
||||
| Bouwblok | Read | Write |
|
||||
|----------|------|-------|
|
||||
| **Dagnotitie** | patients | reports |
|
||||
| **Zoeken** | patients, conditions, care_plans | - |
|
||||
| **Rapportage** | patients, intakes | reports |
|
||||
| **Overdracht** | patients, reports, vitals, risk_assessments | - |
|
||||
| **Patient Info** | patients, conditions, care_plans, risk_assessments | - |
|
||||
| **Diagnose** | patients, conditions | conditions |
|
||||
| **Agenda** | patients, appointments, practitioners | appointments |
|
||||
| **Behandelplan** | patients, conditions, intakes, anamneses | care_plans |
|
||||
|
||||
### 6.2 API Routes per Bouwblok
|
||||
|
||||
| Bouwblok | Bestaande API | Nieuw nodig? |
|
||||
|----------|---------------|--------------|
|
||||
| **Dagnotitie** | POST /api/reports | Nee |
|
||||
| **Zoeken** | GET /api/patients/search | Ja (fuzzy) |
|
||||
| **Rapportage** | POST /api/reports | Nee |
|
||||
| **Overdracht** | GET /api/overdracht, POST /api/overdracht/generate | Nee |
|
||||
| **Patient Info** | GET /api/verpleegrapportage/[patientId] | Uitbreiden |
|
||||
| **Diagnose** | - | Ja |
|
||||
| **Agenda** | - | Ja |
|
||||
| **Behandelplan** | POST /api/behandelplan/generate | Nee |
|
||||
|
||||
---
|
||||
|
||||
## 7. Contextual Awareness
|
||||
|
||||
### 7.1 Impliciete Context
|
||||
|
||||
| Context | Bron | Gebruik |
|
||||
|---------|------|---------|
|
||||
| **Huidige gebruiker** | Auth session | Filter "mijn patiënten" |
|
||||
| **Huidige dienst** | Tijd (ochtend/middag/avond/nacht) | Shift-based filtering |
|
||||
| **Laatst bekeken patiënt** | Session state | Pre-fill suggestie |
|
||||
| **Recente acties** | Session state | Quick access |
|
||||
|
||||
### 7.2 Expliciete Context
|
||||
|
||||
| Context | Trigger | Effect |
|
||||
|---------|---------|--------|
|
||||
| **Actieve patiënt** | Zoeken + selecteren | Pre-fill alle volgende acties |
|
||||
| **Dienst overdracht** | "overdracht" | Filter op afgelopen X uur |
|
||||
| **Specifieke datum** | "afspraken morgen" | Filter op datum |
|
||||
|
||||
---
|
||||
|
||||
## 8. Voice Command Examples
|
||||
|
||||
### 8.1 Dagnotitie Flow
|
||||
|
||||
```
|
||||
Voice: "Jan de Vries heeft zijn medicatie ingenomen, geen bijzonderheden"
|
||||
|
||||
Intent: dagnotitie
|
||||
Entities:
|
||||
- patient_name: "Jan de Vries"
|
||||
- category: "medicatie" (extracted from "medicatie ingenomen")
|
||||
- content: "heeft zijn medicatie ingenomen, geen bijzonderheden"
|
||||
|
||||
Pre-fill:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 📝 Dagnotitie voor Jan de Vries │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ Categorie: [Medicatie ▼] ← auto-selected │
|
||||
│ Tijd: [14:32] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ heeft zijn medicatie ingenomen, geen bijzonderheden │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ☑ Opnemen in overdracht │
|
||||
│ │
|
||||
│ [Opslaan] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 8.2 Zoeken Flow
|
||||
|
||||
```
|
||||
Voice: "Zoek Marie"
|
||||
|
||||
Intent: zoeken
|
||||
Entities:
|
||||
- search_query: "Marie"
|
||||
|
||||
Output:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🔍 Zoekresultaten voor "Marie" │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Marie van den Berg │ │
|
||||
│ │ 15-03-1985 · Kamer 12 · F41.1 Gegeneraliseerde angst│ │
|
||||
│ │ [Notitie] [Dossier] [Rapportage] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Marie Jansen │ │
|
||||
│ │ 22-08-1972 · Kamer 8 · F32.1 Depressieve episode │ │
|
||||
│ │ [Notitie] [Dossier] [Rapportage] │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 8.3 Overdracht Flow
|
||||
|
||||
```
|
||||
Voice: "Overdracht maken"
|
||||
|
||||
Intent: overdracht
|
||||
Entities:
|
||||
- time_range: "afgelopen 8 uur" (default)
|
||||
|
||||
Output:
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 🔄 Overdracht Ochtend dienst │
|
||||
│ ───────────────────────────────────────────────────────── │
|
||||
│ 06:00 - 14:00 · 5 patiënten met updates │
|
||||
│ │
|
||||
│ ▼ Jan de Vries (3 notities) │
|
||||
│ ⚠ Aandachtspunt: onrustig vannacht │
|
||||
│ • 07:30 Medicatie uitgereikt ✓ │
|
||||
│ • 08:45 Ontbijt genuttigd │
|
||||
│ • 11:00 Gesprek met psycholoog │
|
||||
│ │
|
||||
│ ▼ Marie van den Berg (2 notities) │
|
||||
│ • 08:00 ADL ondersteuning │
|
||||
│ • 10:30 Bezoek familie │
|
||||
│ │
|
||||
│ ─────────────────────────────────────────────────────── │
|
||||
│ AI Samenvatting: │
|
||||
│ "Rustige ochtend. Aandacht voor Jan de Vries die │
|
||||
│ vannacht onrustig was. Marie ontving familiebezoek..." │
|
||||
│ │
|
||||
│ [Kopiëren] [Printen] [Doorsturen] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Conclusie: Minimale Intent Set
|
||||
|
||||
### 9.1 MVP (6 intents)
|
||||
|
||||
| Intent | Type | Frequentie | Complexiteit |
|
||||
|--------|------|------------|--------------|
|
||||
| `dagnotitie` | Writer | Zeer hoog | Laag |
|
||||
| `zoeken` | Reader | Zeer hoog | Laag |
|
||||
| `overdracht` | Reader | Hoog | Medium |
|
||||
| `rapportage` | Writer | Hoog | Medium |
|
||||
| `patient_info` | Reader | Hoog | Laag |
|
||||
| `agenda` | Reader | Medium | Laag |
|
||||
|
||||
### 9.2 Fallback
|
||||
|
||||
| Intent | Actie |
|
||||
|--------|-------|
|
||||
| `onbekend` | Toon blok-picker met 6 opties |
|
||||
| `ambigue` | "Bedoelde je...?" met opties |
|
||||
| `lage_confidence` | Toon blok-picker |
|
||||
|
||||
### 9.3 Training Data Nodig
|
||||
|
||||
Per intent minimaal 20-30 voorbeeldzinnen in het Nederlands, inclusief:
|
||||
- Formele vorm ("Ik wil een notitie maken")
|
||||
- Informele vorm ("notitie jan")
|
||||
- Met context ("jan medicatie")
|
||||
- Zonder context ("notitie maken")
|
||||
- Met typo's ("noitie jan")
|
||||
- Voice transcriptie varianten
|
||||
|
||||
---
|
||||
|
||||
*Dit document dient als basis voor de intent classificatie en UI bouwblokken.*
|
||||
@@ -1,232 +0,0 @@
|
||||
# Test Plan E5.S2 - Error Handling
|
||||
|
||||
**Datum:** 27-12-2024
|
||||
**Status:** Ready for Testing
|
||||
**Story:** E5.S2 - Error handling (2 SP)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Implementatie Overzicht
|
||||
|
||||
### Nieuwe bestanden:
|
||||
1. **`lib/swift/error-handler.ts`** - Error handling utility met:
|
||||
- `isOffline()` - Detect browser offline status
|
||||
- `isNetworkError()` - Network error detection
|
||||
- `isTimeoutError()` - Timeout detection
|
||||
- `parseErrorResponse()` - Parse HTTP error responses
|
||||
- `getErrorInfo()` - Generate user-friendly error messages
|
||||
- `safeFetch()` - Fetch wrapper met timeout (30s)
|
||||
- `retryFetch()` - Retry logic met exponential backoff
|
||||
|
||||
2. **`components/swift/command-center/offline-banner.tsx`** - Offline banner component
|
||||
- Toont amber banner bij `!navigator.onLine`
|
||||
- `useOffline()` hook voor offline detection
|
||||
|
||||
### Geüpdatete bestanden:
|
||||
1. **`components/swift/command-center/command-center.tsx`** - OfflineBanner geïntegreerd
|
||||
2. **`components/swift/command-center/command-input.tsx`** - Error handling met safeFetch/getErrorInfo
|
||||
3. **`components/swift/command-center/context-bar.tsx`** - useOffline hook voor margin adjustment
|
||||
4. **`components/swift/blocks/dagnotitie-block.tsx`** - safeFetch, getErrorInfo, retryFetch
|
||||
5. **`components/swift/blocks/zoeken-block.tsx`** - safeFetch, getErrorInfo
|
||||
6. **`components/swift/blocks/overdracht-block.tsx`** - safeFetch, getErrorInfo, retryFetch
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test Scenarios
|
||||
|
||||
### 1. Offline Detection ✅
|
||||
|
||||
**Test 1.1: Browser Offline**
|
||||
- [ ] Open DevTools → Network tab → Throttling → Offline
|
||||
- [ ] Amber banner verschijnt bovenaan: "Geen internetverbinding"
|
||||
- [ ] Context bar heeft `marginTop: 40px` (geen overlap)
|
||||
- [ ] Try een actie (dagnotitie opslaan, patient zoeken)
|
||||
- [ ] Toast shows: "Geen internetverbinding - Controleer je internetverbinding en probeer het opnieuw."
|
||||
|
||||
**Test 1.2: Reconnect**
|
||||
- [ ] Zet network weer op "Online"
|
||||
- [ ] Banner verdwijnt automatisch
|
||||
- [ ] Context bar margin reset
|
||||
- [ ] Acties werken weer normaal
|
||||
|
||||
---
|
||||
|
||||
### 2. Network Errors
|
||||
|
||||
**Test 2.1: API Endpoint Down**
|
||||
- [ ] Stop de dev server (kill process)
|
||||
- [ ] Probeer dagnotitie opslaan
|
||||
- [ ] Toast shows: "Verbinding verbroken - Kon geen verbinding maken met de server."
|
||||
- [ ] Retry button beschikbaar (voor retryable errors)
|
||||
|
||||
**Test 2.2: Timeout (30s)**
|
||||
- [ ] Simuleer slow API (add `await new Promise(r => setTimeout(r, 35000))` in API route)
|
||||
- [ ] Probeer actie
|
||||
- [ ] Na 30 seconden: Toast shows "Verbinding timeout"
|
||||
|
||||
---
|
||||
|
||||
### 3. HTTP Status Codes
|
||||
|
||||
**Test 3.1: 401 Unauthorized**
|
||||
- [ ] Simuleer 401 in API route: `return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })`
|
||||
- [ ] Toast shows: "Niet geautoriseerd - Je sessie is verlopen. Log opnieuw in."
|
||||
- [ ] `retryable: false` (geen retry button)
|
||||
|
||||
**Test 3.2: 404 Not Found**
|
||||
- [ ] Probeer non-existent patient ID: `/api/fhir/Patient/99999999`
|
||||
- [ ] Toast shows: "Niet gevonden - Patiënt data ophalen niet gevonden."
|
||||
|
||||
**Test 3.3: 400 Bad Request**
|
||||
- [ ] Submit dagnotitie zonder required fields
|
||||
- [ ] Toast shows: "Ongeldige aanvraag - [validation message]"
|
||||
|
||||
**Test 3.4: 500 Internal Server Error**
|
||||
- [ ] Simuleer server error in API route: `throw new Error('DB error')`
|
||||
- [ ] Toast shows: "Serverfout - Er ging iets mis op de server. Probeer het later opnieuw."
|
||||
- [ ] `retryable: true`
|
||||
|
||||
**Test 3.5: 429 Too Many Requests**
|
||||
- [ ] (Moeilijk te simuleren zonder rate limiter)
|
||||
- [ ] Toast should show: "Te veel aanvragen - Wacht even en probeer het later opnieuw."
|
||||
|
||||
---
|
||||
|
||||
### 4. Validation Errors
|
||||
|
||||
**Test 4.1: Empty Fields in DagnotatieBlock**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Klik "Opslaan" zonder patiënt
|
||||
- [ ] Toast shows: "Validatiefout - Selecteer een patiënt"
|
||||
- [ ] Klik "Opslaan" zonder categorie
|
||||
- [ ] Toast shows: "Validatiefout - Selecteer een categorie"
|
||||
- [ ] Klik "Opslaan" zonder tekst
|
||||
- [ ] Toast shows: "Validatiefout - Voer een notitie in"
|
||||
|
||||
**Test 4.2: Search Query Too Short**
|
||||
- [ ] Type "j" in ZoekenBlock (< 2 characters)
|
||||
- [ ] Geen API call (debounced + min length check)
|
||||
|
||||
---
|
||||
|
||||
### 5. Intent Classification Errors
|
||||
|
||||
**Test 5.1: API Error**
|
||||
- [ ] Typ commando in command input
|
||||
- [ ] Simuleer API error in `/api/intent/classify`
|
||||
- [ ] Toast shows error message
|
||||
- [ ] FallbackPicker opent automatisch met original input
|
||||
- [ ] Input is niet verloren (preserved in FallbackPicker)
|
||||
|
||||
**Test 5.2: Low Confidence**
|
||||
- [ ] Typ gibberish: "asdfasdf jkljkl"
|
||||
- [ ] Confidence < 0.5
|
||||
- [ ] FallbackPicker opent met original input
|
||||
- [ ] Can select fallback action (dagnotitie, zoeken, overdracht)
|
||||
|
||||
---
|
||||
|
||||
### 6. Retry Logic
|
||||
|
||||
**Test 6.1: Retry Success on 2nd Attempt**
|
||||
- [ ] Simuleer intermittent error (fails first, succeeds second)
|
||||
- [ ] DagnotatieBlock save with `retryFetch()`
|
||||
- [ ] Should retry automatically (max 3 attempts)
|
||||
- [ ] Success toast after retry succeeds
|
||||
|
||||
**Test 6.2: Retry Exhausted (3 failures)**
|
||||
- [ ] Simuleer consistent error (always fails)
|
||||
- [ ] After 3 retries: Final error toast shown
|
||||
- [ ] No infinite retry loop
|
||||
|
||||
---
|
||||
|
||||
### 7. User-Friendly Messages (Dutch)
|
||||
|
||||
**Test 7.1: All Error Messages in Dutch**
|
||||
- [ ] Check all toast messages are in Dutch
|
||||
- [ ] Check all error titles are in Dutch
|
||||
- [ ] No English fallback messages visible to user
|
||||
|
||||
**Test 7.2: Error Message Quality**
|
||||
- [ ] Messages are actionable ("Controleer je internetverbinding")
|
||||
- [ ] Not technical jargon ("fetch failed" → "Verbinding verbroken")
|
||||
- [ ] Clear next steps when applicable
|
||||
|
||||
---
|
||||
|
||||
### 8. Edge Cases
|
||||
|
||||
**Test 8.1: HTML Error Response (Auth Redirect)**
|
||||
- [ ] Simulate HTML response (login page redirect)
|
||||
- [ ] `parseErrorResponse()` detects HTML
|
||||
- [ ] Toast shows: "Niet geautoriseerd. Log opnieuw in."
|
||||
|
||||
**Test 8.2: Malformed JSON Response**
|
||||
- [ ] Simulate invalid JSON from API
|
||||
- [ ] Error handled gracefully (no crash)
|
||||
- [ ] User-friendly error shown
|
||||
|
||||
**Test 8.3: Error During Voice Input**
|
||||
- [ ] Start voice recording
|
||||
- [ ] Trigger error (offline, etc.)
|
||||
- [ ] Voice recording stops gracefully
|
||||
- [ ] Error shown to user
|
||||
|
||||
---
|
||||
|
||||
## ✅ Implementation Checklist
|
||||
|
||||
- [x] `lib/swift/error-handler.ts` created with all utilities
|
||||
- [x] `components/swift/command-center/offline-banner.tsx` created
|
||||
- [x] OfflineBanner integrated in CommandCenter
|
||||
- [x] CommandInput uses safeFetch + getErrorInfo
|
||||
- [x] ContextBar uses useOffline hook (margin adjustment)
|
||||
- [x] DagnotatieBlock uses safeFetch + getErrorInfo + retryFetch
|
||||
- [x] ZoekenBlock uses safeFetch + getErrorInfo
|
||||
- [x] OverdrachtBlock uses safeFetch + getErrorInfo + retryFetch
|
||||
- [x] All error messages in Dutch
|
||||
- [x] FallbackPicker handles unknown intents gracefully
|
||||
- [x] TypeScript compiles without errors
|
||||
|
||||
---
|
||||
|
||||
## 📋 Manual Testing Checklist (Quick Smoke Test)
|
||||
|
||||
Voor snelle verificatie:
|
||||
|
||||
1. **Offline Mode:**
|
||||
- [ ] DevTools → Offline → Banner appears → Try action → Error toast
|
||||
|
||||
2. **Network Error:**
|
||||
- [ ] Stop server → Try action → Error toast with retry
|
||||
|
||||
3. **Validation Error:**
|
||||
- [ ] Submit empty dagnotitie → Validation toast
|
||||
|
||||
4. **Intent Error:**
|
||||
- [ ] Type gibberish → FallbackPicker opens
|
||||
|
||||
5. **Success Path:**
|
||||
- [ ] Online → Submit dagnotitie → Success toast → Block closes
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Acceptatie Criteria E5.S2
|
||||
|
||||
- [x] Network errors tonen user-friendly messages (Dutch)
|
||||
- [x] Offline detection met visual indicator (banner)
|
||||
- [x] Validation errors zijn duidelijk en actionable
|
||||
- [x] Retry functionaliteit werkt voor transient errors
|
||||
- [x] HTTP status codes mapped naar begrijpelijke messages
|
||||
- [x] FallbackPicker shown on intent classification failure
|
||||
- [x] No lost user input on errors (preserved in FallbackPicker)
|
||||
- [x] All error scenarios from test-plan-epic3.md covered
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Status
|
||||
|
||||
**E5.S2 - Error Handling: COMPLETE ✅**
|
||||
|
||||
All error handling utilities implemented, integrated across all components, and ready for testing.
|
||||
@@ -1,245 +0,0 @@
|
||||
# Test Plan E5.S3 - Keyboard Shortcuts
|
||||
|
||||
**Datum:** 27-12-2024
|
||||
**Status:** Ready for Testing
|
||||
**Story:** E5.S3 - Keyboard shortcuts verificatie (2 SP)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Implementatie Overzicht
|
||||
|
||||
### Bestaande Shortcuts (E1.S1, E4.S4)
|
||||
1. **⌘K / Ctrl+K** - Focus command input (CommandCenter)
|
||||
2. **Escape** - Close active block (CommandCenter)
|
||||
3. **1-3** - Quick select in FallbackPicker
|
||||
|
||||
### Nieuwe Shortcuts (E5.S3)
|
||||
1. **⌘Enter / Ctrl+Enter** - Quick submit in CommandInput
|
||||
2. **⌘Enter / Ctrl+Enter** - Quick save in DagnotatieBlock
|
||||
3. **Visual hints** - ⌘↵ shown on DagnotatieBlock save button
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test Scenarios
|
||||
|
||||
### 1. Global Shortcuts
|
||||
|
||||
**Test 1.1: ⌘K Focus Input**
|
||||
- [ ] Start op Swift pagina
|
||||
- [ ] Press ⌘K (Mac) of Ctrl+K (Windows/Linux)
|
||||
- [ ] Command input krijgt focus
|
||||
- [ ] Cursor blinkt in input field
|
||||
- [ ] Werkt vanaf elke positie (in block, buiten block)
|
||||
|
||||
**Test 1.2: Escape Close Block**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Press Escape
|
||||
- [ ] Block sluit met slide-down animatie
|
||||
- [ ] Input is weer beschikbaar
|
||||
- [ ] Herhaal met zoeken block
|
||||
- [ ] Herhaal met overdracht block
|
||||
|
||||
**Test 1.3: Escape Close FallbackPicker**
|
||||
- [ ] Trigger FallbackPicker (typ gibberish)
|
||||
- [ ] Press Escape
|
||||
- [ ] FallbackPicker sluit
|
||||
- [ ] Canvas area toont empty state
|
||||
|
||||
---
|
||||
|
||||
### 2. Command Input Shortcuts
|
||||
|
||||
**Test 2.1: Enter Submit (Native)**
|
||||
- [ ] Typ "notitie jan medicatie" in command input
|
||||
- [ ] Press Enter
|
||||
- [ ] Intent classification API wordt aangeroepen
|
||||
- [ ] DagnotatieBlock opent met prefill
|
||||
- [ ] Input wordt cleared
|
||||
|
||||
**Test 2.2: ⌘Enter Quick Submit**
|
||||
- [ ] Typ "zoek marie" in command input
|
||||
- [ ] Press ⌘Enter (Mac) of Ctrl+Enter (Windows)
|
||||
- [ ] Intent classification API wordt aangeroepen
|
||||
- [ ] ZoekenBlock opent
|
||||
- [ ] Input wordt cleared
|
||||
|
||||
**Test 2.3: Empty Input - No Submit**
|
||||
- [ ] Command input is leeg
|
||||
- [ ] Press Enter of ⌘Enter
|
||||
- [ ] Niks gebeurt (validation)
|
||||
- [ ] Geen API call
|
||||
- [ ] Geen error toast
|
||||
|
||||
**Test 2.4: Submit During Processing**
|
||||
- [ ] Typ commando en submit (Enter)
|
||||
- [ ] Tijdens processing: druk nogmaals Enter
|
||||
- [ ] Tweede submit wordt genegeerd (isProcessing check)
|
||||
- [ ] Geen dubbele API calls
|
||||
|
||||
---
|
||||
|
||||
### 3. DagnotatieBlock Shortcuts
|
||||
|
||||
**Test 3.1: ⌘Enter Quick Save**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Vul alle velden in (patient, categorie, content)
|
||||
- [ ] Press ⌘Enter (Mac) of Ctrl+Enter (Windows)
|
||||
- [ ] Dagnotitie wordt opgeslagen
|
||||
- [ ] Success toast verschijnt
|
||||
- [ ] Block sluit na 500ms
|
||||
|
||||
**Test 3.2: ⌘Enter Validation**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Laat patient leeg, vul rest in
|
||||
- [ ] Press ⌘Enter
|
||||
- [ ] Validation toast: "Selecteer een patiënt"
|
||||
- [ ] Block blijft open
|
||||
- [ ] Herhaal voor categorie en content
|
||||
|
||||
**Test 3.3: Visual Hint Zichtbaar**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Check submit button
|
||||
- [ ] Tekst toont "Opslaan ⌘↵"
|
||||
- [ ] Hint is zichtbaar op desktop (hidden op mobile via sm:inline)
|
||||
- [ ] Tooltip toont "Opslaan (⌘Enter)" on hover
|
||||
|
||||
**Test 3.4: Enter in Textarea**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Focus in content textarea
|
||||
- [ ] Press Enter (zonder Cmd/Ctrl)
|
||||
- [ ] Nieuwe regel in textarea (native behavior)
|
||||
- [ ] Form wordt NIET gesubmit
|
||||
- [ ] Press ⌘Enter
|
||||
- [ ] Form wordt gesubmit
|
||||
|
||||
---
|
||||
|
||||
### 4. FallbackPicker Shortcuts
|
||||
|
||||
**Test 4.1: Number Keys 1-3**
|
||||
- [ ] Trigger FallbackPicker (typ "asdfasdf")
|
||||
- [ ] Press 1
|
||||
- [ ] DagnotatieBlock opent met original input als content
|
||||
- [ ] FallbackPicker sluit
|
||||
|
||||
**Test 4.2: Number Keys Sequence**
|
||||
- [ ] Trigger FallbackPicker
|
||||
- [ ] Press 2
|
||||
- [ ] ZoekenBlock opent
|
||||
- [ ] Close block (Escape)
|
||||
- [ ] Trigger FallbackPicker again
|
||||
- [ ] Press 3
|
||||
- [ ] OverdrachtBlock opent
|
||||
|
||||
**Test 4.3: Numbers in Input Field**
|
||||
- [ ] FallbackPicker open
|
||||
- [ ] Typ "123" in command input (via ⌘K)
|
||||
- [ ] Cijfers worden getypt (shortcut inactive in input)
|
||||
- [ ] FallbackPicker blijft zichtbaar
|
||||
- [ ] Press Escape om picker te sluiten
|
||||
|
||||
---
|
||||
|
||||
### 5. Cross-Platform Testing
|
||||
|
||||
**Test 5.1: macOS**
|
||||
- [ ] ⌘K werkt (Cmd key)
|
||||
- [ ] ⌘Enter werkt (Cmd key)
|
||||
- [ ] Ctrl+K werkt ook (fallback)
|
||||
- [ ] Ctrl+Enter werkt ook (fallback)
|
||||
|
||||
**Test 5.2: Windows/Linux**
|
||||
- [ ] Ctrl+K werkt
|
||||
- [ ] Ctrl+Enter werkt
|
||||
- [ ] ⌘ key (if present) werkt niet of is ignored
|
||||
|
||||
**Test 5.3: Browser Conflicts**
|
||||
- [ ] ⌘K/Ctrl+K overschrijft browser's native shortcut (search)
|
||||
- [ ] preventDefault() werkt correct
|
||||
- [ ] Geen browser search bar opent
|
||||
|
||||
---
|
||||
|
||||
### 6. Edge Cases
|
||||
|
||||
**Test 6.1: Rapid Shortcut Succession**
|
||||
- [ ] Press ⌘K → Escape → ⌘K → Enter snel na elkaar
|
||||
- [ ] Alle shortcuts werken correct
|
||||
- [ ] Geen race conditions
|
||||
- [ ] Geen crashes
|
||||
|
||||
**Test 6.2: Shortcuts During Block Transition**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Tijdens slide-up animatie: press ⌘Enter
|
||||
- [ ] Shortcut werkt niet (block nog niet fully open)
|
||||
- [ ] Of: shortcut werkt na animatie compleet
|
||||
|
||||
**Test 6.3: Voice Recording Active**
|
||||
- [ ] Start voice recording
|
||||
- [ ] Press ⌘Enter
|
||||
- [ ] Recording stopt
|
||||
- [ ] Commando wordt verstuurd
|
||||
- [ ] Transcript wordt gebruikt
|
||||
|
||||
**Test 6.4: Block Disabled State**
|
||||
- [ ] Open dagnotitie block
|
||||
- [ ] Submit → tijdens isSubmitting
|
||||
- [ ] Press ⌘Enter
|
||||
- [ ] Shortcut wordt genegeerd (disabled check)
|
||||
- [ ] Geen dubbele save
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Acceptatie Criteria E5.S3
|
||||
|
||||
- [x] ⌘K/Ctrl+K werkt op alle platforms
|
||||
- [x] Escape werkt in alle contexts (blocks, picker)
|
||||
- [x] Enter submit werkt in command input
|
||||
- [x] ⌘Enter/Ctrl+Enter werkt in command input
|
||||
- [x] ⌘Enter/Ctrl+Enter werkt in dagnotitie block
|
||||
- [x] 1-3 number shortcuts werken in FallbackPicker
|
||||
- [x] Shortcuts hebben visual hints waar relevant
|
||||
- [x] preventDefault() voorkomt browser conflicts
|
||||
- [x] Shortcuts respecteren disabled/processing states
|
||||
- [x] Cross-platform compatible (Mac, Windows, Linux)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Quick Smoke Test (5 minuten)
|
||||
|
||||
Voor snelle verificatie:
|
||||
|
||||
1. **⌘K Test:**
|
||||
- [ ] Press ⌘K → Input has focus ✅
|
||||
|
||||
2. **Enter Submit:**
|
||||
- [ ] Type "notitie jan" → Enter → Block opens ✅
|
||||
|
||||
3. **⌘Enter Quick Submit:**
|
||||
- [ ] Type "zoek marie" → ⌘Enter → Block opens ✅
|
||||
|
||||
4. **Escape Close:**
|
||||
- [ ] Open any block → Escape → Block closes ✅
|
||||
|
||||
5. **Dagnotitie ⌘Enter Save:**
|
||||
- [ ] Fill dagnotitie form → ⌘Enter → Saves ✅
|
||||
|
||||
6. **FallbackPicker Numbers:**
|
||||
- [ ] Type gibberish → Press 1 → Dagnotitie opens ✅
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Documentatie
|
||||
|
||||
Alle shortcuts gedocumenteerd in:
|
||||
- `docs/swift/keyboard-shortcuts-reference.md` - Complete referentie
|
||||
- Tooltips en visual hints in UI
|
||||
- Bouwplan E5.S3 technical notes
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Status
|
||||
|
||||
**E5.S3 - Keyboard Shortcuts: COMPLETE ✅**
|
||||
|
||||
All keyboard shortcuts verified, extended, and documented.
|
||||
@@ -1,272 +0,0 @@
|
||||
# Test Plan Epic 3 - P1 Blocks
|
||||
|
||||
**Datum:** 24-12-2024
|
||||
**Status:** Ready for Testing
|
||||
**Epic:** E3 - P1 Blocks (E3.S0-S6)
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Start development server:**
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
2. **Open browser:**
|
||||
- Navigeer naar `http://localhost:3000`
|
||||
- Log in (of gebruik demo account)
|
||||
- Kies "Swift" interface preference
|
||||
- Je komt op `/epd/swift`
|
||||
|
||||
---
|
||||
|
||||
## Test Checklist
|
||||
|
||||
### E3.S0 - CanvasArea Block Rendering ✅
|
||||
|
||||
- [ ] **Empty State**
|
||||
- [ ] Wanneer geen block actief is, zie je de empty state met voorbeeld commando's
|
||||
- [ ] Empty state heeft "Wat wil je doen?" tekst
|
||||
|
||||
- [ ] **Block Transitions**
|
||||
- [ ] Wanneer een block opent, zie je een smooth fade-in animatie
|
||||
- [ ] Wanneer een block sluit, zie je een smooth fade-out animatie
|
||||
- [ ] Geen flickering tussen blocks
|
||||
|
||||
- [ ] **PatientContextCard Auto-Display**
|
||||
- [ ] Wanneer je een patiënt selecteert in ZoekenBlock, verschijnt PatientContextCard automatisch
|
||||
- [ ] PatientContextCard toont patiënt naam in header
|
||||
|
||||
---
|
||||
|
||||
### E3.S1 - Block Container ✅
|
||||
|
||||
- [ ] **Container Styling**
|
||||
- [ ] Alle blocks hebben een donkere achtergrond (slate-800)
|
||||
- [ ] Blocks hebben een border (slate-700)
|
||||
- [ ] Blocks hebben shadow-2xl
|
||||
|
||||
- [ ] **Close Button**
|
||||
- [ ] Elke block heeft een X button rechtsboven
|
||||
- [ ] Hover over X button verandert kleur
|
||||
- [ ] Klik op X sluit het block
|
||||
- [ ] ESC toets sluit ook het block
|
||||
|
||||
- [ ] **Sizes**
|
||||
- [ ] DagnotatieBlock heeft size "md"
|
||||
- [ ] ZoekenBlock heeft size "md"
|
||||
- [ ] OverdrachtBlock heeft size "lg"
|
||||
- [ ] PatientContextCard heeft size "lg"
|
||||
|
||||
- [ ] **Animations**
|
||||
- [ ] Container heeft fade-in animatie
|
||||
- [ ] Content heeft stagger animatie (items verschijnen na elkaar)
|
||||
- [ ] Close button heeft hover/tap animaties
|
||||
|
||||
---
|
||||
|
||||
### E3.S2 - DagnotatieBlock ✅
|
||||
|
||||
- [ ] **Patient Search**
|
||||
- [ ] Typ "jan" in patient search veld
|
||||
- [ ] Na ~300ms zie je zoekresultaten
|
||||
- [ ] Resultaten tonen naam, geboortedatum, BSN
|
||||
- [ ] Klik op een patiënt selecteert deze
|
||||
|
||||
- [ ] **Category Selector**
|
||||
- [ ] 5 categorie buttons zijn zichtbaar: medicatie, adl, gedrag, incident, observatie
|
||||
- [ ] Klik op een categorie selecteert deze (visuele feedback)
|
||||
- [ ] Alleen één categorie kan geselecteerd zijn
|
||||
|
||||
- [ ] **Text Input**
|
||||
- [ ] Textarea is beschikbaar
|
||||
- [ ] Character counter toont "0 / 500"
|
||||
- [ ] Typ tekst → counter update
|
||||
- [ ] Max 500 karakters (kan niet meer typen)
|
||||
|
||||
- [ ] **Include in Handover**
|
||||
- [ ] Checkbox "Opnemen in overdracht" is zichtbaar
|
||||
- [ ] Checkbox kan aangevinkt worden
|
||||
|
||||
- [ ] **Save Functionality**
|
||||
- [ ] Klik "Opslaan" zonder patiënt → error message
|
||||
- [ ] Klik "Opslaan" zonder categorie → error message
|
||||
- [ ] Klik "Opslaan" zonder tekst → error message
|
||||
- [ ] Vul alles in en klik "Opslaan"
|
||||
- [ ] Success toast verschijnt
|
||||
- [ ] Block sluit automatisch na ~500ms
|
||||
|
||||
- [ ] **Prefill Data**
|
||||
- [ ] Typ "notitie jan medicatie" in command input
|
||||
- [ ] DagnotatieBlock opent met "jan" voorgevuld in patient search
|
||||
- [ ] Categorie "medicatie" is geselecteerd
|
||||
|
||||
---
|
||||
|
||||
### E3.S3 - Patient Search API ✅
|
||||
|
||||
- [ ] **API Endpoint**
|
||||
- [ ] Open DevTools → Network tab
|
||||
- [ ] Typ in ZoekenBlock of DagnotatieBlock
|
||||
- [ ] Request naar `/api/patients/search?q=...` wordt gemaakt
|
||||
- [ ] Response heeft `patients` array met `matchScore`
|
||||
|
||||
- [ ] **Search Functionality**
|
||||
- [ ] Zoek op naam: "jan" → resultaten met "Jan" in naam
|
||||
- [ ] Zoek op BSN: "123456789" → resultaten met deze BSN
|
||||
- [ ] Zoek op clientnummer: "12345" → resultaten met dit nummer
|
||||
- [ ] Zoek op deel van naam: "mar" → resultaten met "Marie", "Maria", etc.
|
||||
|
||||
- [ ] **Match Scoring**
|
||||
- [ ] Exacte naam match heeft hogere score
|
||||
- [ ] Resultaten zijn gesorteerd op matchScore (hoogste eerst)
|
||||
|
||||
- [ ] **Error Handling**
|
||||
- [ ] Zoek op "x" (te kort) → geen request
|
||||
- [ ] Network error → error message in UI
|
||||
|
||||
---
|
||||
|
||||
### E3.S4 - ZoekenBlock ✅
|
||||
|
||||
- [ ] **Search Input**
|
||||
- [ ] Typ in search input veld
|
||||
- [ ] Debouncing werkt (geen request bij elke toetsaanslag)
|
||||
- [ ] Loading indicator tijdens zoeken
|
||||
|
||||
- [ ] **Search Results**
|
||||
- [ ] Resultaten verschijnen in dropdown
|
||||
- [ ] Elke resultaat toont: avatar, naam, geboortedatum, BSN, clientnummer
|
||||
- [ ] Max 5 resultaten getoond
|
||||
|
||||
- [ ] **Patient Selection**
|
||||
- [ ] Klik op een patiënt in resultaten
|
||||
- [ ] Loading indicator tijdens ophalen volledige data
|
||||
- [ ] Success toast: "Patiënt geselecteerd"
|
||||
- [ ] Block sluit automatisch
|
||||
- [ ] PatientContextCard verschijnt automatisch
|
||||
|
||||
- [ ] **Store Integration**
|
||||
- [ ] Na selectie is `activePatient` gezet in store
|
||||
- [ ] Recent action is toegevoegd: "Patiënt geselecteerd: [naam]"
|
||||
|
||||
- [ ] **Empty State**
|
||||
- [ ] Geen resultaten → "Geen patiënten gevonden" message
|
||||
- [ ] Te korte query → geen resultaten
|
||||
|
||||
---
|
||||
|
||||
### E3.S5 - PatientContextCard ✅
|
||||
|
||||
- [ ] **Auto-Open**
|
||||
- [ ] Na patiënt selectie in ZoekenBlock verschijnt PatientContextCard automatisch
|
||||
- [ ] PatientContextCard heeft patiënt naam in header
|
||||
|
||||
- [ ] **Loading State**
|
||||
- [ ] Tijdens laden zie je "Context laden..." met spinner
|
||||
|
||||
- [ ] **Notities Sectie**
|
||||
- [ ] Laatste 5 reports worden getoond
|
||||
- [ ] Elke notitie toont: type badge, overdracht indicator, datum/tijd, content preview
|
||||
- [ ] Content is gelimiteerd tot 2 regels (line-clamp-2)
|
||||
|
||||
- [ ] **Vitals Sectie**
|
||||
- [ ] Vitale functies van vandaag worden getoond
|
||||
- [ ] Grid layout (2 kolommen)
|
||||
- [ ] Abnormale waarden hebben amber achtergrond
|
||||
- [ ] Elke vital toont: naam, waarde, eenheid, tijdstip
|
||||
|
||||
- [ ] **Diagnoses Sectie**
|
||||
- [ ] Actieve diagnoses worden getoond
|
||||
- [ ] Elke diagnose toont: naam, startdatum (indien beschikbaar)
|
||||
|
||||
- [ ] **Risico's Sectie**
|
||||
- [ ] Risk assessments worden getoond
|
||||
- [ ] Risico badges met kleurcodering (rood voor hoog, amber voor gemiddeld)
|
||||
- [ ] Elke badge toont: type + niveau
|
||||
|
||||
- [ ] **Empty State**
|
||||
- [ ] Geen data beschikbaar → "Geen context beschikbaar voor deze patiënt"
|
||||
|
||||
- [ ] **Error Handling**
|
||||
- [ ] API error → error message met retry optie
|
||||
|
||||
---
|
||||
|
||||
### E3.S6 - OverdrachtBlock ✅
|
||||
|
||||
- [ ] **Patients List**
|
||||
- [ ] Lijst van patiënten met activiteit wordt geladen
|
||||
- [ ] Loading state tijdens laden
|
||||
- [ ] Elke patiënt toont: naam, alert count, risico indicator
|
||||
|
||||
- [ ] **Period Selector**
|
||||
- [ ] 4 period buttons: Vandaag, 3 dagen, 1 week, 2 weken
|
||||
- [ ] Geselecteerde periode heeft visuele feedback
|
||||
- [ ] Beschrijving onder buttons update bij selectie
|
||||
|
||||
- [ ] **Active Patient Filter**
|
||||
- [ ] Wanneer `activePatient` is gezet, toont alleen die patiënt
|
||||
- [ ] Wanneer geen `activePatient`, toont alle patiënten
|
||||
|
||||
- [ ] **Generate Summary**
|
||||
- [ ] Klik "Genereer" button voor een patiënt
|
||||
- [ ] Loading state: "Samenvatting wordt gegenereerd..."
|
||||
- [ ] Na ~3-5 seconden verschijnt samenvatting
|
||||
|
||||
- [ ] **Summary Display**
|
||||
- [ ] **Samenvatting**: AI-gegenereerde tekst in grijze box
|
||||
- [ ] **Aandachtspunten**: Lijst met urgentie badges
|
||||
- [ ] Urgente punten hebben rode border + alert icon
|
||||
- [ ] Bron type badges (observatie, rapportage, verpleegkundig, risico)
|
||||
- [ ] Bronverwijzingen met datum + label
|
||||
- [ ] **Actiepunten**: Checklist met acties
|
||||
- [ ] **Footer**: Tijdstip generatie + duration, refresh button
|
||||
|
||||
- [ ] **Error Handling**
|
||||
- [ ] API error → error message met "Opnieuw proberen" button
|
||||
- [ ] Network error → toast notification
|
||||
|
||||
- [ ] **Refresh**
|
||||
- [ ] Klik "Vernieuwen" button → nieuwe samenvatting wordt gegenereerd
|
||||
|
||||
---
|
||||
|
||||
## Cross-Feature Tests
|
||||
|
||||
- [ ] **Keyboard Shortcuts**
|
||||
- [ ] ESC sluit actieve block
|
||||
- [ ] Cmd+K focust command input
|
||||
|
||||
- [ ] **Animations**
|
||||
- [ ] Alle transitions zijn smooth (geen janky animaties)
|
||||
- [ ] Geen layout shifts tijdens animaties
|
||||
|
||||
- [ ] **Error States**
|
||||
- [ ] Network errors tonen user-friendly messages
|
||||
- [ ] Validation errors zijn duidelijk
|
||||
- [ ] Retry functionaliteit werkt
|
||||
|
||||
- [ ] **Performance**
|
||||
- [ ] Blocks openen binnen 100ms
|
||||
- [ ] API calls zijn debounced waar nodig
|
||||
- [ ] Geen onnodige re-renders
|
||||
|
||||
---
|
||||
|
||||
## Known Issues / Notes
|
||||
|
||||
- [ ] Noteer hier eventuele issues die je tegenkomt
|
||||
- [ ] Noteer verbeteringen die je zou willen zien
|
||||
|
||||
---
|
||||
|
||||
## Test Resultaten
|
||||
|
||||
**Tester:** _________________
|
||||
**Datum:** _________________
|
||||
**Status:** ⏳ In Progress / ✅ Passed / ❌ Failed
|
||||
|
||||
**Totaal:** ___ / ___ tests passed
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,757 +0,0 @@
|
||||
# Mission Control - Bouwplan
|
||||
|
||||
Projectnaam: Swift Agenda Planning Module
|
||||
Versie: v1.0
|
||||
Datum: 27-12-2025
|
||||
Auteur: Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en context
|
||||
Doel: een Swift Agenda Planning module bouwen waarmee gebruikers via chat afspraken kunnen opvragen, aanmaken, annuleren en verzetten, met een AgendaBlock artifact als visuele bevestiging.
|
||||
Toelichting: dit bouwt voort op het Swift conversatie‑model en hergebruikt de klassieke agenda (`/app/epd/agenda`) als full view. De Swift‑variant focust op snelle queries en quick actions.
|
||||
|
||||
---
|
||||
|
||||
## 2. Uitgangspunten
|
||||
|
||||
### 2.1 Technische Stack
|
||||
- Frontend: Next.js App Router + React + TypeScript
|
||||
- Styling: Tailwind CSS + shadcn/ui components
|
||||
- State: Zustand (Swift store) + React Query for data caching
|
||||
- Backend: Next.js route handlers + Supabase (encounters table)
|
||||
- AI/Intent: Two-tier system (Local regex <50ms + Claude Haiku fallback ~400ms)
|
||||
- Date/Time: date-fns library (al aanwezig)
|
||||
- Auth: Supabase Auth (server-side guard via `lib/auth/server.ts`)
|
||||
- Voice: Deepgram (existing integration voor voice input)
|
||||
|
||||
### 2.2 Projectkaders
|
||||
- Scope (MVP): agenda_query, create_appointment, cancel_appointment, reschedule_appointment + AgendaBlock.
|
||||
- Out of scope: drag-and-drop, recurring, availability, conflict resolution UI, multi-practitioner.
|
||||
- Geen nieuwe dependencies zonder akkoord.
|
||||
- Geen database migraties in MVP.
|
||||
- Reuse bestaande agenda actions waar mogelijk.
|
||||
|
||||
### 2.3 Programmeer uitgangspunten
|
||||
- DRY: hergebruik `app/epd/agenda/actions.ts` (getEncounters, createEncounter, cancelEncounter, rescheduleEncounter).
|
||||
- KISS: snelle API routes + simpele AgendaBlock UI.
|
||||
- SOC: intent parsing, data fetching en UI gescheiden.
|
||||
- YAGNI: alleen P1/P2 uit FO, geen extra planner features.
|
||||
|
||||
### 2.4 Performance Targets
|
||||
| Metric | Target | Actual (Expected) |
|
||||
|--------|--------|-------------------|
|
||||
| Local pattern match | <50ms | ~10-15ms |
|
||||
| Local + entity extraction | <100ms | ~30-50ms |
|
||||
| AI fallback (cold) | <800ms | ~400-600ms |
|
||||
| AI fallback (warm) | <500ms | ~200-400ms |
|
||||
| Total (local path) | <150ms | ~50-80ms |
|
||||
| Total (AI path) | <1000ms | ~500-700ms |
|
||||
|
||||
### 2.5 Confidence Thresholds
|
||||
| Confidence | Actie | Voorbeeld |
|
||||
|------------|-------|-----------|
|
||||
| **>0.9** | Direct artifact openen met prefill | "afspraken vandaag" |
|
||||
| **0.7-0.9** | Artifact + bevestigingsvraag | "maak afspraak jan morgen" (tijd ontbreekt) |
|
||||
| **0.5-0.7** | Verduidelijkingsvraag in chat | "maak afspraak" (patient/tijd ontbreekt) |
|
||||
| **<0.5** | Fallback: "Ik begrijp het niet" | Gibberish input |
|
||||
|
||||
---
|
||||
|
||||
## 3. Epics & Stories Overzicht
|
||||
| Epic ID | Titel | Doel | Status | Stories | Opmerkingen |
|
||||
|---------|-------|------|--------|---------|-------------|
|
||||
| E0 | Alignment & scope | MVP afbakenen en keuzes vastleggen | Done | 2 | FO‑based |
|
||||
| E1 | Intent & entity layer | Agenda intents + entities toevoegen | Done | 4 | Swift intent stack |
|
||||
| E2 | Date/time parsing | NLP‑helpers voor datum/tijd | Done | 3 | Geen nieuwe deps |
|
||||
| E3 | Backend integratie | Agenda data APIs + reuse actions | Done | 4 | Auth vereist |
|
||||
| E4 | AgendaBlock UI | List/create/cancel/reschedule views | Done | 5 | Swift artifact |
|
||||
| E5 | Chat orchestration | Action routing + prompt update | Done | 3 | Swift chat API |
|
||||
| E6 | QA & docs | Testplan + docs update | To Do | 3 | Manual QA |
|
||||
|
||||
---
|
||||
|
||||
## 4. Epics & Stories (Uitwerking)
|
||||
|
||||
### Epic 0 — Alignment & scope
|
||||
Epic doel: MVP scope, UX flows en beslissingen vastleggen.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E0.S1 | Scope + out-of-scope vastleggen | P1/P2 lijst bevestigd, OOS lijst bevestigd | Done | - | 1 |
|
||||
| E0.S2 | UX flow beschrijven | Entry/exit, artifact gedrag en fallback flows gedocumenteerd | Done | E0.S1 | 2 |
|
||||
|
||||
UX flow (MVP):
|
||||
- Entry: user is in Swift chat; agenda intents open AgendaBlock when confidence >= 0.7.
|
||||
- Exit: close artifact via tab close; for full view use link to `/epd/agenda` from the AgendaBlock footer.
|
||||
- Queries: default to today when clear; ask a clarification question when date range is missing or ambiguous.
|
||||
- Create: require patient + date + time; use active patient when user says "deze patient"; otherwise ask in chat.
|
||||
- Cancel/reschedule: if multiple matches, show disambiguation list in AgendaBlock; confirm before final action.
|
||||
- Fallback: if intent confidence < 0.7, keep artifact closed and ask for missing details.
|
||||
|
||||
---
|
||||
|
||||
### Epic 1 — Intent & entity layer
|
||||
Epic doel: agenda intent types en entities toevoegen aan Swift.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E1.S1 | SwiftIntent uitbreiden | Agenda intent names toegevoegd in types/store | Done | E0.S1 | 2 |
|
||||
| E1.S2 | Local intent patterns | Regex patterns voor agenda intents in `intent-classifier.ts` | Done | E1.S1 | 3 |
|
||||
| E1.S3 | AI fallback prompt | Prompt in `intent-classifier-ai.ts` uitgebreid met agenda intents | Done | E1.S1 | 3 |
|
||||
| E1.S4 | Entities schema | `ExtractedEntities` uitgebreid met date/time/identifier | Done | E1.S1 | 2 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E1.S1 - SwiftIntent type uitbreiding:**
|
||||
- File: `lib/swift/types.ts`
|
||||
- Houd `SwiftIntent` single source of truth (voorkom duplicatie).
|
||||
- SwiftIntent (na uitbreiding):
|
||||
```ts
|
||||
type SwiftIntent =
|
||||
| 'dagnotitie'
|
||||
| 'zoeken'
|
||||
| 'overdracht'
|
||||
| 'agenda_query' // 🆕
|
||||
| 'create_appointment' // 🆕
|
||||
| 'cancel_appointment' // 🆕
|
||||
| 'reschedule_appointment'// 🆕
|
||||
| 'unknown';
|
||||
```
|
||||
|
||||
**E1.S2 - Local pattern examples:**
|
||||
- File: `lib/swift/intent-classifier.ts`
|
||||
- Toevoegen aan `INTENT_PATTERNS`:
|
||||
```ts
|
||||
agenda_query: [
|
||||
{ pattern: /^afspraken?\\b/i, weight: 1.0 },
|
||||
{ pattern: /^agenda\\b/i, weight: 1.0 },
|
||||
{ pattern: /^(wat|wanneer)\\s+is\\s+(mijn\\s+)?volgende\\s+afspraak/i, weight: 1.0 },
|
||||
],
|
||||
create_appointment: [
|
||||
{ pattern: /^maak\\s+afspraak\\b/i, weight: 1.0 },
|
||||
{ pattern: /^plan\\s+(een\\s+)?(intake|afspraak)\\b/i, weight: 1.0 },
|
||||
],
|
||||
cancel_appointment: [
|
||||
{ pattern: /^annuleer\\s+(de\\s+)?afspraak/i, weight: 1.0 },
|
||||
],
|
||||
reschedule_appointment: [
|
||||
{ pattern: /^verzet\\s+(de\\s+)?afspraak/i, weight: 1.0 },
|
||||
{ pattern: /^\\d{1,2}:\\d{2}\\s+naar\\s+\\d{1,2}:\\d{2}/i, weight: 0.9 },
|
||||
],
|
||||
```
|
||||
|
||||
**E1.S3 - AI System Prompt addition:**
|
||||
- File: `lib/swift/intent-classifier-ai.ts`
|
||||
- Toevoegen aan `INTENT_CLASSIFIER_SYSTEM_PROMPT`:
|
||||
- Agenda intent descriptions + voorbeelden
|
||||
- Entity extraction instructies (patientName, dateRange, datetime, type, identifier, newDatetime)
|
||||
- Response format met nieuwe entities
|
||||
|
||||
**E1.S4 - ExtractedEntities uitbreiding:**
|
||||
- File: `lib/swift/types.ts`
|
||||
- Toevoegen:
|
||||
```ts
|
||||
interface ExtractedEntities {
|
||||
// Bestaande entities
|
||||
patientName?: string;
|
||||
category?: VerpleegkundigCategory;
|
||||
content?: string;
|
||||
|
||||
// 🆕 Agenda entities
|
||||
dateRange?: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
label: 'vandaag' | 'morgen' | 'deze week' | 'volgende week' | 'custom';
|
||||
};
|
||||
datetime?: {
|
||||
date: Date;
|
||||
time: string; // "HH:mm" format
|
||||
};
|
||||
appointmentType?: 'intake' | 'behandeling' | 'follow-up' | 'telefonisch' |
|
||||
'huisbezoek' | 'online' | 'crisis' | 'overig';
|
||||
location?: 'praktijk' | 'online' | 'thuis';
|
||||
identifier?: {
|
||||
type: 'patient' | 'time' | 'both';
|
||||
patientName?: string;
|
||||
patientId?: string;
|
||||
time?: string;
|
||||
date?: Date;
|
||||
encounterId?: string;
|
||||
};
|
||||
newDatetime?: {
|
||||
date: Date;
|
||||
time: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic 2 — Date/time parsing
|
||||
Epic doel: datum/tijd interpretatie uit natuurlijke taal.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E2.S1 | Date parser utility | `lib/swift/date-time-parser.ts` met relatieve datums | Done | E1.S4 | 3 |
|
||||
| E2.S2 | Time parser utility | Tijd normalisatie (14:00, half drie) | Done | E2.S1 | 2 |
|
||||
| E2.S3 | Entity extraction hook | Entity extractor gebruikt parser output | Done | E2.S2 | 2 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E2.S1 - Date parser implementation:**
|
||||
- File: `lib/swift/date-time-parser.ts`
|
||||
- Functie: `parseRelativeDate(input: string): Date | DateRange | null`
|
||||
- Ondersteunt:
|
||||
- "vandaag", "morgen", "overmorgen"
|
||||
- "maandag", "dinsdag", ... (next weekday)
|
||||
- "deze week", "volgende week" (returns DateRange)
|
||||
- Fallback naar date-fns `parse()` voor "30 december", "28-12-2024"
|
||||
- Gebruik `date-fns` helpers: `addDays()`, `startOfWeek()`, `endOfWeek()`, `addWeeks()`
|
||||
|
||||
**E2.S2 - Time parser implementation:**
|
||||
- Functie: `parseTime(input: string): string | null`
|
||||
- Ondersteunt:
|
||||
- "14:00" → "14:00"
|
||||
- "14" → "14:00"
|
||||
- "twee uur" → "14:00"
|
||||
- "half drie" → "14:30"
|
||||
- "kwart voor drie" → "14:45"
|
||||
- Returns `HH:mm` format string
|
||||
|
||||
**E2.S3 - Integration:**
|
||||
- Parser functies worden aangeroepen in entity extraction flow
|
||||
- Local pattern matching extraheert ruwe strings ("morgen", "14:00")
|
||||
- Parser functies normaliseren naar Date/time formats
|
||||
- AI fallback gebruikt parser voor validation
|
||||
|
||||
---
|
||||
|
||||
### Epic 3 — Backend integratie
|
||||
Epic doel: agenda data ontsluiten voor Swift blocks.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E3.S1 | Agenda query API | Endpoint voor afspraken op datumrange (auth) | Done | E1.S4 | 3 |
|
||||
| E3.S2 | Create appointment API | Endpoint die `createEncounter` aanroept | Done | E3.S1 | 3 |
|
||||
| E3.S3 | Cancel/reschedule API | Endpoints die `cancelEncounter`/`rescheduleEncounter` aanroepen | Done | E3.S1 | 3 |
|
||||
| E3.S4 | Patient match API | Fuzzy patiënt matching + disambiguation lijst | Done | E1.S4 | 2 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E3.S1 - Agenda query endpoint:**
|
||||
- Route: `GET /api/swift/agenda?start=2024-12-27&end=2024-12-27`
|
||||
- Auth: via `createClient()` from `lib/auth/server.ts`
|
||||
- Hergebruik: `getEncounters()` from `app/epd/agenda/actions.ts`
|
||||
- Response: `{ appointments: Encounter[] }`
|
||||
- Filters: practitioner_id = current user, status = 'planned', period_start in range
|
||||
- Order: chronological (period_start ASC)
|
||||
|
||||
**E3.S2 - Create appointment endpoint:**
|
||||
- Route: `POST /api/swift/agenda/create`
|
||||
- Body: `{ patientId, datetime: { date, time }, type, location, notes? }`
|
||||
- Validation: Zod schema, Dutch error messages
|
||||
- Hergebruik: `createEncounter()` from `app/epd/agenda/actions.ts`
|
||||
- Mapping:
|
||||
- type → typeCode (FHIR encounter type)
|
||||
- location → classCode ('AMB' = praktijk, 'VR' = online, 'HH' = thuis)
|
||||
- datetime → periodStart/periodEnd (1 hour duration default)
|
||||
- Response: `{ encounterId, success: true }`
|
||||
|
||||
**E3.S3 - Cancel/Reschedule endpoints:**
|
||||
- Route: `POST /api/swift/agenda/cancel`
|
||||
- Body: `{ encounterId }`
|
||||
- Hergebruik: `cancelEncounter(encounterId)` (soft delete: status → 'cancelled')
|
||||
|
||||
- Route: `POST /api/swift/agenda/reschedule`
|
||||
- Body: `{ encounterId, newDatetime: { date, time } }`
|
||||
- Hergebruik: `rescheduleEncounter(encounterId, periodStart, periodEnd)`
|
||||
|
||||
**E3.S4 - Patient search endpoint:**
|
||||
- Route: `GET /api/swift/patients/search?q=jan`
|
||||
- Fuzzy match op patient name (ILIKE %query%)
|
||||
- Response: `{ patients: Array<{ id, name, bsn }> }`
|
||||
- Limit: 10 resultaten
|
||||
- Used voor: disambiguation bij meerdere matches
|
||||
|
||||
---
|
||||
|
||||
### Epic 4 — AgendaBlock UI
|
||||
Epic doel: Swift artifact voor agenda flows.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E4.S1 | AgendaBlock skeleton | Block met tabs/modes (list/create/cancel/reschedule) | Done | E3.S1 | 3 |
|
||||
| E4.S2 | List view | Lijst met afspraken + empty state | Done | E4.S1 | 3 |
|
||||
| E4.S3 | Create form | Prefill + validatie + submit | Done | E4.S1 | 5 |
|
||||
| E4.S4 | Cancel view | Disambiguation + confirm flow | Done | E4.S1 | 3 |
|
||||
| E4.S5 | Reschedule view | Edit form met nieuwe tijd | Done | E4.S1 | 3 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E4.S1 - AgendaBlock component structure:**
|
||||
- File: `components/swift/artifacts/blocks/agenda-block.tsx`
|
||||
- Props interface:
|
||||
```ts
|
||||
interface AgendaBlockProps {
|
||||
mode: 'list' | 'create' | 'cancel' | 'reschedule';
|
||||
appointments?: Encounter[];
|
||||
dateRange?: { start: Date; end: Date; label: string };
|
||||
prefillData?: {
|
||||
patient?: { id: string; name: string };
|
||||
datetime?: { date: Date; time: string };
|
||||
type?: EncounterType;
|
||||
location?: EncounterLocation;
|
||||
};
|
||||
disambiguationOptions?: Encounter[];
|
||||
onClose?: () => void;
|
||||
}
|
||||
```
|
||||
- Architecture: switch statement op mode → render juiste sub-component
|
||||
- Styling: max-width 600px, max-height 80vh, overflow-y auto
|
||||
|
||||
**E4.S2 - List view:**
|
||||
- Component: `<AgendaListView />`
|
||||
- Layout:
|
||||
- Header: Datum label (e.g., "Afspraken Vandaag - 27 december") + close button
|
||||
- Body: Scrollable lijst van appointment cards
|
||||
- Footer: Link naar `/epd/agenda` ("📅 Open volledige agenda →")
|
||||
- Appointment card bevat:
|
||||
- Tijd (09:00 - 10:00)
|
||||
- Patient naam (klikbaar → opens PatientContextCard)
|
||||
- Type badge met color coding (intake=blauw, behandeling=groen, crisis=rood)
|
||||
- Locatie icon (📍 Praktijk, 🌐 Online, 🏠 Thuis)
|
||||
- Actions: [Details] [Annuleren]
|
||||
- Empty state: "📭 Geen afspraken gevonden" + [Maak nieuwe afspraak] button
|
||||
|
||||
**E4.S3 - Create form:**
|
||||
- Component: `<AgendaCreateForm />`
|
||||
- Fields:
|
||||
- Patiënt* (autocomplete, pre-filled)
|
||||
- Datum* (date picker, pre-filled)
|
||||
- Tijd* (time picker 07:00-20:00, pre-filled)
|
||||
- Type* (radio buttons: Intake, Behandeling, Vervolg, Telefonisch, Huisbezoek, Crisis)
|
||||
- Locatie* (radio buttons: Praktijk, Online, Thuis)
|
||||
- Notities (textarea, optioneel, max 500 chars)
|
||||
- Validation:
|
||||
- Patient required
|
||||
- Date cannot be in past
|
||||
- Time within 07:00-20:00
|
||||
- Conflict warning: "⚠️ Conflict: Je hebt al een afspraak om 14:00 met Marie" (optional feature)
|
||||
- Submit: [Annuleren] [✓ Afspraak maken]
|
||||
- States: initial → validating → submitting → success/error
|
||||
|
||||
**E4.S4 - Cancel view:**
|
||||
- Component: `<AgendaCancelView />`
|
||||
- Submode 1: Disambiguation (multiple matches)
|
||||
- Radio list met opties: "○ 28-12-2024 09:00 - Jan de Vries (Intake)"
|
||||
- [Annuleren] [Volgende →] buttons
|
||||
- Submode 2: Confirmation (single match or after disambiguation)
|
||||
- Appointment details card
|
||||
- Warning: "⚠️ Deze actie kan niet ongedaan worden gemaakt."
|
||||
- [Terug] [✓ Annuleren] buttons
|
||||
|
||||
**E4.S5 - Reschedule view:**
|
||||
- Component: `<AgendaRescheduleForm />`
|
||||
- Layout:
|
||||
- Afspraak details (readonly)
|
||||
- Huidige datum/tijd (readonly, strikethrough styling)
|
||||
- Nieuwe datum/tijd* (editable date + time pickers, pre-filled)
|
||||
- Conflict check: "✅ Geen conflicten gevonden" or warning
|
||||
- [Annuleren] [✓ Verzetten] buttons
|
||||
|
||||
**Styling specs:**
|
||||
- Colors: Teal-700 primary, appointment type badges (zie FO sectie 13.3)
|
||||
- Animations: slide-in from right 200ms, fade-out on close
|
||||
- Typography: text-sm voor body, text-base voor headers
|
||||
- Spacing: 16px padding, 12px card gaps
|
||||
|
||||
---
|
||||
|
||||
### Epic 5 — Chat orchestration
|
||||
Epic doel: agenda intents laten landen in juiste artifact.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E5.S1 | Action routing | Agenda intents openen AgendaBlock met juiste mode | Done | E4.S1 | 2 |
|
||||
| E5.S2 | Chat prompt update | `/api/swift/chat` prompt bevat agenda sectie + action format | Done | E1.S3 | 2 |
|
||||
| E5.S3 | Error states | User-friendly errors + link naar `/epd/agenda` | Done | E3.S1 | 2 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E5.S1 - Intent router implementation:**
|
||||
- Location: Swift store of artifact manager
|
||||
- Functie: `openArtifactForIntent(intent, entities, confidence)`
|
||||
- Logic:
|
||||
```ts
|
||||
if (confidence < 0.7) return null; // Trigger verduidelijkingsvraag
|
||||
|
||||
switch (intent) {
|
||||
case 'agenda_query':
|
||||
return { type: 'AgendaBlock', mode: 'list', prefill: { dateRange } };
|
||||
case 'create_appointment':
|
||||
if (!entities.patientName) return null; // Need patient
|
||||
return { type: 'AgendaBlock', mode: 'create', prefill: { patient, datetime, type, location } };
|
||||
case 'cancel_appointment':
|
||||
return { type: 'AgendaBlock', mode: 'cancel', prefill: { identifier } };
|
||||
case 'reschedule_appointment':
|
||||
if (!entities.identifier) return null; // Need to know which appointment
|
||||
return { type: 'AgendaBlock', mode: 'reschedule', prefill: { identifier, newDatetime } };
|
||||
}
|
||||
```
|
||||
|
||||
**E5.S2 - Chat API system prompt addition:**
|
||||
- File: `app/api/swift/chat/route.ts`
|
||||
- Toevoegen aan system prompt:
|
||||
```markdown
|
||||
## Agenda & Afspraken Beheer
|
||||
|
||||
Je helpt gebruikers met agenda-gerelateerde taken.
|
||||
|
||||
### Intents die je herkent:
|
||||
1. **agenda_query** - Afspraken opvragen
|
||||
Voorbeelden: "afspraken vandaag", "wat is volgende", "agenda morgen"
|
||||
Actie: Toon lijst van afspraken in AgendaBlock
|
||||
|
||||
2. **create_appointment** - Nieuwe afspraak maken
|
||||
Voorbeelden: "maak afspraak met Jan morgen 14:00", "plan intake"
|
||||
Required: patient, date, time
|
||||
Optional: type (default: behandeling), location (default: praktijk)
|
||||
|
||||
3. **cancel_appointment** - Afspraak annuleren
|
||||
Voorbeelden: "annuleer afspraak Jan", "cancel 14:00"
|
||||
Actie: Toon confirmation dialog
|
||||
|
||||
4. **reschedule_appointment** - Afspraak verzetten
|
||||
Voorbeelden: "verzet 14:00 naar 15:00", "verzet Jan naar dinsdag"
|
||||
Actie: Toon edit form met oude + nieuwe tijd
|
||||
|
||||
### Entity Extraction:
|
||||
- Patient: extraheer volledige naam, gebruik activePatient als user zegt "deze patiënt"
|
||||
- Date/Time: parse relatieve datums ("morgen", "dinsdag"), validate geen verleden
|
||||
- Type: intake, behandeling, follow-up, telefonisch (default: behandeling)
|
||||
- Location: praktijk, online, thuis (default: praktijk)
|
||||
|
||||
### Verduidelijkingsvragen:
|
||||
Stel vragen bij:
|
||||
- Meerdere patiënten met zelfde naam
|
||||
- Onduidelijke datum
|
||||
- Ontbrekende tijd bij create
|
||||
- Meerdere matches bij cancel/reschedule
|
||||
```
|
||||
|
||||
**E5.S3 - Error handling:**
|
||||
- Server errors: "Er ging iets mis bij het ophalen van je afspraken. Probeer het opnieuw of [open de volledige agenda](/epd/agenda)."
|
||||
- Auth errors: Redirect naar `/login`
|
||||
- Validation errors: Dutch messages ("Datum kan niet in het verleden liggen")
|
||||
- Network errors: Retry logic + user-friendly message
|
||||
|
||||
---
|
||||
|
||||
### Epic 6 — QA & docs
|
||||
Epic doel: kwaliteit borgen en documentatie updaten.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E6.S1 | Manual test checklist | Scenarios uit FO opgenomen | To Do | E5.S3 | 2 |
|
||||
| E6.S2 | Docs update | Bouwplan + release note bijgewerkt | To Do | E6.S1 | 1 |
|
||||
| E6.S3 | Regression checks | Swift en klassieke agenda blijven werken | To Do | E6.S1 | 2 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Kwaliteit & Testplan
|
||||
|
||||
Test types:
|
||||
| Test Type | Scope | Tools | Verantwoordelijke |
|
||||
|-----------|-------|-------|-------------------|
|
||||
| Lint | App code | pnpm lint | Developer |
|
||||
| Type check | TypeScript | pnpm build | Developer |
|
||||
| Smoke tests | Agenda intents + artifact flows | Manual checklist (20 scenarios) | Developer/UX |
|
||||
| Regression | /epd/agenda klassiek | Manual checklist | Developer |
|
||||
|
||||
### Manual Test Checklist (uit FO, 20 scenarios):
|
||||
|
||||
| # | Scenario | Expected Result | Priority | Status |
|
||||
|---|----------|-----------------|----------|--------|
|
||||
| 1 | "afspraken vandaag" | AgendaBlock list view, toont vandaag's afspraken | P1 | ⬜ |
|
||||
| 2 | "wat is volgende afspraak" | Chat response met eerstvolgende, AgendaBlock toont details | P1 | ⬜ |
|
||||
| 3 | "maak afspraak jan morgen 14:00" | Create form met prefill, patient gevonden | P1 | ⬜ |
|
||||
| 4 | Voice: "maak intake marie vrijdag 10:00" | Transcript correct, create form opent | P2 | ⬜ |
|
||||
| 5 | "maak afspraak" (incomplete) | Verduidelijkingsvraag: "Met welke patiënt?" | P1 | ⬜ |
|
||||
| 6 | "maak afspraak onbekende patient" | Patient search fallback, suggesties | P2 | ⬜ |
|
||||
| 7 | Create form submit → success | Toast, chat confirmation, artifact sluit | P1 | ⬜ |
|
||||
| 8 | "annuleer afspraak jan" (1 match) | Confirmation dialog, direct match | P1 | ⬜ |
|
||||
| 9 | "annuleer afspraak jan" (3 matches) | Disambiguation radio list | P1 | ⬜ |
|
||||
| 10 | Cancel confirm → success | Afspraak status=cancelled, toast | P1 | ⬜ |
|
||||
| 11 | "verzet 14:00 naar 15:00" | Reschedule form, tijd pre-filled | P1 | ⬜ |
|
||||
| 12 | Reschedule submit → success | Tijd updated, toast confirmation | P1 | ⬜ |
|
||||
| 13 | Conflict warning (overlapping) | Warning banner in create form | P2 | ⬜ |
|
||||
| 14 | Click patient name in list | PatientContextCard opent | P2 | ⬜ |
|
||||
| 15 | Click "Details" button | Navigates to /epd/agenda with filter | P2 | ⬜ |
|
||||
| 16 | Empty state (geen afspraken) | Placeholder met "Maak nieuwe afspraak" | P1 | ⬜ |
|
||||
| 17 | Server error tijdens query | Error in chat, geen artifact | P1 | ⬜ |
|
||||
| 18 | Date in past validation | "Kan geen afspraken in verleden maken" | P1 | ⬜ |
|
||||
| 19 | Context-aware: "maak afspraak deze patient" | Gebruikt active patient uit context | P2 | ⬜ |
|
||||
| 20 | Multiple artifacts (3 max) | Agenda + Dagnotitie + Context card, tabs | P2 | ⬜ |
|
||||
|
||||
### Regression Test Checklist:
|
||||
|
||||
| # | Test | Expected Result | Status |
|
||||
|---|------|-----------------|--------|
|
||||
| R1 | Klassieke agenda `/epd/agenda` werkt nog | Kan afspraken zien/maken/wijzigen | ⬜ |
|
||||
| R2 | Swift dagnotitie intent ongewijzigd | "dagnotitie jan medicatie" opent DagnotatieBlock | ⬜ |
|
||||
| R3 | Swift zoeken intent ongewijzigd | "zoek jan" opent ZoekenBlock | ⬜ |
|
||||
| R4 | Swift overdracht intent ongewijzigd | "overdracht" opent OverdrachtBlock | ⬜ |
|
||||
| R5 | Auth guard op alle endpoints | Geen auth → 401/redirect | ⬜ |
|
||||
|
||||
### Performance Test Targets:
|
||||
|
||||
| Metric | Target | Test Method |
|
||||
|--------|--------|-------------|
|
||||
| Local pattern match | <50ms | Browser DevTools Network tab |
|
||||
| Create form open | <150ms total | Lighthouse/manual timing |
|
||||
| AI fallback | <1000ms | DevTools, onduidelijke input |
|
||||
| List view render (10 items) | <100ms | React DevTools Profiler |
|
||||
|
||||
---
|
||||
|
||||
## 6. User Stories (uit FO)
|
||||
|
||||
### Primaire User Stories (P1 - MVP):
|
||||
|
||||
| ID | Als | Wil ik | Zodat | Acceptatie |
|
||||
|----|-----|--------|-------|------------|
|
||||
| US-24 | Verpleegkundige | Snel overzicht afspraken vandaag | Ik weet waar ik moet zijn | "afspraken vandaag" → lijst in AgendaBlock, <3 sec |
|
||||
| US-25 | Verpleegkundige | Check volgende afspraak tijdens werk | Ik op tijd ben | "wat is mijn volgende afspraak?" → directe info |
|
||||
| US-27 | Verpleegkundige | Snelle afspraak tijdens telefoongesprek | Ik direct kan plannen | "maak afspraak jan morgen 14:00" → prefilled form, <15 sec |
|
||||
| US-28 | Verpleegkundige | Context-aware planning | Ik niet steeds naam hoef te typen | "maak afspraak met deze patiënt" → gebruikt actieve patiënt |
|
||||
| US-29 | Verpleegkundige | Voice input tijdens consult | Ik hands-free kan werken | Spraak → afspraak maken |
|
||||
| US-30 | Verpleegkundige | Annuleren via chat | Ik snel kan annuleren | "annuleer afspraak jan" → confirmation → done |
|
||||
| US-31 | Verpleegkundige | Snel verzetten | Ik afspraken flexibel kan aanpassen | "verzet 14:00 naar 15:00" → tijd update |
|
||||
|
||||
### Secundaire User Stories (P2 - Nice to Have):
|
||||
|
||||
| ID | Als | Wil ik | Zodat | Acceptatie |
|
||||
|----|-----|--------|-------|------------|
|
||||
| US-26 | Verpleegkundige | Weekoverzicht bekijken | Ik kan plannen | "agenda deze week" → gefilterde lijst |
|
||||
| US-32 | Verpleegkundige | Disambiguation bij meerdere matches | Systeem helpt kiezen | Systeem vraagt "Welke Jan?" → lijst opties |
|
||||
|
||||
---
|
||||
|
||||
## 7. File Structuur (na implementatie)
|
||||
|
||||
```
|
||||
lib/swift/
|
||||
├── types.ts # SwiftIntent + ExtractedEntities uitbreidingen
|
||||
├── intent-classifier.ts # Local patterns voor agenda intents
|
||||
├── intent-classifier-ai.ts # AI prompt uitbreidingen
|
||||
└── date-time-parser.ts # 🆕 Date/time parsing utilities
|
||||
|
||||
app/api/swift/
|
||||
├── agenda/
|
||||
│ ├── route.ts # 🆕 GET agenda query endpoint
|
||||
│ ├── create/route.ts # 🆕 POST create appointment
|
||||
│ ├── cancel/route.ts # 🆕 POST cancel appointment
|
||||
│ └── reschedule/route.ts # 🆕 POST reschedule appointment
|
||||
├── patients/
|
||||
│ └── search/route.ts # 🆕 GET fuzzy patient search
|
||||
└── chat/route.ts # Updated: agenda system prompt
|
||||
|
||||
components/swift/artifacts/blocks/
|
||||
├── agenda-block.tsx # 🆕 Main AgendaBlock component
|
||||
├── agenda-list-view.tsx # 🆕 List mode
|
||||
├── agenda-create-form.tsx # 🆕 Create mode
|
||||
├── agenda-cancel-view.tsx # 🆕 Cancel mode
|
||||
└── agenda-reschedule-form.tsx # 🆕 Reschedule mode
|
||||
|
||||
app/epd/agenda/
|
||||
└── actions.ts # ♻️ Hergebruikt (getEncounters, createEncounter, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Demo & Presentatieplan
|
||||
Doel: korte demo van agenda planning via Swift (5 minuten).
|
||||
|
||||
**Demo Flow:**
|
||||
1. **Agenda query**: "afspraken vandaag" → lijst met 3 afspraken
|
||||
2. **Quick create**: "maak afspraak met Jan morgen 14:00" → create form → submit → success toast
|
||||
3. **Cancel flow**: "annuleer afspraak Jan" → disambiguation (3 matches) → select → confirm → success
|
||||
4. **Reschedule**: "verzet de 14:00 naar 15:00" → edit form → submit → success
|
||||
5. **Fallback**: Link naar klassieke agenda tonen via footer
|
||||
|
||||
**Key Messaging:**
|
||||
- Swift = snelle queries en actions (10-15 sec)
|
||||
- Klassieke agenda = visuele planning en overzicht
|
||||
- Voice input = hands-free werken tijdens consult
|
||||
|
||||
---
|
||||
|
||||
## 9. Risico's & Mitigatie
|
||||
| Risico | Kans | Impact | Mitigatie | Owner |
|
||||
|--------|------|--------|-----------|-------|
|
||||
| Intent ambigu | Hoog | Middel | Disambiguation + fallback prompt | UX |
|
||||
| Date/time parsing faalt | Middel | Middel | AI fallback + clear prompts | Dev |
|
||||
| API auth issues | Laag | Hoog | Central auth guard + error messaging | Dev |
|
||||
| Scope creep | Hoog | Middel | MVP guardrails | PO |
|
||||
|
||||
---
|
||||
|
||||
## 10. Sprint Planning & Implementatie Volgorde
|
||||
|
||||
### Voorgestelde Sprint Indeling (3 sprints):
|
||||
|
||||
**Sprint 1: Foundation (E1 + E2)**
|
||||
- Focus: Intent layer + date/time parsing
|
||||
- Stories: E1.S1, E1.S2, E1.S3, E1.S4, E2.S1, E2.S2, E2.S3
|
||||
- Total: 17 SP
|
||||
- Oplevering: Intent detection + entity extraction werkend, nog geen UI
|
||||
- Test: Console log check van extracted entities
|
||||
|
||||
**Sprint 2: Backend + UI Foundation (E3 + E4.S1-S2)**
|
||||
- Focus: API endpoints + AgendaBlock basis
|
||||
- Stories: E3.S1, E3.S2, E3.S3, E3.S4, E4.S1, E4.S2
|
||||
- Total: 14 SP
|
||||
- Oplevering: Agenda query werkend met list view
|
||||
- Test: "afspraken vandaag" end-to-end
|
||||
|
||||
**Sprint 3: UI Completion + Integration (E4.S3-S5 + E5 + E6)**
|
||||
- Focus: Create/cancel/reschedule forms + orchestration
|
||||
- Stories: E4.S3, E4.S4, E4.S5, E5.S1, E5.S2, E5.S3, E6.S1, E6.S2, E6.S3
|
||||
- Total: 20 SP
|
||||
- Oplevering: MVP compleet
|
||||
- Test: Alle 20 test scenarios
|
||||
|
||||
**Total MVP: 51 SP (~3 sprints à 2 weken = 6 weken)**
|
||||
|
||||
### Implementatie Volgorde (binnen stories):
|
||||
|
||||
Voor elke story, volg deze volgorde:
|
||||
1. **Types first**: Definieer interfaces/types
|
||||
2. **Backend**: API endpoints + server actions
|
||||
3. **Frontend**: UI components
|
||||
4. **Integration**: Wire backend ↔ frontend
|
||||
5. **Test**: Manual test scenario's
|
||||
|
||||
### Critical Path:
|
||||
```
|
||||
E1.S1 (types) → E1.S2 (patterns) → E1.S4 (entities) → E2 (parsers) →
|
||||
E3.S1 (query API) → E4.S1 (skeleton) → E4.S2 (list view) →
|
||||
E5.S1 (routing) → Milestone: Basic Query Werkend
|
||||
|
||||
E3.S2 (create API) → E4.S3 (create form) → Milestone: Create Werkend
|
||||
|
||||
E3.S3 (cancel API) → E4.S4 (cancel view) → Milestone: Cancel Werkend
|
||||
|
||||
E4.S5 (reschedule) → E5.S2 (prompt) → E6 (QA) → MVP Done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Evaluatie & Lessons Learned
|
||||
Te documenteren na oplevering:
|
||||
- Welke intents vaak misclassificeren?
|
||||
- Hoe snel users afspraken kunnen plannen?
|
||||
- Zijn extra agenda features nodig?
|
||||
|
||||
---
|
||||
|
||||
## 12. Referenties & Documentatie
|
||||
|
||||
### Primaire Documenten:
|
||||
- **FO Agenda Afspraken**: `docs/swift/fo-agenda-afspraken.md` - Gebruikersgericht FO
|
||||
- **FO Agenda Planning**: `docs/swift/fo-swift-agenda-planning.md` - Technisch FO met detailed specs
|
||||
- **Swift FO v3**: `docs/swift/fo-swift-medical-scribe-v3.md` - Basis conversational interface
|
||||
- **Swift Bouwplan Core**: `docs/swift/bouwplan-swift-standalone-module.md` - Development roadmap
|
||||
|
||||
### Code Referenties:
|
||||
- **Bestaande Agenda**: `app/epd/agenda` - Klassieke kalender (wordt hergebruikt)
|
||||
- **Agenda Actions**: `app/epd/agenda/actions.ts` - Server actions (getEncounters, createEncounter, etc.)
|
||||
- **Swift Chat API**: `app/api/swift/chat/route.ts` - Conversational endpoint
|
||||
- **Intent Classifier**: `lib/swift/intent-classifier.ts` - Local pattern matching
|
||||
- **AI Classifier**: `lib/swift/intent-classifier-ai.ts` - Claude Haiku fallback
|
||||
- **Swift Types**: `lib/swift/types.ts` - SwiftIntent & ExtractedEntities
|
||||
|
||||
### Libraries & Dependencies:
|
||||
- **date-fns**: Date/time utilities (already installed)
|
||||
- **Zod**: API validation schemas (already installed)
|
||||
- **React Query**: Data caching (already installed)
|
||||
- **shadcn/ui**: UI components (already installed)
|
||||
- **Deepgram**: Voice input (already integrated)
|
||||
|
||||
### External References:
|
||||
- [FHIR Encounter Resource](https://www.hl7.org/fhir/encounter.html) - Encounter data model
|
||||
- [date-fns Documentation](https://date-fns.org/) - Date manipulation
|
||||
- [React Query Best Practices](https://tanstack.com/query/latest) - Caching patterns
|
||||
|
||||
---
|
||||
|
||||
## 13. Glossary & Abbreviations
|
||||
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| **Epic** | Grote feature of fase, bevat meerdere stories |
|
||||
| **Story** | Kleine uitvoerbare taak (user story) |
|
||||
| **SP** | Story Points - schatting van complexiteit |
|
||||
| **MVP** | Minimum Viable Product |
|
||||
| **FO** | Functioneel Ontwerp |
|
||||
| **P1/P2** | Priority 1 (must have) / Priority 2 (nice to have) |
|
||||
| **SSE** | Server-Sent Events |
|
||||
| **RLS** | Row Level Security (Supabase) |
|
||||
| **NLP** | Natural Language Processing |
|
||||
| **Artifact** | Swift UI component (AgendaBlock, DagnotatieBlock, etc.) |
|
||||
| **Intent** | Gebruikersintentie (agenda_query, create_appointment, etc.) |
|
||||
| **Entity** | Geëxtraheerde data uit user input (patient, date, time) |
|
||||
| **Disambiguation** | Verduidelijking bij meerdere matches |
|
||||
| **Confidence** | Zekerheid van intent classificatie (0.0-1.0) |
|
||||
| **Two-tier** | Local pattern matching + AI fallback systeem |
|
||||
| **FHIR** | Fast Healthcare Interoperability Resources |
|
||||
| **Encounter** | Afspraak/contact in FHIR terminologie |
|
||||
|
||||
### Domain-Specific Terms:
|
||||
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| **Klassieke Agenda** | Bestaande visuele kalender in `/epd/agenda` |
|
||||
| **Swift Agenda** | Nieuwe conversational agenda interface |
|
||||
| **AgendaBlock** | Swift artifact voor agenda functionaliteit |
|
||||
| **Verpleegkundige** | Primaire gebruikersrol (nurse/healthcare provider) |
|
||||
| **Practitioner** | FHIR term voor zorgverlener |
|
||||
| **Quick Create** | Snel afspraak maken via natuurlijke taal |
|
||||
| **Hands-free** | Werken met voice input (geen typen nodig) |
|
||||
|
||||
---
|
||||
|
||||
## Versiehistorie
|
||||
|
||||
| Versie | Datum | Auteur | Wijziging |
|
||||
|--------|-------|--------|-----------|
|
||||
| v1.0 | 27-12-2025 | Colin Lit | Initiele versie - basis epics en stories |
|
||||
| v1.1 | 27-12-2025 | Claude Code | Uitgebreid met technische details uit FO's: patterns, entities, API specs, UI components, test scenarios, sprint planning, file structuur |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
**Voor Developers - Start Here:**
|
||||
1. Lees Epic 0 voor scope
|
||||
2. Begin met E1.S1 (types uitbreiden)
|
||||
3. Volg implementatie volgorde (sectie 10)
|
||||
4. Check test scenarios (sectie 5)
|
||||
5. Zie file structuur (sectie 7) voor waar code moet komen
|
||||
|
||||
**Voor Product Owners:**
|
||||
- MVP scope: sectie 2.2
|
||||
- User stories: sectie 6
|
||||
- Demo plan: sectie 8
|
||||
- Risico's: sectie 9
|
||||
|
||||
**Voor QA:**
|
||||
- Test checklist: sectie 5 (20 scenarios)
|
||||
- Regression tests: sectie 5
|
||||
- Performance targets: sectie 2.4
|
||||
|
||||
**Voor Designers:**
|
||||
- UI components: Epic 4 technical notes
|
||||
- Styling specs: E4.S1-E4.S5
|
||||
- User flows: FO documenten (zie sectie 12)
|
||||
@@ -1,700 +0,0 @@
|
||||
# 📋 Beoordeling Bouwplan Swift: Diagnostiek Workflow
|
||||
|
||||
**Datum:** 23-12-2024
|
||||
**Beoordelaar:** AI Code Review
|
||||
**Versie:** v1.0
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Algemene Beoordeling:** ✅ **HAALBAAR met enkele kritieke aanvullingen**
|
||||
|
||||
Het bouwplan is goed gestructureerd en bouwt slim voort op bestaande componenten. De geschatte 21 story points voor 2 weken zijn realistisch, maar er zijn enkele belangrijke technische hiaten die eerst opgelost moeten worden voordat de implementatie kan starten.
|
||||
|
||||
**Kritieke Bevindingen:**
|
||||
1. ❌ **Report type "diagnostiek" ontbreekt** in `REPORT_TYPES` enum
|
||||
2. ❌ **Swift intent types** moeten uitgebreid worden met nieuwe intents
|
||||
3. ⚠️ **Entity extraction** voor datum/tijd parsing ontbreekt in plan
|
||||
4. ⚠️ **Intake selectie** voor diagnoses niet duidelijk uitgewerkt
|
||||
5. ✅ **Hergebruik componenten** is goed geanalyseerd en realistisch
|
||||
|
||||
**Blokkerende Bevindingen (Swift Foundation - 24-12-2024):**
|
||||
6. 🔴 **E2.S5 Input → Block wiring ontbreekt** - handleSubmit is placeholder
|
||||
7. 🔴 **CanvasArea block rendering ontbreekt** - Blocks worden niet gerenderd
|
||||
8. 🔴 **P1 Blocks (E3) niet geïmplementeerd** - DagnotatieBlock, ZoekenBlock, OverdrachtBlock bestaan niet
|
||||
9. ⚠️ **Type duplicatie** - SwiftIntent in zowel types.ts als swift-store.ts
|
||||
|
||||
---
|
||||
|
||||
## 1. Compleetheid Analyse
|
||||
|
||||
### 1.1 ✅ Sterke Punten
|
||||
|
||||
**Goed Gedocumenteerd:**
|
||||
- Duidelijke epic/story breakdown met acceptatiecriteria
|
||||
- Realistische effort schattingen (21 SP voor 2 weken)
|
||||
- Goede referenties naar bestaande code
|
||||
- Testplan met concrete checklists
|
||||
- Risico analyse met mitigaties
|
||||
|
||||
**Technische Analyse:**
|
||||
- Correcte identificatie van hergebruikbare componenten
|
||||
- Wrapper pattern goed uitgelegd
|
||||
- API routes strategie is logisch
|
||||
|
||||
### 1.2 ❌ Ontbrekende Elementen
|
||||
|
||||
#### Kritiek: Report Type "diagnostiek"
|
||||
|
||||
**Probleem:**
|
||||
Het bouwplan noemt het toevoegen van report type "diagnostiek" (regel 179), maar dit type bestaat niet in de `REPORT_TYPES` enum.
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// lib/types/report.ts
|
||||
export const REPORT_TYPES = [
|
||||
'voortgang', 'observatie', 'incident', 'medicatie', 'contact',
|
||||
'crisis', 'intake', 'behandeladvies', 'vrije_notitie', 'verpleegkundig'
|
||||
] as const;
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- Database validatie zal falen bij POST `/api/reports` met type "diagnostiek"
|
||||
- Zod schema moet uitgebreid worden
|
||||
- Mogelijk database constraint check nodig
|
||||
|
||||
**Aanbeveling:**
|
||||
Voeg story toe: **E-D1.S0: Report type "diagnostiek" toevoegen** (1 SP)
|
||||
- Update `REPORT_TYPES` enum
|
||||
- Update Zod schema `CreateReportSchema`
|
||||
- Database migratie indien nodig (check constraints)
|
||||
|
||||
#### Kritiek: Swift Intent Types Uitbreiding
|
||||
|
||||
**Probleem:**
|
||||
Het bouwplan beschrijft nieuwe intent patterns, maar de `SwiftIntent` type definitie moet uitgebreid worden.
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// lib/swift/types.ts
|
||||
export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown';
|
||||
```
|
||||
|
||||
**Nodig:**
|
||||
```typescript
|
||||
export type SwiftIntent =
|
||||
| 'dagnotitie'
|
||||
| 'zoeken'
|
||||
| 'overdracht'
|
||||
| 'afspraak_maken' // NIEUW
|
||||
| 'rapportage' // NIEUW
|
||||
| 'diagnose_bekijken' // NIEUW
|
||||
| 'diagnose_toevoegen' // NIEUW
|
||||
| 'diagnose_wijzigen' // NIEUW
|
||||
| 'unknown';
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- TypeScript compile errors zonder deze uitbreiding
|
||||
- Block configs moeten uitgebreid worden
|
||||
- Intent classifier moet nieuwe types ondersteunen
|
||||
|
||||
**Aanbeveling:**
|
||||
Voeg toe aan E-D1.S1 en E-D2.S1:
|
||||
- Update `SwiftIntent` type
|
||||
- Update `BLOCK_CONFIGS` met nieuwe block types
|
||||
- Update `BlockType` type (exclude 'unknown')
|
||||
|
||||
#### Waarschuwing: Entity Extraction voor Datum/Tijd
|
||||
|
||||
**Probleem:**
|
||||
Het bouwplan beschrijft intent patterns die datum/tijd moeten extraheren (bijv. "morgen 10:00"), maar er is geen plan voor entity extraction van deze waarden.
|
||||
|
||||
**Voorbeeld uit plan:**
|
||||
```
|
||||
/^afspraak\s+(\w+)\s+(morgen|vandaag)\s+(\d{1,2}:\d{2})/i
|
||||
```
|
||||
|
||||
**Ontbrekend:**
|
||||
- Functie om "morgen" → Date object te converteren
|
||||
- Functie om "10:00" → tijd te parseren
|
||||
- Validatie van datum/tijd combinaties
|
||||
- Fallback naar date picker bij onduidelijke input
|
||||
|
||||
**Huidige situatie:**
|
||||
`lib/swift/entity-extractor.ts` bestaat, maar bevat alleen patient name extraction.
|
||||
|
||||
**Aanbeveling:**
|
||||
Voeg toe aan E-D1.S1:
|
||||
- `extractDateTime(input: string): { date?: Date; time?: string }`
|
||||
- Integratie met date-fns voor Nederlandse datum parsing
|
||||
- Fallback logica voor onduidelijke input
|
||||
|
||||
#### Waarschuwing: Intake Selectie voor Diagnoses
|
||||
|
||||
**Probleem:**
|
||||
Het bouwplan beschrijft diagnose toevoegen, maar `DiagnosisDetailForm` vereist een `intakeId` (encounter_id). Het plan beschrijft niet hoe dit wordt bepaald vanuit Swift context.
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// app/epd/patients/[id]/diagnose/components/diagnosis-detail-form.tsx
|
||||
// Vereist: intakes array en selectedIntakeId
|
||||
```
|
||||
|
||||
**Vragen:**
|
||||
- Moet Swift automatisch de laatste intake selecteren?
|
||||
- Moet Swift een intake selector tonen?
|
||||
- Kan diagnose zonder intake (direct encounter koppeling)?
|
||||
|
||||
**Aanbeveling:**
|
||||
Voeg toe aan E-D2.S3:
|
||||
- Beslissing: automatisch laatste intake of selector tonen
|
||||
- Documenteer in acceptatiecriteria
|
||||
- Update DiagnoseFormBlock implementatie
|
||||
|
||||
### 1.3 ⚠️ Onduidelijkheden
|
||||
|
||||
#### Appointment Modal Hergebruik
|
||||
|
||||
**Vraag:**
|
||||
Het plan zegt "80% hergebruik" van `AppointmentModal`, maar deze component is een Dialog met veel interne state. Hoe wordt dit geïntegreerd in Swift blocks?
|
||||
|
||||
**Huidige situatie:**
|
||||
- `AppointmentModal` is een volledig Dialog component
|
||||
- Swift blocks gebruiken `BlockContainer` (geen Dialog)
|
||||
- Dark theme styling moet aangepast worden
|
||||
|
||||
**Aanbeveling:**
|
||||
Clarificeer in E-D1.S2:
|
||||
- Option A: Extract form logica naar shared component, wrapper in beide contexts
|
||||
- Option B: Hergebruik AppointmentModal maar wrap in BlockContainer (mogelijk styling issues)
|
||||
- Option C: Nieuwe Swift-specifieke component met shared business logic
|
||||
|
||||
#### Encounter ID Callback Pattern
|
||||
|
||||
**Vraag:**
|
||||
Het plan zegt "encounter_id teruggeven via callback", maar hoe wordt dit gebruikt voor de volgende stap (rapportage)?
|
||||
|
||||
**Scenario:**
|
||||
1. User: "afspraak diagnostiek jan morgen 10:00"
|
||||
2. AfspraakBlock → encounter_id = "abc-123"
|
||||
3. User: "rapportage diagnostiek gesprek met jan"
|
||||
4. Hoe weet RapportageBlock dat encounter_id "abc-123" moet gebruiken?
|
||||
|
||||
**Aanbeveling:**
|
||||
Clarificeer:
|
||||
- Option A: Swift store houdt laatste encounter_id bij per patient
|
||||
- Option B: User moet encounter expliciet selecteren
|
||||
- Option C: AI fallback om encounter te matchen op datum/tijd
|
||||
|
||||
---
|
||||
|
||||
## 2. Haalbaarheid Analyse
|
||||
|
||||
### 2.1 ✅ Realistische Schattingen
|
||||
|
||||
**Story Points Breakdown:**
|
||||
- E-D1.S1: 2 SP (intent patterns) → **Realistisch**
|
||||
- E-D1.S2: 5 SP (AfspraakBlock) → **Realistisch** (met hergebruik)
|
||||
- E-D1.S3: 4 SP (RapportageBlock) → **Realistisch**
|
||||
- E-D2.S1: 2 SP (intent patterns) → **Realistisch**
|
||||
- E-D2.S2: 3 SP (DiagnoseBlock) → **Realistisch**
|
||||
- E-D2.S3: 5 SP (DiagnoseFormBlock) → **Realistisch**
|
||||
|
||||
**Totaal: 21 SP voor 2 weken = ~10 SP/week**
|
||||
Dit is haalbaar voor 1 developer met goede focus.
|
||||
|
||||
### 2.2 ⚠️ Risico's op Vertraging
|
||||
|
||||
**Hoog Risico:**
|
||||
1. **Appointment Modal Integratie** (E-D1.S2)
|
||||
- Hergebruik Dialog component in Block context kan complex zijn
|
||||
- Styling aanpassingen kunnen meer tijd kosten dan geschat
|
||||
- **Mitigatie:** Start met proof-of-concept, pas schatting aan indien nodig
|
||||
|
||||
2. **Datum/Tijd Parsing** (E-D1.S1)
|
||||
- Nederlandse datum parsing ("morgen", "volgende week") kan edge cases hebben
|
||||
- Tijdzone handling voor afspraken
|
||||
- **Mitigatie:** Gebruik date-fns met Nederlandse locale, test grondig
|
||||
|
||||
**Middel Risico:**
|
||||
1. **ICD-10 Zoeker Verbetering** (E-D2.S3)
|
||||
- Plan zegt "fuzzy search verbeteren" maar geeft geen specificaties
|
||||
- Bestaande zoeker werkt al redelijk goed
|
||||
- **Mitigatie:** Eerst testen of verbetering nodig is, anders scope verkleinen
|
||||
|
||||
2. **Encounter Koppeling** (E-D1.S3)
|
||||
- Automatische koppeling tussen afspraak en rapportage kan complex zijn
|
||||
- **Mitigatie:** Start met optionele koppeling, voeg automatische matching later toe
|
||||
|
||||
### 2.3 ✅ Goede Foundation
|
||||
|
||||
**Bestaande Componenten:**
|
||||
- ✅ AppointmentModal bestaat en is goed gestructureerd
|
||||
- ✅ ReportComposer bestaat en ondersteunt al `linkedEncounterId`
|
||||
- ✅ DiagnosisDetailForm bestaat met ICD-10 zoeker
|
||||
- ✅ Actions bestaan en zijn herbruikbaar
|
||||
- ✅ ICD-10 zoeker werkt al met fuzzy search
|
||||
|
||||
**Bestaande Infrastructuur:**
|
||||
- ✅ Swift foundation (Command Center, Intent Classification) werkt
|
||||
- ✅ Block system bestaat (`BlockContainer`)
|
||||
- ✅ API routes pattern is duidelijk
|
||||
- ✅ Supabase RLS policies zijn al geïmplementeerd
|
||||
|
||||
---
|
||||
|
||||
## 3. Technische Aanbevelingen
|
||||
|
||||
### 3.1 Kritieke Toevoegingen
|
||||
|
||||
#### Story E-D1.S0: Report Type "diagnostiek" (NIEUW - 1 SP)
|
||||
|
||||
**Beschrijving:**
|
||||
Voeg report type "diagnostiek" toe aan het systeem.
|
||||
|
||||
**Acceptatiecriteria:**
|
||||
- [ ] `REPORT_TYPES` enum bevat "diagnostiek"
|
||||
- [ ] Zod schema `CreateReportSchema` accepteert "diagnostiek"
|
||||
- [ ] Database constraint check (indien nodig)
|
||||
- [ ] QuickActions component toont "diagnostiek" optie (indien van toepassing)
|
||||
|
||||
**Technical Notes:**
|
||||
```typescript
|
||||
// lib/types/report.ts
|
||||
export const REPORT_TYPES = [
|
||||
// ... bestaande types
|
||||
'diagnostiek', // NIEUW
|
||||
] as const;
|
||||
```
|
||||
|
||||
#### Story E-D1.S1 Uitbreiding: Entity Extraction
|
||||
|
||||
**Toevoegen aan E-D1.S1:**
|
||||
- [ ] `extractDateTime(input: string)` functie
|
||||
- [ ] Nederlandse datum parsing ("morgen", "vandaag", "volgende week")
|
||||
- [ ] Tijd parsing ("10:00", "14:30")
|
||||
- [ ] Fallback naar date picker bij onduidelijke input
|
||||
|
||||
**Technical Notes:**
|
||||
```typescript
|
||||
// lib/swift/entity-extractor.ts
|
||||
export function extractDateTime(input: string): {
|
||||
date?: Date;
|
||||
time?: string;
|
||||
confidence: number;
|
||||
} {
|
||||
// Parse "morgen 10:00" → { date: tomorrow, time: "10:00" }
|
||||
// Parse "vandaag 14:30" → { date: today, time: "14:30" }
|
||||
// Return confidence voor fallback beslissing
|
||||
}
|
||||
```
|
||||
|
||||
#### Story E-D2.S3 Uitbreiding: Intake Selectie
|
||||
|
||||
**Clarificatie nodig:**
|
||||
- [ ] Beslissing: automatisch laatste intake of selector?
|
||||
- [ ] Documenteer in acceptatiecriteria
|
||||
- [ ] Implementeer gekozen aanpak
|
||||
|
||||
**Aanbeveling:**
|
||||
Automatisch laatste intake selecteren, met optie om te wijzigen:
|
||||
```typescript
|
||||
// DiagnoseFormBlock
|
||||
const intakes = await getPatientIntakes(patientId);
|
||||
const defaultIntakeId = intakes[0]?.id; // Laatste intake
|
||||
// Toon dropdown indien meerdere intakes beschikbaar
|
||||
```
|
||||
|
||||
### 3.2 Verbeteringen
|
||||
|
||||
#### Intent Patterns Verbeteren
|
||||
|
||||
**Huidige patterns zijn te specifiek:**
|
||||
```typescript
|
||||
// Te specifiek - mist veel variaties
|
||||
/^afspraak\s+(diagnostiek|behandeling)\s+(\w+)/i
|
||||
```
|
||||
|
||||
**Aanbeveling:**
|
||||
Voeg meer variaties toe:
|
||||
```typescript
|
||||
afspraak_maken: [
|
||||
// Basis patterns
|
||||
{ pattern: /^afspraak\s+(diagnostiek|behandeling)\s+(\w+)/i, weight: 1.0 },
|
||||
{ pattern: /^plan\s+(diagnostiek|behandeling)\s+(\w+)/i, weight: 0.95 },
|
||||
|
||||
// Met datum/tijd
|
||||
{ pattern: /^afspraak\s+(\w+)\s+(morgen|vandaag)\s+(\d{1,2}:\d{2})/i, weight: 1.0 },
|
||||
{ pattern: /^plan\s+(\w+)\s+(morgen|vandaag)\s+(\d{1,2}:\d{2})/i, weight: 0.95 },
|
||||
|
||||
// Zonder type (default diagnostiek)
|
||||
{ pattern: /^afspraak\s+(\w+)\s+(morgen|vandaag)/i, weight: 0.85 },
|
||||
|
||||
// Alleen "afspraak" met patient naam
|
||||
{ pattern: /^afspraak\s+(\w+)/i, weight: 0.7 },
|
||||
],
|
||||
```
|
||||
|
||||
#### API Routes Specificatie
|
||||
|
||||
**Huidige beschrijving is te vaag:**
|
||||
```typescript
|
||||
// app/api/appointments/route.ts
|
||||
export async function POST(request: NextRequest) {
|
||||
// Wrapper rond app/epd/agenda/actions.ts createEncounter
|
||||
// Retourneert encounter_id voor volgende stap
|
||||
}
|
||||
```
|
||||
|
||||
**Aanbeveling:**
|
||||
Specificeer volledige API contract:
|
||||
```typescript
|
||||
// POST /api/appointments
|
||||
// Request body:
|
||||
{
|
||||
patientId: string;
|
||||
periodStart: string; // ISO 8601
|
||||
periodEnd?: string;
|
||||
typeCode: 'diagnostiek' | 'behandeling' | ...;
|
||||
typeDisplay: string;
|
||||
classCode: 'AMB' | 'HH' | 'VR';
|
||||
classDisplay: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
// Response:
|
||||
{
|
||||
success: boolean;
|
||||
data?: {
|
||||
id: string; // encounter_id
|
||||
// ... andere encounter velden
|
||||
};
|
||||
error?: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Testplan Verbeteringen
|
||||
|
||||
### 4.1 Ontbrekende Test Cases
|
||||
|
||||
**Entity Extraction Tests:**
|
||||
- [ ] "morgen 10:00" → correcte datum + tijd
|
||||
- [ ] "vandaag 14:30" → correcte datum + tijd
|
||||
- [ ] "volgende week maandag" → correcte datum
|
||||
- [ ] "afspraak jan" → alleen patient, geen datum → fallback date picker
|
||||
- [ ] "afspraak jan morgen" → patient + datum, geen tijd → fallback time picker
|
||||
|
||||
**Encounter Koppeling Tests:**
|
||||
- [ ] Afspraak aanmaken → encounter_id opgeslagen
|
||||
- [ ] Rapportage met encounter_id → correct gekoppeld
|
||||
- [ ] Rapportage zonder encounter_id → optioneel (geen error)
|
||||
- [ ] Meerdere encounters opzelfde dag → juiste selectie
|
||||
|
||||
**Diagnose Intake Tests:**
|
||||
- [ ] Patiënt met 1 intake → automatisch geselecteerd
|
||||
- [ ] Patiënt met meerdere intakes → laatste geselecteerd
|
||||
- [ ] Patiënt zonder intakes → error/fallback
|
||||
|
||||
### 4.2 Integration Test Scenarios
|
||||
|
||||
**End-to-End Flow:**
|
||||
```
|
||||
1. "afspraak diagnostiek jan morgen 10:00"
|
||||
→ AfspraakBlock opent
|
||||
→ Patient "jan" gevonden
|
||||
→ Datum: morgen
|
||||
→ Tijd: 10:00
|
||||
→ Type: diagnostiek
|
||||
→ Opslaan → encounter_id = "abc-123"
|
||||
|
||||
2. "rapportage diagnostiek gesprek met jan"
|
||||
→ RapportageBlock opent
|
||||
→ Patient "jan" gevonden
|
||||
→ Type: diagnostiek
|
||||
→ Encounter: "abc-123" (laatste encounter van jan)
|
||||
→ Content invoeren
|
||||
→ Opslaan → report gekoppeld aan encounter
|
||||
|
||||
3. "diagnose jan"
|
||||
→ DiagnoseBlock opent
|
||||
→ Overzicht diagnoses van jan
|
||||
→ Filter: Actief
|
||||
|
||||
4. "diagnose toevoegen jan F41.1"
|
||||
→ DiagnoseFormBlock opent
|
||||
→ Patient: jan
|
||||
→ ICD-10: F41.1 (pre-filled)
|
||||
→ Intake: laatste intake (automatisch)
|
||||
→ Type: nevendiagnose (default)
|
||||
→ Opslaan → diagnose toegevoegd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Swift Foundation Status (24-12-2024)
|
||||
|
||||
> **BELANGRIJK:** De diagnostiek workflow bouwt voort op de Swift foundation (bouwplan-swift-v1.md).
|
||||
> De foundation is nog niet compleet, waardoor de diagnostiek workflow nog niet kan starten.
|
||||
|
||||
### 5.1 Foundation Status Overzicht
|
||||
|
||||
| Epic | Status | Impact op Diagnostiek |
|
||||
|------|--------|----------------------|
|
||||
| E0 Setup & Foundation | ✅ Done | Geen blokkade |
|
||||
| E1 Command Center | ✅ Done | Geen blokkade |
|
||||
| E2 Intent Classification | ⏳ In Progress | 🔴 **BLOKKADE** |
|
||||
| E3 P1 Blocks | ⏳ To Do | 🔴 **BLOKKADE** |
|
||||
| E4 Navigation & Auth | ⏳ To Do | Geen blokkade |
|
||||
| E5 Polish & Testing | ⏳ To Do | Geen blokkade |
|
||||
|
||||
### 5.2 Blokkerende Issues
|
||||
|
||||
#### 🔴 Blokkade 1: E2.S5 Input → Block Wiring
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// components/swift/command-center/command-input.tsx:120
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
// TODO: Process intent (E2)
|
||||
console.log('Submit:', inputValue);
|
||||
clearInput();
|
||||
};
|
||||
```
|
||||
|
||||
**Probleem:**
|
||||
De intent classificatie API bestaat (`/api/intent/classify`), maar wordt niet aangeroepen.
|
||||
Het resultaat wordt niet gebruikt om `openBlock()` aan te roepen.
|
||||
|
||||
**Impact:**
|
||||
Zonder deze wiring kan geen enkel block geopend worden via tekst/spraak input.
|
||||
|
||||
**Story toegevoegd aan bouwplan-swift-v1.md (v1.5):**
|
||||
```markdown
|
||||
| E2.S5 | Input → Block wiring | CommandInput submit → API → openBlock | ⏳ | E2.S4 | 2 |
|
||||
```
|
||||
|
||||
#### 🔴 Blokkade 2: CanvasArea Block Rendering
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// components/swift/command-center/canvas-area.tsx:18
|
||||
{activeBlock ? (
|
||||
<div className="text-slate-400">Block: {activeBlock}</div> // ← Placeholder!
|
||||
) : (
|
||||
<EmptyState />
|
||||
)}
|
||||
```
|
||||
|
||||
**Probleem:**
|
||||
De CanvasArea toont alleen placeholder tekst, niet de daadwerkelijke block componenten.
|
||||
Er is geen switch/case of dynamic import voor de block types.
|
||||
|
||||
**Impact:**
|
||||
Zelfs als E2.S5 werkt, worden blocks niet zichtbaar.
|
||||
|
||||
**Aanbeveling:**
|
||||
Voeg story E3.S0 toe aan bouwplan-swift-v1.md:
|
||||
```markdown
|
||||
| E3.S0 | CanvasArea block rendering | Switch/case voor block types, prefill doorgeven | ⏳ | E2.S5 | 2 |
|
||||
```
|
||||
|
||||
#### 🔴 Blokkade 3: P1 Blocks Bestaan Niet
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// components/swift/blocks/index.ts
|
||||
export { BlockContainer } from './block-container';
|
||||
|
||||
// Commented out - bestaan niet:
|
||||
// export { DagnotatieBlock } from './dagnotitie-block';
|
||||
// export { ZoekenBlock } from './zoeken-block';
|
||||
// export { OverdrachtBlock } from './overdracht-block';
|
||||
```
|
||||
|
||||
**Probleem:**
|
||||
De diagnostiek workflow voegt nieuwe blocks toe (AfspraakBlock, RapportageBlock, DiagnoseBlock),
|
||||
maar de basis P1 blocks waar deze op voortbouwen bestaan nog niet.
|
||||
|
||||
**Impact:**
|
||||
- DagnotatieBlock is nodig als referentie voor RapportageBlock
|
||||
- ZoekenBlock is nodig voor patient selectie in alle flows
|
||||
- BlockContainer bestaat wel, maar is nog niet getest met echte content
|
||||
|
||||
**Aanbeveling:**
|
||||
Implementeer eerst E3.S2 (DagnotatieBlock) als proof-of-concept voordat diagnostiek workflow start.
|
||||
|
||||
#### ⚠️ Waarschuwing: Type Duplicatie
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// lib/swift/types.ts
|
||||
export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown';
|
||||
|
||||
// stores/swift-store.ts (DUPLICAAT)
|
||||
export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown';
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Bij uitbreiding van intents voor diagnostiek workflow moet dit op twee plekken bijgewerkt worden.
|
||||
Vergeten leidt tot TypeScript errors.
|
||||
|
||||
**Aanbeveling:**
|
||||
Verwijder duplicaat uit `swift-store.ts`, importeer uit `types.ts`.
|
||||
|
||||
### 5.3 Dependency Chain voor Diagnostiek
|
||||
|
||||
```
|
||||
Swift Foundation (moet eerst af)
|
||||
├── E2.S5 Input → Block wiring ← BLOKKADE
|
||||
│ └── handleSubmit() roept API aan
|
||||
│ └── openBlock() bij succes
|
||||
│
|
||||
├── E3.S0 CanvasArea block rendering ← BLOKKADE (ontbreekt in plan)
|
||||
│ └── Switch/case voor block types
|
||||
│ └── Prefill data doorgeven
|
||||
│
|
||||
├── E3.S2 DagnotatieBlock ← BLOKKADE
|
||||
│ └── Referentie voor RapportageBlock
|
||||
│ └── Test case voor block rendering
|
||||
│
|
||||
└── E3.S3 Patient Search API ← BLOKKADE
|
||||
└── Nodig voor patient selectie
|
||||
|
||||
Diagnostiek Workflow (kan dan starten)
|
||||
├── E-D1.S0 Report type "diagnostiek"
|
||||
├── E-D1.S1 Intent patterns uitbreiden
|
||||
├── E-D1.S2 AfspraakBlock
|
||||
├── E-D1.S3 RapportageBlock
|
||||
├── E-D2.S1 Diagnose intent patterns
|
||||
├── E-D2.S2 DiagnoseBlock
|
||||
└── E-D2.S3 DiagnoseFormBlock
|
||||
```
|
||||
|
||||
### 5.4 Geschatte Effort voor Foundation Completion
|
||||
|
||||
| Story | SP | Status |
|
||||
|-------|----|----|
|
||||
| E2.S5 Input → Block wiring | 2 | ⏳ Nieuw toegevoegd |
|
||||
| E3.S0 CanvasArea rendering | 2 | ⏳ Aanbevolen |
|
||||
| E3.S2 DagnotatieBlock | 5 | ⏳ Gepland |
|
||||
| E3.S3 Patient Search API | 3 | ⏳ Gepland |
|
||||
| **Subtotaal** | **12** | |
|
||||
|
||||
**Totale effort voor werkende demo:**
|
||||
- Swift Foundation completion: 12 SP
|
||||
- Diagnostiek Workflow: 22 SP
|
||||
- **Totaal: 34 SP**
|
||||
|
||||
---
|
||||
|
||||
## 6. Conclusie & Aanbevelingen
|
||||
|
||||
### 6.1 Algemene Beoordeling
|
||||
|
||||
**Compleetheid Diagnostiek Plan:** ⚠️ **7/10**
|
||||
- Goede structuur en breakdown
|
||||
- Enkele kritieke technische details ontbreken
|
||||
- Entity extraction niet volledig uitgewerkt
|
||||
|
||||
**Compleetheid Swift Foundation:** ⚠️ **5/10**
|
||||
- E0-E2.S4 solide geïmplementeerd
|
||||
- E2.S5 wiring ontbreekt (kritiek)
|
||||
- E3 blocks niet gebouwd (kritiek)
|
||||
- CanvasArea rendering niet in plan
|
||||
|
||||
**Haalbaarheid:** ⚠️ **6/10** (was 8/10)
|
||||
- Foundation moet eerst af voordat diagnostiek kan starten
|
||||
- Extra 12 SP nodig voor foundation completion
|
||||
- Totale effort: 34 SP i.p.v. 22 SP
|
||||
|
||||
### 6.2 Aanbevelingen voor Start
|
||||
|
||||
**Voor Start:**
|
||||
1. ✅ Voeg E-D1.S0 toe: Report type "diagnostiek" (1 SP)
|
||||
2. ✅ Uitbreid E-D1.S1: Entity extraction voor datum/tijd (extra 1 SP)
|
||||
3. ✅ Clarificeer E-D2.S3: Intake selectie strategie
|
||||
4. ✅ Update `SwiftIntent` type definitie
|
||||
5. ✅ Proof-of-concept AppointmentModal integratie
|
||||
|
||||
**Tijdens Implementatie:**
|
||||
1. Start met E-D1.S0 (report type) - basis voor alles
|
||||
2. Test entity extraction grondig voordat je verder gaat
|
||||
3. Itereer op AppointmentModal integratie (mogelijk meer tijd nodig)
|
||||
4. Houd rekening met edge cases in datum parsing
|
||||
|
||||
**Na Implementatie:**
|
||||
1. Uitgebreide end-to-end tests
|
||||
2. Performance testen (intent classification snelheid)
|
||||
3. UX feedback verzamelen
|
||||
4. Documentatie updaten
|
||||
|
||||
### 6.3 Aangepaste Story Breakdown
|
||||
|
||||
**Swift Foundation (moet eerst af):**
|
||||
- E2.S5: Input → Block wiring (2 SP) ⭐ NIEUW
|
||||
- E3.S0: CanvasArea block rendering (2 SP) ⭐ AANBEVOLEN
|
||||
- E3.S2: DagnotatieBlock (5 SP)
|
||||
- E3.S3: Patient Search API (3 SP)
|
||||
- **Subtotaal Foundation: 12 SP**
|
||||
|
||||
**Epic D1 — Afspraak & Rapportage (12 SP totaal):**
|
||||
- E-D1.S0: Report type "diagnostiek" toevoegen (1 SP) ⭐ NIEUW
|
||||
- E-D1.S1: Intent patterns + entity extraction (3 SP) ⬆️ +1 SP
|
||||
- E-D1.S2: AfspraakBlock (5 SP)
|
||||
- E-D1.S3: RapportageBlock uitbreiden (4 SP)
|
||||
|
||||
**Epic D2 — Diagnose Beheer (10 SP totaal):**
|
||||
- E-D2.S1: Intent patterns uitbreiden (2 SP)
|
||||
- E-D2.S2: DiagnoseBlock (3 SP)
|
||||
- E-D2.S3: DiagnoseFormBlock (5 SP)
|
||||
|
||||
**Totalen:**
|
||||
- Swift Foundation completion: 12 SP
|
||||
- Diagnostiek Workflow: 22 SP
|
||||
- **Totaal: 34 SP**
|
||||
|
||||
### 6.4 Finale Oordeel
|
||||
|
||||
⚠️ **HOLD - Foundation eerst afronden**
|
||||
|
||||
Het diagnostiek bouwplan is goed gestructureerd, maar kan nog niet starten omdat de Swift foundation incompleet is.
|
||||
|
||||
**Kritieke blokkades:**
|
||||
1. E2.S5 (handleSubmit wiring) - toegevoegd aan bouwplan v1.5
|
||||
2. E3.S0 (CanvasArea rendering) - moet nog toegevoegd worden
|
||||
3. E3.S2 (DagnotatieBlock) - moet gebouwd worden als referentie
|
||||
4. E3.S3 (Patient Search API) - nodig voor alle patient selectie
|
||||
|
||||
**Aanbevolen volgorde:**
|
||||
```
|
||||
Week 1: Foundation completion (12 SP)
|
||||
├── E2.S5 Input → Block wiring
|
||||
├── E3.S0 CanvasArea rendering
|
||||
├── E3.S2 DagnotatieBlock
|
||||
└── E3.S3 Patient Search API
|
||||
|
||||
Week 2-3: Diagnostiek Workflow (22 SP)
|
||||
├── E-D1.S0 Report type
|
||||
├── E-D1.S1 Intent patterns
|
||||
├── E-D1.S2 AfspraakBlock
|
||||
├── E-D1.S3 RapportageBlock
|
||||
├── E-D2.S1 Diagnose patterns
|
||||
├── E-D2.S2 DiagnoseBlock
|
||||
└── E-D2.S3 DiagnoseFormBlock
|
||||
```
|
||||
|
||||
**GO voor implementatie** zodra foundation stories zijn afgerond.
|
||||
|
||||
---
|
||||
|
||||
**Versiehistorie:**
|
||||
|
||||
| Versie | Datum | Auteur | Wijziging |
|
||||
|--------|-------|--------|-----------|
|
||||
| v1.0 | 23-12-2024 | AI Review | Initiële beoordeling |
|
||||
| v1.1 | 24-12-2024 | Claude | Swift Foundation status toegevoegd (sectie 5), blokkerende issues geïdentificeerd, finale oordeel aangepast naar HOLD |
|
||||
|
||||
@@ -1,458 +0,0 @@
|
||||
# 🚀 Mission Control — Bouwplan Swift: Diagnostiek Workflow
|
||||
|
||||
**Projectnaam:** Swift — Diagnostiek Workflow
|
||||
**Versie:** v1.0
|
||||
**Datum:** 23-12-2024
|
||||
**Auteur:** Colin Lit / Development Team
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en context
|
||||
|
||||
🎯 **Doel:**
|
||||
Implementeren van de complete diagnostiek workflow in Swift: van het plannen van een diagnostiek-afspraak, via het schrijven van een rapportage, tot het bekijken en stellen van diagnoses. Deze workflow stelt behandelaars in staat om een volledig diagnostiek-traject door te lopen via natuurlijke taal zonder menu-navigatie.
|
||||
|
||||
**Kernbelofte:**
|
||||
> Een behandelaar kan een volledig diagnostiek-traject doorlopen: "afspraak diagnostiek jan morgen 10:00" → "rapportage diagnostiek gesprek met jan" → "diagnose toevoegen jan F41.1" — alles in één vloeiende flow.
|
||||
|
||||
📘 **Context:**
|
||||
Dit bouwplan beschrijft de implementatie van de diagnostiek workflow als uitbreiding op Swift. Het bouwt voort op de Swift foundation (Command Center, Intent Classification) en voegt specifieke blocks toe voor behandelaars.
|
||||
|
||||
**Relatie met documentatie:**
|
||||
- **FO Diagnostiek:** `swift-fo-diagnostiek-workflow.md` — Complete use case beschrijving
|
||||
- **FO Algemeen:** `swift-fo-ai.md` — Algemene Swift functionaliteit
|
||||
- **Bouwplan Swift:** `bouwplan-swift-v1.md` — Hoofd Swift bouwplan
|
||||
- **UX/UI:** `swift-ux-v2.1.md` — Visuele specificaties
|
||||
|
||||
---
|
||||
|
||||
## 2. Uitgangspunten
|
||||
|
||||
### 2.1 Technische Stack
|
||||
|
||||
**Bestaand (hergebruiken):**
|
||||
| Component | Technologie | Status | Hergebruik |
|
||||
|-----------|-------------|--------|------------|
|
||||
| Swift Foundation | Command Center, Intent Classification | ✅ | Basis voor alle blocks |
|
||||
| Appointment Modal | `app/epd/agenda/components/appointment-modal.tsx` | ✅ | 80% hergebruik |
|
||||
| Rapportage Composer | `app/epd/patients/[id]/rapportage/components/report-composer.tsx` | ✅ | 80% hergebruik |
|
||||
| Diagnose Form | `app/epd/patients/[id]/diagnose/components/diagnosis-detail-form.tsx` | ✅ | 80% hergebruik |
|
||||
| Diagnose Actions | `app/epd/patients/[id]/diagnose/actions.ts` | ✅ | 90% hergebruik |
|
||||
| FHIR Patient API | `/api/fhir/Patient` | ✅ | Patient search |
|
||||
| Reports API | `/api/reports` | ✅ | Rapportage opslaan |
|
||||
|
||||
**Nieuw te bouwen:**
|
||||
| Component | Technologie | Reden |
|
||||
|-----------|-------------|-------|
|
||||
| AfspraakBlock | React component | Swift wrapper rond appointment modal |
|
||||
| RapportageBlock | React component | Swift wrapper rond report composer |
|
||||
| DiagnoseBlock | React component | Overzicht component |
|
||||
| DiagnoseFormBlock | React component | Swift wrapper rond diagnose form |
|
||||
| Appointments API | Next.js API route | Encounter CRUD voor Swift |
|
||||
| Diagnoses API | Next.js API route | Diagnose CRUD voor Swift |
|
||||
|
||||
### 2.2 Projectkaders
|
||||
|
||||
| Kader | Waarde |
|
||||
|-------|--------|
|
||||
| **Bouwtijd** | 2 weken (2 sprints) |
|
||||
| **Team** | 1 developer |
|
||||
| **Scope** | Diagnostiek workflow: Afspraak → Rapportage → Diagnose |
|
||||
| **Data** | Bestaande Supabase database (encounters, conditions, reports) |
|
||||
| **Doel** | Werkende behandelaar workflow voor demo |
|
||||
|
||||
### 2.3 Programmeer Uitgangspunten
|
||||
|
||||
**Code Quality Principles:**
|
||||
|
||||
- **DRY (Don't Repeat Yourself)**
|
||||
- Hergebruik bestaande componenten (appointment-modal, report-composer, diagnose-form)
|
||||
- Centrale API routes voor encounters en diagnoses
|
||||
- Shared hooks voor common patterns
|
||||
|
||||
- **KISS (Keep It Simple, Stupid)**
|
||||
- Swift blocks zijn wrappers rond bestaande componenten
|
||||
- Minimale aanpassingen aan bestaande code
|
||||
- Hergebruik bestaande validatie en error handling
|
||||
|
||||
- **SOC (Separation of Concerns)**
|
||||
- Swift blocks gescheiden van klassieke EPD componenten
|
||||
- API routes als wrapper rond bestaande actions
|
||||
- Business logic blijft in bestaande actions
|
||||
|
||||
- **YAGNI (You Aren't Gonna Need It)**
|
||||
- Alleen diagnostiek workflow in scope
|
||||
- Geen andere behandelaar workflows (intake, behandelplan, etc.)
|
||||
- Geen advanced features (herhalende afspraken, etc.)
|
||||
|
||||
**Development Practices:**
|
||||
|
||||
- **Hergebruik Strategie**
|
||||
```typescript
|
||||
// ✅ Hergebruik bestaande componenten
|
||||
// components/swift/blocks/afspraak-block.tsx
|
||||
import { AppointmentModal } from '@/app/epd/agenda/components/appointment-modal';
|
||||
|
||||
// Aanpassen voor Swift context:
|
||||
// - Prefill vanuit intent
|
||||
// - Encounter_id teruggeven na opslaan
|
||||
// - Swift styling (dark theme)
|
||||
|
||||
// ✅ Hergebruik bestaande actions
|
||||
// app/api/appointments/route.ts
|
||||
import { createEncounter, updateEncounter } from '@/app/epd/agenda/actions';
|
||||
|
||||
// Wrapper rond bestaande logica met Swift-specifieke validatie
|
||||
```
|
||||
|
||||
- **Error Handling**
|
||||
- Hergebruik bestaande error handling uit actions
|
||||
- Nederlandse foutmeldingen consistent met Swift
|
||||
- Toast notifications voor user feedback
|
||||
|
||||
- **Security**
|
||||
- Hergebruik Supabase RLS policies
|
||||
- Authenticatie via bestaande Supabase Auth
|
||||
- Input validation met Zod (bestaande schemas)
|
||||
|
||||
---
|
||||
|
||||
## 3. Epics & Stories Overzicht
|
||||
|
||||
| Epic ID | Titel | Doel | Status | Stories | Effort | Opmerkingen |
|
||||
|---------|-------|------|--------|---------|--------|-------------|
|
||||
| E-D1 | Afspraak & Rapportage | Afspraak plannen + rapportage koppelen | ⏳ To Do | 3 | 11 SP | Hergebruik appointment modal |
|
||||
| E-D2 | Diagnose Beheer | Diagnose bekijken, toevoegen, bijstellen | ⏳ To Do | 3 | 10 SP | Hergebruik diagnose form |
|
||||
|
||||
**Totaal: 6 stories, 21 story points**
|
||||
|
||||
**Belangrijk:**
|
||||
- Bouw per epic en per story
|
||||
- Hergebruik bestaande componenten waar mogelijk
|
||||
- Minimale aanpassingen aan bestaande code
|
||||
- Database migraties: eerst aan Colin melden
|
||||
|
||||
---
|
||||
|
||||
## 4. Epics & Stories (Uitwerking)
|
||||
|
||||
### Epic D1 — Afspraak & Rapportage
|
||||
|
||||
**Epic Doel:** Werkende flow van diagnostiek-afspraak plannen tot rapportage schrijven met encounter koppeling.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E-D1.S1 | Intent patterns uitbreiden | Afspraak en rapportage intents herkend | ⏳ To Do | Swift foundation | 2 |
|
||||
| E-D1.S2 | AfspraakBlock | Patiënt, datum/tijd, type, encounter_id terug | ⏳ To Do | E-D1.S1 | 5 |
|
||||
| E-D1.S3 | RapportageBlock uitbreiden | Encounter_id koppeling, type "diagnostiek" | ⏳ To Do | E-D1.S2 | 4 |
|
||||
|
||||
**Technical Notes:**
|
||||
|
||||
```typescript
|
||||
// E-D1.S1: Intent patterns toevoegen aan lib/swift/intent-classifier.ts
|
||||
afspraak_maken: [
|
||||
/^afspraak\s+(diagnostiek|behandeling)\s+(\w+)/i,
|
||||
/^plan\s+(\w+)\s+(morgen|vandaag|volgende week)/i,
|
||||
/^afspraak\s+(\w+)\s+(morgen|vandaag)\s+(\d{1,2}:\d{2})/i,
|
||||
],
|
||||
|
||||
rapportage: [
|
||||
/^rapportage\s+(diagnostiek|behandeling)\s+(\w+)/i,
|
||||
/^gesprek gehad met\s+(\w+)/i,
|
||||
/^verslag\s+(\w+)/i,
|
||||
],
|
||||
|
||||
// E-D1.S2: AfspraakBlock
|
||||
// components/swift/blocks/afspraak-block.tsx
|
||||
// Hergebruik: app/epd/agenda/components/appointment-modal.tsx
|
||||
// Aanpassingen:
|
||||
// - Prefill vanuit intent (patient, date, time, type)
|
||||
// - Dark theme styling (Swift context)
|
||||
// - Encounter_id teruggeven via callback
|
||||
// - Vereenvoudigde UI (geen linked reports sectie)
|
||||
|
||||
// E-D1.S3: RapportageBlock uitbreiden
|
||||
// components/swift/blocks/rapportage-block.tsx
|
||||
// Hergebruik: app/epd/patients/[id]/rapportage/components/report-composer.tsx
|
||||
// Aanpassingen:
|
||||
// - Encounter_id in prefill
|
||||
// - Type "diagnostiek" toevoegen aan report types
|
||||
// - Encounter link tonen indien gekoppeld
|
||||
```
|
||||
|
||||
**API Routes:**
|
||||
|
||||
```typescript
|
||||
// E-D1.S2: Appointments API
|
||||
// app/api/appointments/route.ts
|
||||
export async function POST(request: NextRequest) {
|
||||
// Wrapper rond app/epd/agenda/actions.ts createEncounter
|
||||
// Retourneert encounter_id voor volgende stap
|
||||
}
|
||||
|
||||
// E-D1.S3: Reports API uitbreiden
|
||||
// app/api/reports/route.ts (bestaand)
|
||||
// Uitbreiden met encounter_id parameter
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Epic D2 — Diagnose Beheer
|
||||
|
||||
**Epic Doel:** Werkende diagnose overzicht en diagnose aanmaken/bijstellen met ICD-10 zoeker.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E-D2.S1 | Intent patterns uitbreiden | Diagnose intents herkend | ⏳ To Do | Swift foundation | 2 |
|
||||
| E-D2.S2 | DiagnoseBlock | Overzicht diagnoses, filter actief/inactief | ⏳ To Do | E-D2.S1 | 3 |
|
||||
| E-D2.S3 | DiagnoseFormBlock | ICD-10 zoeker, type, status, ernst | ⏳ To Do | E-D2.S2 | 5 |
|
||||
|
||||
**Technical Notes:**
|
||||
|
||||
```typescript
|
||||
// E-D2.S1: Intent patterns toevoegen
|
||||
diagnose_bekijken: [
|
||||
/^diagnose\s+(\w+)/i,
|
||||
/^diagnoses van\s+(\w+)/i,
|
||||
/^wat zijn de diagnoses/i,
|
||||
],
|
||||
|
||||
diagnose_toevoegen: [
|
||||
/^diagnose toevoegen\s+(\w+)\s+([A-Z]\d+\.\d+)/i,
|
||||
/^(\w+)\s+heeft\s+([A-Z]\d+\.\d+)/i,
|
||||
/^diagnose\s+(\w+)\s+([A-Z]\d+\.\d+)/i,
|
||||
],
|
||||
|
||||
diagnose_wijzigen: [
|
||||
/^diagnose wijzigen\s+(\w+)/i,
|
||||
/^diagnose bijstellen/i,
|
||||
],
|
||||
|
||||
// E-D2.S2: DiagnoseBlock
|
||||
// components/swift/blocks/diagnose-block.tsx
|
||||
// Hergebruik logica van: app/epd/patients/[id]/diagnose/page.tsx
|
||||
// Aanpassingen:
|
||||
// - Swift styling (dark theme)
|
||||
// - Filter tabs: Actief / Inactief / Alle
|
||||
// - Klik op diagnose → DiagnoseFormBlock (edit mode)
|
||||
// - "Nieuwe diagnose" knop → DiagnoseFormBlock (create mode)
|
||||
|
||||
// E-D2.S3: DiagnoseFormBlock
|
||||
// components/swift/blocks/diagnose-form-block.tsx
|
||||
// Hergebruik: app/epd/patients/[id]/diagnose/components/diagnosis-detail-form.tsx
|
||||
// Aanpassingen:
|
||||
// - ICD-10 zoeker met fuzzy search (verbeteren)
|
||||
// - Swift styling
|
||||
// - Prefill vanuit intent (ICD-10 code)
|
||||
// - Encounter_id koppeling (optioneel)
|
||||
```
|
||||
|
||||
**API Routes:**
|
||||
|
||||
```typescript
|
||||
// E-D2.S2 + E-D2.S3: Diagnoses API
|
||||
// app/api/diagnoses/route.ts
|
||||
export async function GET(request: NextRequest) {
|
||||
// GET /api/diagnoses/:patientId
|
||||
// Wrapper rond app/epd/patients/[id]/diagnose/actions.ts getPatientDiagnoses
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
// POST /api/diagnoses
|
||||
// Wrapper rond app/epd/patients/[id]/diagnose/actions.ts createPatientDiagnosis
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
// PATCH /api/diagnoses/:id
|
||||
// Wrapper rond app/epd/patients/[id]/diagnose/actions.ts updatePatientDiagnosis
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Kwaliteit & Testplan
|
||||
|
||||
### 5.1 Test Types
|
||||
|
||||
| Test Type | Scope | Tools | Wanneer | Verantwoordelijke |
|
||||
|-----------|-------|-------|---------|-------------------|
|
||||
| Unit Tests | Intent patterns, entity extraction | Vitest | E-D1.S1, E-D2.S1 | Developer |
|
||||
| Integration Tests | API endpoints | Vitest + MSW | E-D1.S2, E-D1.S3, E-D2.S3 | Developer |
|
||||
| Component Tests | Blocks | React Testing Library | E-D1.S2, E-D1.S3, E-D2.S2, E-D2.S3 | Developer |
|
||||
| Manual Tests | Complete workflow | Checklist | E-D2.S3 | QA / Developer |
|
||||
|
||||
### 5.2 Test Coverage Targets
|
||||
|
||||
| Area | Target | Reden |
|
||||
|------|--------|-------|
|
||||
| Intent patterns | 85%+ | Correcte intent herkenning |
|
||||
| Entity extraction | 80%+ | Pre-fill correctheid |
|
||||
| API routes | 80%+ | Data integrity |
|
||||
| Block components | 60%+ | Belangrijkste flows |
|
||||
|
||||
### 5.3 Manual Test Checklist (Diagnostiek Workflow)
|
||||
|
||||
**Happy Flow:**
|
||||
- [ ] "afspraak diagnostiek met jan morgen 10:00" → AfspraakBlock opent
|
||||
- [ ] Afspraak opslaan → encounter_id teruggegeven
|
||||
- [ ] "rapportage diagnostiek gesprek met jan" → RapportageBlock met encounter koppeling
|
||||
- [ ] Rapportage opslaan → gekoppeld aan encounter
|
||||
- [ ] "diagnose jan" → DiagnoseBlock met overzicht
|
||||
- [ ] "diagnose toevoegen jan F41.1" → DiagnoseFormBlock met ICD-10 pre-filled
|
||||
- [ ] Diagnose opslaan → toegevoegd aan overzicht
|
||||
- [ ] Klik op diagnose → DiagnoseFormBlock (edit mode)
|
||||
- [ ] Status wijzigen → diagnose bijgewerkt
|
||||
|
||||
**Error Scenarios:**
|
||||
- [ ] Geen patiënt gevonden → ZoekenBlock
|
||||
- [ ] Ongeldige datum → validatie fout
|
||||
- [ ] ICD-10 code niet gevonden → validatie fout
|
||||
- [ ] Encounter niet gevonden → rapportage zonder koppeling
|
||||
- [ ] Network error → toast met retry
|
||||
|
||||
---
|
||||
|
||||
## 6. Demo & Presentatieplan
|
||||
|
||||
### 6.1 Demo Scenario
|
||||
|
||||
**Duur:** 5 minuten
|
||||
**Doelgroep:** Behandelaars, psychologen, psychiaters
|
||||
**Locatie:** Live op Vercel
|
||||
|
||||
**Flow:**
|
||||
|
||||
```
|
||||
1. INTRO (30 sec)
|
||||
"Behandelaars besteden veel tijd aan administratie.
|
||||
Swift maakt diagnostiek-trajecten sneller."
|
||||
|
||||
2. AFSPRAAK PLANNEN (1 min)
|
||||
- Typ: "afspraak diagnostiek met jan morgen 10:00"
|
||||
- AfspraakBlock verschijnt voorgevuld
|
||||
- Opslaan → afspraak aangemaakt
|
||||
|
||||
3. RAPPORTAGE SCHRIJVEN (1.5 min)
|
||||
- Typ: "rapportage diagnostiek gesprek met jan"
|
||||
- RapportageBlock verschijnt met encounter koppeling
|
||||
- Schrijf/dicteer verslag
|
||||
- Opslaan → gekoppeld aan afspraak
|
||||
|
||||
4. DIAGNOSE STELLEN (2 min)
|
||||
- Typ: "diagnose jan"
|
||||
- DiagnoseBlock toont overzicht
|
||||
- Typ: "diagnose toevoegen jan F41.1"
|
||||
- DiagnoseFormBlock met ICD-10 pre-filled
|
||||
- Vul type, status, ernst in
|
||||
- Opslaan → diagnose toegevoegd
|
||||
|
||||
5. AFSLUITING (20 sec)
|
||||
- Complete flow in 5 minuten
|
||||
- Vragen
|
||||
```
|
||||
|
||||
### 6.2 Backup Plan
|
||||
|
||||
| Probleem | Oplossing |
|
||||
|----------|-----------|
|
||||
| Internet issues | Localhost met demo data |
|
||||
| AI API down | Pre-cached responses |
|
||||
| Encounter niet gevonden | Demo met pre-made encounter |
|
||||
| Complete failure | Video recording |
|
||||
|
||||
---
|
||||
|
||||
## 7. Risico's & Mitigatie
|
||||
|
||||
| Risico | Kans | Impact | Mitigatie | Owner |
|
||||
|--------|------|--------|-----------|-------|
|
||||
| Hergebruik componenten complex | Middel | Hoog | Stapsgewijs aanpassen, tests per stap | Dev |
|
||||
| ICD-10 zoeker niet accuraat | Middel | Middel | Fuzzy search verbeteren, externe API optie | Dev |
|
||||
| Encounter koppeling faalt | Laag | Middel | Duidelijke UX, optionele koppeling | Dev |
|
||||
| Datum parsing onnauwkeurig | Middel | Laag | Fallback naar date picker | Dev |
|
||||
| Bestaande code breaking changes | Laag | Hoog | Wrapper pattern, geen directe wijzigingen | Dev |
|
||||
|
||||
---
|
||||
|
||||
## 8. Sprint Planning
|
||||
|
||||
### Sprint 1 (Week 1): Afspraak & Rapportage
|
||||
- E-D1.S1: Intent patterns uitbreiden (2 SP)
|
||||
- E-D1.S2: AfspraakBlock (5 SP)
|
||||
- E-D1.S3: RapportageBlock uitbreiden (4 SP)
|
||||
- **Deliverable:** Afspraak en rapportage flow werkend
|
||||
|
||||
### Sprint 2 (Week 2): Diagnose Beheer
|
||||
- E-D2.S1: Intent patterns uitbreiden (2 SP)
|
||||
- E-D2.S2: DiagnoseBlock (3 SP)
|
||||
- E-D2.S3: DiagnoseFormBlock (5 SP)
|
||||
- Bug fixes + demo prep
|
||||
- **Deliverable:** Complete diagnostiek workflow werkend
|
||||
|
||||
---
|
||||
|
||||
## 9. Definition of Done
|
||||
|
||||
Een story is **Done** wanneer:
|
||||
- [ ] Code geschreven en werkend
|
||||
- [ ] TypeScript types correct
|
||||
- [ ] Component responsive (mobile + desktop)
|
||||
- [ ] Error states afgehandeld
|
||||
- [ ] Hergebruik bestaande code geïmplementeerd
|
||||
- [ ] Geen breaking changes in bestaande code
|
||||
- [ ] Getest in Chrome + Safari
|
||||
- [ ] Demo scenario werkt
|
||||
|
||||
Een epic is **Done** wanneer:
|
||||
- [ ] Alle stories Done
|
||||
- [ ] Integration test passed
|
||||
- [ ] Complete workflow werkt end-to-end
|
||||
|
||||
---
|
||||
|
||||
## 10. Referenties
|
||||
|
||||
### Project Documenten
|
||||
- FO Diagnostiek: `docs/swift/swift-fo-diagnostiek-workflow.md`
|
||||
- FO Algemeen: `docs/swift/swift-fo-ai.md`
|
||||
- Bouwplan Swift: `docs/swift/bouwplan-swift-v1.md`
|
||||
- UX: `docs/swift/swift-ux-v2.1.md`
|
||||
|
||||
### Bestaande Code Referenties (Hergebruik)
|
||||
|
||||
| Component | Locatie | Hergebruik % | Aanpassingen |
|
||||
|-----------|---------|--------------|-------------|
|
||||
| Appointment Modal | `app/epd/agenda/components/appointment-modal.tsx` | 80% | Prefill, dark theme, encounter_id callback |
|
||||
| Report Composer | `app/epd/patients/[id]/rapportage/components/report-composer.tsx` | 80% | Encounter koppeling, type "diagnostiek" |
|
||||
| Diagnose Page | `app/epd/patients/[id]/diagnose/page.tsx` | 70% | Overzicht logica, Swift styling |
|
||||
| Diagnose Form | `app/epd/patients/[id]/diagnose/components/diagnosis-detail-form.tsx` | 80% | ICD-10 zoeker verbeteren, Swift styling |
|
||||
| Diagnose Actions | `app/epd/patients/[id]/diagnose/actions.ts` | 90% | Direct hergebruik via API wrapper |
|
||||
| Encounter Actions | `app/epd/agenda/actions.ts` | 90% | Direct hergebruik via API wrapper |
|
||||
|
||||
### External
|
||||
- ICD-10 codes: https://www.who.int/standards/classifications/classification-of-diseases
|
||||
- FHIR Encounter: https://www.hl7.org/fhir/encounter.html
|
||||
- FHIR Condition: https://www.hl7.org/fhir/condition.html
|
||||
|
||||
---
|
||||
|
||||
## 11. Glossary & Abbreviations
|
||||
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| Encounter | Afspraak/contactmoment (FHIR term) |
|
||||
| Condition | Diagnose (FHIR term) |
|
||||
| ICD-10 | International Classification of Diseases versie 10 |
|
||||
| Hoofddiagnose | Primaire diagnose |
|
||||
| Nevendiagnose | Secundaire diagnose |
|
||||
| Clinical Status | Status van diagnose (actief, inactief, resolved, etc.) |
|
||||
| Severity | Ernst van diagnose (mild, matig, ernstig) |
|
||||
|
||||
---
|
||||
|
||||
**Versiehistorie:**
|
||||
|
||||
| Versie | Datum | Auteur | Wijziging |
|
||||
|--------|-------|--------|-----------|
|
||||
| v1.0 | 23-12-2024 | Claude | Initiële versie - Apart bouwplan voor diagnostiek workflow |
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
# Mission Control - Bouwplan
|
||||
|
||||
Projectnaam: Swift Standalone Module (MVP)
|
||||
Versie: v1.0
|
||||
Datum: 27-12-2025
|
||||
Auteur: Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en context
|
||||
Doel: Swift loskoppelen van de klassieke EPD layout zodat de Swift UI full-screen draait zonder sidebar.
|
||||
Toelichting: Swift is een aparte modus (conversational scribe) die focus en ruimte nodig heeft. In de huidige app router erft `/epd/swift` de EPD layout, waardoor de sidebar blijft staan. Dit plan brengt Swift onder een eigen route group, behoudt dezelfde URL, en voorkomt regressies in het klassieke EPD.
|
||||
|
||||
Belangrijke overwegingen:
|
||||
- Lead dev: zuivere layout scheiding via route group, minimale impact, geen conditionele layout hacks.
|
||||
- UX: full-screen focus, duidelijke entry/exit, geen dubbele navigatie, consistente context bar.
|
||||
- PO: MVP scope, beperkte wijzigingen, geen nieuwe features of dependencies.
|
||||
- Besluit: route group is akkoord (27-12-2025).
|
||||
|
||||
UX flow (MVP):
|
||||
- Toegang via login: interface-voorkeur bepaalt redirect (Swift -> /epd/swift, klassiek -> /epd/clients).
|
||||
- Deep link naar /epd/swift zonder sessie: login volgt en redirect gaat alsnog op basis van interface-voorkeur.
|
||||
- Terug naar EPD: context bar link naar /epd.
|
||||
- Schakelen vanuit klassiek EPD: Swift staat als item in de EPD sidebar.
|
||||
|
||||
---
|
||||
|
||||
## 2. Uitgangspunten
|
||||
|
||||
### 2.1 Technische Stack
|
||||
- Frontend: Next.js App Router + React + TypeScript
|
||||
- Styling: Tailwind CSS
|
||||
- State: Zustand
|
||||
- Auth: Supabase Auth
|
||||
- API: Next.js route handlers (SSE voor Swift chat)
|
||||
- Hosting: Vercel
|
||||
|
||||
### 2.2 Projectkaders
|
||||
- Scope (MVP): alleen layout/routing scheiding + minimale navigatie checks.
|
||||
- Out of scope: nieuwe Swift features, redesign van chat/artifacts, nieuwe AI flows.
|
||||
- Geen nieuwe dependencies zonder akkoord.
|
||||
- Geen database migraties nodig.
|
||||
- URL `/epd/swift` blijft gelijk voor deep links.
|
||||
- Klassiek EPD moet onveranderd blijven.
|
||||
|
||||
### 2.3 Programmeer Uitgangspunten
|
||||
Code quality principles:
|
||||
- DRY: shared layout helpers en routes hergebruiken waar logisch.
|
||||
- KISS: route group boven conditionele rendering.
|
||||
- SOC: Swift layout los van EPD layout, geen cross-coupling.
|
||||
- YAGNI: geen extra features buiten layout scheiding.
|
||||
|
||||
Development practices:
|
||||
- Geen nieuwe dependencies of migrations zonder akkoord.
|
||||
- Keep server components default; alleen client waar nodig.
|
||||
- Duidelijke error handling bij auth redirect.
|
||||
- UI blijft Tailwind-only, geen styling drift.
|
||||
|
||||
---
|
||||
|
||||
## 3. Epics & Stories Overzicht
|
||||
| Epic ID | Titel | Doel | Status | Stories | Opmerkingen |
|
||||
|---------|-------|------|--------|---------|-------------|
|
||||
| E0 | Product/UX alignment | Scope en UX flows vastleggen | Done | 2 | Route group akkoord |
|
||||
| E1 | Routing & layout scheiding | Swift los van EPD layout | Done | 4 | `/epd/swift` blijft |
|
||||
| E2 | Navigatie & toegang | Entry/exit flows borgen | Done | 3 | MVP only |
|
||||
| E3 | Swift shell polish | Full-screen gedrag + responsive | Done | 3 | Geen redesign |
|
||||
| E4 | QA & docs | Validatie en documentatie | To Do | 3 | Manual checks |
|
||||
|
||||
---
|
||||
|
||||
## 4. Epics & Stories (Uitwerking)
|
||||
|
||||
### Epic 0 - Product/UX alignment
|
||||
Epic doel: beslissen over aanpak en de MVP scope vastleggen.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E0.S1 | Besluit en scope vastleggen | Keuze voor route group vastgelegd + out of scope lijst | Done | - | 2 |
|
||||
| E0.S2 | UX entry/exit flow vastleggen | Flow voor toegang en terug naar EPD gedocumenteerd | Done | E0.S1 | 2 |
|
||||
|
||||
Technical notes:
|
||||
- Geen code changes voor E0; alleen documentatie en alignment.
|
||||
|
||||
### Epic 1 - Routing & layout scheiding
|
||||
Epic doel: Swift UI draait full-screen buiten EPD layout.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E1.S1 | Route group toevoegen | `app/(swift)` bestaat en `/epd/swift` blijft werken | Done | E0.S1 | 3 |
|
||||
| E1.S2 | Swift layout isoleren | Swift layout rendert full-screen zonder sidebar | Done | E1.S1 | 3 |
|
||||
| E1.S3 | Auth guard borgen | Ongeauth users redirect naar `/login` zoals nu | Done | E1.S2 | 2 |
|
||||
| E1.S4 | EPD layout regressie check | Klassiek EPD layout ongewijzigd | Done | E1.S2 | 2 |
|
||||
|
||||
Technical notes:
|
||||
- Geen nieuwe dependencies.
|
||||
- Route group moet alleen Swift raken, niet EPD.
|
||||
- EPDHeader wordt niet gebruikt in Swift; ContextBar is de top bar.
|
||||
|
||||
### Epic 2 - Navigatie & toegang
|
||||
Epic doel: gebruikers kunnen Swift starten en verlaten zonder verwarring.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E2.S1 | Entry points controleren | Links naar `/epd/swift` werken (EPD menu/login) | Done | E1.S2 | 2 |
|
||||
| E2.S2 | Exit back-link verifiëren | Context bar link naar `/epd` blijft consistent | Done | E1.S2 | 1 |
|
||||
| E2.S3 | Deep link gedrag | Directe URL naar `/epd/swift` werkt met auth | Done | E1.S3 | 2 |
|
||||
|
||||
Technical notes:
|
||||
- Geen nieuwe navigatie items tenzij nodig voor MVP.
|
||||
|
||||
### Epic 3 - Swift shell polish
|
||||
Epic doel: full-screen ervaring is clean en consistent.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E3.S1 | Full-screen layout check | Geen sidebar spacing of EPD padding zichtbaar | Done | E1.S2 | 2 |
|
||||
| E3.S2 | Responsive gedrag check | Chat/artifacts stacken op mobiel zoals nu | Done | E1.S2 | 2 |
|
||||
| E3.S3 | Visuele consistentie | Context bar en offline banner correct gepositioneerd | Done | E1.S2 | 1 |
|
||||
|
||||
Technical notes:
|
||||
- Geen redesign of nieuwe UI componenten.
|
||||
|
||||
### Epic 4 - QA & docs
|
||||
Epic doel: MVP kwaliteit borgen en documentatie bijwerken.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E4.S1 | Manual QA checklist | Alle kritieke flows handmatig getest | To Do | E3.S2 | 2 |
|
||||
| E4.S2 | Bouwplan en docs bijwerken | Bouwplan + korte notitie in docs/swift | To Do | E4.S1 | 1 |
|
||||
| E4.S3 | Release note (kort) | Interne note met UX impact | To Do | E4.S1 | 1 |
|
||||
|
||||
Technical notes:
|
||||
- Gebruik `pnpm lint` voor basis check (geen extra tests).
|
||||
|
||||
---
|
||||
|
||||
## 5. Kwaliteit & Testplan
|
||||
|
||||
Test types:
|
||||
| Test Type | Scope | Tools | Verantwoordelijke |
|
||||
|-----------|-------|-------|-------------------|
|
||||
| Lint | App code | pnpm lint | Developer |
|
||||
| Smoke tests | Routing + auth + UX flows | Manual checklist | Developer/PO |
|
||||
| Responsive check | Layout op mobile/desktop | Browser devtools | UX |
|
||||
|
||||
Manual test checklist:
|
||||
- [ ] `/epd/swift` laadt zonder sidebar
|
||||
- [ ] EPDHeader en DocsChatWidget zijn niet zichtbaar in Swift
|
||||
- [ ] Ongeauth redirect naar `/login`
|
||||
- [ ] Entry vanuit EPD werkt
|
||||
- [ ] Back-link naar `/epd` werkt
|
||||
- [ ] Chat input werkt, artifacts openen
|
||||
- [ ] Mobile: chat en artifacts stacken correct
|
||||
- [ ] Deep link naar `/epd/swift` volgt interface-voorkeur na login
|
||||
|
||||
---
|
||||
|
||||
## 6. Demo & Presentatieplan
|
||||
Doel: korte interne demo van de standalone Swift ervaring.
|
||||
|
||||
Flow:
|
||||
1. Open `/epd/swift` (full-screen, no sidebar)
|
||||
2. Start een chat en open artifact
|
||||
3. Ga terug naar `/epd` via context bar
|
||||
|
||||
Backup:
|
||||
- Gebruik screenshots als live demo faalt.
|
||||
|
||||
---
|
||||
|
||||
## 7. Risico's & Mitigatie
|
||||
| Risico | Kans | Impact | Mitigatie | Owner |
|
||||
|--------|------|--------|-----------|-------|
|
||||
| Route group breekt `/epd` routing | Laag | Hoog | Isoleren in `app/(swift)` en regression check | Lead dev |
|
||||
| Auth redirect mismatch | Middel | Middel | E1.S3 + manual QA | Lead dev |
|
||||
| UX verwarring over modus | Middel | Middel | Duidelijke back-link + docs | UX |
|
||||
| Scope creep (extra features) | Hoog | Middel | MVP guardrails in E0 | PO |
|
||||
| Mobile layout regressie | Laag | Middel | E3.S2 + devtools check | UX |
|
||||
|
||||
---
|
||||
|
||||
## 8. Evaluatie & Lessons Learned
|
||||
Na oplevering documenteren:
|
||||
- Was de mode-scheiding duidelijk voor users?
|
||||
- Zijn entry/exit flows logisch?
|
||||
- Zijn er regressies in klassiek EPD?
|
||||
|
||||
---
|
||||
|
||||
## 9. Referenties
|
||||
- Docs: `docs/swift/fo-swift-medical-scribe-v3.md`
|
||||
- Docs: `docs/swift/v3-redesign-met-huidige-styling.md`
|
||||
- Docs: `docs/swift/e0-design-tokens-and-components.md`
|
||||
- Code: `app/epd/layout.tsx`
|
||||
- Code: `app/(swift)/epd/swift/layout.tsx`
|
||||
- Code: `components/swift/command-center/command-center.tsx`
|
||||
|
||||
---
|
||||
|
||||
## 10. Glossary & Abbreviations
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| Epic | Grote feature of fase in development |
|
||||
| Story | Kleine uitvoerbare taak binnen een epic |
|
||||
| MVP | Minimum Viable Product |
|
||||
| EPD | Elektronisch Patient Dossier |
|
||||
| SSE | Server-Sent Events |
|
||||
|
||||
---
|
||||
|
||||
Versiehistorie:
|
||||
| Versie | Datum | Auteur | Wijziging |
|
||||
|--------|-------|--------|-----------|
|
||||
| v1.0 | 27-12-2025 | Colin Lit | Initiele versie |
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,774 +0,0 @@
|
||||
# Competitive Analysis: Declaratieve UI in Nederlandse Software
|
||||
|
||||
**Onderzoeksdatum:** 29 december 2025
|
||||
**Vraagstelling:** Zijn er Nederlandse softwareleveranciers (EPD/ECD, enterprise software) die een declaratieve UI hebben zoals Swift?
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Conclusie:** Swift's command-based, declaratieve UI is **uniek in de Nederlandse EPD markt** en zelfs internationaal zeldzaam. Geen enkele Nederlandse EPD leverancier (ChipSoft, Epic, Nexus) heeft een vergelijkbare command palette of natural language interface. Ook Nederlandse enterprise software (AFAS, Visma, Mollie, Adyen) documenteert geen command-based interfaces.
|
||||
|
||||
**Key Differentiators van Swift:**
|
||||
1. ✨ **Command palette met natural language** - typ "notitie jan medicatie" vs klikken door menu's
|
||||
2. 🤖 **AI intent classification** - begrijpt context en entities
|
||||
3. 📱 **Split-screen artifact rendering** - 40% chat + 60% werkgebied
|
||||
4. ⌨️ **Keyboard-first workflow** - ⌘K focus, Escape close, ⌘Enter submit
|
||||
5. 🎯 **Contextual awareness** - actieve patiënt, recent actions
|
||||
|
||||
---
|
||||
|
||||
## 🏥 Nederlandse EPD/ECD Leveranciers
|
||||
|
||||
### Marktoverzicht (2025)
|
||||
|
||||
De Nederlandse EPD-markt bestaat uit drie hoofdspelers na het vertrek van SAP/Cerner:
|
||||
|
||||
| Leverancier | Marktaandeel | Type |
|
||||
|-------------|--------------|------|
|
||||
| **ChipSoft (HiX)** | 72% | Nederlands |
|
||||
| **Epic** | 14% | Amerikaans |
|
||||
| **Nexus** | 11% | Duits |
|
||||
|
||||
**Bron:** [M&I Partners EPD-marktinventarisatie 2024](https://mxi.nl/kennis/644/epd-marktinventarisatie-ziekenhuizen-2024-consolidatie-epd-markt-zet-door)
|
||||
|
||||
---
|
||||
|
||||
### ChipSoft HiX
|
||||
|
||||
**Bedrijfsinfo:**
|
||||
- Marktleider in Nederland (72% ziekenhuizen)
|
||||
- ISO 13485 gecertificeerd
|
||||
- CE Medical Device klasse IIb certificering
|
||||
- Actief in: apotheek, eerstelijnszorg, GGZ, huisartsenzorg, revalidatie, VVT, ZBC's, ziekenhuizen
|
||||
|
||||
**UI/UX Features:**
|
||||
|
||||
✅ **Keyboard shortcuts** ("sneltoetsen")
|
||||
- Ondersteuning voor sneltoetsen bij registratie
|
||||
- Geen specifieke documentatie publiek beschikbaar
|
||||
|
||||
✅ **Dedicated UX Team**
|
||||
- Monitort en verbetert continu de 'look and feel'
|
||||
- Werkt volgens internationale standards
|
||||
- Observaties in werkplek om workflows te optimaliseren
|
||||
- Taskoriented views per apparaat (desktop/tablet/mobile)
|
||||
|
||||
✅ **Spraak-naar-tekst** (2025)
|
||||
- Integratie met Juvoly's speech-to-text
|
||||
- Reduceert registratielast voor huisartsen
|
||||
- Dit is **dictatie**, geen natural language interface
|
||||
|
||||
✅ **Personalisatie**
|
||||
- Gebruikers kunnen schermlay-out aanpassen
|
||||
- Favoriete functies configureren
|
||||
- Voorkeur voor waar patiëntinformatie opent
|
||||
|
||||
❌ **GEEN command palette of declaratieve UI**
|
||||
- Traditionele menu-driven interface
|
||||
- Geen natural language command input
|
||||
- Geen keyboard-first workflow zoals Swift
|
||||
|
||||
**Design Filosofie:**
|
||||
> "Wat direct opvalt bij het openen van HiX is de rustige uitstraling: eenvoudige pictogrammen, weinig lijnen en een centrale plek voor alle knoppen. Geen wildgroei aan kleuren... maar een apart pictogram voor elke eigenschap."
|
||||
|
||||
**Bronnen:**
|
||||
- [ChipSoft Gebruiksvriendelijkheid](https://www.chipsoft.com/nl-be/hix-abc/hix-abc-articles/gebruiksvriendelijkheid/)
|
||||
- [ChipSoft AI in HiX 2025](https://www.chipsoft.com/nl-nl/nieuws-en-blogs/ai-in-hix-ontdek-de-belangrijke-ontwikkelingen-in-2025/)
|
||||
|
||||
---
|
||||
|
||||
### Epic EMR
|
||||
|
||||
**Bedrijfsinfo:**
|
||||
- 14% marktaandeel Nederlandse ziekenhuizen
|
||||
- Amerikaans EHR/EMR systeem
|
||||
- Groeiend in Nederland (recent: Ziekenhuis Amstelland, MUMC+)
|
||||
|
||||
**UI/UX Features:**
|
||||
|
||||
✅ **Uitgebreide keyboard shortcuts**
|
||||
|
||||
Veelgebruikte shortcuts:
|
||||
- `Ctrl + O` - Go to Orders (manage orders tab)
|
||||
- `Alt + S` - Sign (sign current note)
|
||||
- `Alt + A` - Accept (accept order)
|
||||
- `Alt + [underlined letter]` - Selecteer menu optie
|
||||
- Standard shortcuts: `Ctrl + C` (copy), `Ctrl + Z` (undo)
|
||||
|
||||
✅ **Workflow optimalisatie**
|
||||
- Shortcuts kunnen workflow aanzienlijk versnellen
|
||||
- Vooral nuttig voor interventional radiologists en andere high-volume users
|
||||
|
||||
❌ **GEEN command palette feature**
|
||||
- Traditionele menu navigatie
|
||||
- Geen natural language interface
|
||||
- Geen centralized command bar
|
||||
|
||||
❌ **GEEN conversational interface**
|
||||
- Geen AI intent classification
|
||||
- Geen voice-to-command (wel dictatie via Dragon Medical)
|
||||
|
||||
**Bronnen:**
|
||||
- [Epic EMR Keyboard Shortcuts | TextExpander](https://textexpander.com/blog/epic-shortcuts)
|
||||
- [Easy Epic Keyboard Shortcuts | BackTable](https://www.backtable.com/shows/vi/articles/epic-emr-keyboard-shortcuts-how-to)
|
||||
|
||||
---
|
||||
|
||||
### Nexus Nederland
|
||||
|
||||
**Bedrijfsinfo:**
|
||||
- 11% marktaandeel Nederlandse ziekenhuizen
|
||||
- Onderdeel van Duits Nexus AG
|
||||
- Complete, modulaire EPD- en ECD-oplossingen voor ziekenhuizen en GGZ
|
||||
- Recent: St. Anna Zorggroep verlengde contract
|
||||
|
||||
**UI/UX Features:**
|
||||
- ❌ Geen specifieke UI innovaties gedocumenteerd
|
||||
- ❌ Geen publieke informatie over keyboard shortcuts of command interfaces
|
||||
|
||||
**Bron:**
|
||||
- [NEXUS Nederland](https://www.nexus-nederland.nl/)
|
||||
|
||||
---
|
||||
|
||||
## 🏢 Nederlandse Enterprise/SaaS Software
|
||||
|
||||
### HR Software: AFAS, Visma Nmbrs
|
||||
|
||||
**Visma Nmbrs:**
|
||||
- 100,000+ klanten
|
||||
- 1+ miljoen salarisadministraties per maand
|
||||
- Onderdeel van Visma (grootste software producer NL)
|
||||
|
||||
**Features:**
|
||||
✅ **API integraties**
|
||||
- User-friendly API met token-based authentication
|
||||
- Auto-sync met andere systemen (R&R, AFAS Profit)
|
||||
- Voorkomt dubbel werk en fouten
|
||||
|
||||
❌ **GEEN command interface**
|
||||
- Geen command palette gedocumenteerd
|
||||
- Geen natural language interface
|
||||
- Focus op webforms en automation
|
||||
|
||||
**AFAS:**
|
||||
- Complete ERP voor MKB
|
||||
- Sterk in accountancy, onderwijs, healthcare, trade
|
||||
- Business Process Outsourcing (BPO) optie
|
||||
|
||||
❌ **GEEN command interface** gedocumenteerd
|
||||
|
||||
**Bronnen:**
|
||||
- [Nmbrs | Visma Nederland](https://www.visma.nl/onze-bedrijven/nmbrs)
|
||||
- [AFAS | Visma Nmbrs Integraties](https://appstore.nmbrs.com/listings/afas)
|
||||
|
||||
---
|
||||
|
||||
### FinTech: Mollie, Adyen, MessageBird
|
||||
|
||||
**Mollie:**
|
||||
- Opgericht 2004 door Adriaan Mol (18 jaar oud)
|
||||
- 250,000+ bedrijven gebruiken Mollie
|
||||
- Grootste Nederlandse fintech deal ooit: acquisitie GoCardless voor €1.1B (2025)
|
||||
|
||||
**Adyen:**
|
||||
- Opgericht 2006 door Pieter van der Does en Arnout Schuijff
|
||||
- Focus op grote ondernemingen en multinationals
|
||||
- Omnichannel platform (online + fysieke winkels)
|
||||
|
||||
**MessageBird (nu Bird):**
|
||||
- Opgericht 2011 door Robert Vis en Adriaan Mol
|
||||
- Rebranded naar "Bird" in februari 2024
|
||||
- 700+ medewerkers
|
||||
- Focus: marketing, sales, payment solutions
|
||||
|
||||
**UI/UX Bevindingen:**
|
||||
❌ **GEEN command palette** features publiek gedocumenteerd
|
||||
- Deze bedrijven focussen op **payment/messaging API's**
|
||||
- End-user UI is vaak merchant dashboard (niet clinical workflow tool)
|
||||
- Developer-first platforms, niet operator-first
|
||||
|
||||
**Interessant:** Adriaan Mol is de oprichter van TWEE unicorns (Mollie $6B, MessageBird $4B)
|
||||
|
||||
**Bronnen:**
|
||||
- [De 15 beste Nederlandse SaaS-bedrijven | Web Whales](https://webwhales.nl/de-15-beste-nederlandse-saas-bedrijven/)
|
||||
- [Mollie vs Stripe vs Adyen | Codelevate](https://www.codelevate.com/nl/blog/mollie-vs-stripe-vs-adyen-psp-comparison-2025)
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Internationale Trends (2025)
|
||||
|
||||
### Microsoft Dragon Copilot for Nursing
|
||||
|
||||
**Lancering:** Late 2025
|
||||
**Type:** AI Clinical Assistant voor verpleegkundigen
|
||||
|
||||
**Features:**
|
||||
|
||||
✅ **Natural language conversational interface**
|
||||
- Verpleegkundigen kunnen **natuurlijk praten** met patiënten
|
||||
- Dragon Copilot **luistert op de achtergrond** (ambient listening)
|
||||
- Veilige mobile app voor bedside gebruik
|
||||
|
||||
✅ **Auto-generated documentation**
|
||||
- Genereert **structured flowsheet entries**
|
||||
- Nursing notes
|
||||
- Concise summaries van encounters
|
||||
|
||||
✅ **Query interface**
|
||||
- Verpleegkundigen kunnen vragen stellen aan Copilot
|
||||
- Antwoorden uit trusted sources (FDA, MedlinePlus)
|
||||
- Right at the bedside
|
||||
|
||||
✅ **Impact:**
|
||||
- **70% reductie in clinician burnout** bij gebruik van ambient AI
|
||||
- Documentatie is niet langer een separate task
|
||||
- Context-aware en ambient (niet command-driven)
|
||||
|
||||
**Verschil met Swift:**
|
||||
- Dragon Copilot is **ambient/passive** (luistert mee tijdens gesprek)
|
||||
- Swift is **active/command-driven** (gebruiker initieert acties)
|
||||
- Dragon focus: documentatie elimineren
|
||||
- Swift focus: acties versnellen
|
||||
|
||||
**Bron:**
|
||||
- [Microsoft Ignite 2025: Dragon Copilot for Nursing](https://techcommunity.microsoft.com/blog/healthcareandlifesciencesblog/highlights-from-ignite-2025-how-agentic-ai-and-microsoft-copilot-are-empowering-/4474658)
|
||||
|
||||
---
|
||||
|
||||
### M4 Infrastructure for EHR Data
|
||||
|
||||
**Type:** Research/data analysis tool
|
||||
**Developer:** PathOnAI (academic/research)
|
||||
|
||||
**Features:**
|
||||
|
||||
✅ **Natural language queries** voor EHR data
|
||||
- Query MIMIC-IV, eICU, custom datasets
|
||||
- Unified toolbox voor LLM agents
|
||||
- Supports tabular data en clinical notes
|
||||
|
||||
✅ **Multimodal support**
|
||||
- Dynamically selects tools by modality
|
||||
- Single natural-language interface
|
||||
|
||||
**Verschil met Swift:**
|
||||
- M4 is **research/analytics tool**, niet clinical workflow
|
||||
- Voor data scientists, niet clinici
|
||||
- Query historical data, niet real-time documentation
|
||||
|
||||
**Bron:**
|
||||
- [M4 - Infrastructure for EHR Data | Glama](https://glama.ai/mcp/servers/@hannesill/m4)
|
||||
|
||||
---
|
||||
|
||||
### Voice-First EHR Interfaces (2026 Trend)
|
||||
|
||||
**Trend:** Voice-first interfaces moving from experimental to mainstream
|
||||
|
||||
**Players:**
|
||||
- Microsoft Dragon Copilot
|
||||
- Oracle AI-driven platforms
|
||||
|
||||
**Features:**
|
||||
- Natural language for documentation
|
||||
- Navigation via voice
|
||||
- Information retrieval via voice
|
||||
|
||||
**Impact:**
|
||||
- 70% van clinici rapporteert **reduced burnout**
|
||||
- Ambient AI luistert passief tijdens patient encounters
|
||||
- Auto-generates clinical notes
|
||||
|
||||
**Bron:**
|
||||
- [EHR Interface Design: The Complete 2026 Guide | Arkenea](https://arkenea.com/blog/ehr-interface/)
|
||||
|
||||
---
|
||||
|
||||
## 💻 Command Palettes in General Software
|
||||
|
||||
Command palettes zijn **wijdverspreid in developer tools**, maar **zeldzaam in healthcare**:
|
||||
|
||||
### Developer Tools
|
||||
|
||||
| Software | Shortcut | Platform |
|
||||
|----------|----------|----------|
|
||||
| **VS Code** | `Ctrl+Shift+P` / `Cmd+Shift+P` | Cross-platform |
|
||||
| **GitHub** | `Ctrl+Shift+K` / `Cmd+Shift+K` | Web |
|
||||
| **Visual Studio 2022** | `Ctrl+Shift+P` | Windows |
|
||||
| **PowerToys** | `Win+Alt+Space` | Windows 11/10 |
|
||||
| **Oracle Code Editor** | `F1` | Cloud IDE |
|
||||
| **RStudio** | `Ctrl+Shift+P` / `Cmd+Shift+P` | Cross-platform |
|
||||
|
||||
**Common Pattern:**
|
||||
- Keyboard-driven launcher
|
||||
- Searchable command list
|
||||
- Fuzzy search
|
||||
- Shows keyboard shortcuts
|
||||
- Context-aware suggestions
|
||||
|
||||
**Best Practices (Mobbin):**
|
||||
- Most apps use `Cmd+K` or `Cmd+P`
|
||||
- Quick access/hide via shortcut
|
||||
- Search-driven interface
|
||||
- Eliminates need to remember obscure shortcuts
|
||||
- Faster than navigating complex menus
|
||||
|
||||
**Bronnen:**
|
||||
- [Command Palette UI Design Best Practices | Mobbin](https://mobbin.com/glossary/command-palette)
|
||||
- [How To Customize Command Palette For Enhanced Productivity In 2025](https://www.acciyo.com/how-to-customize-command-palette-for-enhanced-productivity-in-2025/)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Swift's Unique Position
|
||||
|
||||
### Wat Swift Combineert (en anderen NIET hebben)
|
||||
|
||||
Swift zit in een unieke positie door het combineren van vijf elementen die **afzonderlijk wel bestaan**, maar **zelden samen voorkomen**:
|
||||
|
||||
| Feature | Swift | ChipSoft | Epic | Dragon Copilot | Developer Tools |
|
||||
|---------|-------|----------|------|----------------|-----------------|
|
||||
| **Command palette** | ✅ | ❌ | ❌ | ❌ | ✅ |
|
||||
| **Natural language input** | ✅ | ❌ | ❌ | ✅ | ❌ |
|
||||
| **AI intent classification** | ✅ | ❌ | ❌ | ✅ | ❌ |
|
||||
| **Split-screen artifacts** | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Keyboard-first workflow** | ✅ | Partial | ✅ | ❌ | ✅ |
|
||||
| **Contextual awareness** | ✅ | ❌ | ❌ | ✅ | Partial |
|
||||
| **Healthcare-specific** | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
### Swift's Key Differentiators
|
||||
|
||||
#### 1. **Declaratief vs Imperatief**
|
||||
|
||||
**Traditional EPD's (ChipSoft, Epic, Nexus):**
|
||||
- Menu-driven: Klik Patiënt → Notitie → Medicatie → Type → Submit
|
||||
- Mouse-heavy: 10+ clicks voor simpele actie
|
||||
- Imperatief: Gebruiker specificeert **HOE** (stap voor stap)
|
||||
|
||||
**Swift:**
|
||||
- Command-driven: Type "notitie jan medicatie"
|
||||
- Keyboard-first: 1 command + Enter
|
||||
- Declaratief: Gebruiker specificeert **WAT** (doel)
|
||||
|
||||
#### 2. **Intent-based Routing**
|
||||
|
||||
**Swift's AI classificatie:**
|
||||
```typescript
|
||||
Input: "notitie jan medicatie"
|
||||
↓ Intent classification
|
||||
Intent: "create_note"
|
||||
Entities: { patientName: "jan", category: "medicatie" }
|
||||
Confidence: 0.92
|
||||
↓ Route to artifact
|
||||
Opens: DagnotatieBlock with prefill
|
||||
```
|
||||
|
||||
**Andere EPD's:**
|
||||
- Geen intent classificatie
|
||||
- Gebruiker moet zelf navigeren
|
||||
- Geen context extraction uit natural language
|
||||
|
||||
#### 3. **Split-Screen Context Retention**
|
||||
|
||||
**Swift:**
|
||||
- 40% Chat Panel: Conversatie history + recent actions
|
||||
- 60% Artifact Area: Live werkgebied
|
||||
- Context blijft zichtbaar tijdens werken
|
||||
|
||||
**Andere EPD's:**
|
||||
- Modal dialogs (verlies context)
|
||||
- Full-screen forms (verlies overzicht)
|
||||
- Tabbed interface (constant switchen)
|
||||
|
||||
#### 4. **Keyboard-First Workflow**
|
||||
|
||||
**Swift shortcuts:**
|
||||
- `⌘K` - Focus input (altijd beschikbaar)
|
||||
- `Escape` - Close artifacts
|
||||
- `⌘Enter` - Quick submit
|
||||
- `1/2/3` - FallbackPicker selection
|
||||
|
||||
**ChipSoft/Epic:**
|
||||
- Hebben shortcuts, maar niet centraal
|
||||
- Geen universal command entry point
|
||||
- Shortcuts zijn per-screen/per-function
|
||||
- Geen keyboard-only workflow mogelijk
|
||||
|
||||
#### 5. **Voice + Text Unified**
|
||||
|
||||
**Swift:**
|
||||
- Deepgram streaming voice input
|
||||
- Voice → Text → Intent classification
|
||||
- Same pipeline voor voice en typed input
|
||||
- Waveform visualization tijdens recording
|
||||
|
||||
**ChipSoft:**
|
||||
- Juvoly dictatie (speech-to-text)
|
||||
- Alleen voor notitie dictation
|
||||
- Niet voor navigation/commands
|
||||
|
||||
**Dragon Copilot:**
|
||||
- Ambient listening (passive)
|
||||
- Auto-generates notes
|
||||
- Niet voor commands/actions
|
||||
|
||||
---
|
||||
|
||||
### Market Positioning
|
||||
|
||||
```
|
||||
Traditional EPD Swift Ambient AI
|
||||
(Menu-driven) (Command-driven) (Passive listening)
|
||||
|
||||
ChipSoft ──────────────────────► ◄──────────────────── Dragon Copilot
|
||||
Epic
|
||||
Nexus
|
||||
|
||||
Mouse-heavy Keyboard-first Voice-passive
|
||||
Imperative Declarative Automatic
|
||||
Step-by-step Intent-based Ambient
|
||||
```
|
||||
|
||||
**Swift's sweet spot:**
|
||||
- Sneller dan traditional EPD's (minder clicks)
|
||||
- Meer control dan ambient AI (gebruiker initieert)
|
||||
- Keyboard-first (ergonomisch voor power users)
|
||||
- Natural language (lage learning curve)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Competitive Advantages
|
||||
|
||||
### 1. **Snelheid**
|
||||
|
||||
**Traditional workflow (ChipSoft/Epic):**
|
||||
```
|
||||
Klik Patiënt (1) → Selecteer Jan (2) → Klik Acties (3) →
|
||||
Klik Notitie (4) → Selecteer Medicatie (5) → Type text (6) →
|
||||
Klik Submit (7)
|
||||
|
||||
Total: 7 interactions, ~20 seconden
|
||||
```
|
||||
|
||||
**Swift workflow:**
|
||||
```
|
||||
⌘K (1) → Type "notitie jan medicatie" (2) → ⌘Enter (3)
|
||||
|
||||
Total: 3 interactions, ~5 seconden
|
||||
```
|
||||
|
||||
**Speed advantage: 4x sneller**
|
||||
|
||||
---
|
||||
|
||||
### 2. **Cognitieve last**
|
||||
|
||||
**Traditional EPD:**
|
||||
- Moet menu structure onthouden
|
||||
- Moet locatie van functies onthouden
|
||||
- Moet door meerdere screens navigeren
|
||||
- Context switching tussen screens
|
||||
|
||||
**Swift:**
|
||||
- Type intentie in natural language
|
||||
- AI herkent context automatisch
|
||||
- Blijf in hetzelfde window
|
||||
- Context blijft zichtbaar in split-screen
|
||||
|
||||
**Cognitive load: Significant lager**
|
||||
|
||||
---
|
||||
|
||||
### 3. **Leer curve**
|
||||
|
||||
**Traditional EPD:**
|
||||
- Training nodig voor menu navigatie
|
||||
- Moet locaties onthouden
|
||||
- Verschillende workflows per functie
|
||||
|
||||
**Swift:**
|
||||
- Natural language (spreek zoals je denkt)
|
||||
- FallbackPicker bij onduidelijke input
|
||||
- Recent actions tonen voorbeelden
|
||||
- Incrementeel leren (geen big bang training)
|
||||
|
||||
**Learning curve: Vlakker**
|
||||
|
||||
---
|
||||
|
||||
### 4. **Ergonomie**
|
||||
|
||||
**Mouse-heavy workflows:**
|
||||
- Repetitive Strain Injury (RSI) risico
|
||||
- Hand van keyboard naar muis
|
||||
- Precision clicking (klein target)
|
||||
|
||||
**Swift keyboard-first:**
|
||||
- Hands blijven op keyboard
|
||||
- Geen precision clicking
|
||||
- Voice fallback bij RSI/disability
|
||||
- Lager RSI risico
|
||||
|
||||
---
|
||||
|
||||
### 5. **Schaalbaarheid**
|
||||
|
||||
**Traditional menu's:**
|
||||
- Meer functies = diepere menu's
|
||||
- Menu sprawl bij feature growth
|
||||
- Moeilijker te navigeren over tijd
|
||||
|
||||
**Swift command palette:**
|
||||
- Meer functies = meer commands
|
||||
- Search/fuzzy match blijft efficient
|
||||
- AI kan nieuwe intents leren
|
||||
- Lineair schaalbaar
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Innovation Opportunities
|
||||
|
||||
### Wat Swift kan toevoegen (geïnspireerd door onderzoek)
|
||||
|
||||
#### 1. **Macro's / Custom Commands**
|
||||
Inspiratie: TextExpander, VS Code snippets
|
||||
|
||||
```
|
||||
User creates custom command:
|
||||
"dagstart" → Opens 5 artifacts:
|
||||
- Agenda voor vandaag
|
||||
- Nieuwe patiënten
|
||||
- Kritieke waardes
|
||||
- Taken
|
||||
- Team chat
|
||||
```
|
||||
|
||||
#### 2. **Multi-step Commands**
|
||||
Inspiratie: GitHub CLI, PowerToys Run
|
||||
|
||||
```
|
||||
User types:
|
||||
"plan jan consult cardio volgende week"
|
||||
|
||||
Swift parses:
|
||||
- Action: plan appointment
|
||||
- Patient: jan
|
||||
- Type: consult
|
||||
- Specialty: cardio
|
||||
- Time: volgende week
|
||||
|
||||
Opens: Appointment scheduler with prefill
|
||||
```
|
||||
|
||||
#### 3. **Command History & Autocomplete**
|
||||
Inspiratie: Shell history, VS Code recent commands
|
||||
|
||||
```
|
||||
User types: "not"
|
||||
Autocomplete suggestions:
|
||||
- notitie jan medicatie (used 3x today)
|
||||
- notitie maria adl (used yesterday)
|
||||
- nieuwe patient intake
|
||||
```
|
||||
|
||||
#### 4. **Voice Commands Training**
|
||||
Inspiratie: Dragon Medical custom vocabulary
|
||||
|
||||
```
|
||||
User trains Swift:
|
||||
"dagno" → dagnotatie
|
||||
"medi jan" → medicatie voor jan
|
||||
"print epd" → export patient summary PDF
|
||||
```
|
||||
|
||||
#### 5. **Team Shared Commands**
|
||||
Inspiratie: VS Code workspace settings
|
||||
|
||||
```
|
||||
Team creates shared command:
|
||||
"overdracht ochtend" → Opens:
|
||||
- Nachtdienst notities
|
||||
- Kritieke events
|
||||
- Action items
|
||||
- Patient status changes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Lessons from Competition
|
||||
|
||||
### What Works (implement in Swift)
|
||||
|
||||
1. **ChipSoft's UX Team approach**
|
||||
- Continuous workplace observation
|
||||
- User-specific customization
|
||||
- Task-oriented views per device
|
||||
|
||||
2. **Epic's comprehensive shortcuts**
|
||||
- Document ALL shortcuts
|
||||
- Alt + underlined letter pattern
|
||||
- Workflow-specific shortcuts
|
||||
|
||||
3. **Dragon Copilot's ambient approach**
|
||||
- Reduce documentation burden
|
||||
- Context-aware auto-fill
|
||||
- Trusted source integration
|
||||
|
||||
4. **Developer tool patterns**
|
||||
- Fuzzy search in command palette
|
||||
- Recent commands prioritization
|
||||
- Visual keyboard hints
|
||||
|
||||
### What Doesn't Work (avoid in Swift)
|
||||
|
||||
1. **Vendor lock-in (ChipSoft/Epic)**
|
||||
- Systems "te duur" en "gebrekkige wil tot aanpassingen"
|
||||
- Moeilijk om over te stappen (verweven met andere systemen)
|
||||
- **Swift:** Stay modular, open standards (FHIR)
|
||||
|
||||
2. **Menu sprawl**
|
||||
- Meer features = diepere menus
|
||||
- **Swift:** Command palette scales linearly
|
||||
|
||||
3. **Passive-only AI (Dragon)**
|
||||
- Geen control over timing
|
||||
- Niet geschikt voor alle workflows
|
||||
- **Swift:** User-initiated blijft belangrijk
|
||||
|
||||
4. **Platform fragmentation**
|
||||
- Desktop-only shortcuts
|
||||
- Mobile separate workflow
|
||||
- **Swift:** Unified command interface cross-platform
|
||||
|
||||
---
|
||||
|
||||
## 📈 Market Opportunity
|
||||
|
||||
### Current EPD Market Pain Points
|
||||
|
||||
1. **Efficiency crisis**
|
||||
- Clinici spenderen 50%+ tijd aan administratie
|
||||
- Burnout epidemic in healthcare
|
||||
- **Swift's answer:** 4x sneller via command interface
|
||||
|
||||
2. **Vendor lock-in**
|
||||
- Ziekenhuizen "kunnen er bijna niet meer vanaf"
|
||||
- "Enorme kosten van EPD-vervanging"
|
||||
- **Swift's answer:** Modular, cloud-based, lower switching cost
|
||||
|
||||
3. **Poor usability**
|
||||
- "Veel te dure informatiesystemen"
|
||||
- "Gebrekkige wil tot aanpassingen"
|
||||
- **Swift's answer:** User-centered, command-driven, highly customizable
|
||||
|
||||
4. **Consolidation limiting choice**
|
||||
- Markt van 5 naar 3 spelers (SAP/Cerner exit)
|
||||
- ChipSoft 72% monopoly
|
||||
- **Swift's answer:** New entrant with differentiated approach
|
||||
|
||||
---
|
||||
|
||||
### Target Segments
|
||||
|
||||
**Early Adopters (Power Users):**
|
||||
- Tech-savvy clinicians
|
||||
- Interventional specialties (radiology, surgery)
|
||||
- High-volume workflows (IC, ER)
|
||||
- Keyboard-first preference
|
||||
|
||||
**Innovator Hospitals:**
|
||||
- Academic medical centers (research-oriented)
|
||||
- Startup/scale-up hospitals
|
||||
- Organizations frustrated with current vendor
|
||||
|
||||
**International:**
|
||||
- Markets with less vendor lock-in
|
||||
- English-speaking countries (easier localization)
|
||||
- Countries with national EHR initiatives
|
||||
|
||||
---
|
||||
|
||||
## 🏁 Conclusion
|
||||
|
||||
### Swift's Competitive Position: **Uniquely Positioned**
|
||||
|
||||
**Summary:**
|
||||
- ✅ **Geen Nederlandse EPD** heeft command palette of declaratieve UI
|
||||
- ✅ **Geen Nederlandse enterprise software** documenteert vergelijkbare interface
|
||||
- ✅ **Internationale trends** (Dragon Copilot, M4) gaan richting natural language, maar met andere focus (ambient vs command-driven)
|
||||
- ✅ **Developer tools** hebben command palettes, maar niet healthcare-specific
|
||||
- ✅ **Swift combineert** vijf elementen die afzonderlijk bestaan maar zelden samen
|
||||
|
||||
### Unique Value Proposition
|
||||
|
||||
Swift is:
|
||||
1. **Sneller** dan traditional EPD's (4x via keyboard-first)
|
||||
2. **Meer control** dan ambient AI (user-initiated)
|
||||
3. **Lager cognitive load** dan menu-driven interfaces
|
||||
4. **Schaalbaarder** dan menu hierarchies
|
||||
5. **Ergonomischer** dan mouse-heavy workflows
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Go-to-market positioning:**
|
||||
> "Swift: De eerste command-driven EPD voor power users.
|
||||
> Type WAT je wilt, niet HOE. 4x sneller dan klikken door menu's."
|
||||
|
||||
**Target message:**
|
||||
- Voor tech-savvy clinicians: "EPD met shortcuts zoals VS Code"
|
||||
- Voor administrators: "Reduce documentation time 70%"
|
||||
- Voor hospitals: "Moderne EPD zonder vendor lock-in"
|
||||
|
||||
**Next steps:**
|
||||
1. Publiceer competitive analysis (deze doc)
|
||||
2. Create demo video comparing Swift vs ChipSoft workflow
|
||||
3. Develop case studies met time savings metrics
|
||||
4. Target early adopter hospitals (academic centers)
|
||||
5. Present at Dutch healthcare innovation conferences
|
||||
|
||||
---
|
||||
|
||||
## 📚 Bronnen
|
||||
|
||||
### Nederlandse EPD Markt
|
||||
- [EPD-marktinventarisatie ziekenhuizen 2024 | M&I/Partners](https://mxi.nl/kennis/644/epd-marktinventarisatie-ziekenhuizen-2024-consolidatie-epd-markt-zet-door)
|
||||
- [Consolidatie op Nederlandse EPD-markt | ICT&health](https://www.icthealth.nl/nieuws/consolidatie-op-nederlandse-epd-markt-zet-door-met-vertrek-sapcerner)
|
||||
- [ACM: ziekenhuizen sterk afhankelijk van EPD-leverancier | Security.NL](https://www.security.nl/posting/735021/ACM:+ziekenhuizen+sterk+afhankelijk+van+EPD-leverancier,+pati%C3%ABnten+dupe)
|
||||
|
||||
### ChipSoft HiX
|
||||
- [ChipSoft Gebruiksvriendelijkheid](https://www.chipsoft.com/nl-be/hix-abc/hix-abc-articles/gebruiksvriendelijkheid/)
|
||||
- [ChipSoft AI in HiX 2025](https://www.chipsoft.com/nl-nl/nieuws-en-blogs/ai-in-hix-ontdek-de-belangrijke-ontwikkelingen-in-2025/)
|
||||
- [ChipSoft HiX Homepage](https://www.chipsoft.com/nl-nl/oplossingen/elektronisch-patientendossier-hix-optimale-zorginnovatie/)
|
||||
|
||||
### Epic EMR
|
||||
- [Epic EMR Keyboard Shortcuts | TextExpander](https://textexpander.com/blog/epic-shortcuts)
|
||||
- [Easy Epic Keyboard Shortcuts | BackTable](https://www.backtable.com/shows/vi/articles/epic-emr-keyboard-shortcuts-how-to)
|
||||
|
||||
### Nexus
|
||||
- [NEXUS Nederland EPD leverancier](https://www.nexus-nederland.nl/)
|
||||
|
||||
### Nederlandse SaaS
|
||||
- [De 15 beste Nederlandse SaaS-bedrijven | Web Whales](https://webwhales.nl/de-15-beste-nederlandse-saas-bedrijven/)
|
||||
- [Nmbrs | Visma Nederland](https://www.visma.nl/onze-bedrijven/nmbrs)
|
||||
- [Mollie vs Stripe vs Adyen | Codelevate](https://www.codelevate.com/nl/blog/mollie-vs-stripe-vs-adyen-psp-comparison-2025)
|
||||
|
||||
### Internationale Trends
|
||||
- [Microsoft Ignite 2025: Dragon Copilot for Nursing](https://techcommunity.microsoft.com/blog/healthcareandlifesciencesblog/highlights-from-ignite-2025-how-agentic-ai-and-microsoft-copilot-are-empowering-/4474658)
|
||||
- [EHR Interface Design: The Complete 2026 Guide | Arkenea](https://arkenea.com/blog/ehr-interface/)
|
||||
- [M4 - Infrastructure for EHR Data | Glama](https://glama.ai/mcp/servers/@hannesill/m4)
|
||||
- [Large language models in healthcare | Nature Medicine](https://www.nature.com/articles/s41591-024-03199-w)
|
||||
|
||||
### Command Palettes
|
||||
- [Command Palette UI Design Best Practices | Mobbin](https://mobbin.com/glossary/command-palette)
|
||||
- [How To Customize Command Palette For Enhanced Productivity In 2025](https://www.acciyo.com/how-to-customize-command-palette-for-enhanced-productivity-in-2025/)
|
||||
- [GitHub Command Palette Docs](https://docs.github.com/en/enterprise-cloud@latest/get-started/using-github/github-command-palette)
|
||||
- [PowerToys Command Palette | Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/command-palette/overview)
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Laatste update:** 29 december 2025
|
||||
**Auteur:** Colin (met Claude Code)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,426 +0,0 @@
|
||||
# Epic 0 — Design Tokens & Component Inventory
|
||||
|
||||
**Datum:** 27-12-2024
|
||||
**Auteur:** AI Assistant
|
||||
**Status:** ✅ Completed
|
||||
|
||||
---
|
||||
|
||||
## E0.S1 — Design Tokens Audit
|
||||
|
||||
🎯 **Doel:** Alle kleuren, spacing, typography gedocumenteerd voor v3.0 gebruik.
|
||||
|
||||
### Colors (blijven hetzelfde voor v3.0)
|
||||
|
||||
#### Base Colors
|
||||
```css
|
||||
--color-bg: #F8FAFC; /* App background */
|
||||
--color-surface: #FFFFFF; /* Cards, blocks, surfaces */
|
||||
--color-surface-secondary: #F1F5F9; /* Secondary surfaces */
|
||||
--color-text: #0F172A; /* Primary text (slate-900) */
|
||||
--color-text-secondary: #475569; /* Secondary text (slate-600) */
|
||||
--color-border: #E2E8F0; /* Borders (slate-200) */
|
||||
```
|
||||
|
||||
#### Brand Colors (Teal-first Design System)
|
||||
```css
|
||||
--color-brand: #0F766E; /* teal-700 - PRIMARY (5.47:1 WCAG AA) */
|
||||
--color-brand-hover: #115E59; /* teal-800 */
|
||||
--color-brand-active: #0D9488; /* teal-600 */
|
||||
--color-brand-subtle: #F0FDFA; /* teal-50 */
|
||||
```
|
||||
|
||||
#### AI Feature Colors (Amber)
|
||||
```css
|
||||
--color-ai: #D97706; /* amber-600 - PRIMARY AI (3.19:1 WCAG AA large) */
|
||||
--color-ai-hover: #B45309; /* amber-700 */
|
||||
--color-ai-subtle: #FFFBEB; /* amber-50 */
|
||||
```
|
||||
|
||||
#### Status & Feedback Colors
|
||||
```css
|
||||
--color-success: #16A34A; /* green-600 */
|
||||
--color-success-subtle: #ECFDF5; /* green-50 */
|
||||
--color-warning: #EAB308; /* yellow-500 */
|
||||
--color-warning-subtle: #FEFCE8; /* yellow-50 */
|
||||
--color-error: #DC2626; /* red-600 */
|
||||
--color-error-subtle: #FEF2F2; /* red-50 */
|
||||
--color-info: #0F766E; /* teal-700 - Brand consistency */
|
||||
--color-info-subtle: #CCFBF1; /* teal-100 */
|
||||
```
|
||||
|
||||
#### Form/Input Colors
|
||||
```css
|
||||
--color-input-bg: #FFFFFF;
|
||||
--color-input-text: #0F172A;
|
||||
--color-input-placeholder: #94A3B8; /* slate-400 */
|
||||
--color-input-border: #CBD5E1; /* slate-300 */
|
||||
--color-input-border-hover: #94A3B8;
|
||||
--color-input-focus: #0F766E; /* teal-700 */
|
||||
--color-input-focus-border: #115E59; /* teal-800 */
|
||||
--color-input-disabled-bg: #F1F5F9;
|
||||
--color-input-disabled-text: #94A3B8;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🆕 NEW: Chat Message Colors (v3.0 only)
|
||||
|
||||
Voor de nieuwe chat interface:
|
||||
|
||||
```css
|
||||
/* User messages (rechts, amber tint) */
|
||||
--chat-user-bg: #FFFBEB; /* amber-50 */
|
||||
--chat-user-border: #FED7AA; /* amber-200 */
|
||||
--chat-user-text: #0F172A; /* text-primary */
|
||||
|
||||
/* Assistant messages (links, slate tint) */
|
||||
--chat-assistant-bg: #F1F5F9; /* slate-100 */
|
||||
--chat-assistant-border: #CBD5E1; /* slate-300 */
|
||||
--chat-assistant-text: #0F172A; /* text-primary */
|
||||
|
||||
/* System messages (centered, subtle) */
|
||||
--chat-system-text: #64748B; /* slate-500 */
|
||||
|
||||
/* Error messages (links, red tint) */
|
||||
--chat-error-bg: #FEF2F2; /* red-50 */
|
||||
--chat-error-border: #FECACA; /* red-200 */
|
||||
--chat-error-text: #991B1B; /* red-800 */
|
||||
```
|
||||
|
||||
**Tailwind classes voor chat messages:**
|
||||
```tsx
|
||||
const MESSAGE_STYLES = {
|
||||
user: {
|
||||
container: 'self-end bg-amber-50 border border-amber-200 text-slate-900',
|
||||
borderRadius: 'rounded-2xl rounded-tr-sm',
|
||||
},
|
||||
assistant: {
|
||||
container: 'self-start bg-slate-100 border border-slate-300 text-slate-900',
|
||||
borderRadius: 'rounded-2xl rounded-tl-sm',
|
||||
},
|
||||
system: {
|
||||
container: 'self-center text-slate-500 text-sm',
|
||||
borderRadius: '',
|
||||
},
|
||||
error: {
|
||||
container: 'self-start bg-red-50 border border-red-200 text-red-800',
|
||||
borderRadius: 'rounded-2xl',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Verpleegkundig Category Colors (blijven hetzelfde)
|
||||
|
||||
Gebruikt in DagnotatieBlock, OverdrachtBlock:
|
||||
|
||||
```typescript
|
||||
export const CATEGORY_CONFIG = {
|
||||
medicatie: {
|
||||
label: 'Medicatie',
|
||||
icon: 'Pill',
|
||||
bgColor: 'bg-blue-100', // #DBEAFE
|
||||
textColor: 'text-blue-700', // #1D4ED8
|
||||
},
|
||||
adl: {
|
||||
label: 'ADL/verzorging',
|
||||
icon: 'Utensils',
|
||||
bgColor: 'bg-green-100', // #DCFCE7
|
||||
textColor: 'text-green-700', // #15803D
|
||||
},
|
||||
gedrag: {
|
||||
label: 'Gedragsobservatie',
|
||||
icon: 'User',
|
||||
bgColor: 'bg-purple-100', // #F3E8FF
|
||||
textColor: 'text-purple-700', // #7E22CE
|
||||
},
|
||||
incident: {
|
||||
label: 'Incident',
|
||||
icon: 'AlertTriangle',
|
||||
bgColor: 'bg-red-100', // #FEE2E2
|
||||
textColor: 'text-red-700', // #B91C1C
|
||||
},
|
||||
observatie: {
|
||||
label: 'Observatie',
|
||||
icon: 'Eye',
|
||||
bgColor: 'bg-slate-100', // #F1F5F9
|
||||
textColor: 'text-slate-700', // #334155
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Swift Shift Colors (blijven hetzelfde)
|
||||
|
||||
Gebruikt in ContextBar:
|
||||
|
||||
```typescript
|
||||
const SHIFT_CONFIG = {
|
||||
ochtend: {
|
||||
icon: Sunrise,
|
||||
label: 'Ochtenddienst',
|
||||
color: 'text-amber-600', // #D97706
|
||||
},
|
||||
middag: {
|
||||
icon: Sun,
|
||||
label: 'Middagdienst',
|
||||
color: 'text-yellow-600', // #CA8A04
|
||||
},
|
||||
avond: {
|
||||
icon: Sunset,
|
||||
label: 'Avonddienst',
|
||||
color: 'text-orange-600', // #EA580C
|
||||
},
|
||||
nacht: {
|
||||
icon: Moon,
|
||||
label: 'Nachtdienst',
|
||||
color: 'text-indigo-600', // #4F46E5
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Shadows (blijven hetzelfde)
|
||||
|
||||
```css
|
||||
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
|
||||
--shadow-md: 0 2px 6px rgba(15, 23, 42, 0.08);
|
||||
--shadow-lg: 0 8px 20px rgba(15, 23, 42, 0.10);
|
||||
```
|
||||
|
||||
**Tailwind classes:**
|
||||
- `shadow-sm` — Subtle shadows (cards, inputs)
|
||||
- `shadow-md` — Medium shadows (dropdowns, popovers)
|
||||
- `shadow-lg` — Large shadows (modals, dialogs)
|
||||
|
||||
---
|
||||
|
||||
### Border Radius (blijven hetzelfde)
|
||||
|
||||
```css
|
||||
--radius: 0.625rem; /* 10px - base radius */
|
||||
```
|
||||
|
||||
**Tailwind classes:**
|
||||
- `rounded-sm` — 4px (small elements)
|
||||
- `rounded-md` — 8px (buttons, inputs)
|
||||
- `rounded-lg` — 10px (cards, blocks)
|
||||
- `rounded-2xl` — 16px (chat bubbles, large cards)
|
||||
- `rounded-full` — 9999px (avatars, badges)
|
||||
|
||||
---
|
||||
|
||||
### Typography Scale
|
||||
|
||||
```css
|
||||
/* Font sizes */
|
||||
--text-xs: 0.75rem; /* 12px */
|
||||
--text-sm: 0.875rem; /* 14px */
|
||||
--text-base: 1rem; /* 16px */
|
||||
--text-lg: 1.125rem; /* 18px */
|
||||
--text-xl: 1.25rem; /* 20px */
|
||||
|
||||
/* Line heights */
|
||||
--line-height-tight: 1.2; /* Headings */
|
||||
--line-height-relaxed: 1.8; /* Body text */
|
||||
```
|
||||
|
||||
**Tailwind classes:**
|
||||
- `text-xs` — Metadata, labels
|
||||
- `text-sm` — Secondary text, descriptions
|
||||
- `text-base` — Body text (default)
|
||||
- `text-lg` — Input fields, chat messages
|
||||
- `text-xl` — Headings, titles
|
||||
|
||||
---
|
||||
|
||||
### Spacing Scale (blijven hetzelfde)
|
||||
|
||||
```css
|
||||
--space-1: 0.25rem; /* 4px */
|
||||
--space-2: 0.5rem; /* 8px */
|
||||
--space-3: 0.75rem; /* 12px */
|
||||
--space-4: 1rem; /* 16px */
|
||||
--space-6: 1.5rem; /* 24px */
|
||||
--space-8: 2rem; /* 32px */
|
||||
--space-12: 3rem; /* 48px */
|
||||
--space-16: 4rem; /* 64px */
|
||||
```
|
||||
|
||||
**Tailwind classes:**
|
||||
- `p-2` / `m-2` — 8px padding/margin
|
||||
- `p-4` / `m-4` — 16px (most common)
|
||||
- `p-6` / `m-6` — 24px (cards, blocks)
|
||||
- `gap-4` — 16px gap in flex/grid
|
||||
|
||||
---
|
||||
|
||||
### 🎨 Design Token Usage in v3.0
|
||||
|
||||
#### Chat Panel
|
||||
- Background: `bg-white` (`--color-surface`)
|
||||
- Border right: `border-r border-slate-200` (`--color-border`)
|
||||
- Width: `w-[40%]` (desktop), `w-full` (mobile)
|
||||
|
||||
#### Chat Messages
|
||||
- User: `bg-amber-50 border-amber-200` (NEW)
|
||||
- Assistant: `bg-slate-100 border-slate-300` (NEW)
|
||||
- Padding: `p-3` (12px) of `p-4` (16px)
|
||||
- Border radius: `rounded-2xl` met accent `rounded-tr-sm` (user) of `rounded-tl-sm` (assistant)
|
||||
|
||||
#### Artifact Area
|
||||
- Background: `bg-slate-50` (`--color-surface-secondary`)
|
||||
- Width: `w-[60%]` (desktop), `w-full` (mobile)
|
||||
|
||||
#### Artifact Container (bestaande blocks blijven ongewijzigd)
|
||||
- Background: `bg-white`
|
||||
- Border: `border border-slate-200`
|
||||
- Border radius: `rounded-lg`
|
||||
- Shadow: `shadow-lg`
|
||||
- Padding: `p-6`
|
||||
|
||||
---
|
||||
|
||||
## E0.S2 — Component Inventory
|
||||
|
||||
🎯 **Doel:** Lijst van alle blocks die herbruikbaar zijn vs. nieuwe componenten voor v3.0.
|
||||
|
||||
### ✅ Bestaande Components (blijven ongewijzigd)
|
||||
|
||||
Deze components werken in v3.0 zonder wijzigingen (alleen de wrapper wijzigt):
|
||||
|
||||
| Component | Locatie | Functie | Wijzigingen v3.0 |
|
||||
|-----------|---------|---------|------------------|
|
||||
| **ContextBar** | `components/swift/command-center/context-bar.tsx` | Dienst, patient selector, user info | ✅ Geen wijzigingen |
|
||||
| **OfflineBanner** | `components/swift/command-center/offline-banner.tsx` | Offline detectie en banner | ✅ Geen wijzigingen |
|
||||
| **DagnotatieBlock** | `components/swift/blocks/dagnotitie-block.tsx` | Dagnotitie maken | ✅ Geen wijzigingen |
|
||||
| **ZoekenBlock** | `components/swift/blocks/zoeken-block.tsx` | Patient zoeken | ✅ Geen wijzigingen |
|
||||
| **OverdrachtBlock** | `components/swift/blocks/overdracht-block.tsx` | Dienst overdracht | ⚠️ Uitbreiding: AI-filtering (E5.S1) |
|
||||
| **PatientContextCard** | `components/swift/blocks/patient-context-card.tsx` | Patient overzicht | ✅ Geen wijzigingen |
|
||||
| **FallbackPicker** | `components/swift/blocks/fallback-picker.tsx` | Intent fallback | ✅ Geen wijzigingen |
|
||||
| **BlockContainer** | `components/swift/blocks/block-container.tsx` | Generic wrapper voor blocks | ✅ Geen wijzigingen |
|
||||
|
||||
**Total:** 8 components blijven werken zoals ze zijn (behalve OverdrachtBlock met uitbreiding in E5).
|
||||
|
||||
---
|
||||
|
||||
### 🔄 Components die WIJZIGEN
|
||||
|
||||
| Component | Locatie | v2.1 Functie | v3.0 Wijziging |
|
||||
|-----------|---------|--------------|----------------|
|
||||
| **CommandCenter** | `components/swift/command-center/command-center.tsx` | 4-zone layout (context, canvas, recent, input) | → Split-screen layout (40/60) |
|
||||
| **CommandInput** | `components/swift/command-center/command-input.tsx` | Single-line command input | → Multi-line chat input (onderaan chat panel) |
|
||||
| **CanvasArea** | `components/swift/command-center/canvas-area.tsx` | Centered block display | → Vervangen door ArtifactArea (rechts, 60%) |
|
||||
| **RecentStrip** | `components/swift/command-center/recent-strip.tsx` | Recent actions strip | → Verwijderen of integreren in chat history |
|
||||
|
||||
**Total:** 4 components wijzigen.
|
||||
|
||||
---
|
||||
|
||||
### 🆕 Nieuwe Components voor v3.0
|
||||
|
||||
Deze components moeten worden gebouwd:
|
||||
|
||||
| Component | Locatie | Functie | Epic | Story Points |
|
||||
|-----------|---------|---------|------|--------------|
|
||||
| **ChatPanel** | `components/swift/chat/chat-panel.tsx` | Scrollable message list, auto-scroll | E2 | 5 SP |
|
||||
| **ChatMessage** | `components/swift/chat/chat-message.tsx` | Message bubble (user/assistant/system/error) | E2 | 3 SP |
|
||||
| **ChatInput** | `components/swift/chat/chat-input.tsx` | Multi-line input onderaan chat | E2 | 2 SP |
|
||||
| **StreamingIndicator** | `components/swift/chat/streaming-indicator.tsx` | Pulsating dots tijdens AI response | E2 | 1 SP |
|
||||
| **ChatActionLink** | `components/swift/chat/chat-action-link.tsx` | Klikbare action links in chat | E3 | 1 SP |
|
||||
| **ArtifactArea** | `components/swift/artifacts/artifact-area.tsx` | Right-side area (60%) voor blocks | E1 | 2 SP |
|
||||
| **ArtifactContainer** | `components/swift/artifacts/artifact-container.tsx` | Wrapper met tabs (max 3 artifacts) | E4 | 5 SP |
|
||||
| **ArtifactTab** | `components/swift/artifacts/artifact-tab.tsx` | Tab component voor artifact switching | E4 | 2 SP |
|
||||
| **ArtifactPlaceholder** | `components/swift/artifacts/artifact-placeholder.tsx` | Placeholder: "Artifacts verschijnen hier" | E4 | 1 SP |
|
||||
| **LinkedEvidence** | `components/swift/shared/linked-evidence.tsx` | Bronnotitie links met hover preview | E5 | 3 SP |
|
||||
|
||||
**Total:** 10 nieuwe components, **25 Story Points**
|
||||
|
||||
---
|
||||
|
||||
### 📊 Component Architecture (v3.0)
|
||||
|
||||
```
|
||||
components/swift/
|
||||
├── command-center/
|
||||
│ ├── command-center.tsx 🔄 WIJZIGT - Split-screen layout
|
||||
│ ├── context-bar.tsx ✅ BLIJFT
|
||||
│ └── offline-banner.tsx ✅ BLIJFT
|
||||
│
|
||||
├── chat/ 🆕 NIEUW
|
||||
│ ├── chat-panel.tsx
|
||||
│ ├── chat-message.tsx
|
||||
│ ├── chat-input.tsx
|
||||
│ ├── streaming-indicator.tsx
|
||||
│ └── chat-action-link.tsx
|
||||
│
|
||||
├── artifacts/ 🆕 NIEUW
|
||||
│ ├── artifact-area.tsx
|
||||
│ ├── artifact-container.tsx
|
||||
│ ├── artifact-tab.tsx
|
||||
│ └── artifact-placeholder.tsx
|
||||
│
|
||||
├── blocks/ ✅ BLIJVEN
|
||||
│ ├── dagnotitie-block.tsx
|
||||
│ ├── zoeken-block.tsx
|
||||
│ ├── overdracht-block.tsx ⚠️ + AI-filtering (E5)
|
||||
│ ├── patient-context-card.tsx
|
||||
│ ├── fallback-picker.tsx
|
||||
│ └── block-container.tsx
|
||||
│
|
||||
└── shared/ 🆕 NIEUW (partial)
|
||||
└── linked-evidence.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🎯 Component Reuse Strategy
|
||||
|
||||
**Maximize reuse:**
|
||||
1. ✅ **Alle blocks blijven werken** — Geen refactor nodig
|
||||
2. ✅ **Context Bar blijft** — Shift colors, patient selector unchanged
|
||||
3. ✅ **Offline Banner blijft** — Error handling unchanged
|
||||
4. ✅ **Block styling blijft** — Category colors, sizes, shadows unchanged
|
||||
|
||||
**Minimize new code:**
|
||||
1. 🆕 **Chat components** — 10 nieuwe components, maar simpel (message bubbles, input)
|
||||
2. 🆕 **Artifact wrapper** — 4 components voor tab management
|
||||
3. 🔄 **Layout refactor** — Alleen CommandCenter wijzigt naar split-screen
|
||||
|
||||
**Total new code estimate:**
|
||||
- **New components:** ~600 LOC (10 components × ~60 LOC average)
|
||||
- **Modified components:** ~200 LOC (4 components × ~50 LOC changes)
|
||||
- **Total:** ~800 LOC (reasonable scope)
|
||||
|
||||
---
|
||||
|
||||
### ✅ E0.S1 & E0.S2 Complete
|
||||
|
||||
**E0.S1 - Design Tokens Audit:** ✅ Compleet
|
||||
- Alle kleuren gedocumenteerd
|
||||
- Chat message colors toegevoegd (NEW)
|
||||
- Spacing, shadows, typography verified
|
||||
- Verpleegkundig category colors verified
|
||||
- Shift colors verified
|
||||
|
||||
**E0.S2 - Component Inventory:** ✅ Compleet
|
||||
- 8 bestaande components blijven werken
|
||||
- 4 components wijzigen (layout refactor)
|
||||
- 10 nieuwe components nodig (~25 SP)
|
||||
- Architecture diagram gemaakt
|
||||
|
||||
**Next:** E0.S3 - Medical scribe system prompt
|
||||
|
||||
---
|
||||
|
||||
## Referenties
|
||||
|
||||
- `app/globals.css` — Design tokens definitie
|
||||
- `tailwind.config.ts` — Tailwind color palette
|
||||
- `lib/types/report.ts` — Category config
|
||||
- `components/swift/command-center/context-bar.tsx` — Shift config
|
||||
- `components/swift/blocks/` — Bestaande blocks
|
||||
@@ -1,518 +0,0 @@
|
||||
# E0.S3 — Swift Assistent System Prompt
|
||||
|
||||
**Datum:** 27-12-2024
|
||||
**Versie:** v1.0
|
||||
**Auteur:** AI Assistant
|
||||
**Status:** ✅ Ready for testing
|
||||
|
||||
---
|
||||
|
||||
## System Prompt v1.0
|
||||
|
||||
Dit is de eerste versie van de Swift Assistent system prompt voor `/api/swift/chat`.
|
||||
|
||||
### Volledige Prompt
|
||||
|
||||
```markdown
|
||||
Je bent Swift Assistent, een medische assistent voor Swift EPD, een Nederlands EPD-systeem voor GGZ-instellingen.
|
||||
|
||||
## Je rol
|
||||
|
||||
Je helpt zorgmedewerkers (verpleegkundigen, psychiaters, behandelaren) met documentatie en administratie tijdens hun dagelijkse werk.
|
||||
|
||||
### Kernkwaliteiten:
|
||||
- **Natuurlijk Nederlands**: Je spreekt vloeiend, informeel maar professioneel Nederlands
|
||||
- **Begrijpend**: Je begrijpt context en kan doorvragen
|
||||
- **Efficiënt**: Je helpt snel zonder onnodige uitleg
|
||||
- **Betrouwbaar**: Je maakt geen aannames, maar vraagt bij twijfel
|
||||
|
||||
### Tone of voice:
|
||||
- Vriendelijk en behulpzaam (zoals een collega)
|
||||
- Professioneel en respectvol
|
||||
- Kort en to-the-point (geen lange uitleg)
|
||||
- Empatisch voor werkdruk zorgmedewerkers
|
||||
|
||||
## Wat je DOET
|
||||
|
||||
### 1. Intents herkennen
|
||||
|
||||
Je herkent de volgende gebruikersintenties en voert acties uit:
|
||||
|
||||
**P1 Intents (kritiek, hoogfrequent):**
|
||||
|
||||
- **dagnotitie** — Verpleegkundige wil snelle notitie maken
|
||||
- Triggers: "notitie [patient]", "medicatie gegeven", "[patient] heeft...", "incident bij [patient]"
|
||||
- Entities: patient (naam), category (medicatie/adl/gedrag/incident/observatie), content (tekst)
|
||||
|
||||
- **zoeken** — Gebruiker zoekt patiënt
|
||||
- Triggers: "zoek [naam]", "wie is [naam]", "vind [naam]", "patient [naam]"
|
||||
- Entities: query (zoekterm)
|
||||
|
||||
- **patient_info** — Gebruiker wil patiënt overzicht
|
||||
- Triggers: "dossier [patient]", "info [patient]", "open [patient]", "toon [patient]"
|
||||
- Entities: patient (naam of ID)
|
||||
|
||||
- **overdracht** — Dienst overdracht maken
|
||||
- Triggers: "overdracht", "dienst overdracht", "maak overdracht", "wat moet ik weten"
|
||||
- Entities: shift (optioneel: ochtend/middag/avond/nacht)
|
||||
|
||||
**P2 Intents (belangrijk, middenfrequent):**
|
||||
|
||||
- **rapportage** — Behandelrapportage schrijven
|
||||
- Triggers: "rapportage", "gesprek gehad", "behandelgesprek", "evaluatie"
|
||||
- Entities: patient (naam), type (optioneel: gesprek/evaluatie/consult)
|
||||
|
||||
- **agenda** — Afspraken bekijken
|
||||
- Triggers: "agenda", "afspraken", "wie zie ik vandaag"
|
||||
- Entities: date (optioneel: vandaag/morgen/datum)
|
||||
|
||||
### 2. Verduidelijkingsvragen stellen
|
||||
|
||||
Als je twijfelt over de intent of belangrijke informatie mist:
|
||||
|
||||
**Vraag om verduidelijking:**
|
||||
- "Met welke patiënt had je het gesprek?" (patient ontbreekt)
|
||||
- "Wil je een notitie maken of de overdracht bekijken?" (intent onduidelijk)
|
||||
- "Bedoel je Jan de Vries of Jan Bakker?" (meerdere matches)
|
||||
|
||||
**Bevestig interpretatie:**
|
||||
- "Ik maak een dagnotitie voor Jan de Vries. Categorie: Medicatie. Klopt dat?"
|
||||
- "Je wilt de overdracht voor de ochtend. Correct?"
|
||||
|
||||
### 3. Action objects genereren
|
||||
|
||||
Wanneer je een intent herkent EN voldoende informatie hebt, genereer je een JSON action object:
|
||||
|
||||
**Format:**
|
||||
```json
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "dagnotitie",
|
||||
"entities": {
|
||||
"patient": "Jan de Vries",
|
||||
"patientId": "uuid-here",
|
||||
"category": "medicatie",
|
||||
"content": "Medicatie uitgereikt volgens schema"
|
||||
},
|
||||
"confidence": 0.95,
|
||||
"artifact": {
|
||||
"type": "DagnotatieBlock",
|
||||
"prefill": {
|
||||
"patientName": "Jan de Vries",
|
||||
"patientId": "uuid-here",
|
||||
"category": "medicatie",
|
||||
"content": "Medicatie uitgereikt volgens schema"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Confidence thresholds:**
|
||||
- `>0.9` → Open artifact direct met prefill
|
||||
- `0.7-0.9` → Open artifact + bevestigingsvraag
|
||||
- `0.5-0.7` → Verduidelijkingsvraag, geen artifact
|
||||
- `<0.5` → "Ik begrijp het niet helemaal. Kun je het anders zeggen?"
|
||||
|
||||
### 4. Follow-up conversatie
|
||||
|
||||
Gebruikers kunnen doorvragen of aanvullen:
|
||||
|
||||
**Voorbeelden:**
|
||||
```
|
||||
User: "Ik heb medicatie gegeven aan Jan"
|
||||
AI: "Ik maak een dagnotitie voor Jan de Vries. Categorie: Medicatie. Wil je nog iets toevoegen?"
|
||||
[Artifact opent: DagnotatieBlock]
|
||||
|
||||
User: "Voeg toe: hij voelt zich beter vandaag"
|
||||
AI: "Toegevoegd aan de notitie."
|
||||
[Artifact update: content += " Hij voelt zich beter vandaag"]
|
||||
|
||||
User: "Opslaan"
|
||||
AI: "Notitie opgeslagen voor Jan de Vries."
|
||||
[Artifact sluit, toast notification]
|
||||
```
|
||||
|
||||
## Wat je NIET doet
|
||||
|
||||
❌ **Geen medisch advies geven**
|
||||
- Je bent assistent voor documentatie, geen diagnostische tool
|
||||
- Bij medische vragen: "Daarvoor moet je de behandelaar raadplegen."
|
||||
|
||||
❌ **Geen aannames over patiënten**
|
||||
- Als patient naam onduidelijk is → vraag om verduidelijking
|
||||
- Als meerdere patiënten matchen → vraag welke bedoeld wordt
|
||||
|
||||
❌ **Geen lange uitleg**
|
||||
- Houd antwoorden kort en to-the-point
|
||||
- Geen "Ik ga nu..." of "Laat me even kijken..." (doe gewoon)
|
||||
|
||||
❌ **Geen ongevraagde acties**
|
||||
- Maak geen notities zonder expliciete opdracht
|
||||
- Geen notities automatisch opslaan (gebruiker beslist)
|
||||
|
||||
## Context die beschikbaar is
|
||||
|
||||
Je hebt toegang tot de volgende context:
|
||||
|
||||
```json
|
||||
{
|
||||
"activePatient": {
|
||||
"id": "uuid",
|
||||
"name": "Jan de Vries",
|
||||
"age": 59,
|
||||
"room": "12B"
|
||||
},
|
||||
"shift": "ochtend",
|
||||
"user": {
|
||||
"role": "verpleegkundige",
|
||||
"name": "Sarah Verhoeven"
|
||||
},
|
||||
"recentActions": [
|
||||
{
|
||||
"type": "dagnotitie",
|
||||
"patient": "Jan de Vries",
|
||||
"timestamp": "2024-12-27T09:15:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Gebruik context slim:**
|
||||
- Als `activePatient` bekend is, gebruik die als default
|
||||
- Bij "maak notitie" zonder patient naam → gebruik activePatient
|
||||
- Bij "wat heb ik net gedaan" → verwijs naar recentActions
|
||||
|
||||
## Voorbeelden (Request → Response)
|
||||
|
||||
### Voorbeeld 1: Dagnotitie (Happy Path)
|
||||
|
||||
**User:**
|
||||
```
|
||||
Medicatie gegeven aan Jan
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Ik maak een dagnotitie voor Jan de Vries. Categorie: Medicatie. Wil je nog iets toevoegen?
|
||||
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "dagnotitie",
|
||||
"entities": {
|
||||
"patient": "Jan de Vries",
|
||||
"patientId": "abc-123",
|
||||
"category": "medicatie",
|
||||
"content": "Medicatie gegeven"
|
||||
},
|
||||
"confidence": 0.95,
|
||||
"artifact": {
|
||||
"type": "DagnotatieBlock",
|
||||
"prefill": {
|
||||
"patientName": "Jan de Vries",
|
||||
"patientId": "abc-123",
|
||||
"category": "medicatie",
|
||||
"content": "Medicatie gegeven"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Voorbeeld 2: Verduidelijkingsvraag
|
||||
|
||||
**User:**
|
||||
```
|
||||
Ik heb een gesprek gehad
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Met welke patiënt had je het gesprek?
|
||||
```
|
||||
|
||||
**User:**
|
||||
```
|
||||
Jan de Vries
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Ik maak een rapportage voor Jan de Vries. Wat wil je vastleggen?
|
||||
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "rapportage",
|
||||
"entities": {
|
||||
"patient": "Jan de Vries",
|
||||
"patientId": "abc-123"
|
||||
},
|
||||
"confidence": 0.85,
|
||||
"artifact": {
|
||||
"type": "RapportageBlock",
|
||||
"prefill": {
|
||||
"patientName": "Jan de Vries",
|
||||
"patientId": "abc-123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Voorbeeld 3: Patiënt zoeken
|
||||
|
||||
**User:**
|
||||
```
|
||||
Wie is Marie van den Berg?
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Ik zoek Marie van den Berg voor je.
|
||||
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "zoeken",
|
||||
"entities": {
|
||||
"query": "Marie van den Berg"
|
||||
},
|
||||
"confidence": 0.98,
|
||||
"artifact": {
|
||||
"type": "ZoekenBlock",
|
||||
"prefill": {
|
||||
"query": "Marie van den Berg"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Voorbeeld 4: Overdracht
|
||||
|
||||
**User:**
|
||||
```
|
||||
Maak overdracht voor deze dienst
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Ik maak de overdracht voor de ochtenddienst (07:00-15:00).
|
||||
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "overdracht",
|
||||
"entities": {
|
||||
"shift": "ochtend"
|
||||
},
|
||||
"confidence": 0.92,
|
||||
"artifact": {
|
||||
"type": "OverdrachtBlock",
|
||||
"prefill": {
|
||||
"shift": "ochtend"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Voorbeeld 5: Follow-up (context behouden)
|
||||
|
||||
**User:**
|
||||
```
|
||||
Notitie voor Jan: bloeddruk gemeten
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Ik maak een dagnotitie voor Jan de Vries. Categorie: Observatie.
|
||||
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "dagnotitie",
|
||||
"entities": {
|
||||
"patient": "Jan de Vries",
|
||||
"patientId": "abc-123",
|
||||
"category": "observatie",
|
||||
"content": "Bloeddruk gemeten"
|
||||
},
|
||||
"confidence": 0.9,
|
||||
"artifact": {
|
||||
"type": "DagnotatieBlock",
|
||||
"prefill": {
|
||||
"patientName": "Jan de Vries",
|
||||
"patientId": "abc-123",
|
||||
"category": "observatie",
|
||||
"content": "Bloeddruk gemeten"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**User:**
|
||||
```
|
||||
Voeg toe: 135 over 85
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Toegevoegd aan de notitie.
|
||||
|
||||
{
|
||||
"type": "update",
|
||||
"target": "activeArtifact",
|
||||
"update": {
|
||||
"content": "Bloeddruk gemeten: 135 over 85"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Voorbeeld 6: Onduidelijke intent (laag confidence)
|
||||
|
||||
**User:**
|
||||
```
|
||||
Jan
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Wat wil je doen met Jan de Vries? Je kunt bijvoorbeeld:
|
||||
- Een notitie maken
|
||||
- Het dossier openen
|
||||
- Een rapportage schrijven
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Voorbeeld 7: Meerdere patiënten match
|
||||
|
||||
**User:**
|
||||
```
|
||||
Zoek Jan
|
||||
```
|
||||
|
||||
**AI Response (als er meerdere Jannen zijn):**
|
||||
```
|
||||
Ik vind meerdere patiënten met de naam Jan:
|
||||
- Jan de Vries (59 jr, kamer 12B)
|
||||
- Jan Bakker (45 jr, kamer 8A)
|
||||
- Jantine Smit (33 jr, kamer 3C)
|
||||
|
||||
Wie bedoel je?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Intent Mapping Table
|
||||
|
||||
| User Input (NL) | Intent | Confidence | Artifact |
|
||||
|-----------------|--------|------------|----------|
|
||||
| "Medicatie gegeven aan Jan" | dagnotitie | 0.95 | DagnotatieBlock |
|
||||
| "Notitie jan medicatie" | dagnotitie | 0.92 | DagnotatieBlock |
|
||||
| "Incident bij Marie" | dagnotitie | 0.90 | DagnotatieBlock (category: incident) |
|
||||
| "Zoek Jan de Vries" | zoeken | 0.98 | ZoekenBlock |
|
||||
| "Wie is Marie" | zoeken | 0.95 | ZoekenBlock |
|
||||
| "Info Jan" | patient_info | 0.90 | PatientContextCard |
|
||||
| "Dossier Jan" | patient_info | 0.92 | PatientContextCard |
|
||||
| "Overdracht maken" | overdracht | 0.95 | OverdrachtBlock |
|
||||
| "Dienst overdracht" | overdracht | 0.93 | OverdrachtBlock |
|
||||
| "Rapportage Jan" | rapportage | 0.88 | RapportageBlock |
|
||||
| "Gesprek gehad met Jan" | rapportage | 0.85 | RapportageBlock |
|
||||
| "Agenda vandaag" | agenda | 0.95 | AgendaBlock |
|
||||
| "Wie zie ik vandaag" | agenda | 0.90 | AgendaBlock |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Onbekende intent
|
||||
|
||||
**User:**
|
||||
```
|
||||
Blablabla random tekst
|
||||
```
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Ik begrijp niet helemaal wat je wilt doen. Typ "help" voor voorbeelden, of probeer:
|
||||
- "Notitie maken voor [patient]"
|
||||
- "Zoek [patient]"
|
||||
- "Maak overdracht"
|
||||
```
|
||||
|
||||
### Geen patient gevonden
|
||||
|
||||
**User:**
|
||||
```
|
||||
Notitie voor Henk
|
||||
```
|
||||
|
||||
**AI Response (als Henk niet bestaat):**
|
||||
```
|
||||
Ik kan geen patiënt vinden met de naam Henk. Wil je:
|
||||
- De naam anders spellen
|
||||
- Een andere patiënt zoeken
|
||||
```
|
||||
|
||||
### API error tijdens artifact opening
|
||||
|
||||
**AI Response:**
|
||||
```
|
||||
Er ging iets mis bij het openen van de notitie. Probeer het opnieuw, of neem contact op met de helpdesk als het probleem blijft.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt Engineering Notes
|
||||
|
||||
### Strengths v1.0:
|
||||
✅ Duidelijke role definition
|
||||
✅ Concrete intent examples
|
||||
✅ JSON action format gedefinieerd
|
||||
✅ Confidence thresholds
|
||||
✅ Error handling voorbeelden
|
||||
✅ Nederlandse tone of voice
|
||||
|
||||
### Areas for improvement (v2.0):
|
||||
⚠️ Category inference (medicatie, adl, gedrag, incident, observatie) kan beter
|
||||
⚠️ Multi-turn conversation tracking (context window management)
|
||||
⚠️ Edge cases: typos, dialect, afkortingen
|
||||
⚠️ Ambiguity resolution (bijv. "Jan" als er 3 Jannen zijn)
|
||||
|
||||
### Testing checklist:
|
||||
- [ ] Happy path: dagnotitie maken
|
||||
- [ ] Happy path: patient zoeken
|
||||
- [ ] Happy path: overdracht maken
|
||||
- [ ] Verduidelijkingsvraag bij onduidelijke intent
|
||||
- [ ] Meerdere patiënten met zelfde naam
|
||||
- [ ] Follow-up conversatie (context behouden)
|
||||
- [ ] Error handling (unknown intent, patient not found)
|
||||
- [ ] Confidence thresholds (>0.9, 0.7-0.9, <0.5)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (E3.S3)
|
||||
|
||||
1. Implementeer prompt in `/app/api/swift/chat/route.ts`
|
||||
2. Test met Claude API (Sonnet 4.5)
|
||||
3. Iterate op basis van test results
|
||||
4. Document prompt versioning (v1.0, v1.1, v2.0, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Versiehistorie
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| v1.0 | 27-12-2024 | Initial prompt - Dutch Swift Assistent, intents, examples |
|
||||
|
||||
---
|
||||
|
||||
## ✅ E0.S3 Complete
|
||||
|
||||
**Status:** ✅ System prompt v1.0 klaar voor implementatie en testing
|
||||
**Next Epic:** E1 - Foundation (Split-screen layout)
|
||||
@@ -1,581 +0,0 @@
|
||||
# 🧩 Functioneel Ontwerp (FO) – Swift Agenda & Afspraken
|
||||
|
||||
**Projectnaam:** Swift - Agenda & Afspraken Module
|
||||
**Versie:** v1.0
|
||||
**Datum:** 27-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en relatie met het PRD
|
||||
|
||||
🎯 **Doel van dit document:**
|
||||
Dit Functioneel Ontwerp beschrijft **hoe** de agenda- en afsprakenfunctionaliteit binnen Swift werkt. Swift is een conversational medical scribe interface waarin gebruikers via natuurlijke taal (Nederlands) afspraken kunnen opvragen, aanmaken, wijzigen en annuleren. Dit FO beschrijft de gebruikerservaring, UI-interacties en AI-functionaliteit.
|
||||
|
||||
📘 **Relatie met andere documenten:**
|
||||
- **PRD:** Ephemeral UI visie (`nextgen-epd-prd-ephemeral-ui-epd.md`) - Conversational interface voor EPD
|
||||
- **Swift FO v3.0:** `fo-swift-medical-scribe-v3.md` - Basis conversational interface architectuur
|
||||
- **Klassieke Agenda:** `/app/epd/agenda` - Bestaande visuele kalender (blijft bestaan voor complexe planning)
|
||||
- **Bouwplan:** `bouwplan-swift-standalone-module.md` - Development roadmap
|
||||
|
||||
**Kernprincipe:**
|
||||
> Gebruikers kunnen via natuurlijke taal (chat of spraak) snel afspraken beheren zonder door menu's te klikken. Voor visueel overzicht en complexe planning blijft de klassieke kalender beschikbaar. Swift is de **snelle, hands-free** interface; klassieke agenda is de **visuele planner**.
|
||||
|
||||
**Toegevoegde waarde:**
|
||||
|
||||
| Aspect | Klassieke Agenda | Swift Agenda |
|
||||
|--------|------------------|--------------|
|
||||
| **Gebruik** | Visuele weekplanning | Quick actions, queries |
|
||||
| **Input** | Klikken, formulieren | Natuurlijke taal, spraak |
|
||||
| **Snelheid** | ~30-60 sec voor nieuwe afspraak | ~10-15 sec via chat/voice |
|
||||
| **Ideaal voor** | Weekplanning, drag-drop | Tijdens telefoongesprek, hands-free |
|
||||
|
||||
---
|
||||
|
||||
## 2. Overzicht van de belangrijkste onderdelen
|
||||
|
||||
🎯 **Doel:** Overzicht van de functionaliteit binnen de Swift Agenda module.
|
||||
|
||||
### Hoofdonderdelen
|
||||
|
||||
1. **Agenda Queries** - Afspraken opvragen ("afspraken vandaag", "wat is volgende afspraak")
|
||||
2. **Quick Create** - Snel afspraak maken ("maak afspraak jan morgen 14:00")
|
||||
3. **Cancel Flow** - Afspraak annuleren ("annuleer afspraak jan")
|
||||
4. **Reschedule Flow** - Afspraak verzetten ("verzet 14:00 naar 15:00")
|
||||
5. **AgendaBlock** - UI component toont afspraken lijst en formulieren
|
||||
6. **Intent Detection** - AI herkent wat gebruiker wil doen
|
||||
|
||||
### Artifact: AgendaBlock
|
||||
|
||||
Het **AgendaBlock** is het centrale UI-component met 4 modes:
|
||||
|
||||
| Mode | Functie | Trigger |
|
||||
|------|---------|---------|
|
||||
| **List View** | Toont chronologische lijst afspraken | "afspraken vandaag" |
|
||||
| **Create Form** | Formulier voor nieuwe afspraak | "maak afspraak jan" |
|
||||
| **Cancel View** | Confirmation dialog | "annuleer afspraak" |
|
||||
| **Reschedule Form** | Datum/tijd aanpassing | "verzet afspraak" |
|
||||
|
||||
---
|
||||
|
||||
## 3. User Stories
|
||||
|
||||
🎯 **Doel:** Beschrijven wat gebruikers moeten kunnen doen vanuit hun perspectief.
|
||||
|
||||
### Primaire User Stories (P1)
|
||||
|
||||
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|
||||
|----|-----|--------------|------------------|------|
|
||||
| **US-24** | Verpleegkundige | Snel overzicht afspraken vandaag | "afspraken vandaag" → lijst in AgendaBlock, <3 sec | 🔴 P1 |
|
||||
| **US-25** | Verpleegkundige | Check volgende afspraak tijdens werk | "wat is mijn volgende afspraak?" → directe info | 🔴 P1 |
|
||||
| **US-27** | Verpleegkundige | Snelle afspraak tijdens telefoongesprek | "maak afspraak jan morgen 14:00" → prefilled form, <15 sec | 🔴 P1 |
|
||||
| **US-28** | Verpleegkundige | Context-aware planning | "maak afspraak met deze patiënt" → gebruikt actieve patiënt | 🔴 P1 |
|
||||
| **US-29** | Verpleegkundige | Voice input tijdens consult | Hands-free afspraak maken via spraak | 🔴 P1 |
|
||||
| **US-30** | Verpleegkundige | Annuleren via chat | "annuleer afspraak jan" → confirmation → done | 🔴 P1 |
|
||||
| **US-31** | Verpleegkundige | Snel verzetten | "verzet 14:00 naar 15:00" → tijd update | 🔴 P1 |
|
||||
|
||||
### Secundaire User Stories (P2)
|
||||
|
||||
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|
||||
|----|-----|--------------|------------------|------|
|
||||
| **US-26** | Verpleegkundige | Weekoverzicht bekijken | "agenda deze week" → gefilterde lijst | 🟡 P2 |
|
||||
| **US-32** | Verpleegkundige | Disambiguation bij meerdere matches | Systeem vraagt "Welke Jan?" → lijst opties | 🟡 P2 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Functionele werking per onderdeel
|
||||
|
||||
🎯 **Doel:** Per hoofdonderdeel beschrijven wat de gebruiker kan doen en wat het systeem doet.
|
||||
|
||||
### 4.1 Agenda Query (Afspraken opvragen)
|
||||
|
||||
**Wat doet de gebruiker:**
|
||||
- Typt of spreekt: "afspraken vandaag", "wat is mijn volgende afspraak", "agenda morgen"
|
||||
|
||||
**Wat doet het systeem:**
|
||||
1. **Intent detection:** Herkent dat gebruiker afspraken wil opvragen
|
||||
2. **Datum parsing:** Vertaalt "vandaag", "morgen", "deze week" naar datumbereik
|
||||
3. **Data ophalen:** Haalt afspraken op uit database
|
||||
4. **AI response:** Chat toont samenvatting: "Je hebt vandaag 3 afspraken..."
|
||||
5. **AgendaBlock opent:** Rechts verschijnt lijst met afspraken
|
||||
|
||||
**AgendaBlock List View bevat:**
|
||||
- Header met datumbereik ("Afspraken Vandaag - 27 december")
|
||||
- Per afspraak: tijd, patiënt (klikbaar), type badge, locatie
|
||||
- Actions per afspraak: [Details] [Annuleren]
|
||||
- Footer: Link naar volledige klassieke agenda
|
||||
|
||||
**States:**
|
||||
- **Loading:** Spinner tijdens data fetch
|
||||
- **Lijst met afspraken:** Chronologisch geordend
|
||||
- **Empty state:** "Geen afspraken gevonden voor [datum]" + knop "Maak nieuwe afspraak"
|
||||
- **Error:** "Fout bij ophalen afspraken" + link naar klassieke agenda
|
||||
|
||||
**Voorbeeld interactie:**
|
||||
```
|
||||
User: "afspraken vandaag"
|
||||
↓
|
||||
AI: "Je hebt vandaag 3 afspraken:
|
||||
- 09:00 Intake Jan de Vries
|
||||
- 11:30 Behandeling Marie Jansen
|
||||
- 14:00 Vervolggesprek Piet Bakker"
|
||||
↓
|
||||
[AgendaBlock opens rechts met lijst van 3 afspraken]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Quick Create (Afspraak maken)
|
||||
|
||||
**Wat doet de gebruiker:**
|
||||
- Typt: "maak afspraak jan morgen 14:00"
|
||||
- Of spreekt via voice input (spatie-knop)
|
||||
|
||||
**Wat doet het systeem:**
|
||||
1. **Intent detection:** Herkent 'create_appointment' intent
|
||||
2. **Entity extraction:**
|
||||
- Patient: "jan" (fuzzy search in database)
|
||||
- Datum: "morgen" → parses naar 28-12-2024
|
||||
- Tijd: "14:00"
|
||||
- Type: default "behandeling" (kan gespecificeerd worden: "maak intake...")
|
||||
3. **AI response:** "Ik maak een afspraak voor Jan de Vries op 28 december om 14:00."
|
||||
4. **AgendaBlock opent:** Create form met pre-filled velden
|
||||
5. **Gebruiker bevestigt of past aan**
|
||||
6. **Opslaan:** Server action → database → toast "Afspraak aangemaakt!"
|
||||
|
||||
**AgendaBlock Create Form bevat:**
|
||||
- **Patiënt:** Autocomplete dropdown (pre-filled "Jan de Vries")
|
||||
- **Datum:** Date picker (pre-filled: 28-12-2024)
|
||||
- **Tijd:** Time picker (pre-filled: 14:00)
|
||||
- **Type:** Radio buttons (Intake, Behandeling, Vervolg, Telefonisch, Crisis, etc.)
|
||||
- **Locatie:** Radio buttons (Praktijk, Online, Thuis)
|
||||
- **Notities:** Optionele textarea
|
||||
- **Conflict warning:** "⚠️ Je hebt al een afspraak om 14:00 met Marie" (indien van toepassing)
|
||||
- **Actions:** [Annuleren] [✓ Afspraak maken]
|
||||
|
||||
**Form validatie:**
|
||||
- Patiënt is verplicht
|
||||
- Datum kan niet in het verleden
|
||||
- Tijd moet binnen 07:00-20:00
|
||||
|
||||
**Voorbeeld interactie (voice):**
|
||||
```
|
||||
User: [Drukt spatie] "maak intake met Jan de Vries morgen 14:00"
|
||||
↓
|
||||
[Deepgram transcribeert live]
|
||||
↓
|
||||
AI: "Ik maak een intake-afspraak voor Jan de Vries op 28 december om 14:00."
|
||||
↓
|
||||
[AgendaBlock create form opent met prefill]
|
||||
↓
|
||||
User: [Klikt "Afspraak maken"]
|
||||
↓
|
||||
Toast: "✓ Afspraak aangemaakt!"
|
||||
Chat: "Afspraak ingepland voor Jan de Vries op 28 december om 14:00."
|
||||
```
|
||||
|
||||
**Edge cases:**
|
||||
- **Patiënt niet gevonden:** "Ik vond geen patiënt met de naam 'jan'. Bedoel je Jan de Vries of Jan Bakker?" (disambiguation)
|
||||
- **Meerdere Jan's:** Toont lijst met opties in AgendaBlock
|
||||
- **Tijd onduidelijk:** "Hoe laat wil je de afspraak plannen?"
|
||||
- **Incomplete info:** "maak afspraak" → vraagt eerst om patiënt, dan datum/tijd
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Cancel Flow (Afspraak annuleren)
|
||||
|
||||
**Wat doet de gebruiker:**
|
||||
- Typt: "annuleer afspraak jan" of "annuleer de 14:00 afspraak"
|
||||
|
||||
**Wat doet het systeem:**
|
||||
1. **Intent detection:** Herkent 'cancel_appointment'
|
||||
2. **Search matching appointments:**
|
||||
- Op patiëntnaam: zoekt "jan"
|
||||
- Op tijd: zoekt afspraak om 14:00 vandaag
|
||||
3. **Disambiguation (indien meerdere):**
|
||||
- Toont lijst van matching afspraken in AgendaBlock
|
||||
- Gebruiker selecteert welke
|
||||
4. **Confirmation dialog:**
|
||||
- Toont details van geselecteerde afspraak
|
||||
- Waarschuwing: "Deze actie kan niet ongedaan worden gemaakt"
|
||||
5. **Bevestigen:** Status → 'cancelled', toast + chat confirmation
|
||||
|
||||
**AgendaBlock Cancel View:**
|
||||
|
||||
**Single Match:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ❌ Afspraak Annuleren [×] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Wil je deze afspraak annuleren? │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ 28-12-2024 14:00 - 15:00 │ │
|
||||
│ │ Jan de Vries - Intake │ │
|
||||
│ │ Praktijk │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ⚠️ Actie kan niet ongedaan gemaakt │
|
||||
│ │
|
||||
├─────────────────────────────────────────┤
|
||||
│ [Terug] [✓ Annuleren] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Multiple Matches (Disambiguation):**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ❌ Afspraak Annuleren [×] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Welke afspraak wil je annuleren? │
|
||||
│ │
|
||||
│ ○ 28-12 09:00 - Jan de Vries (Intake) │
|
||||
│ ○ 28-12 14:00 - Jan de Vries (Vervolg) │
|
||||
│ ○ 03-01 11:00 - Jan de Vries (Behndl) │
|
||||
│ │
|
||||
├─────────────────────────────────────────┤
|
||||
│ [Annuleren] [Volgende →] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Voorbeeld interactie:**
|
||||
```
|
||||
User: "annuleer afspraak jan"
|
||||
↓
|
||||
[Systeem vindt 3 afspraken met "jan"]
|
||||
↓
|
||||
AI: "Je hebt 3 afspraken met Jan. Welke wil je annuleren?"
|
||||
↓
|
||||
[AgendaBlock toont disambiguation list]
|
||||
↓
|
||||
User: [Selecteert 14:00 afspraak]
|
||||
↓
|
||||
[Confirmation dialog]
|
||||
↓
|
||||
User: [Klikt "Annuleren"]
|
||||
↓
|
||||
Toast: "Afspraak geannuleerd"
|
||||
Chat: "Afspraak met Jan de Vries op 28 december om 14:00 is geannuleerd."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Reschedule Flow (Afspraak verzetten)
|
||||
|
||||
**Wat doet de gebruiker:**
|
||||
- Typt: "verzet 14:00 naar 15:00" of "verzet jan naar dinsdag"
|
||||
|
||||
**Wat doet het systeem:**
|
||||
1. **Intent detection:** Herkent 'reschedule_appointment'
|
||||
2. **Parse old & new time:**
|
||||
- Oude afspraak: "14:00" vandaag
|
||||
- Nieuwe tijd: "15:00"
|
||||
3. **Find appointment:** Zoekt matching afspraak
|
||||
4. **AgendaBlock opent:** Edit form
|
||||
5. **Conflict check:** Controleert of nieuwe tijd vrij is
|
||||
6. **Bevestigen:** Update afspraak → toast + chat
|
||||
|
||||
**AgendaBlock Reschedule Form:**
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 🔄 Afspraak Verzetten [×] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Afspraak │
|
||||
│ Jan de Vries - Intake │
|
||||
│ │
|
||||
│ Huidige tijd │
|
||||
│ 28-12-2024 14:00 - 15:00 │
|
||||
│ (strikethrough) │
|
||||
│ │
|
||||
│ Nieuwe datum/tijd * │
|
||||
│ [28-12-2024 ▼] [15:00 ▼] │
|
||||
│ │
|
||||
│ ✅ Geen conflicten gevonden │
|
||||
│ │
|
||||
├─────────────────────────────────────────┤
|
||||
│ [Annuleren] [✓ Verzetten] │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Voorbeeld interactie:**
|
||||
```
|
||||
User: "verzet de 14:00 naar 15:00"
|
||||
↓
|
||||
AI: "Ik verzet je afspraak van 14:00 met Jan naar 15:00."
|
||||
↓
|
||||
[AgendaBlock reschedule form opent]
|
||||
↓
|
||||
User: [Bevestigt of past aan]
|
||||
↓
|
||||
User: [Klikt "Verzetten"]
|
||||
↓
|
||||
Toast: "Afspraak verzet naar 15:00"
|
||||
Chat: "Afspraak verzet naar 15:00."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.5 AgendaBlock States & Lifecycle
|
||||
|
||||
**Artifact Lifecycle:**
|
||||
1. **Closed (default):** Geen artifact zichtbaar
|
||||
2. **Opening:** Slide-in animation (200ms from right)
|
||||
3. **Active:** Gebruiker kan interacteren
|
||||
4. **Submitting:** Form disabled, spinner op submit button
|
||||
5. **Success:** Toast + chat confirmation → artifact sluit (of blijft voor volgende)
|
||||
6. **Error:** Error message in artifact, re-enable form
|
||||
|
||||
**Max artifacts:** 3 tegelijk (tabs bovenaan bij meerdere)
|
||||
- Bij 4e artifact: oudste sluit automatisch
|
||||
|
||||
**Keyboard shortcuts:**
|
||||
- `⌘K` / `Ctrl+K` - Focus chat input
|
||||
- `Escape` - Sluit artifact
|
||||
- `Enter` - Submit form (in form fields)
|
||||
|
||||
---
|
||||
|
||||
## 5. UI-overzicht (visuele structuur)
|
||||
|
||||
🎯 **Doel:** Inzicht geven in de globale schermopbouw.
|
||||
|
||||
### Split-Screen Layout (Command Center)
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ Context Bar: 🕐 Ochtend | 8 ptn Jan de Vries ▼ 👤 SV │
|
||||
├─────────────────────────────┬─────────────────────────────────┤
|
||||
│ │ │
|
||||
│ CHAT PANEL (40%) │ ARTIFACT AREA (60%) │
|
||||
│ │ │
|
||||
│ 👤 "afspraken vandaag" │ ┌───────────────────────────┐ │
|
||||
│ │ │ 📅 Afspraken Vandaag │ │
|
||||
│ 🤖 Je hebt vandaag 3 │ │ │ │
|
||||
│ afspraken: │ │ 09:00 - Intake │ │
|
||||
│ - 09:00 Intake Jan │ │ Jan de Vries │ │
|
||||
│ - 11:30 Behandeling │ │ 📍 Praktijk │ │
|
||||
│ - 14:00 Vervolg │ │ [Details] [Annuleren] │ │
|
||||
│ │ │ │ │
|
||||
│ 👤 "maak afspraak jan │ │ 11:30 - Behandeling │ │
|
||||
│ morgen 14:00" │ │ Marie Jansen │ │
|
||||
│ │ │ 🌐 Online │ │
|
||||
│ 🤖 Ik maak een afspraak │ │ [Details] [Annuleren] │ │
|
||||
│ voor Jan de Vries... │ │ │ │
|
||||
│ │ │ 14:00 - Vervolg │ │
|
||||
│ [AgendaBlock opent →] │ │ Piet Bakker │ │
|
||||
│ │ │ 📍 Praktijk │ │
|
||||
│ │ │ [Details] [Annuleren] │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ [📅 Open volledige │ │
|
||||
│ │ │ agenda →] │ │
|
||||
│ │ └───────────────────────────┘ │
|
||||
│ │ │
|
||||
├─────────────────────────────┤ │
|
||||
│ 💬 Typ of spreek... 🎤 │ │
|
||||
└─────────────────────────────┴─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### AgendaBlock Modes (UI varianten)
|
||||
|
||||
**Mode 1: List View**
|
||||
- Header: Datum range + close button
|
||||
- Body: Scrollable lijst van appointment cards
|
||||
- Footer: Link naar klassieke agenda
|
||||
|
||||
**Mode 2: Create Form**
|
||||
- Header: "Nieuwe Afspraak" + close button
|
||||
- Body: Form velden (patient, datum, tijd, type, locatie, notities)
|
||||
- Footer: [Annuleren] [✓ Afspraak maken]
|
||||
|
||||
**Mode 3: Cancel View**
|
||||
- Header: "Afspraak Annuleren" + close button
|
||||
- Body: Appointment details + warning message
|
||||
- Footer: [Terug] [✓ Annuleren]
|
||||
|
||||
**Mode 4: Reschedule Form**
|
||||
- Header: "Afspraak Verzetten" + close button
|
||||
- Body: Huidige tijd (readonly) + nieuwe tijd (editable)
|
||||
- Footer: [Annuleren] [✓ Verzetten]
|
||||
|
||||
### Design Tokens
|
||||
|
||||
**Colors:**
|
||||
- Primary: Teal-700 (#0F766E)
|
||||
- User message: Amber-50 bg, amber-200 border
|
||||
- AI message: Slate-100 bg, slate-300 border
|
||||
- Appointment types: Blauw (intake), groen (behandeling), rood (crisis)
|
||||
|
||||
**Spacing:**
|
||||
- Context bar: h-12 (48px)
|
||||
- Chat/artifact gap: 16px
|
||||
- Card spacing: space-y-4
|
||||
|
||||
**Typography:**
|
||||
- Chat messages: text-sm
|
||||
- Headers: text-base font-medium
|
||||
|
||||
---
|
||||
|
||||
## 6. Interacties met AI (functionele beschrijving)
|
||||
|
||||
🎯 **Doel:** Uitleggen waar AI in de flow voorkomt en wat de gebruiker ziet.
|
||||
|
||||
### AI-functies
|
||||
|
||||
| Locatie | AI-actie | Trigger | Input | Output |
|
||||
|---------|----------|---------|-------|--------|
|
||||
| **Chat Input** | Intent detection | User message | "afspraken vandaag" | Intent: 'agenda_query', confidence: 1.0 |
|
||||
| **Chat Input** | Entity extraction | User message | "maak afspraak jan morgen 14:00" | Patient: "jan", date: tomorrow, time: "14:00" |
|
||||
| **Chat Input** | Verduidelijkingsvraag | Incomplete info | "maak afspraak" | "Met welke patiënt wil je afspreken?" |
|
||||
| **Chat Panel** | Streaming response | Intent detected | — | "Je hebt vandaag 3 afspraken..." (typed effect) |
|
||||
| **Patient Search** | Fuzzy matching | "jan" input | Database query | Matches: "Jan de Vries", "Jan Bakker" |
|
||||
| **Date Parser** | Natural language parsing | "morgen", "volgende week dinsdag" | Date string | ISO date: 2024-12-28 |
|
||||
|
||||
### AI Intent Detection (Two-Tier)
|
||||
|
||||
**Tier 1: Local Pattern Matching (<50ms)**
|
||||
- Fast regex-based matching
|
||||
- Client-side execution
|
||||
- Confidence >= 0.8 → direct gebruiken
|
||||
|
||||
Voorbeelden:
|
||||
- "afspraken vandaag" → Pattern: `/^afspraken?\b/i` → Match! (confidence: 1.0)
|
||||
- "maak afspraak" → Pattern: `/^maak\s+afspraak/i` → Match! (confidence: 1.0)
|
||||
|
||||
**Tier 2: AI Fallback (Claude Haiku) (~400ms)**
|
||||
- Voor onduidelijke/complexe input
|
||||
- Server-side execution
|
||||
- Triggered als local confidence <0.8
|
||||
|
||||
Voorbeelden:
|
||||
- "ik wil graag een gesprek plannen" → AI: intent: 'create_appointment', confidence: 0.75
|
||||
- "verzet hem naar volgende week" → AI: intent: 'reschedule', confidence: 0.7 (patient onduidelijk)
|
||||
|
||||
**Confidence Thresholds:**
|
||||
|
||||
| Confidence | Actie | Voorbeeld |
|
||||
|------------|-------|-----------|
|
||||
| **>0.9** | Direct artifact openen | "afspraken vandaag" |
|
||||
| **0.7-0.9** | Artifact + bevestigingsvraag | "maak afspraak jan" (tijd ontbreekt) |
|
||||
| **0.5-0.7** | Verduidelijkingsvraag in chat | "maak afspraak" |
|
||||
| **<0.5** | Fallback: "Ik begrijp het niet" | Gibberish input |
|
||||
|
||||
### Voice Input (Deepgram)
|
||||
|
||||
**Functionaliteit:**
|
||||
- Live transcription tijdens spreken
|
||||
- Pause detection (1.5s stilte) → auto-submit
|
||||
- Nederlands language model
|
||||
|
||||
**User experience:**
|
||||
1. User drukt spatie (of klikt mic icon)
|
||||
2. Mic wordt rood 🔴, waveform animatie
|
||||
3. Live transcript verschijnt in input field
|
||||
4. Na 1.5s stilte: auto-submit
|
||||
5. Intent detection + artifact opening
|
||||
|
||||
**Voorbeeld:**
|
||||
```
|
||||
User: [Drukt spatie]
|
||||
→ Mic: 🔴 LIVE
|
||||
→ User spreekt: "maak afspraak met jan morgen om twee uur"
|
||||
→ Transcript: "maak afspraak met jan morgen om twee uur"
|
||||
→ [1.5s pause]
|
||||
→ Auto-submit
|
||||
→ AI parses: "twee uur" → "14:00"
|
||||
→ AgendaBlock opent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Gebruikersrollen en rechten
|
||||
|
||||
🎯 **Doel:** Beschrijven welke rollen toegang hebben tot agenda functionaliteit.
|
||||
|
||||
| Rol | Toegang | Beperkingen |
|
||||
|-----|---------|-------------|
|
||||
| **Verpleegkundige** | Eigen afspraken maken/wijzigen/annuleren | Alleen eigen practitioner_id |
|
||||
| **Behandelaar** | Eigen afspraken + caseload patiënten | Alleen eigen + team afspraken |
|
||||
| **Manager** | Lezen alle afspraken | Geen create/update/delete |
|
||||
| **Demo-user** | Volledige functionaliteit met fictieve data | Alleen lezen |
|
||||
|
||||
**Permissies:**
|
||||
|
||||
| Actie | Verpleegkundige | Behandelaar | Manager |
|
||||
|-------|-----------------|-------------|---------|
|
||||
| **Agenda query** (eigen) | ✅ | ✅ | ✅ |
|
||||
| **Agenda query** (team) | ❌ | ✅ | ✅ |
|
||||
| **Create appointment** | ✅ | ✅ | ❌ |
|
||||
| **Cancel appointment** (eigen) | ✅ | ✅ | ❌ |
|
||||
| **Reschedule** (eigen) | ✅ | ✅ | ❌ |
|
||||
|
||||
**Database-level (RLS):**
|
||||
- Filter op `practitioner_id = current_user_id`
|
||||
- Voor managers: read-only view
|
||||
|
||||
---
|
||||
|
||||
## 8. Bijlagen & Referenties
|
||||
|
||||
🎯 **Doel:** Linken naar overige documenten.
|
||||
|
||||
### Gerelateerde Documenten
|
||||
|
||||
**Swift Documentatie:**
|
||||
- **Swift FO v3.0:** `docs/swift/fo-swift-medical-scribe-v3.md` - Basis conversational interface
|
||||
- **Swift Bouwplan:** `docs/swift/bouwplan-swift-standalone-module.md` - Development roadmap
|
||||
- **Developer Guide Intent System:** `docs/swift/developer-guide-intent-system.md` - Technische details intent detection
|
||||
- **Scalability Architecture:** `docs/swift/architecture-intent-scalability.md` - Schaalbaarheid optimalisaties
|
||||
|
||||
**Agenda Implementatie:**
|
||||
- **Klassieke Agenda:** `/app/epd/agenda` - Bestaande visuele kalender
|
||||
- **Agenda Actions:** `/app/epd/agenda/actions.ts` - Server actions (wordt hergebruikt)
|
||||
- **Encounters Schema:** Database schema voor afspraken
|
||||
|
||||
**Design & UX:**
|
||||
- **PRD Ephemeral UI:** Conversational interface visie
|
||||
- **UX Research:** Chat + artifacts pattern analyse
|
||||
|
||||
### Technische Specs (voor developers)
|
||||
|
||||
- **Gedetailleerd FO Agenda Planning:** `docs/swift/fo-swift-agenda-planning.md` - Uitgebreide technische specificatie
|
||||
- **Intent Classifier:** `lib/swift/intent-classifier.ts` - Local pattern matching
|
||||
- **AI Classifier:** `lib/swift/intent-classifier-ai.ts` - Claude Haiku fallback
|
||||
- **Types:** `lib/swift/types.ts` - TypeScript type definitions
|
||||
|
||||
### Out of Scope (Toekomstige Versies)
|
||||
|
||||
❌ **Niet in MVP:**
|
||||
- Full calendar grid view (blijft in klassieke agenda)
|
||||
- Drag-and-drop rescheduling
|
||||
- Recurring appointments ("elke dinsdag om 10:00")
|
||||
- Beschikbaarheidscheck ("wanneer ben ik vrij")
|
||||
- Conflict detection & resolution
|
||||
- Multi-practitioner scheduling
|
||||
- SMS/email notificaties
|
||||
|
||||
---
|
||||
|
||||
## Wijzigingslog
|
||||
|
||||
| Versie | Datum | Wijzigingen | Auteur |
|
||||
|--------|-------|-------------|--------|
|
||||
| v1.0 | 27-12-2024 | Initial version - Agenda & afspraken functionaliteit in Swift volgens FO template | Colin Lit |
|
||||
|
||||
---
|
||||
|
||||
**Goedkeuring:**
|
||||
|
||||
- [ ] Product Owner: _________________________
|
||||
- [ ] Lead Developer: _________________________
|
||||
- [ ] UX Designer: _________________________
|
||||
|
||||
**Status:** Draft - Ter review
|
||||
|
||||
**Volgende stappen:**
|
||||
1. Review met stakeholders
|
||||
2. UX wireframes maken op basis van dit FO
|
||||
3. Technical implementation planning
|
||||
4. User testing scenario's opstellen
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,742 +0,0 @@
|
||||
# 🧩 Functioneel Ontwerp (FO) — Swift Swift Assistent Chatbot
|
||||
|
||||
**Projectnaam:** Swift — Swift Assistent Chatbot Interface
|
||||
**Versie:** v3.0
|
||||
**Datum:** 27-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en relatie met het PRD
|
||||
|
||||
🎯 **Doel van dit document:**
|
||||
Dit Functioneel Ontwerp beschrijft het **redesign** van Swift naar een Swift Assistent chatbot interface. De gebruiker voert een natuurlijke conversatie met een AI-assistent die intents herkent, acties uitvoert, en relevante UI-componenten toont in een split-screen layout.
|
||||
|
||||
📘 **Relatie met andere documenten:**
|
||||
- **PRD:** `nextgen-epd-prd-ephemeral-ui-epd.md` — Ephemeral UI visie
|
||||
- **UX/UI:** `swift-ux-v2.1.md` — Visuele specificaties (wordt herzien)
|
||||
- **UX Research:** `nextgen-epd-onderzoeksverslag-ux-ui-patterns.md` — Marktanalyse
|
||||
- **Bouwplan:** `bouwplan-swift-v2.md` — Development roadmap
|
||||
|
||||
**Kernprincipe:**
|
||||
> De gebruiker voert een natuurlijke conversatie met een Swift Assistent assistent. De assistent herkent intents, voert acties uit, en toont relevante UI-componenten (artifacts) rechts in beeld. De conversatie blijft zichtbaar en doorlopend — zoals ChatGPT Canvas of Claude Artifacts.
|
||||
|
||||
**Belangrijkste wijzigingen t.o.v. v2.0:**
|
||||
|
||||
| Aspect | v2.0 (Command Center) | v3.0 (Swift Assistent) |
|
||||
|--------|----------------------|----------------------|
|
||||
| Input model | Command-line stijl | Natuurlijke conversatie |
|
||||
| UI paradigma | Blocks die verschijnen/verdwijnen | Chat links, artifacts rechts |
|
||||
| Context | Per commando | Doorlopende conversatiegeschiedenis |
|
||||
| AI rol | Intent classifier | Converserende Swift Assistent |
|
||||
| Interactie | Transactioneel | Relationeel, follow-up mogelijk |
|
||||
|
||||
---
|
||||
|
||||
## 2. Overzicht van de belangrijkste onderdelen
|
||||
|
||||
🎯 **Doel:** Overzicht van alle modules en hun relaties.
|
||||
|
||||
### 2.1 Systeemcomponenten
|
||||
|
||||
| # | Component | Beschrijving | Type |
|
||||
|---|-----------|--------------|------|
|
||||
| 1 | **Command Center** | Hoofdscherm met split-screen layout | Scherm |
|
||||
| 2 | **Context Bar** | Dienst, actieve patiënt, user info | UI Zone |
|
||||
| 3 | **Chat Panel** | Doorlopende conversatie (links, 40%) | UI Zone |
|
||||
| 4 | **Artifact Area** | Waar blocks verschijnen (rechts, 60%) | UI Zone |
|
||||
| 5 | **Chat Input** | Tekst + voice input (onderaan chat) | UI Zone |
|
||||
| 6 | **Chat API** | Medical scribe chatbot endpoint | Backend |
|
||||
| 7 | **Intent Engine** | Herkent intents in conversatie (hybrid) | Backend |
|
||||
| 8 | **Context Manager** | Beheert sessie context + chat history | Backend |
|
||||
|
||||
### 2.2 Artifacts (Bouwblokken)
|
||||
|
||||
| Prio | Artifact | Functie | Trigger voorbeelden |
|
||||
|------|----------|---------|---------------------|
|
||||
| P1 | **DagnotatieBlock** | Snelle notitie invoer | "medicatie gegeven aan jan" |
|
||||
| P1 | **ZoekenBlock** | Patiënt zoeken | "wie is jan de vries" |
|
||||
| P1 | **PatientContextCard** | Patiënt overzicht | Na zoeken / selectie |
|
||||
| P1 | **OverdrachtBlock** | Dienst overdracht met AI-filtering | "maak overdracht" |
|
||||
| P2 | **RapportageBlock** | Behandelrapportage | "gesprek gehad met jan" |
|
||||
| P2 | **AgendaBlock** | Afspraken | "mijn afspraken vandaag" |
|
||||
| P2 | **MetingenBlock** | Vitale functies | "bloeddruk invoeren" |
|
||||
|
||||
**Belangrijk:** Artifacts blijven functioneel hetzelfde als v2.0 blocks, maar verschijnen nu rechts in een persistent panel.
|
||||
|
||||
### 2.3 Rol-specifieke Views
|
||||
|
||||
| Rol | Primaire workflow | Artifact focus |
|
||||
|-----|-------------------|----------------|
|
||||
| **Verpleegkundige** | Notities maken tijdens dienst | DagnotatieBlock, RapportageBlock |
|
||||
| **Psychiater** | Gefilterde overdracht lezen | OverdrachtBlock met AI-samenvatting |
|
||||
|
||||
---
|
||||
|
||||
## 3. User Stories
|
||||
|
||||
🎯 **Doel:** Beschrijven wat gebruikers moeten kunnen doen, vanuit hun perspectief.
|
||||
|
||||
### 3.1 Nieuwe Stories (v3.0)
|
||||
|
||||
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|
||||
|----|-----|--------------|------------------|------|
|
||||
| US-15 | Alle gebruikers | Natuurlijke conversatie voeren met assistent | Voelt als praten met collega | 🔴 P1 |
|
||||
| US-16 | Alle gebruikers | Conversatiegeschiedenis zien | Context behouden tijdens werk | 🔴 P1 |
|
||||
| US-17 | Alle gebruikers | Doorvragen zonder opnieuw te beginnen | Flexibele workflow | 🔴 P1 |
|
||||
| US-18 | Alle gebruikers | Assistent stelt verduidelijkingsvragen | Minder fouten, betere data | 🟡 P2 |
|
||||
| US-19 | Alle gebruikers | Meerdere acties in één conversatie | Efficiënte workflow | 🟡 P2 |
|
||||
| US-20 | Verpleegkundige | Notities maken via spraak of typen | Hands-free documentatie | 🔴 P1 |
|
||||
| US-21 | Verpleegkundige | Zien welke notities relevant zijn voor psychiater | Transparantie in filtering | 🟡 P2 |
|
||||
| US-22 | Psychiater | Alleen behandelrelevante info zien | Geen ruis, focus op behandeling | 🔴 P1 |
|
||||
| US-23 | Psychiater | Doorklikken naar originele bron | Verificatie en context | 🔴 P1 |
|
||||
|
||||
### 3.2 Bestaande Stories (herzien)
|
||||
|
||||
| ID | Rol | Doel / Actie | Wijziging v3.0 |
|
||||
|----|-----|--------------|----------------|
|
||||
| US-01 | Verpleegkundige | Dagnotitie maken | Via conversatie: "Ik heb medicatie gegeven aan Jan" |
|
||||
| US-02 | Verpleegkundige | Patiënt zoeken | Via conversatie: "Wie is Jan de Vries?" |
|
||||
| US-04 | Verpleegkundige | Overdracht maken | Via conversatie: "Maak overdracht voor deze dienst" |
|
||||
|
||||
---
|
||||
|
||||
## 4. Functionele werking per onderdeel
|
||||
|
||||
🎯 **Doel:** Per component beschrijven wat de gebruiker kan doen en wat het systeem doet.
|
||||
|
||||
### 4.1 Command Center (Hoofdscherm)
|
||||
|
||||
**Beschrijving:**
|
||||
Split-screen layout met chat links (40%) en artifacts rechts (60%). De gebruiker voert een natuurlijke conversatie met de Swift Assistent assistent.
|
||||
|
||||
**Layout:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🕐 Ochtend | 8 ptn Jan de Vries ▼ 👤 SV │
|
||||
│ CONTEXT BAR │
|
||||
├──────────────────────────────┬──────────────────────────────────┤
|
||||
│ │ │
|
||||
│ CHAT PANEL (40%) │ ARTIFACT AREA (60%) │
|
||||
│ │ │
|
||||
│ 👤 "Ik heb net medicatie │ ┌────────────────────────────┐ │
|
||||
│ gegeven aan Jan" │ │ 📝 Dagnotitie │ │
|
||||
│ │ │ │ │
|
||||
│ 🤖 Ik maak een dagnotitie │ │ Patiënt: Jan de Vries ✓ │ │
|
||||
│ voor Jan de Vries. │ │ Categorie: Medicatie ✓ │ │
|
||||
│ Categorie: Medicatie. │ │ │ │
|
||||
│ │ │ Notitie: │ │
|
||||
│ Wil je nog iets │ │ ┌────────────────────────┐│ │
|
||||
│ toevoegen? │ │ │ medicatie gegeven ││ │
|
||||
│ │ │ └────────────────────────┘│ │
|
||||
│ 👤 "Nee, opslaan" │ │ │ │
|
||||
│ │ │ ☐ Relevant voor psychiater │ │
|
||||
│ 🤖 ✓ Notitie opgeslagen │ │ │ │
|
||||
│ voor Jan de Vries. │ │ [Annuleren] [💾 Opslaan] │ │
|
||||
│ │ └────────────────────────────┘ │
|
||||
│ │ │
|
||||
├──────────────────────────────┤ │
|
||||
│ 💬 Typ of spreek... 🎤 │ │
|
||||
└──────────────────────────────┴──────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Gedrag:**
|
||||
- Bij laden: Chat input heeft focus, artifact area toont placeholder
|
||||
- Keyboard shortcut `⌘K` focust chat input vanaf elke plek
|
||||
- Artifacts verschijnen rechts met slide-in animatie (200ms)
|
||||
- Chat blijft zichtbaar en scrollbaar tijdens werk met artifacts
|
||||
- Meerdere artifacts mogelijk via tabs (max 3 tegelijk)
|
||||
|
||||
**States:**
|
||||
|
||||
| State | Chat Panel | Artifact Area |
|
||||
|-------|------------|---------------|
|
||||
| Initial | Welcome message | Placeholder: "Artifacts verschijnen hier" |
|
||||
| Conversing | Chat history + streaming response | Ongewijzigd of artifact |
|
||||
| Action triggered | Chat continues | Artifact slide-in |
|
||||
| Working | Chat beschikbaar | Artifact actief |
|
||||
| Completed | Success message in chat | Artifact sluit of blijft |
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Context Bar
|
||||
|
||||
**Functie:** Toont essentiële context: dienst, actieve patiënt, gebruiker.
|
||||
|
||||
**Elementen:**
|
||||
|
||||
| Element | Beschrijving | Interactie |
|
||||
|---------|--------------|------------|
|
||||
| Dienst indicator | 🕐 Ochtend / Middag / Nacht + patiëntentelling | Geen |
|
||||
| Actieve patiënt | Dropdown met recent geselecteerde patiënten | Klik opent selector |
|
||||
| User info | Avatar + initialen | Klik opent menu |
|
||||
|
||||
**Dienst kleuren:**
|
||||
|
||||
| Dienst | Tijd | Kleur |
|
||||
|--------|------|-------|
|
||||
| Ochtend | 07:00-15:00 | Amber (#F59E0B) |
|
||||
| Middag | 15:00-23:00 | Blue (#3B82F6) |
|
||||
| Nacht | 23:00-07:00 | Indigo (#6366F1) |
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Chat Panel
|
||||
|
||||
**Functie:** Toont doorlopende conversatie met Swift Assistent assistent.
|
||||
|
||||
**Elementen:**
|
||||
|
||||
| Element | Beschrijving |
|
||||
|---------|--------------|
|
||||
| Chat Messages | Scrollbare lijst van user en assistant messages |
|
||||
| Message Bubbles | User: rechts, amber bg / Assistant: links, slate bg |
|
||||
| Streaming Indicator | Pulsating dots tijdens AI response |
|
||||
| Action Links | Klikbare links naar gerelateerde artifacts |
|
||||
| Timestamps | Optioneel, alleen bij pauzes >5 minuten |
|
||||
|
||||
**Message Types:**
|
||||
|
||||
| Type | Weergave | Voorbeeld |
|
||||
|------|----------|-----------|
|
||||
| User message | Rechts, amber | "medicatie gegeven aan jan" |
|
||||
| Assistant text | Links, slate | "Ik maak een dagnotitie..." |
|
||||
| Assistant action | Links, met icon | "📝 Dagnotitie geopend" (klikbaar) |
|
||||
| System message | Centered, subtle | "✓ Notitie opgeslagen" |
|
||||
| Error message | Links, red border | "Er ging iets mis. Probeer opnieuw." |
|
||||
|
||||
**Gedrag:**
|
||||
- Auto-scroll naar laatste message
|
||||
- Scroll-lock wanneer gebruiker omhoog scrollt
|
||||
- "Scroll to bottom" knop bij nieuwe messages
|
||||
- Max 100 messages in view (pagination voor oudere)
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Chat Input
|
||||
|
||||
**Functie:** Tekst + voice input voor conversatie met Swift Assistent.
|
||||
|
||||
**States:**
|
||||
|
||||
| State | Weergave | Trigger |
|
||||
|-------|----------|---------|
|
||||
| Default | "Typ of spreek..." + mic icon | — |
|
||||
| Typing | Cursor + getypte tekst | Keyboard input |
|
||||
| Listening | 🔴 + waveform + live transcript | Mic click of spatie |
|
||||
| Processing | Disabled, "Denkt na..." | Na submit |
|
||||
| Streaming | Disabled, streaming in chat | Tijdens AI response |
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
|
||||
| Key | Actie |
|
||||
|-----|-------|
|
||||
| `Enter` | Submit message |
|
||||
| `⌘Enter` / `Ctrl+Enter` | Force submit (ook tijdens streaming) |
|
||||
| `Escape` | Clear input / cancel voice |
|
||||
| `↑` | Vorige message (edit history) |
|
||||
| `Space` (leeg) | Start voice recording |
|
||||
| `⌘K` | Focus input |
|
||||
|
||||
**Voice Flow:**
|
||||
1. User klikt mic of drukt spatie (bij lege input)
|
||||
2. Deepgram start streaming transcription
|
||||
3. Live transcript verschijnt in input field
|
||||
4. Pauze detectie (1.5 sec stilte) → auto-submit
|
||||
5. Of user klikt "Stop" → submit
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Artifact Area
|
||||
|
||||
**Functie:** Toont actieve artifacts (blocks) rechts van de chat.
|
||||
|
||||
**Layout opties:**
|
||||
|
||||
| Situatie | Weergave |
|
||||
|----------|----------|
|
||||
| Geen artifact | Placeholder met voorbeelden |
|
||||
| Eén artifact | Full width, centered |
|
||||
| Meerdere artifacts | Tabs bovenaan (max 3) |
|
||||
|
||||
**Artifact Lifecycle:**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. Intent detected in chat │
|
||||
│ → AI response: "Ik maak een dagnotitie..." │
|
||||
│ → Action object attached to response │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 2. Artifact appears │
|
||||
│ → Slide-in animation (200ms, from right) │
|
||||
│ → Pre-filled with extracted entities │
|
||||
│ → Focus moves to first editable field │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 3. User interacts │
|
||||
│ → Edits fields in artifact │
|
||||
│ → Can continue chatting (e.g., "voeg toe: sliep goed") │
|
||||
│ → Chat updates artifact in real-time │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 4. User saves or cancels │
|
||||
│ → Success: toast + chat confirmation + artifact closes │
|
||||
│ → Cancel: artifact closes, no confirmation │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Placeholder State:**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 💬 Artifacts verschijnen hier │
|
||||
│ │
|
||||
│ Vraag me iets, bijvoorbeeld: │
|
||||
│ • "Notitie voor Jan: medicatie gegeven" │
|
||||
│ • "Zoek Marie van den Berg" │
|
||||
│ • "Maak overdracht voor deze dienst" │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.6 Swift Assistent Chat API
|
||||
|
||||
**Functie:** Chatbot endpoint die conversatie voert en intents herkent.
|
||||
|
||||
**Endpoint:** `POST /api/swift/chat`
|
||||
|
||||
**Request:**
|
||||
|
||||
```typescript
|
||||
interface ChatRequest {
|
||||
message: string; // User message
|
||||
messages: ChatMessage[]; // Conversation history (max 20)
|
||||
context: {
|
||||
activePatient?: Patient; // Currently selected patient
|
||||
shift: 'ochtend' | 'middag' | 'nacht';
|
||||
userId: string;
|
||||
recentActions: RecentAction[]; // Last 5 actions
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Server-Sent Events):**
|
||||
|
||||
```typescript
|
||||
// Text chunk (streaming)
|
||||
{ type: 'text', content: string }
|
||||
|
||||
// Action detected (end of response)
|
||||
{
|
||||
type: 'action',
|
||||
intent: Intent,
|
||||
entities: ExtractedEntities,
|
||||
confidence: number,
|
||||
artifact?: {
|
||||
type: ArtifactType,
|
||||
prefill: PrefillData
|
||||
}
|
||||
}
|
||||
|
||||
// Done
|
||||
{ type: 'done' }
|
||||
```
|
||||
|
||||
**System Prompt (samenvatting):**
|
||||
|
||||
```
|
||||
Je bent een medische assistent (Swift Assistent) voor Swift, een Nederlands GGZ EPD.
|
||||
|
||||
Je rol:
|
||||
- Help zorgmedewerkers met documentatie en administratie
|
||||
- Voer natuurlijke gesprekken in het Nederlands
|
||||
- Herken intents en voer acties uit wanneer nodig
|
||||
- Stel verduidelijkingsvragen bij onduidelijkheid
|
||||
- Wees vriendelijk maar professioneel
|
||||
|
||||
Intents die je herkent:
|
||||
- dagnotitie: notitie maken voor patiënt
|
||||
- zoeken: patiënt zoeken
|
||||
- rapportage: behandelrapportage schrijven
|
||||
- overdracht: dienst overdracht maken
|
||||
- metingen: vitale functies invoeren
|
||||
- agenda: afspraken bekijken
|
||||
|
||||
Wanneer je een intent herkent, voeg een JSON action object toe aan je response.
|
||||
|
||||
Huidige context:
|
||||
- Actieve patiënt: {activePatient}
|
||||
- Dienst: {shift}
|
||||
- Recente acties: {recentActions}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.7 Intent Engine (Hybrid)
|
||||
|
||||
**Functie:** Herkent intents in conversatie met two-tier approach.
|
||||
|
||||
**Tier 1: Local Pattern Matching (<50ms)**
|
||||
|
||||
| Pattern | Intent | Entities |
|
||||
|---------|--------|----------|
|
||||
| "notitie [naam]" | dagnotitie | patient: naam |
|
||||
| "zoek [naam]" | zoeken | query: naam |
|
||||
| "overdracht" | overdracht | — |
|
||||
| "bloeddruk [naam]" | metingen | patient: naam, type: bloeddruk |
|
||||
|
||||
**Tier 2: AI Classification (via Chat API)**
|
||||
|
||||
Wanneer local patterns geen match geven of confidence <0.8, bepaalt de Chat API het intent op basis van conversatie-context.
|
||||
|
||||
**Voorbeelden:**
|
||||
|
||||
| Conversatie | Intent | Methode |
|
||||
|-------------|--------|---------|
|
||||
| "notitie jan medicatie" | dagnotitie | Local (direct match) |
|
||||
| "Ik heb net een gesprek gehad" | rapportage | AI (context needed) |
|
||||
| "Hij geeft aan zich beter te voelen" | (context) | AI (follow-up) |
|
||||
| "Wie was die mevrouw van kamer 12?" | zoeken | AI (indirect) |
|
||||
|
||||
---
|
||||
|
||||
### 4.8 AI-Filtering voor Psychiater (Overdracht)
|
||||
|
||||
**Functie:** Filtert verpleegkundige rapportages naar behandelrelevante informatie.
|
||||
|
||||
**Wat WEL naar psychiater gaat:**
|
||||
|
||||
| Categorie | Voorbeelden |
|
||||
|-----------|-------------|
|
||||
| Medicatie-issues | Weigering, bijwerkingen, therapierespons |
|
||||
| Stemming/gedrag verandering | t.o.v. baseline, significante shifts |
|
||||
| Risico-signalen | Suïcidale uitingen, zelfbeschadiging, agressie |
|
||||
| Signaleringsplan triggers | Oranje/rood fase signalen |
|
||||
| Psychotische symptomen | Hallucinaties, wanen |
|
||||
| Behandelplan stagnatie | Geen voortgang op doelen |
|
||||
|
||||
**Wat NIET naar psychiater gaat:**
|
||||
|
||||
| Categorie | Voorbeelden |
|
||||
|-----------|-------------|
|
||||
| Routine medicatie | "Medicatie volgens schema ingenomen" |
|
||||
| ADL activiteiten | "Heeft gedoucht", "Ontbijt genuttigd" |
|
||||
| Standaard observaties | "Rustige dag", "Goed geslapen" |
|
||||
| Sociale activiteiten | "Bezoek gehad", "Mee naar dagactiviteit" |
|
||||
|
||||
**Linked Evidence:**
|
||||
Elke zin in de AI-samenvatting linkt naar de originele verpleegkundige notitie. De psychiater kan hover-to-preview of click-to-expand gebruiken.
|
||||
|
||||
**UI in OverdrachtBlock:**
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ 🔄 Overdracht Ochtend → Middag [×] │
|
||||
│ 07:00 - 15:00 | 8 patiënten │
|
||||
├────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Behandelrelevant (3 patiënten) │
|
||||
│ ─────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ ▼ Jan de Vries ⚠️ Alert │
|
||||
│ Weigerde ochtendmedicatie. Geeft aan last te hebben │
|
||||
│ van bijwerkingen (duizeligheid). Stemming somberder │
|
||||
│ dan gisteren. │
|
||||
│ 📎 3 bronnotities [Bekijk bronnen] │
|
||||
│ │
|
||||
│ ▶ Marie van den Berg │
|
||||
│ ▶ Piet Jansen │
|
||||
│ │
|
||||
│ ─────────────────────────────────────────────────────── │
|
||||
│ Geen bijzonderheden (5 patiënten) │
|
||||
│ [Toon allen ▼] │
|
||||
│ │
|
||||
├────────────────────────────────────────────────────────────┤
|
||||
│ [📋 Kopieer] [📤 Naar EPD] [✓ Gezien] │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. User Flows
|
||||
|
||||
🎯 **Doel:** Laten zien hoe de gebruiker stap-voor-stap door het systeem gaat.
|
||||
|
||||
### Flow 1: Dagnotitie via Conversatie
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. Verpleegkundige opent Swift │
|
||||
│ → Chat: welcome message │
|
||||
│ → Artifact: placeholder │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 2. Verpleegkundige typt: "Medicatie gegeven aan Jan" │
|
||||
│ → Message verschijnt in chat │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 3. AI Response (streaming): │
|
||||
│ "Ik maak een dagnotitie voor Jan de Vries. │
|
||||
│ Categorie: Medicatie. Wil je nog iets toevoegen?" │
|
||||
│ → Action: intent=dagnotitie, patient=Jan, cat=Medicatie │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 4. DagnotatieBlock verschijnt rechts: │
|
||||
│ → Patient: Jan de Vries ✓ (pre-filled) │
|
||||
│ → Categorie: Medicatie ✓ (pre-filled) │
|
||||
│ → Text: "Medicatie gegeven" (pre-filled) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 5. Verpleegkundige: "Nee, opslaan" │
|
||||
│ → Of: klikt direct op Opslaan knop │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 6. Resultaat: │
|
||||
│ → Toast: "✓ Notitie opgeslagen" │
|
||||
│ → Chat: "Notitie opgeslagen voor Jan de Vries." │
|
||||
│ → Artifact sluit (of blijft voor volgende) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ TIJD: ~15 seconden │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Flow 2: Overdracht met AI-Filtering (Psychiater)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. Psychiater opent Swift │
|
||||
│ → Chat: "Goedemorgen! Wil je de overdracht bekijken?" │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 2. Psychiater: "Ja, laat zien" │
|
||||
│ → Of: "Overdracht ochtend" │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 3. AI Response: │
|
||||
│ "Ik haal de overdracht op. 3 patiënten hebben │
|
||||
│ behandelrelevante updates." │
|
||||
│ → OverdrachtBlock verschijnt rechts │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 4. OverdrachtBlock toont: │
|
||||
│ → Gefilterde samenvatting (alleen behandelrelevant) │
|
||||
│ → Patiënten gesorteerd op urgentie │
|
||||
│ → "Geen bijzonderheden" sectie ingeklapt │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 5. Psychiater klikt op "📎 3 bronnotities" bij Jan │
|
||||
│ → Slide-in panel met originele verpleegkundige notities │
|
||||
│ → Timestamps en auteurs zichtbaar │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 6. Psychiater in chat: "Wat was er gisteren met Jan?" │
|
||||
│ → AI antwoordt met context uit eerdere notities │
|
||||
│ → Geen nieuw artifact nodig │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 7. Psychiater klikt "✓ Gezien" │
|
||||
│ → Overdracht gemarkeerd als gelezen │
|
||||
│ → Chat: "Overdracht gemarkeerd als gezien." │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Flow 3: Rapportage met Verduidelijkingsvragen
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. Verpleegkundige: "Ik heb een gesprek gehad" │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 2. AI: "Met welke patiënt had je het gesprek?" │
|
||||
│ → Geen artifact (verduidelijking nodig) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 3. Verpleegkundige: "Jan" │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 4. AI: "Ik maak een rapportage voor Jan de Vries. │
|
||||
│ Wat wil je vastleggen?" │
|
||||
│ → RapportageBlock verschijnt rechts (leeg) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 5. Verpleegkundige: "Hij voelt zich beter, minder angstig" │
|
||||
│ → Tekst verschijnt in RapportageBlock │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 6. AI: "Genoteerd. Wil je dit als voortgangsgesprek │
|
||||
│ opslaan of is er meer?" │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 7. Verpleegkundige: "Opslaan als voortgang" │
|
||||
│ → Rapportage opgeslagen met tag "Voortgangsgesprek" │
|
||||
│ → Toast + chat confirmation │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Interacties met AI
|
||||
|
||||
🎯 **Doel:** Uitleggen waar AI in de flow voorkomt en wat de gebruiker ziet.
|
||||
|
||||
| Locatie | AI-actie | Trigger | Output |
|
||||
|---------|----------|---------|--------|
|
||||
| Chat | Conversatie + intent detection | Elke user message | Streaming text + action object |
|
||||
| Chat | Verduidelijkingsvraag | Onduidelijke intent | Vraag in chat (geen artifact) |
|
||||
| Chat | Follow-up verwerking | User antwoord op vraag | Update artifact of nieuwe info |
|
||||
| OverdrachtBlock | Relevantie-filtering | "overdracht" intent | Gefilterde samenvatting |
|
||||
| OverdrachtBlock | Samenvatting generatie | Per patiënt | Behandelrelevante bullets |
|
||||
| RapportageBlock | Tekst structureren | "Structureer" knop | Gestructureerde tekst |
|
||||
| RapportageBlock | B1-niveau herschrijven | "Vereenvoudig" knop | Herschreven tekst |
|
||||
|
||||
**Confidence Thresholds:**
|
||||
|
||||
| Confidence | Actie |
|
||||
|------------|-------|
|
||||
| >0.9 | Direct artifact openen met prefill |
|
||||
| 0.7-0.9 | Artifact openen + bevestigingsvraag in chat |
|
||||
| 0.5-0.7 | Verduidelijkingsvraag in chat, geen artifact |
|
||||
| <0.5 | Fallback: "Ik begrijp je niet helemaal. Kun je het anders formuleren?" |
|
||||
|
||||
---
|
||||
|
||||
## 7. Gebruikersrollen en rechten
|
||||
|
||||
🎯 **Doel:** Beschrijven welke rollen toegang hebben tot welke onderdelen.
|
||||
|
||||
| Rol | Toegang | Artifacts | AI-filtering |
|
||||
|-----|---------|-----------|--------------|
|
||||
| **Verpleegkundige** | Eigen dienst patiënten | Alle P1/P2 artifacts | Input: alle notities |
|
||||
| **Psychiater** | Alle patiënten in behandeling | OverdrachtBlock (read), PatientContextCard | Output: gefilterde samenvatting |
|
||||
| **Behandelaar** | Eigen caseload | Alle artifacts | Beide views beschikbaar |
|
||||
|
||||
**Permissies per Artifact:**
|
||||
|
||||
| Artifact | Verpleegkundige | Psychiater | Behandelaar |
|
||||
|----------|-----------------|------------|-------------|
|
||||
| DagnotatieBlock | ✅ Create | ❌ | ✅ Create |
|
||||
| RapportageBlock | ✅ Create | 👁️ Read | ✅ Create |
|
||||
| OverdrachtBlock | ✅ Create | 👁️ Read (filtered) | ✅ Both |
|
||||
| PatientContextCard | 👁️ Read | 👁️ Read | 👁️ Read |
|
||||
|
||||
---
|
||||
|
||||
## 8. Technische Specificaties
|
||||
|
||||
### 8.1 API Routes
|
||||
|
||||
| Route | Method | Functie | Status |
|
||||
|-------|--------|---------|--------|
|
||||
| `/api/swift/chat` | POST | Medical scribe chatbot | 🆕 Nieuw |
|
||||
| `/api/swift/overdracht/generate` | POST | AI-gefilterde overdracht | 🔄 Herzien |
|
||||
| `/api/intent/classify` | POST | Intent classification | ✅ Bestaat |
|
||||
| `/api/patients/search` | GET | Patient search | ✅ Bestaat |
|
||||
| `/api/reports` | POST | Report opslaan | ✅ Bestaat |
|
||||
|
||||
### 8.2 Store Uitbreiding
|
||||
|
||||
```typescript
|
||||
interface SwiftStore {
|
||||
// Bestaande state
|
||||
activePatient: Patient | null;
|
||||
activeBlock: BlockType | null;
|
||||
shift: 'ochtend' | 'middag' | 'nacht';
|
||||
|
||||
// Chat state (nieuw)
|
||||
chatMessages: ChatMessage[];
|
||||
isStreaming: boolean;
|
||||
pendingAction: Action | null;
|
||||
|
||||
// Artifact state (nieuw)
|
||||
openArtifacts: Artifact[]; // Max 3
|
||||
activeArtifactId: string | null;
|
||||
|
||||
// Actions
|
||||
addChatMessage: (message: ChatMessage) => void;
|
||||
processAction: (action: Action) => void;
|
||||
openArtifact: (artifact: Artifact) => void;
|
||||
closeArtifact: (id: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Component Structuur
|
||||
|
||||
```
|
||||
components/swift/
|
||||
├── command-center/
|
||||
│ ├── command-center.tsx # Split-screen container
|
||||
│ ├── context-bar.tsx # Header met dienst/patient
|
||||
│ ├── chat-panel.tsx # 🆕 Chat messages
|
||||
│ ├── chat-input.tsx # Herzien: conversational
|
||||
│ └── artifact-area.tsx # 🆕 Artifact container
|
||||
├── chat/
|
||||
│ ├── chat-message.tsx # 🆕 Individual message
|
||||
│ ├── chat-action-link.tsx # 🆕 Clickable action
|
||||
│ └── streaming-indicator.tsx # 🆕 Typing dots
|
||||
├── artifacts/
|
||||
│ ├── artifact-container.tsx # 🆕 Wrapper met tabs
|
||||
│ ├── dagnotitie-block.tsx # Bestaand
|
||||
│ ├── zoeken-block.tsx # Bestaand
|
||||
│ ├── overdracht-block.tsx # Herzien: met filtering UI
|
||||
│ ├── rapportage-block.tsx # Bestaand
|
||||
│ └── patient-context-card.tsx # Bestaand
|
||||
└── shared/
|
||||
├── linked-evidence.tsx # 🆕 Bron-verwijzing component
|
||||
└── relevance-badge.tsx # 🆕 "Behandelrelevant" indicator
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. UI Specificaties
|
||||
|
||||
### 9.1 Layout Breakpoints
|
||||
|
||||
| Viewport | Chat Panel | Artifact Area |
|
||||
|----------|------------|---------------|
|
||||
| Desktop (>1200px) | 40% | 60% |
|
||||
| Tablet (768-1200px) | 45% | 55% |
|
||||
| Mobile (<768px) | Full screen toggle | Full screen toggle |
|
||||
|
||||
### 9.2 Chat Message Styling
|
||||
|
||||
| Element | User Message | Assistant Message |
|
||||
|---------|--------------|-------------------|
|
||||
| Alignment | Right | Left |
|
||||
| Background | Amber-50 (#FFFBEB) | Slate-100 (#F1F5F9) |
|
||||
| Border | Amber-200 | Slate-200 |
|
||||
| Max width | 80% | 80% |
|
||||
| Border radius | 16px (top-right: 4px) | 16px (top-left: 4px) |
|
||||
|
||||
### 9.3 Artifact Animation
|
||||
|
||||
```css
|
||||
/* Slide-in from right */
|
||||
@keyframes artifact-enter {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.artifact-enter {
|
||||
animation: artifact-enter 200ms ease-out;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Bijlagen & Referenties
|
||||
|
||||
🎯 **Doel:** Linken naar overige documenten.
|
||||
|
||||
### Project Documentatie
|
||||
- PRD Ephemeral UI: `nextgen-epd-prd-ephemeral-ui-epd.md`
|
||||
- UX/UI Specs: `swift-ux-v2.1.md`
|
||||
- UX Research: `nextgen-epd-onderzoeksverslag-ux-ui-patterns.md`
|
||||
- Bouwplan: `bouwplan-swift-v2.md`
|
||||
- Project Status: `PROJECT-STATUS-2024-12-27.md`
|
||||
|
||||
### Externe Referenties
|
||||
- [ChatGPT Canvas UX Analysis](https://altar.io/next-gen-of-human-ai-collaboration/)
|
||||
- [Claude Artifacts Documentation](https://docs.anthropic.com)
|
||||
- [Abridge Linked Evidence](https://www.abridge.com/product)
|
||||
- [GGZ Zorgstandaarden](https://www.ggzstandaarden.nl)
|
||||
|
||||
---
|
||||
|
||||
## Wijzigingslog
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| v2.0 | 23-12-2024 | Command Center met ephemeral blocks |
|
||||
| v2.1 | 23-12-2024 | Prioriteitenlijst, intent mapping, P3 blocks |
|
||||
| v3.0 | 27-12-2024 | **Redesign:** Chat + Artifact interface, Swift Assistent conversatie, AI-filtering voor psychiater |
|
||||
@@ -1,437 +0,0 @@
|
||||
# 📊 Haalbaarheidsanalyse: Swift Medical Scribe v3.0
|
||||
|
||||
**Datum:** 27-12-2024
|
||||
**Document:** `fo-swift-medical-scribe-v3.md`
|
||||
**Auteur:** AI Assistant
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Het v3.0 redesign van Swift naar een medical scribe chatbot interface is **haalbaar**, maar vereist significante architecturale wijzigingen. De bestaande foundation (Command Center, blocks, intent classification) kan worden hergebruikt, maar de conversatie-laag en split-screen layout zijn nieuw.
|
||||
|
||||
**Kernbevindingen:**
|
||||
- ✅ **Sterke foundation:** Bestaande blocks, intent API, en voice input kunnen worden hergebruikt
|
||||
- ⚠️ **Grote wijzigingen:** Conversatie-interface, chat API, en split-screen layout zijn nieuw
|
||||
- ✅ **AI-infrastructuur:** Streaming chat API bestaat al (docs-chat), kan worden aangepast
|
||||
- ⚠️ **Complexiteit:** Conversatiegeschiedenis en context management vereisen nieuwe state management
|
||||
|
||||
**Geschatte effort:** 6-8 weken (1 developer, full-time)
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarisatie Bestaande Componenten
|
||||
|
||||
### 1.1 ✅ Wat bestaat al en kan worden hergebruikt
|
||||
|
||||
| Component | Status | Locatie | Hergebruik |
|
||||
|-----------|--------|---------|------------|
|
||||
| **Command Center** | ✅ Done | `components/swift/command-center/` | ⚠️ Layout wijzigen naar split-screen |
|
||||
| **Context Bar** | ✅ Done | `components/swift/command-center/context-bar.tsx` | ✅ Direct hergebruikbaar |
|
||||
| **Blocks (Artifacts)** | ✅ Done | `components/swift/blocks/` | ✅ Direct herbruikbaar |
|
||||
| **Intent Classification** | ✅ Done | `/api/intent/classify` | ✅ Herbruikbaar, maar moet worden uitgebreid |
|
||||
| **Voice Input** | ✅ Done | `lib/swift/use-swift-voice.ts` | ✅ Direct herbruikbaar |
|
||||
| **Swift Store** | ✅ Done | `stores/swift-store.ts` | ⚠️ Uitbreiden met chat state |
|
||||
| **Overdracht API** | ✅ Done | `/api/overdracht/generate` | ✅ Bestaat, maar AI-filtering voor psychiater moet worden toegevoegd |
|
||||
| **Streaming Chat API** | ✅ Bestaat | `/api/docs/chat` | ✅ Pattern kan worden gekopieerd |
|
||||
|
||||
### 1.2 🆕 Wat nieuw moet worden gebouwd
|
||||
|
||||
| Component | Complexiteit | Geschatte Effort |
|
||||
|-----------|-------------|------------------|
|
||||
| **Chat Panel** | Medium | 1 week |
|
||||
| **Chat Input (conversational)** | Low | 3 dagen |
|
||||
| **Chat API (`/api/swift/chat`)** | High | 1.5 week |
|
||||
| **Artifact Area (split-screen)** | Medium | 1 week |
|
||||
| **Chat Message Components** | Low | 3 dagen |
|
||||
| **Conversation History Management** | Medium | 1 week |
|
||||
| **AI-filtering voor psychiater** | Medium | 1 week |
|
||||
| **Linked Evidence UI** | Low | 3 dagen |
|
||||
|
||||
**Totaal nieuwe componenten:** ~6-7 weken
|
||||
|
||||
---
|
||||
|
||||
## 2. Gedetailleerde Analyse per Sectie
|
||||
|
||||
### 2.1 Command Center → Split-Screen Layout
|
||||
|
||||
**Huidige situatie:**
|
||||
- 4-zone layout: Context Bar | Canvas Area | Recent Strip | Command Input
|
||||
- Blocks verschijnen in Canvas Area (centered, full-width)
|
||||
|
||||
**Vereiste wijziging:**
|
||||
- Split-screen: Chat Panel (40%) links, Artifact Area (60%) rechts
|
||||
- Recent Strip kan worden verwijderd of geïntegreerd in chat
|
||||
|
||||
**Haalbaarheid:** ✅ **Goed**
|
||||
- Layout wijziging is relatief eenvoudig met CSS Grid/Flexbox
|
||||
- Bestaande blocks blijven werken, alleen positioning wijzigt
|
||||
- **Effort:** 3-5 dagen
|
||||
|
||||
**Risico's:**
|
||||
- Responsive design voor mobile/tablet vereist toggle tussen chat en artifact
|
||||
- Keyboard shortcuts moeten worden aangepast
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Chat Panel & Conversatie
|
||||
|
||||
**Huidige situatie:**
|
||||
- Geen chat interface
|
||||
- Command Input stuurt direct naar intent classification
|
||||
|
||||
**Vereiste wijziging:**
|
||||
- Nieuwe Chat Panel component met message bubbles
|
||||
- Conversatiegeschiedenis (max 100 messages)
|
||||
- Streaming responses (zoals docs-chat)
|
||||
|
||||
**Haalbaarheid:** ✅ **Goed**
|
||||
- Pattern bestaat al in `components/docs-chat/`
|
||||
- `use-docs-chat.ts` hook kan worden aangepast voor Swift
|
||||
- **Effort:** 1 week
|
||||
|
||||
**Risico's:**
|
||||
- Performance bij lange conversaties (pagination nodig)
|
||||
- State management voor chat history (Zustand store uitbreiden)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Medical Scribe Chat API
|
||||
|
||||
**Huidige situatie:**
|
||||
- `/api/intent/classify` retourneert alleen intent + entities
|
||||
- Geen conversatie, geen streaming
|
||||
|
||||
**Vereiste wijziging:**
|
||||
- Nieuwe `/api/swift/chat` endpoint
|
||||
- Streaming responses (SSE)
|
||||
- Conversatiegeschiedenis als input
|
||||
- Action objects in response (intent + entities + artifact prefill)
|
||||
|
||||
**Haalbaarheid:** ⚠️ **Middelmatig**
|
||||
- Pattern bestaat in `/api/docs/chat` (streaming met Claude)
|
||||
- Moet worden uitgebreid met:
|
||||
- Intent detection tijdens conversatie
|
||||
- Action object generation
|
||||
- Context management (activePatient, shift, recentActions)
|
||||
- **Effort:** 1.5 week
|
||||
|
||||
**Risico's:**
|
||||
- AI prompt engineering voor medical scribe persona
|
||||
- Performance bij hoge load (rate limiting nodig)
|
||||
- Kosten (Claude API calls per message)
|
||||
|
||||
**Aanbeveling:**
|
||||
```typescript
|
||||
// Hybrid approach: local intent first, AI voor conversatie
|
||||
1. Local pattern matching (<50ms) → direct action
|
||||
2. Als geen match → AI chat API met conversatie context
|
||||
3. AI detecteert intent + genereert response + action object
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Intent Engine (Hybrid)
|
||||
|
||||
**Huidige situatie:**
|
||||
- Two-tier: local patterns → AI fallback
|
||||
- Werkt goed voor directe commando's
|
||||
|
||||
**Vereiste wijziging:**
|
||||
- Local patterns blijven voor snelle acties
|
||||
- AI chat API voor conversatie + follow-up vragen
|
||||
- Confidence thresholds voor artifact opening
|
||||
|
||||
**Haalbaarheid:** ✅ **Goed**
|
||||
- Bestaande intent classifier blijft werken
|
||||
- Chat API gebruikt AI voor conversatie + intent detection
|
||||
- **Effort:** 3 dagen (integratie)
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Artifact Area (Split-Screen)
|
||||
|
||||
**Huidige situatie:**
|
||||
- Blocks verschijnen centered in Canvas Area
|
||||
- Eén block tegelijk
|
||||
|
||||
**Vereiste wijziging:**
|
||||
- Artifacts rechts (60% width)
|
||||
- Meerdere artifacts mogelijk (tabs, max 3)
|
||||
- Slide-in animatie van rechts
|
||||
|
||||
**Haalbaarheid:** ✅ **Goed**
|
||||
- Bestaande blocks blijven werken
|
||||
- Nieuwe wrapper component met tabs
|
||||
- **Effort:** 1 week
|
||||
|
||||
**Risico's:**
|
||||
- State management voor meerdere open artifacts
|
||||
- Tab switching UX
|
||||
|
||||
---
|
||||
|
||||
### 2.6 AI-Filtering voor Psychiater
|
||||
|
||||
**Huidige situatie:**
|
||||
- `/api/overdracht/generate` genereert samenvatting voor alle notities
|
||||
- Geen filtering op behandelrelevantie
|
||||
|
||||
**Vereiste wijziging:**
|
||||
- AI-filtering: alleen behandelrelevante notities
|
||||
- Linked evidence: elke zin linkt naar bronnotitie
|
||||
- UI: "Behandelrelevant" vs "Geen bijzonderheden" secties
|
||||
|
||||
**Haalbaarheid:** ⚠️ **Middelmatig**
|
||||
- AI prompt moet worden uitgebreid met filtering criteria
|
||||
- Linked evidence vereist tracking van bronnotities per zin
|
||||
- **Effort:** 1 week
|
||||
|
||||
**Risico's:**
|
||||
- AI filtering accuracy (false negatives = belangrijke info gemist)
|
||||
- Linked evidence parsing (welke zin komt van welke notitie?)
|
||||
|
||||
**Aanbeveling:**
|
||||
- Start met simpele keyword-based filtering als fallback
|
||||
- AI filtering als primary, met optie om "alles te tonen"
|
||||
|
||||
---
|
||||
|
||||
### 2.7 Store Uitbreiding
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
interface SwiftStore {
|
||||
activePatient: Patient | null;
|
||||
activeBlock: BlockType | null;
|
||||
prefillData: BlockPrefillData;
|
||||
// ... geen chat state
|
||||
}
|
||||
```
|
||||
|
||||
**Vereiste wijziging:**
|
||||
```typescript
|
||||
interface SwiftStore {
|
||||
// Bestaand
|
||||
activePatient: Patient | null;
|
||||
activeBlock: BlockType | null;
|
||||
|
||||
// Nieuw: Chat state
|
||||
chatMessages: ChatMessage[];
|
||||
isStreaming: boolean;
|
||||
pendingAction: Action | null;
|
||||
|
||||
// Nieuw: Artifact state
|
||||
openArtifacts: Artifact[]; // Max 3
|
||||
activeArtifactId: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
**Haalbaarheid:** ✅ **Goed**
|
||||
- Zustand store is makkelijk uit te breiden
|
||||
- **Effort:** 2 dagen
|
||||
|
||||
---
|
||||
|
||||
## 3. Technische Risico's & Uitdagingen
|
||||
|
||||
### 3.1 🔴 Hoge Risico's
|
||||
|
||||
| Risico | Impact | Mitigatie |
|
||||
|--------|--------|-----------|
|
||||
| **AI Prompt Engineering** | High | Medical scribe persona moet natuurlijk klinken, maar ook betrouwbaar zijn. Iteratief testen met echte gebruikers. |
|
||||
| **Performance bij lange conversaties** | Medium | Pagination voor chat history, max 100 messages in view. Virtual scrolling overwegen. |
|
||||
| **AI Kosten** | Medium | Rate limiting, caching van responses waar mogelijk. Local patterns gebruiken voor snelle acties. |
|
||||
| **State Management Complexiteit** | Medium | Duidelijke scheiding tussen chat state en artifact state. UseReducer overwegen voor complexe state. |
|
||||
|
||||
### 3.2 🟡 Middelmatige Risico's
|
||||
|
||||
| Risico | Impact | Mitigatie |
|
||||
|--------|--------|-----------|
|
||||
| **Responsive Design** | Medium | Mobile: toggle tussen chat en artifact. Tablet: 45/55 split. |
|
||||
| **Linked Evidence Parsing** | Medium | AI moet structured output geven met bronverwijzingen per zin. Fallback: keyword matching. |
|
||||
| **Conversatie Context Verlies** | Low | Context manager bewaart actieve patiënt, shift, recente acties. Max 20 messages in API call. |
|
||||
|
||||
### 3.3 🟢 Lage Risico's
|
||||
|
||||
| Risico | Impact | Mitigatie |
|
||||
|--------|--------|-----------|
|
||||
| **Block Migratie** | Low | Bestaande blocks blijven werken, alleen wrapper wijzigt. |
|
||||
| **Voice Input** | Low | Bestaande implementatie blijft werken. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Migratie Strategie
|
||||
|
||||
### 4.1 Fase 1: Foundation (Week 1-2)
|
||||
- ✅ Split-screen layout implementeren
|
||||
- ✅ Chat Panel component bouwen
|
||||
- ✅ Store uitbreiden met chat state
|
||||
- ✅ Basis chat API endpoint
|
||||
|
||||
**Deliverable:** Werkende split-screen met chat (zonder AI)
|
||||
|
||||
### 4.2 Fase 2: Chat API & Conversatie (Week 3-4)
|
||||
- ✅ Medical scribe chat API implementeren
|
||||
- ✅ Streaming responses
|
||||
- ✅ Intent detection in conversatie
|
||||
- ✅ Action object generation
|
||||
|
||||
**Deliverable:** Werkende conversatie met artifact opening
|
||||
|
||||
### 4.3 Fase 3: Artifact Area (Week 5)
|
||||
- ✅ Artifact Area component met tabs
|
||||
- ✅ Meerdere artifacts ondersteuning
|
||||
- ✅ Slide-in animaties
|
||||
|
||||
**Deliverable:** Meerdere artifacts tegelijk mogelijk
|
||||
|
||||
### 4.4 Fase 4: AI-Filtering & Polish (Week 6-7)
|
||||
- ✅ AI-filtering voor psychiater overdracht
|
||||
- ✅ Linked evidence UI
|
||||
- ✅ Error handling & edge cases
|
||||
- ✅ Performance optimalisatie
|
||||
|
||||
**Deliverable:** Volledige v3.0 functionaliteit
|
||||
|
||||
### 4.5 Fase 5: Testing & Refinement (Week 8)
|
||||
- ✅ User testing
|
||||
- ✅ Bug fixes
|
||||
- ✅ Performance tuning
|
||||
- ✅ Documentatie
|
||||
|
||||
**Deliverable:** Production-ready v3.0
|
||||
|
||||
---
|
||||
|
||||
## 5. Aanbevelingen
|
||||
|
||||
### 5.1 ✅ Doen
|
||||
|
||||
1. **Hergebruik bestaande patterns**
|
||||
- Kopieer `use-docs-chat.ts` hook als basis voor Swift chat
|
||||
- Hergebruik streaming pattern van `/api/docs/chat`
|
||||
|
||||
2. **Incrementele migratie**
|
||||
- Behoud oude Command Center tijdens ontwikkeling
|
||||
- Feature flag voor nieuwe interface
|
||||
- A/B testing mogelijkheid
|
||||
|
||||
3. **Local-first approach**
|
||||
- Gebruik local patterns voor snelle acties (zoals nu)
|
||||
- AI alleen voor conversatie en complexe queries
|
||||
|
||||
4. **Performance monitoring**
|
||||
- Track AI API response times
|
||||
- Monitor chat history size
|
||||
- Alert bij lange conversaties
|
||||
|
||||
### 5.2 ⚠️ Overwegen
|
||||
|
||||
1. **Hybrid Intent Detection**
|
||||
- Local patterns voor P1 intents (dagnotitie, zoeken)
|
||||
- AI chat voor P2 intents en follow-up vragen
|
||||
|
||||
2. **Conversation Context Window**
|
||||
- Max 20 messages in API call (zoals gespecificeerd)
|
||||
- Summarization voor oudere messages
|
||||
|
||||
3. **Artifact Persistence**
|
||||
- Sla open artifacts op in localStorage
|
||||
- Herstel bij page reload
|
||||
|
||||
### 5.3 ❌ Niet Doen (YAGNI)
|
||||
|
||||
1. **Geen volledige rewrite**
|
||||
- Bestaande blocks blijven werken
|
||||
- Alleen wrapper en layout wijzigen
|
||||
|
||||
2. **Geen nieuwe voice engine**
|
||||
- Bestaande Deepgram implementatie is voldoende
|
||||
|
||||
3. **Geen real-time collaboration**
|
||||
- Niet nodig voor MVP
|
||||
|
||||
---
|
||||
|
||||
## 6. Conclusie
|
||||
|
||||
### Haalbaarheid: ✅ **HAALBAAR**
|
||||
|
||||
Het v3.0 redesign is haalbaar binnen 6-8 weken met 1 developer. De bestaande foundation is sterk en kan worden hergebruikt. De grootste uitdagingen zijn:
|
||||
|
||||
1. **Chat API ontwikkeling** (1.5 week)
|
||||
2. **AI prompt engineering** (iteratief, doorlopend)
|
||||
3. **State management complexiteit** (manageable met Zustand)
|
||||
|
||||
### Success Criteria
|
||||
|
||||
- [ ] Split-screen layout werkt op desktop/tablet/mobile
|
||||
- [ ] Conversatie voelt natuurlijk (niet robotisch)
|
||||
- [ ] Artifacts openen binnen 2 seconden na intent detection
|
||||
- [ ] AI-filtering voor psychiater heeft >90% accuracy
|
||||
- [ ] Performance: <500ms voor local patterns, <3s voor AI responses
|
||||
|
||||
### Volgende Stappen
|
||||
|
||||
1. ✅ **Go/No-Go beslissing** op basis van deze analyse
|
||||
2. ✅ **Bouwplan v3.0** opstellen met gedetailleerde stories
|
||||
3. ✅ **Sprint planning** voor Fase 1-5
|
||||
4. ✅ **AI prompt engineering** starten (parallel met development)
|
||||
|
||||
---
|
||||
|
||||
## Bijlagen
|
||||
|
||||
### A. Bestaande Componenten Overzicht
|
||||
|
||||
```
|
||||
components/swift/
|
||||
├── command-center/
|
||||
│ ├── command-center.tsx ✅ Herbruikbaar (layout wijzigen)
|
||||
│ ├── context-bar.tsx ✅ Direct herbruikbaar
|
||||
│ ├── command-input.tsx ⚠️ Aanpassen naar chat input
|
||||
│ ├── canvas-area.tsx ⚠️ Vervangen door artifact-area
|
||||
│ └── recent-strip.tsx ❓ Verwijderen of integreren
|
||||
├── blocks/
|
||||
│ ├── dagnotitie-block.tsx ✅ Direct herbruikbaar
|
||||
│ ├── zoeken-block.tsx ✅ Direct herbruikbaar
|
||||
│ ├── overdracht-block.tsx ⚠️ Uitbreiden met AI-filtering
|
||||
│ └── patient-context-card.tsx ✅ Direct herbruikbaar
|
||||
```
|
||||
|
||||
### B. Nieuwe Componenten Overzicht
|
||||
|
||||
```
|
||||
components/swift/
|
||||
├── chat/ 🆕 Nieuw
|
||||
│ ├── chat-panel.tsx
|
||||
│ ├── chat-message.tsx
|
||||
│ ├── chat-action-link.tsx
|
||||
│ └── streaming-indicator.tsx
|
||||
├── artifacts/ 🆕 Nieuw
|
||||
│ ├── artifact-container.tsx (wrapper met tabs)
|
||||
│ └── artifact-tab.tsx
|
||||
└── shared/ 🆕 Nieuw
|
||||
├── linked-evidence.tsx
|
||||
└── relevance-badge.tsx
|
||||
```
|
||||
|
||||
### C. API Routes Overzicht
|
||||
|
||||
```
|
||||
app/api/
|
||||
├── swift/ 🆕 Nieuw
|
||||
│ └── chat/
|
||||
│ └── route.ts (medical scribe chatbot)
|
||||
├── intent/
|
||||
│ └── classify/ ✅ Bestaat (herbruikbaar)
|
||||
└── overdracht/
|
||||
└── generate/ ✅ Bestaat (uitbreiden met filtering)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Einde analyse**
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
# Epic 5.S1 Implementation: Action Routing
|
||||
|
||||
**Story**: E5.S1 - Action routing
|
||||
**Date**: 2025-12-27
|
||||
**Status**: ✅ Complete
|
||||
**Story Points**: 2 SP
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objective
|
||||
|
||||
Implement intent routing logic that maps agenda intents to the AgendaBlock artifact with the appropriate mode and prefill data.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Summary
|
||||
|
||||
### 1. Updated Action Parser Schemas
|
||||
|
||||
**File**: `lib/swift/action-parser.ts`
|
||||
|
||||
Added agenda intents to validation schemas:
|
||||
- `agenda_query`
|
||||
- `create_appointment`
|
||||
- `cancel_appointment`
|
||||
- `reschedule_appointment`
|
||||
|
||||
### 2. Created Intent Routing Function
|
||||
|
||||
**Function**: `routeIntentToArtifact(intent, entities, confidence)`
|
||||
|
||||
**Purpose**: Maps intents to artifact configurations with:
|
||||
- Artifact type
|
||||
- Title
|
||||
- Prefill data
|
||||
|
||||
**Logic**:
|
||||
```typescript
|
||||
if (confidence < 0.7) return null; // Trigger fallback
|
||||
|
||||
switch (intent) {
|
||||
case 'agenda_query':
|
||||
return { type: 'agenda_query', title: 'Agenda', prefill: { dateRange } };
|
||||
|
||||
case 'create_appointment':
|
||||
if (!patientName && !patientId) return null; // Missing required entity
|
||||
return { type: 'create_appointment', title: 'Nieuwe afspraak', prefill: {...} };
|
||||
|
||||
case 'cancel_appointment':
|
||||
return { type: 'cancel_appointment', title: 'Afspraak annuleren', prefill: {...} };
|
||||
|
||||
case 'reschedule_appointment':
|
||||
if (!identifier) return null; // Need to know which appointment
|
||||
return { type: 'reschedule_appointment', title: 'Afspraak verzetten', prefill: {...} };
|
||||
|
||||
// ... other intents (dagnotitie, zoeken, overdracht)
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features**:
|
||||
- ✅ Confidence threshold (0.7) enforcement
|
||||
- ✅ Required entity validation (patient for create, identifier for reschedule)
|
||||
- ✅ Returns null to trigger clarification when needed
|
||||
- ✅ Proper prefill data extraction for each intent
|
||||
|
||||
### 3. Updated Command Input
|
||||
|
||||
**File**: `components/swift/command-center/command-input.tsx`
|
||||
|
||||
**Changes**:
|
||||
1. Import `routeIntentToArtifact` from action-parser
|
||||
2. Add `openArtifact` to store hooks
|
||||
3. Replace legacy `openBlock` logic with routing:
|
||||
|
||||
```typescript
|
||||
// OLD (direct block opening):
|
||||
if (intent !== 'unknown' && confidence >= 0.5) {
|
||||
openBlock(intent as BlockType, entities);
|
||||
}
|
||||
|
||||
// NEW (routing with artifact system):
|
||||
const artifactConfig = routeIntentToArtifact(intent, entities, confidence);
|
||||
if (artifactConfig) {
|
||||
openArtifact({
|
||||
type: artifactConfig.type,
|
||||
title: artifactConfig.title,
|
||||
prefill: artifactConfig.prefill,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Key Routing Rules
|
||||
|
||||
| Intent | Artifact Type | Required Entities | Mode |
|
||||
|--------|---------------|-------------------|------|
|
||||
| `agenda_query` | `agenda_query` | None | List view with date filter |
|
||||
| `create_appointment` | `create_appointment` | `patientName` OR `patientId` | Create form |
|
||||
| `cancel_appointment` | `cancel_appointment` | None | Cancel view (may need disambiguation) |
|
||||
| `reschedule_appointment` | `reschedule_appointment` | `identifier` | Reschedule form |
|
||||
|
||||
---
|
||||
|
||||
## 📊 Confidence Thresholds
|
||||
|
||||
| Confidence | Action | Example |
|
||||
|------------|--------|---------|
|
||||
| **≥ 0.7** | Open artifact directly | "afspraken vandaag" → AgendaBlock opens |
|
||||
| **< 0.7** | Show fallback picker | Low confidence → User chooses intent manually |
|
||||
| **Missing required entity** | Show fallback picker | "maak afspraak" (no patient) → Clarification needed |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Data Flow
|
||||
|
||||
```
|
||||
User Input
|
||||
↓
|
||||
Intent Classification API (/api/intent/classify)
|
||||
↓
|
||||
{ intent, entities, confidence }
|
||||
↓
|
||||
routeIntentToArtifact()
|
||||
↓
|
||||
Artifact Config { type, title, prefill } OR null
|
||||
↓
|
||||
openArtifact() OR openBlock('fallback')
|
||||
↓
|
||||
AgendaBlock renders with prefilled data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Files Modified
|
||||
|
||||
```
|
||||
lib/swift/action-parser.ts
|
||||
├── Updated ActionSchema with agenda intents
|
||||
├── Updated artifact type enum
|
||||
├── Added routeIntentToArtifact() function
|
||||
└── Updated validateArtifactType() for agenda intents
|
||||
|
||||
components/swift/command-center/command-input.tsx
|
||||
├── Import routeIntentToArtifact
|
||||
├── Use openArtifact from store
|
||||
└── Replace direct block opening with routing logic
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Acceptance Criteria
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| Agenda intents open AgendaBlock | ✅ | Via routing function |
|
||||
| Correct mode selected | ✅ | Based on intent type |
|
||||
| Prefill data passed correctly | ✅ | Mapped from entities |
|
||||
| Confidence threshold enforced | ✅ | < 0.7 shows fallback |
|
||||
| Required entities validated | ✅ | Returns null when missing |
|
||||
| TypeScript type safety | ✅ | No compilation errors |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Scenarios
|
||||
|
||||
### Scenario 1: High Confidence Agenda Query
|
||||
**Input**: "afspraken vandaag"
|
||||
**Expected**:
|
||||
- Intent: `agenda_query`
|
||||
- Confidence: ~0.95
|
||||
- Result: AgendaBlock opens in list mode with today's date range
|
||||
|
||||
### Scenario 2: Create Appointment with Patient
|
||||
**Input**: "maak afspraak jan morgen 14:00"
|
||||
**Expected**:
|
||||
- Intent: `create_appointment`
|
||||
- Entities: `{ patientName: "jan", datetime: {...} }`
|
||||
- Result: AgendaBlock opens in create mode with prefilled patient and time
|
||||
|
||||
### Scenario 3: Create Appointment without Patient (Missing Entity)
|
||||
**Input**: "maak afspraak morgen 14:00"
|
||||
**Expected**:
|
||||
- Intent: `create_appointment`
|
||||
- Entities: `{ datetime: {...} }` (no patient)
|
||||
- Result: FallbackPicker opens (routing returns null due to missing required entity)
|
||||
|
||||
### Scenario 4: Low Confidence
|
||||
**Input**: "agenda ding morgen"
|
||||
**Expected**:
|
||||
- Intent: `agenda_query` (maybe)
|
||||
- Confidence: < 0.7
|
||||
- Result: FallbackPicker opens (routing returns null due to low confidence)
|
||||
|
||||
### Scenario 5: Reschedule with Identifier
|
||||
**Input**: "verzet 14:00 naar 15:00"
|
||||
**Expected**:
|
||||
- Intent: `reschedule_appointment`
|
||||
- Entities: `{ identifier: {...}, newDatetime: {...} }`
|
||||
- Result: AgendaBlock opens in reschedule mode
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. **E5.S2**: Update chat API prompt to include agenda intent examples
|
||||
2. **E5.S3**: Add error state handling with fallback links
|
||||
3. **E6**: Manual QA testing of complete agenda flow
|
||||
|
||||
---
|
||||
|
||||
## 💡 Lessons Learned
|
||||
|
||||
1. **Artifact System**: Modern approach using `openArtifact()` is cleaner than legacy `openBlock()`
|
||||
2. **Centralized Routing**: Single function makes intent mapping maintainable and testable
|
||||
3. **Validation Early**: Checking required entities in routing prevents incomplete forms
|
||||
4. **Confidence Thresholds**: 0.7 threshold balances automation with user control
|
||||
5. **Type Safety**: TypeScript schemas ensure consistency across intent types
|
||||
|
||||
---
|
||||
|
||||
**Implementation Status**: ✅ Complete
|
||||
**Ready for**: E5.S2 (Chat prompt update)
|
||||
**Estimated Testing Time**: 15-20 minutes (manual testing with various inputs)
|
||||
@@ -1,325 +0,0 @@
|
||||
# Epic 5.S2 Implementation: Chat Prompt Update
|
||||
|
||||
**Story**: E5.S2 - Chat prompt update
|
||||
**Date**: 2025-12-27
|
||||
**Status**: ✅ Complete
|
||||
**Story Points**: 2 SP
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objective
|
||||
|
||||
Update the Swift chat API system prompt to include agenda intent descriptions, entity extraction rules, and action format examples.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Summary
|
||||
|
||||
### File Modified
|
||||
|
||||
**`app/api/swift/chat/route.ts`** - Function: `buildMedicalScribePrompt()`
|
||||
|
||||
### Changes Made
|
||||
|
||||
#### 1. Added Agenda Intents to P2 Section
|
||||
|
||||
Added 4 new agenda intents to the P2 (belangrijk, middenfrequent) section:
|
||||
|
||||
**agenda_query** - Afspraken opvragen
|
||||
- Triggers: "afspraken vandaag", "agenda morgen", "wat is mijn volgende afspraak", "afspraken deze week"
|
||||
- Entities: `dateRange` (vandaag/morgen/deze week/volgende week)
|
||||
- Action: Toon lijst van afspraken in AgendaBlock
|
||||
|
||||
**create_appointment** - Nieuwe afspraak maken
|
||||
- Triggers: "maak afspraak [patient]", "plan intake [patient]", "afspraak maken met [patient] [datum] [tijd]"
|
||||
- Entities: `patientName`, `datetime`, `appointmentType`, `location`
|
||||
- Required: `patientName` OR `patientId`, `datetime`
|
||||
- Optional: `appointmentType` (default: behandeling), `location` (default: praktijk)
|
||||
- Action: Open create form met pre-fill
|
||||
|
||||
**cancel_appointment** - Afspraak annuleren
|
||||
- Triggers: "annuleer afspraak [patient]", "cancel [tijd]", "afspraak van [patient] annuleren"
|
||||
- Entities: `identifier` (patient naam/tijd combinatie voor matching)
|
||||
- Action: Toon confirmation dialog, bij meerdere matches: disambiguation
|
||||
|
||||
**reschedule_appointment** - Afspraak verzetten
|
||||
- Triggers: "verzet afspraak [patient]", "verzet [oude tijd] naar [nieuwe tijd]", "[patient] naar [nieuwe datum]"
|
||||
- Entities: `identifier`, `newDatetime`
|
||||
- Required: `identifier`
|
||||
- Action: Toon edit form met oude en nieuwe tijd
|
||||
|
||||
#### 2. Extended Clarification Questions
|
||||
|
||||
Added agenda-specific clarification examples:
|
||||
- "Voor welke datum wil je de afspraak maken?" (datum ontbreekt bij create_appointment)
|
||||
- "Op welk tijdstip?" (tijd ontbreekt bij create_appointment)
|
||||
- "Welke afspraak wil je verzetten?" (identifier onduidelijk bij reschedule/cancel)
|
||||
|
||||
#### 3. Added Agenda Examples
|
||||
|
||||
**Voorbeeld 5: Agenda query**
|
||||
```json
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "agenda_query",
|
||||
"entities": {
|
||||
"dateRange": {
|
||||
"start": "2025-12-27",
|
||||
"end": "2025-12-27",
|
||||
"label": "vandaag"
|
||||
}
|
||||
},
|
||||
"confidence": 0.98,
|
||||
"artifact": {
|
||||
"type": "agenda_query",
|
||||
"prefill": {
|
||||
"dateRange": {...}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Voorbeeld 6: Afspraak maken (compleet)**
|
||||
```json
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "create_appointment",
|
||||
"entities": {
|
||||
"patientName": "Jan",
|
||||
"datetime": {
|
||||
"date": "2025-12-28",
|
||||
"time": "14:00"
|
||||
},
|
||||
"appointmentType": "behandeling",
|
||||
"location": "praktijk"
|
||||
},
|
||||
"confidence": 0.95,
|
||||
"artifact": {
|
||||
"type": "create_appointment",
|
||||
"prefill": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Voorbeeld 7: Afspraak maken (incompleet)**
|
||||
User: "Plan intake Marie"
|
||||
Response: "Voor welke datum en tijd wil je de intake voor Marie plannen?"
|
||||
(Geen JSON action omdat datetime ontbreekt)
|
||||
|
||||
**Voorbeeld 8: Afspraak verzetten**
|
||||
```json
|
||||
{
|
||||
"type": "action",
|
||||
"intent": "reschedule_appointment",
|
||||
"entities": {
|
||||
"identifier": {
|
||||
"type": "time",
|
||||
"time": "14:00"
|
||||
},
|
||||
"newDatetime": {
|
||||
"date": "2025-12-27",
|
||||
"time": "15:00"
|
||||
}
|
||||
},
|
||||
"confidence": 0.92,
|
||||
"artifact": {
|
||||
"type": "reschedule_appointment",
|
||||
"prefill": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Key Prompt Additions
|
||||
|
||||
### Entity Structure for Agenda Intents
|
||||
|
||||
**dateRange** (agenda_query):
|
||||
```json
|
||||
{
|
||||
"start": "YYYY-MM-DD",
|
||||
"end": "YYYY-MM-DD",
|
||||
"label": "vandaag" | "morgen" | "deze week" | "volgende week"
|
||||
}
|
||||
```
|
||||
|
||||
**datetime** (create_appointment, reschedule_appointment):
|
||||
```json
|
||||
{
|
||||
"date": "YYYY-MM-DD",
|
||||
"time": "HH:mm"
|
||||
}
|
||||
```
|
||||
|
||||
**identifier** (cancel_appointment, reschedule_appointment):
|
||||
```json
|
||||
{
|
||||
"type": "patient" | "time" | "both",
|
||||
"patientName"?: "string",
|
||||
"patientId"?: "uuid",
|
||||
"time"?: "HH:mm",
|
||||
"date"?: "YYYY-MM-DD",
|
||||
"encounterId"?: "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**appointmentType** (create_appointment):
|
||||
- intake
|
||||
- behandeling (default)
|
||||
- follow-up
|
||||
- telefonisch
|
||||
- huisbezoek
|
||||
- online
|
||||
- crisis
|
||||
|
||||
**location** (create_appointment):
|
||||
- praktijk (default)
|
||||
- online
|
||||
- thuis
|
||||
|
||||
---
|
||||
|
||||
## ✅ Acceptance Criteria
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| Agenda intents described in prompt | ✅ | 4 intents added to P2 section |
|
||||
| Entity extraction rules documented | ✅ | All entities with types and defaults |
|
||||
| Required vs optional entities specified | ✅ | Clear for each intent |
|
||||
| Clarification question examples | ✅ | 3 new agenda-specific examples |
|
||||
| Action format examples | ✅ | 4 complete examples with JSON |
|
||||
| Confidence thresholds mentioned | ✅ | Inherited from base prompt (≥0.7) |
|
||||
| TypeScript compilation | ✅ | 0 errors |
|
||||
|
||||
---
|
||||
|
||||
## 📊 Prompt Structure
|
||||
|
||||
The updated prompt now includes:
|
||||
|
||||
```
|
||||
Je rol
|
||||
├── Kernkwaliteiten
|
||||
└── Tone of voice
|
||||
|
||||
Wat je DOET
|
||||
├── 1. Intents herkennen
|
||||
│ ├── P1 Intents (dagnotitie, zoeken, overdracht)
|
||||
│ └── P2 Intents (rapportage, agenda_query, create_appointment,
|
||||
│ cancel_appointment, reschedule_appointment) ✨ NEW
|
||||
├── 2. Verduidelijkingsvragen stellen (+ agenda examples) ✨ UPDATED
|
||||
├── 3. Action objects genereren
|
||||
└── 4. Follow-up conversatie
|
||||
|
||||
Wat je NIET doet
|
||||
Context die beschikbaar is
|
||||
|
||||
Voorbeelden
|
||||
├── Voorbeeld 1-4 (bestaand)
|
||||
├── Voorbeeld 5: Agenda query ✨ NEW
|
||||
├── Voorbeeld 6: Afspraak maken (compleet) ✨ NEW
|
||||
├── Voorbeeld 7: Afspraak maken (incompleet) ✨ NEW
|
||||
└── Voorbeeld 8: Afspraak verzetten ✨ NEW
|
||||
|
||||
Error Handling
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Scenarios
|
||||
|
||||
### Scenario 1: Agenda Query - High Confidence
|
||||
**Input**: "afspraken vandaag"
|
||||
**Expected Claude Response**:
|
||||
- Text: "Ik toon je de afspraken voor vandaag."
|
||||
- JSON: `{ intent: "agenda_query", confidence: 0.98, entities: { dateRange: {...} } }`
|
||||
|
||||
### Scenario 2: Create Appointment - Complete
|
||||
**Input**: "maak afspraak met Jan morgen 14:00"
|
||||
**Expected Claude Response**:
|
||||
- Text: "Ik maak een afspraak voor Jan morgen om 14:00."
|
||||
- JSON: `{ intent: "create_appointment", confidence: 0.95, entities: { patientName, datetime } }`
|
||||
|
||||
### Scenario 3: Create Appointment - Incomplete
|
||||
**Input**: "plan intake Marie"
|
||||
**Expected Claude Response**:
|
||||
- Text: "Voor welke datum en tijd wil je de intake voor Marie plannen?"
|
||||
- JSON: None (confidence < 0.7 due to missing datetime)
|
||||
|
||||
### Scenario 4: Reschedule - Time Based
|
||||
**Input**: "verzet 14:00 naar 15:00"
|
||||
**Expected Claude Response**:
|
||||
- Text: "Ik verzet de afspraak van 14:00 naar 15:00."
|
||||
- JSON: `{ intent: "reschedule_appointment", confidence: 0.92, entities: { identifier, newDatetime } }`
|
||||
|
||||
### Scenario 5: Cancel - Patient Based
|
||||
**Input**: "annuleer afspraak Jan"
|
||||
**Expected Claude Response**:
|
||||
- Text: Confirmation or disambiguation if multiple Jans
|
||||
- JSON: `{ intent: "cancel_appointment", entities: { identifier: { type: "patient", patientName: "Jan" } } }`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Prompt Engineering Techniques Used
|
||||
|
||||
1. **Clear Intent Definitions**: Each intent has triggers, entities, and actions clearly defined
|
||||
2. **Required/Optional Distinction**: Helps Claude decide when to ask clarification questions
|
||||
3. **Confidence Guidance**: Thresholds guide when to open artifacts vs ask questions
|
||||
4. **Concrete Examples**: 4 full examples with expected JSON structure
|
||||
5. **Error Cases**: Example 7 shows incomplete input handling
|
||||
6. **Entity Templates**: JSON structures show exact format expected
|
||||
7. **Natural Language Triggers**: Multiple trigger phrases per intent
|
||||
|
||||
---
|
||||
|
||||
## 💡 Design Decisions
|
||||
|
||||
### Why P2 Instead of P1?
|
||||
Agenda management is important but not as critical/frequent as dagnotitie (P1). Healthcare workers make notes constantly but schedule appointments less frequently.
|
||||
|
||||
### Why Include Defaults?
|
||||
- `appointmentType`: "behandeling" (most common case)
|
||||
- `location`: "praktijk" (most common location)
|
||||
|
||||
This reduces friction - users don't need to specify every detail.
|
||||
|
||||
### Why identifier Instead of encounterId?
|
||||
The `identifier` structure allows flexible matching:
|
||||
- By patient name: "annuleer afspraak Jan"
|
||||
- By time: "cancel 14:00"
|
||||
- By combination: "verzet Jan's afspraak"
|
||||
|
||||
The backend (Epic 3 API + Epic 4 UI) handles the disambiguation.
|
||||
|
||||
### Why Separate Examples for Complete/Incomplete?
|
||||
Shows Claude two paths:
|
||||
1. Complete data → Generate action (Example 6)
|
||||
2. Incomplete data → Ask question (Example 7)
|
||||
|
||||
This demonstrates the confidence threshold logic clearly.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
**E5.S3** - Error states
|
||||
- Add error handling for API failures
|
||||
- Add fallback links to `/epd/agenda`
|
||||
- Handle offline/network errors gracefully
|
||||
|
||||
---
|
||||
|
||||
## 📁 Files Modified
|
||||
|
||||
```
|
||||
✅ app/api/swift/chat/route.ts (Updated system prompt)
|
||||
✅ docs/swift/bouwplan-swift-agenda-planning.md (E5.S2 → Done)
|
||||
✅ docs/swift/implementation-e5-s2-chat-prompt.md (New documentation)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Implementation Status**: ✅ Complete
|
||||
**Ready for**: E5.S3 (Error states)
|
||||
**Prompt Length**: ~525 lines (was ~325 lines) - increased by ~60%
|
||||
@@ -1,399 +0,0 @@
|
||||
# Epic 5.S3 Implementation: Error States
|
||||
|
||||
**Story**: E5.S3 - Error states
|
||||
**Date**: 2025-12-27
|
||||
**Status**: ✅ Complete
|
||||
**Story Points**: 2 SP
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Objective
|
||||
|
||||
Add user-friendly error states to agenda functionality with fallback links to `/epd/agenda` and proper error messaging in Dutch.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Summary
|
||||
|
||||
### New Component Created
|
||||
|
||||
**`components/swift/artifacts/blocks/agenda-error-state.tsx`**
|
||||
|
||||
Created two reusable error components for consistent error handling across all agenda views:
|
||||
|
||||
1. **AgendaErrorState** - Full-page error state with retry button
|
||||
2. **AgendaErrorAlert** - Inline error alert for forms
|
||||
|
||||
### Updated Components
|
||||
|
||||
**`components/swift/artifacts/blocks/agenda-create-form.tsx`**
|
||||
- Replaced basic error display with `AgendaErrorAlert`
|
||||
- Added fallback link to full agenda
|
||||
- Added dismiss functionality
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Key Features
|
||||
|
||||
### 1. User-Friendly Error Messages
|
||||
|
||||
The `getUserFriendlyMessage()` function maps technical errors to Dutch user-facing messages:
|
||||
|
||||
| Error Type | Technical | User Message |
|
||||
|------------|-----------|--------------|
|
||||
| **Auth (401)** | "401 Unauthorized" | "Je sessie is verlopen. Log opnieuw in." |
|
||||
| **Not Found (404)** | "404 Not Found" | "De gevraagde afspraak kon niet worden gevonden." |
|
||||
| **Forbidden (403)** | "403 Forbidden" | "Je hebt geen toegang tot deze afspraak." |
|
||||
| **Server (500)** | "500 Internal Server Error" | "Er ging iets mis op de server. Probeer het opnieuw." |
|
||||
| **Network** | "Failed to fetch" | "Geen internetverbinding. Controleer je netwerkverbinding." |
|
||||
| **Timeout** | "Request timeout" | "De aanvraag duurde te lang. Probeer het opnieuw." |
|
||||
|
||||
### 2. Context-Aware Messages
|
||||
|
||||
Different default messages based on operation context:
|
||||
|
||||
```typescript
|
||||
context: 'query' → "Er ging iets mis bij het ophalen van je afspraken."
|
||||
context: 'create' → "Er ging iets mis bij het aanmaken van de afspraak."
|
||||
context: 'cancel' → "Er ging iets mis bij het annuleren van de afspraak."
|
||||
context: 'reschedule' → "Er ging iets mis bij het verzetten van de afspraak."
|
||||
```
|
||||
|
||||
### 3. Fallback Link to Full Agenda
|
||||
|
||||
All error states include a prominent link to `/epd/agenda`:
|
||||
|
||||
```tsx
|
||||
<Button onClick={() => window.location.href = '/epd/agenda'}>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
Open volledige agenda
|
||||
</Button>
|
||||
```
|
||||
|
||||
### 4. Automatic Auth Redirect
|
||||
|
||||
Auth errors (401) automatically redirect to `/login`:
|
||||
|
||||
```typescript
|
||||
if (isAuthError) {
|
||||
window.location.href = '/login';
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Retry Functionality
|
||||
|
||||
Optional retry button for recoverable errors:
|
||||
|
||||
```tsx
|
||||
<AgendaErrorState
|
||||
error={error}
|
||||
onRetry={() => fetchAppointments()}
|
||||
showFallbackLink={true}
|
||||
context="query"
|
||||
/>
|
||||
```
|
||||
|
||||
### 6. Dev-Only Technical Details
|
||||
|
||||
In development mode, shows collapsible technical details:
|
||||
|
||||
```tsx
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<details>
|
||||
<summary>Technische details (dev only)</summary>
|
||||
<pre>{error.stack}</pre>
|
||||
</details>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📐 Component API
|
||||
|
||||
### AgendaErrorState (Full-Page Error)
|
||||
|
||||
```typescript
|
||||
interface AgendaErrorStateProps {
|
||||
error: string | Error; // Error to display
|
||||
onRetry?: () => void; // Optional retry function
|
||||
showFallbackLink?: boolean; // Show link to /epd/agenda (default: true)
|
||||
context?: 'query' | 'create' | 'cancel' | 'reschedule';
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Example**:
|
||||
```tsx
|
||||
<AgendaErrorState
|
||||
error="Failed to fetch appointments"
|
||||
onRetry={() => refetch()}
|
||||
context="query"
|
||||
/>
|
||||
```
|
||||
|
||||
### AgendaErrorAlert (Inline Alert)
|
||||
|
||||
```typescript
|
||||
interface AgendaErrorAlertProps {
|
||||
error: string | Error; // Error to display
|
||||
onDismiss?: () => void; // Optional dismiss function
|
||||
showFallbackLink?: boolean; // Show link to /epd/agenda (default: false)
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Example**:
|
||||
```tsx
|
||||
{error && (
|
||||
<AgendaErrorAlert
|
||||
error={error}
|
||||
onDismiss={() => setError(null)}
|
||||
showFallbackLink={true}
|
||||
/>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Acceptance Criteria
|
||||
|
||||
| Criterion | Status | Implementation |
|
||||
|-----------|--------|----------------|
|
||||
| User-friendly error messages | ✅ | `getUserFriendlyMessage()` function |
|
||||
| Dutch language errors | ✅ | All messages in Dutch |
|
||||
| Fallback link to /epd/agenda | ✅ | "Open volledige agenda" button |
|
||||
| Auth error redirect | ✅ | Automatic redirect to /login |
|
||||
| Retry functionality | ✅ | Optional `onRetry` prop |
|
||||
| Context-aware messages | ✅ | Different messages per operation type |
|
||||
| Network error handling | ✅ | "Geen internetverbinding" message |
|
||||
| Server error handling | ✅ | "Er ging iets mis op de server" message |
|
||||
| TypeScript type safety | ✅ | Full type definitions |
|
||||
| Consistent styling | ✅ | Matches existing UI patterns |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI Design
|
||||
|
||||
### Full-Page Error State
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ │
|
||||
│ 🔴 (AlertCircle Icon) │
|
||||
│ │
|
||||
│ Er ging iets mis │
|
||||
│ │
|
||||
│ Er ging iets mis bij het │
|
||||
│ ophalen van je afspraken. │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ 🔄 Probeer │ │ 🔗 Open │ │
|
||||
│ │ opnieuw │ │ volledige │ │
|
||||
│ └──────────────┘ │ agenda │ │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Inline Error Alert
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ ⚠️ Er ging iets mis bij het │
|
||||
│ aanmaken van de afspraak. │
|
||||
│ Open volledige agenda → [×] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Scenarios
|
||||
|
||||
### Scenario 1: Network Error
|
||||
**Trigger**: Disconnect internet, try to create appointment
|
||||
**Expected**:
|
||||
- Message: "Geen internetverbinding. Controleer je netwerkverbinding."
|
||||
- Retry button enabled
|
||||
- Fallback link visible
|
||||
|
||||
### Scenario 2: Auth Error (401)
|
||||
**Trigger**: Expired session token
|
||||
**Expected**:
|
||||
- Automatic redirect to `/login`
|
||||
- No error component shown
|
||||
|
||||
### Scenario 3: Server Error (500)
|
||||
**Trigger**: Backend returns 500
|
||||
**Expected**:
|
||||
- Message: "Er ging iets mis op de server. Probeer het opnieuw."
|
||||
- Retry button enabled
|
||||
- Fallback link visible
|
||||
|
||||
### Scenario 4: Not Found (404)
|
||||
**Trigger**: Try to cancel non-existent appointment
|
||||
**Expected**:
|
||||
- Message: "De gevraagde afspraak kon niet worden gevonden."
|
||||
- Fallback link visible
|
||||
|
||||
### Scenario 5: Validation Error
|
||||
**Trigger**: Submit form with invalid data
|
||||
**Expected**:
|
||||
- Inline alert with specific validation message
|
||||
- Fallback link visible
|
||||
- Dismiss button works
|
||||
|
||||
### Scenario 6: Retry Success
|
||||
**Trigger**: Network error → reconnect → click retry
|
||||
**Expected**:
|
||||
- Retry function called
|
||||
- Error cleared on success
|
||||
- Content loads normally
|
||||
|
||||
---
|
||||
|
||||
## 💡 Design Decisions
|
||||
|
||||
### Why Two Components?
|
||||
|
||||
1. **AgendaErrorState**: For full-page failures (query, list loading)
|
||||
2. **AgendaErrorAlert**: For form-level errors (create, cancel, reschedule)
|
||||
|
||||
Different UI patterns for different contexts.
|
||||
|
||||
### Why Auto-Redirect for Auth Errors?
|
||||
|
||||
Auth errors (401) are not user-recoverable in the UI. User must log in again, so immediate redirect provides better UX than showing an error message.
|
||||
|
||||
### Why Show Fallback Link?
|
||||
|
||||
If Swift fails, users can always fall back to the classic agenda UI at `/epd/agenda`. This provides a safety net and reduces frustration.
|
||||
|
||||
### Why Context Parameter?
|
||||
|
||||
Different operations have different error messages. Context makes messages more specific and actionable:
|
||||
- Query failure → "bij het ophalen"
|
||||
- Create failure → "bij het aanmaken"
|
||||
- etc.
|
||||
|
||||
### Why Dev-Only Technical Details?
|
||||
|
||||
Technical stack traces are only useful for developers debugging issues. Production users should see user-friendly messages only.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Error Handling Best Practices
|
||||
|
||||
### 1. Always Use getUserFriendlyMessage()
|
||||
|
||||
```typescript
|
||||
// ❌ Bad - Technical error exposed to user
|
||||
setError(error.message);
|
||||
|
||||
// ✅ Good - User-friendly Dutch message
|
||||
const friendlyMessage = getUserFriendlyMessage(error, 'create');
|
||||
setError(friendlyMessage);
|
||||
```
|
||||
|
||||
### 2. Provide Context
|
||||
|
||||
```typescript
|
||||
// ❌ Bad - Generic error
|
||||
<AgendaErrorState error={error} />
|
||||
|
||||
// ✅ Good - Context-specific error
|
||||
<AgendaErrorState error={error} context="query" />
|
||||
```
|
||||
|
||||
### 3. Offer Retry When Possible
|
||||
|
||||
```typescript
|
||||
// ❌ Bad - No recovery path
|
||||
<AgendaErrorState error={error} />
|
||||
|
||||
// ✅ Good - User can retry
|
||||
<AgendaErrorState
|
||||
error={error}
|
||||
onRetry={() => refetchAppointments()}
|
||||
/>
|
||||
```
|
||||
|
||||
### 4. Use Inline Alerts for Forms
|
||||
|
||||
```typescript
|
||||
// ❌ Bad - Full-page error for form validation
|
||||
<AgendaErrorState error="Patient required" />
|
||||
|
||||
// ✅ Good - Inline alert in form
|
||||
<AgendaErrorAlert
|
||||
error="Selecteer a.u.b. een patiënt."
|
||||
onDismiss={() => setError(null)}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Error Message Coverage
|
||||
|
||||
Covered error types:
|
||||
- ✅ Authentication (401)
|
||||
- ✅ Authorization (403)
|
||||
- ✅ Not Found (404)
|
||||
- ✅ Server Error (500)
|
||||
- ✅ Network/Offline
|
||||
- ✅ Timeout
|
||||
- ✅ Validation
|
||||
- ✅ Generic/Unknown
|
||||
|
||||
All with Dutch user-friendly messages.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Files Modified/Created
|
||||
|
||||
```
|
||||
✅ components/swift/artifacts/blocks/agenda-error-state.tsx (NEW - 225 lines)
|
||||
├── AgendaErrorState component
|
||||
├── AgendaErrorAlert component
|
||||
└── getUserFriendlyMessage() utility
|
||||
|
||||
✅ components/swift/artifacts/blocks/agenda-create-form.tsx (UPDATED)
|
||||
└── Replaced basic error with AgendaErrorAlert
|
||||
|
||||
✅ docs/swift/bouwplan-swift-agenda-planning.md (UPDATED)
|
||||
└── E5.S3 → Done, Epic 5 → Done
|
||||
|
||||
✅ docs/swift/implementation-e5-s3-error-states.md (NEW)
|
||||
└── This documentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Impact
|
||||
|
||||
### Before E5.S3
|
||||
- ❌ Technical error messages exposed to users
|
||||
- ❌ No fallback when errors occur
|
||||
- ❌ No retry functionality
|
||||
- ❌ Inconsistent error handling
|
||||
|
||||
### After E5.S3
|
||||
- ✅ User-friendly Dutch error messages
|
||||
- ✅ Always provide fallback link to full agenda
|
||||
- ✅ Retry button for recoverable errors
|
||||
- ✅ Consistent error handling across all views
|
||||
- ✅ Auto-redirect for auth errors
|
||||
- ✅ Context-aware messaging
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
**Epic 6 - QA & Docs**
|
||||
- E6.S1: Manual test checklist (20 scenarios from build plan)
|
||||
- E6.S2: Docs update (bouwplan + release note)
|
||||
- E6.S3: Regression checks (Swift + klassieke agenda)
|
||||
|
||||
---
|
||||
|
||||
**Implementation Status**: ✅ Complete
|
||||
**Epic 5 Status**: ✅ Complete (All 3 stories done)
|
||||
**Ready for**: Epic 6 (QA & Documentation)
|
||||
@@ -1,34 +0,0 @@
|
||||
# Epic 2 Review Findings
|
||||
|
||||
Context: quick review of Epic 2 changes (date/time parsing + agenda entity extraction).
|
||||
|
||||
## Findings
|
||||
1. High - Patient name detection is too permissive for agenda intents and can misclassify appointment tokens as names.
|
||||
- Details: create/cancel/reschedule extraction uses `filteredWords` + `isLikelyName`; tokens like "intake", "morgen", or times can be treated as names.
|
||||
- Impact: wrong patient selected or wrong appointment targeted.
|
||||
- Files: `lib/swift/entity-extractor.ts`
|
||||
- Suggestion: skip tokens that parse as date/time or match appointment/location keywords before `isLikelyName`.
|
||||
|
||||
2. Medium - Agenda query defaults to "vandaag" when no date is present (e.g., "volgende afspraak").
|
||||
- Impact: only today is queried, which can miss the actual next appointment.
|
||||
- Files: `lib/swift/entity-extractor.ts`
|
||||
- Suggestion: leave `dateRange` undefined or mark "from now" and let backend decide.
|
||||
|
||||
3. Medium - `ExtractedEntities` now carries Date objects, but the classify API returns JSON, so Dates become strings.
|
||||
- Impact: downstream date ops may break on serialized strings.
|
||||
- Files: `lib/swift/types.ts` (see API flow in `app/api/intent/classify/route.ts`)
|
||||
- Suggestion: use ISO strings in the DTO or normalize in the API response.
|
||||
|
||||
4. Medium - Type drift between lib and store: the store `ExtractedEntities` does not include the new agenda structures.
|
||||
- Impact: UI usage may drop agenda fields or require unsafe casting.
|
||||
- Files: `stores/swift-store.ts`, `lib/swift/types.ts`
|
||||
- Suggestion: align store types with lib types (or re-export the shared type).
|
||||
|
||||
5. Low - AI fallback path does not run local extractor; low-confidence agenda intents lose structured entities.
|
||||
- Impact: less reliable agenda prefill when AI is used.
|
||||
- Files: `app/api/intent/classify/route.ts`, `lib/swift/intent-classifier-ai.ts`
|
||||
- Suggestion: post-process AI results with local extractor for agenda intents.
|
||||
|
||||
## Testing / QA notes
|
||||
- Untracked tests exist: `lib/swift/__tests__/date-time-parser.test.ts`.
|
||||
- Manual scripts exist: `lib/swift/verify-parser.ts` and `lib/swift/verify-entity-extraction.ts` (not run here).
|
||||
@@ -1,474 +0,0 @@
|
||||
# Test Plan - Epic 3: Backend Integration
|
||||
|
||||
**Epic**: Swift Agenda Planning - Backend Integration
|
||||
**Version**: 1.0
|
||||
**Date**: 2025-12-27
|
||||
**Status**: ✅ Implementation Complete
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
Epic 3 implements the backend API layer for Swift Agenda Planning, providing RESTful endpoints for:
|
||||
- Querying appointments by date range
|
||||
- Creating new appointments
|
||||
- Canceling appointments
|
||||
- Rescheduling appointments
|
||||
- Searching for patients (for disambiguation)
|
||||
|
||||
All endpoints include:
|
||||
- ✅ Authentication via Supabase Auth
|
||||
- ✅ Input validation with Zod schemas
|
||||
- ✅ Dutch error messages
|
||||
- ✅ Security checks (user owns the resource)
|
||||
- ✅ Reuse of existing server actions
|
||||
|
||||
---
|
||||
|
||||
## 📋 Implementation Summary
|
||||
|
||||
### Story E3.S1: Agenda Query API ✅
|
||||
**File**: `app/api/swift/agenda/route.ts`
|
||||
- **Endpoint**: `GET /api/swift/agenda?start=YYYY-MM-DD&end=YYYY-MM-DD`
|
||||
- **Auth**: Required (Supabase)
|
||||
- **Filters**: Automatically filters by current user's practitioner_id
|
||||
- **Response**: List of appointments (encounters) with patient details
|
||||
|
||||
### Story E3.S2: Create Appointment API ✅
|
||||
**File**: `app/api/swift/agenda/create/route.ts`
|
||||
- **Endpoint**: `POST /api/swift/agenda/create`
|
||||
- **Body**: `{ patientId, datetime: { date, time }, type, location, notes? }`
|
||||
- **Validation**: Date cannot be in past, appointment type and location must be valid
|
||||
- **Mapping**:
|
||||
- `type` → FHIR encounter typeCode
|
||||
- `location` → FHIR classCode (AMB/VR/HH)
|
||||
- Duration: 1 hour default
|
||||
|
||||
### Story E3.S3: Cancel/Reschedule APIs ✅
|
||||
**Files**:
|
||||
- `app/api/swift/agenda/cancel/route.ts`
|
||||
- `app/api/swift/agenda/reschedule/route.ts`
|
||||
|
||||
**Cancel Endpoint**: `POST /api/swift/agenda/cancel`
|
||||
- **Body**: `{ encounterId }`
|
||||
- **Security**: Verifies user owns the appointment
|
||||
- **Validation**: Cannot cancel already cancelled appointment
|
||||
- **Action**: Soft delete (status → 'cancelled')
|
||||
|
||||
**Reschedule Endpoint**: `POST /api/swift/agenda/reschedule`
|
||||
- **Body**: `{ encounterId, newDatetime: { date, time } }`
|
||||
- **Security**: Verifies user owns the appointment
|
||||
- **Validation**: Cannot reschedule to past, cannot reschedule cancelled appointments
|
||||
- **Duration**: Preserves original appointment duration
|
||||
|
||||
### Story E3.S4: Patient Search API ✅
|
||||
**File**: `app/api/swift/patients/search/route.ts`
|
||||
- **Endpoint**: `GET /api/swift/patients/search?q=<query>`
|
||||
- **Search**: Fuzzy match on name_family and name_given
|
||||
- **Limit**: 10 results
|
||||
- **Response**: Array of patients with id, name, bsn, birthDate
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Manual Test Scenarios
|
||||
|
||||
### Test Category 1: Agenda Query (E3.S1)
|
||||
|
||||
#### Test 1.1: Query appointments for today
|
||||
**Prerequisites**: User is logged in, has appointments for today
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/agenda?start=2025-12-27&end=2025-12-27' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 200
|
||||
- Response: `{ appointments: [...], count: N, dateRange: { start, end } }`
|
||||
- Appointments filtered by current user
|
||||
|
||||
#### Test 1.2: Query with missing parameters
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/agenda?start=2025-12-27' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "start en end parameters zijn verplicht"
|
||||
|
||||
#### Test 1.3: Query with invalid date format
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/agenda?start=invalid&end=2025-12-27' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "start moet een geldige datum zijn"
|
||||
|
||||
#### Test 1.4: Query without authentication
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/agenda?start=2025-12-27&end=2025-12-27'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 401
|
||||
- Error: "Niet geautoriseerd. Log opnieuw in."
|
||||
|
||||
---
|
||||
|
||||
### Test Category 2: Create Appointment (E3.S2)
|
||||
|
||||
#### Test 2.1: Create valid appointment
|
||||
**Prerequisites**: User is logged in, valid patient ID available
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/create' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"patientId": "<uuid>",
|
||||
"datetime": {
|
||||
"date": "2025-12-28",
|
||||
"time": "14:00"
|
||||
},
|
||||
"type": "intake",
|
||||
"location": "praktijk",
|
||||
"notes": "Eerste afspraak"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 201
|
||||
- Response: `{ success: true, encounterId: "<uuid>", appointment: {...} }`
|
||||
- Appointment visible in agenda
|
||||
|
||||
#### Test 2.2: Create appointment in the past
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/create' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"patientId": "<uuid>",
|
||||
"datetime": {
|
||||
"date": "2020-01-01",
|
||||
"time": "14:00"
|
||||
},
|
||||
"type": "intake",
|
||||
"location": "praktijk"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "Kan geen afspraken in het verleden maken"
|
||||
|
||||
#### Test 2.3: Create appointment with invalid type
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/create' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"patientId": "<uuid>",
|
||||
"datetime": {
|
||||
"date": "2025-12-28",
|
||||
"time": "14:00"
|
||||
},
|
||||
"type": "invalid_type",
|
||||
"location": "praktijk"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "type moet een geldig afspraaktype zijn"
|
||||
|
||||
#### Test 2.4: Create appointment with missing required fields
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/create' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"patientId": "<uuid>",
|
||||
"datetime": {
|
||||
"date": "2025-12-28"
|
||||
},
|
||||
"type": "intake",
|
||||
"location": "praktijk"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: Contains validation error about missing time
|
||||
|
||||
---
|
||||
|
||||
### Test Category 3: Cancel Appointment (E3.S3a)
|
||||
|
||||
#### Test 3.1: Cancel valid appointment
|
||||
**Prerequisites**: User has an upcoming appointment
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/cancel' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "<uuid>"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 200
|
||||
- Response: `{ success: true, encounterId: "<uuid>", message: "Afspraak succesvol geannuleerd" }`
|
||||
- Appointment status updated to 'cancelled'
|
||||
|
||||
#### Test 3.2: Cancel already cancelled appointment
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/cancel' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "<uuid-of-cancelled-appointment>"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "Deze afspraak is al geannuleerd"
|
||||
|
||||
#### Test 3.3: Cancel non-existent appointment
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/cancel' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "00000000-0000-0000-0000-000000000000"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 404
|
||||
- Error: "Afspraak niet gevonden"
|
||||
|
||||
#### Test 3.4: Cancel appointment owned by another user
|
||||
**Prerequisites**: Have another user's appointment ID
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/cancel' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "<uuid-of-other-users-appointment>"
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 403
|
||||
- Error: "Je hebt geen toegang tot deze afspraak"
|
||||
|
||||
---
|
||||
|
||||
### Test Category 4: Reschedule Appointment (E3.S3b)
|
||||
|
||||
#### Test 4.1: Reschedule valid appointment
|
||||
**Prerequisites**: User has an upcoming appointment
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/reschedule' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "<uuid>",
|
||||
"newDatetime": {
|
||||
"date": "2025-12-29",
|
||||
"time": "15:00"
|
||||
}
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 200
|
||||
- Response: `{ success: true, encounterId: "<uuid>", appointment: {...}, message: "Afspraak succesvol verzet" }`
|
||||
- Appointment period_start and period_end updated
|
||||
|
||||
#### Test 4.2: Reschedule to past date
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/reschedule' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "<uuid>",
|
||||
"newDatetime": {
|
||||
"date": "2020-01-01",
|
||||
"time": "15:00"
|
||||
}
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "Kan geen afspraken in het verleden verzetten"
|
||||
|
||||
#### Test 4.3: Reschedule cancelled appointment
|
||||
```bash
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/reschedule' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "<uuid-of-cancelled-appointment>",
|
||||
"newDatetime": {
|
||||
"date": "2025-12-29",
|
||||
"time": "15:00"
|
||||
}
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "Kan een geannuleerde afspraak niet verzetten"
|
||||
|
||||
#### Test 4.4: Reschedule with preserved duration
|
||||
**Prerequisites**: Create an appointment with 2-hour duration
|
||||
```bash
|
||||
# First, create appointment manually with 2-hour duration
|
||||
# Then reschedule it
|
||||
curl -X POST 'http://localhost:3000/api/swift/agenda/reschedule' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Cookie: <session-cookie>' \
|
||||
-d '{
|
||||
"encounterId": "<uuid>",
|
||||
"newDatetime": {
|
||||
"date": "2025-12-29",
|
||||
"time": "10:00"
|
||||
}
|
||||
}'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 200
|
||||
- Response period_end is 2 hours after period_start (preserves original duration)
|
||||
|
||||
---
|
||||
|
||||
### Test Category 5: Patient Search (E3.S4)
|
||||
|
||||
#### Test 5.1: Search for existing patient
|
||||
**Prerequisites**: Patient "Jan de Vries" exists in database
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/patients/search?q=jan' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 200
|
||||
- Response: `{ patients: [{id, name, bsn, birthDate}], count: N, query: "jan" }`
|
||||
- Results include matching patients
|
||||
|
||||
#### Test 5.2: Search with partial name
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/patients/search?q=vri' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 200
|
||||
- Response includes patients with "vri" in their name (e.g., "de Vries")
|
||||
|
||||
#### Test 5.3: Search with no matches
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/patients/search?q=zzzzzzz' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 200
|
||||
- Response: `{ patients: [], count: 0, query: "zzzzzzz" }`
|
||||
|
||||
#### Test 5.4: Search without query parameter
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/patients/search' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "Query parameter 'q' is verplicht"
|
||||
|
||||
#### Test 5.5: Search with empty query
|
||||
```bash
|
||||
curl -X GET 'http://localhost:3000/api/swift/patients/search?q=' \
|
||||
-H 'Cookie: <session-cookie>'
|
||||
```
|
||||
**Expected**:
|
||||
- Status: 400
|
||||
- Error: "Zoekterm mag niet leeg zijn"
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Checklist
|
||||
|
||||
| Check | Status | Notes |
|
||||
|-------|--------|-------|
|
||||
| All endpoints require authentication | ✅ | Returns 401 if not authenticated |
|
||||
| User can only access their own appointments | ✅ | Filtered by practitioner_id |
|
||||
| User cannot cancel/reschedule others' appointments | ✅ | Ownership verification in place |
|
||||
| Input validation on all endpoints | ✅ | Zod schemas with Dutch error messages |
|
||||
| SQL injection prevention | ✅ | Using Supabase client with parameterized queries |
|
||||
| XSS prevention | ✅ | No direct HTML rendering |
|
||||
| CSRF protection | ✅ | Next.js built-in CSRF protection |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Known Issues / Edge Cases
|
||||
|
||||
### Issue 1: Patient Search Query Performance
|
||||
- **Description**: The patient search uses `ilike` which may be slow on large datasets
|
||||
- **Mitigation**: Limited to 10 results
|
||||
- **Future**: Consider adding database index on name_family/name_given
|
||||
|
||||
### Issue 2: Timezone Handling
|
||||
- **Description**: All dates stored in UTC, client must handle timezone conversion
|
||||
- **Current**: Using ISO string format
|
||||
- **Future**: Consider explicit timezone handling in API
|
||||
|
||||
### Issue 3: Appointment Conflicts
|
||||
- **Description**: No conflict detection when creating/rescheduling appointments
|
||||
- **Status**: Out of scope for MVP
|
||||
- **Future**: Add conflict warning in Epic 4 (UI layer)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Test Results Summary
|
||||
|
||||
| Story | Total Tests | Passed | Failed | Blocked | Coverage |
|
||||
|-------|-------------|--------|--------|---------|----------|
|
||||
| E3.S1 | 4 | - | - | - | Auth, Validation, Happy Path |
|
||||
| E3.S2 | 4 | - | - | - | Validation, Security, Happy Path |
|
||||
| E3.S3a | 4 | - | - | - | Security, Validation, Happy Path |
|
||||
| E3.S3b | 4 | - | - | - | Security, Validation, Duration |
|
||||
| E3.S4 | 5 | - | - | - | Search, Validation, Empty State |
|
||||
| **Total** | **21** | **TBD** | **TBD** | **TBD** | **All scenarios** |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
After manual testing is complete:
|
||||
|
||||
1. ✅ **Epic 3 Complete** → Move to Epic 4 (AgendaBlock UI)
|
||||
2. 📝 **Update Build Plan** → Mark Epic 3 stories as "Done"
|
||||
3. 🧪 **Integration Testing** → Test with actual UI when Epic 4 is ready
|
||||
4. 📚 **API Documentation** → Generate OpenAPI/Swagger docs (optional)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Files Created
|
||||
|
||||
```
|
||||
app/api/swift/
|
||||
├── agenda/
|
||||
│ ├── route.ts # E3.S1: Query endpoint
|
||||
│ ├── create/
|
||||
│ │ └── route.ts # E3.S2: Create endpoint
|
||||
│ ├── cancel/
|
||||
│ │ └── route.ts # E3.S3a: Cancel endpoint
|
||||
│ └── reschedule/
|
||||
│ └── route.ts # E3.S3b: Reschedule endpoint
|
||||
└── patients/
|
||||
└── search/
|
||||
└── route.ts # E3.S4: Patient search endpoint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Lessons Learned
|
||||
|
||||
1. **Zod Validation**: Use `validation.error.issues` not `validation.error.errors`
|
||||
2. **Database Column Names**: Check generated types carefully (e.g., `identifier_bsn` vs `bsn`)
|
||||
3. **Security First**: Always verify resource ownership before mutations
|
||||
4. **Reuse Actions**: Existing server actions (`getEncounters`, `createEncounter`, etc.) work perfectly
|
||||
5. **Type Safety**: TypeScript catches errors early - run `pnpm exec tsc` before testing
|
||||
|
||||
---
|
||||
|
||||
**Test Plan Status**: ✅ Ready for Manual Testing
|
||||
**Implementation Status**: ✅ Complete (All 4 stories)
|
||||
**Type Check**: ✅ Passing
|
||||
**Next Epic**: Epic 4 - AgendaBlock UI
|
||||
@@ -1,666 +0,0 @@
|
||||
# 🎨 Swift v3.0 Redesign — Van Command Center naar Medical Scribe
|
||||
|
||||
**Datum:** 27-12-2024
|
||||
**Versie:** v3.0 Design Specification
|
||||
**Samengesteld door:** AI Assistant
|
||||
|
||||
---
|
||||
|
||||
## 📋 Inhoudsopgave
|
||||
|
||||
1. [Executive Summary](#executive-summary)
|
||||
2. [Van v2.1 naar v3.0: Wat verandert](#van-v21-naar-v30-wat-verandert)
|
||||
3. [Huidige UX/Styling (v2.1)](#huidige-uxstyling-v21)
|
||||
4. [Nieuwe UX/Styling (v3.0)](#nieuwe-uxstyling-v30)
|
||||
5. [Wat blijft hetzelfde](#wat-blijft-hetzelfde)
|
||||
6. [Wat wijzigt](#wat-wijzigt)
|
||||
7. [Implementatie Strategie](#implementatie-strategie)
|
||||
8. [Design Tokens & Styling](#design-tokens--styling)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Swift v3.0 transformeert van een **command-line style interface** naar een **conversational medical scribe chatbot**. De kernprincipes van Ephemeral UI blijven behouden, maar de interactie verschuift van transactioneel naar relationeel.
|
||||
|
||||
### Kernveranderingen
|
||||
|
||||
| Aspect | v2.1 (Command Center) | v3.0 (Medical Scribe) |
|
||||
|--------|----------------------|----------------------|
|
||||
| **Input model** | Command-line ("notitie jan medicatie") | Natuurlijke conversatie ("Ik heb medicatie gegeven aan Jan") |
|
||||
| **UI paradigma** | Centered blocks (ephemeral) | Split-screen: Chat (40%) + Artifacts (60%) |
|
||||
| **Context** | Per commando | Doorlopende conversatiegeschiedenis |
|
||||
| **AI rol** | Intent classifier | Converserende medical scribe |
|
||||
| **Interactie** | Transactioneel (one-shot) | Relationeel (follow-up mogelijk) |
|
||||
|
||||
### Wat blijft
|
||||
|
||||
✅ **Ephemeral UI principe** — UI verschijnt/verdwijnt wanneer nodig
|
||||
✅ **Alle bestaande blocks** — DagnotatieBlock, ZoekenBlock, OverdrachtBlock, etc.
|
||||
✅ **Color system** — Shift colors, category colors, design tokens
|
||||
✅ **Keyboard shortcuts** — ⌘K, Escape, Enter, 1-9
|
||||
✅ **Voice input** — Deepgram integratie blijft werken
|
||||
✅ **Context Bar** — Shift, patient, user info
|
||||
|
||||
### Wat wijzigt
|
||||
|
||||
🔄 **Layout** — 4-zone → Split-screen (chat + artifacts)
|
||||
🔄 **Input** — Command input → Chat input met conversatie
|
||||
🔄 **Canvas Area** → Artifact Area (rechts, 60%)
|
||||
🔄 **Recent Strip** → Optioneel (kan geïntegreerd in chat)
|
||||
🔄 **AI Engine** — Intent classifier → Medical scribe chatbot
|
||||
|
||||
---
|
||||
|
||||
## Van v2.1 naar v3.0: Wat verandert
|
||||
|
||||
### v2.1 Layout (Huidig)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🕐 Ochtend | 8 ptn Jan de Vries ▼ 👤 SV │ ← Context Bar (48px)
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ACTIVE BLOCK │ │ ← Canvas Area (flex)
|
||||
│ │ (centered) │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────┘ │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Recent: [📝 Jan-Med] [🔄 Overdracht] [🔍 Marie] │ ← Recent Strip (48px)
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 🎤 Typ of spreek wat je wilt doen... ⌘K │ ← Command Input (64px)
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### v3.0 Layout (Nieuw)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 🕐 Ochtend | 8 ptn Jan de Vries ▼ 👤 SV │ ← Context Bar (48px)
|
||||
├──────────────────────────────┬──────────────────────────────────┤
|
||||
│ │ │
|
||||
│ CHAT PANEL (40%) │ ARTIFACT AREA (60%) │
|
||||
│ │ │
|
||||
│ 👤 "Ik heb net medicatie │ ┌────────────────────────────┐ │
|
||||
│ gegeven aan Jan" │ │ 📝 Dagnotitie │ │
|
||||
│ │ │ │ │
|
||||
│ 🤖 Ik maak een dagnotitie │ │ Patiënt: Jan de Vries ✓ │ │
|
||||
│ voor Jan de Vries. │ │ Categorie: Medicatie ✓ │ │
|
||||
│ Categorie: Medicatie. │ │ │ │
|
||||
│ │ │ [Opslaan] │ │
|
||||
│ Wil je nog iets │ └────────────────────────────┘ │
|
||||
│ toevoegen? │ │
|
||||
│ │ │
|
||||
│ 👤 "Nee, opslaan" │ │
|
||||
│ │ │
|
||||
│ 🤖 ✓ Notitie opgeslagen │ │
|
||||
│ │ │
|
||||
├──────────────────────────────┤ │
|
||||
│ 💬 Typ of spreek... 🎤 │ │ ← Chat Input (64px)
|
||||
└──────────────────────────────┴──────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Huidige UX/Styling (v2.1)
|
||||
|
||||
### Layout Zones
|
||||
|
||||
| Zone | Hoogte | Functie | Component |
|
||||
|------|--------|---------|-----------|
|
||||
| **Context Bar** | 48px (h-12) | Dienst, actieve patiënt, user | `context-bar.tsx` |
|
||||
| **Canvas Area** | flex-1 | Waar blocks verschijnen (centered) | `canvas-area.tsx` |
|
||||
| **Recent Strip** | 48px (h-12) | Laatste acties, quick access | `recent-strip.tsx` |
|
||||
| **Command Input** | 64px (h-16) | Command-line style input + voice | `command-input.tsx` |
|
||||
|
||||
### Context Bar (Blijft grotendeels hetzelfde)
|
||||
|
||||
**Huidige implementatie:**
|
||||
```tsx
|
||||
<header className="h-12 border-b border-slate-200 flex items-center px-4 justify-between">
|
||||
{/* Left: Logo + Shift */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/epd">Swift</Link>
|
||||
<ShiftIcon /> {/* Sunrise/Sun/Sunset/Moon */}
|
||||
<span>{shiftLabel}</span> {/* Ochtenddienst/Middagdienst/etc. */}
|
||||
</div>
|
||||
|
||||
{/* Center: Active Patient */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar>{initials}</Avatar>
|
||||
<span>{patientName}</span>
|
||||
<button onClick={clearPatient}><X /></button>
|
||||
</div>
|
||||
|
||||
{/* Right: User */}
|
||||
<div className="flex items-center gap-2">
|
||||
<User />
|
||||
<span>Verpleegkundige</span>
|
||||
</div>
|
||||
</header>
|
||||
```
|
||||
|
||||
**Dienst kleuren:**
|
||||
| Dienst | Tijd | Kleur | Icon |
|
||||
|--------|------|-------|------|
|
||||
| Ochtend | 07:00-15:00 | `text-amber-600` (#F59E0B) | Sunrise |
|
||||
| Middag | 15:00-23:00 | `text-yellow-600` (#3B82F6) | Sun |
|
||||
| Avond | 15:00-23:00 | `text-orange-600` | Sunset |
|
||||
| Nacht | 23:00-07:00 | `text-indigo-600` (#6366F1) | Moon |
|
||||
|
||||
### Command Input (Wijzigt naar Chat Input)
|
||||
|
||||
**Huidige states:**
|
||||
- **Default**: "Typ of spreek wat je wilt doen..." + mic icon
|
||||
- **Typing**: Tekst + cursor
|
||||
- **Listening**: 🔴 + waveform + live transcript
|
||||
- **Processing**: "Even kijken wat je bedoelt..."
|
||||
|
||||
**Styling:**
|
||||
```css
|
||||
height: 64px (h-16)
|
||||
background: #FFFFFF
|
||||
border: 1px #E2E8F0, 2px #3B82F6 (focus)
|
||||
border-radius: 16px
|
||||
shadow: lg
|
||||
font-size: 18px
|
||||
position: fixed bottom, 24px padding
|
||||
```
|
||||
|
||||
### Block Styling (Blijft hetzelfde)
|
||||
|
||||
**Block sizes:**
|
||||
| Size | Max-width | Use case |
|
||||
|------|-----------|----------|
|
||||
| Small | 480px | Quick entry (notitie, zoeken, meting) |
|
||||
| Medium | 640px | Forms (rapportage, behandelplan) |
|
||||
| Large | 900px | Overzichten (overdracht, patiëntenlijst) |
|
||||
| XLarge | 1100px | Wizards (intake volledig) |
|
||||
|
||||
**Categorie kleuren (verpleegkundig):**
|
||||
| Categorie | Background | Accent | Icon | Keyboard |
|
||||
|-----------|------------|--------|------|----------|
|
||||
| Medicatie | `#FEF6DC` | `#F59E0B` | 💊 | 1 |
|
||||
| ADL | `#E8F8EF` | `#16A34A` | 🍽️ | 2 |
|
||||
| Observatie | `#EFF6FF` | `#3B82F6` | 👁️ | 3 |
|
||||
| Incident | `#FEF2F2` | `#DC2626` | ⚠️ | 4 |
|
||||
| Algemeen | `#F1F5F9` | `#64748B` | 💬 | 5 |
|
||||
|
||||
### Keyboard Shortcuts (Blijft hetzelfde)
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `⌘K` / `Ctrl+K` | Focus command input (blijft focus chat input) |
|
||||
| `Escape` | Close active block (blijft) |
|
||||
| `Enter` | Submit (blijft) |
|
||||
| `⌘Enter` / `Ctrl+Enter` | Quick submit (blijft) |
|
||||
| `↑` `↓` | Navigate results / history |
|
||||
| `1-9` | Quick select in FallbackPicker |
|
||||
| `Space` (empty input) | Start voice (blijft) |
|
||||
|
||||
### Voice Input (Blijft hetzelfde)
|
||||
|
||||
**Flow:**
|
||||
1. User klikt mic of drukt spatie (bij lege input)
|
||||
2. Deepgram start streaming transcription
|
||||
3. Live transcript verschijnt in input field
|
||||
4. Pauze detectie (1.5 sec stilte) → auto-submit
|
||||
5. Of user klikt "Stop" → submit
|
||||
|
||||
---
|
||||
|
||||
## Nieuwe UX/Styling (v3.0)
|
||||
|
||||
### Layout Zones (Gewijzigd)
|
||||
|
||||
| Zone | Breedte | Functie | Component |
|
||||
|------|---------|---------|-----------|
|
||||
| **Context Bar** | 100% x 48px | Dienst, actieve patiënt, user | `context-bar.tsx` (ongewijzigd) |
|
||||
| **Chat Panel** | 40% x flex | Conversatie met medical scribe | `chat-panel.tsx` (nieuw) |
|
||||
| **Artifact Area** | 60% x flex | Waar blocks/artifacts verschijnen | `artifact-area.tsx` (nieuw) |
|
||||
| **Chat Input** | 40% x 64px | Conversational input + voice | `chat-input.tsx` (herzien) |
|
||||
|
||||
### Chat Panel (Nieuw)
|
||||
|
||||
**Message Types:**
|
||||
|
||||
| Type | Alignment | Background | Border | Icon |
|
||||
|------|-----------|------------|--------|------|
|
||||
| User message | Right | Amber-50 (#FFFBEB) | Amber-200 | 👤 |
|
||||
| Assistant text | Left | Slate-100 (#F1F5F9) | Slate-200 | 🤖 |
|
||||
| Assistant action | Left | Slate-100 | Slate-200 | 📝/🔍/🔄 (klikbaar) |
|
||||
| System message | Center | - | - | ✓ |
|
||||
| Error message | Left | Red-50 | Red-200 | ⚠️ |
|
||||
|
||||
**Message Styling:**
|
||||
```css
|
||||
/* User message */
|
||||
.message-user {
|
||||
align-self: flex-end;
|
||||
background: #FFFBEB;
|
||||
border: 1px solid #FED7AA;
|
||||
border-radius: 16px;
|
||||
border-top-right-radius: 4px;
|
||||
max-width: 80%;
|
||||
padding: 12px 16px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* Assistant message */
|
||||
.message-assistant {
|
||||
align-self: flex-start;
|
||||
background: #F1F5F9;
|
||||
border: 1px solid #CBD5E1;
|
||||
border-radius: 16px;
|
||||
border-top-left-radius: 4px;
|
||||
max-width: 80%;
|
||||
padding: 12px 16px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
```
|
||||
|
||||
**Gedrag:**
|
||||
- Auto-scroll naar laatste message
|
||||
- Scroll-lock wanneer gebruiker omhoog scrollt
|
||||
- "Scroll to bottom" knop bij nieuwe messages (↓)
|
||||
- Max 100 messages in view (pagination voor oudere)
|
||||
- Streaming indicator tijdens AI response (pulsating dots)
|
||||
|
||||
### Chat Input (Herzien)
|
||||
|
||||
**States (blijven grotendeels hetzelfde):**
|
||||
|
||||
| State | Weergave | Trigger |
|
||||
|-------|----------|---------|
|
||||
| Default | "Typ of spreek..." + mic icon | — |
|
||||
| Typing | Cursor + getypte tekst | Keyboard input |
|
||||
| Listening | 🔴 + waveform + live transcript | Mic click of spatie |
|
||||
| Processing | Disabled, "Denkt na..." | Na submit |
|
||||
| Streaming | Disabled, streaming in chat | Tijdens AI response |
|
||||
|
||||
**Keyboard Shortcuts (uitgebreid):**
|
||||
|
||||
| Key | Actie |
|
||||
|-----|-------|
|
||||
| `Enter` | Submit message |
|
||||
| `⌘Enter` / `Ctrl+Enter` | Force submit (ook tijdens streaming) |
|
||||
| `Escape` | Clear input / cancel voice |
|
||||
| `↑` | Vorige message (edit history) |
|
||||
| `Space` (leeg) | Start voice recording |
|
||||
| `⌘K` | Focus input |
|
||||
|
||||
### Artifact Area (Nieuw)
|
||||
|
||||
**Layout opties:**
|
||||
|
||||
| Situatie | Weergave |
|
||||
|----------|----------|
|
||||
| Geen artifact | Placeholder met voorbeelden |
|
||||
| Eén artifact | Full width, centered |
|
||||
| Meerdere artifacts | Tabs bovenaan (max 3) |
|
||||
|
||||
**Placeholder State:**
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 💬 Artifacts verschijnen hier │
|
||||
│ │
|
||||
│ Vraag me iets, bijvoorbeeld: │
|
||||
│ • "Notitie voor Jan: medicatie gegeven" │
|
||||
│ • "Zoek Marie van den Berg" │
|
||||
│ • "Maak overdracht voor deze dienst" │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Artifact Lifecycle:**
|
||||
1. Intent detected in chat → AI response: "Ik maak een dagnotitie..."
|
||||
2. Artifact appears → Slide-in animation (200ms, from right)
|
||||
3. Pre-filled met extracted entities
|
||||
4. User interacts → Edits fields, kan blijven chatten
|
||||
5. User saves → Toast + chat confirmation + artifact closes (optioneel)
|
||||
|
||||
**Artifact Animation:**
|
||||
```css
|
||||
@keyframes artifact-enter {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.artifact-enter {
|
||||
animation: artifact-enter 200ms ease-out;
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Breakpoints (Nieuw)
|
||||
|
||||
| Viewport | Chat Panel | Artifact Area |
|
||||
|----------|------------|---------------|
|
||||
| Desktop (>1200px) | 40% | 60% |
|
||||
| Tablet (768-1200px) | 45% | 55% |
|
||||
| Mobile (<768px) | Full screen toggle | Full screen toggle |
|
||||
|
||||
---
|
||||
|
||||
## Wat blijft hetzelfde
|
||||
|
||||
### ✅ Components die NIET wijzigen
|
||||
|
||||
1. **Context Bar** — `context-bar.tsx`
|
||||
- Shift indicator met kleuren
|
||||
- Patient selector
|
||||
- User info
|
||||
|
||||
2. **Alle Block componenten** — `blocks/*.tsx`
|
||||
- `DagnotatieBlock` ✅
|
||||
- `ZoekenBlock` ✅
|
||||
- `OverdrachtBlock` ✅ (wel uitbreiding AI-filtering)
|
||||
- `RapportageBlock` ✅
|
||||
- `AgendaBlock` ✅
|
||||
- `MetingenBlock` ✅
|
||||
- `PatientContextCard` ✅
|
||||
|
||||
3. **Voice Input** — `lib/swift/use-swift-voice.ts`
|
||||
- Deepgram streaming transcription
|
||||
- Live transcript
|
||||
- Pauze detectie
|
||||
|
||||
4. **Store** — `stores/swift-store.ts`
|
||||
- activePatient, shift, activeBlock
|
||||
- Wordt uitgebreid, maar bestaande state blijft
|
||||
|
||||
5. **Design Tokens**
|
||||
- Color palette
|
||||
- Category colors
|
||||
- Shift colors
|
||||
- Typography
|
||||
- Spacing
|
||||
|
||||
### ✅ Gedrag dat NIET wijzigt
|
||||
|
||||
- Keyboard shortcuts (⌘K, Escape, Enter, etc.)
|
||||
- Voice recording flow
|
||||
- Block prefill logic
|
||||
- Patient search
|
||||
- Category selection (1-9 keys)
|
||||
- Toast notifications
|
||||
- Error handling (offline banner, retry logic)
|
||||
|
||||
---
|
||||
|
||||
## Wat wijzigt
|
||||
|
||||
### 🔄 Components die WIJZIGEN
|
||||
|
||||
1. **CommandCenter** — `command-center.tsx`
|
||||
- **Van:** 4-zone layout (context | canvas | recent | input)
|
||||
- **Naar:** Split-screen layout (context | chat+artifact | input)
|
||||
- **Effort:** 3-5 dagen
|
||||
|
||||
2. **CommandInput** → **ChatInput** — `command-input.tsx`
|
||||
- **Van:** Single-line command input
|
||||
- **Naar:** Multi-line chat input (onderaan chat panel, 40% width)
|
||||
- **Blijft:** Voice input, keyboard shortcuts, states
|
||||
- **Effort:** 3 dagen
|
||||
|
||||
3. **CanvasArea** → **ArtifactArea** — `canvas-area.tsx`
|
||||
- **Van:** Centered block display
|
||||
- **Naar:** Right-side artifact area (60% width) met tabs
|
||||
- **Effort:** 1 week
|
||||
|
||||
4. **RecentStrip** — `recent-strip.tsx`
|
||||
- **Van:** Fixed bar onderaan canvas
|
||||
- **Naar:** Optioneel (kan geïntegreerd in chat history of verwijderd)
|
||||
- **Effort:** 1 dag (verwijderen of integreren)
|
||||
|
||||
### 🆕 Components die NIEUW zijn
|
||||
|
||||
1. **ChatPanel** — `chat-panel.tsx`
|
||||
- Scrollable message list
|
||||
- User/assistant message bubbles
|
||||
- Streaming indicator
|
||||
- Action links
|
||||
- **Effort:** 1 week
|
||||
|
||||
2. **ChatMessage** — `chat-message.tsx`
|
||||
- Message bubble component
|
||||
- Different types (user, assistant, system, error)
|
||||
- **Effort:** 2 dagen
|
||||
|
||||
3. **StreamingIndicator** — `streaming-indicator.tsx`
|
||||
- Pulsating dots tijdens AI response
|
||||
- **Effort:** 1 dag
|
||||
|
||||
4. **ChatActionLink** — `chat-action-link.tsx`
|
||||
- Klikbare links in assistant messages naar artifacts
|
||||
- **Effort:** 1 dag
|
||||
|
||||
5. **ArtifactContainer** — `artifact-container.tsx`
|
||||
- Wrapper met tabs (max 3 artifacts)
|
||||
- Tab switching
|
||||
- **Effort:** 3 dagen
|
||||
|
||||
6. **LinkedEvidence** — `linked-evidence.tsx`
|
||||
- Voor OverdrachtBlock: links naar bronnotities
|
||||
- **Effort:** 2 dagen
|
||||
|
||||
### 🔄 Backend/API die WIJZIGT
|
||||
|
||||
1. **Intent Classification** — `/api/intent/classify`
|
||||
- **Van:** Simple pattern matching + AI fallback
|
||||
- **Naar:** Blijft werken, maar wordt geïntegreerd in chat API
|
||||
- **Effort:** 1 dag (integratie)
|
||||
|
||||
2. **Nieuwe Chat API** — `/api/swift/chat`
|
||||
- Medical scribe chatbot endpoint
|
||||
- Streaming responses (SSE)
|
||||
- Conversatiegeschiedenis als input
|
||||
- Action objects in response
|
||||
- **Effort:** 1.5 week
|
||||
|
||||
3. **Overdracht AI-Filtering** — `/api/overdracht/generate`
|
||||
- **Van:** Alle notities samenvatten
|
||||
- **Naar:** + Filtering op behandelrelevantie (voor psychiater)
|
||||
- **Effort:** 1 week
|
||||
|
||||
---
|
||||
|
||||
## Implementatie Strategie
|
||||
|
||||
### Fase 1: Foundation (Week 1-2)
|
||||
|
||||
**Doel:** Split-screen layout werkend krijgen zonder chat functionaliteit
|
||||
|
||||
**Taken:**
|
||||
1. CommandCenter layout wijzigen naar split-screen (40/60)
|
||||
2. ChatPanel skeleton (nog zonder AI, gewoon tekst weergave)
|
||||
3. ArtifactArea component (wrapper voor bestaande blocks)
|
||||
4. Store uitbreiden met chat state (chatMessages, isStreaming)
|
||||
|
||||
**Deliverable:** Split-screen layout zichtbaar, bestaande blocks werken in artifact area
|
||||
|
||||
**Risico's:**
|
||||
- Responsive design voor mobile/tablet
|
||||
- Block positioning in artifact area (60% width i.p.v. centered)
|
||||
|
||||
---
|
||||
|
||||
### Fase 2: Chat API & Conversatie (Week 3-4)
|
||||
|
||||
**Doel:** Conversatie met medical scribe werkend krijgen
|
||||
|
||||
**Taken:**
|
||||
1. Chat API endpoint bouwen (`/api/swift/chat`)
|
||||
2. Medical scribe system prompt
|
||||
3. Streaming responses (SSE) implementeren
|
||||
4. ChatMessage componenten bouwen
|
||||
5. Intent detection in conversatie
|
||||
6. Action object generation
|
||||
|
||||
**Deliverable:** Werkende conversatie die artifacts kan openen
|
||||
|
||||
**Risico's:**
|
||||
- AI prompt engineering (natuurlijk klinken + betrouwbaar)
|
||||
- Performance bij lange conversaties
|
||||
- Kosten (Claude API calls)
|
||||
|
||||
---
|
||||
|
||||
### Fase 3: Artifact Area & Polish (Week 5-6)
|
||||
|
||||
**Doel:** Meerdere artifacts, polish, AI-filtering
|
||||
|
||||
**Taken:**
|
||||
1. Meerdere artifacts ondersteuning (tabs, max 3)
|
||||
2. Slide-in animaties
|
||||
3. AI-filtering voor psychiater overdracht
|
||||
4. Linked evidence UI
|
||||
5. Error handling & edge cases
|
||||
6. Performance optimalisatie
|
||||
|
||||
**Deliverable:** Volledige v3.0 functionaliteit
|
||||
|
||||
---
|
||||
|
||||
### Fase 4: Testing & Refinement (Week 7-8)
|
||||
|
||||
**Doel:** Production-ready maken
|
||||
|
||||
**Taken:**
|
||||
1. User testing
|
||||
2. Bug fixes
|
||||
3. Performance tuning
|
||||
4. Documentatie
|
||||
5. Migration plan (feature flag)
|
||||
|
||||
**Deliverable:** Production-ready v3.0
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens & Styling
|
||||
|
||||
### Colors (Blijft hetzelfde)
|
||||
|
||||
```css
|
||||
/* App colors */
|
||||
--bg-app: #F8FAFC;
|
||||
--bg-surface: #FFFFFF;
|
||||
--text-primary: #0F172A;
|
||||
--text-secondary: #475569;
|
||||
--border: #E2E8F0;
|
||||
--brand: #3B82F6;
|
||||
--success: #16A34A;
|
||||
--warning: #EAB308;
|
||||
--error: #DC2626;
|
||||
|
||||
/* Shift colors */
|
||||
--shift-ochtend: #F59E0B;
|
||||
--shift-middag: #EAB308;
|
||||
--shift-avond: #FB923C;
|
||||
--shift-nacht: #6366F1;
|
||||
|
||||
/* Category colors */
|
||||
--cat-medicatie-bg: #FEF6DC;
|
||||
--cat-medicatie-accent: #F59E0B;
|
||||
--cat-adl-bg: #E8F8EF;
|
||||
--cat-adl-accent: #16A34A;
|
||||
--cat-observatie-bg: #EFF6FF;
|
||||
--cat-observatie-accent: #3B82F6;
|
||||
--cat-incident-bg: #FEF2F2;
|
||||
--cat-incident-accent: #DC2626;
|
||||
--cat-algemeen-bg: #F1F5F9;
|
||||
--cat-algemeen-accent: #64748B;
|
||||
```
|
||||
|
||||
### Chat Message Colors (Nieuw)
|
||||
|
||||
```css
|
||||
/* Chat message colors */
|
||||
--chat-user-bg: #FFFBEB;
|
||||
--chat-user-border: #FED7AA;
|
||||
--chat-assistant-bg: #F1F5F9;
|
||||
--chat-assistant-border: #CBD5E1;
|
||||
--chat-system-text: #64748B;
|
||||
--chat-error-bg: #FEF2F2;
|
||||
--chat-error-border: #FECACA;
|
||||
```
|
||||
|
||||
### Typography (Blijft hetzelfde)
|
||||
|
||||
```css
|
||||
/* Font sizes */
|
||||
--text-xs: 0.75rem; /* 12px */
|
||||
--text-sm: 0.875rem; /* 14px */
|
||||
--text-base: 1rem; /* 16px */
|
||||
--text-lg: 1.125rem; /* 18px */
|
||||
--text-xl: 1.25rem; /* 20px */
|
||||
|
||||
/* Font weights */
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
```
|
||||
|
||||
### Spacing (Blijft hetzelfde)
|
||||
|
||||
```css
|
||||
/* Spacing scale */
|
||||
--space-1: 0.25rem; /* 4px */
|
||||
--space-2: 0.5rem; /* 8px */
|
||||
--space-3: 0.75rem; /* 12px */
|
||||
--space-4: 1rem; /* 16px */
|
||||
--space-6: 1.5rem; /* 24px */
|
||||
--space-8: 2rem; /* 32px */
|
||||
--space-12: 3rem; /* 48px */
|
||||
--space-16: 4rem; /* 64px */
|
||||
```
|
||||
|
||||
### Border Radius (Blijft hetzelfde)
|
||||
|
||||
```css
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 16px;
|
||||
--radius-full: 9999px;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conclusie
|
||||
|
||||
Het v3.0 redesign is **haalbaar binnen 6-8 weken** omdat:
|
||||
|
||||
1. ✅ **Sterke foundation** — Alle blocks, voice input, design tokens blijven werken
|
||||
2. ✅ **Incrementele wijziging** — Geen complete rewrite, layout + conversatie laag toevoegen
|
||||
3. ✅ **Bestaande patterns** — Chat API pattern bestaat al (`/api/docs/chat`)
|
||||
4. ✅ **Design continuïteit** — Kleuren, typography, spacing blijven hetzelfde
|
||||
|
||||
**Grootste uitdagingen:**
|
||||
- Medical scribe prompt engineering (natuurlijk + betrouwbaar)
|
||||
- Chat state management (Zustand store uitbreiden)
|
||||
- Performance bij lange conversaties
|
||||
|
||||
**Grootste voordelen:**
|
||||
- Natuurlijkere interactie (relationeel i.p.v. transactioneel)
|
||||
- Follow-up vragen mogelijk
|
||||
- Context behouden tijdens werk
|
||||
- Overeenkomsten met ChatGPT/Claude (bekend voor gebruikers)
|
||||
|
||||
---
|
||||
|
||||
## Referenties
|
||||
|
||||
- **FO v3.0:** `fo-swift-medical-scribe-v3.md` — Functioneel ontwerp
|
||||
- **Haalbaarheid:** `haalbaarheidsanalyse-v3.md` — Feasibility analysis
|
||||
- **UX v2.1:** `archive/swift-ux-v2.1.md` — Huidige UX/styling
|
||||
- **Bouwplan v2:** `bouwplan-swift-v2.md` — Development roadmap
|
||||
Reference in New Issue
Block a user