feat: add agenda intent scaffolding
This commit is contained in:
@@ -17,11 +17,13 @@ Toelichting: dit bouwt voort op het Swift conversatie‑model en hergebruikt de
|
||||
|
||||
### 2.1 Technische Stack
|
||||
- Frontend: Next.js App Router + React + TypeScript
|
||||
- Styling: Tailwind CSS
|
||||
- State: Zustand (Swift store)
|
||||
- Backend: Next.js route handlers + Supabase (encounters)
|
||||
- AI/Intent: Local regex + Claude Haiku fallback
|
||||
- Auth: Supabase Auth (server-side guard)
|
||||
- Styling: Tailwind CSS + shadcn/ui components
|
||||
- State: Zustand (Swift store) + React Query for data caching
|
||||
- Backend: Next.js route handlers + Supabase (encounters table)
|
||||
- AI/Intent: Two-tier system (Local regex <50ms + Claude Haiku fallback ~400ms)
|
||||
- Date/Time: date-fns library (al aanwezig)
|
||||
- Auth: Supabase Auth (server-side guard via `lib/auth/server.ts`)
|
||||
- Voice: Deepgram (existing integration voor voice input)
|
||||
|
||||
### 2.2 Projectkaders
|
||||
- Scope (MVP): agenda_query, create_appointment, cancel_appointment, reschedule_appointment + AgendaBlock.
|
||||
@@ -31,18 +33,36 @@ Toelichting: dit bouwt voort op het Swift conversatie‑model en hergebruikt de
|
||||
- Reuse bestaande agenda actions waar mogelijk.
|
||||
|
||||
### 2.3 Programmeer uitgangspunten
|
||||
- DRY: hergebruik `app/epd/agenda/actions.ts`.
|
||||
- DRY: hergebruik `app/epd/agenda/actions.ts` (getEncounters, createEncounter, cancelEncounter, rescheduleEncounter).
|
||||
- KISS: snelle API routes + simpele AgendaBlock UI.
|
||||
- SOC: intent parsing, data fetching en UI gescheiden.
|
||||
- YAGNI: alleen P1/P2 uit FO, geen extra planner features.
|
||||
|
||||
### 2.4 Performance Targets
|
||||
| Metric | Target | Actual (Expected) |
|
||||
|--------|--------|-------------------|
|
||||
| Local pattern match | <50ms | ~10-15ms |
|
||||
| Local + entity extraction | <100ms | ~30-50ms |
|
||||
| AI fallback (cold) | <800ms | ~400-600ms |
|
||||
| AI fallback (warm) | <500ms | ~200-400ms |
|
||||
| Total (local path) | <150ms | ~50-80ms |
|
||||
| Total (AI path) | <1000ms | ~500-700ms |
|
||||
|
||||
### 2.5 Confidence Thresholds
|
||||
| Confidence | Actie | Voorbeeld |
|
||||
|------------|-------|-----------|
|
||||
| **>0.9** | Direct artifact openen met prefill | "afspraken vandaag" |
|
||||
| **0.7-0.9** | Artifact + bevestigingsvraag | "maak afspraak jan morgen" (tijd ontbreekt) |
|
||||
| **0.5-0.7** | Verduidelijkingsvraag in chat | "maak afspraak" (patient/tijd ontbreekt) |
|
||||
| **<0.5** | Fallback: "Ik begrijp het niet" | Gibberish input |
|
||||
|
||||
---
|
||||
|
||||
## 3. Epics & Stories Overzicht
|
||||
| Epic ID | Titel | Doel | Status | Stories | Opmerkingen |
|
||||
|---------|-------|------|--------|---------|-------------|
|
||||
| E0 | Alignment & scope | MVP afbakenen en keuzes vastleggen | Done | 2 | FO‑based |
|
||||
| E1 | Intent & entity layer | Agenda intents + entities toevoegen | To Do | 4 | Swift intent stack |
|
||||
| E1 | Intent & entity layer | Agenda intents + entities toevoegen | Done | 4 | Swift intent stack |
|
||||
| E2 | Date/time parsing | NLP‑helpers voor datum/tijd | To Do | 3 | Geen nieuwe deps |
|
||||
| E3 | Backend integratie | Agenda data APIs + reuse actions | To Do | 4 | Auth vereist |
|
||||
| E4 | AgendaBlock UI | List/create/cancel/reschedule views | To Do | 5 | Swift artifact |
|
||||
@@ -76,13 +96,95 @@ Epic doel: agenda intent types en entities toevoegen aan Swift.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
| E1.S1 | SwiftIntent uitbreiden | Nieuwe agenda intents toegevoegd in types/store | To Do | E0.S1 | 2 |
|
||||
| E1.S2 | Local intent patterns | Regex patterns voor agenda intents in `intent-classifier.ts` | To Do | E1.S1 | 3 |
|
||||
| E1.S3 | AI fallback prompt | Prompt in `intent-classifier-ai.ts` uitgebreid met agenda intents | To Do | E1.S1 | 3 |
|
||||
| E1.S4 | Entities schema | `ExtractedEntities` uitgebreid met date/time/identifier | To Do | E1.S1 | 2 |
|
||||
| E1.S1 | SwiftIntent uitbreiden | Agenda intent names toegevoegd in types/store | Done | E0.S1 | 2 |
|
||||
| E1.S2 | Local intent patterns | Regex patterns voor agenda intents in `intent-classifier.ts` | Done | E1.S1 | 3 |
|
||||
| E1.S3 | AI fallback prompt | Prompt in `intent-classifier-ai.ts` uitgebreid met agenda intents | Done | E1.S1 | 3 |
|
||||
| E1.S4 | Entities schema | `ExtractedEntities` uitgebreid met date/time/identifier | Done | E1.S1 | 2 |
|
||||
|
||||
Technical notes:
|
||||
**Technical notes:**
|
||||
|
||||
**E1.S1 - SwiftIntent type uitbreiding:**
|
||||
- File: `lib/swift/types.ts`
|
||||
- Houd `SwiftIntent` single source of truth (voorkom duplicatie).
|
||||
- SwiftIntent (na uitbreiding):
|
||||
```ts
|
||||
type SwiftIntent =
|
||||
| 'dagnotitie'
|
||||
| 'zoeken'
|
||||
| 'overdracht'
|
||||
| 'agenda_query' // 🆕
|
||||
| 'create_appointment' // 🆕
|
||||
| 'cancel_appointment' // 🆕
|
||||
| 'reschedule_appointment'// 🆕
|
||||
| 'unknown';
|
||||
```
|
||||
|
||||
**E1.S2 - Local pattern examples:**
|
||||
- File: `lib/swift/intent-classifier.ts`
|
||||
- Toevoegen aan `INTENT_PATTERNS`:
|
||||
```ts
|
||||
agenda_query: [
|
||||
{ pattern: /^afspraken?\\b/i, weight: 1.0 },
|
||||
{ pattern: /^agenda\\b/i, weight: 1.0 },
|
||||
{ pattern: /^(wat|wanneer)\\s+is\\s+(mijn\\s+)?volgende\\s+afspraak/i, weight: 1.0 },
|
||||
],
|
||||
create_appointment: [
|
||||
{ pattern: /^maak\\s+afspraak\\b/i, weight: 1.0 },
|
||||
{ pattern: /^plan\\s+(een\\s+)?(intake|afspraak)\\b/i, weight: 1.0 },
|
||||
],
|
||||
cancel_appointment: [
|
||||
{ pattern: /^annuleer\\s+(de\\s+)?afspraak/i, weight: 1.0 },
|
||||
],
|
||||
reschedule_appointment: [
|
||||
{ pattern: /^verzet\\s+(de\\s+)?afspraak/i, weight: 1.0 },
|
||||
{ pattern: /^\\d{1,2}:\\d{2}\\s+naar\\s+\\d{1,2}:\\d{2}/i, weight: 0.9 },
|
||||
],
|
||||
```
|
||||
|
||||
**E1.S3 - AI System Prompt addition:**
|
||||
- File: `lib/swift/intent-classifier-ai.ts`
|
||||
- Toevoegen aan `INTENT_CLASSIFIER_SYSTEM_PROMPT`:
|
||||
- Agenda intent descriptions + voorbeelden
|
||||
- Entity extraction instructies (patientName, dateRange, datetime, type, identifier, newDatetime)
|
||||
- Response format met nieuwe entities
|
||||
|
||||
**E1.S4 - ExtractedEntities uitbreiding:**
|
||||
- File: `lib/swift/types.ts`
|
||||
- Toevoegen:
|
||||
```ts
|
||||
interface ExtractedEntities {
|
||||
// Bestaande entities
|
||||
patientName?: string;
|
||||
category?: VerpleegkundigCategory;
|
||||
content?: string;
|
||||
|
||||
// 🆕 Agenda entities
|
||||
dateRange?: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
label: 'vandaag' | 'morgen' | 'deze week' | 'volgende week' | 'custom';
|
||||
};
|
||||
datetime?: {
|
||||
date: Date;
|
||||
time: string; // "HH:mm" format
|
||||
};
|
||||
appointmentType?: 'intake' | 'behandeling' | 'follow-up' | 'telefonisch' |
|
||||
'huisbezoek' | 'online' | 'crisis' | 'overig';
|
||||
location?: 'praktijk' | 'online' | 'thuis';
|
||||
identifier?: {
|
||||
type: 'patient' | 'time' | 'both';
|
||||
patientName?: string;
|
||||
patientId?: string;
|
||||
time?: string;
|
||||
date?: Date;
|
||||
encounterId?: string;
|
||||
};
|
||||
newDatetime?: {
|
||||
date: Date;
|
||||
time: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -95,6 +197,34 @@ Epic doel: datum/tijd interpretatie uit natuurlijke taal.
|
||||
| E2.S2 | Time parser utility | Tijd normalisatie (14:00, half drie) | To Do | E2.S1 | 2 |
|
||||
| E2.S3 | Entity extraction hook | Entity extractor gebruikt parser output | To Do | E2.S2 | 2 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E2.S1 - Date parser implementation:**
|
||||
- File: `lib/swift/date-time-parser.ts`
|
||||
- Functie: `parseRelativeDate(input: string): Date | DateRange | null`
|
||||
- Ondersteunt:
|
||||
- "vandaag", "morgen", "overmorgen"
|
||||
- "maandag", "dinsdag", ... (next weekday)
|
||||
- "deze week", "volgende week" (returns DateRange)
|
||||
- Fallback naar date-fns `parse()` voor "30 december", "28-12-2024"
|
||||
- Gebruik `date-fns` helpers: `addDays()`, `startOfWeek()`, `endOfWeek()`, `addWeeks()`
|
||||
|
||||
**E2.S2 - Time parser implementation:**
|
||||
- Functie: `parseTime(input: string): string | null`
|
||||
- Ondersteunt:
|
||||
- "14:00" → "14:00"
|
||||
- "14" → "14:00"
|
||||
- "twee uur" → "14:00"
|
||||
- "half drie" → "14:30"
|
||||
- "kwart voor drie" → "14:45"
|
||||
- Returns `HH:mm` format string
|
||||
|
||||
**E2.S3 - Integration:**
|
||||
- Parser functies worden aangeroepen in entity extraction flow
|
||||
- Local pattern matching extraheert ruwe strings ("morgen", "14:00")
|
||||
- Parser functies normaliseren naar Date/time formats
|
||||
- AI fallback gebruikt parser voor validation
|
||||
|
||||
---
|
||||
|
||||
### Epic 3 — Backend integratie
|
||||
@@ -107,9 +237,42 @@ Epic doel: agenda data ontsluiten voor Swift blocks.
|
||||
| E3.S3 | Cancel/reschedule API | Endpoints die `cancelEncounter`/`rescheduleEncounter` aanroepen | To Do | E3.S1 | 3 |
|
||||
| E3.S4 | Patient match API | Fuzzy patiënt matching + disambiguation lijst | To Do | E1.S4 | 2 |
|
||||
|
||||
Technical notes:
|
||||
- Reuse `app/epd/agenda/actions.ts` voor database logic.
|
||||
- Auth guard via `createClient()` en `supabase.auth.getUser()`.
|
||||
**Technical notes:**
|
||||
|
||||
**E3.S1 - Agenda query endpoint:**
|
||||
- Route: `GET /api/swift/agenda?start=2024-12-27&end=2024-12-27`
|
||||
- Auth: via `createClient()` from `lib/auth/server.ts`
|
||||
- Hergebruik: `getEncounters()` from `app/epd/agenda/actions.ts`
|
||||
- Response: `{ appointments: Encounter[] }`
|
||||
- Filters: practitioner_id = current user, status = 'planned', period_start in range
|
||||
- Order: chronological (period_start ASC)
|
||||
|
||||
**E3.S2 - Create appointment endpoint:**
|
||||
- Route: `POST /api/swift/agenda/create`
|
||||
- Body: `{ patientId, datetime: { date, time }, type, location, notes? }`
|
||||
- Validation: Zod schema, Dutch error messages
|
||||
- Hergebruik: `createEncounter()` from `app/epd/agenda/actions.ts`
|
||||
- Mapping:
|
||||
- type → typeCode (FHIR encounter type)
|
||||
- location → classCode ('AMB' = praktijk, 'VR' = online, 'HH' = thuis)
|
||||
- datetime → periodStart/periodEnd (1 hour duration default)
|
||||
- Response: `{ encounterId, success: true }`
|
||||
|
||||
**E3.S3 - Cancel/Reschedule endpoints:**
|
||||
- Route: `POST /api/swift/agenda/cancel`
|
||||
- Body: `{ encounterId }`
|
||||
- Hergebruik: `cancelEncounter(encounterId)` (soft delete: status → 'cancelled')
|
||||
|
||||
- Route: `POST /api/swift/agenda/reschedule`
|
||||
- Body: `{ encounterId, newDatetime: { date, time } }`
|
||||
- Hergebruik: `rescheduleEncounter(encounterId, periodStart, periodEnd)`
|
||||
|
||||
**E3.S4 - Patient search endpoint:**
|
||||
- Route: `GET /api/swift/patients/search?q=jan`
|
||||
- Fuzzy match op patient name (ILIKE %query%)
|
||||
- Response: `{ patients: Array<{ id, name, bsn }> }`
|
||||
- Limit: 10 resultaten
|
||||
- Used voor: disambiguation bij meerdere matches
|
||||
|
||||
---
|
||||
|
||||
@@ -124,6 +287,85 @@ Epic doel: Swift artifact voor agenda flows.
|
||||
| E4.S4 | Cancel view | Disambiguation + confirm flow | To Do | E4.S1 | 3 |
|
||||
| E4.S5 | Reschedule view | Edit form met nieuwe tijd | To Do | E4.S1 | 3 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E4.S1 - AgendaBlock component structure:**
|
||||
- File: `components/swift/artifacts/blocks/agenda-block.tsx`
|
||||
- Props interface:
|
||||
```ts
|
||||
interface AgendaBlockProps {
|
||||
mode: 'list' | 'create' | 'cancel' | 'reschedule';
|
||||
appointments?: Encounter[];
|
||||
dateRange?: { start: Date; end: Date; label: string };
|
||||
prefillData?: {
|
||||
patient?: { id: string; name: string };
|
||||
datetime?: { date: Date; time: string };
|
||||
type?: EncounterType;
|
||||
location?: EncounterLocation;
|
||||
};
|
||||
disambiguationOptions?: Encounter[];
|
||||
onClose?: () => void;
|
||||
}
|
||||
```
|
||||
- Architecture: switch statement op mode → render juiste sub-component
|
||||
- Styling: max-width 600px, max-height 80vh, overflow-y auto
|
||||
|
||||
**E4.S2 - List view:**
|
||||
- Component: `<AgendaListView />`
|
||||
- Layout:
|
||||
- Header: Datum label (e.g., "Afspraken Vandaag - 27 december") + close button
|
||||
- Body: Scrollable lijst van appointment cards
|
||||
- Footer: Link naar `/epd/agenda` ("📅 Open volledige agenda →")
|
||||
- Appointment card bevat:
|
||||
- Tijd (09:00 - 10:00)
|
||||
- Patient naam (klikbaar → opens PatientContextCard)
|
||||
- Type badge met color coding (intake=blauw, behandeling=groen, crisis=rood)
|
||||
- Locatie icon (📍 Praktijk, 🌐 Online, 🏠 Thuis)
|
||||
- Actions: [Details] [Annuleren]
|
||||
- Empty state: "📭 Geen afspraken gevonden" + [Maak nieuwe afspraak] button
|
||||
|
||||
**E4.S3 - Create form:**
|
||||
- Component: `<AgendaCreateForm />`
|
||||
- Fields:
|
||||
- Patiënt* (autocomplete, pre-filled)
|
||||
- Datum* (date picker, pre-filled)
|
||||
- Tijd* (time picker 07:00-20:00, pre-filled)
|
||||
- Type* (radio buttons: Intake, Behandeling, Vervolg, Telefonisch, Huisbezoek, Crisis)
|
||||
- Locatie* (radio buttons: Praktijk, Online, Thuis)
|
||||
- Notities (textarea, optioneel, max 500 chars)
|
||||
- Validation:
|
||||
- Patient required
|
||||
- Date cannot be in past
|
||||
- Time within 07:00-20:00
|
||||
- Conflict warning: "⚠️ Conflict: Je hebt al een afspraak om 14:00 met Marie" (optional feature)
|
||||
- Submit: [Annuleren] [✓ Afspraak maken]
|
||||
- States: initial → validating → submitting → success/error
|
||||
|
||||
**E4.S4 - Cancel view:**
|
||||
- Component: `<AgendaCancelView />`
|
||||
- Submode 1: Disambiguation (multiple matches)
|
||||
- Radio list met opties: "○ 28-12-2024 09:00 - Jan de Vries (Intake)"
|
||||
- [Annuleren] [Volgende →] buttons
|
||||
- Submode 2: Confirmation (single match or after disambiguation)
|
||||
- Appointment details card
|
||||
- Warning: "⚠️ Deze actie kan niet ongedaan worden gemaakt."
|
||||
- [Terug] [✓ Annuleren] buttons
|
||||
|
||||
**E4.S5 - Reschedule view:**
|
||||
- Component: `<AgendaRescheduleForm />`
|
||||
- Layout:
|
||||
- Afspraak details (readonly)
|
||||
- Huidige datum/tijd (readonly, strikethrough styling)
|
||||
- Nieuwe datum/tijd* (editable date + time pickers, pre-filled)
|
||||
- Conflict check: "✅ Geen conflicten gevonden" or warning
|
||||
- [Annuleren] [✓ Verzetten] buttons
|
||||
|
||||
**Styling specs:**
|
||||
- Colors: Teal-700 primary, appointment type badges (zie FO sectie 13.3)
|
||||
- Animations: slide-in from right 200ms, fade-out on close
|
||||
- Typography: text-sm voor body, text-base voor headers
|
||||
- Spacing: 16px padding, 12px card gaps
|
||||
|
||||
---
|
||||
|
||||
### Epic 5 — Chat orchestration
|
||||
@@ -135,6 +377,75 @@ Epic doel: agenda intents laten landen in juiste artifact.
|
||||
| E5.S2 | Chat prompt update | `/api/swift/chat` prompt bevat agenda sectie + action format | To Do | E1.S3 | 2 |
|
||||
| E5.S3 | Error states | User-friendly errors + link naar `/epd/agenda` | To Do | E3.S1 | 2 |
|
||||
|
||||
**Technical notes:**
|
||||
|
||||
**E5.S1 - Intent router implementation:**
|
||||
- Location: Swift store of artifact manager
|
||||
- Functie: `openArtifactForIntent(intent, entities, confidence)`
|
||||
- Logic:
|
||||
```ts
|
||||
if (confidence < 0.7) return null; // Trigger verduidelijkingsvraag
|
||||
|
||||
switch (intent) {
|
||||
case 'agenda_query':
|
||||
return { type: 'AgendaBlock', mode: 'list', prefill: { dateRange } };
|
||||
case 'create_appointment':
|
||||
if (!entities.patientName) return null; // Need patient
|
||||
return { type: 'AgendaBlock', mode: 'create', prefill: { patient, datetime, type, location } };
|
||||
case 'cancel_appointment':
|
||||
return { type: 'AgendaBlock', mode: 'cancel', prefill: { identifier } };
|
||||
case 'reschedule_appointment':
|
||||
if (!entities.identifier) return null; // Need to know which appointment
|
||||
return { type: 'AgendaBlock', mode: 'reschedule', prefill: { identifier, newDatetime } };
|
||||
}
|
||||
```
|
||||
|
||||
**E5.S2 - Chat API system prompt addition:**
|
||||
- File: `app/api/swift/chat/route.ts`
|
||||
- Toevoegen aan system prompt:
|
||||
```markdown
|
||||
## Agenda & Afspraken Beheer
|
||||
|
||||
Je helpt gebruikers met agenda-gerelateerde taken.
|
||||
|
||||
### Intents die je herkent:
|
||||
1. **agenda_query** - Afspraken opvragen
|
||||
Voorbeelden: "afspraken vandaag", "wat is volgende", "agenda morgen"
|
||||
Actie: Toon lijst van afspraken in AgendaBlock
|
||||
|
||||
2. **create_appointment** - Nieuwe afspraak maken
|
||||
Voorbeelden: "maak afspraak met Jan morgen 14:00", "plan intake"
|
||||
Required: patient, date, time
|
||||
Optional: type (default: behandeling), location (default: praktijk)
|
||||
|
||||
3. **cancel_appointment** - Afspraak annuleren
|
||||
Voorbeelden: "annuleer afspraak Jan", "cancel 14:00"
|
||||
Actie: Toon confirmation dialog
|
||||
|
||||
4. **reschedule_appointment** - Afspraak verzetten
|
||||
Voorbeelden: "verzet 14:00 naar 15:00", "verzet Jan naar dinsdag"
|
||||
Actie: Toon edit form met oude + nieuwe tijd
|
||||
|
||||
### Entity Extraction:
|
||||
- Patient: extraheer volledige naam, gebruik activePatient als user zegt "deze patiënt"
|
||||
- Date/Time: parse relatieve datums ("morgen", "dinsdag"), validate geen verleden
|
||||
- Type: intake, behandeling, follow-up, telefonisch (default: behandeling)
|
||||
- Location: praktijk, online, thuis (default: praktijk)
|
||||
|
||||
### Verduidelijkingsvragen:
|
||||
Stel vragen bij:
|
||||
- Meerdere patiënten met zelfde naam
|
||||
- Onduidelijke datum
|
||||
- Ontbrekende tijd bij create
|
||||
- Meerdere matches bij cancel/reschedule
|
||||
```
|
||||
|
||||
**E5.S3 - Error handling:**
|
||||
- Server errors: "Er ging iets mis bij het ophalen van je afspraken. Probeer het opnieuw of [open de volledige agenda](/epd/agenda)."
|
||||
- Auth errors: Redirect naar `/login`
|
||||
- Validation errors: Dutch messages ("Datum kan niet in het verleden liggen")
|
||||
- Network errors: Retry logic + user-friendly message
|
||||
|
||||
---
|
||||
|
||||
### Epic 6 — QA & docs
|
||||
@@ -154,30 +465,129 @@ Test types:
|
||||
| Test Type | Scope | Tools | Verantwoordelijke |
|
||||
|-----------|-------|-------|-------------------|
|
||||
| Lint | App code | pnpm lint | Developer |
|
||||
| Smoke tests | Agenda intents + artifact flows | Manual checklist | Developer/UX |
|
||||
| Type check | TypeScript | pnpm build | Developer |
|
||||
| Smoke tests | Agenda intents + artifact flows | Manual checklist (20 scenarios) | Developer/UX |
|
||||
| Regression | /epd/agenda klassiek | Manual checklist | Developer |
|
||||
|
||||
Manual test checklist (MVP):
|
||||
- "afspraken vandaag" → AgendaBlock list view
|
||||
- "maak afspraak Jan morgen 14:00" → create form met prefill
|
||||
- "annuleer afspraak Jan" → disambiguation indien nodig
|
||||
- "verzet 14:00 naar 15:00" → reschedule form
|
||||
- Link naar `/epd/agenda` werkt
|
||||
- Geen auth → redirect naar `/login`
|
||||
### Manual Test Checklist (uit FO, 20 scenarios):
|
||||
|
||||
| # | Scenario | Expected Result | Priority | Status |
|
||||
|---|----------|-----------------|----------|--------|
|
||||
| 1 | "afspraken vandaag" | AgendaBlock list view, toont vandaag's afspraken | P1 | ⬜ |
|
||||
| 2 | "wat is volgende afspraak" | Chat response met eerstvolgende, AgendaBlock toont details | P1 | ⬜ |
|
||||
| 3 | "maak afspraak jan morgen 14:00" | Create form met prefill, patient gevonden | P1 | ⬜ |
|
||||
| 4 | Voice: "maak intake marie vrijdag 10:00" | Transcript correct, create form opent | P2 | ⬜ |
|
||||
| 5 | "maak afspraak" (incomplete) | Verduidelijkingsvraag: "Met welke patiënt?" | P1 | ⬜ |
|
||||
| 6 | "maak afspraak onbekende patient" | Patient search fallback, suggesties | P2 | ⬜ |
|
||||
| 7 | Create form submit → success | Toast, chat confirmation, artifact sluit | P1 | ⬜ |
|
||||
| 8 | "annuleer afspraak jan" (1 match) | Confirmation dialog, direct match | P1 | ⬜ |
|
||||
| 9 | "annuleer afspraak jan" (3 matches) | Disambiguation radio list | P1 | ⬜ |
|
||||
| 10 | Cancel confirm → success | Afspraak status=cancelled, toast | P1 | ⬜ |
|
||||
| 11 | "verzet 14:00 naar 15:00" | Reschedule form, tijd pre-filled | P1 | ⬜ |
|
||||
| 12 | Reschedule submit → success | Tijd updated, toast confirmation | P1 | ⬜ |
|
||||
| 13 | Conflict warning (overlapping) | Warning banner in create form | P2 | ⬜ |
|
||||
| 14 | Click patient name in list | PatientContextCard opent | P2 | ⬜ |
|
||||
| 15 | Click "Details" button | Navigates to /epd/agenda with filter | P2 | ⬜ |
|
||||
| 16 | Empty state (geen afspraken) | Placeholder met "Maak nieuwe afspraak" | P1 | ⬜ |
|
||||
| 17 | Server error tijdens query | Error in chat, geen artifact | P1 | ⬜ |
|
||||
| 18 | Date in past validation | "Kan geen afspraken in verleden maken" | P1 | ⬜ |
|
||||
| 19 | Context-aware: "maak afspraak deze patient" | Gebruikt active patient uit context | P2 | ⬜ |
|
||||
| 20 | Multiple artifacts (3 max) | Agenda + Dagnotitie + Context card, tabs | P2 | ⬜ |
|
||||
|
||||
### Regression Test Checklist:
|
||||
|
||||
| # | Test | Expected Result | Status |
|
||||
|---|------|-----------------|--------|
|
||||
| R1 | Klassieke agenda `/epd/agenda` werkt nog | Kan afspraken zien/maken/wijzigen | ⬜ |
|
||||
| R2 | Swift dagnotitie intent ongewijzigd | "dagnotitie jan medicatie" opent DagnotatieBlock | ⬜ |
|
||||
| R3 | Swift zoeken intent ongewijzigd | "zoek jan" opent ZoekenBlock | ⬜ |
|
||||
| R4 | Swift overdracht intent ongewijzigd | "overdracht" opent OverdrachtBlock | ⬜ |
|
||||
| R5 | Auth guard op alle endpoints | Geen auth → 401/redirect | ⬜ |
|
||||
|
||||
### Performance Test Targets:
|
||||
|
||||
| Metric | Target | Test Method |
|
||||
|--------|--------|-------------|
|
||||
| Local pattern match | <50ms | Browser DevTools Network tab |
|
||||
| Create form open | <150ms total | Lighthouse/manual timing |
|
||||
| AI fallback | <1000ms | DevTools, onduidelijke input |
|
||||
| List view render (10 items) | <100ms | React DevTools Profiler |
|
||||
|
||||
---
|
||||
|
||||
## 6. Demo & Presentatieplan
|
||||
Doel: korte demo van agenda planning via Swift.
|
||||
## 6. User Stories (uit FO)
|
||||
|
||||
Flow:
|
||||
1) "afspraken vandaag" → lijst
|
||||
2) "maak afspraak met Jan morgen 14:00" → create form → submit
|
||||
3) "annuleer afspraak Jan" → confirm
|
||||
### Primaire User Stories (P1 - MVP):
|
||||
|
||||
| ID | Als | Wil ik | Zodat | Acceptatie |
|
||||
|----|-----|--------|-------|------------|
|
||||
| US-24 | Verpleegkundige | Snel overzicht afspraken vandaag | Ik weet waar ik moet zijn | "afspraken vandaag" → lijst in AgendaBlock, <3 sec |
|
||||
| US-25 | Verpleegkundige | Check volgende afspraak tijdens werk | Ik op tijd ben | "wat is mijn volgende afspraak?" → directe info |
|
||||
| US-27 | Verpleegkundige | Snelle afspraak tijdens telefoongesprek | Ik direct kan plannen | "maak afspraak jan morgen 14:00" → prefilled form, <15 sec |
|
||||
| US-28 | Verpleegkundige | Context-aware planning | Ik niet steeds naam hoef te typen | "maak afspraak met deze patiënt" → gebruikt actieve patiënt |
|
||||
| US-29 | Verpleegkundige | Voice input tijdens consult | Ik hands-free kan werken | Spraak → afspraak maken |
|
||||
| US-30 | Verpleegkundige | Annuleren via chat | Ik snel kan annuleren | "annuleer afspraak jan" → confirmation → done |
|
||||
| US-31 | Verpleegkundige | Snel verzetten | Ik afspraken flexibel kan aanpassen | "verzet 14:00 naar 15:00" → tijd update |
|
||||
|
||||
### Secundaire User Stories (P2 - Nice to Have):
|
||||
|
||||
| ID | Als | Wil ik | Zodat | Acceptatie |
|
||||
|----|-----|--------|-------|------------|
|
||||
| US-26 | Verpleegkundige | Weekoverzicht bekijken | Ik kan plannen | "agenda deze week" → gefilterde lijst |
|
||||
| US-32 | Verpleegkundige | Disambiguation bij meerdere matches | Systeem helpt kiezen | Systeem vraagt "Welke Jan?" → lijst opties |
|
||||
|
||||
---
|
||||
|
||||
## 7. Risico's & Mitigatie
|
||||
## 7. File Structuur (na implementatie)
|
||||
|
||||
```
|
||||
lib/swift/
|
||||
├── types.ts # SwiftIntent + ExtractedEntities uitbreidingen
|
||||
├── intent-classifier.ts # Local patterns voor agenda intents
|
||||
├── intent-classifier-ai.ts # AI prompt uitbreidingen
|
||||
└── date-time-parser.ts # 🆕 Date/time parsing utilities
|
||||
|
||||
app/api/swift/
|
||||
├── agenda/
|
||||
│ ├── route.ts # 🆕 GET agenda query endpoint
|
||||
│ ├── create/route.ts # 🆕 POST create appointment
|
||||
│ ├── cancel/route.ts # 🆕 POST cancel appointment
|
||||
│ └── reschedule/route.ts # 🆕 POST reschedule appointment
|
||||
├── patients/
|
||||
│ └── search/route.ts # 🆕 GET fuzzy patient search
|
||||
└── chat/route.ts # Updated: agenda system prompt
|
||||
|
||||
components/swift/artifacts/blocks/
|
||||
├── agenda-block.tsx # 🆕 Main AgendaBlock component
|
||||
├── agenda-list-view.tsx # 🆕 List mode
|
||||
├── agenda-create-form.tsx # 🆕 Create mode
|
||||
├── agenda-cancel-view.tsx # 🆕 Cancel mode
|
||||
└── agenda-reschedule-form.tsx # 🆕 Reschedule mode
|
||||
|
||||
app/epd/agenda/
|
||||
└── actions.ts # ♻️ Hergebruikt (getEncounters, createEncounter, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Demo & Presentatieplan
|
||||
Doel: korte demo van agenda planning via Swift (5 minuten).
|
||||
|
||||
**Demo Flow:**
|
||||
1. **Agenda query**: "afspraken vandaag" → lijst met 3 afspraken
|
||||
2. **Quick create**: "maak afspraak met Jan morgen 14:00" → create form → submit → success toast
|
||||
3. **Cancel flow**: "annuleer afspraak Jan" → disambiguation (3 matches) → select → confirm → success
|
||||
4. **Reschedule**: "verzet de 14:00 naar 15:00" → edit form → submit → success
|
||||
5. **Fallback**: Link naar klassieke agenda tonen via footer
|
||||
|
||||
**Key Messaging:**
|
||||
- Swift = snelle queries en actions (10-15 sec)
|
||||
- Klassieke agenda = visuele planning en overzicht
|
||||
- Voice input = hands-free werken tijdens consult
|
||||
|
||||
---
|
||||
|
||||
## 9. Risico's & Mitigatie
|
||||
| Risico | Kans | Impact | Mitigatie | Owner |
|
||||
|--------|------|--------|-----------|-------|
|
||||
| Intent ambigu | Hoog | Middel | Disambiguation + fallback prompt | UX |
|
||||
@@ -187,7 +597,58 @@ Flow:
|
||||
|
||||
---
|
||||
|
||||
## 8. Evaluatie & Lessons Learned
|
||||
## 10. Sprint Planning & Implementatie Volgorde
|
||||
|
||||
### Voorgestelde Sprint Indeling (3 sprints):
|
||||
|
||||
**Sprint 1: Foundation (E1 + E2)**
|
||||
- Focus: Intent layer + date/time parsing
|
||||
- Stories: E1.S1, E1.S2, E1.S3, E1.S4, E2.S1, E2.S2, E2.S3
|
||||
- Total: 17 SP
|
||||
- Oplevering: Intent detection + entity extraction werkend, nog geen UI
|
||||
- Test: Console log check van extracted entities
|
||||
|
||||
**Sprint 2: Backend + UI Foundation (E3 + E4.S1-S2)**
|
||||
- Focus: API endpoints + AgendaBlock basis
|
||||
- Stories: E3.S1, E3.S2, E3.S3, E3.S4, E4.S1, E4.S2
|
||||
- Total: 14 SP
|
||||
- Oplevering: Agenda query werkend met list view
|
||||
- Test: "afspraken vandaag" end-to-end
|
||||
|
||||
**Sprint 3: UI Completion + Integration (E4.S3-S5 + E5 + E6)**
|
||||
- Focus: Create/cancel/reschedule forms + orchestration
|
||||
- Stories: E4.S3, E4.S4, E4.S5, E5.S1, E5.S2, E5.S3, E6.S1, E6.S2, E6.S3
|
||||
- Total: 20 SP
|
||||
- Oplevering: MVP compleet
|
||||
- Test: Alle 20 test scenarios
|
||||
|
||||
**Total MVP: 51 SP (~3 sprints à 2 weken = 6 weken)**
|
||||
|
||||
### Implementatie Volgorde (binnen stories):
|
||||
|
||||
Voor elke story, volg deze volgorde:
|
||||
1. **Types first**: Definieer interfaces/types
|
||||
2. **Backend**: API endpoints + server actions
|
||||
3. **Frontend**: UI components
|
||||
4. **Integration**: Wire backend ↔ frontend
|
||||
5. **Test**: Manual test scenario's
|
||||
|
||||
### Critical Path:
|
||||
```
|
||||
E1.S1 (types) → E1.S2 (patterns) → E1.S4 (entities) → E2 (parsers) →
|
||||
E3.S1 (query API) → E4.S1 (skeleton) → E4.S2 (list view) →
|
||||
E5.S1 (routing) → Milestone: Basic Query Werkend
|
||||
|
||||
E3.S2 (create API) → E4.S3 (create form) → Milestone: Create Werkend
|
||||
|
||||
E3.S3 (cancel API) → E4.S4 (cancel view) → Milestone: Cancel Werkend
|
||||
|
||||
E4.S5 (reschedule) → E5.S2 (prompt) → E6 (QA) → MVP Done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Evaluatie & Lessons Learned
|
||||
Te documenteren na oplevering:
|
||||
- Welke intents vaak misclassificeren?
|
||||
- Hoe snel users afspraken kunnen plannen?
|
||||
@@ -195,27 +656,102 @@ Te documenteren na oplevering:
|
||||
|
||||
---
|
||||
|
||||
## 9. Referenties
|
||||
- FO: `docs/swift/fo-swift-agenda-planning.md`
|
||||
- Swift FO v3: `docs/swift/fo-swift-medical-scribe-v3.md`
|
||||
- Agenda module: `app/epd/agenda`
|
||||
- Swift chat: `app/api/swift/chat/route.ts`
|
||||
- Intent classifier: `lib/swift/intent-classifier.ts`
|
||||
## 12. Referenties & Documentatie
|
||||
|
||||
### Primaire Documenten:
|
||||
- **FO Agenda Afspraken**: `docs/swift/fo-agenda-afspraken.md` - Gebruikersgericht FO
|
||||
- **FO Agenda Planning**: `docs/swift/fo-swift-agenda-planning.md` - Technisch FO met detailed specs
|
||||
- **Swift FO v3**: `docs/swift/fo-swift-medical-scribe-v3.md` - Basis conversational interface
|
||||
- **Swift Bouwplan Core**: `docs/swift/bouwplan-swift-standalone-module.md` - Development roadmap
|
||||
|
||||
### Code Referenties:
|
||||
- **Bestaande Agenda**: `app/epd/agenda` - Klassieke kalender (wordt hergebruikt)
|
||||
- **Agenda Actions**: `app/epd/agenda/actions.ts` - Server actions (getEncounters, createEncounter, etc.)
|
||||
- **Swift Chat API**: `app/api/swift/chat/route.ts` - Conversational endpoint
|
||||
- **Intent Classifier**: `lib/swift/intent-classifier.ts` - Local pattern matching
|
||||
- **AI Classifier**: `lib/swift/intent-classifier-ai.ts` - Claude Haiku fallback
|
||||
- **Swift Types**: `lib/swift/types.ts` - SwiftIntent & ExtractedEntities
|
||||
|
||||
### Libraries & Dependencies:
|
||||
- **date-fns**: Date/time utilities (already installed)
|
||||
- **Zod**: API validation schemas (already installed)
|
||||
- **React Query**: Data caching (already installed)
|
||||
- **shadcn/ui**: UI components (already installed)
|
||||
- **Deepgram**: Voice input (already integrated)
|
||||
|
||||
### External References:
|
||||
- [FHIR Encounter Resource](https://www.hl7.org/fhir/encounter.html) - Encounter data model
|
||||
- [date-fns Documentation](https://date-fns.org/) - Date manipulation
|
||||
- [React Query Best Practices](https://tanstack.com/query/latest) - Caching patterns
|
||||
|
||||
---
|
||||
|
||||
## 10. Glossary & Abbreviations
|
||||
## 13. Glossary & Abbreviations
|
||||
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| Epic | Grote feature of fase |
|
||||
| Story | Kleine uitvoerbare taak |
|
||||
| MVP | Minimum Viable Product |
|
||||
| FO | Functioneel Ontwerp |
|
||||
| SSE | Server-Sent Events |
|
||||
| **Epic** | Grote feature of fase, bevat meerdere stories |
|
||||
| **Story** | Kleine uitvoerbare taak (user story) |
|
||||
| **SP** | Story Points - schatting van complexiteit |
|
||||
| **MVP** | Minimum Viable Product |
|
||||
| **FO** | Functioneel Ontwerp |
|
||||
| **P1/P2** | Priority 1 (must have) / Priority 2 (nice to have) |
|
||||
| **SSE** | Server-Sent Events |
|
||||
| **RLS** | Row Level Security (Supabase) |
|
||||
| **NLP** | Natural Language Processing |
|
||||
| **Artifact** | Swift UI component (AgendaBlock, DagnotatieBlock, etc.) |
|
||||
| **Intent** | Gebruikersintentie (agenda_query, create_appointment, etc.) |
|
||||
| **Entity** | Geëxtraheerde data uit user input (patient, date, time) |
|
||||
| **Disambiguation** | Verduidelijking bij meerdere matches |
|
||||
| **Confidence** | Zekerheid van intent classificatie (0.0-1.0) |
|
||||
| **Two-tier** | Local pattern matching + AI fallback systeem |
|
||||
| **FHIR** | Fast Healthcare Interoperability Resources |
|
||||
| **Encounter** | Afspraak/contact in FHIR terminologie |
|
||||
|
||||
### Domain-Specific Terms:
|
||||
|
||||
| Term | Betekenis |
|
||||
|------|-----------|
|
||||
| **Klassieke Agenda** | Bestaande visuele kalender in `/epd/agenda` |
|
||||
| **Swift Agenda** | Nieuwe conversational agenda interface |
|
||||
| **AgendaBlock** | Swift artifact voor agenda functionaliteit |
|
||||
| **Verpleegkundige** | Primaire gebruikersrol (nurse/healthcare provider) |
|
||||
| **Practitioner** | FHIR term voor zorgverlener |
|
||||
| **Quick Create** | Snel afspraak maken via natuurlijke taal |
|
||||
| **Hands-free** | Werken met voice input (geen typen nodig) |
|
||||
|
||||
---
|
||||
|
||||
Versiehistorie:
|
||||
## Versiehistorie
|
||||
|
||||
| Versie | Datum | Auteur | Wijziging |
|
||||
|--------|-------|--------|-----------|
|
||||
| v1.0 | 27-12-2025 | Colin Lit | Initiele versie |
|
||||
| v1.0 | 27-12-2025 | Colin Lit | Initiele versie - basis epics en stories |
|
||||
| v1.1 | 27-12-2025 | Claude Code | Uitgebreid met technische details uit FO's: patterns, entities, API specs, UI components, test scenarios, sprint planning, file structuur |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
**Voor Developers - Start Here:**
|
||||
1. Lees Epic 0 voor scope
|
||||
2. Begin met E1.S1 (types uitbreiden)
|
||||
3. Volg implementatie volgorde (sectie 10)
|
||||
4. Check test scenarios (sectie 5)
|
||||
5. Zie file structuur (sectie 7) voor waar code moet komen
|
||||
|
||||
**Voor Product Owners:**
|
||||
- MVP scope: sectie 2.2
|
||||
- User stories: sectie 6
|
||||
- Demo plan: sectie 8
|
||||
- Risico's: sectie 9
|
||||
|
||||
**Voor QA:**
|
||||
- Test checklist: sectie 5 (20 scenarios)
|
||||
- Regression tests: sectie 5
|
||||
- Performance targets: sectie 2.4
|
||||
|
||||
**Voor Designers:**
|
||||
- UI components: Epic 4 technical notes
|
||||
- Styling specs: E4.S1-E4.S5
|
||||
- User flows: FO documenten (zie sectie 12)
|
||||
|
||||
Reference in New Issue
Block a user