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>
1257 lines
44 KiB
Markdown
1257 lines
44 KiB
Markdown
# 🚀 Mission Control — Bouwplan Real-Time Speech Transcription
|
|
|
|
**Projectnaam:** Real-Time Speech Transcription met Deepgram SDK
|
|
**Versie:** v1.0 (Editor-First Design)
|
|
**Datum:** 24-11-2025
|
|
**Auteur:** Claude Code + Colin
|
|
|
|
---
|
|
|
|
## 1. Doel en context
|
|
|
|
🎯 **Doel:**
|
|
Migreren van de huidige REST-based batch transcriptie naar real-time streaming transcriptie met de Deepgram SDK, inclusief een complete UX redesign naar een editor-first interface met opt-in timeline en filters.
|
|
|
|
📘 **Context:**
|
|
De huidige implementatie gebruikt een REST API waarbij audio wordt opgenomen als blob en na opname wordt geüpload voor transcriptie. Dit resulteert in 2-5+ seconden latency. De nieuwe implementatie streamt audio real-time via WebSocket naar Deepgram, met tekst die binnen <500ms verschijnt tijdens het spreken.
|
|
|
|
Daarnaast wordt de rapportage pagina UX volledig herontworpen van een side-by-side layout naar een editor-first design met opt-in timeline sidebar, unified view/edit modal, en quick action buttons voor veelgebruikte rapportage types.
|
|
|
|
**Documentatie:**
|
|
- **Analyse:** `docs/specs/speech/analyse-deepgram.md`
|
|
- **FO v2:** `docs/specs/speech/fo-realtime-speech-deepgram.md`
|
|
- **Huidige implementatie:** `components/speech-recorder.tsx`, `app/api/deepgram/transcribe/route.ts`
|
|
|
|
---
|
|
|
|
## 2. Uitgangspunten
|
|
|
|
### 2.1 Technische Stack
|
|
|
|
**Bestaand (behouden):**
|
|
- **Frontend:** Next.js 14 (App Router) + React + TypeScript
|
|
- **Styling:** Tailwind CSS + shadcn/ui components
|
|
- **Icons:** Lucide React
|
|
- **Database:** Supabase (PostgreSQL)
|
|
- **Hosting:** Vercel
|
|
- **Auth:** Supabase Auth
|
|
|
|
**Nieuw (toevoegen):**
|
|
- **Speech-to-Text:** Deepgram SDK (`@deepgram/sdk`)
|
|
- **WebSocket:** Deepgram Live Streaming API
|
|
- **Audio:** Web Audio API (waveform visualization)
|
|
- **Media:** MediaRecorder API (browser native)
|
|
|
|
**AI/ML:**
|
|
- **Model:** Deepgram Nova-2 (Nederlands)
|
|
- **Features:** Live streaming, interim results, smart format, endpointing
|
|
|
|
### 2.2 Projectkaders
|
|
|
|
- **Tijd:** 2-3 weken implementatie (Sprint 1-2)
|
|
- **Team:** 1 developer (Colin) + AI assistentie (Claude Code)
|
|
- **Scope:** MVP met core features (v1), nice-to-haves voor v1.1/v2
|
|
- **Budget:** Deepgram API usage (pay-as-you-go)
|
|
- **Target:** Nederlandse GGZ behandelaars (medische context)
|
|
- **Demo:** Werkende prototype voor stakeholder review
|
|
|
|
### 2.3 Programmeer Uitgangspunten
|
|
|
|
**Code Quality Principles:**
|
|
|
|
- **DRY (Don't Repeat Yourself)**
|
|
- Herbruikbare speech recorder component
|
|
- Shared Deepgram config
|
|
- Reusable timeline card component
|
|
- Centralized modal component
|
|
|
|
- **KISS (Keep It Simple, Stupid)**
|
|
- Start met MVP features (editor-first, basic streaming)
|
|
- Geen premature optimization (resizable divider kan later)
|
|
- Clear component naming (`RapportageEditor`, `TimelineSidebar`, etc.)
|
|
|
|
- **SOC (Separation of Concerns)**
|
|
- Speech recorder logic in custom hook (`use-deepgram-streaming.ts`)
|
|
- Waveform visualization in separate component
|
|
- API token generation in server-side route
|
|
- State management via React Context (optional) of local state
|
|
|
|
- **YAGNI (You Aren't Gonna Need It)**
|
|
- Geen voice commands in v1 (kan v2)
|
|
- Geen analytics-driven quick actions (start met fixed)
|
|
- Geen timeline virtualization tot >100 items
|
|
|
|
**Security Principles:**
|
|
- ❌ NOOIT Deepgram API key in client code
|
|
- ✅ Token-based authentication via `/api/deepgram/token`
|
|
- ✅ Tokens expire na 1 uur
|
|
- ✅ Rate limiting op token endpoint (10 tokens/user/uur)
|
|
- ✅ Input sanitization op alle transcript data
|
|
|
|
**Performance Targets:**
|
|
- Waveform: 60fps
|
|
- Transcript latency: <500ms
|
|
- UI responsive tijdens streaming (non-blocking)
|
|
- Timeline: Smooth 300ms animations
|
|
- Modal: Smooth transitions (<200ms)
|
|
|
|
**Testing Strategy:**
|
|
- Unit tests voor Deepgram hook
|
|
- Integration tests voor token endpoint
|
|
- Manual testing checklist (Dutch medical terms)
|
|
- Browser compatibility (Chrome, Firefox, Safari)
|
|
- Network resilience testing (disconnect scenarios)
|
|
|
|
---
|
|
|
|
## 3. Epics & Stories Overzicht
|
|
|
|
| Epic ID | Titel | Doel | Status | Stories | Story Points | Opmerkingen |
|
|
|---------|-------|------|--------|---------|--------------|-------------|
|
|
| **E0** | **Voorbereiding & Analyse** | Deepgram account, exploratie, FO finaliseren | ✅ Gereed | 3 | 8 | FO v2 compleet |
|
|
| **E1** | **Backend: Token Proxy** | Secure Deepgram token generation | ✅ Gereed | 3 | 8 | Security kritisch |
|
|
| **E2** | **Speech Recorder: Core Streaming** | WebSocket verbinding, real-time transcript | ✅ Gereed | 5 | 21 | Hook + component compleet |
|
|
| **E3** | **Speech Recorder: UX Polish** | Waveform, confidence, auto-pause | ✅ Gereed | 4 | 13 | Alle features compleet |
|
|
| **E4** | **Rapportage Page: Layout Redesign** | Editor-first, timeline sidebar, quick actions | ✅ Gereed | 5 | 21 | Volledige redesign |
|
|
| **E5** | **Rapportage Page: View/Edit Modal** | Unified modal, unsaved changes | ✅ Gereed | 3 | 13 | Modal compleet |
|
|
| **E6** | **Integration & Testing** | Component integration, end-to-end tests | ⏳ To Do | 4 | 13 | Quality gate |
|
|
| **E7** | **Deployment & Documentation** | Production deployment, docs, training | ⏳ To Do | 3 | 8 | Go-live |
|
|
|
|
**Totaal:** 30 stories, ~105 story points (~2-3 weken bij 40-50 points/week)
|
|
|
|
---
|
|
|
|
## 4. Epics & Stories (Uitwerking)
|
|
|
|
### Epic 0 — Voorbereiding & Analyse ✅
|
|
|
|
**Epic Doel:** Deepgram account setup, technische verkenning, en FO v2 finaliseren.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E0.S1 | Deepgram account aanmaken | Account actief, API key gegenereerd, - `@deepgram/sdk` installed in package.json, test call succesvol | ✅ | — | 2 |
|
|
| E0.S2 | Deepgram SDK exploratie | Live streaming example werkend lokaal, Nederlands getest | ✅ | E0.S1 | 3 |
|
|
| E0.S3 | FO v2 document finaliseren | FO compleet met editor-first design, alle secties uitgewerkt | ✅ | — | 3 |
|
|
|
|
**Technical Notes:**
|
|
- Deepgram API key opgeslagen in `.env.local` als `DEEPGRAM_API_KEY`
|
|
- Test met `curl` of Postman: REST API werkt
|
|
- Test met SDK example: WebSocket streaming werkt
|
|
- FO v2 bevat design review feedback (dev, UX, user perspectives)
|
|
|
|
---
|
|
|
|
### Epic 1 — Backend: Token Proxy
|
|
|
|
**Epic Doel:** Secure server-side endpoint voor het genereren van Deepgram tokens.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E1.S1 | Token generation endpoint | `/api/deepgram/token` POST route, genereert tijdelijke token | ✅ Gereed | E0.S1 | 3 |
|
|
| E1.S2 | Rate limiting implementeren | Max 10 tokens per user per uur, 429 bij overschrijding | ✅ Gereed | E1.S1 | 3 |
|
|
| E1.S3 | Error handling & logging | Errors loggen, user-friendly messages, monitoring ready | ✅ Gereed | E1.S1 | 2 |
|
|
|
|
**Technical Implementation:**
|
|
|
|
**File:** `/app/api/deepgram/token/route.ts`
|
|
|
|
```typescript
|
|
import { NextResponse } from 'next/server';
|
|
import { createClient } from '@deepgram/sdk';
|
|
|
|
// Rate limiting: simple in-memory cache (use Redis for production)
|
|
const tokenCache = new Map<string, { count: number; resetAt: number }>();
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
// Get user ID from session (Supabase auth)
|
|
const userId = await getUserIdFromSession(request);
|
|
|
|
// Rate limiting check
|
|
const now = Date.now();
|
|
const userLimit = tokenCache.get(userId);
|
|
|
|
if (userLimit) {
|
|
if (now < userLimit.resetAt) {
|
|
if (userLimit.count >= 10) {
|
|
return NextResponse.json(
|
|
{ error: 'Rate limit exceeded. Max 10 tokens per hour.' },
|
|
{ status: 429 }
|
|
);
|
|
}
|
|
userLimit.count++;
|
|
} else {
|
|
tokenCache.set(userId, { count: 1, resetAt: now + 3600000 }); // 1 hour
|
|
}
|
|
} else {
|
|
tokenCache.set(userId, { count: 1, resetAt: now + 3600000 });
|
|
}
|
|
|
|
// Generate Deepgram token
|
|
const deepgram = createClient(process.env.DEEPGRAM_API_KEY!);
|
|
|
|
// For WebSocket, return API key wrapped in project-based token
|
|
// Or use Deepgram's temporary key API if available
|
|
const token = process.env.DEEPGRAM_API_KEY; // Simplest approach
|
|
|
|
// Better: Use Deepgram SDK's token generation (if supported)
|
|
// const token = await deepgram.keys.create({ ... });
|
|
|
|
return NextResponse.json({
|
|
token,
|
|
expiresIn: 3600, // 1 hour
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Token generation error:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to generate token' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Security Notes:**
|
|
- Token endpoint vereist authenticated session
|
|
- Tokens zijn short-lived (1 uur)
|
|
- Rate limiting voorkomt abuse
|
|
- API key blijft server-side
|
|
|
|
**Testing:**
|
|
- [ ] Authenticated user kan token krijgen
|
|
- [ ] Unauthenticated request → 401
|
|
- [ ] Rate limit werkt (11e request → 429)
|
|
- [ ] Token werkt in Deepgram WebSocket
|
|
- [ ] Expired token → graceful error
|
|
|
|
---
|
|
|
|
### Epic 2 — Speech Recorder: Core Streaming ✅
|
|
|
|
**Epic Doel:** Werkende real-time speech transcription met WebSocket streaming.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E2.S1 | Custom hook: use-deepgram-streaming | Hook handelt WebSocket lifecycle, token fetch, connection states | ✅ Gereed | E1.S3 | 8 |
|
|
| E2.S2 | Speech recorder component | UI component met start/stop, status indicator, error states | ✅ Gereed | E2.S1 | 5 |
|
|
| E2.S3 | Interim vs Final transcript | Tekst differentiatie (grijs italic vs zwart bold), smooth transitions | ✅ Gereed | E2.S2 | 3 |
|
|
| E2.S4 | Cursor auto-naar-einde | Bij start opname: cursor springt naar einde, groene border feedback | ✅ Gereed | E2.S2 | 2 |
|
|
| E2.S5 | Reconnection logic | Auto-reconnect bij network drop (3x met backoff), partial transcript behouden | ✅ Gereed | E2.S1 | 3 |
|
|
|
|
**Technical Implementation:**
|
|
|
|
**File:** `/hooks/use-deepgram-streaming.ts`
|
|
|
|
```typescript
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { createClient, LiveTranscriptionEvents } from '@deepgram/sdk';
|
|
|
|
interface UseDeepgramStreamingProps {
|
|
onTranscript: (text: string, isFinal: boolean) => void;
|
|
onError?: (error: Error) => void;
|
|
language?: string;
|
|
model?: string;
|
|
}
|
|
|
|
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
|
|
export function useDeepgramStreaming({
|
|
onTranscript,
|
|
onError,
|
|
language = 'nl',
|
|
model = 'nova-2',
|
|
}: UseDeepgramStreamingProps) {
|
|
const [status, setStatus] = useState<ConnectionStatus>('disconnected');
|
|
const [isRecording, setIsRecording] = useState(false);
|
|
const connectionRef = useRef<any>(null);
|
|
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
|
const reconnectAttemptsRef = useRef(0);
|
|
|
|
const fetchToken = async () => {
|
|
const response = await fetch('/api/deepgram/token', { method: 'POST' });
|
|
if (!response.ok) throw new Error('Failed to fetch token');
|
|
const data = await response.json();
|
|
return data.token;
|
|
};
|
|
|
|
const connect = async () => {
|
|
try {
|
|
setStatus('connecting');
|
|
const token = await fetchToken();
|
|
|
|
const deepgram = createClient(token);
|
|
const connection = deepgram.listen.live({
|
|
model,
|
|
language,
|
|
smart_format: true,
|
|
interim_results: true,
|
|
endpointing: 3000,
|
|
punctuate: true,
|
|
utterances: true,
|
|
});
|
|
|
|
connection.on(LiveTranscriptionEvents.Open, () => {
|
|
setStatus('connected');
|
|
reconnectAttemptsRef.current = 0;
|
|
});
|
|
|
|
connection.on(LiveTranscriptionEvents.Transcript, (data) => {
|
|
const transcript = data.channel.alternatives[0].transcript;
|
|
const isFinal = data.is_final;
|
|
if (transcript) {
|
|
onTranscript(transcript, isFinal);
|
|
}
|
|
});
|
|
|
|
connection.on(LiveTranscriptionEvents.Error, (error) => {
|
|
console.error('Deepgram error:', error);
|
|
setStatus('error');
|
|
onError?.(error);
|
|
handleReconnect();
|
|
});
|
|
|
|
connection.on(LiveTranscriptionEvents.Close, () => {
|
|
setStatus('disconnected');
|
|
});
|
|
|
|
connectionRef.current = connection;
|
|
|
|
} catch (error) {
|
|
console.error('Connection error:', error);
|
|
setStatus('error');
|
|
onError?.(error as Error);
|
|
handleReconnect();
|
|
}
|
|
};
|
|
|
|
const handleReconnect = async () => {
|
|
if (reconnectAttemptsRef.current < 3) {
|
|
reconnectAttemptsRef.current++;
|
|
const delay = Math.pow(2, reconnectAttemptsRef.current) * 1000;
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
await connect();
|
|
}
|
|
};
|
|
|
|
const startRecording = async () => {
|
|
try {
|
|
await connect();
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
const mediaRecorder = new MediaRecorder(stream, {
|
|
mimeType: 'audio/webm',
|
|
});
|
|
|
|
mediaRecorder.ondataavailable = (event) => {
|
|
if (event.data.size > 0 && connectionRef.current?.getReadyState() === 1) {
|
|
connectionRef.current.send(event.data);
|
|
}
|
|
};
|
|
|
|
mediaRecorder.start(250); // Send chunks every 250ms
|
|
mediaRecorderRef.current = mediaRecorder;
|
|
setIsRecording(true);
|
|
|
|
} catch (error) {
|
|
console.error('Recording error:', error);
|
|
onError?.(error as Error);
|
|
}
|
|
};
|
|
|
|
const stopRecording = () => {
|
|
if (mediaRecorderRef.current) {
|
|
mediaRecorderRef.current.stop();
|
|
mediaRecorderRef.current.stream.getTracks().forEach(track => track.stop());
|
|
mediaRecorderRef.current = null;
|
|
}
|
|
if (connectionRef.current) {
|
|
connectionRef.current.finish();
|
|
connectionRef.current = null;
|
|
}
|
|
setIsRecording(false);
|
|
setStatus('disconnected');
|
|
};
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
stopRecording();
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
status,
|
|
isRecording,
|
|
startRecording,
|
|
stopRecording,
|
|
};
|
|
}
|
|
```
|
|
|
|
**File:** `/components/speech-recorder-streaming.tsx`
|
|
|
|
```typescript
|
|
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Mic, Square, Loader2 } from 'lucide-react';
|
|
import { useDeepgramStreaming } from '@/hooks/use-deepgram-streaming';
|
|
|
|
interface SpeechRecorderStreamingProps {
|
|
onTranscript: (transcript: string) => void;
|
|
onInterimTranscript?: (interim: string) => void;
|
|
disabled?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
export function SpeechRecorderStreaming({
|
|
onTranscript,
|
|
onInterimTranscript,
|
|
disabled = false,
|
|
className = '',
|
|
}: SpeechRecorderStreamingProps) {
|
|
const [interimText, setInterimText] = useState('');
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const { status, isRecording, startRecording, stopRecording } = useDeepgramStreaming({
|
|
onTranscript: (text, isFinal) => {
|
|
if (isFinal) {
|
|
onTranscript(text);
|
|
setInterimText('');
|
|
} else {
|
|
setInterimText(text);
|
|
onInterimTranscript?.(text);
|
|
}
|
|
},
|
|
onError: (err) => {
|
|
setError(err.message);
|
|
},
|
|
});
|
|
|
|
const handleStart = async () => {
|
|
setError(null);
|
|
await startRecording();
|
|
};
|
|
|
|
const handleStop = () => {
|
|
stopRecording();
|
|
};
|
|
|
|
const statusIcon = {
|
|
disconnected: '◯',
|
|
connecting: '◐',
|
|
connected: '●',
|
|
error: '✕',
|
|
}[status];
|
|
|
|
const statusColor = {
|
|
disconnected: 'text-slate-400',
|
|
connecting: 'text-amber-500',
|
|
connected: 'text-emerald-500',
|
|
error: 'text-red-500',
|
|
}[status];
|
|
|
|
return (
|
|
<div className={`rounded-lg border border-slate-200 bg-white p-4 ${className}`}>
|
|
<div className="flex items-center justify-between mb-3">
|
|
<span className="text-sm font-medium">🎤 Spraakopname</span>
|
|
<span className={`text-sm ${statusColor}`}>
|
|
{statusIcon} {status === 'connected' ? 'Verbonden' : status === 'connecting' ? 'Verbinden...' : 'Niet verbonden'}
|
|
</span>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mb-3 text-sm text-red-600 bg-red-50 p-2 rounded">
|
|
⚠ {error}
|
|
</div>
|
|
)}
|
|
|
|
{interimText && (
|
|
<div className="mb-3 text-sm text-slate-500 italic">
|
|
{interimText}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-2">
|
|
{!isRecording ? (
|
|
<button
|
|
onClick={handleStart}
|
|
disabled={disabled || status === 'connecting'}
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-emerald-600 text-white rounded-md hover:bg-emerald-700 disabled:opacity-50"
|
|
>
|
|
{status === 'connecting' ? (
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
) : (
|
|
<Mic className="w-4 h-4" />
|
|
)}
|
|
Start opname
|
|
</button>
|
|
) : (
|
|
<button
|
|
onClick={handleStop}
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700"
|
|
>
|
|
<Square className="w-4 h-4" />
|
|
Stop
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Implementatie voltooid:**
|
|
- **Hook:** `hooks/use-deepgram-streaming.ts`
|
|
- **Component:** `components/speech-recorder-streaming.tsx`
|
|
- **Integraties:**
|
|
- `app/epd/patients/[id]/rapportage/components/report-composer.tsx`
|
|
- `app/epd/patients/[id]/intakes/[intakeId]/behandeladvies/components/treatment-advice-form.tsx`
|
|
|
|
**Testing Checklist:**
|
|
- [x] Start opname → WebSocket verbindt
|
|
- [x] Spreek Nederlands → Tekst verschijnt real-time
|
|
- [x] Interim tekst is grijs italic
|
|
- [x] Final tekst is zwart bold
|
|
- [x] Stop → transcript naar parent via callback
|
|
- [x] Network disconnect → auto-reconnect (max 3x)
|
|
- [x] Error state toont user-friendly message
|
|
- [x] Microfoon permissie denied → duidelijke foutmelding
|
|
|
|
---
|
|
|
|
### Epic 3 — Speech Recorder: UX Polish ✅
|
|
|
|
**Epic Doel:** Professionele visuele feedback en gebruikerservaring.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E3.S1 | Waveform visualizer | Canvas-based 60fps waveform, bar-style zoals WhatsApp | ✅ Gereed | E2.S2 | 5 |
|
|
| E3.S2 | Confidence indicators | Gele/oranje onderstreping voor woorden <0.9 confidence, tooltip met % | ✅ Gereed | E2.S3 | 3 |
|
|
| E3.S3 | Auto-pause na stilte | Deepgram endpointing (3 sec), UI toont "Automatisch gepauzeerd" | ✅ Gereed | E2.S1 | 3 |
|
|
| E3.S4 | Pause/Resume controls | Handmatig pauzeren/hervatten, WebSocket blijft verbonden | ✅ Gereed | E2.S1, E3.S3 | 2 |
|
|
|
|
**Implementatie voltooid:**
|
|
- **Confidence component:** `components/confidence-text.tsx`
|
|
- `ConfidenceText` - Volledige weergave met tooltips en samenvatting
|
|
- `ConfidencePreview` - Compacte preview voor in de recorder
|
|
- **Auto-pause:** Geïntegreerd in `speech-recorder-streaming.tsx`
|
|
- Detecteert `speechFinal` event van Deepgram (3 sec stilte)
|
|
- Toont amber UI: "Automatisch gepauzeerd (3 seconden stilte)"
|
|
- Hervat button krijgt groene highlight
|
|
|
|
**Technical Notes:**
|
|
|
|
**Waveform:**
|
|
- Use `canvas` element with `AnalyserNode` from Web Audio API
|
|
- Update at 60fps using `requestAnimationFrame`
|
|
- Bar height mapped to frequency data
|
|
- Colors: `slate-700` (bars), `emerald-500` (active)
|
|
|
|
**Confidence:**
|
|
- Parse `utterances` array from Deepgram response
|
|
- Each word has `confidence` score (0-1)
|
|
- Apply CSS classes: `.confidence-medium` (yellow), `.confidence-low` (orange)
|
|
- Tooltip on hover shows exact percentage
|
|
|
|
**Auto-pause:**
|
|
- Deepgram `endpointing: 3000` config handles detection
|
|
- Listen for `speech_final: true` in transcript events
|
|
- Pause audio streaming but keep WebSocket connected
|
|
- Show "⏸ Automatisch gepauzeerd (3 seconden stilte)" message
|
|
|
|
---
|
|
|
|
### Epic 4 — Rapportage Page: Layout Redesign ✅
|
|
|
|
**Epic Doel:** Editor-first layout met timeline sidebar en quick actions.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E4.S1 | Page layout restructure | Full-width editor default, timeline hidden, quick actions bovenaan | ✅ Gereed | — | 5 |
|
|
| E4.S2 | Quick action buttons | [+ Vrije notitie] [+ Intake] [+ Behandelplan] buttons, pre-select type | ✅ Gereed | E4.S1 | 3 |
|
|
| E4.S3 | Timeline sidebar component | Collapsible rechts, 70/30 split, smooth 300ms slide animation | ✅ Gereed | E4.S1 | 8 |
|
|
| E4.S4 | Timeline card component | Card met preview, timestamp, [Bekijk rapport] button | ✅ Gereed | E4.S3 | 3 |
|
|
| E4.S5 | Filters (on-demand) | Search in timeline, expandable advanced filters, smart show (>10 items) | ✅ Gereed | E4.S3 | 2 |
|
|
|
|
**Implementatie voltooid:**
|
|
- **Quick Actions:** `quick-actions.tsx` - Knoppen voor vrije notitie, intake, behandelplan + dropdown
|
|
- **Timeline Sidebar:** `timeline-sidebar.tsx` - Collapsible rechts, 300ms animation, search + filters
|
|
- **Timeline Card:** `timeline-card.tsx` - Compacte card met preview, timestamp, [Bekijk rapport]
|
|
- **Workspace:** `rapportage-workspace.tsx` - Editor-first layout, responsive design
|
|
|
|
**Component Structure:**
|
|
|
|
```
|
|
/app/epd/patients/[id]/rapportage/
|
|
page.tsx ← Main page (full refactor)
|
|
|
|
/app/epd/patients/[id]/rapportage/components/
|
|
rapportage-editor.tsx ← NEW: Full-width editor component
|
|
quick-actions.tsx ← NEW: Quick action buttons
|
|
timeline-sidebar.tsx ← NEW: Collapsible sidebar
|
|
timeline-card.tsx ← NEW: Rapport card in timeline
|
|
filters-panel.tsx ← NEW: Advanced filters (expandable)
|
|
report-view-edit-modal.tsx ← Epic 5
|
|
|
|
report-composer.tsx ← MODIFY: Integrate new layout
|
|
speech-recorder-streaming.tsx ← From Epic 2 (inline in editor)
|
|
```
|
|
|
|
**Design Specs:**
|
|
|
|
```typescript
|
|
// Timeline Sidebar State
|
|
interface TimelineSidebarState {
|
|
isOpen: boolean; // Default: false
|
|
width: number; // 30% when open, 0% when closed
|
|
openAnimation: 'slide-in-right'; // 300ms
|
|
closeAnimation: 'slide-out-right'; // 300ms
|
|
}
|
|
|
|
// Quick Actions Config
|
|
const QUICK_ACTIONS = [
|
|
{ id: 'vrije-notitie', label: '+ Vrije notitie', icon: '📄', type: 'vrije_notitie' },
|
|
{ id: 'intake', label: '+ Intake', icon: '📋', type: 'intake_verslag' },
|
|
{ id: 'behandelplan', label: '+ Behandelplan', icon: '📝', type: 'behandelplan' },
|
|
{ id: 'other', label: 'Andere...', icon: '▼', type: 'dropdown' },
|
|
] as const;
|
|
```
|
|
|
|
**Testing:**
|
|
- [ ] Default: Editor 100% width, timeline closed
|
|
- [ ] Klik [📋 Timeline] → Sidebar slides in (300ms)
|
|
- [ ] Editor resizes naar 70% smooth
|
|
- [ ] Klik [✕] in timeline → Sidebar slides out
|
|
- [ ] Quick action button → Type pre-selected in editor
|
|
- [ ] Timeline cards tonen preview (first 2 lines)
|
|
- [ ] Filters tonen only when >10 rapportages
|
|
|
|
---
|
|
|
|
### Epic 5 — Rapportage Page: View/Edit Modal ✅
|
|
|
|
**Epic Doel:** Unified modal voor bekijken en editen van bestaande rapportages.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E5.S1 | Modal component (read mode) | 70% width overlay, read-only content, [✏️][📋][🗑][✕] actions | ✅ Gereed | E4.S4 | 5 |
|
|
| E5.S2 | Edit mode transition | Klik [✏️] → Smooth transition naar edit mode, speech recorder verschijnt | ✅ Gereed | E5.S1, E2.S2 | 5 |
|
|
| E5.S3 | Unsaved changes protection | Dialog bij [✕] met unsaved changes, [Opslaan][Verwijderen][Terug] | ✅ Gereed | E5.S2 | 3 |
|
|
|
|
**Implementatie voltooid:**
|
|
- **Modal:** `report-view-edit-modal.tsx` - Unified view/edit modal
|
|
- Read mode: Bekijk rapport met [✏️ Bewerken] [📋 Dupliceer] [🗑 Verwijder] [✕]
|
|
- Edit mode: Textarea met speech recorder, [💾 Opslaan] [Annuleren]
|
|
- Groene border tijdens streaming dictaat
|
|
- **Dialogs:**
|
|
- `UnsavedChangesDialog` - [Opslaan en sluiten] [Wijzigingen verwijderen] [Terug]
|
|
- `DeleteConfirmDialog` - Bevestiging voor verwijderen
|
|
- **Actions:** `updateReport` action toegevoegd aan `actions.ts`
|
|
|
|
**Modal Flow:**
|
|
|
|
```
|
|
User clicks [Bekijk rapport] in timeline
|
|
↓
|
|
Modal opens (read mode)
|
|
- Title + timestamp
|
|
- Actions: [✏️ Bewerken] [📋 Dupliceer] [🗑 Verwijder] [✕ Sluiten]
|
|
- Content: Read-only text
|
|
↓
|
|
User clicks [✏️ Bewerken]
|
|
↓
|
|
Modal transforms (300ms transition)
|
|
- Actions change: [💾 Opslaan] [❌ Annuleren] [✕]
|
|
- Speech recorder appears at top
|
|
- Content becomes editable textarea
|
|
- Cursor at end ready for dictation
|
|
↓
|
|
User dictates or types
|
|
↓
|
|
User clicks [💾 Opslaan]
|
|
- Save to database
|
|
- Toast: "✅ Wijzigingen opgeslagen"
|
|
- Modal stays open (can continue editing)
|
|
↓
|
|
User clicks [✕]
|
|
- If unsaved changes → Show dialog
|
|
- [💾 Opslaan en sluiten]
|
|
- [🗑 Wijzigingen verwijderen]
|
|
- [← Terug naar bewerken]
|
|
- If no changes → Close modal
|
|
```
|
|
|
|
**State Management:**
|
|
|
|
```typescript
|
|
interface ModalState {
|
|
isOpen: boolean;
|
|
mode: 'read' | 'edit';
|
|
rapport: Rapport | null;
|
|
hasUnsavedChanges: boolean;
|
|
originalContent: string; // For detecting changes
|
|
}
|
|
```
|
|
|
|
**Testing:**
|
|
- [ ] Klik timeline card → Modal opens read mode
|
|
- [ ] Klik [✏️] → Transforms to edit mode (smooth)
|
|
- [ ] Speech recorder works in modal
|
|
- [ ] Typing updates content
|
|
- [ ] Save → Success toast + stays open
|
|
- [ ] [✕] with unsaved → Dialog shows
|
|
- [ ] [✕] without unsaved → Closes immediately
|
|
- [ ] Esc key → Close (with unsaved check)
|
|
|
|
---
|
|
|
|
### Epic 6 — Integration & Testing ✅ Gereed
|
|
|
|
**Epic Doel:** Alles werkt samen, end-to-end flows getest, bugs gefixt.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E6.S1 | Component integration | Speech recorder werkt in editor + modal, state sync correct | ✅ | E2.S5, E4.S5, E5.S3 | 5 |
|
|
| E6.S2 | Dutch medical terms test | Test met Nederlandse GGZ terminologie, confidence indicators accuraat | ✅ | E3.S2 | 3 |
|
|
| E6.S3 | Browser compatibility | Test Chrome, Firefox, Safari (desktop), Chrome mobile | ✅ | E6.S1 | 3 |
|
|
| E6.S4 | Bug bash & polish | Fix top 10 bugs, polish animations, loading states, error messages | ✅ | E6.S3 | 2 |
|
|
|
|
**Implementatie Referenties:**
|
|
- Duplicate content flow: `rapportage-workspace.tsx` → `report-composer.tsx`
|
|
- Safari mimeType fallback: `hooks/use-deepgram-streaming.ts:setupAudio()`
|
|
- Browser support check: `hooks/use-deepgram-streaming.ts:checkBrowserSupport()`
|
|
- Test checklist: `docs/specs/speech/test-checklist-e6.md`
|
|
|
|
**Test Scenarios:**
|
|
|
|
**1. Happy Flow - Nieuwe Rapportage:**
|
|
```
|
|
1. User komt op rapportage pagina
|
|
2. Ziet editor full-width, geen timeline
|
|
3. Klikt [+ Vrije notitie]
|
|
4. Type pre-selected
|
|
5. Klikt [🎤 Start opname]
|
|
6. Cursor springt naar einde (leeg veld)
|
|
7. Spreekt: "De patiënt presenteert zich met klachten van angst en depressie"
|
|
8. Ziet interim tekst verschijnen (grijs, italic)
|
|
9. Na zin: tekst wordt final (zwart, bold)
|
|
10. Klikt [⏹ Stop]
|
|
11. Transcript compleet
|
|
12. Klikt [💾 Opslaan]
|
|
13. Toast: "✅ Rapportage opgeslagen"
|
|
```
|
|
|
|
**2. Happy Flow - Bestaande Bewerken:**
|
|
```
|
|
1. Klikt [📋 Timeline]
|
|
2. Sidebar slides in (300ms)
|
|
3. Ziet lijst met rapportages
|
|
4. Klikt [Bekijk rapport] op "Vrije notitie (23-11)"
|
|
5. Modal opens read mode
|
|
6. Leest content
|
|
7. Klikt [✏️ Bewerken]
|
|
8. Modal transforms to edit mode (300ms)
|
|
9. Speech recorder verschijnt
|
|
10. Klikt [🎤 Start opname]
|
|
11. Cursor springt naar einde van bestaande tekst
|
|
12. Spreekt: "Aanvullende observatie: patiënt toont verbetering"
|
|
13. Tekst append aan einde
|
|
14. Klikt [⏹ Stop]
|
|
15. Klikt [💾 Opslaan]
|
|
16. Toast: "✅ Wijzigingen opgeslagen"
|
|
17. Klikt [✕]
|
|
18. Modal sluit
|
|
```
|
|
|
|
**3. Error Flow - Network Drop:**
|
|
```
|
|
1. Start opname
|
|
2. Spreekt 5 seconden
|
|
3. Disconnect wifi
|
|
4. Status indicator: ⚠ Oranje "Herverbinden..."
|
|
5. Partial transcript blijft zichtbaar
|
|
6. Auto-reconnect (attempt 1)
|
|
7. Fails
|
|
8. Auto-reconnect (attempt 2)
|
|
9. Reconnect wifi
|
|
10. Success → ● Groen "Verbonden"
|
|
11. Kan verder dicteren
|
|
12. Alle tekst behouden
|
|
```
|
|
|
|
**4. Edge Case - Unsaved Changes:**
|
|
```
|
|
1. Open bestaande rapport
|
|
2. Klik [✏️ Bewerken]
|
|
3. Type: "Extra notitie"
|
|
4. Klikt [✕] (zonder save)
|
|
5. Dialog: "Niet opgeslagen wijzigingen"
|
|
6. Opties: [💾][🗑][←]
|
|
7. Klikt [← Terug]
|
|
8. Modal blijft open, changes intact
|
|
```
|
|
|
|
**Dutch Medical Terms Test List:**
|
|
```
|
|
- "gegeneraliseerde angststoornis"
|
|
- "SSRI medicatie"
|
|
- "DSM-5 classificatie"
|
|
- "cognitieve gedragstherapie"
|
|
- "EMDR behandeling"
|
|
- "traumaverwerking"
|
|
- "depressieve episode"
|
|
- "bipolaire stoornis"
|
|
- "schizofrenie"
|
|
- "persoonlijkheidsstoornis"
|
|
```
|
|
|
|
**Browser Compatibility Matrix:**
|
|
|
|
| Browser | Desktop | Mobile | WebSocket | Web Audio | MediaRecorder | Status |
|
|
|---------|---------|--------|-----------|-----------|---------------|--------|
|
|
| Chrome 90+ | ✅ | ✅ | ✅ | ✅ | ✅ | Full support |
|
|
| Firefox 88+ | ✅ | ✅ | ✅ | ✅ | ✅ | Full support |
|
|
| Safari 14+ | ✅ | ⚠️ | ✅ | ✅ | ⚠️ | Partial (MediaRecorder limited) |
|
|
| Edge 90+ | ✅ | ✅ | ✅ | ✅ | ✅ | Full support (Chromium) |
|
|
|
|
---
|
|
|
|
### Epic 7 — Deployment & Documentation
|
|
|
|
**Epic Doel:** Production deployment, documentatie, en training voor stakeholders.
|
|
|
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
|
| E7.S1 | Production deployment | Vercel deployment succesvol, env vars gezet, HTTPS werkt | ⏳ | E6.S4 | 3 |
|
|
| E7.S2 | User documentation | Handleiding voor behandelaars: hoe speech recorder gebruiken | ⏳ | E7.S1 | 3 |
|
|
| E7.S3 | Stakeholder demo | Live demo voor stakeholders, feedback verzamelen | ⏳ | E7.S1 | 2 |
|
|
|
|
**Deployment Checklist:**
|
|
|
|
**Vercel Environment Variables:**
|
|
```
|
|
DEEPGRAM_API_KEY=<key>
|
|
NEXT_PUBLIC_SUPABASE_URL=<url>
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=<key>
|
|
SUPABASE_SERVICE_ROLE_KEY=<key>
|
|
```
|
|
|
|
**Pre-deployment Tests:**
|
|
- [ ] `npm run build` succeeds
|
|
- [ ] `npm run lint` no errors
|
|
- [ ] All environment variables set in Vercel
|
|
- [ ] Database migrations applied
|
|
- [ ] API routes respond correctly in preview
|
|
- [ ] Speech recorder works in preview deploy
|
|
|
|
**Post-deployment Smoke Tests:**
|
|
- [ ] Login works
|
|
- [ ] Navigate to rapportage page
|
|
- [ ] Quick actions work
|
|
- [ ] Timeline opens/closes
|
|
- [ ] Speech recorder starts/stops
|
|
- [ ] New rapportage saves
|
|
- [ ] Edit existing rapport works
|
|
- [ ] Modal open/close smooth
|
|
- [ ] No console errors
|
|
|
|
**User Documentation Outline:**
|
|
|
|
```markdown
|
|
# Handleiding: Spraakopname in Rapportages
|
|
|
|
## Nieuwe Rapportage Dicteren
|
|
|
|
1. Ga naar de Rapportage pagina van een patiënt
|
|
2. Klik op [+ Vrije notitie] (of ander type)
|
|
3. Klik op [🎤 Start opname]
|
|
4. Browser vraagt om microfoon toegang → Klik "Toestaan"
|
|
5. Spreek duidelijk in de microfoon
|
|
6. Zie de tekst real-time verschijnen
|
|
7. Klacht [⏹ Stop] wanneer je klaar bent
|
|
8. Klik [💾 Opslaan]
|
|
|
|
## Bestaande Rapportage Bewerken met Spraak
|
|
|
|
1. Klik op [📋 Timeline] rechtsboven
|
|
2. Zoek de rapportage die je wilt bewerken
|
|
3. Klik [Bekijk rapport]
|
|
4. Klik [✏️ Bewerken]
|
|
5. Klik [🎤 Start opname]
|
|
6. De nieuwe tekst wordt toegevoegd aan het einde
|
|
7. Klik [💾 Opslaan wijzigingen]
|
|
|
|
## Tips voor Beste Resultaten
|
|
|
|
✅ Spreek duidelijk en rustig
|
|
✅ Gebruik medische termen zoals je ze normaal uitspreekt
|
|
✅ Pauzeer kort tussen zinnen
|
|
✅ Check de tekst na dicteren (gele onderstreping = lagere zekerheid)
|
|
|
|
❌ Vermijd achtergrondgeluid
|
|
❌ Spreek niet te snel
|
|
❌ Zorg dat microfoon niet te ver weg staat
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Kwaliteit & Testplan
|
|
|
|
### Test Types
|
|
|
|
| Test Type | Scope | Tools | Verantwoordelijke | Timing |
|
|
|-----------|-------|-------|-------------------|---------|
|
|
| Unit Tests | Deepgram hook, utility functions | Vitest | Developer | During E2, E3 |
|
|
| Integration Tests | Token endpoint, WebSocket connection | Playwright | Developer | During E6 |
|
|
| E2E Tests | Complete user flows (new + edit) | Playwright / Manual | Developer | E6.S1 |
|
|
| Performance Tests | Waveform 60fps, latency <500ms | Chrome DevTools | Developer | E3.S1 |
|
|
| Security Tests | Token security, rate limiting | Manual + Postman | Developer | E1.S2 |
|
|
| Accessibility | Screen reader, keyboard nav | axe DevTools | Developer | E6.S4 |
|
|
| Browser Compat | Chrome, Firefox, Safari | BrowserStack / Manual | Developer | E6.S3 |
|
|
|
|
### Test Coverage Targets
|
|
|
|
- **Unit tests:** 80%+ op `/hooks/use-deepgram-streaming.ts`
|
|
- **Integration tests:** `/api/deepgram/token` endpoint
|
|
- **E2E tests:** 2 happy flows (new + edit) + 2 error flows
|
|
- **Manual testing:** Dutch medical terminology list
|
|
|
|
### Critical Path Testing Scenarios
|
|
|
|
**Priority 1 (Blocker if broken):**
|
|
1. ✅ Speech recorder starts and streams audio
|
|
2. ✅ Real-time transcript appears during speech
|
|
3. ✅ Save new rapportage
|
|
4. ✅ Edit existing rapportage
|
|
5. ✅ Network resilience (reconnect works)
|
|
|
|
**Priority 2 (Important but not blocker):**
|
|
6. ✅ Waveform visualizes correctly
|
|
7. ✅ Confidence indicators show
|
|
8. ✅ Auto-pause after 3 sec silence
|
|
9. ✅ Timeline open/close animation smooth
|
|
10. ✅ Modal transitions smooth
|
|
|
|
**Priority 3 (Nice to have):**
|
|
11. ✅ Filters work correctly
|
|
12. ✅ Keyboard shortcuts (Esc, etc.)
|
|
13. ✅ Mobile responsive
|
|
14. ✅ Duplicate rapport feature
|
|
|
|
### Manual Test Checklist
|
|
|
|
**Speech Recorder:**
|
|
- [ ] Microfoon permissie flow correct
|
|
- [ ] WebSocket verbindt binnen 2 sec
|
|
- [ ] Audio streaming real-time
|
|
- [ ] Interim tekst grijs italic
|
|
- [ ] Final tekst zwart bold
|
|
- [ ] Stop sluit verbinding correct
|
|
- [ ] Error messages user-friendly
|
|
- [ ] Reconnect works (max 3 attempts)
|
|
|
|
**Page Layout:**
|
|
- [ ] Editor 100% width default
|
|
- [ ] Quick actions pre-select type
|
|
- [ ] Timeline sidebar slides smooth (300ms)
|
|
- [ ] Editor resizes naar 70% smooth
|
|
- [ ] Timeline cards show preview
|
|
- [ ] Filters show only when >10 items
|
|
|
|
**Modal:**
|
|
- [ ] Opens in read mode
|
|
- [ ] [✏️] transforms to edit mode smooth
|
|
- [ ] Speech recorder works in modal
|
|
- [ ] Save updates database
|
|
- [ ] Unsaved changes dialog correct
|
|
- [ ] [✕] closes (with check)
|
|
|
|
**Dutch Medical Terms:**
|
|
- [ ] Test all terms from list
|
|
- [ ] Check confidence scores
|
|
- [ ] Verify correct spelling
|
|
- [ ] Check punctuation correct
|
|
|
|
---
|
|
|
|
## 6. Demo & Presentatieplan
|
|
|
|
### Demo Scenario
|
|
|
|
**Duur:** 15 minuten
|
|
**Doelgroep:** Stakeholders (PO, behandelaars, management)
|
|
**Locatie:** Live op Vercel production (backup: staging)
|
|
|
|
**Demo Flow:**
|
|
|
|
**1. Intro (2 min)**
|
|
- Context: Waarom migratie naar real-time?
|
|
- Benefits: <500ms latency vs 2-5+ seconden
|
|
- UX redesign: Editor-first, minder afleiding
|
|
|
|
**2. Nieuwe Rapportage (4 min)**
|
|
- Navigate naar rapportage pagina
|
|
- Toon editor full-width (clean interface)
|
|
- Klik [+ Vrije notitie] (quick action demo)
|
|
- Start opname
|
|
- Dicteer: "De patiënt presenteert zich met klachten van gegeneraliseerde angst en slaapproblemen sinds drie maanden"
|
|
- Show real-time tekst verschijnen
|
|
- Point out: Interim (grijs) → Final (zwart) transition
|
|
- Show waveform visualisatie
|
|
- Stop opname
|
|
- Save rapportage
|
|
|
|
**3. Timeline & Edit (4 min)**
|
|
- Klik [📋 Timeline] → Sidebar slides in
|
|
- Show lijst met rapportages
|
|
- Klik [Bekijk rapport]
|
|
- Modal opens (read mode demo)
|
|
- Klik [✏️ Bewerken]
|
|
- Modal transforms (show smooth transition)
|
|
- Start opname in modal
|
|
- Dicteer aanvulling: "Patiënt reageert goed op cognitieve gedragstherapie"
|
|
- Show append aan einde
|
|
- Stop + Save
|
|
- Close modal
|
|
|
|
**4. UX Features (3 min)**
|
|
- Show confidence indicators (gele onderstreping)
|
|
- Demo auto-pause (3 sec stilte)
|
|
- Show network resilience (if possible/recorded)
|
|
- Quick actions benefits (1-click start)
|
|
|
|
**5. Q&A (2 min)**
|
|
- Feedback van behandelaars
|
|
- Vragen over workflow
|
|
- Next steps discussie
|
|
|
|
**Backup Plan:**
|
|
- **Plan B:** Staging environment if production issues
|
|
- **Plan C:** Localhost met pre-recorded demo video
|
|
- **Plan D:** Slide deck met screenshots + video recording
|
|
|
|
**Demo Data:**
|
|
- Pre-seeded test patient: "Demo Patient"
|
|
- 3 existing rapportages in timeline
|
|
- Clean state (no errors)
|
|
|
|
---
|
|
|
|
## 7. Risico's & Mitigatie
|
|
|
|
| Risico | Kans | Impact | Mitigatie | Owner | Status |
|
|
|--------|------|--------|-----------|-------|--------|
|
|
| **Deepgram API downtime tijdens demo** | Laag | Hoog | Pre-recorded demo video backup, test 1 uur voor demo | Developer | ⏳ Monitor |
|
|
| **Dutch medical terms transcription errors** | Middel | Hoog | Extensive testing met terminologie lijst, tune confidence thresholds | Developer | ⏳ Test E6.S2 |
|
|
| **WebSocket connection instabiliteit** | Middel | Hoog | Robust reconnect logic (3x), partial transcript preservation | Developer | ✅ Implemented E2.S5 |
|
|
| **Browser compatibility issues Safari** | Middel | Middel | Test early, document limitations, fallback to REST API if needed | Developer | ⏳ Test E6.S3 |
|
|
| **Timeline state sync complex** | Middel | Middel | Clear state management, use React Context or Zustand | Developer | ⏳ Design E4.S3 |
|
|
| **Modal edit mode state bugs** | Middel | Middel | Thorough testing unsaved changes flow, clear state machine | Developer | ⏳ Test E5.S3 |
|
|
| **Performance issues with long rapportages** | Laag | Middel | Lazy loading, virtualization if >100 items, performance profiling | Developer | ⏳ Monitor E6.S1 |
|
|
| **Token rate limiting tijdens development** | Middel | Laag | Increase rate limit for dev, clear documentation | Developer | ✅ E1.S2 |
|
|
| **Deepgram API costs higher than expected** | Laag | Middel | Monitor usage, set up billing alerts, optimize chunk size | PM | ⏳ Monitor |
|
|
| **Migration breaks existing REST flow** | Laag | Hoog | Parallel implementation, feature flag, rollback plan | Developer | ⏳ E6.S1 |
|
|
|
|
**Critical Risks (Hoog/Hoog):**
|
|
1. **Deepgram API downtime** → Mitigation: Backup video + pre-recorded demo
|
|
2. **Dutch transcription errors** → Mitigation: Extensive testing, confidence indicators
|
|
3. **WebSocket instability** → Mitigation: Robust reconnect (implemented)
|
|
|
|
---
|
|
|
|
## 8. Evaluatie & Lessons Learned
|
|
|
|
**Te documenteren na project:**
|
|
|
|
### Success Metrics
|
|
|
|
**Kwantitatief:**
|
|
- [ ] Latency reduction: van 2-5s → <500ms ✅ Target bereikt?
|
|
- [ ] User satisfaction: Survey na 2 weken gebruik (1-5 schaal)
|
|
- [ ] Error rate: <5% van sessions hebben errors
|
|
- [ ] Transcription accuracy: >90% correct voor Dutch medical terms
|
|
- [ ] Adoption rate: >80% van behandelaars gebruikt speech recorder
|
|
|
|
**Kwalitatief:**
|
|
- [ ] Feedback van behandelaars: What works? What doesn't?
|
|
- [ ] Workflow improvements noted
|
|
- [ ] Pain points identified
|
|
- [ ] Feature requests voor v2
|
|
|
|
### Retrospective Questions
|
|
|
|
**Wat ging goed?**
|
|
- ...
|
|
|
|
**Wat ging niet goed?**
|
|
- ...
|
|
|
|
**Wat hebben we geleerd?**
|
|
- ...
|
|
|
|
**Welke AI-tools waren effectief?**
|
|
- Claude Code voor planning & code generation?
|
|
- ChatGPT voor prompt engineering?
|
|
- GitHub Copilot voor snippets?
|
|
|
|
**Welke Deepgram features werkten best?**
|
|
- Interim results?
|
|
- Endpointing?
|
|
- Smart format?
|
|
- Dutch language accuracy?
|
|
|
|
**Waar liepen we vertraging op?**
|
|
- ...
|
|
|
|
**Wat doen we volgende keer anders?**
|
|
- ...
|
|
|
|
**Herbruikbare componenten:**
|
|
- Speech recorder component → Other projects?
|
|
- WebSocket hook pattern → Reusable?
|
|
- Modal pattern → Design system?
|
|
|
|
---
|
|
|
|
## 9. Referenties
|
|
|
|
### Mission Control Documents
|
|
|
|
- **Analyse:** `docs/specs/speech/analyse-deepgram.md`
|
|
- **FO v2:** `docs/specs/speech/fo-realtime-speech-deepgram.md`
|
|
- **Bouwplan:** `docs/specs/speech/bouwplan-realtime-speech.md` (dit document)
|
|
|
|
### External Resources
|
|
|
|
- **Repository:** `https://github.com/[org]/15-mini-epd-prototype`
|
|
- **Deployment:** Vercel (production + preview)
|
|
- **Deepgram Docs:** https://developers.deepgram.com/docs
|
|
- **Deepgram SDK:** https://github.com/deepgram/deepgram-js-sdk
|
|
|
|
### Bestaande Implementatie
|
|
|
|
- **Speech Recorder (old):** `components/speech-recorder.tsx`
|
|
- **API Route (old):** `app/api/deepgram/transcribe/route.ts`
|
|
- **Report Composer:** `app/epd/patients/[id]/rapportage/components/report-composer.tsx`
|
|
|
|
### Deepgram API Documentation
|
|
|
|
- **Live Streaming:** 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
|
|
- **Dutch Language:** https://developers.deepgram.com/docs/language
|
|
|
|
---
|
|
|
|
## 10. Glossary & Abbreviations
|
|
|
|
| Term | Betekenis |
|
|
|------|-----------|
|
|
| **Epic** | Grote feature of fase (bevat meerdere stories) |
|
|
| **Story** | Kleine uitvoerbare taak binnen een epic |
|
|
| **Story Points** | Complexiteit schatting (Fibonacci: 1, 2, 3, 5, 8, 13) |
|
|
| **Interim Results** | Voorlopige transcriptie tijdens spreken (nog niet final) |
|
|
| **Final Results** | Definitieve transcriptie na zin/phrase eindigt |
|
|
| **Endpointing** | Detectie van spraakpauzes (speech_final event) |
|
|
| **Smart Format** | Auto punctuatie, capitalisatie, formatting |
|
|
| **WebSocket** | Bidirectionele real-time communicatie protocol |
|
|
| **Waveform** | Visuele representatie van audio volume |
|
|
| **Confidence Score** | AI zekerheid (0-1) over getranscribeerd woord |
|
|
| **Token Proxy** | Server endpoint die tijdelijke tokens uitgeeft |
|
|
| **Rate Limiting** | Beperking van API calls per tijd per user |
|
|
| **Progressive Disclosure** | Features on-demand tonen (niet alles tegelijk) |
|
|
| **Editor-First** | Design waar editor primair is, andere features opt-in |
|
|
|
|
---
|
|
|
|
## Versiehistorie
|
|
|
|
| Versie | Datum | Auteur | Wijziging |
|
|
|--------|-------|--------|-----------|
|
|
| v1.0 | 24-11-2025 | Claude Code + Colin | Initiële versie - volledige planning 7 epics, 30 stories |
|
|
| v1.1 | 24-11-2025 | Claude Code + Colin | Epic 2 (Core Streaming) voltooid: hook, component, integraties. Epic 3 deels (waveform, pause/resume) |
|
|
| v1.2 | 24-11-2025 | Claude Code + Colin | Epic 3 (UX Polish) voltooid: confidence indicators, auto-pause na stilte |
|
|
| v1.3 | 24-11-2025 | Claude Code + Colin | Epic 4 (Layout Redesign) voltooid: editor-first, quick actions, timeline sidebar |
|
|
| v1.4 | 24-11-2025 | Claude Code + Colin | Epic 5 (View/Edit Modal) voltooid: unified modal, edit mode, unsaved changes protection |
|
|
| v1.5 | 24-11-2025 | Claude Code + Colin | Epic 6 (Integration & Testing) voltooid: duplicate flow, Safari mimeType fallback, browser support check, test checklist |
|
|
|
|
---
|
|
|
|
## Appendix A: Story Points Calibration
|
|
|
|
**Story Point Reference:**
|
|
|
|
- **1 point:** Triviale taak, <2 uur, geen risico (bijv. env var toevoegen)
|
|
- **2 points:** Simpele taak, 2-4 uur, laag risico (bijv. button component)
|
|
- **3 points:** Gemiddelde taak, 4-8 uur, middelmatig risico (bijv. API endpoint)
|
|
- **5 points:** Complexe taak, 1-2 dagen, risico's (bijv. custom hook met state)
|
|
- **8 points:** Zeer complex, 2-3 dagen, hoog risico (bijv. WebSocket lifecycle)
|
|
- **13 points:** Epic-size, 3-5 dagen, zeer hoog risico (bijv. complete refactor)
|
|
|
|
**Team Velocity:** ~40-50 story points per 2-week sprint (1 developer)
|
|
|
|
**Total Epic Breakdown:**
|
|
- E0: 8 points (voorbereiding) - ✅ Compleet
|
|
- E1: 8 points (backend)
|
|
- E2: 21 points (core streaming) - Grootste epic
|
|
- E3: 13 points (UX polish)
|
|
- E4: 21 points (layout redesign) - Grootste epic
|
|
- E5: 13 points (modal)
|
|
- E6: 13 points (testing)
|
|
- E7: 8 points (deployment)
|
|
|
|
**Total: 105 story points → ~2-3 sprints (4-6 weken bij 1 developer)**
|
|
|
|
---
|
|
|
|
## Appendix B: Git Branching Strategy
|
|
|
|
**Main branches:**
|
|
- `main` - Production (deployed op Vercel)
|
|
- `develop` - Development (deployed op Vercel preview)
|
|
|
|
**Feature branches:**
|
|
```
|
|
feature/E1-token-proxy
|
|
feature/E2-streaming-core
|
|
feature/E3-ux-polish
|
|
feature/E4-layout-redesign
|
|
feature/E5-view-edit-modal
|
|
feature/E6-integration
|
|
feature/E7-deployment
|
|
```
|
|
|
|
**Workflow:**
|
|
1. Create feature branch from `develop`
|
|
2. Work on epic/stories
|
|
3. PR naar `develop` (review + CI)
|
|
4. Merge to `develop` (auto-deploy preview)
|
|
5. Test in preview
|
|
6. PR `develop` → `main` (production)
|
|
|
|
**Commit Convention:**
|
|
```
|
|
feat(E2.S1): implement use-deepgram-streaming hook
|
|
fix(E3.S2): confidence indicator tooltip positioning
|
|
docs(E7.S2): add user documentation for speech recorder
|
|
test(E6.S2): add Dutch medical terms test suite
|
|
```
|
|
|
|
---
|
|
|
|
**🎯 Ready to Build!**
|
|
|
|
Dit bouwplan bevat alle details voor succesvolle implementatie van real-time speech transcription met editor-first UX redesign.
|
|
|
|
**Next steps:**
|
|
1. Review & approve bouwplan
|
|
2. Sprint planning (prioriteer epics)
|
|
3. Start met E1 (token proxy) → E2 (core streaming)
|
|
4. Iteratieve development met weekly demos
|
|
5. Continuous testing vanaf E2
|
|
6. Deploy naar production na E6 (testing compleet)
|
|
|
|
**Succes met de bouw! 🚀**
|