feat(swift): implement agenda planning module (Epic 4 UI)

- Add AgendaBlock core component with list, create, cancel, reschedule modes
- Implement AgendaListView with patient/type/location details and actions
- Implement AgendaCreateForm with fuzzy patient search and validation
- Implement AgendaCancelView with disambiguation support
- Implement AgendaRescheduleForm with date/time picker
- Integrate with server actions (create, cancel, reschedule)
- Add radio-group UI component
- Update documentation and status
This commit is contained in:
colinislit
2025-12-27 22:29:11 +01:00
parent 0031bd5fd1
commit a6b63665e1
18 changed files with 5874 additions and 19 deletions

View File

@@ -0,0 +1,879 @@
# Architecture: Intent System Schaalbaarheid
**Document:** Intent System Scalability & Optimization
**Versie:** 1.0
**Datum:** 27-12-2024
**Auteur:** Colin Lit
---
## 📊 Probleem Analyse
### Huidige Situatie
**Aantal intents:** 7 (dagnotitie, zoeken, overdracht, + 4 agenda intents)
**Patterns per intent:** ~5-10
**Totaal patterns:** ~60
**Performance nu:**
- Classification time: ~10-15ms
- O(n) linear search door alle patterns
- Acceptable voor huidige schaal
### Toekomstige Schaal (geschat)
Bij volledige EPD uitbreiding:
| Module | Nieuwe Intents | Patterns per Intent | Totaal |
|--------|----------------|---------------------|--------|
| **Medicatie** | 5 (voorschrijven, toedienen, stop, bijwerking, controle) | 8 | 40 |
| **Diagnostiek** | 4 (lab aanvragen, uitslagen, röntgen, echo) | 6 | 24 |
| **Behandelplan** | 4 (maken, wijzigen, evalueren, afsluiten) | 7 | 28 |
| **Verpleegkundige acties** | 6 (wondverzorging, katheter, infuus, etc.) | 5 | 30 |
| **Communicatie** | 3 (brief, consult aanvraag, telefoonnota) | 6 | 18 |
| **Rapportages** | 5 (MDO, intake, evaluatie, ontslagbrief) | 7 | 35 |
| **Huidig** | 7 | ~8 | 60 |
| **TOTAAL** | **34 intents** | **~7 avg** | **~235 patterns** |
**Geschatte performance bij 235 patterns:**
- Classification time: ~40-60ms (4x slower)
- Meer pattern conflicts (overlap)
- Moeilijker te maintainen
---
## 🎯 Optimalisatie Strategieën
## Strategie 1: Categoriegebaseerde Hierarchie ⭐ **AANBEVOLEN**
### Concept
Groepeer intents in categorieën en gebruik **two-phase classification**:
1. **Phase 1:** Detect categorie (snel, 5-10 opties)
2. **Phase 2:** Detect intent binnen categorie (kleiner search space)
### Categorie Structuur
```typescript
enum IntentCategory {
DOCUMENTATION = 'documentation', // Notities, rapportages
PATIENT_CARE = 'patient_care', // Medicatie, metingen, acties
SCHEDULING = 'scheduling', // Agenda, planning
COMMUNICATION = 'communication', // Brieven, consults
DIAGNOSTIC = 'diagnostic', // Lab, beeldvorming
ADMINISTRATIVE = 'administrative', // Overdracht, MDO
SEARCH = 'search', // Zoeken, info opvragen
}
type SwiftIntent =
// DOCUMENTATION
| 'dagnotitie'
| 'rapportage_intake'
| 'rapportage_evaluatie'
| 'rapportage_ontslag'
| 'vrije_notitie'
// PATIENT_CARE
| 'medicatie_toedienen'
| 'medicatie_voorschrijven'
| 'medicatie_stop'
| 'meting_vitaal'
| 'wondverzorging'
| 'katheter_verzorging'
// SCHEDULING
| 'agenda_query'
| 'create_appointment'
| 'cancel_appointment'
| 'reschedule_appointment'
// DIAGNOSTIC
| 'lab_aanvraag'
| 'lab_uitslag'
| 'rontgen_aanvraag'
| 'echo_aanvraag'
// COMMUNICATION
| 'brief_huisarts'
| 'consult_aanvraag'
| 'telefoonnota'
// ADMINISTRATIVE
| 'overdracht'
| 'mdo_verslag'
// SEARCH
| 'zoeken'
| 'patient_info'
| 'medicatie_info'
| 'unknown';
```
### Implementation
```typescript
// lib/swift/intent-classifier-hierarchical.ts
interface CategoryPattern {
pattern: RegExp;
category: IntentCategory;
weight: number;
}
// Step 1: Category patterns (small set, ~20 patterns)
const CATEGORY_PATTERNS: CategoryPattern[] = [
// DOCUMENTATION keywords
{ pattern: /\b(notitie|rapportage|verslag|schrijf|document)\b/i,
category: IntentCategory.DOCUMENTATION, weight: 0.9 },
// PATIENT_CARE keywords
{ pattern: /\b(medicatie|toedien|voorschrijf|bloeddruk|temperatuur|pols|wond|katheter|infuus)\b/i,
category: IntentCategory.PATIENT_CARE, weight: 0.9 },
// SCHEDULING keywords
{ pattern: /\b(afspraak|agenda|planning|verzet|annuleer|plan)\b/i,
category: IntentCategory.SCHEDULING, weight: 0.95 },
// DIAGNOSTIC keywords
{ pattern: /\b(lab|bloed|urine|röntgen|echo|scan|onderzoek)\b/i,
category: IntentCategory.DIAGNOSTIC, weight: 0.9 },
// COMMUNICATION keywords
{ pattern: /\b(brief|consult|telefoon|contact|specialist)\b/i,
category: IntentCategory.COMMUNICATION, weight: 0.85 },
// ADMINISTRATIVE keywords
{ pattern: /\b(overdracht|mdo|bespreking|overleg)\b/i,
category: IntentCategory.ADMINISTRATIVE, weight: 0.9 },
// SEARCH keywords (should be last, lowest priority)
{ pattern: /\b(zoek|vind|wie|waar|wanneer|info|gegevens)\b/i,
category: IntentCategory.SEARCH, weight: 0.7 },
];
// Step 2: Intent patterns per category (smaller sets)
const INTENT_PATTERNS_BY_CATEGORY: Record<IntentCategory, Record<string, PatternConfig[]>> = {
[IntentCategory.DOCUMENTATION]: {
dagnotitie: [
{ pattern: /^dagnotitie\b/i, weight: 1.0 },
{ pattern: /^notitie\b/i, weight: 1.0 },
{ pattern: /^\w+\s+(medicatie|adl|gedrag)/i, weight: 0.9 },
],
rapportage_intake: [
{ pattern: /^intake\b/i, weight: 1.0 },
{ pattern: /\bintake\s+(verslag|rapportage)\b/i, weight: 1.0 },
],
vrije_notitie: [
{ pattern: /^vrije\s+notitie\b/i, weight: 1.0 },
{ pattern: /^schrijf\b/i, weight: 0.8 },
],
},
[IntentCategory.PATIENT_CARE]: {
medicatie_toedienen: [
{ pattern: /^medicatie\s+(geven|toedienen)/i, weight: 1.0 },
{ pattern: /^(geef|toedienen)\s+medicatie/i, weight: 1.0 },
{ pattern: /^\w+\s+medicatie\s+(gegeven|toegediend)/i, weight: 0.95 },
],
medicatie_voorschrijven: [
{ pattern: /^voorschrijf\s+medicatie/i, weight: 1.0 },
{ pattern: /^medicatie\s+voorschrijven/i, weight: 1.0 },
{ pattern: /^start\s+medicatie/i, weight: 0.95 },
],
meting_vitaal: [
{ pattern: /^(bloeddruk|temperatuur|pols|saturatie)\b/i, weight: 1.0 },
{ pattern: /^vitale\s+(functies|metingen)/i, weight: 1.0 },
{ pattern: /^\w+\s+(bloeddruk|temperatuur)/i, weight: 0.9 },
],
},
[IntentCategory.SCHEDULING]: {
agenda_query: [
{ pattern: /^afspraken?\b/i, weight: 1.0 },
{ pattern: /^agenda\b/i, weight: 1.0 },
{ pattern: /^wat\s+zijn\s+mijn\s+afspraken/i, weight: 1.0 },
],
create_appointment: [
{ pattern: /^maak\s+afspraak/i, weight: 1.0 },
{ pattern: /^plan\s+(intake|afspraak)/i, weight: 1.0 },
],
cancel_appointment: [
{ pattern: /^annuleer\s+afspraak/i, weight: 1.0 },
],
},
// ... other categories
};
// Two-phase classification
export function classifyIntentHierarchical(input: string): ClassificationResult {
const startTime = performance.now();
// PHASE 1: Detect category (fast, ~20 patterns)
let bestCategory: IntentCategory | null = null;
let categoryConfidence = 0;
for (const { pattern, category, weight } of CATEGORY_PATTERNS) {
if (pattern.test(input)) {
if (weight > categoryConfidence) {
bestCategory = category;
categoryConfidence = weight;
}
}
}
// If no category detected, use SEARCH as fallback
if (!bestCategory || categoryConfidence < 0.5) {
bestCategory = IntentCategory.SEARCH;
}
// PHASE 2: Detect intent within category (smaller search space)
const categoryIntents = INTENT_PATTERNS_BY_CATEGORY[bestCategory];
let bestIntent: SwiftIntent = 'unknown';
let intentConfidence = 0;
for (const [intent, patterns] of Object.entries(categoryIntents)) {
for (const { pattern, weight } of patterns) {
if (pattern.test(input)) {
if (weight > intentConfidence) {
bestIntent = intent as SwiftIntent;
intentConfidence = weight;
}
if (weight === 1.0) break; // Perfect match
}
}
if (intentConfidence === 1.0) break;
}
const processingTimeMs = performance.now() - startTime;
return {
intent: bestIntent,
confidence: Math.min(categoryConfidence, intentConfidence), // Take lowest
category: bestCategory,
processingTimeMs,
};
}
```
### Performance Impact
**Voor 34 intents met 235 patterns:**
| Metric | Flat Structure | Hierarchical | Improvement |
|--------|----------------|--------------|-------------|
| Avg patterns tested | 117 (~50%) | 10 + 12 = 22 | **5.3x faster** |
| Worst case | 235 (all) | 20 + 35 = 55 | **4.3x faster** |
| Best case | 1 | 1 + 1 = 2 | Similar |
| Estimated time | ~50ms | ~12ms | **4.2x faster** |
**Complexity:**
- Flat: O(n) where n = total patterns
- Hierarchical: O(c + i) where c = category patterns, i = intent patterns in category
- Typically: c ≈ 20, i ≈ 10-15 → O(30-35) vs O(235)
---
## Strategie 2: Keyword Index / Trie Structure
### Concept
Pre-index patterns by first keyword voor instant lookup.
```typescript
// Build index at startup
const KEYWORD_INDEX = new Map<string, IntentPattern[]>();
// Index building
for (const [intent, patterns] of Object.entries(INTENT_PATTERNS)) {
for (const pattern of patterns) {
const keywords = extractKeywords(pattern);
for (const keyword of keywords) {
if (!KEYWORD_INDEX.has(keyword)) {
KEYWORD_INDEX.set(keyword, []);
}
KEYWORD_INDEX.get(keyword)!.push({ intent, pattern });
}
}
}
// Fast lookup
function classifyWithIndex(input: string): ClassificationResult {
const firstWord = input.trim().split(/\s+/)[0].toLowerCase();
// O(1) lookup
const candidatePatterns = KEYWORD_INDEX.get(firstWord) || [];
// Test only relevant patterns (typically 3-10 instead of 235)
for (const { intent, pattern } of candidatePatterns) {
if (pattern.test(input)) {
return { intent, confidence: pattern.weight };
}
}
// Fallback: test all patterns (rare)
return classifyFull(input);
}
```
**Voordelen:**
- ✅ O(1) lookup voor common patterns
- ✅ Makkelijk te implementeren
- ✅ Backward compatible
**Nadelen:**
- ❌ Misses patterns zonder duidelijk keyword
- ❌ Extra memory overhead
- ❌ Requires maintenance of index
---
## Strategie 3: Intent Prioriteit (Analytics-Driven)
### Concept
Order intents op basis van gebruiksfrequentie.
```typescript
interface IntentMetrics {
intent: SwiftIntent;
frequency: number; // Times used
avgConfidence: number; // Average confidence
avgProcessingTime: number;
}
// Track usage
const INTENT_STATS = new Map<SwiftIntent, IntentMetrics>();
function trackIntentUsage(intent: SwiftIntent, confidence: number, time: number) {
const stats = INTENT_STATS.get(intent) || {
intent,
frequency: 0,
avgConfidence: 0,
avgProcessingTime: 0,
};
stats.frequency++;
stats.avgConfidence = (stats.avgConfidence * (stats.frequency - 1) + confidence) / stats.frequency;
stats.avgProcessingTime = (stats.avgProcessingTime * (stats.frequency - 1) + time) / stats.frequency;
INTENT_STATS.set(intent, stats);
}
// Periodically reorder patterns based on frequency
function optimizePatternOrder() {
const sorted = Array.from(INTENT_STATS.values())
.sort((a, b) => b.frequency - a.frequency);
// Rebuild INTENT_PATTERNS with high-frequency intents first
const optimized = {};
for (const { intent } of sorted) {
optimized[intent] = INTENT_PATTERNS[intent];
}
return optimized;
}
```
**Impact:**
Als 80% van queries 3 intents gebruikt (dagnotitie, agenda_query, zoeken):
- Average patterns tested: 15 instead of 117
- **7.8x speedup** for common cases
---
## Strategie 4: Compositional Intents
### Concept
Split intents in **base action** + **subject** + **modifiers**.
```typescript
// Instead of flat intents:
type OldIntent =
| 'medicatie_toedienen'
| 'medicatie_voorschrijven'
| 'medicatie_stop'
| 'medicatie_bijwerking'
| 'lab_aanvraag'
| 'lab_uitslag'
| 'rontgen_aanvraag'
// ... 30+ more
// Use compositional structure:
interface ComposedIntent {
action: Action; // toedienen, voorschrijven, aanvragen, etc.
subject: Subject; // medicatie, lab, röntgen, etc.
modifiers?: Modifier[]; // urgent, herhaling, etc.
}
type Action =
| 'create' | 'read' | 'update' | 'delete' // CRUD
| 'toedienen' | 'voorschrijven' | 'stop' // Medicatie-specific
| 'aanvragen' | 'bekijken' | 'afmelden' // Request-specific
;
type Subject =
| 'medicatie' | 'lab' | 'rontgen' | 'echo'
| 'afspraak' | 'notitie' | 'brief'
;
type Modifier =
| 'urgent' | 'spoed' | 'herhaling'
;
// Pattern matching
const ACTION_PATTERNS = {
toedienen: /\b(geef|toedien|gegeven)\b/i,
voorschrijven: /\b(voorschrijf|start|begin)\b/i,
stop: /\b(stop|afbouwen|be[eë]indig)\b/i,
aanvragen: /\b(vraag|aanvraag|aanvragen)\b/i,
};
const SUBJECT_PATTERNS = {
medicatie: /\b(medicatie|medicijn|tablet|pil)\b/i,
lab: /\b(lab|bloed|urine)\b/i,
rontgen: /\b(r[oö]ntgen|x-?ray)\b/i,
};
// Compose intent
function classifyCompositional(input: string): ComposedIntent {
const action = detectAction(input); // Fast, ~10 patterns
const subject = detectSubject(input); // Fast, ~10 patterns
const modifiers = detectModifiers(input); // Optional, ~5 patterns
return { action, subject, modifiers };
}
// Map to legacy intent
function toLegacyIntent(composed: ComposedIntent): SwiftIntent {
const key = `${composed.subject}_${composed.action}`;
const mapping = {
'medicatie_toedienen': 'medicatie_toedienen',
'medicatie_voorschrijven': 'medicatie_voorschrijven',
'lab_aanvragen': 'lab_aanvraag',
// ... etc
};
return mapping[key] || 'unknown';
}
```
**Voordelen:**
- ✅ Veel kleiner pattern set (~25 vs 235)
- ✅ Makkelijker om nieuwe combinaties toe te voegen
- ✅ Natuurlijker voor AI reasoning
**Nadelen:**
- ❌ Requires refactoring
- ❌ Less precise than specific patterns
- ❌ May need disambiguation more often
---
## Strategie 5: Smarter AI Routing (Hybrid Approach)
### Concept
Use **AI for categorization** (fast, cheap) then **local patterns** for specific intent.
```typescript
// Step 1: AI categorizes (very fast with Haiku)
const category = await categorizeWithAI(input); // ~100ms
// Step 2: Local patterns within category
const intent = classifyLocalInCategory(input, category); // ~5ms
// Total: ~105ms (but higher accuracy than pure local)
```
**AI System Prompt for Categorization:**
```typescript
const CATEGORIZATION_PROMPT = `Categoriseer de volgende input in één categorie:
Categorieën:
1. documentation - Notities, verslagen maken
2. patient_care - Medicatie, metingen, verzorging
3. scheduling - Agenda, afspraken
4. diagnostic - Lab, beeldvorming
5. communication - Brieven, consults
6. administrative - Overdracht, MDO
7. search - Zoeken, informatie opvragen
Antwoord met ALLEEN de categorie naam (lowercase).
Input: "${input}"
Categorie:`;
```
**Performance:**
- Categorization: ~100ms (AI call)
- Intent detection: ~5ms (local, small set)
- **Total: ~105ms** (vs ~50ms pure local, but more accurate)
**Trade-off:**
- Slower than pure local (2x)
- But handles ambiguous cases better
- Cheaper than full AI classification (smaller prompt)
---
## Strategie 6: Pattern Optimization
### Specific Optimizations
#### A. Pre-compiled Regex
```typescript
// ❌ BAD: Compile regex on every call
function classify(input: string) {
const pattern = new RegExp(`^${keyword}\\b`, 'i');
return pattern.test(input);
}
// ✅ GOOD: Pre-compile at module load
const PATTERNS = {
dagnotitie: /^dagnotitie\b/i,
zoeken: /^zoek\b/i,
};
function classify(input: string) {
return PATTERNS.dagnotitie.test(input);
}
```
**Impact:** 10-20% faster
#### B. Early Exit on Perfect Match
```typescript
for (const { pattern, weight } of patterns) {
if (pattern.test(input)) {
bestMatch = { pattern, weight };
// Early exit for perfect match
if (weight === 1.0) {
break; // Don't test remaining patterns
}
}
}
```
**Impact:** 30-50% faster for common exact matches
#### C. Pattern Ordering
```typescript
// Order patterns by likelihood (high weight first)
const patterns = [
{ pattern: /^exact\b/i, weight: 1.0 }, // Most likely
{ pattern: /^exact\s+\w+/i, weight: 0.95 }, // Second
{ pattern: /\bpartial\b/i, weight: 0.7 }, // Less likely
];
```
**Impact:** 20-40% faster on average
---
## 📊 Aanbevolen Implementatie Roadmap
### Fase 1: Quick Wins (Week 1)
**Implementeer nu (backward compatible):**
1.**Pattern Optimization**
- Pre-compile all regex
- Add early exit on perfect match
- Reorder patterns by weight (high first)
- **Effort:** 2 uur
- **Gain:** 30-40% sneller
2.**Intent Metrics Tracking**
- Add analytics to track intent frequency
- Log classification times
- **Effort:** 4 uur
- **Gain:** Data voor fase 2
### Fase 2: Hierarchie (Week 2-3)
**Implementeer categorieën:**
3.**Category-based Classification**
- Define 7 categories
- Build category patterns
- Restructure INTENT_PATTERNS by category
- Add two-phase classifier
- Keep old classifier for fallback
- **Effort:** 2 dagen
- **Gain:** 4-5x sneller, better scalability
4.**A/B Testing**
- Test old vs new classifier
- Compare accuracy & performance
- **Effort:** 1 dag
- **Gain:** Confidence in new approach
### Fase 3: Advanced (Maand 2)
**Optioneel, als nodig:**
5. ⚠️ **Keyword Index** (if performance still issue)
- Build keyword → pattern index
- **Effort:** 1 dag
- **Gain:** Extra 2x sneller
6. ⚠️ **Compositional Intents** (if too many intents)
- Refactor to action + subject
- **Effort:** 1 week
- **Gain:** Smaller pattern set, easier to extend
---
## 🎯 Concrete Voorstel voor Swift
### Voor Huidige Situatie (7 intents)
**Aanbeveling:** **Blijf bij huidige flat structure** + pattern optimizations
**Waarom:**
- Current performance is acceptable (<20ms)
- Complexity niet worth it voor 7 intents
- Quick wins genoeg (pre-compile, early exit)
**Implementeer WEL:**
- ✅ Pattern optimization (fase 1)
- ✅ Intent metrics tracking (voor later)
### Voor Toekomst (15+ intents)
**Aanbeveling:** **Overstap naar categorie-based hierarchie**
**Trigger points:**
- Wanneer >15 intents
- Wanneer classification >30ms
- Wanneer veel pattern conflicts
**Implementatie:**
1. Define 7 categories
2. Categorize existing intents
3. Build two-phase classifier
4. Keep old classifier als fallback
5. A/B test
### Code Structuur
```
lib/swift/
├── intent-classifier.ts # Current (keep for now)
├── intent-classifier-hierarchical.ts # New (implement in fase 2)
├── intent-classifier-ai.ts # Current AI fallback
├── intent-categories.ts # Category definitions
├── intent-patterns/ # Split patterns by category
│ ├── documentation.ts
│ ├── patient-care.ts
│ ├── scheduling.ts
│ ├── diagnostic.ts
│ ├── communication.ts
│ ├── administrative.ts
│ └── search.ts
└── types.ts
```
---
## 📈 Performance Benchmarks
### Target Metrics
| Metric | Current | Phase 1 Target | Phase 2 Target | Phase 3 Target |
|--------|---------|----------------|----------------|----------------|
| **Avg classification time** | 12ms | 8ms | 5ms | 3ms |
| **95th percentile** | 25ms | 15ms | 12ms | 8ms |
| **Max intents supported** | 10 | 15 | 40 | 100+ |
| **Memory usage** | 100KB | 120KB | 150KB | 200KB |
### Test Suite
```typescript
// __tests__/performance.test.ts
describe('Intent Classification Performance', () => {
it('should classify in <10ms (avg)', () => {
const inputs = generateTestInputs(1000);
const times = inputs.map(input => {
const start = performance.now();
classifyIntent(input);
return performance.now() - start;
});
const avg = times.reduce((a, b) => a + b) / times.length;
expect(avg).toBeLessThan(10);
});
it('should classify in <30ms (p95)', () => {
const times = [...]; // from above
const p95 = percentile(times, 95);
expect(p95).toBeLessThan(30);
});
it('should handle 40 intents efficiently', () => {
const classifierWith40Intents = buildClassifier(40);
const time = measureClassification(classifierWith40Intents);
expect(time).toBeLessThan(15);
});
});
```
---
## 🔧 Migration Guide
### Van Flat naar Hierarchical
**Step 1: Define Categories**
```typescript
// lib/swift/intent-categories.ts
export const INTENT_CATEGORY_MAP: Record<SwiftIntent, IntentCategory> = {
// Documentation
'dagnotitie': IntentCategory.DOCUMENTATION,
'rapportage_intake': IntentCategory.DOCUMENTATION,
// Patient Care
'meting_vitaal': IntentCategory.PATIENT_CARE,
'medicatie_toedienen': IntentCategory.PATIENT_CARE,
// Scheduling
'agenda_query': IntentCategory.SCHEDULING,
'create_appointment': IntentCategory.SCHEDULING,
// Search
'zoeken': IntentCategory.SEARCH,
// ... etc
};
```
**Step 2: Restructure Patterns**
```bash
# Create pattern files per category
mkdir lib/swift/intent-patterns
touch lib/swift/intent-patterns/documentation.ts
touch lib/swift/intent-patterns/patient-care.ts
# ... etc
```
```typescript
// lib/swift/intent-patterns/documentation.ts
export const DOCUMENTATION_PATTERNS = {
dagnotitie: [
{ pattern: /^dagnotitie\b/i, weight: 1.0 },
// ...
],
rapportage_intake: [
// ...
],
};
```
**Step 3: Build Hierarchical Classifier**
```typescript
// lib/swift/intent-classifier-hierarchical.ts
import { DOCUMENTATION_PATTERNS } from './intent-patterns/documentation';
import { PATIENT_CARE_PATTERNS } from './intent-patterns/patient-care';
// ... import all
export const PATTERNS_BY_CATEGORY = {
[IntentCategory.DOCUMENTATION]: DOCUMENTATION_PATTERNS,
[IntentCategory.PATIENT_CARE]: PATIENT_CARE_PATTERNS,
// ...
};
```
**Step 4: Feature Flag**
```typescript
// Use feature flag for gradual rollout
const USE_HIERARCHICAL_CLASSIFIER = process.env.NEXT_PUBLIC_USE_HIERARCHICAL === 'true';
export function classifyIntent(input: string) {
if (USE_HIERARCHICAL_CLASSIFIER) {
return classifyIntentHierarchical(input);
}
return classifyIntentFlat(input); // Old implementation
}
```
**Step 5: A/B Test & Monitor**
```typescript
// Log both results for comparison
const flatResult = classifyIntentFlat(input);
const hierarchicalResult = classifyIntentHierarchical(input);
analytics.track('intent_classification_comparison', {
input,
flatIntent: flatResult.intent,
flatConfidence: flatResult.confidence,
flatTime: flatResult.processingTimeMs,
hierarchicalIntent: hierarchicalResult.intent,
hierarchicalConfidence: hierarchicalResult.confidence,
hierarchicalTime: hierarchicalResult.processingTimeMs,
agreement: flatResult.intent === hierarchicalResult.intent,
});
// Use hierarchical if enabled
return USE_HIERARCHICAL_CLASSIFIER ? hierarchicalResult : flatResult;
```
---
## 💡 Samenvatting
### Aanbevolen Aanpak
**NU (0-7 intents):**
- ✅ Implement pattern optimizations (fase 1)
- ✅ Add metrics tracking
- ⏸️ Wait met hierarchie
**LATER (15+ intents):**
- ✅ Implement categorie-based hierarchie (fase 2)
- ✅ Optioneel: keyword index of compositional intents
**Grootste Impact:**
1. **Category hierarchie** → 4-5x sneller, schaalbaar tot 40+ intents
2. **Pattern optimization** → 30-40% sneller, makkelijk win
3. **Priority ordering** → 7-8x sneller voor common cases
**Effort vs Gain:**
| Strategie | Effort | Performance Gain | Scalability Gain | When to Implement |
|-----------|--------|------------------|------------------|-------------------|
| Pattern optimization | 2 uur | 30-40% | Low | ✅ Now |
| Category hierarchie | 2 dagen | 4-5x | High | When >15 intents |
| Keyword index | 1 dag | 2x extra | Medium | If still slow |
| Compositional | 1 week | 8-10x | Very High | When >40 intents |
| AI categorization | 3 dagen | 0x (slower) | High (accuracy) | If accuracy issues |
**Quick Decision Matrix:**
```
Current intents < 10?
→ Pattern optimization only
Current intents 10-20?
→ Pattern optimization + start planning hierarchie
Current intents 20-40?
→ Implement category hierarchie NOW
Current intents >40?
→ Consider compositional intents
```

