feat(overdracht): E0 + E1 - Database setup en API nursing logs

Epic 0 - Database Setup:
- nursing_logs tabel met indexes en constraints
- RLS policies (SELECT/INSERT/UPDATE/DELETE)
- TypeScript types gegenereerd

Epic 1 - API Nursing Logs:
- GET/POST /api/nursing-logs (lijst + aanmaken)
- PATCH/DELETE /api/nursing-logs/[id] (bewerken + verwijderen)
- Zod validatie schemas
- Automatische shift_date berekening

Documentatie:
- PRD, FO, TO en Bouwplan voor Overdracht Dashboard

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
colinislit
2025-12-06 00:03:18 +01:00
parent ac98070fe4
commit 08cffd0245
9 changed files with 2638 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { createClient } from '@/lib/auth/server';
import {
UpdateNursingLogSchema,
calculateShiftDate,
} from '@/lib/types/nursing-log';
interface RouteParams {
params: Promise<{ id: string }>;
}
export async function PATCH(request: NextRequest, { params }: RouteParams) {
try {
const { id } = await params;
if (!z.string().uuid().safeParse(id).success) {
return NextResponse.json(
{ error: 'id moet een geldige UUID zijn' },
{ status: 400 }
);
}
const body = await request.json();
const result = UpdateNursingLogSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: 'Validatiefout',
details: result.error.issues.map((issue) => ({
field: issue.path.join('.'),
message: issue.message,
})),
},
{ status: 400 }
);
}
const supabase = await createClient();
const { data: authData } = await supabase.auth.getUser();
if (!authData?.user) {
return NextResponse.json({ error: 'Niet geautoriseerd' }, { status: 401 });
}
// Check if log exists and belongs to current user
const { data: existingLog, error: fetchError } = await supabase
.from('nursing_logs')
.select('id, created_by')
.eq('id', id)
.single();
if (fetchError || !existingLog) {
return NextResponse.json(
{ error: 'Dagnotitie niet gevonden' },
{ status: 404 }
);
}
if (existingLog.created_by !== authData.user.id) {
return NextResponse.json(
{ error: 'Je kunt alleen je eigen notities bewerken' },
{ status: 403 }
);
}
// Build update object
const updateData: Record<string, unknown> = {};
const { category, content, timestamp, include_in_handover } = result.data;
if (category !== undefined) updateData.category = category;
if (content !== undefined) updateData.content = content;
if (include_in_handover !== undefined)
updateData.include_in_handover = include_in_handover;
// If timestamp changes, recalculate shift_date
if (timestamp !== undefined) {
updateData.timestamp = timestamp;
updateData.shift_date = calculateShiftDate(timestamp);
}
if (Object.keys(updateData).length === 0) {
return NextResponse.json(
{ error: 'Geen velden om te updaten' },
{ status: 400 }
);
}
const { data, error } = await supabase
.from('nursing_logs')
.update(updateData)
.eq('id', id)
.select('*')
.single();
if (error) {
console.error('Error updating nursing log:', error);
return NextResponse.json(
{ error: 'Bijwerken mislukt', details: error.message },
{ status: 500 }
);
}
return NextResponse.json(data);
} catch (error) {
console.error('Unexpected error in PATCH /api/nursing-logs/[id]:', error);
if (error instanceof SyntaxError) {
return NextResponse.json(
{ error: 'Ongeldige JSON in request body' },
{ status: 400 }
);
}
return NextResponse.json(
{ error: 'Onverwachte serverfout' },
{ status: 500 }
);
}
}
export async function DELETE(request: NextRequest, { params }: RouteParams) {
try {
const { id } = await params;
if (!z.string().uuid().safeParse(id).success) {
return NextResponse.json(
{ error: 'id moet een geldige UUID zijn' },
{ status: 400 }
);
}
const supabase = await createClient();
const { data: authData } = await supabase.auth.getUser();
if (!authData?.user) {
return NextResponse.json({ error: 'Niet geautoriseerd' }, { status: 401 });
}
// Check if log exists and belongs to current user
const { data: existingLog, error: fetchError } = await supabase
.from('nursing_logs')
.select('id, created_by')
.eq('id', id)
.single();
if (fetchError || !existingLog) {
return NextResponse.json(
{ error: 'Dagnotitie niet gevonden' },
{ status: 404 }
);
}
if (existingLog.created_by !== authData.user.id) {
return NextResponse.json(
{ error: 'Je kunt alleen je eigen notities verwijderen' },
{ status: 403 }
);
}
// Hard delete (RLS policy already ensures user can only delete own logs)
const { error } = await supabase
.from('nursing_logs')
.delete()
.eq('id', id);
if (error) {
console.error('Error deleting nursing log:', error);
return NextResponse.json(
{ error: 'Verwijderen mislukt', details: error.message },
{ status: 500 }
);
}
return new NextResponse(null, { status: 204 });
} catch (error) {
console.error('Unexpected error in DELETE /api/nursing-logs/[id]:', error);
return NextResponse.json(
{ error: 'Onverwachte serverfout' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,146 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { createClient } from '@/lib/auth/server';
import {
CreateNursingLogSchema,
calculateShiftDate,
type NursingLogListResponse,
} from '@/lib/types/nursing-log';
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const patientId = searchParams.get('patientId');
const date = searchParams.get('date'); // Optional: YYYY-MM-DD format
if (!patientId) {
return NextResponse.json(
{ error: 'patientId query parameter is verplicht' },
{ status: 400 }
);
}
if (!z.string().uuid().safeParse(patientId).success) {
return NextResponse.json(
{ error: 'patientId moet een geldige UUID zijn' },
{ status: 400 }
);
}
// Validate date format if provided
if (date && !/^\d{4}-\d{2}-\d{2}$/.test(date)) {
return NextResponse.json(
{ error: 'date moet in YYYY-MM-DD formaat zijn' },
{ status: 400 }
);
}
const supabase = await createClient();
let query = supabase
.from('nursing_logs')
.select('*')
.eq('patient_id', patientId)
.order('timestamp', { ascending: false });
// Filter by shift_date if date is provided
if (date) {
query = query.eq('shift_date', date);
}
const { data, error } = await query;
if (error) {
console.error('Error fetching nursing logs:', error);
return NextResponse.json(
{ error: 'Fout bij ophalen dagnotities', details: error.message },
{ status: 500 }
);
}
const response: NursingLogListResponse = {
logs: data ?? [],
total: data?.length ?? 0,
};
return NextResponse.json(response);
} catch (error) {
console.error('Unexpected error in GET /api/nursing-logs:', error);
return NextResponse.json(
{ error: 'Onverwachte serverfout' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const result = CreateNursingLogSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: 'Validatiefout',
details: result.error.issues.map((issue) => ({
field: issue.path.join('.'),
message: issue.message,
})),
},
{ status: 400 }
);
}
const supabase = await createClient();
const { data: authData } = await supabase.auth.getUser();
if (!authData?.user) {
return NextResponse.json({ error: 'Niet geautoriseerd' }, { status: 401 });
}
const { patient_id, category, content, timestamp, include_in_handover } =
result.data;
// Use provided timestamp or current time
const logTimestamp = timestamp || new Date().toISOString();
// Calculate shift_date from timestamp
const shiftDate = calculateShiftDate(logTimestamp);
const { data, error } = await supabase
.from('nursing_logs')
.insert({
patient_id,
category,
content,
timestamp: logTimestamp,
shift_date: shiftDate,
include_in_handover: include_in_handover ?? false,
created_by: authData.user.id,
})
.select('*')
.single();
if (error) {
console.error('Error creating nursing log:', error);
return NextResponse.json(
{ error: 'Opslaan mislukt', details: error.message },
{ status: 500 }
);
}
return NextResponse.json(data, { status: 201 });
} catch (error) {
console.error('Unexpected error in POST /api/nursing-logs:', error);
if (error instanceof SyntaxError) {
return NextResponse.json(
{ error: 'Ongeldige JSON in request body' },
{ status: 400 }
);
}
return NextResponse.json(
{ error: 'Onverwachte serverfout' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,314 @@
# Mission Control - Bouwplan Overdracht Dashboard
**Projectnaam:** Verpleegkundige Overdracht Dashboard
**Versie:** v1.0
**Datum:** 05-12-2024
**Auteur:** Colin Lit
---
## 1. Doel en context
**Doel:** Een werkend MVP bouwen van het Overdracht Dashboard met Dagregistratie Module. Verpleegkundigen kunnen snel patiëntinformatie overzien en met AI-hulp een beknopte overdracht genereren in 30 seconden.
**Context:**
- Verpleegkundigen doen gemiddeld 6 overdrachten per dag
- Huidige workflow is tijdrovend en foutgevoelig
- Dit dashboard bundelt vitals, rapportages, dagnotities en risico's
- AI genereert gestructureerde samenvattingen met bronverwijzingen
**Relatie met andere documenten:**
- PRD: `prd-overdracht-dashboard-v1.md` - scope en requirements
- FO: `fo-overdracht-dashboard-v1.1.md` - functionele specificatie
- TO: `to-overdracht-dashboard-v1.md` - technische architectuur
---
## 2. Uitgangspunten
### 2.1 Technische Stack
| Component | Technologie | Status |
|-----------|-------------|--------|
| Frontend | Next.js 15 (App Router) | Bestaand |
| Backend | Next.js API Routes | Bestaand |
| Database | Supabase (PostgreSQL) | Bestaand |
| AI | Claude claude-sonnet-4-20250514 (Anthropic) | Bestaand |
| Styling | TailwindCSS + shadcn/ui | Bestaand |
| Validation | Zod | Bestaand |
| Auth | Supabase Auth + RLS | Bestaand |
### 2.2 Projectkaders
- **Nieuwe dependencies:** Geen (alles aanwezig)
- **Database:** 1 nieuwe tabel (`nursing_logs`)
- **Routes:** 2 nieuwe secties (`/epd/overdracht/`, `/epd/dagregistratie/`)
- **API endpoints:** 5 nieuwe endpoints
- **Data:** Demo data beschikbaar (patients, encounters, reports)
### 2.3 Programmeer Uitgangspunten
**Code Quality Principles:**
- **DRY:** Hergebruik bestaande componenten (Card, Badge, AIButton)
- **KISS:** Eenvoudige Server Components waar mogelijk
- **SOC:** API logic in `/api/`, UI in `/app/epd/`, types in `/lib/types/`
- **YAGNI:** Alleen MVP features, geen "nice to have"
**Bestaande Patterns:**
- AI integratie: `app/api/behandelplan/generate/route.ts`
- CRUD API: `app/api/reports/route.ts`
- Form components: `app/epd/patients/[id]/screening/`
- Card layouts: `components/ui/card.tsx`
---
## 3. Epics & Stories Overzicht
| Epic ID | Titel | Doel | Status | Stories | Complexiteit |
|---------|-------|------|--------|---------|--------------|
| E0 | Database Setup | nursing_logs tabel + RLS | ⏳ To Do | 2 | Laag |
| E1 | API Nursing Logs | CRUD endpoints voor dagnotities | ⏳ To Do | 2 | Laag |
| E2 | API Overdracht | Endpoints voor overdracht data + AI | ⏳ To Do | 3 | Middel |
| E3 | Dagregistratie UI | Quick entry module | ⏳ To Do | 3 | Middel |
| E4 | Overdracht Overzicht | Patiënten grid | ⏳ To Do | 2 | Middel |
| E5 | Overdracht Detail | Info blokken + AI samenvatting | ⏳ To Do | 4 | Middel |
| E6 | Integratie & Polish | Sidebar, navigatie, testing | ⏳ To Do | 3 | Laag |
**Totaal:** 19 stories
---
## 4. Epics & Stories (Uitwerking)
### Epic 0 - Database Setup
**Epic Doel:** nursing_logs tabel aanmaken met RLS policies en indexes.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|----------|--------------|---------------------|--------|------|----|
| E0.S1 | nursing_logs tabel aanmaken | Tabel bestaat met alle kolommen uit TO, indexes aanwezig | ⏳ | - | 2 |
| E0.S2 | RLS policies implementeren | SELECT/INSERT/UPDATE/DELETE policies actief, alleen eigen logs muteerbaar | ⏳ | E0.S1 | 2 |
**Technical Notes:**
- Migratie via `npx supabase migration new create_nursing_logs`
- Kolommen: id, patient_id, shift_date, timestamp, category, content, include_in_handover, created_by
- Categories: medicatie, adl, gedrag, incident, observatie
---
### Epic 1 - API Nursing Logs
**Epic Doel:** CRUD endpoints voor dagnotities.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|----------|--------------|---------------------|--------|------|----|
| E1.S1 | GET/POST /api/nursing-logs | Ophalen per patient+date, aanmaken met Zod validatie | ⏳ | E0.S2 | 3 |
| E1.S2 | PATCH/DELETE /api/nursing-logs/[id] | Update eigen logs, soft delete | ⏳ | E1.S1 | 2 |
**Technical Notes:**
- Pattern volgen van `app/api/reports/route.ts`
- Zod schema: CreateNursingLogSchema, UpdateNursingLogSchema
- shift_date automatisch bepalen op basis van timestamp
---
### Epic 2 - API Overdracht
**Epic Doel:** Endpoints voor overdracht overzicht, detail en AI generatie.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|----------|--------------|---------------------|--------|------|----|
| E2.S1 | GET /api/overdracht/patients | Retourneert patiënten met encounters vandaag + alert counts | ⏳ | E0.S2 | 3 |
| E2.S2 | GET /api/overdracht/[patientId] | Retourneert patient + vitals + reports + logs + risks + conditions | ⏳ | E1.S1 | 5 |
| E2.S3 | POST /api/overdracht/generate | AI samenvatting met bronverwijzingen, logging naar ai_events | ⏳ | E2.S2 | 5 |
**Technical Notes:**
- E2.S2: Parallel queries via Promise.all()
- E2.S3: Pattern van `behandelplan/generate`, nieuwe prompt in `lib/ai/overdracht-prompt.ts`
- AI output: { samenvatting, aandachtspunten[], actiepunten[] }
---
### Epic 3 - Dagregistratie UI
**Epic Doel:** Quick entry module voor verpleegkundige notities.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|----------|--------------|---------------------|--------|------|----|
| E3.S1 | Dagregistratie page | Route `/epd/dagregistratie/[patientId]`, lijst van notities vandaag | ⏳ | E1.S2 | 3 |
| E3.S2 | Quick entry form | Categorie dropdown, tijd, tekst (max 500), overdracht checkbox | ⏳ | E3.S1 | 5 |
| E3.S3 | Edit/Delete functionality | Inline edit, confirm delete dialog | ⏳ | E3.S2 | 3 |
**Technical Notes:**
- Icons per categorie: Pill (medicatie), Utensils (adl), User (gedrag), AlertTriangle (incident), FileText (observatie)
- Kleuren: red (incident), blue (medicatie), green (adl), purple (gedrag), gray (observatie)
- Optimistic UI updates
---
### Epic 4 - Overdracht Overzicht
**Epic Doel:** Grid van patiënten met alerts voor overdracht.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|----------|--------------|---------------------|--------|------|----|
| E4.S1 | Overdracht overzicht page | Route `/epd/overdracht/`, grid van PatientCards, filter tabs | ⏳ | E2.S1 | 5 |
| E4.S2 | PatientCard component | Naam, leeftijd, alert badge (rood=hoog risico), doorklik | ⏳ | E4.S1 | 3 |
**Technical Notes:**
- Filter tabs: "Alle patiënten", "Met alerts"
- Alert count = high_risk_count + abnormal_vitals_count + marked_logs_count
- Responsive grid: 1 col mobile, 2 col tablet, 3-4 col desktop
---
### Epic 5 - Overdracht Detail
**Epic Doel:** Patiënt detail pagina met informatieblokken en AI samenvatting.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|----------|--------------|---------------------|--------|------|----|
| E5.S1 | Overdracht detail page | Route `/epd/overdracht/[patientId]`, patient header, 2-kolom layout | ⏳ | E2.S2 | 3 |
| E5.S2 | Info blokken: Vitals + Reports | VitalsBlock (metingen vandaag), ReportsBlock (24u) | ⏳ | E5.S1 | 5 |
| E5.S3 | Info blokken: Logs + Risks | NursingLogsBlock (gemarkeerd), RisksBlock (actief) | ⏳ | E5.S2 | 5 |
| E5.S4 | AI Samenvatting blok | AIButton "Genereer samenvatting", loading state, output met bronnen | ⏳ | E2.S3, E5.S3 | 5 |
**Technical Notes:**
- Linker kolom: Vitals, Reports, Logs, Risks (scrollable)
- Rechter kolom: AI Samenvatting (sticky)
- Bronverwijzingen klikbaar naar originele record
- Empty states per blok
---
### Epic 6 - Integratie & Polish
**Epic Doel:** Sidebar link, navigatie en testing.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP |
|----------|--------------|---------------------|--------|------|----|
| E6.S1 | Sidebar uitbreiden | "Overdracht" link in EPD sidebar met alert badge | ⏳ | E4.S1 | 1 |
| E6.S2 | Navigatie links | Link van dagregistratie naar overdracht en vice versa | ⏳ | E5.S3 | 2 |
| E6.S3 | Smoke testing | Alle flows werken, geen console errors, performance OK | ⏳ | E6.S2 | 3 |
**Technical Notes:**
- Sidebar icon: ClipboardList (lucide)
- Badge toont totaal aantal alerts
- Test met bestaande demo patients
---
## 5. Kwaliteit & Testplan
### Test Types
| Test Type | Scope | Verantwoordelijke |
|-----------|-------|-------------------|
| Manual Testing | Alle flows | Developer |
| TypeScript | Type checking | Build process |
| RLS Testing | Database policies | Developer |
### Manual Test Checklist
**Dagregistratie:**
- [ ] Nieuwe notitie aanmaken werkt
- [ ] Categorie selectie werkt
- [ ] "Opnemen in overdracht" checkbox werkt
- [ ] Edit notitie werkt
- [ ] Delete notitie werkt (met confirm)
- [ ] Lijst refresht na mutatie
**Overdracht Overzicht:**
- [ ] Grid toont patiënten met encounters vandaag
- [ ] Alert badges tonen correct aantal
- [ ] Filter "Met alerts" werkt
- [ ] Doorklik naar detail werkt
**Overdracht Detail:**
- [ ] Patient header toont naam, leeftijd, diagnose
- [ ] Vitals blok toont metingen vandaag (of empty state)
- [ ] Reports blok toont laatste 24u (of empty state)
- [ ] Nursing logs blok toont gemarkeerde notities
- [ ] Risks blok toont actieve risico's
- [ ] AI samenvatting genereert binnen 5 sec
- [ ] Bronverwijzingen in AI output zijn correct
**Performance:**
- [ ] Overzicht laadt < 2 sec
- [ ] Detail laadt < 1.5 sec
- [ ] AI response < 5 sec
---
## 6. Succescriteria (uit PRD)
- [ ] Overzicht laadt binnen 2 seconden
- [ ] Patiënt detail toont alle 6 informatieblokken correct (incl. dagnotities)
- [ ] Dagregistratie form submit < 1 seconde
- [ ] AI samenvatting genereert binnen 5 seconden
- [ ] AI output is begrijpelijk en medisch relevant
- [ ] AI integreert dagnotities correct in samenvatting
- [ ] Navigatie tussen overzicht, detail en dagregistratie werkt vlot
- [ ] Alerts (hoog risico, afwijkende vitals, gemarkeerde notities) zijn direct zichtbaar
- [ ] Empty states bij ontbrekende data zijn informatief
---
## 7. Risico's & Mitigatie
| Risico | Kans | Impact | Mitigatie |
|--------|------|--------|-----------|
| AI samenvatting te lang/vaag | Middel | Hoog | Strikte prompt, max tokens, testen |
| Geen vitale functies in systeem | Hoog | Laag | Graceful empty state |
| Performance bij veel data | Laag | Middel | Indexes, parallel queries |
| AI hallucinaties | Laag | Hoog | Bronverwijzingen verplicht |
| VPK vergeet notities markeren | Middel | Middel | UI hint, standaard checkbox |
---
## 8. Niet in Scope (MVP)
| Feature | Reden |
|---------|-------|
| Medicatie-invoer | Alleen weergave, aparte module |
| Historische trends | Geen grafieken |
| PDF export | Komt later |
| Notificaties | Geen realtime alerts |
| Multi-afdeling | Te complex |
| Rechten per rol | Beperkt onderscheid VPK/arts |
---
## 9. Referenties
### Mission Control Documents
| Document | Status |
|----------|--------|
| PRD Overdracht Dashboard v1.0 | Gereed |
| FO Overdracht Dashboard v1.1 | Gereed |
| TO Overdracht Dashboard v1.0 | Gereed |
| Bouwplan v1.0 | Gereed (dit document) |
### Code Locaties
| Wat | Locatie |
|-----|---------|
| AI prompt pattern | `lib/ai/behandelplan-prompt.ts` |
| API route pattern | `app/api/reports/route.ts` |
| shadcn components | `components/ui/` |
| EPD layout | `app/epd/layout.tsx` |
| Sidebar | `app/epd/components/epd-sidebar.tsx` |
---
## 10. Glossary
| Term | Betekenis |
|------|-----------|
| Overdracht | Mondelinge/schriftelijke informatieoverdracht tussen diensten |
| VPK | Verpleegkundige |
| Dagnotitie | Korte registratie tijdens dienst (nursing_log) |
| Alert | Signaal voor aandacht (hoog risico, afwijking, incident) |
| Handover | Engelse term voor overdracht |
---
## Changelog
| Versie | Datum | Auteur | Wijziging |
|--------|-------|--------|-----------|
| v1.0 | 05-12-2024 | Colin | Initieel bouwplan gebaseerd op PRD, FO en TO |

View File

@@ -0,0 +1,642 @@
# 🧩 Functioneel Ontwerp (FO) — Verpleegkundige Overdracht Dashboard
**Projectnaam:** Verpleegkundige Overdracht Dashboard
**Versie:** v1.1 (met Dagregistratie Module)
**Datum:** 05-12-2024
**Auteur:** Colin Lit
---
## 1. Doel en relatie met het PRD
🎯 **Doel van dit document:**
Dit Functioneel Ontwerp beschrijft **hoe** het Overdracht Dashboard uit het PRD functioneel zal werken — wat de verpleegkundige ziet, doet en ervaart. Waar het PRD uitlegt *wat en waarom*, laat dit FO zien *hoe dit in de praktijk werkt*.
📘 **Relatie met PRD:**
- PRD-referentie: `prd-overdracht-dashboard-v1.md`
- Dit FO is de functionele uitwerking van PRD secties 3 (Kernfunctionaliteiten) en 4 (Gebruikersflows)
**Kernprincipe:**
> Elke getoonde informatie moet **traceerbaar** zijn naar de bron. De gebruiker moet kunnen zien waar data vandaan komt en kunnen doorklikken naar het originele record.
**Nieuw in v1.1:**
> **Dagregistratie Module** - Verpleegkundigen kunnen tijdens hun dienst snelle notities maken (medicatie, ADL, gedrag, incidenten) en markeren welke relevant zijn voor overdracht. Dit scheidt operationele dagregistratie van behandelrapportages die in het decursus komen.
---
## 2. Overzicht van de belangrijkste onderdelen
🎯 **Doel:** Kort overzicht van schermen en componenten binnen het Overdracht Dashboard.
| # | Onderdeel | Beschrijving | Route |
|---|-----------|--------------|-------|
| 1 | **Overdracht Overzicht** | Grid van alle patiënten voor vandaag | `/epd/overdracht/` |
| 2 | **Patiënt Detail** | Informatieblokken + AI samenvatting | `/epd/overdracht/[patientId]` |
| 3 | **Dagregistratie Module** | Snelle verpleegkundige notities met overdracht-markering | `/epd/dagregistratie/[patientId]` |
| 4 | **Vitale Functies Blok** | Metingen vandaag met trend indicators | Detail pagina |
| 5 | **Rapportages Blok** | Behandelrapportages (24u) met bronlinks | Detail pagina |
| 6 | **Dagnotities Blok** | Verpleegkundige registraties vandaag | Detail pagina |
| 7 | **Medicatie Blok** | Huidige medicatie *(placeholder)* | Detail pagina |
| 8 | **Risico's Blok** | Actieve risicotaxaties | Detail pagina |
| 9 | **AI Samenvatting Blok** | Gegenereerde overdracht met bronverwijzingen | Detail pagina |
---
## 3. User Stories
🎯 **Doel:** Beschrijven wat gebruikers moeten kunnen doen, vanuit hun perspectief.
### MVP User Stories (origineel)
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|----|-----|--------------|------------------|------|
| **OD-US01** | Verpleegkundige | Overzicht zien van alle patiënten voor vandaag | Weet wie ik moet overdragen | 🔴 Hoog |
| **OD-US02** | Verpleegkundige | Filteren op patiënten met alerts | Focus op urgente cases | 🔴 Hoog |
| **OD-US03** | Verpleegkundige | Doorklikken naar patiënt detail | Zie alle relevante info | 🔴 Hoog |
| **OD-US04** | Verpleegkundige | Vitale functies zien met afwijkingen gemarkeerd | Direct zien wat er aan de hand is | 🔴 Hoog |
| **OD-US05** | Verpleegkundige | Recente rapportages lezen | Context voor overdracht | 🔴 Hoog |
| **OD-US06** | Verpleegkundige | Doorklikken naar originele rapportage | Bronverificatie | 🔴 Hoog |
| **OD-US07** | Verpleegkundige | Risico's zien met ernst-niveau | Weet wat aandacht nodig heeft | 🟡 Middel |
| **OD-US08** | Verpleegkundige | AI-samenvatting genereren | Snelle overdracht in 30 sec | 🔴 Hoog |
| **OD-US09** | Verpleegkundige | In AI-samenvatting bronnen zien | Weet waar info vandaan komt | 🔴 Hoog |
| **OD-US10** | Verpleegkundige | Vanuit AI-samenvatting doorklikken naar bron | Kan details checken | 🟡 Middel |
### Nieuwe User Stories (Dagregistratie)
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|----|-----|--------------|------------------|------|
| **OD-US11** | Verpleegkundige | Tijdens dienst snelle notitie toevoegen | Registreer gebeurtenis direct | 🔴 Hoog |
| **OD-US12** | Verpleegkundige | Notitie categoriseren (medicatie/ADL/gedrag/incident) | Heldere structuur | 🔴 Hoog |
| **OD-US13** | Verpleegkundige | Markeren welke notities relevant zijn voor overdracht | Controle over wat gedeeld wordt | 🔴 Hoog |
| **OD-US14** | Verpleegkundige | Overzicht van dagnotities zien | Snel terugkijken wat er gebeurd is | 🔴 Hoog |
| **OD-US15** | Verpleegkundige | Dagnotitie bewerken/verwijderen | Correctie mogelijk | 🟡 Middel |
| **OD-US16** | Psychiater | Alleen relevante notities in overdracht zien | Geen informatie-overload | 🔴 Hoog |
| **OD-US17** | Psychiater | AI-samenvatting inclusief dagnotities | Compleet beeld van dienst | 🔴 Hoog |
---
## 4. Functionele werking per onderdeel
🎯 **Doel:** Per hoofdonderdeel beschrijven wat de gebruiker kan doen en wat het systeem doet.
### 4.1 Dagregistratie Module (NIEUW)
**Route:** `/epd/dagregistratie/[patientId]`
**Doel:** Snelle registratie tijdens dienst van operationele gebeurtenissen die relevant kunnen zijn voor overdracht, maar niet in het behandelverloop (decursus) horen.
**Functionaliteit:**
| Actie | Beschrijving | Systeem reactie |
|-------|--------------|-----------------|
| **Nieuwe notitie** | Klik "+ Registratie" | Toon quick-entry form |
| **Categorie selecteren** | Dropdown: Medicatie, ADL, Gedrag, Incident, Observatie | Icoon + kleurcodering |
| **Tijd instellen** | Standaard: nu, Aanpasbaar | Timestamp registratie |
| **Tekst invoeren** | Kort tekstveld (max 500 chars) | Autosave draft |
| **Overdracht markeren** | Checkbox "Opnemen in overdracht" | Badge in lijst |
| **Opslaan** | Submit form | Insert nursing_log, refresh lijst |
| **Bewerken** | Klik op notitie | Inline edit mode |
| **Verwijderen** | Trash icon | Confirm dialog → delete |
**UI Componenten:**
```
┌─────────────────────────────────────────┐
│ Dagregistratie - Jan de Vries │
│ Donderdag 5 december 2024 │
├─────────────────────────────────────────┤
│ [+ Nieuwe registratie] Filter: [Alle]│
├─────────────────────────────────────────┤
│ │
│ 14:30 🔴 Incident [Overdracht] │
│ Verbale escalatie bij groepsactiviteit │
│ Collega heeft de-escalatie gedaan │
│ [Bewerken] [Verwijderen] │
│ │
│ 12:00 💊 Medicatie [Overdracht] │
│ Lithium geweigerd - "Voel me goed" │
│ [Bewerken] [Verwijderen] │
│ │
│ 08:00 💊 Medicatie │
│ Olanzapine 10mg toegediend conform │
│ [Bewerken] [Verwijderen] │
│ │
│ 07:30 🍽️ ADL │
│ Ontbijt volledig genuttigd │
│ [Bewerken] [Verwijderen] │
│ │
└─────────────────────────────────────────┘
```
**Quick Entry Form:**
```
┌─────────────────────────────────────┐
│ Nieuwe registratie │
├─────────────────────────────────────┤
│ Categorie: [Incident ▼] │
│ 💊 Medicatie │
│ 🍽️ ADL/verzorging │
│ 👤 Gedragsobservatie │
│ 🔴 Incident │
│ 📝 Algemene observatie │
│ │
│ Tijd: [14:30] [Nu] │
│ │
│ Omschrijving: │
│ ┌─────────────────────────────────┐ │
│ │ Patiënt werd geprikkeld tijdens │ │
│ │ groepsactiviteit, verbale │ │
│ │ escalatie. Collega heeft... │ │
│ └─────────────────────────────────┘ │
│ 234 / 500 karakters │
│ │
│ ☑️ Opnemen in overdracht │
│ │
│ [Annuleren] [Opslaan] │
└─────────────────────────────────────┘
```
**Data Flow:**
```
User input
nursing_logs table
Overdracht Detail (indien marked)
AI Samenvatting (contextueel)
```
---
### 4.2 Overdracht Overzicht (`/epd/overdracht/`)
**Context:** Level 1 - Alle patiënten voor vandaag
[Rest blijft hetzelfde als origineel FO]
**Layout:**
```
┌─────────────────────────────────────────────────────────────────┐
│ EPD Sidebar │ Overdracht │
│ │ │
│ ┌─────────┐ │ ┌──────────────────────────────────────────────┐ │
│ │Dashboard│ │ │ Overdracht 🔴 3 alerts │ │
│ │─────────│ │ │ Donderdag 5 december 2024 · 8 patiënten │ │
│ │Cliënten │ │ ├──────────────────────────────────────────────┤ │
│ │─────────│ │ │ [Alle patiënten (8)] [Met alerts (3)] │ │
│ │Agenda │ │ ├──────────────────────────────────────────────┤ │
│ │─────────│ │ │ │ │
│ │►Overdracht│ │ ┌───────── ┌───────── ┌───────── │ │
│ │─────────│ │ │ │ Jan V. │ │ Marie K.│ │ Piet B. │ │ │
│ │Rapportage│ │ │ │ 🔴 2 │ │ OK │ │ 🟡 1 │ │ │
│ └─────────┘ │ │ │ 67 jaar │ │ 45 jaar │ │ 52 jaar │ │ │
│ │ │ │ [→] │ │ [→] │ │ [→] │ │ │
│ │ │ └───────── └───────── └───────── │ │
└─────────────────────────────────────────────────────────────────┘
```
**Patiënt Card Inhoud:**
| Element | Toelichting |
|---------|-------------|
| Naam | Voornaam + initiaal achternaam |
| Alert badge | 🔴 Aantal hoog-risico items |
| Leeftijd | Berekend uit geboortedatum |
| Klik | → Navigeer naar detail pagina |
**Filter Functionaliteit:**
```typescript
// Pseudo-code
if (filter === 'met-alerts') {
patients = patients.filter(p =>
p.high_risk_count > 0 ||
p.abnormal_vitals_count > 0 ||
p.marked_nursing_logs > 0 // NIEUW
)
}
```
---
### 4.3 Patiënt Detail (`/epd/overdracht/[patientId]`)
**Context:** Level 2 - specifieke patiënt overdracht informatie
**Layout (uitgebreid met Dagnotities blok):**
```
┌─────────────────────────────────────────────────────────────────┐
│ ← Terug naar overzicht │
├─────────────────────────────────────────────────────────────────┤
│ Jan de Vries │
│ ♂ 67 jaar · Depressieve stoornis, recidiverend │
├────────────────────────────────┬────────────────────────────────┤
│ │ │
│ ┌─ Vitale Functies ────────┐ │ ┌─ AI Samenvatting ────────┐ │
│ │ ... │ │ │ │ │
│ └───────────────────────────┘ │ │ [Genereer samenvatting] │ │
│ │ │ │ │
│ ┌─ Rapportages ─────────────┐ │ │ Samenvatting... │ │
│ │ Behandelverslagen (24u) │ │ │ │ │
│ │ [3 rapportages] │ │ │ Aandachtspunten: │ │
│ └───────────────────────────┘ │ │ • Medicatie geweigerd │ │
│ │ │ • Incident vanmiddag │ │
│ ┌─ Dagnotities ─────────────┐ │ │ │ │ ← NIEUW
│ │ VPK registraties vandaag │ │ │ Actiepunten: │ │
│ │ [5 notities, 2 relevant] │ │ │ • Bloeddruk checken │ │
│ │ → [Dagregistratie] │ │ │ │ │
│ └───────────────────────────┘ │ └───────────────────────────┘ │
│ │ │
│ ┌─ Medicatie ───────────────┐ │ │
│ │ [Placeholder] │ │ │
│ └───────────────────────────┘ │ │
│ │ │
│ ┌─ Risico's ────────────────┐ │ │
│ │ ... │ │ │
│ └───────────────────────────┘ │ │
│ │ │
└────────────────────────────────┴────────────────────────────────┘
```
---
### 4.4 Dagnotities Blok (NIEUW)
**Doel:** Toon relevante verpleegkundige registraties in overdracht context
**Inhoud:**
```
┌─ Dagnotities ─────────────────────────────────────┐
│ Verpleegkundige registraties vandaag │
│ │
│ 14:30 🔴 Incident [Overdracht] │
│ Verbale escalatie bij groepsactiviteit │
│ Collega heeft de-escalatie gedaan │
│ Bron: nursing_logs/789 │
│ │
│ 12:00 💊 Medicatie [Overdracht] │
│ Lithium geweigerd - "Voel me goed" │
│ Bron: nursing_logs/788 │
│ │
│ [Toon alle registraties (5)] → /dagregistratie/ │
└────────────────────────────────────────────────────┘
```
**Business Rules:**
| Regel | Implementatie |
|-------|---------------|
| Toon alleen gemarkeerde notities | `WHERE include_in_handover = true` |
| Sorteer chronologisch (nieuw → oud) | `ORDER BY timestamp DESC` |
| Max 5 items inline | Rest via link naar dagregistratie |
| Urgentie kleuren | 🔴 Incident > 💊 Medicatie > 👤 Gedrag > 🍽️ ADL |
---
## 5. Interacties met AI (functionele beschrijving)
🎯 **Doel:** Uitleggen waar AI in de flow voorkomt en wat de gebruiker ziet.
### 5.1 AI Overdracht Generator (uitgebreid)
| Aspect | Beschrijving |
|--------|--------------|
| **Locatie** | AI Samenvatting blok op patiënt detail pagina |
| **Trigger** | Klik op "Genereer samenvatting" button |
| **Input context** | Vitals (vandaag) + Reports (24u) + **Nursing Logs (marked)** + Risks (actief) + Conditions (actief) |
| **Processing** | ~3-5 seconden, progress indicator |
| **Output** | Samenvatting + aandachtspunten (met bronnen) + actiepunten |
### 5.2 AI Prompt Strategie (aangepast)
**System Prompt Kernpunten:**
- Rol: Ervaren verpleegkundige die overdrachten maakt
- Taal: Nederlands, zakelijk, beknopt
- Focus: Veranderingen, zorgen, actiepunten
- **Bronvermelding:** Bij elk aandachtspunt de databron vermelden
- **Nieuwe data:** Verwerk nursing_logs als operationele context
**Context Structuur voor AI (uitgebreid):**
```
PATIENT: [naam], [leeftijd] jaar
DIAGNOSES:
- [diagnose 1] (source: conditions/[id])
- [diagnose 2] (source: conditions/[id])
VITALE FUNCTIES (vandaag):
- Bloeddruk: 145/92 mmHg [VERHOOGD] (source: observations/[id], 14:30)
- Hartslag: 78 bpm [NORMAAL] (source: observations/[id], 14:30)
RAPPORTAGES (laatste 24u):
- [14:15] Voortgangsnotitie: "..." (source: reports/[id])
- [09:30] Contactmoment: "..." (source: reports/[id])
DAGREGISTRATIES (dienst vandaag, relevant voor overdracht): ← NIEUW
- [14:30] [INCIDENT] Verbale escalatie bij groepsactiviteit... (source: nursing_logs/789)
- [12:00] [MEDICATIE] Lithium geweigerd - "Voel me goed" (source: nursing_logs/788)
RISICO'S:
- [HOOG] Suïcidaliteit: "..." (source: risk_assessments/[id])
- [MIDDEL] Zelfverwaarlozing: "..." (source: risk_assessments/[id])
```
**Expected Output (voorbeeld):**
```json
{
"samenvatting": "67-jarige man met recidiverende depressie. Medicatie-therapietrouw problematisch vandaag (Lithium geweigerd), incident vanmiddag met verbale escalatie.",
"aandachtspunten": [
{
"tekst": "Lithium medicatie geweigerd om 12:00 - patiënt geeft aan zich goed te voelen",
"urgent": false,
"bron": {
"type": "dagnotitie",
"id": "nursing-788",
"datum": "05-12-2024 12:00",
"label": "Medicatie registratie"
}
},
{
"tekst": "Incident 14:30 - verbale escalatie tijdens groepsactiviteit, de-escalatie door collega",
"urgent": true,
"bron": {
"type": "dagnotitie",
"id": "nursing-789",
"datum": "05-12-2024 14:30",
"label": "Incident registratie"
}
},
{
"tekst": "Bloeddruk verhoogd (145/92) - monitoring nodig",
"urgent": false,
"bron": {
"type": "observatie",
"id": "obs-456",
"datum": "05-12-2024 14:30",
"label": "Vitale functies"
}
}
],
"actiepunten": [
"Overleg arts over medicatie-weigering en compliance",
"Bloeddruk controleren over 2 uur",
"Evalueer triggers voor incident in behandelplan"
]
}
```
---
## 6. Database Schema (NIEUW)
🎯 **Doel:** Duidelijk maken welke nieuwe data-structuren nodig zijn.
### 6.1 Nieuwe Tabel: nursing_logs
```sql
CREATE TABLE nursing_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
patient_id UUID NOT NULL REFERENCES patients(id) ON DELETE CASCADE,
-- Timing
shift_date DATE NOT NULL, -- Voor filtering per dienst
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Content
category TEXT NOT NULL CHECK (category IN ('medicatie', 'adl', 'gedrag', 'incident', 'observatie')),
content TEXT NOT NULL, -- Max 500 chars in UI
-- Overdracht
include_in_handover BOOLEAN NOT NULL DEFAULT false,
-- Metadata
created_by UUID NOT NULL REFERENCES auth.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_nursing_logs_patient ON nursing_logs(patient_id);
CREATE INDEX idx_nursing_logs_shift ON nursing_logs(shift_date);
CREATE INDEX idx_nursing_logs_handover ON nursing_logs(patient_id, include_in_handover);
```
**Verschil met reports tabel:**
| Aspect | reports | nursing_logs |
|--------|---------|--------------|
| **Doel** | Behandelverloof (decursus) | Operationele dagregistratie |
| **Lengte** | Lang (rich text) | Kort (max 500 chars) |
| **Levensduur** | Jaren | Dagen/weken |
| **In overdracht** | Soms | Frequent |
| **Gebruiker** | Behandelaar + VPK | Voornamelijk VPK |
---
## 7. Gebruikersflows (uitgebreid)
🎯 **Doel:** Laten zien hoe de gebruiker stap-voor-stap door het systeem gaat.
### Flow 1: Dagelijkse Overdracht (aangepast)
```
1. Verpleegkundige opent Overdracht pagina
2. Ziet grid van alle patiënten voor vandaag
3. Filtert eventueel op "Met alerts"
4. Klikt op patiënt voor detail view
5. Bekijkt informatieblokken:
- Vitals
- Behandelrapportages
- Dagnotities (NIEUW)
- Risico's
6. Klikt "Genereer samenvatting"
7. AI maakt beknopte overdracht (incl. dagnotities)
8. Verpleegkundige gebruikt samenvatting voor overdracht aan psychiater
```
### Flow 2: Registratie Tijdens Dienst (NIEUW)
```
1. Verpleegkundige tijdens dienst: gebeurtenis met patiënt
2. Opent Dagregistratie module voor die patiënt
3. Klikt "+ Nieuwe registratie"
4. Vult in:
- Categorie (bijv. Incident)
- Tijd (standaard: nu)
- Omschrijving (kort, 2-3 zinnen)
- ☑️ "Opnemen in overdracht"
5. Slaat op
6. Notitie verschijnt in lijst met [Overdracht] badge
7. Aan eind dienst: notitie automatisch in overdracht-view
```
### Flow 3: Psychiater Bekijkt Overdracht (NIEUW)
```
1. Psychiater opent Overdracht dashboard
2. Ziet lijst met patiënten waarvan VPK dienst had
3. Opent patiënt detail
4. Bekijkt Dagnotities blok:
- Ziet alleen gemarkeerde items
- Leest 2 incidenten + 1 medicatie-weigering
5. Klikt "Genereer samenvatting"
6. AI vat samen: "Vandaag 2 incidenten, medicatie geweigerd, verhoogde bloeddruk"
7. Psychiater bespreekt met VPK of patiënt
```
---
## 8. Gebruikersrollen en rechten
🎯 **Doel:** Beschrijven welke rollen toegang hebben.
### MVP: Uniform toegangsmodel
| Rol | Dagregistratie | Overdracht Dashboard | Beperkingen |
|-----|----------------|---------------------|-------------|
| Verpleegkundige | Volledige CRUD | Volledige functionaliteit | Alleen eigen patiënten |
| Psychiater | Read-only | Volledige functionaliteit | Alleen eigen patiënten |
### Data Scoping (uitgebreid)
- **Patiënten:** Gefilterd op actieve encounters vandaag
- **Reports:** Gefilterd op `created_by` of team-toegang
- **Nursing Logs:** Gefilterd op shift_date (vandaag) en `created_by`
- **Risico's:** Via intake → patient relatie
---
## 9. Niet in Scope (aangepast)
🎯 **Doel:** Duidelijk maken wat (nog) niet wordt gebouwd.
| Feature | Reden exclusie |
|---------|----------------|
| Medicatie-invoer | Alleen weergave, CRUD is aparte module |
| Templating dagnotities | Vrije tekst is sneller voor MVP |
| Historische dagnotities | Alleen vandaag, archivering later |
| Multi-afdeling view | Te complex voor MVP, alleen eigen patiënten |
| Historische trends | Geen grafieken of lange termijn overzichten |
| Notificaties/push | Geen realtime alerts |
| Print/export | Geen PDF of print functionaliteit |
| Rechten per rol | Beperkt onderscheid VPK/arts (komt later) |
| Metingen invoer | Aparte functionaliteit, hier alleen weergave |
| Dicteer-functie | Typen is snel genoeg voor korte notities |
---
## 10. Succescriteria (uitgebreid)
🎯 **Doel:** Objectieve meetlat voor een geslaagde oplevering.
- [ ] Overzicht laadt binnen 2 seconden
- [ ] Patiënt detail toont alle 6 informatieblokken correct (incl. dagnotities)
- [ ] Dagregistratie form submit < 1 seconde
- [ ] AI samenvatting genereert binnen 5 seconden
- [ ] AI output is begrijpelijk en medisch relevant
- [ ] AI integreert dagnotities correct in samenvatting
- [ ] Navigatie tussen overzicht, detail en dagregistratie werkt vlot
- [ ] Alerts (hoog risico, afwijkende vitals, markeerde notities) zijn direct zichtbaar
- [ ] Empty states bij ontbrekende data zijn informatief
- [ ] Dagnotities met [Overdracht] badge zijn duidelijk herkenbaar
---
## 11. Risico's & Mitigatie (uitgebreid)
🎯 **Doel:** Risico's vroeg signaleren en plannen hoe ermee om te gaan.
| Risico | Impact | Mitigatie |
|--------|--------|-----------|
| AI samenvatting te lang/vaag | Hoog | Strikte prompt met max lengte, testen met echte data |
| Geen vitale functies in systeem | Middel | Graceful empty state, instructie om metingen toe te voegen |
| Medicatie tabel bestaat niet | Middel | Placeholder blok met "Binnenkort beschikbaar" |
| Performance bij veel patiënten | Middel | Parallel queries, pagination indien nodig |
| Risico's gekoppeld aan intake ipv patient | Laag | Query via intake tabel |
| VPK vergeet notities markeren | Hoog | AI pre-selectie als backup |
| Dubbele registratie (reports + nursing_logs) | Middel | Duidelijke UI scheiding + training |
| Nursing_logs niet archiveren | Laag | Auto-delete na 30 dagen (fase 2) |
---
## 12. Roadmap / Vervolg (Post-MVP)
🎯 **Doel:** Richting geven aan toekomstige uitbreidingen.
| Fase | Feature | Beschrijving |
|------|---------|--------------|
| 2 | Medicatie module | Volledige CRUD voor medicatie, koppeling met nursing_logs |
| 3 | Templates dagnotities | Snelle keuzes: "Medicatie conform", "Eetpatroon normaal" |
| 4 | Archivering | Auto-delete nursing_logs > 30 dagen, export naar archief |
| 5 | Trend grafieken | Vitale functies over tijd |
| 6 | PDF export | Formele overdracht documenten |
| 7 | Agenda integratie | Koppeling met encounters/afspraken |
| 8 | Notificaties | Alerts bij kritieke waarden |
| 9 | Multi-afdeling | Overzicht meerdere afdelingen |
| 10 | Spraak-naar-tekst | Dicteer dagnotities (Deepgram integratie) |
---
## 13. Bijlagen & Referenties
🎯 **Doel:** Linken naar gerelateerde documenten.
### Interne Documenten
| Document | Status |
|----------|--------|
| PRD Overdracht Dashboard v1.0 | ✅ Gereed |
| FO Overdracht Dashboard v1.1 | ✅ Gereed (dit document) |
| TO Overdracht Dashboard | 📋 Nog te schrijven |
| UX Stylesheet | ✅ Beschikbaar |
### Database Tabellen (uitgebreid)
| Tabel | Velden gebruikt | Nieuw? |
|-------|-----------------|--------|
| `nursing_logs` | id, patient_id, shift_date, timestamp, category, content, include_in_handover, created_by | ✅ Ja |
| `observations` | id, patient_id, code_display, value_quantity_value, interpretation_code, effective_datetime | Bestaand |
| `reports` | id, patient_id, type, content, created_at, created_by | Bestaand |
| `risk_assessments` | id, intake_id, risk_type, risk_level, rationale, measures, assessment_date | Bestaand |
| `conditions` | id, patient_id, code_display, clinical_status | Bestaand |
| `patients` | id, name_given, name_family, birth_date, gender | Bestaand |
| `encounters` | id, patient_id, period_start, type_display | Bestaand |
### Externe Referenties
| Bron | Gebruik |
|------|---------|
| shadcn/ui | Card, Button, Badge, Form components |
| Lucide React | Icons (Pill, Utensils, User, AlertTriangle) |
| Anthropic Claude API | AI samenvatting generatie |
| Supabase RLS | Row-level security voor nursing_logs |
### UI Component Hergebruik
| Component | Locatie | Hergebruik voor |
|-----------|---------|-----------------|
| Quick Entry Form | Rapportage module | Nursing logs form |
| Badge component | Risico's | Overdracht marker |
| Card layout | Behandelplan | Informatie blokken |
| AI Button | Behandelplan | Genereer samenvatting |
---
## Changelog
| Versie | Datum | Auteur | Wijziging |
|--------|-------|--------|-----------|
| v1.1 | 05-12-2024 | Colin | Dagregistratie module toegevoegd, nursing_logs tabel, uitgebreide flows |
| v1.0 | 05-12-2024 | Colin | Initieel FO met focus op traceerbaarheid en bronvermelding |
---
**Einde Functioneel Ontwerp - Overdracht Dashboard v1.1**

View File

@@ -0,0 +1,172 @@
# Verpleegkundige Overdracht Dashboard
## Implementatieplan
### Stap 1: PRD aanmaken
Maak folder en PRD bestand:
- `docs/specs/overdracht/prd-overdracht-dashboard-v1.md`
### Stap 2: Implementatie (na PRD)
Zie technisch plan onderaan.
---
## PRD Content (te schrijven naar docs/specs/overdracht/prd-overdracht-dashboard-v1.md)
```markdown
# 📄 Product Requirements Document (PRD) Verpleegkundige Overdracht Dashboard
**Projectnaam:** Verpleegkundige Overdracht Dashboard
**Versie:** v1.0
**Datum:** 05-12-2024
**Auteur:** Colin Lit
---
## 1. Doelstelling
Een dashboard voor verpleegkundigen die dagelijks meerdere overdrachten doen aan artsen. Het dashboard bundelt alle relevante patiëntinformatie (metingen, rapportages, medicatie, risico's) in overzichtelijke blokken en biedt een AI-functie om automatisch een beknopte overdracht-samenvatting te genereren.
**Focus:** Snelheid en efficiëntie - verpleegkundigen hebben weinig tijd en moeten snel de juiste informatie kunnen vinden en overdragen.
**Type:** MVP/Prototype met AI-integratie
---
## 2. Doelgroep
**Primaire gebruikers:**
- **Verpleegkundigen (GGZ):** Doen ~6 overdrachten per dag aan artsen/collega's. Hebben behoefte aan snel overzicht van patiëntstatus, wijzigingen en aandachtspunten.
**Secundaire gebruikers:**
- **Artsen:** Ontvangen de overdracht, willen beknopte maar complete informatie.
- **Teamleiders:** Overzicht van alle patiënten en eventuele alerts.
**Kernbehoeften:**
- Snel overzicht van alle patiënten die overgedragen moeten worden
- Per patiënt: vitale functies, recente notities, medicatie, risico's
- AI-hulp om overdracht samen te vatten in 30 seconden
---
## 3. Kernfunctionaliteiten (MVP-scope)
### 3.1 Niveau 1: Behandelaar Overzicht
1. **Patiëntenlijst:** Grid van alle actieve patiënten met afspraken/encounters vandaag
2. **Quick Stats per patiënt:** Naam, leeftijd, aantal alerts, recente activiteit
3. **Filter op alerts:** Toon alleen patiënten met hoog risico of afwijkende metingen
4. **Doorklik naar detail:** Navigatie naar patiënt-specifiek overdracht scherm
### 3.2 Niveau 2: Patiënt Detail
5. **Informatieblokken:**
- **Vitale functies:** Metingen van vandaag (bloeddruk, hartslag, temperatuur, O2, ademhaling)
- **Rapportages:** Recente notities en observaties (laatste 24 uur)
- **Medicatie:** Huidige medicatie en recente wijzigingen *(placeholder voor MVP)*
- **Risico's:** Actieve risicotaxaties met ernst-niveau
6. **AI Samenvatting Blok:**
- Eén compact blok met "Genereer samenvatting" knop
- AI genereert beknopte overdracht op basis van alle informatieblokken
- Output: samenvatting (max 3 zinnen) + aandachtspunten + actiepunten
- Kan opnieuw gegenereerd worden bij nieuwe data
### 3.3 AI Integratie
7. **Overdracht Generator:**
- Input: vitals + reports + risks + diagnoses
- Output: gestructureerde JSON met samenvatting, aandachtspunten, actiepunten
- Taal: Nederlands, zakelijk, beknopt
- Markering van urgente zaken met [URGENT]
---
## 4. Gebruikersflows
### Flow 1: Dagelijkse Overdracht
1. Verpleegkundige opent Overdracht pagina
2. Ziet grid van alle patiënten voor vandaag
3. Filtert eventueel op "Met alerts"
4. Klikt op patiënt voor detail view
5. Bekijkt informatieblokken (vitals, reports, risico's)
6. Klikt "Genereer samenvatting"
7. AI maakt beknopte overdracht
8. Verpleegkundige gebruikt samenvatting voor mondelinge/schriftelijke overdracht
### Flow 2: Snelle Check bij Alert
1. Verpleegkundige ziet rode badge op patiënt-card (hoog risico)
2. Klikt direct door naar detail
3. Ziet welke vitale functies afwijkend zijn
4. Checkt bijbehorende rapportages
5. Neemt direct actie of escaleert
---
## 5. Niet in Scope
- **Medicatie-invoer:** Alleen weergave, geen CRUD (aparte module)
- **Multi-afdeling view:** Alleen eigen patiënten
- **Historische trends:** Geen grafieken of lange termijn overzichten
- **Notificaties/push:** Geen realtime alerts
- **Print/export:** Geen PDF of print functionaliteit
- **Rechten per rol:** Geen onderscheid verpleegkundige/arts (komt later)
---
## 6. Succescriteria
- [ ] Overzicht laadt binnen 2 seconden
- [ ] Patiënt detail toont alle 4 informatieblokken correct
- [ ] AI samenvatting genereert binnen 5 seconden
- [ ] AI output is begrijpelijk en medisch relevant
- [ ] Navigatie tussen overzicht en detail werkt vlot
- [ ] Alerts (hoog risico, afwijkende vitals) zijn direct zichtbaar
---
## 7. Risico's & Mitigatie
| Risico | Impact | Mitigatie |
|--------|--------|-----------|
| AI samenvatting te lang/vaag | Hoog | Strikte prompt met max lengte, testen met echte data |
| Geen vitale functies in systeem | Middel | Graceful empty state, instructie om metingen toe te voegen |
| Medicatie tabel bestaat niet | Middel | Placeholder blok met "Binnenkort beschikbaar" |
| Performance bij veel patiënten | Middel | Parallel queries, pagination indien nodig |
---
## 8. Roadmap / Vervolg (Post-MVP)
- **Fase 2:** Medicatie module met volledige CRUD
- **Fase 3:** Trend grafieken voor vitale functies over tijd
- **Fase 4:** Export naar PDF voor formele overdracht
- **Fase 5:** Integratie met agenda (koppeling encounters)
- **Fase 6:** Notificaties bij kritieke waarden
---
## 9. Bijlagen & Referenties
- **Bestaande AI integratie:** `/app/api/behandelplan/generate/route.ts` (prompt pattern)
- **Database schema:** `observations`, `reports`, `risk_assessments` tabellen
- **UI componenten:** `/components/ui/ai-button.tsx`, Card components
- **Gerelateerde docs:** `docs/specs/ai-integratie/`, `docs/design/datamodel-documentatie.md`
```
---
## Technisch Plan (na PRD goedkeuring)
### Route Structuur
```
/epd/overdracht/ → Level 1: Overzicht
/epd/overdracht/[patientId] → Level 2: Detail
```
### Te maken bestanden
1. `docs/specs/overdracht/prd-overdracht-dashboard-v1.md` (PRD)
2. `/lib/types/handover.ts` (types)
3. `/app/epd/overdracht/actions.ts` (server actions)
4. `/app/epd/overdracht/page.tsx` (overzicht)
5. `/app/epd/overdracht/[patientId]/page.tsx` (detail)
6. `/app/api/overdracht/generate/route.ts` (AI endpoint)
7. `/components/overdracht/*.tsx` (UI componenten)
8. Update `/app/epd/components/epd-sidebar.tsx` (navigatie)

View File

@@ -0,0 +1,197 @@
# 📄 Product Requirements Document (PRD) Verpleegkundige Overdracht Dashboard
**Projectnaam:** Verpleegkundige Overdracht Dashboard
**Versie:** v1.0
**Datum:** 05-12-2024
**Auteur:** Colin Lit
---
## 1. Doelstelling
🎯 **Doel:** Een dashboard voor verpleegkundigen die dagelijks meerdere overdrachten doen aan artsen. Het dashboard bundelt alle relevante patiëntinformatie (metingen, rapportages, medicatie, risico's) in overzichtelijke blokken en biedt een AI-functie om automatisch een beknopte overdracht-samenvatting te genereren.
**Focus:** Snelheid en efficiëntie - verpleegkundigen hebben weinig tijd en moeten snel de juiste informatie kunnen vinden en overdragen.
**Type:** MVP/Prototype met AI-integratie
> Een verpleegkundige doet gemiddeld 6 overdrachten per dag aan artsen. Met dit dashboard kan zij in 30 seconden een complete overdracht genereren, inclusief AI-samenvatting met aandachtspunten en actiepunten.
---
## 2. Doelgroep
🎯 **Doel:** Schets wie de eindgebruikers, stakeholders en testers zijn.
**Primaire gebruikers:**
- **Verpleegkundigen (GGZ):** Doen ~6 overdrachten per dag aan artsen/collega's. Hebben behoefte aan snel overzicht van patiëntstatus, wijzigingen en aandachtspunten.
**Secundaire gebruikers:**
- **Artsen:** Ontvangen de overdracht, willen beknopte maar complete informatie.
- **Teamleiders:** Overzicht van alle patiënten en eventuele alerts.
**Kernbehoeften:**
- Snel overzicht van alle patiënten die overgedragen moeten worden
- Per patiënt: vitale functies, recente notities, medicatie, risico's
- AI-hulp om overdracht samen te vatten in 30 seconden
---
## 3. Kernfunctionaliteiten (MVP-scope)
🎯 **Doel:** Afbakenen van de minimale werkende functies.
### 3.1 Niveau 1: Behandelaar Overzicht (`/epd/overdracht/`)
| # | Functie | Beschrijving |
|---|---------|--------------|
| 1 | **Patiëntenlijst** | Grid van alle actieve patiënten met afspraken/encounters vandaag |
| 2 | **Quick Stats per patiënt** | Naam, leeftijd, aantal alerts, recente activiteit |
| 3 | **Filter op alerts** | Toon alleen patiënten met hoog risico of afwijkende metingen |
| 4 | **Doorklik naar detail** | Navigatie naar patiënt-specifiek overdracht scherm |
### 3.2 Niveau 2: Patiënt Detail (`/epd/overdracht/[patientId]`)
| # | Functie | Beschrijving |
|---|---------|--------------|
| 5 | **Vitale functies blok** | Metingen van vandaag (bloeddruk, hartslag, temperatuur, O2, ademhaling) |
| 6 | **Rapportages blok** | Recente notities en observaties (laatste 24 uur) |
| 7 | **Medicatie blok** | Huidige medicatie en recente wijzigingen *(placeholder voor MVP)* |
| 8 | **Risico's blok** | Actieve risicotaxaties met ernst-niveau |
| 9 | **AI Samenvatting blok** | Compact blok met "Genereer samenvatting" knop |
### 3.3 AI Integratie
| # | Functie | Beschrijving |
|---|---------|--------------|
| 10 | **Overdracht Generator** | AI genereert beknopte overdracht op basis van alle informatieblokken |
| 11 | **Gestructureerde output** | Samenvatting (max 3 zinnen) + aandachtspunten + actiepunten |
| 12 | **Urgentie markering** | Urgente zaken gemarkeerd met [URGENT] |
---
## 4. Gebruikersflows (Demo- of MVP-flows)
🎯 **Doel:** Laten zien hoe de gebruiker stap-voor-stap door het systeem gaat.
### Flow 1: Dagelijkse Overdracht
```
1. Verpleegkundige opent Overdracht pagina
2. Ziet grid van alle patiënten voor vandaag
3. Filtert eventueel op "Met alerts"
4. Klikt op patiënt voor detail view
5. Bekijkt informatieblokken (vitals, reports, risico's)
6. Klikt "Genereer samenvatting"
7. AI maakt beknopte overdracht
8. Verpleegkundige gebruikt samenvatting voor mondelinge/schriftelijke overdracht
```
### Flow 2: Snelle Check bij Alert
```
1. Verpleegkundige ziet rode badge op patiënt-card (hoog risico)
2. Klikt direct door naar detail
3. Ziet welke vitale functies afwijkend zijn
4. Checkt bijbehorende rapportages
5. Neemt direct actie of escaleert
```
---
## 5. Niet in Scope
🎯 **Doel:** Duidelijk maken wat (nog) niet wordt gebouwd.
| Feature | Reden exclusie |
|---------|----------------|
| Medicatie-invoer | Alleen weergave, CRUD is aparte module |
| Multi-afdeling view | Te complex voor MVP, alleen eigen patiënten |
| Historische trends | Geen grafieken of lange termijn overzichten |
| Notificaties/push | Geen realtime alerts |
| Print/export | Geen PDF of print functionaliteit |
| Rechten per rol | Geen onderscheid verpleegkundige/arts (komt later) |
| Metingen invoer | Aparte functionaliteit, hier alleen weergave |
---
## 6. Succescriteria
🎯 **Doel:** Objectieve meetlat voor een geslaagde oplevering.
- [ ] Overzicht laadt binnen 2 seconden
- [ ] Patiënt detail toont alle 4 informatieblokken correct
- [ ] AI samenvatting genereert binnen 5 seconden
- [ ] AI output is begrijpelijk en medisch relevant
- [ ] Navigatie tussen overzicht en detail werkt vlot
- [ ] Alerts (hoog risico, afwijkende vitals) zijn direct zichtbaar
- [ ] Empty states bij ontbrekende data zijn informatief
---
## 7. Risico's & Mitigatie
🎯 **Doel:** Risico's vroeg signaleren en plannen hoe ermee om te gaan.
| Risico | Impact | Mitigatie |
|--------|--------|-----------|
| AI samenvatting te lang/vaag | Hoog | Strikte prompt met max lengte, testen met echte data |
| Geen vitale functies in systeem | Middel | Graceful empty state, instructie om metingen toe te voegen |
| Medicatie tabel bestaat niet | Middel | Placeholder blok met "Binnenkort beschikbaar" |
| Performance bij veel patiënten | Middel | Parallel queries, pagination indien nodig |
| Risico's gekoppeld aan intake ipv patient | Laag | Query via intake tabel |
---
## 8. Roadmap / Vervolg (Post-MVP)
🎯 **Doel:** Richting geven aan toekomstige uitbreidingen.
| Fase | Feature | Beschrijving |
|------|---------|--------------|
| 2 | Medicatie module | Volledige CRUD voor medicatie |
| 3 | Trend grafieken | Vitale functies over tijd |
| 4 | PDF export | Formele overdracht documenten |
| 5 | Agenda integratie | Koppeling met encounters/afspraken |
| 6 | Notificaties | Alerts bij kritieke waarden |
| 7 | Multi-afdeling | Overzicht meerdere afdelingen |
---
## 9. Bijlagen & Referenties
🎯 **Doel:** Bronnen koppelen voor context en consistentie.
### Bestaande Code Patterns
| Pattern | Locatie | Hergebruik voor |
|---------|---------|-----------------|
| AI integratie | `/app/api/behandelplan/generate/route.ts` | Overdracht generator |
| Server actions | `/app/epd/patients/[id]/behandelplan/actions.ts` | Data fetching |
| Card components | `/components/ui/card.tsx` | Informatieblokken |
| AI Button | `/components/ui/ai-button.tsx` | Genereer knop |
### Database Tabellen
| Tabel | Gebruik |
|-------|---------|
| `observations` | Vitale functies (FHIR-compliant) |
| `reports` | Rapportages/notities |
| `risk_assessments` | Risico's (via intakes) |
| `conditions` | Diagnoses |
| `patients`, `encounters` | Basis data |
### Gerelateerde Documentatie
- FO (Functioneel Ontwerp) *nog te schrijven*
- TO (Technisch Ontwerp) *nog te schrijven*
- `docs/specs/ai-integratie/` AI prompt patterns
- `docs/design/datamodel-documentatie.md` Database schema
---
## Changelog
| Versie | Datum | Auteur | Wijziging |
|--------|-------|--------|-----------|
| v1.0 | 05-12-2024 | Colin | Initieel PRD |

View File

@@ -0,0 +1,806 @@
# Technisch Ontwerp (TO) - Verpleegkundige Overdracht Dashboard
**Projectnaam:** Verpleegkundige Overdracht Dashboard
**Versie:** v1.0
**Datum:** 05-12-2024
**Auteur:** Claude (AI-assisted)
---
## 1. Doel en relatie met PRD en FO
**Doel van dit document:**
Dit Technisch Ontwerp beschrijft **hoe** het Overdracht Dashboard technisch wordt gebouwd. Het vertaalt de functionele specificaties uit FO v1.1 naar concrete technische implementatie.
**Relatie met andere documenten:**
- PRD: `prd-overdracht-dashboard-v1.md` - wat en waarom
- FO: `fo-overdracht-dashboard-v1.1.md` - hoe functioneel
- TO: dit document - hoe technisch
**Technische haalbaarheidsanalyse:**
| Aspect | Status | Toelichting |
|--------|--------|-------------|
| Database tabellen | Deels aanwezig | patients, encounters, observations, reports, risk_assessments, conditions aanwezig. nursing_logs moet worden toegevoegd |
| Frontend framework | Aanwezig | Next.js 15 App Router met EPD layout |
| UI componenten | Aanwezig | shadcn/ui (Card, Button, Badge, Dialog, etc.) |
| AI integratie | Aanwezig | Claude API via behandelplan/generate pattern |
| Authentication | Aanwezig | Supabase Auth met RLS |
---
## 2. Technische Architectuur Overzicht
```
┌─────────────────────────────────────────────────────────────────────┐
│ Frontend (Next.js 15) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ /epd/overdracht/ /epd/dagregistratie/[patientId] │
│ ├── page.tsx ├── page.tsx │
│ │ (Overzicht Grid) │ (Dagregistratie Module) │
│ │ │ │
│ └── [patientId]/ └── components/ │
│ └── page.tsx ├── log-list.tsx │
│ (Patiënt Detail) ├── log-form.tsx │
│ └── log-card.tsx │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ API Routes (Next.js) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ /api/overdracht/ │
│ ├── patients/route.ts GET - Patiënten voor vandaag │
│ ├── [patientId]/route.ts GET - Detail data voor patiënt │
│ └── generate/route.ts POST - AI samenvatting genereren │
│ │
│ /api/nursing-logs/ │
│ ├── route.ts GET/POST - CRUD nursing logs │
│ └── [logId]/route.ts PATCH/DELETE - Update/delete log │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ Database (Supabase) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Bestaand: Nieuw: │
│ ├── patients ├── nursing_logs │
│ ├── encounters │ │
│ ├── observations │ │
│ ├── reports │ │
│ ├── risk_assessments │ │
│ ├── conditions │ │
│ ├── intakes │ │
│ └── ai_events │ │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ External Services │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Claude API (Anthropic) AI samenvatting generatie │
│ Supabase Auth Session-based authentication │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 3. Techstack Selectie
| Component | Technologie | Argumentatie | Bestaand |
|-----------|-------------|--------------|----------|
| Frontend | Next.js 15 (App Router) | Bestaande codebase, SSR, TypeScript | Ja |
| Backend | Next.js API Routes | Co-located met frontend | Ja |
| Database | Supabase (PostgreSQL) | Realtime, auth included, RLS | Ja |
| AI | Claude claude-sonnet-4-20250514 | Bestaande integratie, Nederlands | Ja |
| Styling | TailwindCSS + shadcn/ui | Bestaande componenten | Ja |
| Validation | Zod | Type-safe validatie, bestaand pattern | Ja |
| State | React Server Components + Client | Minimale client state | Ja |
---
## 4. Datamodel
### 4.1 Nieuwe Tabel: nursing_logs
```sql
-- Migratie: create_nursing_logs_table
CREATE TABLE public.nursing_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
patient_id UUID NOT NULL REFERENCES patients(id) ON DELETE CASCADE,
-- Timing
shift_date DATE NOT NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Content
category TEXT NOT NULL CHECK (category IN (
'medicatie', 'adl', 'gedrag', 'incident', 'observatie'
)),
content TEXT NOT NULL CHECK (char_length(content) <= 500),
-- Overdracht markering
include_in_handover BOOLEAN NOT NULL DEFAULT false,
-- Metadata
created_by UUID NOT NULL REFERENCES auth.users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Indexes voor performance
CREATE INDEX idx_nursing_logs_patient ON nursing_logs(patient_id);
CREATE INDEX idx_nursing_logs_shift ON nursing_logs(shift_date);
CREATE INDEX idx_nursing_logs_handover ON nursing_logs(patient_id, include_in_handover)
WHERE include_in_handover = true;
CREATE INDEX idx_nursing_logs_timestamp ON nursing_logs(patient_id, timestamp DESC);
-- Updated_at trigger
CREATE OR REPLACE FUNCTION update_nursing_logs_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER nursing_logs_updated_at
BEFORE UPDATE ON nursing_logs
FOR EACH ROW
EXECUTE FUNCTION update_nursing_logs_updated_at();
-- Comments
COMMENT ON TABLE nursing_logs IS
'Operationele verpleegkundige dagregistraties - kort, snel, met overdracht-markering';
COMMENT ON COLUMN nursing_logs.shift_date IS
'Datum van de dienst (voor filtering per dag)';
COMMENT ON COLUMN nursing_logs.include_in_handover IS
'True als deze notitie relevant is voor overdracht';
```
### 4.2 RLS Policies voor nursing_logs
```sql
-- Enable RLS
ALTER TABLE nursing_logs ENABLE ROW LEVEL SECURITY;
-- Policy: Authenticated users kunnen lezen
CREATE POLICY "nursing_logs_select_authenticated" ON nursing_logs
FOR SELECT
TO authenticated
USING (true);
-- Policy: Authenticated users kunnen inserten
CREATE POLICY "nursing_logs_insert_authenticated" ON nursing_logs
FOR INSERT
TO authenticated
WITH CHECK (created_by = auth.uid());
-- Policy: Eigen logs kunnen updaten
CREATE POLICY "nursing_logs_update_own" ON nursing_logs
FOR UPDATE
TO authenticated
USING (created_by = auth.uid())
WITH CHECK (created_by = auth.uid());
-- Policy: Eigen logs kunnen deleten
CREATE POLICY "nursing_logs_delete_own" ON nursing_logs
FOR DELETE
TO authenticated
USING (created_by = auth.uid());
```
### 4.3 TypeScript Types
```typescript
// lib/types/nursing-log.ts
import { z } from 'zod';
export const NursingLogCategory = z.enum([
'medicatie',
'adl',
'gedrag',
'incident',
'observatie'
]);
export type NursingLogCategory = z.infer<typeof NursingLogCategory>;
export const NursingLogSchema = z.object({
id: z.string().uuid(),
patient_id: z.string().uuid(),
shift_date: z.string(), // ISO date
timestamp: z.string(), // ISO datetime
category: NursingLogCategory,
content: z.string().min(1).max(500),
include_in_handover: z.boolean(),
created_by: z.string().uuid(),
created_at: z.string(),
updated_at: z.string(),
});
export type NursingLog = z.infer<typeof NursingLogSchema>;
export const CreateNursingLogSchema = z.object({
patient_id: z.string().uuid(),
category: NursingLogCategory,
content: z.string().min(1).max(500),
timestamp: z.string().optional(), // Default: now
include_in_handover: z.boolean().default(false),
});
export type CreateNursingLog = z.infer<typeof CreateNursingLogSchema>;
export const UpdateNursingLogSchema = z.object({
category: NursingLogCategory.optional(),
content: z.string().min(1).max(500).optional(),
timestamp: z.string().optional(),
include_in_handover: z.boolean().optional(),
});
export type UpdateNursingLog = z.infer<typeof UpdateNursingLogSchema>;
// Category display mapping
export const CATEGORY_CONFIG: Record<NursingLogCategory, {
label: string;
icon: string;
color: string;
}> = {
medicatie: { label: 'Medicatie', icon: 'Pill', color: 'blue' },
adl: { label: 'ADL/verzorging', icon: 'Utensils', color: 'green' },
gedrag: { label: 'Gedragsobservatie', icon: 'User', color: 'purple' },
incident: { label: 'Incident', icon: 'AlertTriangle', color: 'red' },
observatie: { label: 'Algemene observatie', icon: 'FileText', color: 'gray' },
};
```
### 4.4 Bestaande Tabellen (queries)
```typescript
// Overdracht context queries
// 1. Patiënten voor vandaag (via actieve encounters)
const patientsToday = supabase
.from('encounters')
.select(`
patient_id,
patients!inner (
id, name_given, name_family, birth_date, gender
)
`)
.gte('period_start', todayStart)
.lte('period_start', todayEnd)
.in('status', ['planned', 'in-progress']);
// 2. Vitale functies (observations) vandaag
const vitalsToday = supabase
.from('observations')
.select('*')
.eq('patient_id', patientId)
.eq('category', 'vital-signs')
.gte('effective_datetime', todayStart)
.order('effective_datetime', { ascending: false });
// 3. Rapportages laatste 24 uur
const reportsLast24h = supabase
.from('reports')
.select('*')
.eq('patient_id', patientId)
.gte('created_at', last24h)
.is('deleted_at', null)
.order('created_at', { ascending: false });
// 4. Actieve risico's (via intake)
const activeRisks = supabase
.from('risk_assessments')
.select(`
*,
intakes!inner (patient_id)
`)
.eq('intakes.patient_id', patientId)
.in('risk_level', ['hoog', 'zeer_hoog', 'gemiddeld']);
// 5. Actieve diagnoses
const activeConditions = supabase
.from('conditions')
.select('*')
.eq('patient_id', patientId)
.eq('clinical_status', 'active');
```
---
## 5. API Ontwerp
### 5.1 Overdracht Endpoints
| Endpoint | Method | Input | Output | Auth |
|----------|--------|-------|--------|------|
| `/api/overdracht/patients` | GET | `?date=YYYY-MM-DD` | `PatientOverzicht[]` | Required |
| `/api/overdracht/[patientId]` | GET | - | `PatientDetail` | Required |
| `/api/overdracht/generate` | POST | `{ patientId }` | `AISamenvatting` | Required |
### 5.2 Nursing Logs Endpoints
| Endpoint | Method | Input | Output | Auth |
|----------|--------|-------|--------|------|
| `/api/nursing-logs` | GET | `?patientId&date` | `NursingLog[]` | Required |
| `/api/nursing-logs` | POST | `CreateNursingLog` | `NursingLog` | Required |
| `/api/nursing-logs/[logId]` | PATCH | `UpdateNursingLog` | `NursingLog` | Required |
| `/api/nursing-logs/[logId]` | DELETE | - | `204` | Required |
### 5.3 Response Types
```typescript
// lib/types/overdracht.ts
// Patiënt overzicht (lijst view)
export interface PatientOverzicht {
id: string;
name_given: string[];
name_family: string;
birth_date: string;
gender: string;
alerts: {
high_risk_count: number;
abnormal_vitals_count: number;
marked_logs_count: number;
};
}
// Patiënt detail (detail view)
export interface PatientDetail {
patient: {
id: string;
name_given: string[];
name_family: string;
name_prefix?: string;
birth_date: string;
gender: string;
};
vitals: VitalSign[];
reports: Report[];
nursingLogs: NursingLog[];
risks: RiskAssessment[];
conditions: Condition[];
}
// Vitale functie met interpretatie
export interface VitalSign {
id: string;
code_display: string;
value_quantity_value: number;
value_quantity_unit: string;
interpretation_code?: string; // 'H' | 'L' | 'N'
effective_datetime: string;
source_id: string;
}
// AI Samenvatting output
export interface AISamenvatting {
samenvatting: string;
aandachtspunten: Aandachtspunt[];
actiepunten: string[];
generatedAt: string;
durationMs: number;
}
export interface Aandachtspunt {
tekst: string;
urgent: boolean;
bron: {
type: 'observatie' | 'rapportage' | 'dagnotitie' | 'risico';
id: string;
datum: string;
label: string;
};
}
```
---
## 6. AI Integratie
### 6.1 AI Overdracht Generator
**Locatie:** `/api/overdracht/generate/route.ts`
**Pattern:** Gebaseerd op bestaande `behandelplan/generate` implementatie.
```typescript
// lib/ai/overdracht-prompt.ts
export const OVERDRACHT_SYSTEM_PROMPT = `Je bent een ervaren verpleegkundige die overdrachten maakt in een GGZ-instelling.
Je taak: Maak een beknopte, relevante overdracht voor de opvolgende dienst.
## Outputformaat (JSON)
{
"samenvatting": "1-2 zinnen over de patiënt en wat er speelt",
"aandachtspunten": [
{
"tekst": "Beschrijving van het aandachtspunt",
"urgent": true/false,
"bron": {
"type": "observatie|rapportage|dagnotitie|risico",
"id": "source-id",
"datum": "DD-MM-YYYY HH:mm",
"label": "Korte beschrijving bron"
}
}
],
"actiepunten": [
"Concrete actie voor opvolgende dienst"
]
}
## Regels
1. Taal: Nederlands, zakelijk, beknopt
2. Focus op: veranderingen, afwijkingen, aandachtspunten
3. Elke aandachtspunt MOET een bronverwijzing hebben
4. Maximum 5 aandachtspunten, 3 actiepunten
5. Markeer urgent=true voor:
- Medicatie-weigering
- Incidenten
- Sterk afwijkende vitals
- Hoog-risico situaties
`;
export function buildOverdrachtUserPrompt(context: OverdrachtContext): string {
const lines: string[] = [
`PATIENT: ${context.patientName}, ${context.age} jaar, ${context.gender}`,
'',
'DIAGNOSES:',
...context.conditions.map(c =>
`- ${c.code_display} (source: conditions/${c.id})`
),
'',
'VITALE FUNCTIES (vandaag):',
...context.vitals.map(v =>
`- ${v.code_display}: ${v.value_quantity_value} ${v.value_quantity_unit} ` +
`[${interpretationLabel(v.interpretation_code)}] ` +
`(source: observations/${v.id}, ${formatTime(v.effective_datetime)})`
),
'',
'RAPPORTAGES (laatste 24u):',
...context.reports.map(r =>
`- [${formatTime(r.created_at)}] ${r.type}: "${truncate(r.content, 200)}" ` +
`(source: reports/${r.id})`
),
'',
'DAGREGISTRATIES (relevant voor overdracht):',
...context.nursingLogs.map(l =>
`- [${formatTime(l.timestamp)}] [${l.category.toUpperCase()}] ${l.content} ` +
`(source: nursing_logs/${l.id})`
),
'',
'RISICOS:',
...context.risks.map(r =>
`- [${r.risk_level.toUpperCase()}] ${r.risk_type}: "${truncate(r.rationale, 150)}" ` +
`(source: risk_assessments/${r.id})`
),
];
return lines.join('\n');
}
```
### 6.2 AI Event Logging
```typescript
// Uitbreiding ai_events.kind enum
// Voeg toe: 'overdracht_generate'
await supabase.from('ai_events').insert({
kind: 'overdracht_generate',
patient_id: patientId,
request: {
vitalCount: context.vitals.length,
reportCount: context.reports.length,
logCount: context.nursingLogs.length,
riskCount: context.risks.length,
},
response: {
aandachtspuntenCount: result.aandachtspunten.length,
actiepuntenCount: result.actiepunten.length,
urgentCount: result.aandachtspunten.filter(a => a.urgent).length,
},
duration_ms: durationMs,
});
```
---
## 7. Frontend Componenten
### 7.1 Nieuwe Routes
```
app/epd/
├── overdracht/
│ ├── page.tsx # Overzicht grid
│ ├── [patientId]/
│ │ └── page.tsx # Patiënt detail
│ └── components/
│ ├── patient-card.tsx # Card in overzicht grid
│ ├── vitals-block.tsx # Vitale functies blok
│ ├── reports-block.tsx # Rapportages blok
│ ├── nursing-logs-block.tsx # Dagnotities blok
│ ├── risks-block.tsx # Risico's blok
│ └── ai-summary-block.tsx # AI samenvatting blok
└── dagregistratie/
└── [patientId]/
├── page.tsx # Dagregistratie module
└── components/
├── log-list.tsx # Lijst van notities
├── log-form.tsx # Quick entry form
└── log-card.tsx # Individuele notitie card
```
### 7.2 Component Hergebruik
| Nieuw Component | Hergebruik van | Locatie |
|-----------------|----------------|---------|
| patient-card.tsx | Card (shadcn), Badge | components/ui/ |
| ai-summary-block.tsx | AIButton pattern | behandelplan/ |
| log-form.tsx | Form patterns | screening/activity-log.tsx |
| risks-block.tsx | Card, Badge | intakes/risk/ |
### 7.3 Sidebar Uitbreiding
```typescript
// app/epd/components/epd-sidebar.tsx
// Voeg toe aan navigatie items:
{
title: 'Overdracht',
href: '/epd/overdracht',
icon: ClipboardList,
badge: alertCount > 0 ? alertCount : undefined,
},
```
---
## 8. Security & Compliance
### 8.1 Authentication Flow
```
User Request
┌─────────────────┐
│ Supabase Auth │ ← Session cookie
│ (JWT validation)│
└────────┬────────┘
┌─────────────────┐
│ RLS Policies │ ← auth.uid() check
│ (Row filtering) │
└────────┬────────┘
┌─────────────────┐
│ API Response │
└─────────────────┘
```
### 8.2 Security Checklist
- [x] **Authentication:** Supabase Auth (bestaand)
- [x] **Authorization:** RLS policies op nursing_logs
- [x] **Data Encryption:** PostgreSQL at rest, HTTPS in transit
- [x] **Input Validation:** Zod schemas op alle endpoints
- [x] **CORS:** Via Next.js middleware (bestaand)
- [x] **Audit Trail:** ai_events tabel voor AI calls
### 8.3 AVG/GDPR Compliance
- **Data minimalisatie:** nursing_logs max 500 chars
- **Retention:** nursing_logs cleanup na 30 dagen (fase 2)
- **Audit:** created_by tracking op alle records
- **Access logging:** Via Supabase logs
---
## 9. Performance & Scalability
### 9.1 Performance Targets
| Metric | Target | Implementatie |
|--------|--------|---------------|
| Overzicht load | < 2s | Parallel queries, index op encounters |
| Detail load | < 1.5s | Batch queries via Promise.all() |
| Log submit | < 500ms | Direct insert, optimistic UI |
| AI response | < 5s | Claude claude-sonnet-4-20250514, max_tokens=2048 |
### 9.2 Database Indexes
```sql
-- Bestaande indexes (verificatie)
CREATE INDEX IF NOT EXISTS idx_encounters_period ON encounters(period_start);
CREATE INDEX IF NOT EXISTS idx_observations_patient_datetime ON observations(patient_id, effective_datetime DESC);
CREATE INDEX IF NOT EXISTS idx_reports_patient_created ON reports(patient_id, created_at DESC);
-- Nieuwe indexes voor overdracht
CREATE INDEX idx_nursing_logs_patient ON nursing_logs(patient_id);
CREATE INDEX idx_nursing_logs_shift ON nursing_logs(shift_date);
CREATE INDEX idx_nursing_logs_handover ON nursing_logs(patient_id, include_in_handover)
WHERE include_in_handover = true;
```
### 9.3 Query Optimalisatie
```typescript
// Parallelle queries voor detail pagina
export async function getPatientDetail(patientId: string) {
const supabase = await createClient();
const today = new Date().toISOString().split('T')[0];
const last24h = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
const [
{ data: patient },
{ data: vitals },
{ data: reports },
{ data: nursingLogs },
{ data: risks },
{ data: conditions },
] = await Promise.all([
supabase.from('patients').select('*').eq('id', patientId).single(),
supabase.from('observations')
.select('*')
.eq('patient_id', patientId)
.eq('category', 'vital-signs')
.gte('effective_datetime', today)
.order('effective_datetime', { ascending: false }),
supabase.from('reports')
.select('*')
.eq('patient_id', patientId)
.gte('created_at', last24h)
.is('deleted_at', null)
.order('created_at', { ascending: false }),
supabase.from('nursing_logs')
.select('*')
.eq('patient_id', patientId)
.eq('shift_date', today)
.order('timestamp', { ascending: false }),
// Risks via intakes join
supabase.from('risk_assessments')
.select('*, intakes!inner(patient_id)')
.eq('intakes.patient_id', patientId),
supabase.from('conditions')
.select('*')
.eq('patient_id', patientId)
.eq('clinical_status', 'active'),
]);
return { patient, vitals, reports, nursingLogs, risks, conditions };
}
```
---
## 10. Deployment & Implementatie
### 10.1 Implementatie Volgorde
```
Fase 1: Database (1 migratie)
├── nursing_logs tabel
├── RLS policies
└── Indexes
Fase 2: API Routes
├── /api/nursing-logs (CRUD)
├── /api/overdracht/patients
├── /api/overdracht/[patientId]
└── /api/overdracht/generate
Fase 3: Frontend - Dagregistratie
├── /epd/dagregistratie/[patientId]/page.tsx
├── Log form component
└── Log list component
Fase 4: Frontend - Overdracht
├── /epd/overdracht/page.tsx
├── /epd/overdracht/[patientId]/page.tsx
├── Info blokken (vitals, reports, logs, risks)
└── AI samenvatting blok
Fase 5: Integratie
├── Sidebar link toevoegen
├── AI prompt refinement
└── Testing & polish
```
### 10.2 Migratie Commands
```bash
# Database migratie
npx supabase migration new create_nursing_logs_table
# TypeScript types regenereren
npx supabase gen types typescript --linked > lib/supabase/database.types.ts
```
---
## 11. Monitoring & Logging
### 11.1 Key Metrics
| Metric | Doel | Bron |
|--------|------|------|
| AI success rate | > 95% | ai_events.kind = 'overdracht_generate' |
| Avg AI duration | < 5s | ai_events.duration_ms |
| Log creations/day | Tracking | nursing_logs.created_at |
| Handover marks % | Tracking | nursing_logs.include_in_handover |
### 11.2 Error Tracking
```typescript
// Sentry error context (bestaand pattern)
Sentry.setContext('overdracht', {
patientId,
vitalCount: vitals.length,
reportCount: reports.length,
logCount: nursingLogs.length,
});
```
---
## 12. Risico's & Technische Mitigatie
| Risico | Impact | Waarschijnlijkheid | Mitigatie |
|--------|--------|-------------------|-----------|
| Claude API down | Hoog | Laag | Graceful error, toon data zonder AI |
| Veel patiënten (>50) | Middel | Middel | Pagination in overzicht, lazy loading |
| Lege data (geen vitals) | Laag | Hoog | Empty states per blok, instructieve tekst |
| AI hallucinaties | Hoog | Laag | Bronverwijzingen verplicht, validatie |
| Performance nursing_logs | Middel | Laag | Indexes, 30-dagen cleanup (fase 2) |
---
## 13. Bijlagen & Referenties
### 13.1 Projectdocumenten
| Document | Status |
|----------|--------|
| PRD Overdracht Dashboard v1.0 | Gereed |
| FO Overdracht Dashboard v1.1 | Gereed |
| TO Overdracht Dashboard v1.0 | Gereed (dit document) |
### 13.2 Code Locaties
| Wat | Locatie |
|-----|---------|
| AI prompt pattern | `lib/ai/behandelplan-prompt.ts` |
| API route pattern | `app/api/reports/route.ts` |
| shadcn components | `components/ui/` |
| Supabase client | `lib/auth/server.ts` |
| Database types | `lib/supabase/database.types.ts` |
### 13.3 Externe Documentatie
| Bron | URL |
|------|-----|
| Next.js App Router | https://nextjs.org/docs/app |
| Supabase RLS | https://supabase.com/docs/guides/auth/row-level-security |
| Claude API | https://docs.anthropic.com/en/api |
| shadcn/ui | https://ui.shadcn.com |
---
## Changelog
| Versie | Datum | Auteur | Wijziging |
|--------|-------|--------|-----------|
| v1.0 | 05-12-2024 | Claude | Initieel TO gebaseerd op FO v1.1 |
---
**Einde Technisch Ontwerp - Overdracht Dashboard v1.0**

View File

@@ -129,6 +129,7 @@ export type Database = {
based_on_examinations: string[] | null
based_on_intake_id: string | null
based_on_risk_assessments: string[] | null
behandelstructuur: Json | null
care_team_ids: string[] | null
category_code: string | null
category_display: string | null
@@ -137,6 +138,7 @@ export type Database = {
created_date: string | null
description: string | null
encounter_id: string | null
evaluatiemomenten: Json | null
goals: Json | null
id: string
identifier: string | null
@@ -145,9 +147,13 @@ export type Database = {
patient_id: string
period_end: string | null
period_start: string | null
published_at: string | null
sessie_planning: Json | null
status: Database["public"]["Enums"]["careplan_status"]
title: string
updated_at: string | null
veiligheidsplan: Json | null
version: number | null
}
Insert: {
activities?: Json | null
@@ -157,6 +163,7 @@ export type Database = {
based_on_examinations?: string[] | null
based_on_intake_id?: string | null
based_on_risk_assessments?: string[] | null
behandelstructuur?: Json | null
care_team_ids?: string[] | null
category_code?: string | null
category_display?: string | null
@@ -165,6 +172,7 @@ export type Database = {
created_date?: string | null
description?: string | null
encounter_id?: string | null
evaluatiemomenten?: Json | null
goals?: Json | null
id?: string
identifier?: string | null
@@ -173,9 +181,13 @@ export type Database = {
patient_id: string
period_end?: string | null
period_start?: string | null
published_at?: string | null
sessie_planning?: Json | null
status?: Database["public"]["Enums"]["careplan_status"]
title: string
updated_at?: string | null
veiligheidsplan?: Json | null
version?: number | null
}
Update: {
activities?: Json | null
@@ -185,6 +197,7 @@ export type Database = {
based_on_examinations?: string[] | null
based_on_intake_id?: string | null
based_on_risk_assessments?: string[] | null
behandelstructuur?: Json | null
care_team_ids?: string[] | null
category_code?: string | null
category_display?: string | null
@@ -193,6 +206,7 @@ export type Database = {
created_date?: string | null
description?: string | null
encounter_id?: string | null
evaluatiemomenten?: Json | null
goals?: Json | null
id?: string
identifier?: string | null
@@ -201,9 +215,13 @@ export type Database = {
patient_id?: string
period_end?: string | null
period_start?: string | null
published_at?: string | null
sessie_planning?: Json | null
status?: Database["public"]["Enums"]["careplan_status"]
title?: string
updated_at?: string | null
veiligheidsplan?: Json | null
version?: number | null
}
Relationships: [
{
@@ -765,6 +783,53 @@ export type Database = {
}
Relationships: []
}
nursing_logs: {
Row: {
category: string
content: string
created_at: string
created_by: string
id: string
include_in_handover: boolean
patient_id: string
shift_date: string
timestamp: string
updated_at: string
}
Insert: {
category: string
content: string
created_at?: string
created_by: string
id?: string
include_in_handover?: boolean
patient_id: string
shift_date: string
timestamp?: string
updated_at?: string
}
Update: {
category?: string
content?: string
created_at?: string
created_by?: string
id?: string
include_in_handover?: boolean
patient_id?: string
shift_date?: string
timestamp?: string
updated_at?: string
}
Relationships: [
{
foreignKeyName: "nursing_logs_patient_id_fkey"
columns: ["patient_id"]
isOneToOne: false
referencedRelation: "patients"
referencedColumns: ["id"]
},
]
}
observations: {
Row: {
body_site: string | null

114
lib/types/nursing-log.ts Normal file
View File

@@ -0,0 +1,114 @@
import { z } from 'zod';
import type { Database } from '@/lib/supabase/database.types';
// Database types
export type NursingLog = Database['public']['Tables']['nursing_logs']['Row'];
export type NursingLogInsert = Database['public']['Tables']['nursing_logs']['Insert'];
export type NursingLogUpdate = Database['public']['Tables']['nursing_logs']['Update'];
// Category enum
export const NURSING_LOG_CATEGORIES = [
'medicatie',
'adl',
'gedrag',
'incident',
'observatie',
] as const;
export type NursingLogCategory = (typeof NURSING_LOG_CATEGORIES)[number];
// Zod schemas for validation
export const NursingLogCategorySchema = z.enum(NURSING_LOG_CATEGORIES);
export const CreateNursingLogSchema = z.object({
patient_id: z.string().uuid('Patient ID moet een geldige UUID zijn'),
category: NursingLogCategorySchema,
content: z
.string()
.min(1, 'Notitie mag niet leeg zijn')
.max(500, 'Notitie mag maximaal 500 karakters bevatten'),
timestamp: z.string().datetime().optional(),
include_in_handover: z.boolean().default(false),
});
export type CreateNursingLogInput = z.infer<typeof CreateNursingLogSchema>;
export const UpdateNursingLogSchema = z.object({
category: NursingLogCategorySchema.optional(),
content: z
.string()
.min(1, 'Notitie mag niet leeg zijn')
.max(500, 'Notitie mag maximaal 500 karakters bevatten')
.optional(),
timestamp: z.string().datetime().optional(),
include_in_handover: z.boolean().optional(),
});
export type UpdateNursingLogInput = z.infer<typeof UpdateNursingLogSchema>;
// Response types
export interface NursingLogListResponse {
logs: NursingLog[];
total: number;
}
// Category display configuration
export const CATEGORY_CONFIG: Record<
NursingLogCategory,
{
label: string;
icon: string;
color: string;
bgColor: string;
textColor: string;
}
> = {
medicatie: {
label: 'Medicatie',
icon: 'Pill',
color: 'blue',
bgColor: 'bg-blue-100',
textColor: 'text-blue-700',
},
adl: {
label: 'ADL/verzorging',
icon: 'Utensils',
color: 'green',
bgColor: 'bg-green-100',
textColor: 'text-green-700',
},
gedrag: {
label: 'Gedragsobservatie',
icon: 'User',
color: 'purple',
bgColor: 'bg-purple-100',
textColor: 'text-purple-700',
},
incident: {
label: 'Incident',
icon: 'AlertTriangle',
color: 'red',
bgColor: 'bg-red-100',
textColor: 'text-red-700',
},
observatie: {
label: 'Algemene observatie',
icon: 'FileText',
color: 'gray',
bgColor: 'bg-gray-100',
textColor: 'text-gray-700',
},
};
// Helper function to calculate shift_date from timestamp
export function calculateShiftDate(timestamp: Date | string): string {
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
const hours = date.getHours();
// Night shift (before 7:00) belongs to previous day
if (hours < 7) {
date.setDate(date.getDate() - 1);
}
return date.toISOString().split('T')[0];
}