feat: add agenda intent scaffolding

This commit is contained in:
colinislit
2025-12-27 21:46:24 +01:00
parent 5b27a3b538
commit 6460b4361f
7 changed files with 751 additions and 51 deletions

View File

@@ -8,12 +8,16 @@
*/ */
import { useSwiftStore, type SwiftIntent } from '@/stores/swift-store'; import { useSwiftStore, type SwiftIntent } from '@/stores/swift-store';
import { FileText, Search, ArrowRightLeft, HelpCircle, Clock } from 'lucide-react'; import { FileText, Search, ArrowRightLeft, HelpCircle, Clock, Calendar, Plus, X } from 'lucide-react';
const INTENT_CONFIG: Record<SwiftIntent, { icon: typeof FileText; color: string; label: string }> = { const INTENT_CONFIG: Record<SwiftIntent, { icon: typeof FileText; color: string; label: string }> = {
dagnotitie: { icon: FileText, color: 'text-blue-600 bg-blue-50 border border-blue-200', label: 'Notitie' }, dagnotitie: { icon: FileText, color: 'text-blue-600 bg-blue-50 border border-blue-200', label: 'Notitie' },
zoeken: { icon: Search, color: 'text-emerald-600 bg-emerald-50 border border-emerald-200', label: 'Zoeken' }, zoeken: { icon: Search, color: 'text-emerald-600 bg-emerald-50 border border-emerald-200', label: 'Zoeken' },
overdracht: { icon: ArrowRightLeft, color: 'text-purple-600 bg-purple-50 border border-purple-200', label: 'Overdracht' }, overdracht: { icon: ArrowRightLeft, color: 'text-purple-600 bg-purple-50 border border-purple-200', label: 'Overdracht' },
agenda_query: { icon: Calendar, color: 'text-teal-600 bg-teal-50 border border-teal-200', label: 'Agenda' },
create_appointment: { icon: Plus, color: 'text-green-600 bg-green-50 border border-green-200', label: 'Afspraak' },
cancel_appointment: { icon: X, color: 'text-red-600 bg-red-50 border border-red-200', label: 'Annuleren' },
reschedule_appointment: { icon: Clock, color: 'text-amber-600 bg-amber-50 border border-amber-200', label: 'Verzetten' },
unknown: { icon: HelpCircle, color: 'text-slate-600 bg-slate-50 border border-slate-200', label: 'Actie' }, unknown: { icon: HelpCircle, color: 'text-slate-600 bg-slate-50 border border-slate-200', label: 'Actie' },
}; };

View File