View File

@@ -281,11 +281,11 @@ Epic doel: Swift artifact voor agenda flows.
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|----------|--------------|---------------------|--------|------------------|--------------|
| E4.S1 | AgendaBlock skeleton | Block met tabs/modes (list/create/cancel/reschedule) | To Do | E3.S1 | 3 |
| E4.S2 | List view | Lijst met afspraken + empty state | To Do | E4.S1 | 3 |
| E4.S3 | Create form | Prefill + validatie + submit | To Do | E4.S1 | 5 |
| 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.S1 | AgendaBlock skeleton | Block met tabs/modes (list/create/cancel/reschedule) | Done | E3.S1 | 3 |
| E4.S2 | List view | Lijst met afspraken + empty state | Done | E4.S1 | 3 |
| E4.S3 | Create form | Prefill + validatie + submit | Done | E4.S1 | 5 |
| E4.S4 | Cancel view | Disambiguation + confirm flow | Done | E4.S1 | 3 |
| E4.S5 | Reschedule view | Edit form met nieuwe tijd | Done | E4.S1 | 3 |
**Technical notes:**

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,581 @@
# 🧩 Functioneel Ontwerp (FO) Swift Agenda & Afspraken
**Projectnaam:** Swift - Agenda & Afspraken Module
**Versie:** v1.0
**Datum:** 27-12-2024
**Auteur:** Colin Lit
---
## 1. Doel en relatie met het PRD
🎯 **Doel van dit document:**
Dit Functioneel Ontwerp beschrijft **hoe** de agenda- en afsprakenfunctionaliteit binnen Swift werkt. Swift is een conversational medical scribe interface waarin gebruikers via natuurlijke taal (Nederlands) afspraken kunnen opvragen, aanmaken, wijzigen en annuleren. Dit FO beschrijft de gebruikerservaring, UI-interacties en AI-functionaliteit.
📘 **Relatie met andere documenten:**
- **PRD:** Ephemeral UI visie (`nextgen-epd-prd-ephemeral-ui-epd.md`) - Conversational interface voor EPD
- **Swift FO v3.0:** `fo-swift-medical-scribe-v3.md` - Basis conversational interface architectuur
- **Klassieke Agenda:** `/app/epd/agenda` - Bestaande visuele kalender (blijft bestaan voor complexe planning)
- **Bouwplan:** `bouwplan-swift-standalone-module.md` - Development roadmap
**Kernprincipe:**
> Gebruikers kunnen via natuurlijke taal (chat of spraak) snel afspraken beheren zonder door menu's te klikken. Voor visueel overzicht en complexe planning blijft de klassieke kalender beschikbaar. Swift is de **snelle, hands-free** interface; klassieke agenda is de **visuele planner**.
**Toegevoegde waarde:**
| Aspect | Klassieke Agenda | Swift Agenda |
|--------|------------------|--------------|
| **Gebruik** | Visuele weekplanning | Quick actions, queries |
| **Input** | Klikken, formulieren | Natuurlijke taal, spraak |
| **Snelheid** | ~30-60 sec voor nieuwe afspraak | ~10-15 sec via chat/voice |
| **Ideaal voor** | Weekplanning, drag-drop | Tijdens telefoongesprek, hands-free |
---
## 2. Overzicht van de belangrijkste onderdelen
🎯 **Doel:** Overzicht van de functionaliteit binnen de Swift Agenda module.
### Hoofdonderdelen
1. **Agenda Queries** - Afspraken opvragen ("afspraken vandaag", "wat is volgende afspraak")
2. **Quick Create** - Snel afspraak maken ("maak afspraak jan morgen 14:00")
3. **Cancel Flow** - Afspraak annuleren ("annuleer afspraak jan")
4. **Reschedule Flow** - Afspraak verzetten ("verzet 14:00 naar 15:00")
5. **AgendaBlock** - UI component toont afspraken lijst en formulieren
6. **Intent Detection** - AI herkent wat gebruiker wil doen
### Artifact: AgendaBlock
Het **AgendaBlock** is het centrale UI-component met 4 modes:
| Mode | Functie | Trigger |
|------|---------|---------|
| **List View** | Toont chronologische lijst afspraken | "afspraken vandaag" |
| **Create Form** | Formulier voor nieuwe afspraak | "maak afspraak jan" |
| **Cancel View** | Confirmation dialog | "annuleer afspraak" |
| **Reschedule Form** | Datum/tijd aanpassing | "verzet afspraak" |
---
## 3. User Stories
🎯 **Doel:** Beschrijven wat gebruikers moeten kunnen doen vanuit hun perspectief.
### Primaire User Stories (P1)
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|----|-----|--------------|------------------|------|
| **US-24** | Verpleegkundige | Snel overzicht afspraken vandaag | "afspraken vandaag" → lijst in AgendaBlock, <3 sec | 🔴 P1 |
| **US-25** | Verpleegkundige | Check volgende afspraak tijdens werk | "wat is mijn volgende afspraak?" → directe info | 🔴 P1 |
| **US-27** | Verpleegkundige | Snelle afspraak tijdens telefoongesprek | "maak afspraak jan morgen 14:00" → prefilled form, <15 sec | 🔴 P1 |
| **US-28** | Verpleegkundige | Context-aware planning | "maak afspraak met deze patiënt" → gebruikt actieve patiënt | 🔴 P1 |
| **US-29** | Verpleegkundige | Voice input tijdens consult | Hands-free afspraak maken via spraak | 🔴 P1 |
| **US-30** | Verpleegkundige | Annuleren via chat | "annuleer afspraak jan" → confirmation → done | 🔴 P1 |
| **US-31** | Verpleegkundige | Snel verzetten | "verzet 14:00 naar 15:00" → tijd update | 🔴 P1 |
### Secundaire User Stories (P2)
| ID | Rol | Doel / Actie | Verwachte waarde | Prio |
|----|-----|--------------|------------------|------|
| **US-26** | Verpleegkundige | Weekoverzicht bekijken | "agenda deze week" → gefilterde lijst | 🟡 P2 |
| **US-32** | Verpleegkundige | Disambiguation bij meerdere matches | Systeem vraagt "Welke Jan?" → lijst opties | 🟡 P2 |
---
## 4. Functionele werking per onderdeel
🎯 **Doel:** Per hoofdonderdeel beschrijven wat de gebruiker kan doen en wat het systeem doet.
### 4.1 Agenda Query (Afspraken opvragen)
**Wat doet de gebruiker:**
- Typt of spreekt: "afspraken vandaag", "wat is mijn volgende afspraak", "agenda morgen"
**Wat doet het systeem:**
1. **Intent detection:** Herkent dat gebruiker afspraken wil opvragen
2. **Datum parsing:** Vertaalt "vandaag", "morgen", "deze week" naar datumbereik
3. **Data ophalen:** Haalt afspraken op uit database
4. **AI response:** Chat toont samenvatting: "Je hebt vandaag 3 afspraken..."
5. **AgendaBlock opent:** Rechts verschijnt lijst met afspraken
**AgendaBlock List View bevat:**
- Header met datumbereik ("Afspraken Vandaag - 27 december")
- Per afspraak: tijd, patiënt (klikbaar), type badge, locatie
- Actions per afspraak: [Details] [Annuleren]
- Footer: Link naar volledige klassieke agenda
**States:**
- **Loading:** Spinner tijdens data fetch
- **Lijst met afspraken:** Chronologisch geordend
- **Empty state:** "Geen afspraken gevonden voor [datum]" + knop "Maak nieuwe afspraak"
- **Error:** "Fout bij ophalen afspraken" + link naar klassieke agenda
**Voorbeeld interactie:**
```
User: "afspraken vandaag"
AI: "Je hebt vandaag 3 afspraken:
- 09:00 Intake Jan de Vries
- 11:30 Behandeling Marie Jansen
- 14:00 Vervolggesprek Piet Bakker"
[AgendaBlock opens rechts met lijst van 3 afspraken]
```
---
### 4.2 Quick Create (Afspraak maken)
**Wat doet de gebruiker:**
- Typt: "maak afspraak jan morgen 14:00"
- Of spreekt via voice input (spatie-knop)
**Wat doet het systeem:**
1. **Intent detection:** Herkent 'create_appointment' intent
2. **Entity extraction:**
- Patient: "jan" (fuzzy search in database)
- Datum: "morgen" → parses naar 28-12-2024
- Tijd: "14:00"
- Type: default "behandeling" (kan gespecificeerd worden: "maak intake...")
3. **AI response:** "Ik maak een afspraak voor Jan de Vries op 28 december om 14:00."
4. **AgendaBlock opent:** Create form met pre-filled velden
5. **Gebruiker bevestigt of past aan**
6. **Opslaan:** Server action → database → toast "Afspraak aangemaakt!"
**AgendaBlock Create Form bevat:**
- **Patiënt:** Autocomplete dropdown (pre-filled "Jan de Vries")
- **Datum:** Date picker (pre-filled: 28-12-2024)
- **Tijd:** Time picker (pre-filled: 14:00)
- **Type:** Radio buttons (Intake, Behandeling, Vervolg, Telefonisch, Crisis, etc.)
- **Locatie:** Radio buttons (Praktijk, Online, Thuis)
- **Notities:** Optionele textarea
- **Conflict warning:** "⚠️ Je hebt al een afspraak om 14:00 met Marie" (indien van toepassing)
- **Actions:** [Annuleren] [✓ Afspraak maken]
**Form validatie:**
- Patiënt is verplicht
- Datum kan niet in het verleden
- Tijd moet binnen 07:00-20:00
**Voorbeeld interactie (voice):**
```
User: [Drukt spatie] "maak intake met Jan de Vries morgen 14:00"
[Deepgram transcribeert live]
AI: "Ik maak een intake-afspraak voor Jan de Vries op 28 december om 14:00."
[AgendaBlock create form opent met prefill]
User: [Klikt "Afspraak maken"]
Toast: "✓ Afspraak aangemaakt!"
Chat: "Afspraak ingepland voor Jan de Vries op 28 december om 14:00."
```
**Edge cases:**
- **Patiënt niet gevonden:** "Ik vond geen patiënt met de naam 'jan'. Bedoel je Jan de Vries of Jan Bakker?" (disambiguation)
- **Meerdere Jan's:** Toont lijst met opties in AgendaBlock
- **Tijd onduidelijk:** "Hoe laat wil je de afspraak plannen?"
- **Incomplete info:** "maak afspraak" → vraagt eerst om patiënt, dan datum/tijd
---
### 4.3 Cancel Flow (Afspraak annuleren)
**Wat doet de gebruiker:**
- Typt: "annuleer afspraak jan" of "annuleer de 14:00 afspraak"
**Wat doet het systeem:**
1. **Intent detection:** Herkent 'cancel_appointment'
2. **Search matching appointments:**
- Op patiëntnaam: zoekt "jan"
- Op tijd: zoekt afspraak om 14:00 vandaag
3. **Disambiguation (indien meerdere):**
- Toont lijst van matching afspraken in AgendaBlock
- Gebruiker selecteert welke
4. **Confirmation dialog:**
- Toont details van geselecteerde afspraak
- Waarschuwing: "Deze actie kan niet ongedaan worden gemaakt"
5. **Bevestigen:** Status → 'cancelled', toast + chat confirmation
**AgendaBlock Cancel View:**
**Single Match:**
```
┌─────────────────────────────────────────┐
│ ❌ Afspraak Annuleren [×] │
├─────────────────────────────────────────┤
│ │
│ Wil je deze afspraak annuleren? │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ 28-12-2024 14:00 - 15:00 │ │
│ │ Jan de Vries - Intake │ │
│ │ Praktijk │ │
│ └─────────────────────────────────────┘ │
│ │
│ ⚠️ Actie kan niet ongedaan gemaakt │
│ │
├─────────────────────────────────────────┤
│ [Terug] [✓ Annuleren] │
└─────────────────────────────────────────┘
```
**Multiple Matches (Disambiguation):**
```
┌─────────────────────────────────────────┐
│ ❌ Afspraak Annuleren [×] │
├─────────────────────────────────────────┤
│ Welke afspraak wil je annuleren? │
│ │
│ ○ 28-12 09:00 - Jan de Vries (Intake) │
│ ○ 28-12 14:00 - Jan de Vries (Vervolg) │
│ ○ 03-01 11:00 - Jan de Vries (Behndl) │
│ │
├─────────────────────────────────────────┤
│ [Annuleren] [Volgende →] │
└─────────────────────────────────────────┘
```
**Voorbeeld interactie:**
```
User: "annuleer afspraak jan"
[Systeem vindt 3 afspraken met "jan"]
AI: "Je hebt 3 afspraken met Jan. Welke wil je annuleren?"
[AgendaBlock toont disambiguation list]
User: [Selecteert 14:00 afspraak]
[Confirmation dialog]
User: [Klikt "Annuleren"]
Toast: "Afspraak geannuleerd"
Chat: "Afspraak met Jan de Vries op 28 december om 14:00 is geannuleerd."
```
---
### 4.4 Reschedule Flow (Afspraak verzetten)
**Wat doet de gebruiker:**
- Typt: "verzet 14:00 naar 15:00" of "verzet jan naar dinsdag"
**Wat doet het systeem:**
1. **Intent detection:** Herkent 'reschedule_appointment'
2. **Parse old & new time:**
- Oude afspraak: "14:00" vandaag
- Nieuwe tijd: "15:00"
3. **Find appointment:** Zoekt matching afspraak
4. **AgendaBlock opent:** Edit form
5. **Conflict check:** Controleert of nieuwe tijd vrij is
6. **Bevestigen:** Update afspraak → toast + chat
**AgendaBlock Reschedule Form:**
```
┌─────────────────────────────────────────┐
│ 🔄 Afspraak Verzetten [×] │
├─────────────────────────────────────────┤
│ │
│ Afspraak │
│ Jan de Vries - Intake │
│ │
│ Huidige tijd │
│ 28-12-2024 14:00 - 15:00 │
│ (strikethrough) │
│ │
│ Nieuwe datum/tijd * │
│ [28-12-2024 ▼] [15:00 ▼] │
│ │
│ ✅ Geen conflicten gevonden │
│ │
├─────────────────────────────────────────┤
│ [Annuleren] [✓ Verzetten] │
└─────────────────────────────────────────┘
```
**Voorbeeld interactie:**
```
User: "verzet de 14:00 naar 15:00"
AI: "Ik verzet je afspraak van 14:00 met Jan naar 15:00."
[AgendaBlock reschedule form opent]
User: [Bevestigt of past aan]
User: [Klikt "Verzetten"]
Toast: "Afspraak verzet naar 15:00"
Chat: "Afspraak verzet naar 15:00."
```
---
### 4.5 AgendaBlock States & Lifecycle
**Artifact Lifecycle:**
1. **Closed (default):** Geen artifact zichtbaar
2. **Opening:** Slide-in animation (200ms from right)
3. **Active:** Gebruiker kan interacteren
4. **Submitting:** Form disabled, spinner op submit button
5. **Success:** Toast + chat confirmation → artifact sluit (of blijft voor volgende)
6. **Error:** Error message in artifact, re-enable form
**Max artifacts:** 3 tegelijk (tabs bovenaan bij meerdere)
- Bij 4e artifact: oudste sluit automatisch
**Keyboard shortcuts:**
- `⌘K` / `Ctrl+K` - Focus chat input
- `Escape` - Sluit artifact
- `Enter` - Submit form (in form fields)
---
## 5. UI-overzicht (visuele structuur)
🎯 **Doel:** Inzicht geven in de globale schermopbouw.
### Split-Screen Layout (Command Center)
```
┌───────────────────────────────────────────────────────────────┐
│ Context Bar: 🕐 Ochtend | 8 ptn Jan de Vries ▼ 👤 SV │
├─────────────────────────────┬─────────────────────────────────┤
│ │ │
│ CHAT PANEL (40%) │ ARTIFACT AREA (60%) │
│ │ │
│ 👤 "afspraken vandaag" │ ┌───────────────────────────┐ │
│ │ │ 📅 Afspraken Vandaag │ │
│ 🤖 Je hebt vandaag 3 │ │ │ │
│ afspraken: │ │ 09:00 - Intake │ │
│ - 09:00 Intake Jan │ │ Jan de Vries │ │
│ - 11:30 Behandeling │ │ 📍 Praktijk │ │
│ - 14:00 Vervolg │ │ [Details] [Annuleren] │ │
│ │ │ │ │
│ 👤 "maak afspraak jan │ │ 11:30 - Behandeling │ │
│ morgen 14:00" │ │ Marie Jansen │ │
│ │ │ 🌐 Online │ │
│ 🤖 Ik maak een afspraak │ │ [Details] [Annuleren] │ │
│ voor Jan de Vries... │ │ │ │
│ │ │ 14:00 - Vervolg │ │
│ [AgendaBlock opent →] │ │ Piet Bakker │ │
│ │ │ 📍 Praktijk │ │
│ │ │ [Details] [Annuleren] │ │
│ │ │ │ │
│ │ │ [📅 Open volledige │ │
│ │ │ agenda →] │ │
│ │ └───────────────────────────┘ │
│ │ │
├─────────────────────────────┤ │
│ 💬 Typ of spreek... 🎤 │ │
└─────────────────────────────┴─────────────────────────────────┘
```
### AgendaBlock Modes (UI varianten)
**Mode 1: List View**
- Header: Datum range + close button
- Body: Scrollable lijst van appointment cards
- Footer: Link naar klassieke agenda
**Mode 2: Create Form**
- Header: "Nieuwe Afspraak" + close button
- Body: Form velden (patient, datum, tijd, type, locatie, notities)
- Footer: [Annuleren] [✓ Afspraak maken]
**Mode 3: Cancel View**
- Header: "Afspraak Annuleren" + close button
- Body: Appointment details + warning message
- Footer: [Terug] [✓ Annuleren]
**Mode 4: Reschedule Form**
- Header: "Afspraak Verzetten" + close button
- Body: Huidige tijd (readonly) + nieuwe tijd (editable)
- Footer: [Annuleren] [✓ Verzetten]
### Design Tokens
**Colors:**
- Primary: Teal-700 (#0F766E)
- User message: Amber-50 bg, amber-200 border
- AI message: Slate-100 bg, slate-300 border
- Appointment types: Blauw (intake), groen (behandeling), rood (crisis)
**Spacing:**
- Context bar: h-12 (48px)
- Chat/artifact gap: 16px
- Card spacing: space-y-4
**Typography:**
- Chat messages: text-sm
- Headers: text-base font-medium
---
## 6. Interacties met AI (functionele beschrijving)
🎯 **Doel:** Uitleggen waar AI in de flow voorkomt en wat de gebruiker ziet.
### AI-functies
| Locatie | AI-actie | Trigger | Input | Output |
|---------|----------|---------|-------|--------|
| **Chat Input** | Intent detection | User message | "afspraken vandaag" | Intent: 'agenda_query', confidence: 1.0 |
| **Chat Input** | Entity extraction | User message | "maak afspraak jan morgen 14:00" | Patient: "jan", date: tomorrow, time: "14:00" |
| **Chat Input** | Verduidelijkingsvraag | Incomplete info | "maak afspraak" | "Met welke patiënt wil je afspreken?" |
| **Chat Panel** | Streaming response | Intent detected | — | "Je hebt vandaag 3 afspraken..." (typed effect) |
| **Patient Search** | Fuzzy matching | "jan" input | Database query | Matches: "Jan de Vries", "Jan Bakker" |
| **Date Parser** | Natural language parsing | "morgen", "volgende week dinsdag" | Date string | ISO date: 2024-12-28 |
### AI Intent Detection (Two-Tier)
**Tier 1: Local Pattern Matching (<50ms)**
- Fast regex-based matching
- Client-side execution
- Confidence >= 0.8 → direct gebruiken
Voorbeelden:
- "afspraken vandaag" → Pattern: `/^afspraken?\b/i` → Match! (confidence: 1.0)
- "maak afspraak" → Pattern: `/^maak\s+afspraak/i` → Match! (confidence: 1.0)
**Tier 2: AI Fallback (Claude Haiku) (~400ms)**
- Voor onduidelijke/complexe input
- Server-side execution
- Triggered als local confidence <0.8
Voorbeelden:
- "ik wil graag een gesprek plannen" → AI: intent: 'create_appointment', confidence: 0.75
- "verzet hem naar volgende week" → AI: intent: 'reschedule', confidence: 0.7 (patient onduidelijk)
**Confidence Thresholds:**
| Confidence | Actie | Voorbeeld |
|------------|-------|-----------|
| **>0.9** | Direct artifact openen | "afspraken vandaag" |
| **0.7-0.9** | Artifact + bevestigingsvraag | "maak afspraak jan" (tijd ontbreekt) |
| **0.5-0.7** | Verduidelijkingsvraag in chat | "maak afspraak" |
| **<0.5** | Fallback: "Ik begrijp het niet" | Gibberish input |
### Voice Input (Deepgram)
**Functionaliteit:**
- Live transcription tijdens spreken
- Pause detection (1.5s stilte) → auto-submit
- Nederlands language model
**User experience:**
1. User drukt spatie (of klikt mic icon)
2. Mic wordt rood 🔴, waveform animatie
3. Live transcript verschijnt in input field
4. Na 1.5s stilte: auto-submit
5. Intent detection + artifact opening
**Voorbeeld:**
```
User: [Drukt spatie]
→ Mic: 🔴 LIVE
→ User spreekt: "maak afspraak met jan morgen om twee uur"
→ Transcript: "maak afspraak met jan morgen om twee uur"
→ [1.5s pause]
→ Auto-submit
→ AI parses: "twee uur" → "14:00"
→ AgendaBlock opent
```
---
## 7. Gebruikersrollen en rechten
🎯 **Doel:** Beschrijven welke rollen toegang hebben tot agenda functionaliteit.
| Rol | Toegang | Beperkingen |
|-----|---------|-------------|
| **Verpleegkundige** | Eigen afspraken maken/wijzigen/annuleren | Alleen eigen practitioner_id |
| **Behandelaar** | Eigen afspraken + caseload patiënten | Alleen eigen + team afspraken |
| **Manager** | Lezen alle afspraken | Geen create/update/delete |
| **Demo-user** | Volledige functionaliteit met fictieve data | Alleen lezen |
**Permissies:**
| Actie | Verpleegkundige | Behandelaar | Manager |
|-------|-----------------|-------------|---------|
| **Agenda query** (eigen) | ✅ | ✅ | ✅ |
| **Agenda query** (team) | ❌ | ✅ | ✅ |
| **Create appointment** | ✅ | ✅ | ❌ |
| **Cancel appointment** (eigen) | ✅ | ✅ | ❌ |
| **Reschedule** (eigen) | ✅ | ✅ | ❌ |
**Database-level (RLS):**
- Filter op `practitioner_id = current_user_id`
- Voor managers: read-only view
---
## 8. Bijlagen & Referenties
🎯 **Doel:** Linken naar overige documenten.
### Gerelateerde Documenten
**Swift Documentatie:**
- **Swift FO v3.0:** `docs/swift/fo-swift-medical-scribe-v3.md` - Basis conversational interface
- **Swift Bouwplan:** `docs/swift/bouwplan-swift-standalone-module.md` - Development roadmap
- **Developer Guide Intent System:** `docs/swift/developer-guide-intent-system.md` - Technische details intent detection
- **Scalability Architecture:** `docs/swift/architecture-intent-scalability.md` - Schaalbaarheid optimalisaties
**Agenda Implementatie:**
- **Klassieke Agenda:** `/app/epd/agenda` - Bestaande visuele kalender
- **Agenda Actions:** `/app/epd/agenda/actions.ts` - Server actions (wordt hergebruikt)
- **Encounters Schema:** Database schema voor afspraken
**Design & UX:**
- **PRD Ephemeral UI:** Conversational interface visie
- **UX Research:** Chat + artifacts pattern analyse
### Technische Specs (voor developers)
- **Gedetailleerd FO Agenda Planning:** `docs/swift/fo-swift-agenda-planning.md` - Uitgebreide technische specificatie
- **Intent Classifier:** `lib/swift/intent-classifier.ts` - Local pattern matching
- **AI Classifier:** `lib/swift/intent-classifier-ai.ts` - Claude Haiku fallback
- **Types:** `lib/swift/types.ts` - TypeScript type definitions
### Out of Scope (Toekomstige Versies)
**Niet in MVP:**
- Full calendar grid view (blijft in klassieke agenda)
- Drag-and-drop rescheduling
- Recurring appointments ("elke dinsdag om 10:00")
- Beschikbaarheidscheck ("wanneer ben ik vrij")
- Conflict detection & resolution
- Multi-practitioner scheduling
- SMS/email notificaties
---
## Wijzigingslog
| Versie | Datum | Wijzigingen | Auteur |
|--------|-------|-------------|--------|
| v1.0 | 27-12-2024 | Initial version - Agenda & afspraken functionaliteit in Swift volgens FO template | Colin Lit |
---
**Goedkeuring:**
- [ ] Product Owner: _________________________
- [ ] Lead Developer: _________________________
- [ ] UX Designer: _________________________
**Status:** Draft - Ter review
**Volgende stappen:**
1. Review met stakeholders
2. UX wireframes maken op basis van dit FO
3. Technical implementation planning
4. User testing scenario's opstellen

File diff suppressed because it is too large Load Diff