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

55 KiB
Raw Blame History

🧩 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

  1. Waveform Visualizer - Real-time audio volume visualisatie
  2. Confidence Indicators - Markering van onzekere woorden
  3. Smart Insertion Logic - Veilig invoegen in bestaande teksten
  4. Auto-Pause Mechanisme - Automatisch pauzeren na stilte
  5. Connection Status Indicator - Visuele feedback van verbindingsstatus

Backend

  1. 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]
  1. 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:

// 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:

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:

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