@@ -17,11 +17,13 @@ Toelichting: dit bouwt voort op het Swift conversatiemodel en hergebruikt de
### 2.1 Technische Stack ### 2.1 Technische Stack
- Frontend: Next.js App Router + React + TypeScript - Frontend: Next.js App Router + React + TypeScript
- Styling: Tailwind CSS - Styling: Tailwind CSS + shadcn/ui components
- State: Zustand (Swift store) - State: Zustand (Swift store) + React Query for data caching
- Backend: Next.js route handlers + Supabase (encounters) - Backend: Next.js route handlers + Supabase (encounters table)
- AI/Intent: Local regex + Claude Haiku fallback - AI/Intent: Two-tier system (Local regex <50ms + Claude Haiku fallback ~400ms)
- Auth: Supabase Auth (server-side guard) - 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 ### 2.2 Projectkaders
- Scope (MVP): agenda_query, create_appointment, cancel_appointment, reschedule_appointment + AgendaBlock. - Scope (MVP): agenda_query, create_appointment, cancel_appointment, reschedule_appointment + AgendaBlock.
@@ -31,18 +33,36 @@ Toelichting: dit bouwt voort op het Swift conversatiemodel en hergebruikt de
- Reuse bestaande agenda actions waar mogelijk. - Reuse bestaande agenda actions waar mogelijk.
### 2.3 Programmeer uitgangspunten ### 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. - KISS: snelle API routes + simpele AgendaBlock UI.
- SOC: intent parsing, data fetching en UI gescheiden. - SOC: intent parsing, data fetching en UI gescheiden.
- YAGNI: alleen P1/P2 uit FO, geen extra planner features. - 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 ## 3. Epics & Stories Overzicht
| Epic ID | Titel | Doel | Status | Stories | Opmerkingen | | Epic ID | Titel | Doel | Status | Stories | Opmerkingen |
|---------|-------|------|--------|---------|-------------| |---------|-------|------|--------|---------|-------------|
| E0 | Alignment & scope | MVP afbakenen en keuzes vastleggen | Done | 2 | FObased | | E0 | Alignment & scope | MVP afbakenen en keuzes vastleggen | Done | 2 | FObased |
| 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 | NLPhelpers voor datum/tijd | To Do | 3 | Geen nieuwe deps | | E2 | Date/time parsing | NLPhelpers voor datum/tijd | To Do | 3 | Geen nieuwe deps |
| E3 | Backend integratie | Agenda data APIs + reuse actions | To Do | 4 | Auth vereist | | 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 | | 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 | | 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.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` | To Do | E1.S1 | 3 | | 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 | To Do | 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 | To Do | E1.S1 | 2 | | 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). - 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.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 | | 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 ### 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.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 | | E3.S4 | Patient match API | Fuzzy patiënt matching + disambiguation lijst | To Do | E1.S4 | 2 |
Technical notes: **Technical notes:**
- Reuse `app/epd/agenda/actions.ts` voor database logic.
- Auth guard via `createClient()` en `supabase.auth.getUser()`. **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.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 | | 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 ### 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.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 | | 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 ### Epic 6 — QA & docs
@@ -154,30 +465,129 @@ Test types:
| Test Type | Scope | Tools | Verantwoordelijke | | Test Type | Scope | Tools | Verantwoordelijke |
|-----------|-------|-------|-------------------| |-----------|-------|-------|-------------------|
| Lint | App code | pnpm lint | Developer | | 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 | | Regression | /epd/agenda klassiek | Manual checklist | Developer |
Manual test checklist (MVP): ### Manual Test Checklist (uit FO, 20 scenarios):
- "afspraken vandaag" → AgendaBlock list view
- "maak afspraak Jan morgen 14:00" → create form met prefill | # | Scenario | Expected Result | Priority | Status |
- "annuleer afspraak Jan" → disambiguation indien nodig |---|----------|-----------------|----------|--------|
- "verzet 14:00 naar 15:00" → reschedule form | 1 | "afspraken vandaag" | AgendaBlock list view, toont vandaag's afspraken | P1 | ⬜ |
- Link naar `/epd/agenda` werkt | 2 | "wat is volgende afspraak" | Chat response met eerstvolgende, AgendaBlock toont details | P1 | ⬜ |
- Geen auth → redirect naar `/login` | 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 ## 6. User Stories (uit FO)
Doel: korte demo van agenda planning via Swift.
Flow: ### Primaire User Stories (P1 - MVP):
1) "afspraken vandaag" → lijst
2) "maak afspraak met Jan morgen 14:00" → create form → submit | ID | Als | Wil ik | Zodat | Acceptatie |
3) "annuleer afspraak Jan" → confirm |----|-----|--------|-------|------------|
| 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 | | Risico | Kans | Impact | Mitigatie | Owner |
|--------|------|--------|-----------|-------| |--------|------|--------|-----------|-------|
| Intent ambigu | Hoog | Middel | Disambiguation + fallback prompt | UX | | 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: Te documenteren na oplevering:
- Welke intents vaak misclassificeren? - Welke intents vaak misclassificeren?
- Hoe snel users afspraken kunnen plannen? - Hoe snel users afspraken kunnen plannen?
@@ -195,27 +656,102 @@ Te documenteren na oplevering:
--- ---
## 9. Referenties ## 12. Referenties & Documentatie
- FO: `docs/swift/fo-swift-agenda-planning.md`
- Swift FO v3: `docs/swift/fo-swift-medical-scribe-v3.md` ### Primaire Documenten:
- Agenda module: `app/epd/agenda` - **FO Agenda Afspraken**: `docs/swift/fo-agenda-afspraken.md` - Gebruikersgericht FO
- Swift chat: `app/api/swift/chat/route.ts` - **FO Agenda Planning**: `docs/swift/fo-swift-agenda-planning.md` - Technisch FO met detailed specs
- Intent classifier: `lib/swift/intent-classifier.ts` - **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 | | Term | Betekenis |
|------|-----------| |------|-----------|
| Epic | Grote feature of fase | | **Epic** | Grote feature of fase, bevat meerdere stories |
| Story | Kleine uitvoerbare taak | | **Story** | Kleine uitvoerbare taak (user story) |
| MVP | Minimum Viable Product | | **SP** | Story Points - schatting van complexiteit |
| FO | Functioneel Ontwerp | | **MVP** | Minimum Viable Product |
| SSE | Server-Sent Events | | **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 | | 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)

View File

@@ -20,6 +20,9 @@ const ActionSchema = z.object({
category: z.enum(['medicatie', 'adl', 'gedrag', 'incident', 'observatie']).optional(), category: z.enum(['medicatie', 'adl', 'gedrag', 'incident', 'observatie']).optional(),
content: z.string().optional(), content: z.string().optional(),
query: z.string().optional(), // For zoeken intent query: z.string().optional(), // For zoeken intent
date: z.string().optional(),
time: z.string().optional(),
identifier: z.string().optional(),
}), }),
confidence: z.number().min(0).max(1), confidence: z.number().min(0).max(1),
artifact: z artifact: z

View File

@@ -11,12 +11,25 @@ import type { VerpleegkundigCategory } from '@/lib/types/report';
// Zod schema for AI response validation // Zod schema for AI response validation
const AIIntentResponseSchema = z.object({ const AIIntentResponseSchema = z.object({
intent: z.enum(['dagnotitie', 'zoeken', 'overdracht', 'unknown']), intent: z.enum([
'dagnotitie',
'zoeken',
'overdracht',
'agenda_query',
'create_appointment',
'cancel_appointment',
'reschedule_appointment',
'unknown',
]),
confidence: z.number().min(0).max(1), confidence: z.number().min(0).max(1),
entities: z.object({ entities: z.object({
patientName: z.string().optional(), patientName: z.string().optional(),
category: z.enum(['medicatie', 'adl', 'gedrag', 'incident', 'observatie']).optional(), category: z.enum(['medicatie', 'adl', 'gedrag', 'incident', 'observatie']).optional(),
content: z.string().optional(), content: z.string().optional(),
query: z.string().optional(),
date: z.string().optional(),
time: z.string().optional(),
identifier: z.string().optional(),
}).optional(), }).optional(),
reasoning: z.string().optional(), reasoning: z.string().optional(),
}); });
@@ -45,7 +58,19 @@ Je taak is om de intentie van een zorgmedewerker te classificeren in één van d
3. **overdracht** - Gebruiker wil een overdracht/samenvatting van de dienst 3. **overdracht** - Gebruiker wil een overdracht/samenvatting van de dienst
Voorbeelden: "overdracht", "wat moet ik weten", "dienst afronden" Voorbeelden: "overdracht", "wat moet ik weten", "dienst afronden"
4. **unknown** - Intentie is onduidelijk of past niet in bovenstaande categorieën 4. **agenda_query** - Gebruiker wil afspraken opvragen of de agenda zien
Voorbeelden: "afspraken vandaag", "wat is mijn volgende afspraak", "agenda volgende week"
5. **create_appointment** - Gebruiker wil een afspraak maken of plannen
Voorbeelden: "maak afspraak met jan morgen 14:00", "plan intake volgende week"
6. **cancel_appointment** - Gebruiker wil een afspraak annuleren
Voorbeelden: "annuleer afspraak jan", "zeg afspraak af"
7. **reschedule_appointment** - Gebruiker wil een afspraak verzetten
Voorbeelden: "verzet 14:00 naar 15:00", "verplaats afspraak naar dinsdag"
8. **unknown** - Intentie is onduidelijk of past niet in bovenstaande categorieën
Voor dagnotitie, extraheer ook: Voor dagnotitie, extraheer ook:
- patientName: de naam van de patiënt (indien genoemd) - patientName: de naam van de patiënt (indien genoemd)
@@ -55,14 +80,41 @@ Voor dagnotitie, extraheer ook:
Voor zoeken, extraheer: Voor zoeken, extraheer:
- patientName: de naam die gezocht wordt - patientName: de naam die gezocht wordt
Voor agenda_query, extraheer:
- query: het relevante datum-/tijd-bereik of scope (bijv. "vandaag", "volgende week")
- patientName: de patiëntnaam als die expliciet genoemd is
- date: een expliciete datum als losse waarde (bijv. "2025-01-05")
- time: een expliciete tijd (24-uurs, bijv. "14:00")
Voor create_appointment, extraheer:
- patientName: de patiëntnaam als die expliciet genoemd is
- query: datum/tijd/type/locatie details in vrije tekst (bijv. "morgen 14:00 intake")
- date: een expliciete datum als losse waarde (bijv. "2025-01-05")
- time: een expliciete tijd (24-uurs, bijv. "14:00")
Voor cancel_appointment, extraheer:
- patientName: de patiëntnaam als die expliciet genoemd is
- query: afspraakdetails in vrije tekst (bijv. "afspraak om 14:00", "afspraak van vrijdag")
- identifier: een expliciete afspraak-id indien genoemd
Voor reschedule_appointment, extraheer:
- patientName: de patiëntnaam als die expliciet genoemd is
- query: huidige + nieuwe datum/tijd in vrije tekst (bijv. "14:00 naar 15:00")
- date: de nieuwe expliciete datum indien genoemd
- time: de nieuwe expliciete tijd indien genoemd
Antwoord ALLEEN met een JSON object in dit formaat: Antwoord ALLEEN met een JSON object in dit formaat:
{ {
"intent": "dagnotitie" | "zoeken" | "overdracht" | "unknown", "intent": "dagnotitie" | "zoeken" | "overdracht" | "agenda_query" | "create_appointment" | "cancel_appointment" | "reschedule_appointment" | "unknown",
"confidence": 0.0-1.0, "confidence": 0.0-1.0,
"entities": { "entities": {
"patientName": "naam" (optioneel), "patientName": "naam" (optioneel),
"category": "medicatie" | "adl" | "gedrag" | "incident" | "observatie" (optioneel), "category": "medicatie" | "adl" | "gedrag" | "incident" | "observatie" (optioneel),
"content": "inhoud" (optioneel) "content": "inhoud" (optioneel),
"query": "vrije tekst voor planning" (optioneel),
"date": "YYYY-MM-DD" (optioneel),
"time": "HH:MM" (optioneel),
"identifier": "id" (optioneel)
}, },
"reasoning": "korte uitleg" (optioneel) "reasoning": "korte uitleg" (optioneel)
}`; }`;
@@ -138,6 +190,10 @@ export async function classifyIntentWithAI(input: string): Promise<AIClassificat
patientName: validated.entities?.patientName, patientName: validated.entities?.patientName,
category: validated.entities?.category as VerpleegkundigCategory | undefined, category: validated.entities?.category as VerpleegkundigCategory | undefined,
content: validated.entities?.content, content: validated.entities?.content,
query: validated.entities?.query,
date: validated.entities?.date,
time: validated.entities?.time,
identifier: validated.entities?.identifier,
}, },
source: 'ai', source: 'ai',
processingTimeMs, processingTimeMs,

View File

@@ -93,6 +93,65 @@ const INTENT_PATTERNS: Record<Exclude<SwiftIntent, 'unknown'>, PatternConfig[]>
{ pattern: /^begin\s+dienst\b/i, weight: 0.9 }, { pattern: /^begin\s+dienst\b/i, weight: 0.9 },
{ pattern: /^nieuwe?\s+dienst\b/i, weight: 0.85 }, { pattern: /^nieuwe?\s+dienst\b/i, weight: 0.85 },
], ],
// Agenda intents
agenda_query: [
// Exact commands
{ pattern: /^agenda\b/i, weight: 1.0 },
{ pattern: /^mijn\s+agenda\b/i, weight: 0.95 },
{ pattern: /^volgende\s+afspraak\b/i, weight: 0.95 },
// Question patterns
{ pattern: /^wat\s+zijn\s+(mijn\s+)?afspraken\b/i, weight: 1.0 },
{ pattern: /^(wat|wanneer)\s+is\s+(mijn\s+)?volgende\s+afspraak\b/i, weight: 1.0 },
// Date scoped queries
{ pattern: /^afspraken\s+(vandaag|morgen|deze\s+week|volgende\s+week)\b/i, weight: 0.95 },
{ pattern: /^(vandaag|morgen|deze\s+week|volgende\s+week)\s+afspraken\b/i, weight: 0.9 },
{ pattern: /^agenda\s+(vandaag|morgen|deze\s+week|volgende\s+week)\b/i, weight: 0.9 },
{ pattern: /^planning\s+(vandaag|morgen|deze\s+week|volgende\s+week)\b/i, weight: 0.85 },
{ pattern: /^afspraken\b(?!\s+(maken|plannen|inplannen|annuleren|verzetten|verplaatsen|verschuiven))\b/i, weight: 0.85 },
// Verb patterns
{ pattern: /^toon\s+agenda\b/i, weight: 0.9 },
{ pattern: /^laat\s+(mijn\s+)?agenda\s+zien\b/i, weight: 0.85 },
],
create_appointment: [
// Exact commands
{ pattern: /^maak\s+afspraak\b/i, weight: 1.0 },
{ pattern: /^plan\s+(een\s+)?(afspraak|intake|gesprek)\b/i, weight: 1.0 },
{ pattern: /^afspraak\s+(maken|plannen|inplannen)\b/i, weight: 0.95 },
{ pattern: /^afspraken\s+(maken|plannen|inplannen)\b/i, weight: 0.95 },
{ pattern: /^nieuwe?\s+afspraak\b/i, weight: 0.95 },
// Type-first patterns
{ pattern: /^maak\s+(een\s+)?(intake|behandeling|gesprek)\b/i, weight: 0.9 },
{ pattern: /^(intake|behandeling|gesprek)\s+(met\s+)?\w+/i, weight: 0.85 },
{ pattern: /^afspraak\s+met\s+\w+/i, weight: 0.9 },
{ pattern: /^plan\s+afspraak\s+met\s+\w+/i, weight: 0.9 },
],
cancel_appointment: [
// Exact commands
{ pattern: /^annuleer\s+(de\s+)?afspraak\b/i, weight: 1.0 },
{ pattern: /^cancel\s+(de\s+)?afspraak\b/i, weight: 1.0 },
{ pattern: /^verwijder\s+afspraak\b/i, weight: 0.95 },
{ pattern: /^afspraak\s+annuleren\b/i, weight: 0.95 },
{ pattern: /^zeg\s+afspraak\s+af\b/i, weight: 0.95 },
// Short forms
{ pattern: /^annuleer\s+\w+/i, weight: 0.7 }, // "annuleer jan"
],
reschedule_appointment: [
// Exact commands
{ pattern: /^verzet\s+(de\s+)?afspraak\b/i, weight: 1.0 },
{ pattern: /^verplaats\s+(de\s+)?afspraak\b/i, weight: 1.0 },
{ pattern: /^verschuif\s+afspraak\b/i, weight: 0.95 },
{ pattern: /^afspraak\s+verzetten\b/i, weight: 0.95 },
// Time shifts
{ pattern: /^\d{1,2}[:.]\d{2}\s+naar\s+\d{1,2}[:.]\d{2}\b/i, weight: 0.9 },
{ pattern: /^(verzet|verplaats)\s+\d{1,2}[:.]\d{2}\b/i, weight: 0.9 },
{ pattern: /^verzet\s+\w+\s+naar\b/i, weight: 0.85 }, // "verzet jan naar dinsdag"
],
}; };
// Help patterns (separate, always check) // Help patterns (separate, always check)

View File

@@ -7,7 +7,15 @@
import type { VerpleegkundigCategory } from '@/lib/types/report'; import type { VerpleegkundigCategory } from '@/lib/types/report';
// Intent types // Intent types
export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown'; export type SwiftIntent =
| 'dagnotitie'
| 'zoeken'
| 'overdracht'
| 'agenda_query'
| 'create_appointment'
| 'cancel_appointment'
| 'reschedule_appointment'
| 'unknown';
export type BlockType = Exclude<SwiftIntent, 'unknown'> | 'patient-dashboard'; export type BlockType = Exclude<SwiftIntent, 'unknown'> | 'patient-dashboard';
@@ -29,6 +37,9 @@ export interface ExtractedEntities {
category?: VerpleegkundigCategory; category?: VerpleegkundigCategory;
content?: string; content?: string;
query?: string; query?: string;
date?: string;
time?: string;
identifier?: string;
} }
// Block sizes // Block sizes
@@ -62,6 +73,30 @@ export const BLOCK_CONFIGS: Record<BlockType, BlockConfig> = {
size: 'lg', size: 'lg',
icon: 'ArrowRightLeft', icon: 'ArrowRightLeft',
}, },
agenda_query: {
type: 'agenda_query',
title: 'Agenda',
size: 'lg',
icon: 'Calendar',
},
create_appointment: {
type: 'create_appointment',
title: 'Nieuwe afspraak',
size: 'lg',
icon: 'Plus',
},
cancel_appointment: {
type: 'cancel_appointment',
title: 'Afspraak annuleren',
size: 'lg',
icon: 'X',
},
reschedule_appointment: {
type: 'reschedule_appointment',
title: 'Afspraak verzetten',
size: 'lg',
icon: 'Clock',
},
'patient-dashboard': { 'patient-dashboard': {
type: 'patient-dashboard', type: 'patient-dashboard',
title: 'Patiëntoverzicht', title: 'Patiëntoverzicht',

View File

@@ -13,6 +13,10 @@ export type SwiftIntent =
| 'dagnotitie' | 'dagnotitie'
| 'zoeken' | 'zoeken'
| 'overdracht' | 'overdracht'
| 'agenda_query'
| 'create_appointment'
| 'cancel_appointment'
| 'reschedule_appointment'
| 'unknown'; | 'unknown';
export type BlockType = Exclude<SwiftIntent, 'unknown'> | 'fallback' | 'patient-dashboard'; export type BlockType = Exclude<SwiftIntent, 'unknown'> | 'fallback' | 'patient-dashboard';
@@ -45,6 +49,9 @@ export interface ExtractedEntities {
category?: VerpleegkundigCategory; category?: VerpleegkundigCategory;
content?: string; content?: string;
query?: string; query?: string;
date?: string;
time?: string;
identifier?: string;
} }
// Block prefill data // Block prefill data