Files
triqura-ecd/docs/specs/speech/fo-realtime-speech-deepgram.md
colinislit dce3943963 feat: rapportage UI refactor, speech streaming, docs & seed data
Rapportage:
- Refactor workspace into modular components (quick-actions, timeline-card, timeline-sidebar)
- Add updateReport action for inline editing
- Improve report timeline with better UX

Speech:
- Add Deepgram token API endpoint
- Add use-deepgram-streaming hook
- Add confidence-text component

Docs:
- Add architecture documentation
- Add performance optimization plan
- Add speech specs and seed data docs

Scripts & Data:
- Add seed-reports script and migration
- Update AGENTS.md guidelines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 14:07:08 +01:00

1214 lines
55 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🧩 Functioneel Ontwerp (FO) Real-Time Speech Transcription met Deepgram SDK
**Projectnaam:** Real-Time Speech Transcription
**Versie:** v2.0 (Editor-First Design)
**Datum:** 24-11-2025
**Auteur:** Claude Code (met input van Colin)
---
## 1. Doel en relatie met het PRD
🎯 **Doel van dit document:**
Dit Functioneel Ontwerp beschrijft de migratie van de huidige REST-based batch transcriptie naar real-time streaming transcriptie met de Deepgram SDK. Het document legt uit **hoe** de gebruiker (clinicus/behandelaar) de nieuwe real-time speech-to-text functionaliteit zal ervaren tijdens het dicteren van medische rapportages en behandelplannen.
📘 **Context:**
- **Huidige situatie:** Audio wordt opgenomen als blob, na opname geüpload en getranscribeerd (2-5+ seconden latency)
- **Nieuwe situatie:** Audio wordt real-time gestreamed via WebSocket, tekst verschijnt live tijdens het spreken (<500ms latency)
- **Belangrijkste verbetering:** Direct visuele feedback, professionele UX met waveform visualisatie, confidence indicators, en slimme auto-pause functionaliteit
📄 **Relatie tot andere documenten:**
- **Analyse:** `docs/specs/speech/analyse-deepgram.md` - Technische analyse van migratie impact
- **Implementatie:** Te ontwikkelen Technisch Ontwerp (TO)
- **Context:** Dit wordt gebruikt in Report Composer en Treatment Advice Form componenten
---
## 2. Overzicht van de belangrijkste onderdelen
De nieuwe real-time speech transcriptie bestaat uit de volgende hoofdonderdelen:
### Core Components
1. **Rapportage Editor (full-width)** - Primaire werkgebied voor het maken en bewerken van rapportages
2. **Speech Recorder Component (streaming)** - Inline opname met WebSocket verbinding
3. **Quick Action Buttons** - Snelle knoppen voor veelgebruikte rapportage types
4. **Timeline Sidebar (opt-in)** - Collapsible geschiedenis van rapportages
5. **Filters (on-demand)** - Verborgen, te openen via zoeken
### Speech Features
6. **Waveform Visualizer** - Real-time audio volume visualisatie
7. **Confidence Indicators** - Markering van onzekere woorden
8. **Smart Insertion Logic** - Veilig invoegen in bestaande teksten
9. **Auto-Pause Mechanisme** - Automatisch pauzeren na stilte
10. **Connection Status Indicator** - Visuele feedback van verbindingsstatus
### Backend
11. **Token Proxy Endpoint** - Veilige API key management (server-side)
---
## 3. User Stories
**User Story Template:**
> Als [rol/gebruiker] wil ik [doel of actie] zodat [reden/waarde].
### Primaire User Stories
| ID | Rol | Doel / Actie | Verwachte waarde | Prioriteit |
|----|------|---------------|------------------|-------------|
| US-01 | Behandelaar | Real-time tekst zien tijdens dicteren | Direct feedback, weet dat het werkt | Hoog |
| US-02 | Behandelaar | Waveform zien tijdens opname | Visual bevestiging dat microfoon werkt | Hoog |
| US-03 | Behandelaar | Onzekere woorden gemarkeerd zien | Kan direct corrigeren tijdens dicteren | Hoog |
| US-04 | Behandelaar | Veilig dicteren in bestaande rapportage | Geen risico op per ongeluk overschrijven | Hoog |
| US-05 | Behandelaar | Automatisch pauzeren na stilte | Voorkomt lange stiltes, natuurlijke workflow | Middel |
| US-06 | Behandelaar | Verbindingsstatus zien | Weet of transcriptie actief is | Middel |
| US-07 | Behandelaar | Pauzeren en hervatten zonder data verlies | Flexibele workflow, kan onderbreken | Middel |
| US-08 | Behandelaar | Partial transcript behouden bij fout | Verlies nooit werk, zelfs bij netwerkproblemen | Hoog |
| **US-09** | **Behandelaar** | **Snel nieuwe rapportage starten met quick button** | **Minder clicks, snellere workflow** | **Hoog** |
| **US-10** | **Behandelaar** | **Timeline alleen tonen als nodig** | **Geen afleiding tijdens schrijven** | **Hoog** |
| **US-11** | **Behandelaar** | **Eenvoudig bestaande rapportage bekijken en editen** | **Geen verwarring tussen view/edit modes** | **Hoog** |
| **US-12** | **Behandelaar** | **Filters alleen zien als ik zoek** | **Cleaner interface, minder overwhelm** | **Middel** |
### Uitgewerkte User Stories
**US-01: Real-time feedback**
> Als behandelaar wil ik de tekst real-time zien verschijnen tijdens het dicteren, zodat ik direct zie dat de transcriptie werkt en of medische termen correct worden herkend.
**US-04: Veilig dicteren**
> Als behandelaar wil ik veilig kunnen dicteren in een bestaande rapportage zonder risico op overschrijven, zodat ik vertrouwen heb dat mijn bestaande werk beschermd is.
**US-08: Data behoud**
> Als behandelaar wil ik dat mijn transcript behouden blijft als de verbinding wegvalt, zodat ik niet al mijn gedicteerde werk verlies bij netwerkproblemen.
**US-09: Quick actions (v2)**
> Als behandelaar wil ik met één klik een nieuwe "Vrije notitie" kunnen starten, zodat ik niet elke keer via een dropdown het type hoef te selecteren.
**US-10: Timeline opt-in (v2)**
> Als behandelaar wil ik de timeline alleen zien als ik ernaar zoek, zodat ik niet wordt afgeleid tijdens het schrijven van een nieuwe rapportage.
**US-11: Unified view/edit (v2)**
> Als behandelaar wil ik een rapportage kunnen openen om te lezen, en daarna direct kunnen editen zonder modal te sluiten en opnieuw te openen, zodat mijn workflow vloeiend blijft.
**US-12: Filters on-demand (v2)**
> Als behandelaar wil ik alleen filters zien als ik op 'Zoeken' klik, zodat mijn scherm niet vol staat met controls die ik zelden nodig heb.
---
## 4. Functionele werking per onderdeel
### 4.1 Rapportage Pagina Layout (Editor-First Design)
**Initiële staat - Editor focus (geen timeline zichtbaar):**
```
┌───────────────────────────────────────────────────────────────────┐
│ [< Cliënten] Jan de Vries Screening Geb: 18-11-1972 21:14│
├───────────────────────────────────────────────────────────────────┤
│ │
│ Nieuwe rapportage maken: │
│ [+ Vrije notitie] [+ Intake] [+ Behandelplan] [Andere... ▼] │
│ [📋 Timeline] │← Toggle
│ [🔍 Zoeken] │← Filters
│ │
├───────────────────────────────────────────────────────────────────┤
│ │
│ Type: Vrije notitie │
│ │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ 🎤 Opname ◯ Niet verbonden [⚙] ││
│ │ [🎤 Start opname] ││
│ └──────────────────────────────────────────────────────────────┘│
│ │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ ││
│ │ Beschrijf wat je wilt vastleggen... ││
│ │ ││
│ │ ││
│ │ ││
│ │ ││
│ └──────────────────────────────────────────────────────────────┘│
│ │
│ [💾 Opslaan] [🤖 Analyseer met AI ▼] │
│ │
└───────────────────────────────────────────────────────────────────┘
```
**Key principles v2:**
1. **Editor krijgt volledige breedte** - Geen split attention
2. **Timeline is opt-in** - Klik [📋 Timeline] om te openen
3. **Filters zijn hidden** - Klik [🔍 Zoeken] om te openen
4. **Quick actions bovenaan** - Common types als directe knoppen
5. **Focus op creatie** - Primary task is nieuwe rapportage maken
### 4.2 Quick Action Buttons
**Locatie:** Bovenaan editor, direct onder page header
**Gedrag:**
- Klik [+ Vrije notitie] → Start direct nieuwe vrije notitie (type pre-selected)
- Klik [+ Intake] → Start direct nieuw intake verslag
- Klik [+ Behandelplan] → Start direct nieuw behandelplan
- Klik [Andere... ▼] → Dropdown met alle andere types
**Visuele feedback na klik:**
```
┌───────────────────────────────────────────────────────────────┐
│ [+ Vrije notitie] [+ Intake] [+ Behandelplan] [Andere... ▼] │
│ ↑ Active (emerald-500 background) │
└───────────────────────────────────────────────────────────────┘
Editor updates naar:
Type: Intake verslag ← Pre-selected
[Textarea ready voor input]
```
**Smart defaults:**
- Onthoud laatste type (localStorage)
- Meest gebruikte types eerst (kan analytics-based worden)
- Voor 90% van use cases: 1 click om te starten
**Rationale:**
- 80/20 regel: Meeste users maken dezelfde 3-4 types rapportages
- Vermijdt dropdown navigeren elke keer
- Snellere workflow (1 vs 2 clicks)
### 4.3 Timeline Sidebar (Opt-In)
**Trigger:** Klik [📋 Timeline] button rechtsboven
**Layout met timeline open:**
```
┌──────────────────────────────────────┬──────────────────────────┐
│ RAPPORTAGE EDITOR │ 📋 TIJDLIJN [✕] │
│ │ │
│ [+ Vrije notitie] [+ Intake] [...▼] │ [🔍] [_______________] │← Search
│ │ │
│ Type: Vrije notitie │ ┌──────────────────────┐│
│ │ │ 📄 Vrije notitie ││
│ [🎤 Speech recorder...] │ │ 23-11, 21:43 ││
│ │ │ Dit is de eerste... ││
│ [Textarea...] │ │ ││
│ │ │ [Bekijk rapport] ││← Single action
│ │ └──────────────────────┘│
│ [Actions...] │ │
│ │ ┌──────────────────────┐│
│ │ │ 📄 Intake verslag ││
│ │ │ 22-11, 14:20 ││
└──────────────────────────────────────┴──────────────────────────┘
70% 30%
```
**Gedrag:**
- Timeline slides in van rechts (300ms animation)
- Editor width shrinks naar 70% (smooth transition)
- Timeline is scrollbaar (independent scroll van editor)
- Klik [✕] → Timeline slides out, editor expands naar 100%
**Timeline Card Design:**
```
┌──────────────────────────────┐
│ 📄 Vrije notitie │ ← Icon + type
│ 23-11-2025, 21:43 │ ← Timestamp
│ ongeveer 20 uur geleden │ ← Relative time
│ ──────────────────────────── │
│ Dit is de eerste rapportage │ ← Preview (first 2 lines)
│ voor Jan de Vries... │
│ │
│ [Bekijk rapport] │ ← Single action (unified)
└──────────────────────────────┘
```
**Rationale - Single [Bekijk] button:**
- Geen [Bewerk] button meer (verwarrend)
- Klik rapport → Opens in modal (read mode)
- Modal heeft [✏️ Bewerken] knop als nodig
- Unified view/edit flow (zie sectie 4.6)
**State:**
- Timeline open/closed state opgeslagen in localStorage
- User preference persists tussen sessies
- Optional: Resizable divider (drag tussen editor en timeline)
### 4.4 Filters (On-Demand)
**Trigger:** Klik [🔍 Zoeken] button rechtsboven
**Gedrag optie A: Expandable filter bar**
```
Collapsed (default):
┌───────────────────────────────────────────┐
│ [+ Quick actions...] [📋][🔍 Zoeken]│
└───────────────────────────────────────────┘
Klik [🔍 Zoeken] →
Expanded:
┌───────────────────────────────────────────────────────────┐
│ [+ Quick actions...] [📋][🔍 Zoeken]│
├───────────────────────────────────────────────────────────┤
│ 🔍 Filters │
│ Type: [Alle ▼] Auteur: [Alle ▼] Van: [__] T/m: [__] │
│ AI: ○ Alles ○ Met AI ○ Handmatig [Reset] │
└───────────────────────────────────────────────────────────┘
```
**Gedrag optie B: Filter in timeline**
```
Timeline open + klik [🔍] in timeline header:
┌──────────────────────────┐
│ 📋 TIJDLIJN [✕] │
│ │
│ [🔍] [_______________] │← Search always visible in timeline
│ │
│ [▼ Filters] │← Expandable advanced filters
│ │
│ [Rapportages...] │
└──────────────────────────┘
```
**Aanbeveling: Optie B** (Filters in timeline sidebar)
- Filters zijn contextual bij timeline
- Niet op main editor (keeps editor clean)
- Timeline is toch al opt-in, filters zijn secondary to that
**Smart filter behavior:**
- Bij < 10 rapportages: Geen filters nodig, hide completely
- Bij > 10 rapportages: Toon [🔍] in timeline
- Search is fuzzy: zoekt in title, content, type
### 4.5 Speech Recorder Component (Inline, Compact)
**Initiële staat (niet aan het opnemen):**
```
┌──────────────────────────────────────────────────────────┐
│ 🎤 Opname ◯ Niet verbonden [⚙] │
│ [🎤 Start opname] │
└──────────────────────────────────────────────────────────┘
```
**Gedrag bij klikken op "Start opname":**
1. Systeem vraagt microfoon permissie (indien nog niet gegeven)
2. Cursor springt automatisch naar einde van tekstveld
3. Status indicator wordt ◐ Geel "Verbinden..."
4. WebSocket verbinding wordt opgezet met Deepgram
5. Status wordt ● Groen "Verbonden & streaming"
6. Waveform visualisatie start
7. Tekst verschijnt live tijdens spreken
**Actieve opname staat:**
```
┌─────────────────────────────────────┐
│ Spraakopname │
│ ● Verbonden & streaming [⚙] │
│ │
│ De patiënt presenteert zich met │ ← Final (zwart, normaal)
│ klachten van langdurige vermoeidh.. │ ← Interim (grijs, italic)
│ │
│ ━━━━━━━━●━━━━━━━━━━━━━━━━━ │ ← Waveform
│ │
│ [⏸ Pauzeer] [⏹ Stop] │
└─────────────────────────────────────┘
```
**Tekst differentiatie:**
- **Interim tekst** (real-time, nog niet definitief):
- Kleur: `text-slate-500` (grijs)
- Font-style: `italic`
- Font-weight: `400` (normaal)
- Wordt continu geüpdatet tijdens spreken
- **Final tekst** (definitief, na zinsbeëindiging):
- Kleur: `text-slate-900` (zwart)
- Font-style: `normal`
- Font-weight: `500` (medium bold)
- Locked in, wordt niet meer gewijzigd
**Animaties:**
- Nieuwe woorden fade in met 200ms ease-out
- Interim → Final transitie met 300ms spring animatie
- Smooth scroll naar nieuwste tekst
### 4.2 Waveform Visualizer
**Gedrag:**
- Canvas-based visualisatie (60fps)
- Toont audio volume in real-time
- Bar-style waveform zoals WhatsApp voice messages
- Smooth animaties met spring physics
**Visuele feedback:**
```
Stil: ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
Zacht: ▂▃▂▁▂▃▂▁▂▃▂▁▂▃▂▁▂▃▂
Normaal: ▄▅▆▅▄▃▄▅▆▅▄▃▄▅▆▅▄▃▄
Luid: ▆▇█▇▆▅▆▇█▇▆▅▆▇█▇▆▅▆
```
**Kleuren:**
- Bars: `slate-700` (standaard)
- Active bar (huidige positie): `emerald-500`
- Tijdens stilte: bars worden transparanter
**Functie:**
- Visuele bevestiging dat microfoon audio oppikt
- Helpt gebruiker zien of ze hard genoeg spreken
- Professional uitstraling (zoals moderne opname-apps)
### 4.3 Confidence Indicators
**Wanneer getoond:**
- Alleen voor final tekst (niet voor interim)
- Automatisch voor woorden met confidence < 0.9
**Visuele markering:**
**High confidence (≥0.9):** Geen markering
```
De patiënt heeft een diagnose van GAD
```
**Medium confidence (0.7-0.9):** Gele onderstreping
```
De patiënt heeft een diagnose van ˜GAD˜
Hover tooltip:
Zekerheid: 78%
```
**Low confidence (<0.7):** Oranje onderstreping + suggestie
```
De patiënt heeft een diagnose van ˜GAT˜
Hover tooltip:
Zekerheid: 65%
Bedoelde je: GAD, gat?
Klik om te herhalen
```
**Interactie:**
- Hover over gemarkeerd woord → tooltip met confidence %
- Klik op woord → optie om dit deel opnieuw te dicteren
- Kan genegeerd worden (is alleen suggestie)
**Medische context:**
- Medische afkortingen worden automatisch CAPS (DSM, SSRI, GAD)
- Smart formatting voor Nederlandse interpunctie
- Patiëntnamen uit context (indien beschikbaar via props)
### 4.4 Smart Insertion Logic (Overschrijf-preventie)
**Probleem:** Gebruiker opent bestaande rapportage en wil dicteren → risico dat bestaande tekst overschreven wordt.
**Oplossing:** Automatische cursor-naar-einde + visuele feedback
**Flow bij starten opname:**
**Scenario A: Leeg tekstveld**
```
1. User klikt [Start opname]
2. Start direct met opnemen
3. Tekst verschijnt vanaf begin
```
**Scenario B: Bestaand tekst, cursor aan einde**
```
1. User klikt [Start opname]
2. Groen glow rond tekstgebied
3. Tekst verschijnt aan einde (waar cursor al stond)
```
**Scenario C: Bestaand tekst, cursor NIET aan einde**
```
1. User klikt [Start opname]
2. Cursor springt automatisch naar einde
3. Korte notificatie: "Cursor verplaatst naar einde"
4. Groen glow rond tekstgebied
5. Tekst verschijnt aan einde
```
**Visuele feedback tijdens dictaat:**
```
┌─────────────────────────────────────┐
│ Rapportage │ ← Groene border tijdens dictaat
│ [Bestaande tekst...] │
│ │
│ [Live transcriptie hier...] │
│ █ ← Cursor (groen bolletje) │
│ │
│ 🟢 Aan het dicteren │
└─────────────────────────────────────┘
```
**CSS/Styling tijdens dictaat-modus:**
- Border: `border-emerald-500 border-2`
- Subtle glow: `shadow-emerald-500/20`
- Cursor indicator: Groen bolletje naast cursor
**Na stoppen:**
- Groene border verdwijnt
- Tekst blijft staan
- Cursor blijft op positie
- Gebruiker kan handmatig verder typen/editen
### 4.6 Unified View/Edit Modal (Bestaande Rapportages)
**Trigger:** Klik [Bekijk rapport] in timeline card
**Modal opent in Read mode (default):**
```
┌─────────────────────────────────────────┐
│ Vrije notitie │
│ 23-11-2025, 21:43 │
│ │
│ [✏️] [📋 Dupliceer] [🗑] [✕] │← Actions
├─────────────────────────────────────────┤
│ │
│ Dit is de eerste rapportage voor │← Read-only content
│ Jan de Vries op é plek. │
│ │
│ [Content...] │
│ │
│ │
├─────────────────────────────────────────┤
│ [Sluiten] │
└─────────────────────────────────────────┘
```
**Klik [✏️ Bewerken] → Modal wordt editable:**
```
┌─────────────────────────────────────────┐
│ Vrije notitie (bewerken) │
│ 23-11-2025, 21:43 │
│ │
│ [💾] [❌] [✕] │← Edit actions
├─────────────────────────────────────────┤
│ 🎤 Opname ◯ Niet verbonden [⚙] │← Speech recorder
│ [🎤 Start opname om door te gaan] │
├─────────────────────────────────────────┤
│ ┌─────────────────────────────────────┐ │
│ │ Dit is de eerste rapportage voor │ │← Editable textarea
│ │ Jan de Vries op é plek. █ │ │← Cursor
│ │ │ │
│ │ [Kan verder typen of dicteren...] │ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ [💾 Opslaan wijzigingen] [❌ Annuleren] │
└─────────────────────────────────────────┘
```
**Flow:**
1. User kli
kt [Bekijk rapport] in timeline
2. Modal opent overlay over editor (70% width, centered)
3. **Default = Read mode:** Alleen lezen, geen edit
4. User klikt [✏️ Bewerken]:
- Modal transformeert naar edit mode (smooth transition)
- Speech recorder verschijnt bovenaan
- Content wordt editable textarea
- Actions veranderen naar [Opslaan]/[Annuleren]
5. User kan:
- Typen in textarea
- Dicteren via speech recorder (cursor gaat naar einde)
- Klik [Opslaan] → Save + modal blijft open (success toast)
- Klik [Annuleren] → Discard changes, terug naar read mode
- Klik [✕] → Close modal (met unsaved warning indien nodig)
**Unsaved changes protection:**
```
User editeert text, klikt [✕] zonder te saven:
┌───────────────────────────────┐
│ Niet opgeslagen wijzigingen │
│ │
│ Wil je de wijzigingen opslaan?│
│ │
│ [💾 Opslaan] │
│ [🗑 Verwijderen] │
│ [← Terug naar bewerken] │
└───────────────────────────────┘
```
**Rationale unified view/edit:**
- Gebruiker hoeft niet te raden: view of edit?
- Start altijd met lezen (meest voorkomend)
- Als edit nodig: 1 click [✏️]
- Same context, geen modal sluiten/heropenen
- Clear state: read icons vs edit icons
**Extra features in modal:**
- [📋 Dupliceer]: Kopieer rapport naar nieuwe rapportage in editor
- [🗑 Verwijder]: Delete rapport (met confirmation)
- Keyboard shortcuts: `E` = Edit, `Esc` = Close
### 4.7 Auto-Pause Mechanisme
**Trigger:** 3 seconden stilte (geen spraak gedetecteerd)
**Gedrag:**
1. Deepgram endpointing detecteert stilte
2. Na 3 seconden → automatisch pauzeren
3. WebSocket blijft verbonden (geen reconnect nodig)
4. Audio streaming stopt
5. UI update naar "gepauzeerd" staat
**Gepauzeerde staat:**
```
┌─────────────────────────────────────┐
│ Spraakopname │
│ ● Verbonden [⚙] │
│ │
│ [Transcriptie tot nu toe...] │
│ │
│ ⏸ Automatisch gepauzeerd │
│ (3 seconden stilte) │
│ │
│ [▶ Hervat opname] [⏹ Stop] │
└─────────────────────────────────────┘
```
**Hervatten:**
- Klik [▶ Hervat opname]
- Audio streaming start onmiddellijk (geen nieuwe verbinding)
- Waveform hervat
- Transcriptie gaat verder waar het stopte
**Voordelen:**
- Voorkomt lange stiltes in transcript
- Natuurlijke workflow (denken tijdens dicteren)
- Geen data verlies
- Geen reconnection overhead
**Configuratie:**
- Deepgram endpointing: `3000ms` (3 seconden)
- User kan niet handmatig instellen (fixed voor consistentie)
### 4.6 Connection Status Indicator
**Altijd zichtbaar** in rechterbovenhoek van component:
**Stati en kleuren:**
| Status | Icoon | Kleur | Betekenis |
|--------|-------|-------|-----------|
| Niet verbonden | ◯ | `text-slate-400` | Niet aan het opnemen |
| Verbinden... | ◐ | `text-amber-500` | WebSocket aan het opzetten |
| Verbonden | ● | `text-emerald-500` | Actief aan het streamen |
| Herverbinden... | ⚠ | `text-orange-500` | Netwerk probleem, aan het reconnecten |
| Fout | ✕ | `text-red-500` | Fout opgetreden |
**Interactie:**
- Geen interactie bij groen/grijs
- Bij oranje (herverbinden): toon "Herverbinden... transcript blijft behouden"
- Bij rood (fout): toon [Opnieuw proberen] button
**Fout scenario met recovery:**
```
┌─────────────────────────────────────┐
│ Spraakopname │
│ ✕ Verbinding verloren [⚙] │
│ │
│ [Transcript tot nu toe behouden] │
│ │
│ ⚠ Netwerkfout: kon niet verbinden │
│ met Deepgram │
│ │
│ [🔄 Opnieuw proberen] [💾 Bewaar] │
└─────────────────────────────────────┘
```
**Auto-reconnect logica:**
- Bij netwerk drop: automatisch 3x reconnect proberen
- Tussen pogingen: 1s, 2s, 4s backoff
- Audio wordt gebufferd tijdens reconnect
- Partial transcript altijd behouden
- Na 3 pogingen: toon manual retry button
### 4.7 Pause & Resume Controls
**Beschikbare acties tijdens opname:**
**[⏸ Pauzeer]** - Handmatig pauzeren
- Stopt audio streaming
- Behoudt WebSocket verbinding
- Transcript blijft zichtbaar
- Kan hervatten zonder data verlies
**[⏹ Stop]** - Definitief stoppen
- Stopt audio streaming
- Sluit WebSocket verbinding
- Transcript wordt naar parent component gestuurd (via `onTranscript` callback)
- Reset component naar initiële staat
**[▶ Hervat]** - Hervatten na pause
- Hervat audio streaming
- Gaat verder waar gestopt
- Geen nieuwe verbinding nodig
**Flow voorbeeld:**
```
[Start] → Opname loopt → [Pauzeer] → Gepauzeerd → [Hervat] → Opname loopt → [Stop] → Klaar
(na 3s stilte: auto-pause)
```
---
## 5. UI-overzicht (visuele structuur) - v2 Editor-First
### 5.1 Pagina Layout - Default (Geen Timeline)
**Editor heeft volledige breedte, focus op creatie:**
```
┌─────────────────────────────────────────────────────────────────────┐
│ [< Cliënten] Jan de Vries Screening Geb: 18-11-1972 21:14 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Nieuwe rapportage maken: │
│ [+ Vrije notitie] [+ Intake] [+ Behandelplan] [Andere... ▼] │
│ [📋 Timeline] │
│ [🔍 Zoeken] │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Type: Vrije notitie │
│ │
│ ┌────────────────────────────────────────────────────────────────┐│
│ │ 🎤 Opname ● Verbonden & streaming [⚙] ││
│ │ ━━━━━━━━●━━━━━━━━ [⏸ Pauzeer] [⏹ Stop] ││ ← Speech inline
│ └────────────────────────────────────────────────────────────────┘│
│ │
│ ┌────────────────────────────────────────────────────────────────┐│
│ │ ││
│ │ De patiënt presenteert zich met klachten van ││ ← Live tekst
│ │ langdurige vermoeidheid... ││
│ │ ││
│ │ ││
│ │ ││
│ └────────────────────────────────────────────────────────────────┘│
│ │
│ [💾 Opslaan] [🤖 Analyseer met AI ▼] │
│ │
└─────────────────────────────────────────────────────────────────────┘
100% width
```
### 5.2 Pagina Layout - Met Timeline Open
**Timeline sidebar rechts, editor links (70/30 split):**
```
┌──────────────────────────────────────┬──────────────────────────────┐
│ RAPPORTAGE EDITOR │ 📋 TIJDLIJN [✕] │
│ │ │
│ [+ Vrije notitie] [+ Intake] [...▼] │ [🔍] [_______________] │
│ │ │
│ Type: Vrije notitie │ ┌──────────────────────────┐│
│ │ │ 📄 Vrije notitie ││
│ ┌──────────────────────────────────┐ │ │ 23-11, 21:43 ││
│ │ 🎤 Opname ● Verbonden [⚙] ││ │ │ Dit is de eerste... ││
│ │ ━━━━●━━━ [⏸][⏹] ││ │ │ ││
│ └──────────────────────────────────┘ │ │ [Bekijk rapport] ││
│ │ └──────────────────────────┘│
│ ┌──────────────────────────────────┐ │ │
│ │ [Textarea met live tekst...] ││ │ ┌──────────────────────────┐│
│ │ ││ │ │ 📄 Intake verslag ││
│ │ ││ │ │ 22-11, 14:20 ││
│ └──────────────────────────────────┘ │ │ [Bekijk rapport] ││
│ │ └──────────────────────────┘│
│ [💾][🤖 AI ▼] │ │
│ │ [+ Nieuwe rapportage] │
└──────────────────────────────────────┴──────────────────────────────┘
70% 30%
```
### 5.3 Modal - View/Edit Unified
**Modal overlay (70% viewport width, centered):**
```
┌───────────────────────────────────────────────┐
│ Vrije notitie │
│ 23-11-2025, 21:43 │
│ │
│ [✏️ Bewerken] [📋 Dupliceer] [🗑] [✕] │
├───────────────────────────────────────────────┤
│ │
│ Dit is de eerste rapportage voor │← Read mode
│ Jan de Vries op é plek. │
│ │
│ Lorem ipsum dolor sit amet... │
│ │
│ │
├───────────────────────────────────────────────┤
│ [Sluiten] │
└───────────────────────────────────────────────┘
↓ Klik [✏️ Bewerken]
┌───────────────────────────────────────────────┐
│ Vrije notitie (bewerken) │
│ 23-11-2025, 21:43 │
│ │
│ [💾 Opslaan] [❌ Annuleren] [✕] │
├───────────────────────────────────────────────┤
│ 🎤 Opname ◯ Niet verbonden [⚙] │← Speech added
│ [🎤 Start opname om door te gaan] │
├───────────────────────────────────────────────┤
│ ┌───────────────────────────────────────────┐ │
│ │ Dit is de eerste rapportage voor │ │← Editable
│ │ Jan de Vries op é plek. █ │ │
│ │ │ │
│ │ Lorem ipsum dolor sit amet... │ │
│ └───────────────────────────────────────────┘ │
├───────────────────────────────────────────────┤
│ [💾 Opslaan wijzigingen] [❌ Annuleren] │
└───────────────────────────────────────────────┘
```
### 5.4 Responsive Gedrag - v2
**Desktop (>1200px):**
- Full layout zoals hierboven
- Timeline 30%, Editor 70% (resizable optional)
- Modal 70% viewport width
**Laptop (1024px-1200px):**
- Timeline 35%, Editor 65%
- Modal 80% viewport width
- Quick actions blijven horizontaal
**Tablet (768px-1024px):**
- Timeline 40%, Editor 60%
- Of: Toggle tussen Timeline OR Editor (niet beide)
- Modal 90% viewport width
- Quick actions 2 regels (2x2 grid)
**Mobile (<768px):**
- Stack layout:
- Header met patient info (sticky)
- Quick actions (vertical stack of horizontal scroll)
- Editor (full width)
- Timeline in drawer (slide from bottom)
- Modal fullscreen
- Speech recorder compact (waveform smaller)
### 5.5 Interactie States
**Editor tijdens dictaat:**
- Groene border (`border-emerald-500`)
- Subtle glow (`shadow-emerald-500/20`)
- Cursor indicator groen bolletje
**Timeline card - Active (being edited in modal):**
- Emerald border (`border-emerald-500`)
- Emerald background (`bg-emerald-50/50`)
- Visual link naar open modal
**Quick action button - Active:**
- Emerald background (`bg-emerald-500`)
- White text (`text-white`)
- Bold font (`font-semibold`)
**Timeline sidebar - States:**
- Closed: Editor 100% width
- Opening: 300ms slide-in animation from right
- Open: Editor 70%, Timeline 30%
- Closing: 300ms slide-out animation to right
- Smooth width transitions on editor
### 5.6 Empty States
**Timeline leeg:**
```
┌────────────────────────────┐
│ 📋 TIJDLIJN [✕] │
│ │
│ 📄 │
│ Nog geen rapportages │
│ │
│ Maak je eerste │
│ rapportage in het │
│ editor paneel → │
│ │
│ │
│ [+ Nieuwe rapportage] │
└────────────────────────────┘
```
**Editor zonder type selected:**
```
┌───────────────────────────────────┐
│ Kies een type om te beginnen: │
│ [+ Vrije notitie] [+ Intake] │
│ [+ Behandelplan] [Andere... ▼] │
└───────────────────────────────────┘
```
---
## 6. Interacties met AI (functionele beschrijving)
### 6.1 AI Context voor Speech Transcription
**Deepgram AI features gebruikt:**
| Feature | Configuratie | Doel | User-facing impact |
|---------|--------------|------|-------------------|
| Live Streaming | WebSocket | Real-time audio verwerking | Tekst verschijnt tijdens spreken |
| Interim Results | `interim_results: true` | Voorlopige transcripties | Grijs/italic tekst live updates |
| Smart Format | `smart_format: true` | Auto punctuatie/capitalisatie | Nederlandse interpunctie correct |
| Endpointing | `endpointing: 3000` | Detecteer spraakpauzes | Auto-pause na 3 sec stilte |
| Language | `language: nl` | Nederlandse taal model | Correcte Nederlandse herkenning |
| Model | `model: nova-2` | Laatste Deepgram model | Beste accuracy voor Nederlands |
| Utterances | `utterances: true` | Word-level timestamps + confidence | Confidence indicators per woord |
### 6.2 Confidence Score Processing
**AI Output → UI Mapping:**
```javascript
// Deepgram stuurt per word:
{
word: "gegeneraliseerde",
confidence: 0.76,
start: 1.234,
end: 1.789
}
// UI toont:
"gegeneraliseerde" met gele onderstreping
Tooltip: "Zekerheid: 76%"
```
**Drempelwaarden:**
- ≥0.9: Geen markering (high confidence)
- 0.7-0.9: Gele onderstreping (medium confidence)
- <0.7: Oranje markering + suggestie (low confidence)
### 6.3 Medical Terminology Enhancement (toekomstig)
**Mogelijke AI uitbreidingen voor v2:**
- Custom medical dictionary voor Nederlandse GGZ termen
- Patient naam herkenning uit context
- DSM-5 classificatie auto-correct
- Medicatie namen CAPS formatting
- Sectie detectie ("Anamnese:", "Diagnose:", etc.)
**Niet in v1, maar voorbereid in architectuur**
---
## 7. Gebruikersrollen en rechten
**Voor deze feature:** Geen specifieke rol-differentiatie nodig.
**Aanname:**
- Alle behandelaars die toegang hebben tot Report Composer en Treatment Advice Form kunnen spraakopname gebruiken
- Geen aparte permissies voor speech-to-text
- Eventueel: audit logging van dictaat gebruik (in toekomst)
**Privacy overwegingen:**
- Audio wordt **niet** opgeslagen op onze servers
- Direct gestreamed naar Deepgram (SSL/TLS encrypted)
- Transcript wordt opgeslagen als onderdeel van rapportage/behandelplan (bestaand gedrag)
- Deepgram DPA (Data Processing Agreement) moet gecheckt worden voor GDPR/AVG compliance
---
## 8. Ontwerpkeuzes en Rationale - v2 Editor-First Design
### 8.1 Design Philosophy: v1 → v2 Evolution
**v1 Design (Afgewezen):**
- Side-by-side timeline (35%) en editor (65%)
- Filters altijd zichtbaar bovenaan
- [Bewerk] en [Bekijk] als aparte knoppen
- Type selector elke keer handmatig kiezen
**Problemen met v1 (uit design review):**
- **Split attention:** Gebruiker moet beide panelen monitoren
- **Afleiding:** Timeline zichtbaar tijdens schrijven
- **Complexity overload:** Filters, timeline, editor allemaal tegelijk
- **Verwarrende flows:** Bewerk vs Bekijk decision upfront
- **Slow workflow:** Te veel clicks voor veelvoorkomende taken
**v2 Design (Gekozen):**
- **Editor-first:** Full-width default, focus op creatie
- **Opt-in complexity:** Timeline/filters alleen als nodig
- **Unified view/edit:** Één knop, smooth transition
- **Quick actions:** 1-click voor common types
**Kernprincipes v2:**
1. **Primary task first** - Nieuwe rapportage maken is belangrijkste use case
2. **Progressive disclosure** - Toon features on-demand
3. **Minimize clicks** - Optimize voor 80% use cases
4. **No split attention** - Één focus area tegelijk
### 8.2 Besluit: Timeline Opt-In (Rechts Sidebar)
**Gekozen:** Timeline als collapsible sidebar rechts
**Rationale:**
- **Editor is primair** - Nieuwe rapportage maken > oude bekijken
- **Geen afleiding** - Timeline default closed tijdens schrijven
- **Context on-demand** - Klik [📋 Timeline] als referentie nodig
- **Rechts placement** - Sidebar pattern (zoals VS Code, Notion)
**Afgewezen alternatieven:**
- **Timeline links (v1):** Suggereert dat timeline primair is (niet waar)
- **Timeline altijd zichtbaar:** Leidt af, split attention
- **Timeline als tab:** Te veel context switching, verlies editor inhoud
**Design review insights:**
- ✅ Frontend dev: "Minder complex dan side-by-side sync"
- ✅ UX designer: "Opt-in vermindert cognitive load"
- ✅ End user: "Ik wil niet afgeleid worden tijdens dicteren"
### 8.3 Besluit: Quick Action Buttons
**Gekozen:** [+ Vrije notitie] [+ Intake] [+ Behandelplan] [Andere... ▼]
**Rationale:**
- **80/20 regel:** 3-4 types = 80% van gebruik
- **1 click start:** Geen dropdown navigation
- **Visual affordance:** Knoppen zijn duidelijker dan dropdown
- **Remembers last used:** Meest gebruikte type highlighted
**Data assumptions (te valideren):**
- Vrije notities: ~50% van rapportages
- Intake: ~25%
- Behandelplan: ~15%
- Overige: ~10%
**Design review insights:**
- ✅ End user: "Ik maak meestal vrije notities, dit bespaart clicks"
- ✅ UX designer: "Duidelijke affordances, geen verborgen functionaliteit"
### 8.4 Besluit: Unified View/Edit Modal
**Gekozen:** Single [Bekijk rapport] → Modal met [✏️ Bewerken] optie
**Rationale:**
- **No upfront decision:** Gebruiker hoeft niet te kiezen tussen view/edit
- **Natural flow:** Eerst lezen (meest common), dan editen als nodig
- **Same context:** Blijf in dezelfde modal, geen context loss
- **Clear states:** Read vs Edit visueel distinct
**Afgewezen alternative (v1):**
- **[Bewerk] + [Bekijk] buttons:** Verwarrend, user moet raden
- **Direct edit:** Te risicovol, per ongeluk wijzigingen
**Flow comparison:**
| Scenario | v1 (split buttons) | v2 (unified modal) |
|----------|------|------|
| Rapport lezen | Klik [Bekijk] → Modal | Klik [Bekijk] → Modal |
| Rapport editen | Klik [Bewerk] → Editor laadt | Klik [Bekijk] → [✏️] → Editeer in modal |
| Lezen, ziet fout, wil editen | [✕] Modal → [Bewerk] → Re-load | [✏️] → Direct editbaar (1 click) |
**v2 is beter:** Minder clicks voor "bekijken dan editen" flow (meest common)
**Design review insights:**
- ✅ End user: "Ik wil rapport eerst zien voordat ik edit"
- ✅ UX designer: "Unified flow is duidelijker, minder decision fatigue"
- ✅ Frontend dev: "Gemakkelijker state management, één component"
### 8.5 Besluit: Filters On-Demand (In Timeline)
**Gekozen:** Filters verborgen, alleen in timeline wanneer >10 rapportages
**Rationale:**
- **Most users don't need filters:** <10 rapportages per patiënt typisch
- **Contextual:** Filters horen bij zoeken in timeline, niet bij editor
- **Clean interface:** Geen onnodige controls
- **Smart detection:** Toon alleen als relevant
**Gedrag:**
- <10 rapportages: Geen filter UI, alleen search
- >10 rapportages: [▼ Filters] expand optie verschijnt
- Timeline closed: Filters niet zichtbaar (irrelevant)
**Afgewezen alternative (v1):**
- **Filters altijd zichtbaar bovenaan:** Neemt 60px ruimte, laag usage
- **Filters in main toolbar:** Not contextual, altijd in zicht
**Design review insights:**
- ✅ End user: "Ik heb 5 rapportages, waarom zie ik al die filters?"
- ✅ UX designer: "Progressive disclosure principle - toon als nodig"
### 8.6 Besluit: Speech Inline (Bij Textarea)
**Gekozen:** Speech recorder compact, direct boven textarea
**Rationale:**
- **Proximity:** Bij waar tekst verschijnt (usability principle)
- **Inline:** Geen popup/modal/sidebar
- **Compact:** ~80px height collapsed, ~120px expanded
- **Live preview:** Zie waveform + tekst samen
**Design stays from original v1:**
- ✅ Auto cursor-naar-einde (veilig, geen overschrijven)
- ✅ Waveform visualizer (professional feedback)
- ✅ Confidence indicators (medical term validation)
- ✅ Auto-pause na 3 sec (natural pauses)
**No changes needed:** Speech recorder design was good in v1
### 8.7 Besluit: Mobile Strategy
**Gekozen:** Stack + drawer pattern
**Rationale:**
- **Editor full-width:** Primary task has full screen
- **Timeline in drawer:** Swipe up from bottom
- **Quick actions:** Horizontal scroll or 2x2 grid
- **Modal fullscreen:** Editing in modal takes full mobile screen
**Alternative considered:**
- **Tabs (Timeline | Editor):** Loses context, too much switching
- **Shrink both panels:** Everything too small, unusable
**Design review insights:**
- ✅ UX designer: "Mobile needs different paradigm, not just responsive"
- ⚠️ Frontend dev: "Drawer animation complexity, but doable"
### 8.8 Design Review Summary
**v2 solves v1 critical issues:**
1.**No split attention** - Editor full-width default
2.**No confusion** - Unified view/edit flow
3.**Faster workflow** - Quick actions, 1-click common tasks
4.**Less overwhelming** - Opt-in timeline/filters
5.**Clear hierarchy** - Editor = primary, timeline = secondary
**Remaining considerations:**
- ⚠️ **Unsaved changes:** Need warning dialog (planned)
- ⚠️ **Keyboard shortcuts:** Power users want efficiency (v2 feature)
- ⚠️ **Resizable timeline:** Users have different preferences (optional v1.1)
**Score evolution:**
- v1 design (rejected): 6/10 (good ideas, poor execution)
- v2 design (current): 8/10 (user-tested principles, clear hierarchy)
### 8.9 Implementation Priorities
**Must have (v1):**
1. Editor-first layout with full-width
2. Quick action buttons (top 3 types)
3. Timeline opt-in sidebar (collapsible)
4. Unified modal (view → edit flow)
5. Speech inline with all features (waveform, confidence, auto-pause)
**Should have (v1.1):**
6. Filters in timeline (smart show/hide)
7. Unsaved changes protection dialog
8. Keyboard shortcuts (E=edit, Esc=close, etc.)
9. Resizable timeline divider
**Nice to have (v2):**
10. Analytics-driven quick action order
11. Voice commands ("nieuwe paragraaf")
12. Duplicate rapport feature
13. Timeline infinite scroll/virtualization
---
## 9. Technische randvoorwaarden (interface met TO)
**Deze sectie is input voor het Technisch Ontwerp:**
### 9.1 API/Props Interface
**Speech Recorder Component Props:**
```typescript
interface SpeechRecorderStreamingProps {
onTranscript: (transcript: string) => void; // Callback voor final transcript
onInterimTranscript?: (interim: string) => void; // Optional: interim updates
onError?: (error: Error) => void;
disabled?: boolean;
className?: string;
// Optional: context voor AI enhancement
patientName?: string;
documentType?: 'rapport' | 'behandelplan' | 'intake';
}
```
### 9.2 Browser Requirements
**Minimum support:**
- Chrome/Edge 90+ (WebSocket + Web Audio API + MediaRecorder)
- Firefox 88+
- Safari 14+ (mogelijk beperkte ondersteuning)
**Fallback strategie:**
- Feature detection voor WebSocket/Web Audio
- Als niet ondersteund: terug naar oude REST API implementatie
- Duidelijke melding: "Je browser ondersteunt geen real-time transcriptie"
### 9.3 Performance Requirements
- Waveform: 60fps update rate
- Transcript latency: <500ms van spraak tot tekst verschijnt
- UI moet responsive blijven tijdens streaming (geen blocking)
- Memory: max 50MB voor component (audio buffering)
### 9.4 Security Requirements
- API key NOOIT in client code
- Token-based authentication voor WebSocket
- Tokens expire na 1 uur
- Rate limiting op token endpoint (max 10 tokens/user/hour)
- SSL/TLS voor alle verbindingen
---
## 10. Bijlagen & Referenties
**Interne documenten:**
- `docs/specs/speech/analyse-deepgram.md` - Technische analyse van migratie
- `docs/templates/fo_template.md` - Template gebruikt voor dit document
**Componenten die speech recorder gebruiken:**
- `app/epd/patients/[id]/rapportage/components/report-composer.tsx`
- `app/epd/patients/[id]/intakes/[intakeId]/behandeladvies/components/treatment-advice-form.tsx`
**Bestaande implementatie:**
- `components/speech-recorder.tsx` - Huidige REST-based versie
- `app/api/deepgram/transcribe/route.ts` - Huidige server-side endpoint
**Deepgram documentatie:**
- [Live Streaming Audio](https://developers.deepgram.com/docs/live-streaming-audio)
- [Interim Results](https://developers.deepgram.com/docs/interim-results)
- [Endpointing](https://developers.deepgram.com/docs/endpointing)
- [Smart Format](https://developers.deepgram.com/docs/smart-format)
**Design System:**
- shadcn/ui componenten (Button, Dialog, Toast)
- Tailwind CSS styling
- Lucide React icons
---
## 11. Open vragen en toekomstige iteraties
### Open vragen voor implementatie:
- [ ] Deepgram DPA review voor AVG/GDPR compliance
- [ ] Exacte placement: inline vs sidebar vs floating? (aanbeveling: inline)
- [ ] Mobile UX: fullscreen tijdens opname of blijft in place?
- [ ] Autosave integratie: triggert partial transcript autosave?
### Toekomstige v2 features:
- Medical dictionary custom voor Nederlandse GGZ termen
- Sectie auto-detectie ("Anamnese:", "Diagnose:", etc.)
- Voice commands ("nieuwe paragraaf", "verwijder laatste zin")
- Multi-speaker detection (als patiënt mee praat)
- Playback functie van opgenomen audio (vereist lokale opslag)
- Export naar audio file voor archivering
---
**Status:** Ready for Technical Design
**Next steps:** Technisch Ontwerp (TO) uitwerken + implementatie planning