16 KiB
16 KiB
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
// 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
// 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
// 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
// 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.