Epic 5 compleet: Chat orchestration voor Swift Agenda Planning. Alle agenda intents worden nu correct gerouteerd naar AgendaBlock met user-friendly error handling en fallback opties. E5.S1 - Action Routing (2 SP) - routeIntentToArtifact() functie in action-parser - Maps agenda intents naar juiste AgendaBlock mode - Confidence threshold enforcement (< 0.7 → fallback) - Required entity validation (patient voor create, identifier voor reschedule) - Command-input gebruikt nieuwe routing ipv legacy openBlock - Migratie naar modern artifact systeem (openArtifact) E5.S2 - Chat Prompt Update (2 SP) - 4 agenda intents toegevoegd aan Swift chat system prompt - agenda_query: afspraken opvragen op datumrange - create_appointment: nieuwe afspraak maken (required: patient, datetime) - cancel_appointment: afspraak annuleren (disambiguation support) - reschedule_appointment: afspraak verzetten (required: identifier) - Entity extraction rules gedocumenteerd (dateRange, datetime, identifier) - 4 complete voorbeelden met JSON action format - Clarification questions voor incomplete data - Prompt size: ~325 → ~525 regels (+60%) E5.S3 - Error States (2 SP) - AgendaErrorState component voor full-page errors - AgendaErrorAlert component voor inline form errors - getUserFriendlyMessage() vertaalt technical → user-friendly Dutch - Auto-redirect bij auth errors (401 → /login) - Fallback link naar /epd/agenda in alle error states - Context-aware messaging (query/create/cancel/reschedule) - Retry functionaliteit voor recoverable errors - Dev-only technical details collapsible Error message mapping: - 401 → "Je sessie is verlopen. Log opnieuw in." + auto-redirect - 403 → "Je hebt geen toegang tot deze afspraak." - 404 → "De gevraagde afspraak kon niet worden gevonden." - 500 → "Er ging iets mis op de server. Probeer het opnieuw." - Network → "Geen internetverbinding. Controleer je netwerkverbinding." - Timeout → "De aanvraag duurde te lang. Probeer het opnieuw." Components updated: - command-input: gebruikt routeIntentToArtifact + openArtifact - agenda-create-form: gebruikt AgendaErrorAlert met fallback link - chat/route: uitgebreide system prompt met agenda sectie Nieuwe files: - lib/swift/action-parser.ts: routeIntentToArtifact() functie - components/swift/artifacts/blocks/agenda-error-state.tsx (225 regels) - docs/swift/implementation-e5-s1-action-routing.md - docs/swift/implementation-e5-s2-chat-prompt.md - docs/swift/implementation-e5-s3-error-states.md Documentatie: - Bouwplan bijgewerkt: Epic 5 → Done - 3 implementation docs met API specs en testing scenarios - Error handling best practices gedocumenteerd Progress: 48 SP / 51 SP (94%) - Epic 6 (QA) remaining 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
8.9 KiB
Epic 5.S2 Implementation: Chat Prompt Update
Story: E5.S2 - Chat prompt update Date: 2025-12-27 Status: ✅ Complete Story Points: 2 SP
🎯 Objective
Update the Swift chat API system prompt to include agenda intent descriptions, entity extraction rules, and action format examples.
📝 Implementation Summary
File Modified
app/api/swift/chat/route.ts - Function: buildMedicalScribePrompt()
Changes Made
1. Added Agenda Intents to P2 Section
Added 4 new agenda intents to the P2 (belangrijk, middenfrequent) section:
agenda_query - Afspraken opvragen
- Triggers: "afspraken vandaag", "agenda morgen", "wat is mijn volgende afspraak", "afspraken deze week"
- Entities:
dateRange(vandaag/morgen/deze week/volgende week) - Action: Toon lijst van afspraken in AgendaBlock
create_appointment - Nieuwe afspraak maken
- Triggers: "maak afspraak [patient]", "plan intake [patient]", "afspraak maken met [patient] [datum] [tijd]"
- Entities:
patientName,datetime,appointmentType,location - Required:
patientNameORpatientId,datetime - Optional:
appointmentType(default: behandeling),location(default: praktijk) - Action: Open create form met pre-fill
cancel_appointment - Afspraak annuleren
- Triggers: "annuleer afspraak [patient]", "cancel [tijd]", "afspraak van [patient] annuleren"
- Entities:
identifier(patient naam/tijd combinatie voor matching) - Action: Toon confirmation dialog, bij meerdere matches: disambiguation
reschedule_appointment - Afspraak verzetten
- Triggers: "verzet afspraak [patient]", "verzet [oude tijd] naar [nieuwe tijd]", "[patient] naar [nieuwe datum]"
- Entities:
identifier,newDatetime - Required:
identifier - Action: Toon edit form met oude en nieuwe tijd
2. Extended Clarification Questions
Added agenda-specific clarification examples:
- "Voor welke datum wil je de afspraak maken?" (datum ontbreekt bij create_appointment)
- "Op welk tijdstip?" (tijd ontbreekt bij create_appointment)
- "Welke afspraak wil je verzetten?" (identifier onduidelijk bij reschedule/cancel)
3. Added Agenda Examples
Voorbeeld 5: Agenda query
{
"type": "action",
"intent": "agenda_query",
"entities": {
"dateRange": {
"start": "2025-12-27",
"end": "2025-12-27",
"label": "vandaag"
}
},
"confidence": 0.98,
"artifact": {
"type": "agenda_query",
"prefill": {
"dateRange": {...}
}
}
}
Voorbeeld 6: Afspraak maken (compleet)
{
"type": "action",
"intent": "create_appointment",
"entities": {
"patientName": "Jan",
"datetime": {
"date": "2025-12-28",
"time": "14:00"
},
"appointmentType": "behandeling",
"location": "praktijk"
},
"confidence": 0.95,
"artifact": {
"type": "create_appointment",
"prefill": {...}
}
}
Voorbeeld 7: Afspraak maken (incompleet) User: "Plan intake Marie" Response: "Voor welke datum en tijd wil je de intake voor Marie plannen?" (Geen JSON action omdat datetime ontbreekt)
Voorbeeld 8: Afspraak verzetten
{
"type": "action",
"intent": "reschedule_appointment",
"entities": {
"identifier": {
"type": "time",
"time": "14:00"
},
"newDatetime": {
"date": "2025-12-27",
"time": "15:00"
}
},
"confidence": 0.92,
"artifact": {
"type": "reschedule_appointment",
"prefill": {...}
}
}
🔑 Key Prompt Additions
Entity Structure for Agenda Intents
dateRange (agenda_query):
{
"start": "YYYY-MM-DD",
"end": "YYYY-MM-DD",
"label": "vandaag" | "morgen" | "deze week" | "volgende week"
}
datetime (create_appointment, reschedule_appointment):
{
"date": "YYYY-MM-DD",
"time": "HH:mm"
}
identifier (cancel_appointment, reschedule_appointment):
{
"type": "patient" | "time" | "both",
"patientName"?: "string",
"patientId"?: "uuid",
"time"?: "HH:mm",
"date"?: "YYYY-MM-DD",
"encounterId"?: "uuid"
}
appointmentType (create_appointment):
- intake
- behandeling (default)
- follow-up
- telefonisch
- huisbezoek
- online
- crisis
location (create_appointment):
- praktijk (default)
- online
- thuis
✅ Acceptance Criteria
| Criterion | Status | Notes |
|---|---|---|
| Agenda intents described in prompt | ✅ | 4 intents added to P2 section |
| Entity extraction rules documented | ✅ | All entities with types and defaults |
| Required vs optional entities specified | ✅ | Clear for each intent |
| Clarification question examples | ✅ | 3 new agenda-specific examples |
| Action format examples | ✅ | 4 complete examples with JSON |
| Confidence thresholds mentioned | ✅ | Inherited from base prompt (≥0.7) |
| TypeScript compilation | ✅ | 0 errors |
📊 Prompt Structure
The updated prompt now includes:
Je rol
├── Kernkwaliteiten
└── Tone of voice
Wat je DOET
├── 1. Intents herkennen
│ ├── P1 Intents (dagnotitie, zoeken, overdracht)
│ └── P2 Intents (rapportage, agenda_query, create_appointment,
│ cancel_appointment, reschedule_appointment) ✨ NEW
├── 2. Verduidelijkingsvragen stellen (+ agenda examples) ✨ UPDATED
├── 3. Action objects genereren
└── 4. Follow-up conversatie
Wat je NIET doet
Context die beschikbaar is
Voorbeelden
├── Voorbeeld 1-4 (bestaand)
├── Voorbeeld 5: Agenda query ✨ NEW
├── Voorbeeld 6: Afspraak maken (compleet) ✨ NEW
├── Voorbeeld 7: Afspraak maken (incompleet) ✨ NEW
└── Voorbeeld 8: Afspraak verzetten ✨ NEW
Error Handling
🧪 Testing Scenarios
Scenario 1: Agenda Query - High Confidence
Input: "afspraken vandaag" Expected Claude Response:
- Text: "Ik toon je de afspraken voor vandaag."
- JSON:
{ intent: "agenda_query", confidence: 0.98, entities: { dateRange: {...} } }
Scenario 2: Create Appointment - Complete
Input: "maak afspraak met Jan morgen 14:00" Expected Claude Response:
- Text: "Ik maak een afspraak voor Jan morgen om 14:00."
- JSON:
{ intent: "create_appointment", confidence: 0.95, entities: { patientName, datetime } }
Scenario 3: Create Appointment - Incomplete
Input: "plan intake Marie" Expected Claude Response:
- Text: "Voor welke datum en tijd wil je de intake voor Marie plannen?"
- JSON: None (confidence < 0.7 due to missing datetime)
Scenario 4: Reschedule - Time Based
Input: "verzet 14:00 naar 15:00" Expected Claude Response:
- Text: "Ik verzet de afspraak van 14:00 naar 15:00."
- JSON:
{ intent: "reschedule_appointment", confidence: 0.92, entities: { identifier, newDatetime } }
Scenario 5: Cancel - Patient Based
Input: "annuleer afspraak Jan" Expected Claude Response:
- Text: Confirmation or disambiguation if multiple Jans
- JSON:
{ intent: "cancel_appointment", entities: { identifier: { type: "patient", patientName: "Jan" } } }
🎯 Prompt Engineering Techniques Used
- Clear Intent Definitions: Each intent has triggers, entities, and actions clearly defined
- Required/Optional Distinction: Helps Claude decide when to ask clarification questions
- Confidence Guidance: Thresholds guide when to open artifacts vs ask questions
- Concrete Examples: 4 full examples with expected JSON structure
- Error Cases: Example 7 shows incomplete input handling
- Entity Templates: JSON structures show exact format expected
- Natural Language Triggers: Multiple trigger phrases per intent
💡 Design Decisions
Why P2 Instead of P1?
Agenda management is important but not as critical/frequent as dagnotitie (P1). Healthcare workers make notes constantly but schedule appointments less frequently.
Why Include Defaults?
appointmentType: "behandeling" (most common case)location: "praktijk" (most common location)
This reduces friction - users don't need to specify every detail.
Why identifier Instead of encounterId?
The identifier structure allows flexible matching:
- By patient name: "annuleer afspraak Jan"
- By time: "cancel 14:00"
- By combination: "verzet Jan's afspraak"
The backend (Epic 3 API + Epic 4 UI) handles the disambiguation.
Why Separate Examples for Complete/Incomplete?
Shows Claude two paths:
- Complete data → Generate action (Example 6)
- Incomplete data → Ask question (Example 7)
This demonstrates the confidence threshold logic clearly.
🚀 Next Steps
E5.S3 - Error states
- Add error handling for API failures
- Add fallback links to
/epd/agenda - Handle offline/network errors gracefully
📁 Files Modified
✅ app/api/swift/chat/route.ts (Updated system prompt)
✅ docs/swift/bouwplan-swift-agenda-planning.md (E5.S2 → Done)
✅ docs/swift/implementation-e5-s2-chat-prompt.md (New documentation)
Implementation Status: ✅ Complete Ready for: E5.S3 (Error states) Prompt Length: ~525 lines (was ~325 lines) - increased by ~60%