refactor(swift): update references from Medical Scribe to Swift Assistent
- Updated comments and documentation to reflect the new branding of the chat API and related components. - Renamed functions and variables to align with the Swift Assistent terminology. - Enhanced UI components with animations using Framer Motion for a smoother user experience. - Removed outdated architecture documentation related to the Medical Scribe system. This change is part of the transition to the Swift Assistent branding, ensuring consistency across the application.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Swift Chat API Route (v3.0)
|
||||
*
|
||||
* Streaming chat endpoint voor Swift Medical Scribe met Server-Sent Events (SSE).
|
||||
* Streaming chat endpoint voor Swift Assistent met Server-Sent Events (SSE).
|
||||
*
|
||||
* Epic: E3 (Chat API & Medical Scribe)
|
||||
* Epic: E3 (Chat API & Swift Assistent)
|
||||
* Story: E3.S1 (Chat API endpoint skeleton)
|
||||
*/
|
||||
|
||||
@@ -76,10 +76,10 @@ const RequestSchema = z.object({
|
||||
type RequestData = z.infer<typeof RequestSchema>;
|
||||
|
||||
/**
|
||||
* Build medical scribe system prompt (E3.S3)
|
||||
* Build Swift Assistent system prompt (E3.S3)
|
||||
* Full prompt with intent detection, entity extraction, and action generation
|
||||
*/
|
||||
function buildMedicalScribePrompt(context?: RequestData['context']): string {
|
||||
function buildSystemPrompt(context?: RequestData['context']): string {
|
||||
// Build context section
|
||||
const patientContext = context?.activePatient
|
||||
? `{
|
||||
@@ -92,7 +92,7 @@ function buildMedicalScribePrompt(context?: RequestData['context']): string {
|
||||
|
||||
const shiftContext = context?.shift ?? 'ochtend';
|
||||
|
||||
return `Je bent een medische assistent (medical scribe) voor Swift, een Nederlands EPD-systeem voor GGZ-instellingen.
|
||||
return `Je bent Swift Assistent, een medische assistent voor Swift EPD, een Nederlands EPD-systeem voor GGZ-instellingen.
|
||||
|
||||
## Je rol
|
||||
|
||||
@@ -524,8 +524,8 @@ export async function POST(request: NextRequest) {
|
||||
// 4. Prepare conversation history (limit to last N messages)
|
||||
const history = messages.slice(-MAX_HISTORY_MESSAGES);
|
||||
|
||||
// 5. Build medical scribe system prompt (E3.S3)
|
||||
const systemPrompt = buildMedicalScribePrompt(context);
|
||||
// 5. Build Swift Assistent system prompt (E3.S3)
|
||||
const systemPrompt = buildSystemPrompt(context);
|
||||
|
||||
// 6. Check for Claude API key
|
||||
const apiKey = process.env.ANTHROPIC_API_KEY;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AgendaListView } from './agenda-list-view';
|
||||
import { AgendaCreateForm } from './agenda-create-form';
|
||||
import { AgendaCancelView } from './agenda-cancel-view';
|
||||
import { AgendaRescheduleForm } from './agenda-reschedule-form';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
export interface AgendaBlockProps {
|
||||
mode: 'list' | 'create' | 'cancel' | 'reschedule';
|
||||
@@ -36,6 +37,7 @@ export function AgendaBlock({
|
||||
case 'list':
|
||||
return (
|
||||
<AgendaListView
|
||||
key="list"
|
||||
appointments={appointments}
|
||||
dateRange={dateRange}
|
||||
onClose={onClose}
|
||||
@@ -44,25 +46,43 @@ export function AgendaBlock({
|
||||
/>
|
||||
);
|
||||
case 'create':
|
||||
return <AgendaCreateForm prefillData={prefillData} onClose={onClose} />;
|
||||
return <AgendaCreateForm key="create" prefillData={prefillData} onClose={onClose} />;
|
||||
case 'cancel':
|
||||
return (
|
||||
<AgendaCancelView
|
||||
key="cancel"
|
||||
disambiguationOptions={disambiguationOptions}
|
||||
prefillData={prefillData}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
case 'reschedule':
|
||||
return <AgendaRescheduleForm prefillData={prefillData} onClose={onClose} />;
|
||||
return <AgendaRescheduleForm key="reschedule" prefillData={prefillData} onClose={onClose} />;
|
||||
default:
|
||||
return <div className="p-4 text-red-500">Unknown mode: {mode}</div>;
|
||||
return <div key="error" className="p-4 text-red-500">Unknown mode: {mode}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[600px] max-h-[80vh] overflow-y-auto bg-white border rounded shadow-sm">
|
||||
{renderContent()}
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20, scale: 0.98 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ opacity: 0, y: 10, scale: 0.98 }}
|
||||
transition={{ type: "spring", stiffness: 350, damping: 25 }}
|
||||
className="w-full max-w-[600px] max-h-[80vh] h-[600px] overflow-hidden bg-white/95 backdrop-blur-xl border border-black/5 rounded-2xl shadow-2xl flex flex-col"
|
||||
>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={mode}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: -20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="flex flex-col h-full overflow-hidden"
|
||||
>
|
||||
{renderContent()}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { cancelEncounter } from '@/app/epd/agenda/actions';
|
||||
import { CalendarEvent, APPOINTMENT_TYPES, AppointmentTypeCode } from '@/app/epd/agenda/types';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface AgendaCancelViewProps {
|
||||
disambiguationOptions?: CalendarEvent[];
|
||||
@@ -74,7 +75,7 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">Afspraak geannuleerd</h3>
|
||||
<p className="text-sm text-gray-500 mt-1 mb-6">De afspraak is succesvol verwijderd uit de agenda.</p>
|
||||
<Button onClick={onClose} variant="outline">Sluiten</Button>
|
||||
<Button onClick={onClose} variant="outline" className="border-black/5 bg-white/50 hover:bg-white">Sluiten</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -82,16 +83,21 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
// Disambiguation Mode
|
||||
if (!effectiveEncounter && disambiguationOptions && disambiguationOptions.length > 1) {
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-red-700">Afspraak annuleren</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-4 overflow-y-auto">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
<div className="flex-1 p-5 overflow-y-auto">
|
||||
<p className="text-sm text-gray-600 mb-4 font-medium">
|
||||
Er zijn meerdere afspraken gevonden. Welke wil je annuleren?
|
||||
</p>
|
||||
|
||||
@@ -103,11 +109,11 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
const timeStr = format(new Date(evt.start), 'HH:mm');
|
||||
|
||||
return (
|
||||
<div key={evt.id} className="flex items-center space-x-2 border rounded-lg p-3 hover:bg-gray-50 cursor-pointer">
|
||||
<div key={evt.id} className="flex items-center space-x-3 border border-black/5 rounded-xl p-4 hover:bg-white/50 cursor-pointer bg-white/40 transition-colors">
|
||||
<RadioGroupItem value={evt.id} id={evt.id} />
|
||||
<Label htmlFor={evt.id} className="flex-1 cursor-pointer">
|
||||
<div className="font-medium text-gray-900">{evt.title}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
<div className="font-semibold text-gray-900">{evt.title}</div>
|
||||
<div className="text-sm text-gray-500 mt-0.5">
|
||||
{dateStr} om {timeStr} • {encounter.type_display || APPOINTMENT_TYPES[typeCode]}
|
||||
</div>
|
||||
</Label>
|
||||
@@ -117,17 +123,17 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} className="flex-1">Annuleren</Button>
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">Annuleren</Button>
|
||||
<Button
|
||||
onClick={() => { /* State updates automatically via RadioGroup */ }}
|
||||
disabled={!selectedEncounterId}
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white"
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white shadow-md hover:shadow-lg h-10 rounded-lg"
|
||||
>
|
||||
Volgende
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,63 +146,70 @@ export function AgendaCancelView({ disambiguationOptions, prefillData, onClose }
|
||||
const endTimeStr = effectiveEncounter.end ? format(new Date(effectiveEncounter.end), 'HH:mm') : '';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-red-700">Weet je het zeker?</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-4">
|
||||
<div className="bg-red-50 border border-red-100 rounded-lg p-4 mb-6">
|
||||
<div className="flex-1 p-5">
|
||||
<div className="bg-red-50/70 border border-red-100/50 rounded-xl p-4 mb-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-red-600 mt-0.5" />
|
||||
<div className="text-sm text-red-800">
|
||||
<p className="font-medium">Deze actie kan niet ongedaan worden gemaakt.</p>
|
||||
<p className="mt-1 opacity-90">De afspraak wordt permanent uit de agenda verwijderd.</p>
|
||||
<div className="p-2 bg-red-100 rounded-full mt-0.5">
|
||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||
</div>
|
||||
<div className="text-sm text-red-900">
|
||||
<p className="font-semibold text-base">Deze actie kan niet ongedaan worden gemaakt.</p>
|
||||
<p className="mt-1 opacity-90 leading-relaxed">De afspraak met <span className="font-semibold">{effectiveEncounter.title}</span> wordt permanent uit de agenda verwijderd.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg p-4 bg-white shadow-sm">
|
||||
<h4 className="font-medium text-gray-900 mb-2">{effectiveEncounter.title}</h4>
|
||||
<div className="space-y-2 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="border border-black/5 rounded-xl p-5 bg-white/60 shadow-sm backdrop-blur-sm">
|
||||
<h4 className="font-semibold text-gray-900 mb-3 text-lg">{effectiveEncounter.title}</h4>
|
||||
<div className="space-y-3 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-3">
|
||||
<Calendar className="h-4 w-4 text-gray-400" />
|
||||
<span className="capitalize">{dateStr}</span>
|
||||
<span className="capitalize font-medium">{dateStr}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Clock className="h-4 w-4 text-gray-400" />
|
||||
<span>{timeStr} {endTimeStr && `- ${endTimeStr}`}</span>
|
||||
<span className="font-medium">{timeStr} {endTimeStr && `- ${endTimeStr}`}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-block w-20 text-gray-400">Type:</span>
|
||||
<span className="font-medium">{encounter.type_display || APPOINTMENT_TYPES[typeCode]}</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-block w-5 text-center text-gray-400">•</span>
|
||||
<span className="font-medium bg-gray-100 px-2 py-0.5 rounded text-gray-700">{encounter.type_display || APPOINTMENT_TYPES[typeCode]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mt-4 p-3 bg-red-100 text-red-700 text-sm rounded-md">
|
||||
<div className="mt-4 p-4 bg-red-50 text-red-700 text-sm rounded-xl border border-red-100">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} disabled={isSubmitting} className="flex-1">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" onClick={onClose} disabled={isSubmitting} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">
|
||||
Terug
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white"
|
||||
className="flex-1 bg-red-600 hover:bg-red-700 text-white shadow-md hover:shadow-lg h-10 rounded-lg"
|
||||
>
|
||||
{isSubmitting ? 'Annuleren...' : 'Ja, annuleer afspraak'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -165,17 +166,22 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, ease: 'easeOut' }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<h3 className="font-semibold text-lg text-teal-700">Nieuwe afspraak inplannen</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-gray-900">Nieuwe afspraak inplannen</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 text-gray-500 hover:text-gray-900 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Form Body */}
|
||||
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto p-4 space-y-5">
|
||||
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto p-5 space-y-5">
|
||||
|
||||
{error && (
|
||||
<AgendaErrorAlert
|
||||
@@ -186,7 +192,7 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
)}
|
||||
|
||||
{/* Patient Selection */}
|
||||
<div className="space-y-1.5" ref={searchRef}>
|
||||
<div className="space-y-2" ref={searchRef}>
|
||||
<Label htmlFor="patient" className="text-sm font-medium text-gray-700">Patiënt <span className="text-red-500">*</span></Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -197,7 +203,7 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="Zoek op naam..."
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
autoComplete="off"
|
||||
/>
|
||||
{isSearching && (
|
||||
@@ -207,16 +213,16 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
)}
|
||||
|
||||
{showResults && searchResults.length > 0 && (
|
||||
<div className="absolute z-10 w-full mt-1 bg-white border rounded-md shadow-lg max-h-48 overflow-y-auto">
|
||||
<div className="absolute z-10 w-full mt-1 bg-white/95 backdrop-blur-xl border border-black/5 rounded-xl shadow-xl max-h-48 overflow-y-auto">
|
||||
{searchResults.map((p) => (
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => handlePatientSelect(p)}
|
||||
className="w-full text-left px-3 py-2 hover:bg-teal-50 text-sm flex flex-col border-b last:border-0"
|
||||
className="w-full text-left px-4 py-3 hover:bg-teal-50/50 text-sm flex flex-col border-b last:border-0 border-black/5 transition-colors"
|
||||
>
|
||||
<span className="font-medium text-gray-900">{p.name}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-gray-500 mt-0.5">
|
||||
{p.birthDate && format(new Date(p.birthDate), 'dd-MM-yyyy')}
|
||||
{p.bsn && ` • BSN: ${p.bsn}`}
|
||||
</span>
|
||||
@@ -228,8 +234,8 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Date & Time */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1.5">
|
||||
<div className="grid grid-cols-2 gap-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="date" className="text-sm font-medium text-gray-700">Datum <span className="text-red-500">*</span></Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -240,13 +246,13 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="time" className="text-sm font-medium text-gray-700">Tijd <span className="text-red-500">*</span></Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -257,7 +263,7 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
type="time"
|
||||
value={time}
|
||||
onChange={(e) => setTime(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -265,9 +271,9 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Type Selection */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-gray-700">Type afspraak <span className="text-red-500">*</span></Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="grid grid-cols-2 gap-2.5">
|
||||
{(Object.keys(APPOINTMENT_TYPES) as AppointmentTypeCode[]).map((t) => {
|
||||
const bg = APPOINTMENT_TYPE_COLORS[t].bg;
|
||||
const text = APPOINTMENT_TYPE_COLORS[t].text;
|
||||
@@ -280,18 +286,18 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
type="button"
|
||||
onClick={() => setType(t)}
|
||||
className={`
|
||||
px-3 py-2 text-xs font-medium rounded-md border text-left transition-all
|
||||
${isActive ? 'ring-2 ring-offset-1 ring-teal-500' : 'hover:bg-gray-50'}
|
||||
px-3 py-2.5 text-xs font-medium rounded-lg border text-left transition-all
|
||||
${isActive ? 'ring-2 ring-offset-1 ring-teal-500 shadow-sm' : 'hover:bg-black/5 bg-white/40'}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: isActive ? bg : 'white',
|
||||
backgroundColor: isActive ? bg : undefined,
|
||||
color: isActive ? text : '#374151',
|
||||
borderColor: isActive ? border : '#e5e7eb'
|
||||
borderColor: isActive ? border : 'rgba(0,0,0,0.1)'
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>{APPOINTMENT_TYPES[t]}</span>
|
||||
{isActive && <Check className="h-3 w-3" />}
|
||||
{isActive && <Check className="h-3.5 w-3.5" />}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
@@ -300,24 +306,24 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Location Selection */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium text-gray-700">Locatie <span className="text-red-500">*</span></Label>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2.5">
|
||||
{(Object.keys(LOCATION_CLASSES) as LocationClassCode[]).map((l) => (
|
||||
<button
|
||||
key={l}
|
||||
type="button"
|
||||
onClick={() => setLocation(l)}
|
||||
className={`
|
||||
flex-1 py-2 px-3 text-xs font-medium rounded-md border flex items-center justify-center gap-1.5 transition-all
|
||||
flex-1 py-2.5 px-3 text-xs font-medium rounded-lg border flex items-center justify-center gap-2 transition-all
|
||||
${location === l
|
||||
? 'bg-teal-50 border-teal-200 text-teal-800 ring-2 ring-teal-500 ring-opacity-20'
|
||||
: 'bg-white border-gray-200 text-gray-600 hover:bg-gray-50'}
|
||||
? 'bg-teal-50 border-teal-200 text-teal-800 ring-2 ring-teal-500 ring-opacity-20 shadow-sm'
|
||||
: 'bg-white/40 border-black/10 text-gray-600 hover:bg-black/5'}
|
||||
`}
|
||||
>
|
||||
{l === 'AMB' && <MapPin className="h-3 w-3" />}
|
||||
{l === 'VR' && <div className="h-3 w-3 border rounded-full" />}
|
||||
{l === 'HH' && <div className="h-3 w-3 bg-current rounded-sm" />}
|
||||
{l === 'AMB' && <MapPin className="h-3.5 w-3.5" />}
|
||||
{l === 'VR' && <div className="h-3.5 w-3.5 border rounded-full" />}
|
||||
{l === 'HH' && <div className="h-3.5 w-3.5 bg-current rounded-sm" />}
|
||||
{LOCATION_CLASSES[l]}
|
||||
</button>
|
||||
))}
|
||||
@@ -325,14 +331,14 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="notes" className="text-sm font-medium text-gray-700">Notities (optioneel)</Label>
|
||||
<Textarea
|
||||
id="notes"
|
||||
value={notes}
|
||||
onChange={(e) => setNotes(e.target.value)}
|
||||
placeholder="Bijv. bijzonderheden, reden van komst..."
|
||||
className="h-20 text-sm resize-none"
|
||||
className="h-24 text-sm resize-none bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -340,15 +346,15 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
</form>
|
||||
|
||||
{/* Footer / Actions */}
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">
|
||||
Annuleren
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white"
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white shadow-md hover:shadow-lg transition-all h-10 rounded-lg"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -360,6 +366,6 @@ export function AgendaCreateForm({ prefillData, onClose }: AgendaCreateFormProps
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nl } from 'date-fns/locale';
|
||||
import { Calendar, MapPin, Globe, Home, Clock, X, Info, ChevronRight, Ban } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
CalendarEvent,
|
||||
APPOINTMENT_TYPE_COLORS,
|
||||
@@ -22,6 +23,21 @@ interface AgendaListViewProps {
|
||||
onViewDetails?: (encounter: CalendarEvent) => void;
|
||||
}
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
show: { opacity: 1, y: 0 }
|
||||
};
|
||||
|
||||
export function AgendaListView({
|
||||
appointments = [],
|
||||
dateRange,
|
||||
@@ -55,33 +71,43 @@ export function AgendaListView({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<div className="flex flex-col h-full bg-transparent">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<div className="flex items-center gap-2 text-teal-700">
|
||||
<Calendar className="h-5 w-5" />
|
||||
<h3 className="font-semibold text-lg capitalize">{formatDateLabel()}</h3>
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<div className="flex items-center gap-3 text-teal-700">
|
||||
<div className="p-2 bg-teal-50 rounded-lg">
|
||||
<Calendar className="h-5 w-5" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-lg text-gray-900 capitalize leading-tight">{formatDateLabel()}</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 text-gray-500 hover:text-gray-900 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<motion.div
|
||||
className="flex-1 overflow-y-auto p-5 space-y-3"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
>
|
||||
{appointments.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-10 text-center text-gray-500">
|
||||
<div className="bg-gray-50 p-4 rounded-full mb-3">
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="flex flex-col items-center justify-center h-full text-center text-gray-500"
|
||||
>
|
||||
<div className="bg-gray-50/50 p-4 rounded-full mb-4">
|
||||
<Calendar className="h-8 w-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="font-medium">Geen afspraken gevonden</p>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
<p className="font-medium text-gray-900">Geen afspraken gevonden</p>
|
||||
<p className="text-sm text-gray-500 mt-1 max-w-[200px]">
|
||||
Er staan geen afspraken gepland voor deze periode.
|
||||
</p>
|
||||
<Button variant="outline" className="mt-4 gap-2 text-teal-600 border-teal-200 hover:bg-teal-50">
|
||||
<Button variant="outline" className="mt-6 gap-2 text-teal-700 border-teal-200/50 hover:bg-teal-50/50 bg-white/50">
|
||||
<span className="text-lg leading-none">+</span> Maak nieuwe afspraak
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
appointments.map((evt) => {
|
||||
const encounter = evt.extendedProps.encounter;
|
||||
@@ -94,18 +120,19 @@ export function AgendaListView({
|
||||
const endTime = evt.end ? format(new Date(evt.end), 'HH:mm') : '';
|
||||
|
||||
return (
|
||||
<div
|
||||
<motion.div
|
||||
key={evt.id}
|
||||
className="group border rounded-lg p-3 hover:border-teal-200 hover:shadow-sm transition-all bg-white"
|
||||
variants={itemVariants}
|
||||
className="group border border-black/5 rounded-2xl p-4 hover:border-teal-200/50 hover:shadow-md transition-all bg-white/60 hover:bg-white/90 backdrop-blur-sm"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
||||
<Clock className="h-3.5 w-3.5 text-gray-400" />
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-gray-500 mb-0.5">
|
||||
<Clock className="h-3.5 w-3.5" />
|
||||
<span>{startTime} {endTime && `- ${endTime}`}</span>
|
||||
</div>
|
||||
<button
|
||||
className="text-left font-semibold text-teal-700 hover:underline mt-0.5"
|
||||
className="text-left text-base font-semibold text-gray-900 hover:text-teal-700 hover:underline transition-colors"
|
||||
onClick={() => console.log('Open patient context', patient?.id)}
|
||||
>
|
||||
{evt.title}
|
||||
@@ -118,7 +145,7 @@ export function AgendaListView({
|
||||
color: typeColor.text,
|
||||
borderColor: typeColor.border
|
||||
}}
|
||||
className="whitespace-nowrap shadow-none"
|
||||
className="whitespace-nowrap shadow-none px-2.5 py-0.5 text-xs font-medium rounded-md"
|
||||
>
|
||||
{encounter.type_display || APPOINTMENT_TYPES[typeCode] || typeCode}
|
||||
</Badge>
|
||||
@@ -130,15 +157,15 @@ export function AgendaListView({
|
||||
<span>{encounter.class_display || LOCATION_CLASSES[classCode] || classCode}</span>
|
||||
</div>
|
||||
{encounter.status === 'cancelled' && (
|
||||
<Badge variant="destructive" className="h-5 px-1.5">Geannuleerd</Badge>
|
||||
<Badge variant="destructive" className="h-5 px-1.5 font-normal rounded-sm">Geannuleerd</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 mt-3 pt-2 border-t border-gray-50 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div className="flex items-center justify-end gap-2 mt-4 pt-3 border-t border-black/5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs text-gray-500 hover:text-red-600 hover:bg-red-50"
|
||||
className="h-8 text-xs font-medium text-gray-500 hover:text-red-600 hover:bg-red-50/50 rounded-lg px-3"
|
||||
onClick={() => onCancelAppointment?.(evt)}
|
||||
>
|
||||
Annuleren
|
||||
@@ -146,25 +173,25 @@ export function AgendaListView({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs text-teal-600 hover:bg-teal-50"
|
||||
className="h-8 text-xs font-medium text-teal-700 hover:bg-teal-50/50 rounded-lg px-3"
|
||||
onClick={() => onViewDetails?.(evt)}
|
||||
>
|
||||
Details <ChevronRight className="ml-1 h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-3 bg-gray-50 border-t text-center">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 text-center">
|
||||
<a
|
||||
href="/epd/agenda"
|
||||
className="text-xs font-medium text-teal-600 hover:text-teal-700 hover:underline inline-flex items-center gap-1"
|
||||
className="text-sm font-medium text-teal-700 hover:text-teal-800 hover:underline inline-flex items-center gap-1 transition-colors"
|
||||
>
|
||||
Open volledige agenda <ChevronRight className="h-3 w-3" />
|
||||
Open volledige agenda <ChevronRight className="h-4 w-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { rescheduleEncounter } from '@/app/epd/agenda/actions';
|
||||
import { CalendarEvent } from '@/app/epd/agenda/types';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
interface AgendaRescheduleFormProps {
|
||||
prefillData?: {
|
||||
@@ -98,7 +99,7 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
<p className="text-sm text-gray-500 mt-1 mb-6">
|
||||
De afspraak is verplaatst naar {format(new Date(`${date}T${time}`), 'd MMMM HH:mm', { locale: nl })}.
|
||||
</p>
|
||||
<Button onClick={onClose} variant="outline">Sluiten</Button>
|
||||
<Button onClick={onClose} variant="outline" className="border-black/5 bg-white/50 hover:bg-white">Sluiten</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -117,30 +118,39 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
const currentStr = currentStart ? format(currentStart, 'd MMMM yyyy HH:mm', { locale: nl }) : 'Onbekend';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-white">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex flex-col h-full bg-transparent"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<div className="flex items-center justify-between p-5 border-b border-black/5">
|
||||
<h3 className="font-semibold text-lg text-teal-700">Afspraak verzetten</h3>
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8 hover:bg-gray-100 rounded-full">
|
||||
<X className="h-4 w-4" />
|
||||
<Button variant="ghost" size="icon" onClick={onClose} className="h-9 w-9 hover:bg-black/5 rounded-full">
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<form onSubmit={handleSubmit} className="flex-1 p-4 overflow-y-auto space-y-6">
|
||||
<form onSubmit={handleSubmit} className="flex-1 p-5 overflow-y-auto space-y-6">
|
||||
|
||||
{/* Info Card */}
|
||||
<div className="bg-blue-50 border border-blue-100 rounded-lg p-4">
|
||||
{encounter && <h4 className="font-medium text-blue-900 mb-2">{encounter.title}</h4>}
|
||||
<div className="flex items-center gap-2 text-sm text-blue-700 opacity-80 decoration-slate-400">
|
||||
<div className="bg-blue-50/70 border border-blue-100/50 rounded-xl p-5 shadow-sm">
|
||||
{encounter && <h4 className="font-semibold text-blue-900 mb-3 text-lg">{encounter.title}</h4>}
|
||||
<div className="flex items-center gap-3 text-sm text-blue-800/60 decoration-slate-400 mb-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span className="line-through decoration-blue-900/40">{currentStr}</span>
|
||||
<span className="line-through decoration-blue-900/30">{currentStr}</span>
|
||||
</div>
|
||||
<div className="flex justify-center my-1">
|
||||
<ArrowRight className="h-4 w-4 text-blue-400 rotate-90 sm:rotate-0" />
|
||||
|
||||
<div className="pl-0.5 my-2">
|
||||
<div className="h-6 border-l-2 border-blue-200 ml-2 border-dashed"></div>
|
||||
<ArrowRight className="h-5 w-5 text-blue-500 my-1" />
|
||||
<div className="h-2 border-l-2 border-blue-200 ml-2 border-dashed"></div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-blue-800">
|
||||
<Calendar className="h-4 w-4" />
|
||||
|
||||
<div className="flex items-center gap-3 text-base font-bold text-blue-800 bg-blue-100/50 p-3 rounded-lg border border-blue-200/50">
|
||||
<Calendar className="h-5 w-5" />
|
||||
<span>
|
||||
{date && time ? format(new Date(`${date}T${time}`), 'd MMMM yyyy HH:mm', { locale: nl }) : '...'}
|
||||
</span>
|
||||
@@ -148,14 +158,14 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border border-red-200 rounded-md flex items-center gap-2 text-sm text-red-700">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-xl flex items-center gap-3 text-sm text-red-700">
|
||||
<AlertCircle className="h-5 w-5 shrink-0" />
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="date" className="text-sm font-medium text-gray-700">Nieuwe datum</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -166,13 +176,13 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => setDate(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="time" className="text-sm font-medium text-gray-700">Nieuwe tijd</Label>
|
||||
<div className="relative">
|
||||
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||
@@ -183,7 +193,7 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
type="time"
|
||||
value={time}
|
||||
onChange={(e) => setTime(e.target.value)}
|
||||
className="pl-9"
|
||||
className="pl-10 h-10 bg-white/50 backdrop-blur-sm border-black/10 focus:bg-white transition-colors"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -192,19 +202,19 @@ export function AgendaRescheduleForm({ prefillData, onClose }: AgendaRescheduleF
|
||||
</form>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-4 bg-gray-50 border-t flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1">
|
||||
<div className="p-5 bg-gray-50/80 backdrop-blur-sm border-t border-black/5 flex justify-between gap-3">
|
||||
<Button variant="outline" type="button" onClick={onClose} disabled={isSubmitting} className="flex-1 border-black/10 bg-white/50 hover:bg-white h-10 rounded-lg">
|
||||
Annuleren
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white"
|
||||
className="flex-1 bg-teal-600 hover:bg-teal-700 text-white shadow-md hover:shadow-lg h-10 rounded-lg"
|
||||
>
|
||||
{isSubmitting ? 'Verplaatsen...' : 'Bevestig wijziging'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export function ChatPanel() {
|
||||
<div className="max-w-md text-center text-slate-500">
|
||||
<div className="text-4xl mb-4">💬</div>
|
||||
<h3 className="text-lg font-medium text-slate-700 mb-2">
|
||||
Welkom bij Swift Medical Scribe
|
||||
Welkom bij Swift Assistent
|
||||
</h3>
|
||||
<p className="text-sm mb-4">
|
||||
Typ of spreek wat je wilt doen...
|
||||
|
||||
109
docs/implementatieplan-lead-dev.md
Normal file
109
docs/implementatieplan-lead-dev.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Implementatieplan - codebase stabilisatie (lead dev)
|
||||
|
||||
Datum: 2025-12-29
|
||||
Owner: lead dev
|
||||
Status: draft
|
||||
|
||||
## Doel
|
||||
De codebase consistent, voorspelbaar en onderhoudbaar maken, met focus op build stabiliteit,
|
||||
duidelijke type-sources en het verwijderen van dubbele of ongebruikte onderdelen.
|
||||
|
||||
## Context en uitgangspunten
|
||||
- We beperken de scope tot stabilisatie en onderhoud (geen nieuwe features).
|
||||
- Geen nieuwe dependencies of migraties zonder expliciet akkoord.
|
||||
- Kleine, reviewbare changes per fase om regressies te beperken.
|
||||
- Types en docs moeten dezelfde bron van waarheid volgen.
|
||||
|
||||
## Scope
|
||||
In scope:
|
||||
- Build en styling configuratie normaliseren.
|
||||
- Supabase types en documentatie harmoniseren.
|
||||
- Archief routes uit app/ halen of expliciet isoleren.
|
||||
- Duplicaten en ongebruikte onderdelen opruimen.
|
||||
- Validatie en minimale guardrails toevoegen.
|
||||
|
||||
Out of scope:
|
||||
- Nieuwe UI features of redesigns.
|
||||
- Grote database schema wijzigingen.
|
||||
- Performance tuning buiten concrete issues.
|
||||
|
||||
## Beslissingen nodig (voor start)
|
||||
1) Tailwind stack: v3 of v4, en welke PostCSS config blijft.
|
||||
2) Canonical supabase types file en generator (database.types.ts vs types.ts).
|
||||
3) Definitieve plek voor archief code (buiten app/ of in docs/).
|
||||
|
||||
## Fase 0 - Alignment en inventarisatie (0.5 dag)
|
||||
Doel: scope en keuzes vastleggen.
|
||||
Taken:
|
||||
- Keuzes bevestigen voor Tailwind, Supabase types en archief locatie.
|
||||
- Afbakening van bestanden die we willen behouden of verwijderen.
|
||||
Acceptatiecriteria:
|
||||
- Keuzes vastgelegd in dit document (beslissingen sectie bijgewerkt).
|
||||
|
||||
## Fase 1 - Build en styling cleanup (1-2 dagen)
|
||||
Doel: 1 bron van waarheid voor PostCSS en Tailwind.
|
||||
Taken:
|
||||
- Dubbele PostCSS config verwijderen en 1 config overhouden.
|
||||
- Tailwind dependency set consistent maken met gekozen versie.
|
||||
- globals.css.backup status bepalen (verwijderen of archiveren buiten app/).
|
||||
Acceptatiecriteria:
|
||||
- Build gebruikt 1 PostCSS config en 1 Tailwind variant.
|
||||
- Geen dubbele of conflicterende CSS entrypoints.
|
||||
|
||||
## Fase 2 - Supabase types en docs (1 dag)
|
||||
Doel: types en documentatie in sync.
|
||||
Taken:
|
||||
- Canonical types file kiezen.
|
||||
- scripts en docs bijwerken zodat generator naar de juiste file schrijft.
|
||||
- lib/supabase/index.ts export laten verwijzen naar de gekozen types file.
|
||||
Acceptatiecriteria:
|
||||
- types:generate schrijft naar de gekozen file.
|
||||
- Alle imports verwijzen naar dezelfde type bron.
|
||||
- Docs verwijzen naar dezelfde workflow.
|
||||
|
||||
## Fase 3 - Routing hygiene en archief (1-2 dagen)
|
||||
Doel: legacy code niet meer routable in Next.
|
||||
Taken:
|
||||
- app/epd/_archive verplaatsen buiten app/ (bijv. docs/ of archive/).
|
||||
- Eventuele redirect routes behouden in app/ waar nodig.
|
||||
- Verwijzingen in docs updaten naar nieuwe archief locatie.
|
||||
Acceptatiecriteria:
|
||||
- Geen /epd/_archive routes in runtime.
|
||||
- Legacy code blijft beschikbaar voor referentie buiten app/.
|
||||
|
||||
## Fase 4 - Duplicaten en ongebruikte onderdelen (1 dag)
|
||||
Doel: dubbele helpers en ongebruikte componenten verwijderen of consolideren.
|
||||
Taken:
|
||||
- period-utils consolideren en importen alignen.
|
||||
- rapportage-workspace vs rapportage-workspace-v2 keuze vastleggen en opruimen.
|
||||
- Snelle scan voor overige duidelijke duplicaten met lage impact.
|
||||
Acceptatiecriteria:
|
||||
- Een set period utils met consistente API.
|
||||
- Geen ongebruikte componenten in hoofdpad.
|
||||
|
||||
## Fase 5 - Validatie en guardrails (0.5-1 dag)
|
||||
Doel: minimale zekerheid dat we niets breken.
|
||||
Taken:
|
||||
- pnpm lint en pnpm build draaien.
|
||||
- Handmatige QA op kernflows (login, patients, rapportage, agenda).
|
||||
- Documenteren van checks in CHANGELOG of korte release note.
|
||||
Acceptatiecriteria:
|
||||
- Lint en build groen.
|
||||
- Handmatige checks gedocumenteerd.
|
||||
|
||||
## Risicos en mitigatie
|
||||
- Build regressies door Tailwind switch -> kleine PRs en snelle rollback.
|
||||
- Type regressies door types file wijziging -> importen met rg checken.
|
||||
- Archief code nog nodig -> verplaatsen ipv verwijderen.
|
||||
|
||||
## Definition of Done
|
||||
- 1 build pipeline voor CSS (PostCSS + Tailwind).
|
||||
- 1 canonical Supabase types file met up to date docs.
|
||||
- Geen archief routes in app/ runtime.
|
||||
- Duplicaten en unused code opgeruimd.
|
||||
- Lint/build groen en QA notes beschikbaar.
|
||||
|
||||
## Open vragen
|
||||
- Welke Tailwind versie heeft voorkeur?
|
||||
- Welke types file is de bron van waarheid?
|
||||
- Waar moet archief code definitief landen?
|
||||
1292
docs/intent/architecture-intent-scalability.md
Normal file
1292
docs/intent/architecture-intent-scalability.md
Normal file
File diff suppressed because it is too large
Load Diff
2231
docs/intent/architecture-swift-cortex-v2.md
Normal file
2231
docs/intent/architecture-swift-cortex-v2.md
Normal file
File diff suppressed because it is too large
Load Diff
167
docs/intent/fo-swift-intent-system-v2.md
Normal file
167
docs/intent/fo-swift-intent-system-v2.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 🧩 Functioneel Ontwerp (FO) – Swift Intent System V2
|
||||
|
||||
**Projectnaam:** Swift Intent Architecture V2
|
||||
**Versie:** v2.0 (Draft)
|
||||
**Datum:** 29-12-2025
|
||||
**Auteur:** Colin Lit (Antigravity AI)
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel en relatie met het PRD
|
||||
🎯 **Doel van dit document:**
|
||||
Dit FO beschrijft de functionele werking van de "Next Gen" Swift Intent Architectuur. Waar V1 focuste op snelheid en basiscommando's ("Reactive"), focust V2 op contextbegrip, meervoudige intenties en proactieve ondersteuning ("Agentic").
|
||||
|
||||
📘 **Relatie tot vorige documentatie:**
|
||||
Dit document vervangt de architectuur uit `architecture-intent-scalability.md` (Strategie 1: Strict Hierarchy) en kiest voor de **Hybride Route** (Strategie 5 + Agentic extensions) zoals besproken in de UX Evaluatie.
|
||||
|
||||
---
|
||||
|
||||
## 2. Overzicht van de belangrijkste onderdelen
|
||||
🎯 **Architectuurmodel:** "The Swift Cortex"
|
||||
Het systeem bestaat uit drie samenwerkende lagen die elk een andere rol spelen in de interactie:
|
||||
|
||||
1. **Layer 1: The Reflex Arc (De Snelle Reflex)**
|
||||
* *Rol:* Directe uitvoering van simpele, veelvoorkomende commando's.
|
||||
* *Voorbeeld:* "Afspraken vandaag", "Navigeer dossier".
|
||||
|
||||
2. **Layer 2: The Intent Orchestrator (Het Brein)**
|
||||
* *Rol:* AI-gedreven analyse voor complexe zinnen, context-disambiguatie en **Multi-Intents**.
|
||||
* *Voorbeeld:* "Zeg Jan af **en** maak een notitie."
|
||||
|
||||
3. **Layer 3: The Safety Net & Suggestion Engine (De Partner)**
|
||||
* *Rol:* Proactieve business logic die *na* een actie meedenkt.
|
||||
* *Voorbeeld:* Na "Wondzorg registratie" → Suggestie: "Wondcontrole inplannen?"
|
||||
|
||||
---
|
||||
|
||||
## 3. Userstories
|
||||
|
||||
**User Story Template:**
|
||||
> Als [rol] wil ik [actie] zodat [waarde].
|
||||
|
||||
| ID | Rol | Doel / Actie | Context / Voorbeeld | Prioriteit |
|
||||
|----|------|---------------|-------------------|-------------|
|
||||
| **US-V2-01** | Vpk | **Multi-Intent** commando's geven | "Meld Jan af voor vandaag **en** bel zijn huisarts." | Hoog |
|
||||
| **US-V2-02** | Vpk | **Context-aware** begrepen worden | "Plan wondzorg **morgen**" (Snap dat 'morgen' refereert aan *mijn* agenda). | Hoog |
|
||||
| **US-V2-03** | Regie | **Proactieve checks** op veiligheid | Bij voorschrijven lithium: "Check laatste nierfunctie?" | Middel |
|
||||
| **US-V2-04** | Psych | **Impliciete intenties** verwerkt zien | "Patiënt was suïcidaal" → Systeem oppert crisisprotocol start. | Hoog |
|
||||
| **US-V2-05** | Vpk | Geen "Computer says no" ervaring | Bij twijfel: vraag verduidelijking i.p.v. "Ik begrijp het niet". | Hoog |
|
||||
|
||||
---
|
||||
|
||||
## 4. Functionele werking per onderdeel
|
||||
|
||||
### 4.1 Layer 1: The Reflex Arc (Local First)
|
||||
* **Trigger:** Elke gebruikersinput (spraak/tekst).
|
||||
* **Werking:** Checkt razendsnel (<20ms) of de input matcht met een `Local Pattern` (Regex).
|
||||
* **Conditie:** Alleen bij **Confidence > 0.9** (vrijwel zeker) voert hij direct uit.
|
||||
* **Fallback:** Bij twijfel (<0.9) of geen match → *Direct doorsturen naar Layer 2*.
|
||||
|
||||
### 4.2 Layer 2: The Intent Orchestrator (AI Router)
|
||||
* **Trigger:** Input die te complex of dubbelzinnig is voor Layer 1.
|
||||
* **Input Context:** Ontvangt niet alleen de zin, maar ook: `ActivePatient`, `CurrentView`, `Time`.
|
||||
* **Werking:**
|
||||
1. Analyseert intentie(s).
|
||||
2. Splits samengestelde zinnen ("En", "Daarna") in een **Action Chain**.
|
||||
3. Extraheert entities (Wie, Wanneer, Wat).
|
||||
* **Output:** Een lijst van uit te voeren acties: `[ActionA, ActionB]`.
|
||||
|
||||
### 4.3 Layer 3: The Safety Net (Post-Action Logic)
|
||||
* **Trigger:** Succesvolle afronding van een intent (bijv. `CreateAppointment` klaar).
|
||||
* **Werking:** Draait `Domain Rules` op de uitgevoerde actie.
|
||||
* **UI:** Toont een **Suggestion Toast** of **Card** ("Wil je ook...?").
|
||||
* **Voorbeeld:**
|
||||
* *Actie:* Medicatie gestart.
|
||||
* *Rule:* "Nieuwe medicatie vereist evaluatie na 2 weken."
|
||||
* *Suggestie:* "Evaluatie afspraak inplannen over 14 dagen?"
|
||||
|
||||
---
|
||||
|
||||
## 5. UI-overzicht (Flow)
|
||||
|
||||
De UI past zich aan op basis van de complexiteit van de intentie.
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. Input: "Zeg Jan af en maak notitie: grieperig" │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 2. Processing (Cortex): "1 moment, ik verwerk 2 acties..." │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 3. Execution UI (Stacked Cards) │
|
||||
│ ┌──────────────────────────────────────┐ │
|
||||
│ │ ✅ Afspraak Jan (14:00) Geannuleerd │ │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────────────┐ │
|
||||
│ │ 📝 Concept Notitie: "grieperig" │ [Bevestigen] │
|
||||
│ └──────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 4. Safety Net (Proactive Toast) │
|
||||
│ 💡 "Wil je de griep-poli waarschuwen?" [Ja, doe maar] [X] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Interacties met AI (Specificaties)
|
||||
|
||||
| Component | Trigger | AI Model | Prompt Strategie | Output Structuur |
|
||||
|-----------|---------|----------|------------------|------------------|
|
||||
| **Reflex** | User Input | *Geen (Regex)* | N.v.t. | `SingleIntent` |
|
||||
| **Cortex** | Complex Input | Claude 3.5 Haiku | "You are an Orchestrator. Output a JSON list of intents." | `Array<IntentAction>` |
|
||||
| **Safety** | Action Done | Regelset / Small AI | "Based on this action, what is the protocol?" | `Suggestion | null` |
|
||||
|
||||
### 6.1 Multi-Intent Data Model
|
||||
Het systeem moet worden omgebouwd van `Single Intent` naar `Intent Chain`:
|
||||
|
||||
**Oud:**
|
||||
```typescript
|
||||
interface Result { intent: SwiftIntent }
|
||||
```
|
||||
|
||||
**Nieuw:**
|
||||
```typescript
|
||||
interface IntentChain {
|
||||
originalInput: string;
|
||||
actions: IntentAction[];
|
||||
}
|
||||
|
||||
interface IntentAction {
|
||||
intent: SwiftIntent;
|
||||
entities: ExtractedEntities;
|
||||
status: 'pending' | 'success' | 'failed';
|
||||
requiresConfirmation: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Migratie & Roadmap
|
||||
|
||||
### Fase 1: Hybrid Foundation (Week 1-2)
|
||||
* Implementatie van de **Reflex/Cortex switch**.
|
||||
* Zorgen dat *alle* twijfelgevallen naar de AI gaan (geen "Unknown" errors meer).
|
||||
* Context object (`ActivePatient`) meegeven aan AI.
|
||||
|
||||
### Fase 2: Orchestration (Week 3-4)
|
||||
* Refactor frontend om `IntentChain` (lijstjes) te ondersteunen.
|
||||
* Prompt engineering voor multi-intent herkenning ("En", "Daarna").
|
||||
|
||||
### Fase 3: Proactivity (Maand 2)
|
||||
* Bouwen van de `Safety Net` listeners.
|
||||
* Protocollen toevoegen voor Medicatie en Wondzorg.
|
||||
|
||||
---
|
||||
|
||||
## 8. Bijlagen & Referenties
|
||||
* **PRD/Vision:** `docs/swift/ux-simulation-intent-next-level.md`
|
||||
* **Technical Base:** `lib/swift/intent-classifier-ai.ts`
|
||||
* **Legacy Docs:** `docs/swift/intent-architecture-v2-proposal.md`
|
||||
356
docs/intent/haalbaarheidsanalyse-swift-cortex-v2.md
Normal file
356
docs/intent/haalbaarheidsanalyse-swift-cortex-v2.md
Normal file
@@ -0,0 +1,356 @@
|
||||
# Haalbaarheidsanalyse: Swift Cortex V2
|
||||
|
||||
**Datum:** 29-12-2025
|
||||
**Auteur:** Claude Code (Opus 4.5)
|
||||
**Status:** Analyse compleet
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Conclusie: HAALBAAR met gefaseerde aanpak**
|
||||
|
||||
De Swift Cortex V2 architectuur is **goed haalbaar** binnen de bestaande codebase. De huidige implementatie biedt een solide fundament met ~70% van de benodigde infrastructuur al aanwezig. De voorgestelde V2 architectuur sluit naadloos aan op bestaande patterns.
|
||||
|
||||
| Aspect | Score | Toelichting |
|
||||
|--------|-------|-------------|
|
||||
| **Technische haalbaarheid** | 🟢 Hoog | Bestaande architectuur is compatibel |
|
||||
| **Complexiteit** | 🟡 Middel | Multi-intent en Safety Net zijn nieuwe concepten |
|
||||
| **Risico** | 🟢 Laag | Incrementeel te bouwen, backward compatible |
|
||||
| **MVP Scope** | 🟢 Realistisch | 6 user stories, goed afgebakend |
|
||||
|
||||
---
|
||||
|
||||
## 1. Vergelijking: Huidige Staat vs V2 Visie
|
||||
|
||||
### 1.1 Architectuur Mapping
|
||||
|
||||
| V2 Concept | Huidige Implementatie | Gap |
|
||||
|------------|----------------------|-----|
|
||||
| **Layer 1: Reflex Arc** | ✅ `intent-classifier.ts` (60 patterns) | Minimaal - voeg complexity detection toe |
|
||||
| **Layer 2: Orchestrator** | ✅ `intent-classifier-ai.ts` (Haiku) | Middel - upgrade prompt voor multi-intent |
|
||||
| **Layer 3: Safety Net** | ❌ Niet aanwezig | Nieuw te bouwen |
|
||||
| **Context Injection** | 🟡 Basis aanwezig (`activePatient`, `shift`) | Uitbreiden met `agendaToday`, `recentIntents` |
|
||||
| **Multi-Intent Chains** | ❌ Single intent model | Data model refactor nodig |
|
||||
| **Entity Extraction** | ✅ `entity-extractor.ts` | Minimaal - voeg `patientResolution` toe |
|
||||
| **Date/Time Parsing** | ✅ `date-time-parser.ts` | Geen gap |
|
||||
|
||||
### 1.2 Bestaande Bestanden (Assets)
|
||||
|
||||
```
|
||||
lib/swift/
|
||||
├── types.ts ✅ Basis types, uitbreiden met IntentChain
|
||||
├── intent-classifier.ts ✅ Layer 1 basis, voeg signals detection toe
|
||||
├── intent-classifier-ai.ts ✅ Layer 2 basis, upgrade prompt
|
||||
├── entity-extractor.ts ✅ Compleet, kleine uitbreiding
|
||||
├── date-time-parser.ts ✅ Compleet, geen wijzigingen
|
||||
├── action-parser.ts ✅ Refactor voor chains
|
||||
├── chat-api.ts ✅ Behouden
|
||||
├── error-handler.ts ✅ Recent toegevoegd
|
||||
└── [NIEUW] reflex-classifier.ts → Upgrade van intent-classifier
|
||||
└── [NIEUW] orchestrator.ts → Upgrade van intent-classifier-ai
|
||||
└── [NIEUW] safety-net.ts → Nieuw te bouwen
|
||||
|
||||
stores/
|
||||
└── swift-store.ts ✅ Uitbreiden met chain state + suggestions
|
||||
|
||||
components/swift/
|
||||
├── chat/ ✅ Bestaand, voeg ActionChainCard toe
|
||||
├── artifacts/ ✅ Bestaand, geen wijzigingen
|
||||
├── command-center/ ✅ Bestaand, voeg SuggestionToast toe
|
||||
└── [NIEUW] suggestion-toast.tsx
|
||||
└── [NIEUW] chat/action-chain-card.tsx
|
||||
└── [NIEUW] chat/clarification-card.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Gap Analyse per V2 Feature
|
||||
|
||||
### 2.1 Multi-Intent Support (US-MVP-01)
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// Huidige single-intent response
|
||||
interface ClassificationResult {
|
||||
intent: SwiftIntent;
|
||||
confidence: number;
|
||||
}
|
||||
```
|
||||
|
||||
**V2 vereist:**
|
||||
```typescript
|
||||
// Multi-intent chain
|
||||
interface IntentChain {
|
||||
actions: IntentAction[]; // Array i.p.v. single
|
||||
status: 'pending' | 'executing' | 'completed';
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- `types.ts`: Nieuwe interfaces toevoegen (~50 regels)
|
||||
- `orchestrator.ts`: Nieuwe AI prompt met multi-intent instructies (~150 regels)
|
||||
- `swift-store.ts`: Chain state toevoegen (~30 regels)
|
||||
- `action-chain-card.tsx`: Nieuwe UI component (~150 regels)
|
||||
|
||||
**Effort: M (Medium)**
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Context Awareness (US-MVP-02, US-MVP-03)
|
||||
|
||||
**Huidige situatie:**
|
||||
```typescript
|
||||
// In chat API - basic context
|
||||
context: {
|
||||
activePatient?: { id, first_name, last_name },
|
||||
shift?: ShiftType
|
||||
}
|
||||
```
|
||||
|
||||
**V2 vereist:**
|
||||
```typescript
|
||||
interface SwiftContext {
|
||||
activePatient: { id, name, recentNotes?, upcomingAppointments? };
|
||||
currentView: string;
|
||||
shift: ShiftType;
|
||||
currentTime: Date;
|
||||
agendaToday: Appointment[];
|
||||
recentIntents: RecentIntent[];
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- Nieuwe `GET /api/swift/context` endpoint (~80 regels)
|
||||
- Context builder utility (~50 regels)
|
||||
- Store uitbreiding voor context sync (~20 regels)
|
||||
|
||||
**Effort: S (Small)**
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Hybrid Reflex/Cortex Switch (US-MVP-04, US-MVP-05)
|
||||
|
||||
**Huidige situatie:**
|
||||
- `intent-classifier.ts` heeft al confidence threshold (0.8)
|
||||
- AI fallback via `intent-classifier-ai.ts` werkt al
|
||||
|
||||
**V2 verschil:**
|
||||
- Expliciete "complexity signals" detectie (multi-intent woorden: "en", "daarna")
|
||||
- Context-afhankelijke woorden detectie ("hij", "haar", "deze")
|
||||
|
||||
**Impact:**
|
||||
- Upgrade `intent-classifier.ts` → `reflex-classifier.ts` (~100 regels diff)
|
||||
- Voeg `MULTI_INTENT_SIGNALS` en `CONTEXT_SIGNALS` regex arrays toe
|
||||
|
||||
**Effort: S (Small)** - Grootste deel al gebouwd
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Safety Net / Proactive Suggestions (US-MVP-06)
|
||||
|
||||
**Huidige situatie:**
|
||||
- Niet aanwezig
|
||||
|
||||
**V2 vereist:**
|
||||
- Protocol Rules database
|
||||
- `evaluateSafetyNet()` functie
|
||||
- `SuggestionToast` component
|
||||
- Store state voor suggestions
|
||||
|
||||
**Impact:**
|
||||
- `safety-net.ts`: Nieuwe module (~200 regels)
|
||||
- `suggestion-toast.tsx`: Nieuw component (~100 regels)
|
||||
- Store uitbreiding (~40 regels)
|
||||
- Integratie in action execution flow
|
||||
|
||||
**Effort: L (Large)** - Volledig nieuw concept
|
||||
|
||||
---
|
||||
|
||||
## 3. Risico Analyse
|
||||
|
||||
### 3.1 Technische Risico's
|
||||
|
||||
| Risico | Impact | Kans | Mitigatie |
|
||||
|--------|--------|------|-----------|
|
||||
| AI Prompt instabiliteit | Multi-intent parsing faalt | Laag | Uitgebreide test dataset, fallback naar single |
|
||||
| Performance degradatie | Latency >2s | Laag | Haiku is snel, cache context |
|
||||
| State complexity | Race conditions | Middel | Zustand immer middleware, clear action flow |
|
||||
| Backward compatibility | V1 features breken | Laag | Feature flags, adapter pattern |
|
||||
|
||||
### 3.2 Scope Risico's
|
||||
|
||||
| Risico | Impact | Mitigatie |
|
||||
|--------|--------|-----------|
|
||||
| Feature creep | MVP te groot | Strikte scope (6 stories) |
|
||||
| Protocol complexity | Safety Net te ambitieus | Begin met 1 hardcoded regel |
|
||||
| Over-engineering | Te veel abstractie | "Working software" first |
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementatie Strategie
|
||||
|
||||
### 4.1 Aanbevolen Fasering
|
||||
|
||||
```
|
||||
Fase 1: Foundation (Week 1)
|
||||
├── SwiftContext type definitie
|
||||
├── GET /api/swift/context endpoint
|
||||
├── Reflex complexity detection upgrade
|
||||
├── Feature flag: SWIFT_V2_ENABLED
|
||||
└── Deliverable: Context beschikbaar, backward compatible
|
||||
|
||||
Fase 2: Multi-Intent (Week 2)
|
||||
├── IntentChain types
|
||||
├── Orchestrator AI prompt upgrade
|
||||
├── ActionChainCard component
|
||||
├── Store chain state
|
||||
└── Deliverable: "Zeg af en maak notitie" werkt
|
||||
|
||||
Fase 3: Safety Net MVP (Week 3)
|
||||
├── 1 hardcoded protocol regel (wondzorg)
|
||||
├── SuggestionToast component
|
||||
├── Trigger na dagnotitie
|
||||
└── Deliverable: Proactieve suggestie demo
|
||||
|
||||
Fase 4: Polish (Week 4)
|
||||
├── ClarificationCard component
|
||||
├── Error handling
|
||||
├── UI animaties
|
||||
└── Deliverable: Demo-ready prototype
|
||||
```
|
||||
|
||||
### 4.2 Backward Compatibility
|
||||
|
||||
De V2 architectuur kan naast V1 draaien:
|
||||
|
||||
```typescript
|
||||
// lib/swift/classifier-adapter.ts
|
||||
export async function classifyIntent(input: string, context?: SwiftContext) {
|
||||
if (!FEATURE_FLAGS.SWIFT_V2_ENABLED) {
|
||||
return classifyV1(input); // Bestaande flow
|
||||
}
|
||||
|
||||
const reflex = classifyWithReflex(input);
|
||||
if (!reflex.shouldEscalateToAI) {
|
||||
return buildLocalResult(input, reflex);
|
||||
}
|
||||
return classifyWithOrchestrator(input, context);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Effort Schatting
|
||||
|
||||
### 5.1 Per Component
|
||||
|
||||
| Component | Nieuw | Wijziging | Effort |
|
||||
|-----------|-------|-----------|--------|
|
||||
| `types.ts` | 150 regels | - | S |
|
||||
| `reflex-classifier.ts` | 200 regels | upgrade | M |
|
||||
| `orchestrator.ts` | 250 regels | upgrade | M |
|
||||
| `safety-net.ts` | 200 regels | nieuw | M |
|
||||
| `swift-store.ts` | - | +100 regels | S |
|
||||
| `action-chain-card.tsx` | 180 regels | nieuw | M |
|
||||
| `suggestion-toast.tsx` | 100 regels | nieuw | S |
|
||||
| `clarification-card.tsx` | 60 regels | nieuw | S |
|
||||
| `/api/swift/context` | 80 regels | nieuw | S |
|
||||
| `/api/intent/classify` | 150 regels | nieuw | M |
|
||||
| Tests | 300 regels | nieuw | M |
|
||||
|
||||
**Totaal: ~1770 nieuwe regels code**
|
||||
|
||||
### 5.2 Tijdsinschatting
|
||||
|
||||
| Fase | Effort | Complexiteit |
|
||||
|------|--------|--------------|
|
||||
| Fase 1: Foundation | 2-3 dagen | Laag |
|
||||
| Fase 2: Multi-Intent | 3-4 dagen | Middel |
|
||||
| Fase 3: Safety Net | 2-3 dagen | Middel |
|
||||
| Fase 4: Polish | 2-3 dagen | Laag |
|
||||
|
||||
**Totaal: 9-13 werkdagen voor MVP**
|
||||
|
||||
---
|
||||
|
||||
## 6. Aanbevelingen
|
||||
|
||||
### 6.1 DO's
|
||||
|
||||
1. **Start met Foundation** - Context injection is low-risk, high-value
|
||||
2. **Gebruik feature flags** - Mogelijkheid om V2 uit te schakelen
|
||||
3. **Test sentences dataset** - Bouw corpus van 50+ test zinnen voordat je multi-intent bouwt
|
||||
4. **Behoud V1 patterns** - De 60 bestaande regex patterns zijn waardevol
|
||||
|
||||
### 6.2 DON'T's
|
||||
|
||||
1. **Niet alle protocollen tegelijk** - Begin met 1 Safety Net regel
|
||||
2. **Geen over-engineering** - De `ProtocolRule` interface is voor later
|
||||
3. **Niet de store herschrijven** - Extend, niet replace
|
||||
4. **Geen rollout strategie nodig** - Dit is een prototype
|
||||
|
||||
### 6.3 Quick Wins
|
||||
|
||||
1. **Context endpoint** - Direct te bouwen, verbetert AI kwaliteit
|
||||
2. **Complexity detection** - 10 regels code, grote impact
|
||||
3. **Processing indicator** - Al aanwezig (`isStreaming`), polish
|
||||
|
||||
---
|
||||
|
||||
## 7. Conclusie
|
||||
|
||||
### Haalbaarheid: ✅ JA
|
||||
|
||||
De Swift Cortex V2 architectuur is **volledig haalbaar** binnen de bestaande codebase:
|
||||
|
||||
1. **70% infrastructuur bestaat al** - Classifier, entity extraction, store, chat API
|
||||
2. **Incrementeel te bouwen** - Elke fase levert werkende software
|
||||
3. **Backward compatible** - Geen breaking changes voor bestaande features
|
||||
4. **Realistische scope** - 6 user stories, ~1770 regels code
|
||||
5. **Risico's beheersbaar** - Feature flags, fallbacks, tests
|
||||
|
||||
### Kritieke Succesfactoren
|
||||
|
||||
1. **Test dataset eerst** - Bouw 50+ test zinnen voordat je multi-intent implementeert
|
||||
2. **Feature flags** - Zorg dat V2 uitschakelbaar is
|
||||
3. **Iteratief bouwen** - Elke fase moet demo-baar zijn
|
||||
4. **Scope discipline** - Niet alle protocollen, alleen wondzorg voor MVP
|
||||
|
||||
### Volgende Stap
|
||||
|
||||
Start met **Fase 1: Foundation** - de SwiftContext API endpoint. Dit is:
|
||||
- Low risk
|
||||
- Onafhankelijk van andere features
|
||||
- Direct waarde toevoegend aan bestaande AI classificatie
|
||||
- In 1-2 dagen te bouwen
|
||||
|
||||
---
|
||||
|
||||
## Bijlagen
|
||||
|
||||
### A. Bestaande Code Referenties
|
||||
|
||||
| Bestand | Regels | Functie |
|
||||
|---------|--------|---------|
|
||||
| `lib/swift/types.ts` | ~180 | Type definities |
|
||||
| `lib/swift/intent-classifier.ts` | ~200 | Layer 1 classifier |
|
||||
| `lib/swift/intent-classifier-ai.ts` | ~100 | Layer 2 AI fallback |
|
||||
| `lib/swift/entity-extractor.ts` | ~250 | Entity extraction |
|
||||
| `lib/swift/date-time-parser.ts` | ~200 | Datum/tijd parsing |
|
||||
| `lib/swift/action-parser.ts` | ~150 | Action routing |
|
||||
| `stores/swift-store.ts` | ~200 | Zustand state |
|
||||
|
||||
### B. V2 Documentatie Verwijzingen
|
||||
|
||||
- `architecture-swift-cortex-v2.md` - Uitgebreid technisch plan
|
||||
- `fo-swift-intent-system-v2.md` - Functioneel ontwerp
|
||||
- `intent-architecture-v2-proposal.md` - Architectuur voorstel
|
||||
- `mvp-userstories-intent-system.md` - MVP scope
|
||||
|
||||
### C. Versie Historie
|
||||
|
||||
| Versie | Datum | Auteur | Wijzigingen |
|
||||
|--------|-------|--------|-------------|
|
||||
| 1.0 | 29-12-2025 | Claude Code | Initiële analyse |
|
||||
86
docs/intent/intent-architecture-v2-proposal.md
Normal file
86
docs/intent/intent-architecture-v2-proposal.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Architecture Proposal: Swift Intent System V2
|
||||
|
||||
**Status:** Draft
|
||||
**Based on:**
|
||||
- `ux-evaluation-intent-scalability.md` (The "Don't make me think" rule)
|
||||
- `ux-simulation-intent-next-level.md` (The "Be my partner" wish)
|
||||
|
||||
---
|
||||
|
||||
## 1. The Triad Consensus (PO, UX, Dev)
|
||||
|
||||
De PO, UX en Lead Developer hebben de bevindingen geanalyseerd. Dit is de gezamenlijke conclusie:
|
||||
|
||||
> **"We stoppen met optimaliseren voor milliseconden (Pre-optimization) en starten met optimaliseren voor intelligentie (Agentic UI)."**
|
||||
|
||||
* **PO:** "Akkoord met hogere 'cost per interaction' (AI tokens) als dit directe tijdwinst oplevert voor de zorgverlener (minder administratie)."
|
||||
* **UX:** "De interface moet 'Invisible' worden. Geen commando's leren, maar intenties uitspreken."
|
||||
* **Dev:** "De huidige 'Strict Hierarchy' (Strategie 1) is te rigide. We gaan voor een **Hybrid Leader/Follower architectuur**: Lokale snelheid waar kan, AI intelligentie waar moet."
|
||||
|
||||
---
|
||||
|
||||
## 2. Core Architecture: "The Swift Cortex"
|
||||
|
||||
We vervangen de simpele `Classifier` door een slimmere `Cortex`.
|
||||
|
||||
### Layer 1: The Reflex Arc (Lokaal, <20ms)
|
||||
* **Wat:** Regex & Keyword matching (zoals nu).
|
||||
* **Doel:** Directe actie voor "High Confidence, Low Risk" commando's.
|
||||
* **Scope:** Navigatie, simpele queries ("Agenda vandaag"), start commando's.
|
||||
|
||||
### Layer 2: The Intent Orchestrator (AI, ~400ms)
|
||||
* **Wat:** Een kleine LLM (Haiku/Gemini Flash) die fungeert als "Router".
|
||||
* **Nieuwe capability: Multi-Intent Parsing.**
|
||||
* *Input:* "Zeg Jan af en zet in zijn dossier dat hij griep heeft."
|
||||
* *Output:* `[CancelAppointment(Jan), CreateNote(Jan, "Griep")]`
|
||||
* **Nieuwe capability: Entity Disambiguation.**
|
||||
* Snap dat "Jan" verwijst naar de patiënt die ik *vandaaag* in mijn agenda heb.
|
||||
|
||||
### Layer 3: The Safety Net (Contextual Logic)
|
||||
* **Wat:** Een business logic laag die *na* de intent draait.
|
||||
* **Functie:** Proactive Suggestions.
|
||||
* *Trigger:* Intent `CreateWoundCareNote` 'completed'.
|
||||
* *Logic:* Check `Protocol(Wondzorg)`.
|
||||
* *Action:* Suggest `ScheduleFollowUp`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementation Roadmap
|
||||
|
||||
### Phase 1: Hybrid Foundation (Week 1-2)
|
||||
* [ ] Behoud huidige lokale patterns voor snelheid.
|
||||
* [ ] Bouw de **AI Fallback Router** in. Als lokaal faalt (<60% confidence), stuur *direct* volledige context naar AI.
|
||||
* [ ] **UX Win:** Geen "Ik begrijp het niet" meer. Altijd een poging tot begrip.
|
||||
|
||||
### Phase 2: Chains & Context (Week 3-4)
|
||||
* [ ] **Context Injectie:** Stuur `ActivePatient`, `RecentIntents`, en `AgendaToday` mee in de AI prompt.
|
||||
* [ ] **Multi-Intent Support:** Pas de frontend aan om een *lijst* van acties te verwerken in plaats van één.
|
||||
|
||||
### Phase 3: Agentic Proactivity (Maand 2)
|
||||
* [ ] **Suggestion Engine:** UI element (Toaster/Card) dat vraagt: *"Wil je ook X doen?"*
|
||||
* [ ] **Medical Knowledge Base:** Koppel protocollen aan intents.
|
||||
|
||||
---
|
||||
|
||||
## 4. Technisch Ontwerp Schets (Dev)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
User["User Speech"] --> Reflex["Layer 1: Reflex (Regex)"]
|
||||
Reflex -->|Match > 90%| Action["Execute Action"]
|
||||
Reflex -->|No Match / Low Conf| Cortex["Layer 2: AI Cortex"]
|
||||
|
||||
subgraph Context
|
||||
Store["Active Patient"]
|
||||
Agenda["Today's Schedule"]
|
||||
end
|
||||
|
||||
Context --> Cortex
|
||||
|
||||
Cortex -->|Multi-Intent Detected| Plan["Execution Plan"]
|
||||
Plan --> Action1["Action A"]
|
||||
Plan --> Action2["Action B"]
|
||||
|
||||
Action1 --> Safety["Layer 3: Safety Net"]
|
||||
Safety -->|Trigger Found| Suggestion["Suggest Follow-up"]
|
||||
```
|
||||
69
docs/intent/mvp-userstories-intent-system.md
Normal file
69
docs/intent/mvp-userstories-intent-system.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 🚀 MVP Userstories & Scope: Swift Cortex (Public Prototype)
|
||||
|
||||
**Betreft:** Scope voor de "Build in Public" fase van het Swift Intent System V2.
|
||||
**Doel:** Een werkend, indrukwekkend prototype neerzetten dat de kernwaarde van "Agency" (Multi-intent & Context) demonstreert, zonder te verzanden in productie-complexiteit.
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope Definitie
|
||||
|
||||
We splitsen de ontwikkeling in **MVP** (Wat we nu bouwen voor de publieke demo) en **Post-MVP** (Wat nodig is voor een veilig medisch productiesysteem).
|
||||
|
||||
### ✅ In Scope (MVP - "The Prototype")
|
||||
*Focus: Wow-factor, core mechanics, demonstratie van intelligentie.*
|
||||
|
||||
1. **Hybrid Architecture:** De naadloze switch tussen Layer 1 (Reflex) en Layer 2 (AI).
|
||||
2. **Multi-Intent:** Het kunnen verwerken van samengestelde zinnen ("Zeg af en maak notitie").
|
||||
3. **Context Awareness:** Het correct interpreteren van "hij", "deze", "morgen" o.b.v. de huidige schermstatus.
|
||||
4. **UI Feedback:** Visualisatie van het "denkproces" en gestapelde resultaten (Stacked Cards).
|
||||
5. **Basic Safety Net:** Eén hardcoded voorbeeld van proactiviteit (bijv. "Wondcontrole suggestie") om het concept te tonen.
|
||||
|
||||
### ❌ Out of Scope (Post-MVP - "The Product")
|
||||
*Focus: Veiligheid, robuustheid, edge-cases.*
|
||||
|
||||
1. **Complete Medische Protocollen:** Geen volledige rule-engine voor alle ziektebeelden.
|
||||
2. **Complex Rollback:** Geen "Undo" knop voor database mutaties (wel confirmaties vooraf).
|
||||
3. **Offline Mode:** Het prototype gaat uit van internetverbinding.
|
||||
4. **Advanced Error Handling:** Geen automatische retry-mechanismes of fallbacks als de LLM down is.
|
||||
5. **Analytics & Learning:** Geen telemetry opslag voor model-training.
|
||||
|
||||
---
|
||||
|
||||
## 2. MVP User Stories
|
||||
|
||||
Deze stories zijn leidend voor de aankomende bouwfase.
|
||||
|
||||
### Thema 1: De Slimme Assistent (Core Intelligence)
|
||||
|
||||
| ID | Story | Acceptatie Criteria |
|
||||
|----|-------|---------------------|
|
||||
| **US-MVP-01** | Als gebruiker wil ik **twee acties in één zin** kunnen geven ("Zeg Jan af en mail de huisarts"), zodat ik niet hoef te wachten. | - Systeem herkent "en/daarna" signalen.<br>- UI toont twee losse acties in progressie.<br>- Beide acties worden uitgevoerd. |
|
||||
| **US-MVP-02** | Als gebruiker wil ik naar **"deze patiënt"** of **"hij"** kunnen verwijzen, zodat ik natuurlijk kan spreken. | - Systeem gebruikt de `ActivePatient` uit de store om "hij" te invullen.<br>- Als er geen patiënt open staat, vraagt het systeem "Wie bedoel je?". |
|
||||
| **US-MVP-03** | Als gebruiker wil ik **impliciete tijd** ("morgen", "volgende week") kunnen gebruiken, zodat ik geen datums hoef te noemen. | - "Morgen" wordt correct vertaald naar de datum van morgen.<br>- Context (huidige tijd) wordt meegestuurd naar de AI. |
|
||||
|
||||
### Thema 2: Hybride Snelheid (Architecture)
|
||||
|
||||
| ID | Story | Acceptatie Criteria |
|
||||
|----|-------|---------------------|
|
||||
| **US-MVP-04** | Als gebruiker wil ik dat simpele commando's (**"Agenda", "Zoek Jan"**) direct werken (<20ms), zodat het systeem niet traag voelt. | - Reflex Arc (Regex) vangt deze af.<br>- Geen AI spinner zichtbaar, directe actie. |
|
||||
| **US-MVP-05** | Als gebruiker wil ik zien **dat het systeem nadenkt** bij complexe vragen, zodat ik weet dat ik moet wachten. | - Bij trage/AI acties (Layer 2) toont de UI direct een "Processing..." indicator.<br>- Geen "bevroren" scherm. |
|
||||
|
||||
### Thema 3: De Partner (Proactivity - Concept)
|
||||
|
||||
| ID | Story | Acceptatie Criteria |
|
||||
|----|-------|---------------------|
|
||||
| **US-MVP-06** | Als gebruiker wil ik een **proactieve suggestie** zien na een specifieke actie (bijv. wondzorg), zodat ik snap dat het systeem meedenkt. | - *Demo Case:* Na invoeren "Wondverzorging" toont systeem kaartje: "Afspraak wondcontrole inplannen?".<br>- Klikken op "Ja" opent direct het planningsscherm. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Technische Randvoorwaarden (MVP)
|
||||
|
||||
* **Model:** Claude 3.5 Haiku (via Anthropic API).
|
||||
* **Latency Target:** Reflex < 50ms, AI < 1.5s (acceptabel voor prototype).
|
||||
* **Data:** We gebruiken mock-data voor patiënten en agenda, geen echte EPD koppeling.
|
||||
|
||||
## 4. 'Build in Public' roadmap
|
||||
|
||||
1. **Week 1:** De "Reflex" werkend krijgen (De basis).
|
||||
2. **Week 2:** De "Cortex" aansluiten (Multi-intent parsing demo).
|
||||
3. **Week 3:** De UI polijsten (Stacked Cards & Animaties) & Share video.
|
||||
116
docs/intent/review-swift-cortex-v2.md
Normal file
116
docs/intent/review-swift-cortex-v2.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Review Rapport: Swift Cortex & Intent System V2
|
||||
|
||||
**Datum:** 29-12-2025
|
||||
**Betreft:** Review van `architecture-swift-cortex-v2.md` en `fo-swift-intent-system-v2.md`
|
||||
**Reviewers (Simulatie):** Architect, Backend Dev, Frontend Dev, UX Designer, QA Engineer
|
||||
|
||||
---
|
||||
|
||||
## 1. Algemene Conclusie
|
||||
Het voorgestelde **Hyper-Hybrid model (Swift Cortex)** is een sterke, volwassen architectuur die de grootste pijnpunten van V1 (traagheid bij simpele taken, domheid bij complexe taken) effectief oplost. De opsplitsing in drie lagen (Reflex, Orchestrator, Safety Net) is logisch en schaalbaar.
|
||||
|
||||
**Oordeel:** ✅ **Go for launch**, mits onderstaande punten in acht worden genomen.
|
||||
|
||||
---
|
||||
|
||||
## 2. Feedback per Rol
|
||||
|
||||
### 🏗️ Software Architect / Lead
|
||||
**Perspectief:** Systeemsamenhang, onderhoudbaarheid, risico's.
|
||||
|
||||
* **Pros:**
|
||||
* **Separation of Concerns:** De scheiding tussen deterministische regex (L1) en probabilistische AI (L2) beschermt de performance van basisfuncties.
|
||||
* **Schaalbaarheid:** L2 is losgekoppeld; we kunnen het model (Claude Haiku) later vervangen door GPT-4o of een local model zonder L1 te breken.
|
||||
* **Type Safety:** De definities voor `IntentChain` en `SwiftContext` zijn robuust.
|
||||
* **Cons / Risico's:**
|
||||
* **State Complexity:** Het beheren van een `IntentChain` (met statussen als `pending`, `executing`, `failed`) introduceert complexe state management logica. Wat als stap 1 slaagt maar stap 2 faalt? Rollback support (genoemd in L2 architecture overview) is complex om generiek te bouwen.
|
||||
* **Drift:** Risico dat L1 (Regex) en L2 (AI) uit elkaar groeien. Als de AI "agenda" anders interpreteert dan de Regex.
|
||||
* **Advies:** Begin L2 zonder volledige rollback support (fail-forward of handmatige cleanup) om complexiteit te beperken.
|
||||
|
||||
### ⚙️ Backend Developer
|
||||
**Perspectief:** Integratie, LLM API's, performance.
|
||||
|
||||
* **Pros:**
|
||||
* **Duidelijke API's:** De inputs en outputs voor de Orchestrator zijn helder gedefinieerd.
|
||||
* **Model Keuze:** Claude 3.5 Haiku is inderdaad de sweet spot voor snelheid/kosten.
|
||||
* **Cons:**
|
||||
* **Latency:** Zelfs met Haiku is ~400ms+ merkbaar. De UI "wachtstand" is cruciaal.
|
||||
* **Error Handling:** Wat als de API 500't of timeout? De fallback strategie ontbreekt in de docs (terugvallen naar L1 of "Probeer later"?).
|
||||
* **Haalbaarheid:** Goed haalbaar. De prompts (`ORCHESTRATOR_SYSTEM_PROMPT`) zien er solide uit.
|
||||
|
||||
### 🎨 Frontend Developer
|
||||
**Perspectief:** UI implementatie, feedback loops, React state.
|
||||
|
||||
* **Pros:**
|
||||
* **Store Integration:** Uitbreiding van `SwiftStore` is logisch.
|
||||
* **Reflex Snelheid:** Client-side regex betekent instant feedback (<20ms), wat de UX enorm verbetert.
|
||||
* **Cons:**
|
||||
* **UI Complexiteit:** Het visualiseren van "Stacked Cards" voor multi-intents is nieuw. Hoe tonen we de voortgang van "Actie 1 klaar, Actie 2 bezig"?
|
||||
* **Optimistic UI:** Bij L1 kunnen we optimistisch updaten. Bij L2 moeten we wachten. Deze hybride UX (soms direct, soms laden) kan inconsistent voelen als de transities niet soepel zijn.
|
||||
* **Vraag:** Moeten we de "Reflex" logica niet in een Web Worker draaien om de main thread vrij te houden, of is de regex simpel genoeg? (Waarschijnlijk simpel genoeg).
|
||||
|
||||
### 🧘 UX Designer / Product Owner
|
||||
**Perspectief:** Gebruikerswaarde, duidelijkheid, "Magic" factor.
|
||||
|
||||
* **Pros:**
|
||||
* **Killer Feature:** Multi-intent ("Zeg af en email") is een enorme meerwaarde die de gebruiker tijd bespaart.
|
||||
* **Proactivity:** De Safety Net suggesties ("Wondcontrole inplannen?") transformeren het systeem van typemachine naar partner.
|
||||
* **No Dead Ends:** Het doel "Nooit 'ik snap het niet' zeggen" is perfect.
|
||||
* **Cons / Risico's:**
|
||||
* **Uncanny Valley:** Als L1 "dom" voelt en L2 "slim", snapt de gebruiker dan wanneer hij tegen wie praat?
|
||||
* **Over-proactive:** Te veel Safety Net suggesties worden irritant (Clippy effect). "Wil je dit opslaan?" "Wil je dat doen?".
|
||||
* **Advies:** Start Safety Net met zeer conservatieve regels. Alleen medisch kritieke suggesties, geen administratieve "nagging".
|
||||
|
||||
### 🧪 QA Engineer / Tester
|
||||
**Perspectief:** Testbaarheid, betrouwbaarheid.
|
||||
|
||||
* **Pros:**
|
||||
* **L1 is testbaar:** Regex is 100% voorspelbaar -> Unit tests zijn makkelijk.
|
||||
* **Cons:**
|
||||
* **L2 is non-deterministisch:** AI output kan variëren. Hoe schrijven we E2E tests voor "Slimme interpretatie"?
|
||||
* **Context Matrix:** De hoeveelheid combinaties van Context * Input is enorm.
|
||||
* **Advies:** We hebben een "Golden Dataset" nodig van 50+ complexe zinnen die we periodiek tegen de Orchestrator draaien om regressie (dommer worden) te meten.
|
||||
|
||||
---
|
||||
|
||||
## 3. Wat ontbreekt er / Moet erbij?
|
||||
|
||||
1. **Rate Limiting & Cost Guard:** Er is geen mechanisme beschreven om misbruik of "runaway loops" (AI blijft zichzelf aanroepen) te voorkomen.
|
||||
* *Actie:* Toevoegen aan architectuur (o.a. max requests per minuut per user).
|
||||
2. **Telemetry & Feedback Loop:** Hoe weten we of de AI fout zit?
|
||||
* *Actie:* Voeg een simpele "Thumbs up/down" of "Undo" event tracking toe aan de IntentChain. Dit is cruciaal om de prompts te verbeteren.
|
||||
3. **Offline Mode:** Wat doet L2 (Orchestrator) als er geen internet is?
|
||||
* *Actie:* Expliciete fallback: "Je bent offline. Alleen basiscommando's (L1) werken nu."
|
||||
|
||||
## 4. Wat kan er (voor nu) af?
|
||||
|
||||
1. **"Severity" Sentiment Analysis** in `ExtractedEntities`:
|
||||
* *Waarom:* Leuk, maar niet essentieel voor MVP. Maakt de prompt complexer en duurder. Eerst focussen op feit-extractie.
|
||||
2. **Complex Rollback Support:**
|
||||
* *Waarom:* Het engineeren van een "Undo" voor een database-write of email-send is erg complex.
|
||||
* *Alternatief:* Vraag gewoon bevestiging vooraf bij destructieve acties (zoals ook beschreven), dat is genoeg voor V2.
|
||||
|
||||
## 5. Eindoordeel Haalbaarheid
|
||||
|
||||
| Onderdeel | Haalbaarheid | Complexiteit |
|
||||
|-----------|--------------|--------------|
|
||||
| Layer 1 (Reflex) | ⭐⭐⭐⭐⭐ (Hoog) | Laag |
|
||||
| Layer 2 (Orchestrator) | ⭐⭐⭐⭐ (Goed) | Middel |
|
||||
| Layer 3 (Safety Net) | ⭐⭐⭐⭐ (Goed) | Laag (als we simpel beginnen) |
|
||||
| Multi-Intent Frontend | ⭐⭐⭐ (Uitdagend) | Hoog (UI flows) |
|
||||
|
||||
**Advies:** Start direct met **Fase 1 (Reflex + Basic Orchestrator)**. Schuif Layer 3 (Safety Net) naar de volgende sprint om focus te houden op de core flow.
|
||||
|
||||
## 6. Concretie Actiepunten
|
||||
|
||||
Op basis van de bovenstaande analyse zijn dit de direct uit te voeren acties voor het team:
|
||||
|
||||
| Prio | Domein | Actie | Eigenaar |
|
||||
|------|--------|-------|----------|
|
||||
| 🔴 **Prio 1** | **Architectuur** | **Rate Limiting toevoegen**: Bescherm tegen LLM-kosten explosies (max req/min). | Architect / Backend |
|
||||
| 🔴 **Prio 1** | **Backend** | **Error Fallback Strategie**: Wat gebeurt er als Claude 500't? (Fallback naar L1 of error msg). | Backend |
|
||||
| 🟡 **Prio 2** | **QA** | **Golden Dataset**: Stel een lijst samen van 50 test-zinnen voor regressietests. | QA / PO |
|
||||
| 🟡 **Prio 2** | **Frontend** | **Feedback Loop**: Voeg simpele 👍/👎 toe bij AI-acties voor latere analyse. | Frontend |
|
||||
| 🟢 **Prio 3** | **Frontend** | **Offline Mode**: Detecteer connection loss en forceer L1-only modus. | Frontend |
|
||||
| 🟢 **Prio 3** | **Scope** | **Schrappen**: Verwijder 'Severity Analysis' uit de prompt (te complex voor nu). | Backend / PO |
|
||||
| 🟢 **Prio 3** | **Scope** | **Schrappen**: Verwijder complexe 'Rollback' logica, vertrouw op confirmatie-dialogen. | Architect |
|
||||
69
docs/intent/ux-evaluation-intent-scalability.md
Normal file
69
docs/intent/ux-evaluation-intent-scalability.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# UX Evaluatie: Intent System Schaalbaarheid
|
||||
|
||||
**Betreft:** Evaluatie van `architecture-intent-scalability.md` vanuit gebruikersperspectief.
|
||||
**Deelnemers:** Product Owner (PO), UX Designer (UX), Klantvertegenwoordiger (Klant).
|
||||
|
||||
---
|
||||
|
||||
## 1. Simulatie Gesprek
|
||||
|
||||
**PO:** "Bedankt dat jullie er zijn. Tech heeft een plan gemaakt voor de schaalbaarheid van ons 'Intent System'. Kort gezegd: we gaan van 7 naar 35+ intents. Om dit snel te houden, adviseren de developers **Strategie 1: Een strikte hiërarchie**. Eerst bepalen we de categorie (bijv. 'Agenda' of 'Medicatie'), en dan pas de specifieke actie. Ze zeggen dat dit de performance met factor 5 verbetert. Klinkt goed, toch?"
|
||||
|
||||
**UX:** "Ho even. 'Performance' met factor 5... waar hebben we het over? Milliseconden?"
|
||||
|
||||
**PO:** "Eh, ja. Ze zeggen dat het teruggaat van 50ms naar 10ms."
|
||||
|
||||
**UX:** *Zucht.* "Voor een gebruiker is 50ms al 'direct'. Het menselijk brein neemt alles onder de 100ms waar als instant. Gaan we hier een complex hiërarchisch systeem bouwen om 40ms te winnen die niemand voelt? Mijn zorg is de rigiditeit. Wat als een gebruiker zegt: *'Plan wondzorg in voor morgen'*?"
|
||||
|
||||
**Klant:** "Precies. Dat is een dagelijkse zin. Is dat 'Planning' (agenda) of 'Zorg' (wondzorg)?"
|
||||
|
||||
**PO:** "In het technisch voorstel zou dat waarschijnlijk onder 'Planning' vallen, omdat het woord 'plan' erin zit. Maar als het systeem denkt dat het 'Zorg' is, vindt hij nooit de intent 'afspraak maken'."
|
||||
|
||||
**Klant:** "Als dat gebeurt, haken mijn mensen af. Ze gaan niet leren praten zoals de computer. Als ze twee keer 'Ik begrijp het niet' krijgen, typen ze het wel met de hand. En dan is de hele winst van dit spraaksysteem weg."
|
||||
|
||||
**UX:** "Dat is mijn punt. Een hiërarchie introduceert een **extra faalpunt**: de categorie-detectie. Als die fout is, is alles fout. Ik keek naar dat document en zag **Strategie 4: Compositional Intents** staan. Dat gaat over 'Actie' + 'Onderwerp'. Dat klinkt veel meer zoals mensen praten."
|
||||
|
||||
**PO:** "Klopt, maar tech zegt dat Strategie 4 'refactoring' vereist en complexer is om te bouwen."
|
||||
|
||||
**UX:** "Mag ik heel eerlijk zijn? We bouwen een 'Next Gen' EPD. Als we kiezen voor de makkelijkste technische oplossing die resulteert in een domme bot, falen we. Ik heb liever dat het 200ms duurt (nog steeds snel) en dat hij *alles* snapt, dan dat hij in 10ms de verkeerde categorie kiest."
|
||||
|
||||
**Klant:** "Eens. Wat staat er nog meer in? Iets met AI?"
|
||||
|
||||
**PO:** "Ja, **Strategie 5: Hybrid AI**. Daar gebruiken we een klein AI-model om de categorie te bepalen, en dan lokale logica voor de details. Dat duurt wel iets langer, ongeveer 100ms."
|
||||
|
||||
**UX:** "Kijk, dat wordt interessant! 100ms is nog steeds perfect binnen de UX-grenzen van 'responsief'. Maar een AI is veel slimmer in context begrijpen dan een lijstje trefwoorden. Als iemand zegt: *'Ik moet even kwijt dat Jan vandaag erg onrustig was tijdens het wassen'*, snapt een AI dat dit 'Rapportage' is, terwijl een zoekwoord-systeem misschien struikelt over 'wassen' en denkt dat het een taak is."
|
||||
|
||||
**Klant:** "Wat kost dat? Die AI tokens?"
|
||||
|
||||
**PO:** "Dat is een puntje. Lokale regex is gratis. AI kost geld per bericht. Maar we hebben het over kleine modelletjes (Haiku), dus de kosten vallen mee."
|
||||
|
||||
**Klant:** "Luister, een verpleegkundige kost 40 euro per uur. Als ze per dag 10 minuten besparen door vlekkeloze spraaksturing, mag dat systeem van mij best een paar cent per dag kosten. Betrouwbaarheid boven alles."
|
||||
|
||||
**UX:** "Conclusie voor mij: Die 'Strategie 1' (Hiërarchie op regex basis) voelt als *premature optimization*. Ze optimaliseren voor processortijd, niet voor gebruikerservaring. Ik stel voor dat we inzetten op flexibiliteit."
|
||||
|
||||
**PO:** "Dus jullie zeggen: focus niet op de pure snelheidswinst van Strategie 1, maar op de 'begrijpelijkheid' van Strategie 4 of 5?"
|
||||
|
||||
**UX:** "Ja. En ik wil een 'Fail-safe' garantie. Als het hiërarchische systeem twijfelt, mag het niet zeggen 'snap ik niet'. Dan moet het doorschakelen naar die bredere AI. De gebruiker mag *nooit* last hebben van onze database-structuur."
|
||||
|
||||
---
|
||||
|
||||
## 2. Synthese & Advies
|
||||
|
||||
Op basis van de evaluatie is dit de aanbevolen `User-Centric Architecture` koers:
|
||||
|
||||
### 1. Verwerp mic-optimalisatie ten koste van UX
|
||||
Het verschil tussen 12ms en 50ms is irrelevant voor de eindgebruiker, maar een foutieve classificatie is fataal voor het vertrouwen. De "Recommended" status van **Strategie 1 (Strict Hierarchical)** wordt afgewezen als stand-alone oplossing.
|
||||
|
||||
### 2. Kies voor de Hybride Route (Strategie 5 + 1)
|
||||
We adviseren een gelaagd model dat betrouwbaarheid voorop stelt:
|
||||
* **Layer 1 (Lokaal/Snel):** Gebruik high-confidence regex patrons voor overduidelijke commando's (b.v. "Maak afspraak" = Agenda). Dit vangt 80% af met 0ms latency.
|
||||
* **Layer 2 (AI Router):** Bij twijfel (conflicterende patterns) of complexe zinnen (b.v. "Plan wondzorg"), schakel direct door naar de AI Router (Strategie 5). De 100ms latency is acceptabel.
|
||||
|
||||
### 3. Lange Termijn: Compositional Thinking (Strategie 4)
|
||||
De backend moet idealiter toe naar **Strategie 4 (Actie + Onderwerp)**.
|
||||
* Gebruikers denken in `Actie` (plannen, stoppen, starten) op een `Subject` (medicatie, afspraak).
|
||||
* Dit is robuuster dan rigide categorieën.
|
||||
|
||||
### 4. Actiepunten
|
||||
1. **UX Validatie Set:** Maak een lijst van 50 "dubbelzinnige" zinnen die categorieën kruisen (zoals "Plan wondzorg"). Test het systeem hiertegen.
|
||||
2. **Safety Net:** Implementeer de regel: *"Bij twijfel onder de 0.8 confidence -> Altijd AI Fallback gebruiken"*. Beter iets trager en goed, dan snel en fout.
|
||||
83
docs/intent/ux-simulation-intent-next-level.md
Normal file
83
docs/intent/ux-simulation-intent-next-level.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# UX Simulatie: Intent System "Next Level"
|
||||
|
||||
**Betreft:** Brainstormsessie voor de volgende fase van de Swift Intent Architectuur.
|
||||
**Doel:** Het systeem transformeren van "Command-Response" naar "Proactive Assistant".
|
||||
**Deelnemers:**
|
||||
* **PO (Product Owner):** Focus op waarde, haalbaarheid en roadmap.
|
||||
* **UX (UX Specialist):** Focus op interactie, vertrouwen en de "Invisible Interface".
|
||||
* **Vpk (Verpleegkundige):** Focus op snelheid, handen-vrij werken, administratieve lastenverlichting.
|
||||
* **Psych (Psycholoog):** Focus op nuance, cliënt-context, emotionele lading en rapportage.
|
||||
* **Regie (Psychiater/Regiebehandelaar):** Focus op veiligheid, medicatie, complexiteit en het grote plaatje.
|
||||
|
||||
---
|
||||
|
||||
## 1. De Huidige Stand van Zaken
|
||||
|
||||
**PO:** "Welkom allen. Even een recap: ons huidige systeem is *snel*. We snappen commando's als 'Maak afspraak' en 'Schrijf dagnotitie' binnen 100ms. De basis staat. Maar Colin en ik willen weten: wat is het *volgende niveau*? Waar lopen jullie in de praktijk nog tegenaan?"
|
||||
|
||||
**Vpk:** "Het is snel, ja. Maar het voelt nog wel als een *computer*. Ik moet commando's geven. Als ik een cliënt heb gewassen en een wond heb verzorgd, moet ik zeggen: *'Maak notitie: wond verzorgd'* én daarna *'Plan wondcontrole over 3 dagen'*. Waarom snapt hij niet dat bij een 'vochtige wond' er *altijd* een controle hoort?"
|
||||
|
||||
**UX:** "Interessant. Je wilt toe naar **impliciete intents**. Het systeem moet snappen dat Actie A vaak Actie B impliceert."
|
||||
|
||||
## 2. Diepgang & Context (De Psycholoog)
|
||||
|
||||
**Psych:** "Mij gaat het om de inhoud. Ik spreek cliënten die suïcidaal kunnen zijn. Als ik dicteer: *'Cliënt oogt somber, spreekt over uitzichtloosheid'*, dan wil ik niet dat Swift alleen maar zegt: 'Notitie opgeslagen'. Ik wil dat hij met me meedenkt. 'Moet ik het signaleringsplan updaten?' of 'Wil je de crisisdienst bellen?'."
|
||||
|
||||
**PO:** "Oef, dat is spannend. Dan gaan we van 'uitvoeren' naar 'adviseren'. Durven we dat aan qua liability?"
|
||||
|
||||
**Regie:** "We *moeten* dat aandurven, als 'Safety Net'. Kijk, ik schrijf medicatie voor. Als ik zeg: *'Start Lithium 400mg'*, dan verwacht ik dat Swift direct checkt: 'Hé, de laatste nierfunctie is van 6 maanden geleden. Lab aanvragen?'. Nu is het systeem passief. Ik wil een **actieve partner**."
|
||||
|
||||
## 3. Complexe Flows & Agentic Behavior
|
||||
|
||||
**UX:** "We hebben het hier over een fundamentele shift.
|
||||
* Niveau 1 (Nu): **Reactive**. Jij vraagt, wij draaien.
|
||||
* Niveau 2 (Wens): **Agentic**. Het systeem *begrijpt* processen en *stelt voor*."
|
||||
|
||||
**Vpk:** "En mag het ook minder 'gescheiden' zijn? Nu zit ik in 'Agenda modus' of 'Dossier modus'. Soms wil ik zeggen: *'Jan voelt zich niet lekker, ik meld hem af voor therapie en bel zijn huisarts'*. Dat zijn drie dingen: Notitie, Agenda wijziging, Taak aanmaken. Nu struikelt Swift daarover."
|
||||
|
||||
**PO:** "Multi-intent support. Dat staat op de backlog, maar is technisch pittig."
|
||||
|
||||
**UX:** "Voor de gebruiker bestaat er geen backlog. Voor de gebruiker is het één handeling: 'Reageer op situatie Jan'. Als wij ze dwingen dat op te knippen in drie spraakopdrachten, zijn we een obstakel."
|
||||
|
||||
**Regie:** "Precies. 'Ontlasten' betekent dat ik mijn *intentie* uitspreek, niet mijn *administratie*. Mijn intentie is 'Zorg voor Jan regelen'. De administratie (agenda, brief, notitie) is jullie probleem."
|
||||
|
||||
## 4. Nuance & "Smart Patterns"
|
||||
|
||||
**Psych:** "Nog iets kleins: toon. Als ik een notitie maak over een agressie-incident, is mijn stemgebruik anders. Kan Swift dat niet markeren? 'Let op: emotionele lading hoog'. En dat hij dat bij de overdracht aan de avonddienst highlight?"
|
||||
|
||||
**UX:** "Sentiment analysis als metadata bij intents. Heel vet. Dan wordt de intent 'Maak notitie' verrijkt met `severity: high`."
|
||||
|
||||
**Vpk:** "En alsjeblieft, stop met die eindeloze bevestigingsvragen voor dingen die ik elke dag doe. Als ik zeg 'Rapportage ADL ok', hoef ik niet te horen 'Wil je een rapportage maken met tekst ADL ok?'. Ja, natuurlijk. *Just do it*."
|
||||
|
||||
**PO:** "Klinkt als 'Adaptive Confidence'. Als je iets vaak doet, hoeft het systeem minder te vragen."
|
||||
|
||||
---
|
||||
|
||||
## 5. Synthese: Het Nieuwe "Level"
|
||||
|
||||
Op basis van dit gesprek identificeren we drie pijlers voor de optimalisatie:
|
||||
|
||||
### Pijler 1: Chain of Thought & Multi-Intents
|
||||
De gebruiker denkt niet in silo's.
|
||||
* **Use Case:** "Zeg afspraak af en maak notitie dat hij ziek is."
|
||||
* **Oplossing:** Een "Orchestrator" die de zin opsplitst en meerdere intents parallel of sequentieel afvuurt.
|
||||
|
||||
### Pijler 2: Context-Aware Proactivity (The Safety Net)
|
||||
Swift moet "weten" wat medisch logisch is.
|
||||
* **Use Case:** "Start medicatie X" -> Systeem checkt lab/interacties. "Wond verzorgd" -> Systeem suggereert vervolgafspraak.
|
||||
* **Oplossing:** `Intent Triggers`. Een intent kan een *andere* intent triggeren als suggestie (een "Follow-up Action").
|
||||
|
||||
### Pijler 3: Adaptive & Invisible Interface
|
||||
Minder frictie voor power users.
|
||||
* **Use Case:** Geen bevestiging voor routine-taken, wel voor afwijkende zaken.
|
||||
* **Oplossing:** User-specific confidence thresholds. Het systeem leert wat *jij* normaal vindt.
|
||||
|
||||
---
|
||||
|
||||
## 6. Advies aan Tech (Colin)
|
||||
|
||||
1. **Bouw een 'Intent Chainer':** Support voor `[Intent A] AND [Intent B]` in één uiting.
|
||||
2. **Implementeer 'Follow-up Suggestions':** Na succesvolle afronding van Intent A, kan de UI (of spraak) direct vragen: "Wil je ook Intent B doen?" (o.b.v. regels of AI).
|
||||
3. **Context Injectie:** De intent-classifier moet niet alleen de *zin* krijgen, maar ook de *huidige cliënt-status* (bijv. "Laatste labwaardes", "Openstaande agenda"). Dit maakt de AI slimmer zonder trager te worden.
|
||||
|
||||
**UX Conclusie:** "We stoppen met het bouwen van een 'Spraakgestuurd Toetsenbord'. We gaan bouwen aan een **AI Collega**."
|
||||
627
docs/socials/content-tijdslijn-swift-intent-driven-epd.md
Normal file
627
docs/socials/content-tijdslijn-swift-intent-driven-epd.md
Normal file
@@ -0,0 +1,627 @@
|
||||
# 📅 Content Tijdslijn: Swift Intent-Driven EPD
|
||||
|
||||
**Project:** Opvolging AI Speedrun
|
||||
**Centrale vraag:** Is het intent-driven EPD de volgende generatie?
|
||||
**Format:** Build in Public op LinkedIn
|
||||
**Auteur:** Colin Lit
|
||||
**Start:** Januari 2025
|
||||
|
||||
---
|
||||
|
||||
## De Rode Draad
|
||||
|
||||
**AI Speedrun (afgerond):**
|
||||
> "Hoe ver kom je in 4 weken met AI-tooling?"
|
||||
> → Antwoord: Je kunt de kernonderdelen van een traditioneel EPD bouwen.
|
||||
> → Cliffhanger: "Maar hoe ziet het next-gen EPD er eigenlijk uit?"
|
||||
|
||||
**Swift (nu):**
|
||||
> "Is het intent-driven EPD de volgende generatie?"
|
||||
> → Onderzoek: Wat als je niet meer navigeert, maar gewoon zegt wat je wilt?
|
||||
> → Build: Van concept naar werkend prototype
|
||||
> → Validatie: Werkt dit voor echte zorgverleners?
|
||||
|
||||
---
|
||||
|
||||
## Fase 1: De Vraag Stellen (Week 1-2)
|
||||
|
||||
### Post 1: De Cliffhanger Oppakken
|
||||
**Timing:** Week 1, dag 1
|
||||
**Type:** Tekst + afbeelding
|
||||
**Doel:** Aankondigen van het vervolg, de vraag scherp stellen
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐀𝐈 𝐒𝐏𝐄𝐄𝐃𝐑𝐔𝐍 𝟐.𝟎 - 𝐃𝐞 𝐯𝐫𝐚𝐚𝐠 𝐝𝐢𝐞 𝐛𝐥𝐞𝐞𝐟 𝐡𝐚𝐧𝐠𝐞𝐧
|
||||
|
||||
4 weken geleden sloot ik de AI Speedrun af met een werkend EPD-prototype.
|
||||
95 commits. 118.000 regels code. Van intake tot behandelplan.
|
||||
|
||||
Maar het voelde als... een snellere versie van hetzelfde.
|
||||
|
||||
Dezelfde menu's. Dezelfde klikpaden. Dezelfde logica.
|
||||
Alleen gebouwd met AI in plaats van een team van 10.
|
||||
|
||||
De vraag die bleef hangen:
|
||||
𝑊𝑎𝑡 𝑎𝑙𝑠 𝑤𝑒 𝑛𝑖𝑒𝑡 𝑠𝑛𝑒𝑙𝑙𝑒𝑟 ℎ𝑒𝑡𝑧𝑒𝑙𝑓𝑑𝑒 𝑏𝑜𝑢𝑤𝑒𝑛, 𝑚𝑎𝑎𝑟 𝑖𝑒𝑡𝑠 𝑓𝑢𝑛𝑑𝑎𝑚𝑒𝑛𝑡𝑒𝑒𝑙 𝑎𝑛𝑑𝑒𝑟𝑠?
|
||||
|
||||
Dus ik ging graven. In de research. In de trends.
|
||||
En ik stuitte op een term die steeds terugkwam:
|
||||
|
||||
**Intent-driven UI.**
|
||||
|
||||
Jakob Nielsen (ja, die van de 10 usability heuristics) noemt het
|
||||
"het eerste nieuwe UI-paradigma in 60 jaar."
|
||||
|
||||
Het idee: je vertelt het systeem niet meer *hoe* je iets moet doen.
|
||||
Je vertelt het *wat* je wilt bereiken.
|
||||
|
||||
"Notitie voor Jan over de medicatie."
|
||||
En het juiste scherm verschijnt. Met de juiste velden. Voor de juiste patiënt.
|
||||
|
||||
Geen menu's. Geen 12 klikken. Gewoon: zeggen wat je wilt.
|
||||
|
||||
Dit is mijn volgende experiment.
|
||||
|
||||
De komende weken ga ik uitzoeken:
|
||||
- Werkt dit concept in de praktijk?
|
||||
- Kun je het bouwen met de huidige AI-tooling?
|
||||
- En belangrijker: willen zorgverleners dit eigenlijk?
|
||||
|
||||
Ik noem het **Swift** - omdat snelheid de kern is.
|
||||
|
||||
Volg mee. Ik deel alles. De successen én de momenten dat ik denk:
|
||||
dit werkt voor geen meter.
|
||||
|
||||
Eerste vraag aan jullie:
|
||||
👇 Hoeveel klikken kost jouw meest voorkomende handeling in je EPD?
|
||||
```
|
||||
|
||||
**Visual:** Screenshot van traditionele EPD-navigatie vs. één inputveld met "notitie Jan medicatie"
|
||||
|
||||
---
|
||||
|
||||
### Post 2: Het Probleem Concreet Maken
|
||||
**Timing:** Week 1, dag 4
|
||||
**Type:** Carrousel (4-5 slides)
|
||||
**Doel:** Het probleem voelbaar maken voor de doelgroep
|
||||
|
||||
**Carrousel slides:**
|
||||
|
||||
**Slide 1:** "12 klikken voor één notitie"
|
||||
**Slide 2:** De klikroute uitgetekend (Menu → Patiënten → Zoeken → Jan → Dossier → Rapportage → Nieuwe notitie → etc.)
|
||||
**Slide 3:** "Wat als je gewoon zegt: 'Notitie Jan medicatie'?"
|
||||
**Slide 4:** Het concept: één inputveld, systeem begrijpt intentie
|
||||
**Slide 5:** "Dit ga ik de komende weken bouwen. Volg mee."
|
||||
|
||||
---
|
||||
|
||||
### Post 3: De Research Delen
|
||||
**Timing:** Week 2, dag 1
|
||||
**Type:** Tekst + link naar bronnenonderzoek
|
||||
**Doel:** Autoriteit opbouwen, laten zien dat dit niet uit de lucht komt vallen
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - 𝐖𝐚𝐭 𝐝𝐞 𝐞𝐱𝐩𝐞𝐫𝐭𝐬 𝐳𝐞𝐠𝐠𝐞𝐧
|
||||
|
||||
Voordat ik ga bouwen, wilde ik weten:
|
||||
ben ik gek, of zien anderen dit ook?
|
||||
|
||||
Dus ik dook in de research. Dit vond ik:
|
||||
|
||||
**Jakob Nielsen** (mei 2023):
|
||||
"AI introduceert het 3e UI-paradigma in de computergeschiedenis.
|
||||
Intent-based outcome specification."
|
||||
|
||||
**Nielsen Norman Group** (2024):
|
||||
"Outcome-oriented design - designers definiëren constraints,
|
||||
AI genereert de interface."
|
||||
|
||||
**Google** (december 2024):
|
||||
Rolt "Dynamic View" uit - Gemini genereert complete interfaces per prompt.
|
||||
|
||||
**Vercel**:
|
||||
Open-sourcet hun Generative UI technologie van v0.dev.
|
||||
|
||||
De grote jongens bewegen allemaal dezelfde kant op.
|
||||
|
||||
Maar weet je wat ik nergens vond?
|
||||
|
||||
**Een implementatie voor healthcare.**
|
||||
|
||||
Epic doet AI voor notities schrijven. Oracle Health doet voice commands.
|
||||
Maar niemand doet intent-driven navigatie in een EPD.
|
||||
|
||||
Niemand zegt: "notitie Jan" en krijgt direct het juiste scherm.
|
||||
|
||||
Dat is de gap. En dat is wat ik ga bouwen.
|
||||
|
||||
📎 Ik heb al mijn bronnen verzameld in een document.
|
||||
Comment "BRONNEN" en ik stuur je de link.
|
||||
|
||||
Volgende week: de eerste regels code.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fase 2: De Bouw (Week 3-6)
|
||||
|
||||
### Post 4: Dag 1 - De Eerste Intentie
|
||||
**Timing:** Week 3, dag 1
|
||||
**Type:** Video (30-60 sec) + tekst
|
||||
**Doel:** Laten zien dat het werkt, hype creëren
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - 𝐃𝐚𝐠 𝟏: 𝐇𝐞𝐭 𝐰𝐞𝐫𝐤𝐭
|
||||
|
||||
Ik typte: "notitie Jan medicatie"
|
||||
|
||||
Het systeem:
|
||||
1. Herkende de intentie (dagnotitie)
|
||||
2. Vond de patiënt (Jan de Vries)
|
||||
3. Opende het juiste formulier
|
||||
4. Vulde de context in
|
||||
|
||||
Tijd: 1.2 seconden.
|
||||
|
||||
[VIDEO: schermopname van de flow]
|
||||
|
||||
Oké, het is nog lelijk. De UI is basic.
|
||||
Maar de kern werkt.
|
||||
|
||||
De AI begrijpt wat ik wil en geeft me direct waar ik moet zijn.
|
||||
|
||||
Dit is dag 1.
|
||||
|
||||
De stack tot nu toe:
|
||||
- Next.js voor de frontend
|
||||
- Claude voor intent-classificatie
|
||||
- ~200 regels code
|
||||
|
||||
Volgende stap: meerdere intenties herkennen.
|
||||
"zoek Piet" / "overdracht" / "behandelplan Marie"
|
||||
|
||||
De vraag die ik mezelf stel:
|
||||
Hoe ver kan ik komen voordat de complexiteit explodeert?
|
||||
|
||||
Wordt vervolgd. 🏃♂️
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Post 5: De Intenties Uitbreiden
|
||||
**Timing:** Week 3, dag 4
|
||||
**Type:** Tekst + afbeelding (intent mapping diagram)
|
||||
**Doel:** Technische diepgang voor de nerds, toegankelijk voor de rest
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - 𝟖 𝐢𝐧𝐭𝐞𝐧𝐭𝐢𝐞𝐬, 𝟏 𝐢𝐧𝐩𝐮𝐭𝐯𝐞𝐥𝐝
|
||||
|
||||
Deze week uitgebreid van 1 naar 8 intenties:
|
||||
|
||||
"notitie Jan" → Dagnotitie opent voor Jan
|
||||
"zoek Piet" → Patiënt zoeken, resultaten tonen
|
||||
"overdracht" → Overdrachtsscherm met AI-samenvatting
|
||||
"behandelplan Marie" → Behandelplan Marie opent
|
||||
"intake nieuwe" → Nieuwe intake starten
|
||||
"agenda vandaag" → Dagagenda tonen
|
||||
"medicatie Jan" → Medicatie-overzicht Jan
|
||||
"vitalen Piet" → Vitale functies invoeren
|
||||
|
||||
Hoe werkt het onder de motorkap?
|
||||
|
||||
Stap 1: Snelle keyword-matching (~10ms)
|
||||
"notitie" + naam = dagnotitie intent, hoge confidence
|
||||
|
||||
Stap 2: AI-fallback voor ambigue input (~200ms)
|
||||
"ik heb net iets besproken met Jan" = AI bepaalt: dagnotitie
|
||||
|
||||
De truc: lokaal waar het kan, AI waar het moet.
|
||||
|
||||
Resultaat tot nu toe:
|
||||
- 8 werkende intenties
|
||||
- ~400 regels code
|
||||
- Intent accuracy: ~90% op mijn testset
|
||||
|
||||
Wat nog moet:
|
||||
- Ambigue namen ("Jan" → welke Jan?)
|
||||
- Context-switching (midden in een taak iets anders doen)
|
||||
- Voice input (dicteren in plaats van typen)
|
||||
|
||||
De echte test komt volgende week:
|
||||
Ik ga het laten zien aan een verpleegkundige.
|
||||
|
||||
Spannend. Want dan weet ik of dit alleen cool is voor developers,
|
||||
of ook echt nuttig voor de mensen die ermee moeten werken.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Post 6: De Eerste Gebruikerstest
|
||||
**Timing:** Week 4, dag 2
|
||||
**Type:** Tekst + quote van tester
|
||||
**Doel:** Validatie, social proof, kwetsbaarheid tonen
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - "𝐃𝐢𝐭 𝐢𝐬 𝐰𝐚𝐭 𝐢𝐤 𝐚𝐥𝐭𝐢𝐣𝐝 𝐰𝐢𝐥𝐝𝐞"
|
||||
|
||||
Gisteren liet ik Swift zien aan [naam], verpleegkundige in de GGZ.
|
||||
|
||||
Haar eerste reactie toen ze "notitie Jan medicatie" typte
|
||||
en direct in het juiste scherm zat:
|
||||
|
||||
"Wacht. Dat is het? Geen menu's?"
|
||||
|
||||
Ik knikte.
|
||||
|
||||
"Dit is wat ik altijd wilde. Gewoon zeggen wat ik wil doen
|
||||
in plaats van zoeken waar het zit."
|
||||
|
||||
Toen de kritische vragen:
|
||||
- "Wat als ik me vertype?" → Fuzzy matching, suggesties
|
||||
- "Wat als er twee Jannen zijn?" → Keuzelijst
|
||||
- "Wat met privacy?" → Alles lokaal, niets naar externe servers*
|
||||
|
||||
*behalve de AI-calls, maar die bevatten geen patiëntdata in dit prototype
|
||||
|
||||
Wat niet werkte:
|
||||
- Voice input was te traag in haar test
|
||||
- Sommige intenties waren niet intuïtief ("vitalen" vs "bloeddruk")
|
||||
- Ze wilde kunnen praten, niet typen
|
||||
|
||||
Notities voor volgende iteratie:
|
||||
1. Voice-first maken, typen als fallback
|
||||
2. Synoniemen toevoegen aan intent-matching
|
||||
3. Sneltoetsen voor power users
|
||||
|
||||
Dit is waarom je bouwt in het openbaar.
|
||||
Je krijgt feedback die je zelf nooit had bedacht.
|
||||
|
||||
[naam], bedankt voor je eerlijkheid. 🙏
|
||||
|
||||
Wie wil Swift ook testen? DM me.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Post 7: Voice Input Toevoegen
|
||||
**Timing:** Week 5, dag 1
|
||||
**Type:** Video (60 sec) + tekst
|
||||
**Doel:** Wow-moment, laten zien dat het next-level gaat
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - 𝐍𝐮 𝐦𝐞𝐭 𝐬𝐭𝐞𝐦
|
||||
|
||||
"Notitie Jan, medicatie gegeven om 14 uur."
|
||||
|
||||
[VIDEO: Colin spreekt, systeem opent notitie met vooringevulde tekst]
|
||||
|
||||
Na de feedback van vorige week: voice-first.
|
||||
|
||||
Wat er gebeurt:
|
||||
1. Deepgram vangt de spraak op (real-time)
|
||||
2. Intent-classificatie bepaalt: dagnotitie voor Jan
|
||||
3. De gesproken tekst wordt direct in de notitie gezet
|
||||
4. Je hoeft alleen nog te reviewen en opslaan
|
||||
|
||||
Van spraak naar opgeslagen notitie: ~8 seconden.
|
||||
|
||||
Vergelijk dat met:
|
||||
Menu → Patiënten → Zoeken → Jan → Dossier → Rapportage →
|
||||
Nieuwe notitie → Typen → Opslaan
|
||||
|
||||
Dat is ~45 seconden. Minstens.
|
||||
|
||||
De techniek:
|
||||
- Deepgram Nova-2 voor Nederlands
|
||||
- Streaming transcriptie (je ziet tekst verschijnen terwijl je praat)
|
||||
- Lokale intent-classificatie + AI-fallback
|
||||
|
||||
Kosten: ~€0.01 per minuut spraak.
|
||||
|
||||
Wat ik leerde:
|
||||
- Nederlandse spraakherkenning is verbazend goed geworden
|
||||
- "Medicatie" wordt soms "Medicare" (lol)
|
||||
- Push-to-talk werkt beter dan continuous listening
|
||||
|
||||
Volgende stap: testen in een echte omgeving.
|
||||
Met achtergrondgeluid. Collega's die praten. Telefoons die rinkelen.
|
||||
|
||||
Daar gaat het spannend worden.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Post 8: De Beperkingen Eerlijk Benoemen
|
||||
**Timing:** Week 5, dag 4
|
||||
**Type:** Tekst
|
||||
**Doel:** Geloofwaardigheid door kwetsbaarheid, eerlijk zijn over wat niet werkt
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - 𝐖𝐚𝐭 (𝐧𝐨𝐠) 𝐧𝐢𝐞𝐭 𝐰𝐞𝐫𝐤𝐭
|
||||
|
||||
Tijd voor eerlijkheid.
|
||||
|
||||
Swift is cool in een demo. Maar het is geen product.
|
||||
|
||||
Dit werkt nog niet:
|
||||
|
||||
**1. Complexe intenties**
|
||||
"Maak een afspraak met Jan volgende week dinsdag om 14:00
|
||||
voor een medicatie-evaluatie"
|
||||
→ Te veel variabelen. AI raakt in de war.
|
||||
|
||||
**2. Context over sessies heen**
|
||||
Als je gisteren met Jan bezig was en vandaag terugkomt,
|
||||
weet Swift dat niet. Elke sessie begint blanco.
|
||||
|
||||
**3. Fouten herstellen**
|
||||
Als het systeem de verkeerde patiënt pakt,
|
||||
moet je handmatig terug. Geen "nee, ik bedoelde de andere Jan."
|
||||
|
||||
**4. Integratie met bestaande systemen**
|
||||
Swift praat nu met zijn eigen database.
|
||||
Niet met ChipSoft. Niet met Nedap. Niet met Epic.
|
||||
|
||||
**5. Wet- en regelgeving**
|
||||
HIPAA? AVG? NEN7510?
|
||||
Dit is een prototype, geen gecertificeerd medisch hulpmiddel.
|
||||
|
||||
Waarom deel ik dit?
|
||||
|
||||
Omdat "build in public" niet alleen de successen is.
|
||||
Het is ook eerlijk zijn over de gaten.
|
||||
|
||||
De vraag die ik mezelf stel:
|
||||
Zijn dit oplosbare problemen, of fundamentele beperkingen van het concept?
|
||||
|
||||
Ik denk: oplosbaar. Maar het kost tijd.
|
||||
Meer tijd dan 4 weken.
|
||||
|
||||
Dit wordt geen sprint meer. Dit wordt een marathon.
|
||||
|
||||
Wie heeft ervaring met het oplossen van deze problemen?
|
||||
👇 Ik hoor graag hoe jullie dit aanpakken.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fase 3: De Reflectie (Week 6-8)
|
||||
|
||||
### Post 9: Terugblik - Wat Hebben We Geleerd?
|
||||
**Timing:** Week 6, dag 2
|
||||
**Type:** Tekst + infographic
|
||||
**Doel:** Samenvatten, autoriteit claimen, community betrekken
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - 𝟔 𝐰𝐞𝐤𝐞𝐧, 𝐰𝐚𝐭 𝐡𝐞𝐛𝐛𝐞𝐧 𝐰𝐞 𝐠𝐞𝐥𝐞𝐞𝐫𝐝?
|
||||
|
||||
De centrale vraag was:
|
||||
"Is het intent-driven EPD de volgende generatie?"
|
||||
|
||||
Mijn antwoord na 6 weken bouwen:
|
||||
|
||||
**Ja, maar.**
|
||||
|
||||
✅ Het concept werkt
|
||||
Van "notitie Jan medicatie" naar opgeslagen rapportage in 8 seconden.
|
||||
Dat is geen science fiction meer. Dat is werkende code.
|
||||
|
||||
✅ Zorgverleners willen dit
|
||||
Elke test-gebruiker zei varianten van:
|
||||
"Waarom kan mijn EPD dit niet?"
|
||||
|
||||
✅ De technologie is er
|
||||
Spraakherkenning, intent-classificatie, generative UI.
|
||||
De bouwblokken bestaan. Je hoeft ze alleen te combineren.
|
||||
|
||||
❌ Maar het is niet plug-and-play
|
||||
Integratie met bestaande EPD's is een nachtmerrie.
|
||||
Certificering kost tijd en geld.
|
||||
En de edge cases zijn eindeloos.
|
||||
|
||||
**De echte insight:**
|
||||
|
||||
Het gaat niet om een nieuw EPD bouwen.
|
||||
Het gaat om een **laag** bovenop bestaande systemen.
|
||||
|
||||
Zoals Raycast bovenop macOS.
|
||||
Zoals Superhuman bovenop email.
|
||||
|
||||
Een intent-driven interface die praat met whatever EPD eronder zit.
|
||||
|
||||
Dat is de volgende stap.
|
||||
|
||||
Wil je mee? Ik zoek:
|
||||
- GGZ-organisaties die willen piloten
|
||||
- EPD-leveranciers die willen samenwerken
|
||||
- Developers die dit interessant vinden
|
||||
|
||||
DM staat open. 🚀
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Post 10: De Visie - Waar Gaat Dit Naartoe?
|
||||
**Timing:** Week 7, dag 1
|
||||
**Type:** Tekst + concept visual
|
||||
**Doel:** Thought leadership, positioneren voor de lange termijn
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐇𝐨𝐞 𝐢𝐤 𝐝𝐞𝐧𝐤 𝐝𝐚𝐭 𝐄𝐏𝐃'𝐬 𝐞𝐫 𝐨𝐯𝐞𝐫 𝟓 𝐣𝐚𝐚𝐫 𝐮𝐢𝐭𝐳𝐢𝐞𝐧
|
||||
|
||||
Geen menu's met 47 opties.
|
||||
Geen tabbladen die je moet onthouden.
|
||||
Geen handleidingen van 200 pagina's.
|
||||
|
||||
In plaats daarvan:
|
||||
|
||||
**Je zegt wat je wilt. Het systeem doet de rest.**
|
||||
|
||||
"Bereid mijn spreekuur voor."
|
||||
→ Alle relevante info van je patiënten staat klaar.
|
||||
|
||||
"Wat is er veranderd sinds mijn laatste dienst?"
|
||||
→ AI-samenvatting van alle updates, gefilterd op wat voor jou relevant is.
|
||||
|
||||
"Start overdracht."
|
||||
→ Voice-gestuurde rapportage die direct in het dossier komt.
|
||||
|
||||
Dit is niet mijn fantasie.
|
||||
|
||||
Dit is waar Google, Apple, en Microsoft allemaal naartoe werken.
|
||||
Jakob Nielsen noemt het "het 3e UI-paradigma in 60 jaar."
|
||||
|
||||
Healthcare loopt altijd 10 jaar achter op consumer tech.
|
||||
|
||||
Maar dat hoeft niet.
|
||||
|
||||
De afgelopen weken heb ik laten zien dat de technologie er is.
|
||||
De vraag is: wie durft het eerste te implementeren?
|
||||
|
||||
Ik zet mijn geld op de kleine, wendbare spelers.
|
||||
Niet de dinosaurussen met legacy systemen.
|
||||
|
||||
De startups. De innovatieve zorginstellingen.
|
||||
De mensen die snappen dat de huidige situatie onhoudbaar is.
|
||||
|
||||
Ben jij er één van? Laten we praten.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Post 11: Call-to-Action - Wat Nu?
|
||||
**Timing:** Week 8, dag 1
|
||||
**Type:** Tekst
|
||||
**Doel:** Leads genereren, concrete volgende stappen
|
||||
|
||||
**Concept:**
|
||||
```
|
||||
𝐒𝐖𝐈𝐅𝐓 - 𝐖𝐚𝐭 𝐧𝐮?
|
||||
|
||||
8 weken geleden begon ik met een vraag:
|
||||
"Is het intent-driven EPD de volgende generatie?"
|
||||
|
||||
Nu heb ik:
|
||||
- Een werkend prototype
|
||||
- Validatie van 5 zorgverleners
|
||||
- Een duidelijke visie op waar dit naartoe gaat
|
||||
- En een inbox vol met "wanneer kan ik dit gebruiken?"
|
||||
|
||||
Het eerlijke antwoord: nog niet.
|
||||
|
||||
Swift is een prototype. Een proof of concept.
|
||||
Geen product dat je morgen kunt kopen.
|
||||
|
||||
Maar ik wil het wel een product maken.
|
||||
|
||||
Daarom zoek ik:
|
||||
|
||||
**🏥 Zorgorganisaties**
|
||||
Die willen piloten met intent-driven documentatie.
|
||||
Klein beginnen. Eén afdeling. Echte feedback.
|
||||
|
||||
**🤝 EPD-leveranciers**
|
||||
Die snappen dat de toekomst niet in meer features zit,
|
||||
maar in betere interactie. Partnerschap > concurrentie.
|
||||
|
||||
**💻 Developers met zorg-ervaring**
|
||||
Die dit net zo frustrerend vinden als ik
|
||||
en er iets aan willen doen.
|
||||
|
||||
**📣 Mensen die dit verhaal willen delen**
|
||||
Want verandering begint met bewustwording.
|
||||
|
||||
Dit is geen einde. Dit is een begin.
|
||||
|
||||
De AI Speedrun was het bewijs dat je snel kunt bouwen.
|
||||
Swift is het bewijs dat je fundamenteel anders kunt bouwen.
|
||||
|
||||
De volgende stap is het bewijs dat het ook werkt in de echte wereld.
|
||||
|
||||
Wie doet mee?
|
||||
|
||||
---
|
||||
|
||||
P.S. Alle code, documentatie en bronnen die ik heb gebruikt:
|
||||
[link naar repository of site]
|
||||
|
||||
Transparantie tot het einde. 🏃♂️
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optionele Posts (tussentijds)
|
||||
|
||||
### Quick Win Posts
|
||||
Korte updates als er iets cools gebeurt:
|
||||
|
||||
- **"Net een bug gefixt die 3 dagen kostte"** - Authenticiteit
|
||||
- **"Iemand noemde dit 'magie'"** - Social proof
|
||||
- **"Plot twist: dit idee bestaat al 60 jaar"** - Nielsen referentie
|
||||
- **"Mijn vrouw vroeg wanneer ik weer normaal ga doen"** - Humor/menselijkheid
|
||||
|
||||
### Engagement Posts
|
||||
Vragen aan de community:
|
||||
|
||||
- **"Wat is jouw meest gefrustreerde EPD-moment?"**
|
||||
- **"Hoeveel tijd ben je kwijt aan navigeren vs. documenteren?"**
|
||||
- **"Zou je voice-input gebruiken als het goed werkte?"**
|
||||
|
||||
### Educational Posts
|
||||
Uitleg voor niet-techneuten:
|
||||
|
||||
- **"Intent-driven UI in 60 seconden uitgelegd"** - Carrousel
|
||||
- **"Waarom je EPD voelt als software uit 2005"** - Achtergrond
|
||||
- **"De 3 UI-paradigma's volgens Jakob Nielsen"** - Autoriteit
|
||||
|
||||
---
|
||||
|
||||
## Samenvattend
|
||||
|
||||
| Week | Post | Type | Doel |
|
||||
|------|------|------|------|
|
||||
| 1 | Cliffhanger oppakken | Tekst + visual | Aankondigen |
|
||||
| 1 | Het probleem | Carrousel | Herkenning |
|
||||
| 2 | De research | Tekst + link | Autoriteit |
|
||||
| 3 | Dag 1: Het werkt | Video + tekst | Hype |
|
||||
| 3 | Intenties uitbreiden | Tekst + diagram | Technisch |
|
||||
| 4 | Eerste gebruikerstest | Tekst + quote | Validatie |
|
||||
| 5 | Voice input | Video + tekst | Wow-moment |
|
||||
| 5 | Beperkingen | Tekst | Eerlijkheid |
|
||||
| 6 | Terugblik | Tekst + infographic | Samenvatten |
|
||||
| 7 | De visie | Tekst + visual | Thought leadership |
|
||||
| 8 | Call-to-action | Tekst | Leads |
|
||||
|
||||
---
|
||||
|
||||
## Toon & Stijl (consistent met AI Speedrun)
|
||||
|
||||
**Wel:**
|
||||
- Eerste persoon ("ik bouw", niet "wij bouwen")
|
||||
- Concrete cijfers en tijdsindicaties
|
||||
- Eerlijk over wat niet werkt
|
||||
- Retorische vragen die prikken
|
||||
- Emoji's spaarzaam (max 2-3 per post)
|
||||
- Bold/cursief voor nadruk
|
||||
- Korte zinnen, witruimte
|
||||
- CTA aan het einde
|
||||
|
||||
**Niet:**
|
||||
- Corporate jargon ("synergy", "leverage")
|
||||
- Overdreven claims ("revolutionair", "baanbrekend")
|
||||
- Alleen maar successen delen
|
||||
- Te technisch voor de doelgroep
|
||||
- Lange lappen tekst zonder structuur
|
||||
|
||||
---
|
||||
|
||||
*Dit document is een levend plan - pas aan op basis van wat werkt en wat niet.*
|
||||
@@ -1,879 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 🚀 Mission Control — Bouwplan Swift v3.0
|
||||
|
||||
💡 **Transformatie:** Van Command Center naar Medical Scribe Chatbot Interface
|
||||
💡 **Transformatie:** Van Command Center naar Swift Assistent Chatbot Interface
|
||||
|
||||
---
|
||||
|
||||
**Projectnaam:** Swift Medical Scribe v3.0
|
||||
**Projectnaam:** Swift Swift Assistent v3.0
|
||||
**Versie:** v3.0
|
||||
**Datum:** 27-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
## 1. Doel en context
|
||||
|
||||
🎯 **Doel:** Swift transformeren van een command-line style interface naar een conversational medical scribe chatbot met split-screen layout (chat links, artifacts rechts).
|
||||
🎯 **Doel:** Swift transformeren van een command-line style interface naar een conversational Swift Assistent chatbot met split-screen layout (chat links, artifacts rechts).
|
||||
|
||||
📘 **Context:**
|
||||
De huidige Swift v2.1 werkt met een command-line paradigma waar gebruikers kort commando's typen ("notitie jan medicatie"). Dit werkt goed, maar voelt transactioneel aan. Gebruikers willen doorvragen, context behouden, en natuurlijker interacteren met het systeem.
|
||||
@@ -29,7 +29,7 @@ De huidige Swift v2.1 werkt met een command-line paradigma waar gebruikers kort
|
||||
4. **Bekende UX** — Lijkt op ChatGPT Canvas / Claude Artifacts (bekend voor gebruikers)
|
||||
|
||||
**Referenties:**
|
||||
- **FO v3.0:** `fo-swift-medical-scribe-v3.md` — Functioneel ontwerp medical scribe
|
||||
- **FO v3.0:** `fo-swift-medical-scribe-v3.md` — Functioneel ontwerp Swift Assistent
|
||||
- **Haalbaarheid:** `haalbaarheidsanalyse-v3.md` — Feasibility analysis (6-8 weken, haalbaar)
|
||||
- **UX Analyse:** `v3-redesign-met-huidige-styling.md` — Wat blijft vs. wijzigt
|
||||
- **UX v2.1:** `archive/swift-ux-v2.1.md` — Huidige UX/styling
|
||||
@@ -221,7 +221,7 @@ const useChatStore = create<ChatState>((set) => ({
|
||||
| E0 | Pre-work & Planning | Design tokens, component audit, system prompt | ✅ **Compleet** | 3/3 | 5 SP | Docs aangemaakt |
|
||||
| E1 | Foundation - Split-screen | Layout naar 40/60 split | ✅ **Compleet** | 3/3 | 12 SP | E1.S1 geskipt (geen feature flag) |
|
||||
| E2 | Chat Panel & Messages | Chat UI zonder AI | ✅ **Compleet** | 5/5 | 13 SP | Scrolling, input, shortcuts |
|
||||
| E3 | Chat API & Medical Scribe | AI conversatie werkend | ✅ **Compleet** | 6/6 | 21 SP | Artifact opening werkend! |
|
||||
| E3 | Chat API & Swift Assistent | AI conversatie werkend | ✅ **Compleet** | 6/6 | 21 SP | Artifact opening werkend! |
|
||||
| E4 | Artifact Area & Tabs | Meerdere artifacts mogelijk | ✅ **Compleet** | 3/4 | 10 SP | E4.S4 geskipt (placeholder in E4.S1) |
|
||||
| E5 | AI-Filtering & Polish | Psychiater filtering, polish | ✅ **Compleet** | 5/5 | 13 SP | E5 COMPLEET 🎉 |
|
||||
| E6 | Testing & Refinement | QA, bugs, performance | ⏳ To Do | 0/4 | 8 SP | Week 7-8 |
|
||||
@@ -240,7 +240,7 @@ const useChatStore = create<ChatState>((set) => ({
|
||||
|
||||
### Epic 0 — Pre-work & Planning ✅ **COMPLEET**
|
||||
|
||||
**Epic Doel:** Voorbereiding werk voordat development start. Design tokens verificatie, component audit, medical scribe system prompt.
|
||||
**Epic Doel:** Voorbereiding werk voordat development start. Design tokens verificatie, component audit, Swift Assistent system prompt.
|
||||
|
||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||
@@ -400,7 +400,7 @@ const MESSAGE_STYLES = {
|
||||
|
||||
---
|
||||
|
||||
### Epic 3 — Chat API & Medical Scribe ✅ **COMPLEET**
|
||||
### Epic 3 — Chat API & Swift Assistent ✅ **COMPLEET**
|
||||
|
||||
**Epic Doel:** AI conversatie werkend krijgen met intent detection en artifact opening.
|
||||
|
||||
@@ -441,7 +441,7 @@ export async function POST(req: Request) {
|
||||
|
||||
**E3.S3 - System Prompt (samenvatting):**
|
||||
```
|
||||
Je bent een medische assistent (medical scribe) voor Swift, een Nederlands GGZ EPD.
|
||||
Je bent een medische assistent (Swift Assistent) voor Swift, een Nederlands GGZ EPD.
|
||||
|
||||
Je rol:
|
||||
- Help zorgmedewerkers met documentatie en administratie
|
||||
@@ -542,7 +542,7 @@ export function useChatStream() {
|
||||
- ✅ Conversation history (max 20 messages)
|
||||
|
||||
**Deliverables (E3.S3 compleet):**
|
||||
- ✅ `buildMedicalScribePrompt()` functie (243 regels) — Volledige medical scribe prompt v1.0
|
||||
- ✅ `buildMedicalScribePrompt()` functie (243 regels) — Volledige Swift Assistent prompt v1.0
|
||||
- ✅ Intent detection instructies: dagnotitie, zoeken, overdracht, rapportage
|
||||
- ✅ P1 & P2 intents met triggers en entities
|
||||
- ✅ Confidence thresholds (>0.9, 0.7-0.9, 0.5-0.7, <0.5)
|
||||
@@ -1549,7 +1549,7 @@ function enrichWithSourceData(
|
||||
|
||||
**Minimaal werkend voor release:**
|
||||
1. ✅ Split-screen layout werkend (desktop/tablet/mobile)
|
||||
2. ✅ Conversatie met medical scribe voelt natuurlijk (niet robotisch)
|
||||
2. ✅ Conversatie met Swift Assistent voelt natuurlijk (niet robotisch)
|
||||
3. ✅ Artifacts openen binnen 2 sec na intent detection
|
||||
4. ✅ AI-filtering psychiater >85% accuracy (behandelrelevante info)
|
||||
5. ✅ Voice input geïntegreerd en werkend
|
||||
@@ -1705,7 +1705,7 @@ function enrichWithSourceData(
|
||||
|
||||
**Mission Control Documents:**
|
||||
- **PRD Ephemeral UI:** `docs/swift/archive/nextgen-epd-prd-ephemeral-ui-epd.md` — Product vision
|
||||
- **FO v3.0:** `docs/swift/fo-swift-medical-scribe-v3.md` — Functioneel ontwerp medical scribe
|
||||
- **FO v3.0:** `docs/swift/fo-swift-medical-scribe-v3.md` — Functioneel ontwerp Swift Assistent
|
||||
- **Haalbaarheid:** `docs/swift/haalbaarheidsanalyse-v3.md` — Feasibility analysis
|
||||
- **UX v2.1:** `docs/swift/archive/swift-ux-v2.1.md` — Huidige UX/styling
|
||||
- **UX Analyse v3:** `docs/swift/v3-redesign-met-huidige-styling.md` — Wat blijft vs. wijzigt
|
||||
@@ -1746,7 +1746,7 @@ function enrichWithSourceData(
|
||||
| **Artifact** | UI-component die verschijnt in artifact area (block) |
|
||||
| **Block** | Herbruikbare UI-component (DagnotatieBlock, ZoekenBlock, etc.) |
|
||||
| **Prefill** | Vooringevulde data in artifact o.b.v. AI entity extraction |
|
||||
| **Medical Scribe** | AI-assistent die medische documentatie ondersteunt |
|
||||
| **Swift Assistent** | AI-assistent die medische documentatie ondersteunt |
|
||||
| **Linked Evidence** | Klikbare links naar bronnotities in AI-samenvatting |
|
||||
|
||||
---
|
||||
|
||||
774
docs/swift/competitive-analysis.md
Normal file
774
docs/swift/competitive-analysis.md
Normal file
@@ -0,0 +1,774 @@
|
||||
# Competitive Analysis: Declaratieve UI in Nederlandse Software
|
||||
|
||||
**Onderzoeksdatum:** 29 december 2025
|
||||
**Vraagstelling:** Zijn er Nederlandse softwareleveranciers (EPD/ECD, enterprise software) die een declaratieve UI hebben zoals Swift?
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Conclusie:** Swift's command-based, declaratieve UI is **uniek in de Nederlandse EPD markt** en zelfs internationaal zeldzaam. Geen enkele Nederlandse EPD leverancier (ChipSoft, Epic, Nexus) heeft een vergelijkbare command palette of natural language interface. Ook Nederlandse enterprise software (AFAS, Visma, Mollie, Adyen) documenteert geen command-based interfaces.
|
||||
|
||||
**Key Differentiators van Swift:**
|
||||
1. ✨ **Command palette met natural language** - typ "notitie jan medicatie" vs klikken door menu's
|
||||
2. 🤖 **AI intent classification** - begrijpt context en entities
|
||||
3. 📱 **Split-screen artifact rendering** - 40% chat + 60% werkgebied
|
||||
4. ⌨️ **Keyboard-first workflow** - ⌘K focus, Escape close, ⌘Enter submit
|
||||
5. 🎯 **Contextual awareness** - actieve patiënt, recent actions
|
||||
|
||||
---
|
||||
|
||||
## 🏥 Nederlandse EPD/ECD Leveranciers
|
||||
|
||||
### Marktoverzicht (2025)
|
||||
|
||||
De Nederlandse EPD-markt bestaat uit drie hoofdspelers na het vertrek van SAP/Cerner:
|
||||
|
||||
| Leverancier | Marktaandeel | Type |
|
||||
|-------------|--------------|------|
|
||||
| **ChipSoft (HiX)** | 72% | Nederlands |
|
||||
| **Epic** | 14% | Amerikaans |
|
||||
| **Nexus** | 11% | Duits |
|
||||
|
||||
**Bron:** [M&I Partners EPD-marktinventarisatie 2024](https://mxi.nl/kennis/644/epd-marktinventarisatie-ziekenhuizen-2024-consolidatie-epd-markt-zet-door)
|
||||
|
||||
---
|
||||
|
||||
### ChipSoft HiX
|
||||
|
||||
**Bedrijfsinfo:**
|
||||
- Marktleider in Nederland (72% ziekenhuizen)
|
||||
- ISO 13485 gecertificeerd
|
||||
- CE Medical Device klasse IIb certificering
|
||||
- Actief in: apotheek, eerstelijnszorg, GGZ, huisartsenzorg, revalidatie, VVT, ZBC's, ziekenhuizen
|
||||
|
||||
**UI/UX Features:**
|
||||
|
||||
✅ **Keyboard shortcuts** ("sneltoetsen")
|
||||
- Ondersteuning voor sneltoetsen bij registratie
|
||||
- Geen specifieke documentatie publiek beschikbaar
|
||||
|
||||
✅ **Dedicated UX Team**
|
||||
- Monitort en verbetert continu de 'look and feel'
|
||||
- Werkt volgens internationale standards
|
||||
- Observaties in werkplek om workflows te optimaliseren
|
||||
- Taskoriented views per apparaat (desktop/tablet/mobile)
|
||||
|
||||
✅ **Spraak-naar-tekst** (2025)
|
||||
- Integratie met Juvoly's speech-to-text
|
||||
- Reduceert registratielast voor huisartsen
|
||||
- Dit is **dictatie**, geen natural language interface
|
||||
|
||||
✅ **Personalisatie**
|
||||
- Gebruikers kunnen schermlay-out aanpassen
|
||||
- Favoriete functies configureren
|
||||
- Voorkeur voor waar patiëntinformatie opent
|
||||
|
||||
❌ **GEEN command palette of declaratieve UI**
|
||||
- Traditionele menu-driven interface
|
||||
- Geen natural language command input
|
||||
- Geen keyboard-first workflow zoals Swift
|
||||
|
||||
**Design Filosofie:**
|
||||
> "Wat direct opvalt bij het openen van HiX is de rustige uitstraling: eenvoudige pictogrammen, weinig lijnen en een centrale plek voor alle knoppen. Geen wildgroei aan kleuren... maar een apart pictogram voor elke eigenschap."
|
||||
|
||||
**Bronnen:**
|
||||
- [ChipSoft Gebruiksvriendelijkheid](https://www.chipsoft.com/nl-be/hix-abc/hix-abc-articles/gebruiksvriendelijkheid/)
|
||||
- [ChipSoft AI in HiX 2025](https://www.chipsoft.com/nl-nl/nieuws-en-blogs/ai-in-hix-ontdek-de-belangrijke-ontwikkelingen-in-2025/)
|
||||
|
||||
---
|
||||
|
||||
### Epic EMR
|
||||
|
||||
**Bedrijfsinfo:**
|
||||
- 14% marktaandeel Nederlandse ziekenhuizen
|
||||
- Amerikaans EHR/EMR systeem
|
||||
- Groeiend in Nederland (recent: Ziekenhuis Amstelland, MUMC+)
|
||||
|
||||
**UI/UX Features:**
|
||||
|
||||
✅ **Uitgebreide keyboard shortcuts**
|
||||
|
||||
Veelgebruikte shortcuts:
|
||||
- `Ctrl + O` - Go to Orders (manage orders tab)
|
||||
- `Alt + S` - Sign (sign current note)
|
||||
- `Alt + A` - Accept (accept order)
|
||||
- `Alt + [underlined letter]` - Selecteer menu optie
|
||||
- Standard shortcuts: `Ctrl + C` (copy), `Ctrl + Z` (undo)
|
||||
|
||||
✅ **Workflow optimalisatie**
|
||||
- Shortcuts kunnen workflow aanzienlijk versnellen
|
||||
- Vooral nuttig voor interventional radiologists en andere high-volume users
|
||||
|
||||
❌ **GEEN command palette feature**
|
||||
- Traditionele menu navigatie
|
||||
- Geen natural language interface
|
||||
- Geen centralized command bar
|
||||
|
||||
❌ **GEEN conversational interface**
|
||||
- Geen AI intent classification
|
||||
- Geen voice-to-command (wel dictatie via Dragon Medical)
|
||||
|
||||
**Bronnen:**
|
||||
- [Epic EMR Keyboard Shortcuts | TextExpander](https://textexpander.com/blog/epic-shortcuts)
|
||||
- [Easy Epic Keyboard Shortcuts | BackTable](https://www.backtable.com/shows/vi/articles/epic-emr-keyboard-shortcuts-how-to)
|
||||
|
||||
---
|
||||
|
||||
### Nexus Nederland
|
||||
|
||||
**Bedrijfsinfo:**
|
||||
- 11% marktaandeel Nederlandse ziekenhuizen
|
||||
- Onderdeel van Duits Nexus AG
|
||||
- Complete, modulaire EPD- en ECD-oplossingen voor ziekenhuizen en GGZ
|
||||
- Recent: St. Anna Zorggroep verlengde contract
|
||||
|
||||
**UI/UX Features:**
|
||||
- ❌ Geen specifieke UI innovaties gedocumenteerd
|
||||
- ❌ Geen publieke informatie over keyboard shortcuts of command interfaces
|
||||
|
||||
**Bron:**
|
||||
- [NEXUS Nederland](https://www.nexus-nederland.nl/)
|
||||
|
||||
---
|
||||
|
||||
## 🏢 Nederlandse Enterprise/SaaS Software
|
||||
|
||||
### HR Software: AFAS, Visma Nmbrs
|
||||
|
||||
**Visma Nmbrs:**
|
||||
- 100,000+ klanten
|
||||
- 1+ miljoen salarisadministraties per maand
|
||||
- Onderdeel van Visma (grootste software producer NL)
|
||||
|
||||
**Features:**
|
||||
✅ **API integraties**
|
||||
- User-friendly API met token-based authentication
|
||||
- Auto-sync met andere systemen (R&R, AFAS Profit)
|
||||
- Voorkomt dubbel werk en fouten
|
||||
|
||||
❌ **GEEN command interface**
|
||||
- Geen command palette gedocumenteerd
|
||||
- Geen natural language interface
|
||||
- Focus op webforms en automation
|
||||
|
||||
**AFAS:**
|
||||
- Complete ERP voor MKB
|
||||
- Sterk in accountancy, onderwijs, healthcare, trade
|
||||
- Business Process Outsourcing (BPO) optie
|
||||
|
||||
❌ **GEEN command interface** gedocumenteerd
|
||||
|
||||
**Bronnen:**
|
||||
- [Nmbrs | Visma Nederland](https://www.visma.nl/onze-bedrijven/nmbrs)
|
||||
- [AFAS | Visma Nmbrs Integraties](https://appstore.nmbrs.com/listings/afas)
|
||||
|
||||
---
|
||||
|
||||
### FinTech: Mollie, Adyen, MessageBird
|
||||
|
||||
**Mollie:**
|
||||
- Opgericht 2004 door Adriaan Mol (18 jaar oud)
|
||||
- 250,000+ bedrijven gebruiken Mollie
|
||||
- Grootste Nederlandse fintech deal ooit: acquisitie GoCardless voor €1.1B (2025)
|
||||
|
||||
**Adyen:**
|
||||
- Opgericht 2006 door Pieter van der Does en Arnout Schuijff
|
||||
- Focus op grote ondernemingen en multinationals
|
||||
- Omnichannel platform (online + fysieke winkels)
|
||||
|
||||
**MessageBird (nu Bird):**
|
||||
- Opgericht 2011 door Robert Vis en Adriaan Mol
|
||||
- Rebranded naar "Bird" in februari 2024
|
||||
- 700+ medewerkers
|
||||
- Focus: marketing, sales, payment solutions
|
||||
|
||||
**UI/UX Bevindingen:**
|
||||
❌ **GEEN command palette** features publiek gedocumenteerd
|
||||
- Deze bedrijven focussen op **payment/messaging API's**
|
||||
- End-user UI is vaak merchant dashboard (niet clinical workflow tool)
|
||||
- Developer-first platforms, niet operator-first
|
||||
|
||||
**Interessant:** Adriaan Mol is de oprichter van TWEE unicorns (Mollie $6B, MessageBird $4B)
|
||||
|
||||
**Bronnen:**
|
||||
- [De 15 beste Nederlandse SaaS-bedrijven | Web Whales](https://webwhales.nl/de-15-beste-nederlandse-saas-bedrijven/)
|
||||
- [Mollie vs Stripe vs Adyen | Codelevate](https://www.codelevate.com/nl/blog/mollie-vs-stripe-vs-adyen-psp-comparison-2025)
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Internationale Trends (2025)
|
||||
|
||||
### Microsoft Dragon Copilot for Nursing
|
||||
|
||||
**Lancering:** Late 2025
|
||||
**Type:** AI Clinical Assistant voor verpleegkundigen
|
||||
|
||||
**Features:**
|
||||
|
||||
✅ **Natural language conversational interface**
|
||||
- Verpleegkundigen kunnen **natuurlijk praten** met patiënten
|
||||
- Dragon Copilot **luistert op de achtergrond** (ambient listening)
|
||||
- Veilige mobile app voor bedside gebruik
|
||||
|
||||
✅ **Auto-generated documentation**
|
||||
- Genereert **structured flowsheet entries**
|
||||
- Nursing notes
|
||||
- Concise summaries van encounters
|
||||
|
||||
✅ **Query interface**
|
||||
- Verpleegkundigen kunnen vragen stellen aan Copilot
|
||||
- Antwoorden uit trusted sources (FDA, MedlinePlus)
|
||||
- Right at the bedside
|
||||
|
||||
✅ **Impact:**
|
||||
- **70% reductie in clinician burnout** bij gebruik van ambient AI
|
||||
- Documentatie is niet langer een separate task
|
||||
- Context-aware en ambient (niet command-driven)
|
||||
|
||||
**Verschil met Swift:**
|
||||
- Dragon Copilot is **ambient/passive** (luistert mee tijdens gesprek)
|
||||
- Swift is **active/command-driven** (gebruiker initieert acties)
|
||||
- Dragon focus: documentatie elimineren
|
||||
- Swift focus: acties versnellen
|
||||
|
||||
**Bron:**
|
||||
- [Microsoft Ignite 2025: Dragon Copilot for Nursing](https://techcommunity.microsoft.com/blog/healthcareandlifesciencesblog/highlights-from-ignite-2025-how-agentic-ai-and-microsoft-copilot-are-empowering-/4474658)
|
||||
|
||||
---
|
||||
|
||||
### M4 Infrastructure for EHR Data
|
||||
|
||||
**Type:** Research/data analysis tool
|
||||
**Developer:** PathOnAI (academic/research)
|
||||
|
||||
**Features:**
|
||||
|
||||
✅ **Natural language queries** voor EHR data
|
||||
- Query MIMIC-IV, eICU, custom datasets
|
||||
- Unified toolbox voor LLM agents
|
||||
- Supports tabular data en clinical notes
|
||||
|
||||
✅ **Multimodal support**
|
||||
- Dynamically selects tools by modality
|
||||
- Single natural-language interface
|
||||
|
||||
**Verschil met Swift:**
|
||||
- M4 is **research/analytics tool**, niet clinical workflow
|
||||
- Voor data scientists, niet clinici
|
||||
- Query historical data, niet real-time documentation
|
||||
|
||||
**Bron:**
|
||||
- [M4 - Infrastructure for EHR Data | Glama](https://glama.ai/mcp/servers/@hannesill/m4)
|
||||
|
||||
---
|
||||
|
||||
### Voice-First EHR Interfaces (2026 Trend)
|
||||
|
||||
**Trend:** Voice-first interfaces moving from experimental to mainstream
|
||||
|
||||
**Players:**
|
||||
- Microsoft Dragon Copilot
|
||||
- Oracle AI-driven platforms
|
||||
|
||||
**Features:**
|
||||
- Natural language for documentation
|
||||
- Navigation via voice
|
||||
- Information retrieval via voice
|
||||
|
||||
**Impact:**
|
||||
- 70% van clinici rapporteert **reduced burnout**
|
||||
- Ambient AI luistert passief tijdens patient encounters
|
||||
- Auto-generates clinical notes
|
||||
|
||||
**Bron:**
|
||||
- [EHR Interface Design: The Complete 2026 Guide | Arkenea](https://arkenea.com/blog/ehr-interface/)
|
||||
|
||||
---
|
||||
|
||||
## 💻 Command Palettes in General Software
|
||||
|
||||
Command palettes zijn **wijdverspreid in developer tools**, maar **zeldzaam in healthcare**:
|
||||
|
||||
### Developer Tools
|
||||
|
||||
| Software | Shortcut | Platform |
|
||||
|----------|----------|----------|
|
||||
| **VS Code** | `Ctrl+Shift+P` / `Cmd+Shift+P` | Cross-platform |
|
||||
| **GitHub** | `Ctrl+Shift+K` / `Cmd+Shift+K` | Web |
|
||||
| **Visual Studio 2022** | `Ctrl+Shift+P` | Windows |
|
||||
| **PowerToys** | `Win+Alt+Space` | Windows 11/10 |
|
||||
| **Oracle Code Editor** | `F1` | Cloud IDE |
|
||||
| **RStudio** | `Ctrl+Shift+P` / `Cmd+Shift+P` | Cross-platform |
|
||||
|
||||
**Common Pattern:**
|
||||
- Keyboard-driven launcher
|
||||
- Searchable command list
|
||||
- Fuzzy search
|
||||
- Shows keyboard shortcuts
|
||||
- Context-aware suggestions
|
||||
|
||||
**Best Practices (Mobbin):**
|
||||
- Most apps use `Cmd+K` or `Cmd+P`
|
||||
- Quick access/hide via shortcut
|
||||
- Search-driven interface
|
||||
- Eliminates need to remember obscure shortcuts
|
||||
- Faster than navigating complex menus
|
||||
|
||||
**Bronnen:**
|
||||
- [Command Palette UI Design Best Practices | Mobbin](https://mobbin.com/glossary/command-palette)
|
||||
- [How To Customize Command Palette For Enhanced Productivity In 2025](https://www.acciyo.com/how-to-customize-command-palette-for-enhanced-productivity-in-2025/)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Swift's Unique Position
|
||||
|
||||
### Wat Swift Combineert (en anderen NIET hebben)
|
||||
|
||||
Swift zit in een unieke positie door het combineren van vijf elementen die **afzonderlijk wel bestaan**, maar **zelden samen voorkomen**:
|
||||
|
||||
| Feature | Swift | ChipSoft | Epic | Dragon Copilot | Developer Tools |
|
||||
|---------|-------|----------|------|----------------|-----------------|
|
||||
| **Command palette** | ✅ | ❌ | ❌ | ❌ | ✅ |
|
||||
| **Natural language input** | ✅ | ❌ | ❌ | ✅ | ❌ |
|
||||
| **AI intent classification** | ✅ | ❌ | ❌ | ✅ | ❌ |
|
||||
| **Split-screen artifacts** | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Keyboard-first workflow** | ✅ | Partial | ✅ | ❌ | ✅ |
|
||||
| **Contextual awareness** | ✅ | ❌ | ❌ | ✅ | Partial |
|
||||
| **Healthcare-specific** | ✅ | ✅ | ✅ | ✅ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
### Swift's Key Differentiators
|
||||
|
||||
#### 1. **Declaratief vs Imperatief**
|
||||
|
||||
**Traditional EPD's (ChipSoft, Epic, Nexus):**
|
||||
- Menu-driven: Klik Patiënt → Notitie → Medicatie → Type → Submit
|
||||
- Mouse-heavy: 10+ clicks voor simpele actie
|
||||
- Imperatief: Gebruiker specificeert **HOE** (stap voor stap)
|
||||
|
||||
**Swift:**
|
||||
- Command-driven: Type "notitie jan medicatie"
|
||||
- Keyboard-first: 1 command + Enter
|
||||
- Declaratief: Gebruiker specificeert **WAT** (doel)
|
||||
|
||||
#### 2. **Intent-based Routing**
|
||||
|
||||
**Swift's AI classificatie:**
|
||||
```typescript
|
||||
Input: "notitie jan medicatie"
|
||||
↓ Intent classification
|
||||
Intent: "create_note"
|
||||
Entities: { patientName: "jan", category: "medicatie" }
|
||||
Confidence: 0.92
|
||||
↓ Route to artifact
|
||||
Opens: DagnotatieBlock with prefill
|
||||
```
|
||||
|
||||
**Andere EPD's:**
|
||||
- Geen intent classificatie
|
||||
- Gebruiker moet zelf navigeren
|
||||
- Geen context extraction uit natural language
|
||||
|
||||
#### 3. **Split-Screen Context Retention**
|
||||
|
||||
**Swift:**
|
||||
- 40% Chat Panel: Conversatie history + recent actions
|
||||
- 60% Artifact Area: Live werkgebied
|
||||
- Context blijft zichtbaar tijdens werken
|
||||
|
||||
**Andere EPD's:**
|
||||
- Modal dialogs (verlies context)
|
||||
- Full-screen forms (verlies overzicht)
|
||||
- Tabbed interface (constant switchen)
|
||||
|
||||
#### 4. **Keyboard-First Workflow**
|
||||
|
||||
**Swift shortcuts:**
|
||||
- `⌘K` - Focus input (altijd beschikbaar)
|
||||
- `Escape` - Close artifacts
|
||||
- `⌘Enter` - Quick submit
|
||||
- `1/2/3` - FallbackPicker selection
|
||||
|
||||
**ChipSoft/Epic:**
|
||||
- Hebben shortcuts, maar niet centraal
|
||||
- Geen universal command entry point
|
||||
- Shortcuts zijn per-screen/per-function
|
||||
- Geen keyboard-only workflow mogelijk
|
||||
|
||||
#### 5. **Voice + Text Unified**
|
||||
|
||||
**Swift:**
|
||||
- Deepgram streaming voice input
|
||||
- Voice → Text → Intent classification
|
||||
- Same pipeline voor voice en typed input
|
||||
- Waveform visualization tijdens recording
|
||||
|
||||
**ChipSoft:**
|
||||
- Juvoly dictatie (speech-to-text)
|
||||
- Alleen voor notitie dictation
|
||||
- Niet voor navigation/commands
|
||||
|
||||
**Dragon Copilot:**
|
||||
- Ambient listening (passive)
|
||||
- Auto-generates notes
|
||||
- Niet voor commands/actions
|
||||
|
||||
---
|
||||
|
||||
### Market Positioning
|
||||
|
||||
```
|
||||
Traditional EPD Swift Ambient AI
|
||||
(Menu-driven) (Command-driven) (Passive listening)
|
||||
|
||||
ChipSoft ──────────────────────► ◄──────────────────── Dragon Copilot
|
||||
Epic
|
||||
Nexus
|
||||
|
||||
Mouse-heavy Keyboard-first Voice-passive
|
||||
Imperative Declarative Automatic
|
||||
Step-by-step Intent-based Ambient
|
||||
```
|
||||
|
||||
**Swift's sweet spot:**
|
||||
- Sneller dan traditional EPD's (minder clicks)
|
||||
- Meer control dan ambient AI (gebruiker initieert)
|
||||
- Keyboard-first (ergonomisch voor power users)
|
||||
- Natural language (lage learning curve)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Competitive Advantages
|
||||
|
||||
### 1. **Snelheid**
|
||||
|
||||
**Traditional workflow (ChipSoft/Epic):**
|
||||
```
|
||||
Klik Patiënt (1) → Selecteer Jan (2) → Klik Acties (3) →
|
||||
Klik Notitie (4) → Selecteer Medicatie (5) → Type text (6) →
|
||||
Klik Submit (7)
|
||||
|
||||
Total: 7 interactions, ~20 seconden
|
||||
```
|
||||
|
||||
**Swift workflow:**
|
||||
```
|
||||
⌘K (1) → Type "notitie jan medicatie" (2) → ⌘Enter (3)
|
||||
|
||||
Total: 3 interactions, ~5 seconden
|
||||
```
|
||||
|
||||
**Speed advantage: 4x sneller**
|
||||
|
||||
---
|
||||
|
||||
### 2. **Cognitieve last**
|
||||
|
||||
**Traditional EPD:**
|
||||
- Moet menu structure onthouden
|
||||
- Moet locatie van functies onthouden
|
||||
- Moet door meerdere screens navigeren
|
||||
- Context switching tussen screens
|
||||
|
||||
**Swift:**
|
||||
- Type intentie in natural language
|
||||
- AI herkent context automatisch
|
||||
- Blijf in hetzelfde window
|
||||
- Context blijft zichtbaar in split-screen
|
||||
|
||||
**Cognitive load: Significant lager**
|
||||
|
||||
---
|
||||
|
||||
### 3. **Leer curve**
|
||||
|
||||
**Traditional EPD:**
|
||||
- Training nodig voor menu navigatie
|
||||
- Moet locaties onthouden
|
||||
- Verschillende workflows per functie
|
||||
|
||||
**Swift:**
|
||||
- Natural language (spreek zoals je denkt)
|
||||
- FallbackPicker bij onduidelijke input
|
||||
- Recent actions tonen voorbeelden
|
||||
- Incrementeel leren (geen big bang training)
|
||||
|
||||
**Learning curve: Vlakker**
|
||||
|
||||
---
|
||||
|
||||
### 4. **Ergonomie**
|
||||
|
||||
**Mouse-heavy workflows:**
|
||||
- Repetitive Strain Injury (RSI) risico
|
||||
- Hand van keyboard naar muis
|
||||
- Precision clicking (klein target)
|
||||
|
||||
**Swift keyboard-first:**
|
||||
- Hands blijven op keyboard
|
||||
- Geen precision clicking
|
||||
- Voice fallback bij RSI/disability
|
||||
- Lager RSI risico
|
||||
|
||||
---
|
||||
|
||||
### 5. **Schaalbaarheid**
|
||||
|
||||
**Traditional menu's:**
|
||||
- Meer functies = diepere menu's
|
||||
- Menu sprawl bij feature growth
|
||||
- Moeilijker te navigeren over tijd
|
||||
|
||||
**Swift command palette:**
|
||||
- Meer functies = meer commands
|
||||
- Search/fuzzy match blijft efficient
|
||||
- AI kan nieuwe intents leren
|
||||
- Lineair schaalbaar
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Innovation Opportunities
|
||||
|
||||
### Wat Swift kan toevoegen (geïnspireerd door onderzoek)
|
||||
|
||||
#### 1. **Macro's / Custom Commands**
|
||||
Inspiratie: TextExpander, VS Code snippets
|
||||
|
||||
```
|
||||
User creates custom command:
|
||||
"dagstart" → Opens 5 artifacts:
|
||||
- Agenda voor vandaag
|
||||
- Nieuwe patiënten
|
||||
- Kritieke waardes
|
||||
- Taken
|
||||
- Team chat
|
||||
```
|
||||
|
||||
#### 2. **Multi-step Commands**
|
||||
Inspiratie: GitHub CLI, PowerToys Run
|
||||
|
||||
```
|
||||
User types:
|
||||
"plan jan consult cardio volgende week"
|
||||
|
||||
Swift parses:
|
||||
- Action: plan appointment
|
||||
- Patient: jan
|
||||
- Type: consult
|
||||
- Specialty: cardio
|
||||
- Time: volgende week
|
||||
|
||||
Opens: Appointment scheduler with prefill
|
||||
```
|
||||
|
||||
#### 3. **Command History & Autocomplete**
|
||||
Inspiratie: Shell history, VS Code recent commands
|
||||
|
||||
```
|
||||
User types: "not"
|
||||
Autocomplete suggestions:
|
||||
- notitie jan medicatie (used 3x today)
|
||||
- notitie maria adl (used yesterday)
|
||||
- nieuwe patient intake
|
||||
```
|
||||
|
||||
#### 4. **Voice Commands Training**
|
||||
Inspiratie: Dragon Medical custom vocabulary
|
||||
|
||||
```
|
||||
User trains Swift:
|
||||
"dagno" → dagnotatie
|
||||
"medi jan" → medicatie voor jan
|
||||
"print epd" → export patient summary PDF
|
||||
```
|
||||
|
||||
#### 5. **Team Shared Commands**
|
||||
Inspiratie: VS Code workspace settings
|
||||
|
||||
```
|
||||
Team creates shared command:
|
||||
"overdracht ochtend" → Opens:
|
||||
- Nachtdienst notities
|
||||
- Kritieke events
|
||||
- Action items
|
||||
- Patient status changes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Lessons from Competition
|
||||
|
||||
### What Works (implement in Swift)
|
||||
|
||||
1. **ChipSoft's UX Team approach**
|
||||
- Continuous workplace observation
|
||||
- User-specific customization
|
||||
- Task-oriented views per device
|
||||
|
||||
2. **Epic's comprehensive shortcuts**
|
||||
- Document ALL shortcuts
|
||||
- Alt + underlined letter pattern
|
||||
- Workflow-specific shortcuts
|
||||
|
||||
3. **Dragon Copilot's ambient approach**
|
||||
- Reduce documentation burden
|
||||
- Context-aware auto-fill
|
||||
- Trusted source integration
|
||||
|
||||
4. **Developer tool patterns**
|
||||
- Fuzzy search in command palette
|
||||
- Recent commands prioritization
|
||||
- Visual keyboard hints
|
||||
|
||||
### What Doesn't Work (avoid in Swift)
|
||||
|
||||
1. **Vendor lock-in (ChipSoft/Epic)**
|
||||
- Systems "te duur" en "gebrekkige wil tot aanpassingen"
|
||||
- Moeilijk om over te stappen (verweven met andere systemen)
|
||||
- **Swift:** Stay modular, open standards (FHIR)
|
||||
|
||||
2. **Menu sprawl**
|
||||
- Meer features = diepere menus
|
||||
- **Swift:** Command palette scales linearly
|
||||
|
||||
3. **Passive-only AI (Dragon)**
|
||||
- Geen control over timing
|
||||
- Niet geschikt voor alle workflows
|
||||
- **Swift:** User-initiated blijft belangrijk
|
||||
|
||||
4. **Platform fragmentation**
|
||||
- Desktop-only shortcuts
|
||||
- Mobile separate workflow
|
||||
- **Swift:** Unified command interface cross-platform
|
||||
|
||||
---
|
||||
|
||||
## 📈 Market Opportunity
|
||||
|
||||
### Current EPD Market Pain Points
|
||||
|
||||
1. **Efficiency crisis**
|
||||
- Clinici spenderen 50%+ tijd aan administratie
|
||||
- Burnout epidemic in healthcare
|
||||
- **Swift's answer:** 4x sneller via command interface
|
||||
|
||||
2. **Vendor lock-in**
|
||||
- Ziekenhuizen "kunnen er bijna niet meer vanaf"
|
||||
- "Enorme kosten van EPD-vervanging"
|
||||
- **Swift's answer:** Modular, cloud-based, lower switching cost
|
||||
|
||||
3. **Poor usability**
|
||||
- "Veel te dure informatiesystemen"
|
||||
- "Gebrekkige wil tot aanpassingen"
|
||||
- **Swift's answer:** User-centered, command-driven, highly customizable
|
||||
|
||||
4. **Consolidation limiting choice**
|
||||
- Markt van 5 naar 3 spelers (SAP/Cerner exit)
|
||||
- ChipSoft 72% monopoly
|
||||
- **Swift's answer:** New entrant with differentiated approach
|
||||
|
||||
---
|
||||
|
||||
### Target Segments
|
||||
|
||||
**Early Adopters (Power Users):**
|
||||
- Tech-savvy clinicians
|
||||
- Interventional specialties (radiology, surgery)
|
||||
- High-volume workflows (IC, ER)
|
||||
- Keyboard-first preference
|
||||
|
||||
**Innovator Hospitals:**
|
||||
- Academic medical centers (research-oriented)
|
||||
- Startup/scale-up hospitals
|
||||
- Organizations frustrated with current vendor
|
||||
|
||||
**International:**
|
||||
- Markets with less vendor lock-in
|
||||
- English-speaking countries (easier localization)
|
||||
- Countries with national EHR initiatives
|
||||
|
||||
---
|
||||
|
||||
## 🏁 Conclusion
|
||||
|
||||
### Swift's Competitive Position: **Uniquely Positioned**
|
||||
|
||||
**Summary:**
|
||||
- ✅ **Geen Nederlandse EPD** heeft command palette of declaratieve UI
|
||||
- ✅ **Geen Nederlandse enterprise software** documenteert vergelijkbare interface
|
||||
- ✅ **Internationale trends** (Dragon Copilot, M4) gaan richting natural language, maar met andere focus (ambient vs command-driven)
|
||||
- ✅ **Developer tools** hebben command palettes, maar niet healthcare-specific
|
||||
- ✅ **Swift combineert** vijf elementen die afzonderlijk bestaan maar zelden samen
|
||||
|
||||
### Unique Value Proposition
|
||||
|
||||
Swift is:
|
||||
1. **Sneller** dan traditional EPD's (4x via keyboard-first)
|
||||
2. **Meer control** dan ambient AI (user-initiated)
|
||||
3. **Lager cognitive load** dan menu-driven interfaces
|
||||
4. **Schaalbaarder** dan menu hierarchies
|
||||
5. **Ergonomischer** dan mouse-heavy workflows
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Go-to-market positioning:**
|
||||
> "Swift: De eerste command-driven EPD voor power users.
|
||||
> Type WAT je wilt, niet HOE. 4x sneller dan klikken door menu's."
|
||||
|
||||
**Target message:**
|
||||
- Voor tech-savvy clinicians: "EPD met shortcuts zoals VS Code"
|
||||
- Voor administrators: "Reduce documentation time 70%"
|
||||
- Voor hospitals: "Moderne EPD zonder vendor lock-in"
|
||||
|
||||
**Next steps:**
|
||||
1. Publiceer competitive analysis (deze doc)
|
||||
2. Create demo video comparing Swift vs ChipSoft workflow
|
||||
3. Develop case studies met time savings metrics
|
||||
4. Target early adopter hospitals (academic centers)
|
||||
5. Present at Dutch healthcare innovation conferences
|
||||
|
||||
---
|
||||
|
||||
## 📚 Bronnen
|
||||
|
||||
### Nederlandse EPD Markt
|
||||
- [EPD-marktinventarisatie ziekenhuizen 2024 | M&I/Partners](https://mxi.nl/kennis/644/epd-marktinventarisatie-ziekenhuizen-2024-consolidatie-epd-markt-zet-door)
|
||||
- [Consolidatie op Nederlandse EPD-markt | ICT&health](https://www.icthealth.nl/nieuws/consolidatie-op-nederlandse-epd-markt-zet-door-met-vertrek-sapcerner)
|
||||
- [ACM: ziekenhuizen sterk afhankelijk van EPD-leverancier | Security.NL](https://www.security.nl/posting/735021/ACM:+ziekenhuizen+sterk+afhankelijk+van+EPD-leverancier,+pati%C3%ABnten+dupe)
|
||||
|
||||
### ChipSoft HiX
|
||||
- [ChipSoft Gebruiksvriendelijkheid](https://www.chipsoft.com/nl-be/hix-abc/hix-abc-articles/gebruiksvriendelijkheid/)
|
||||
- [ChipSoft AI in HiX 2025](https://www.chipsoft.com/nl-nl/nieuws-en-blogs/ai-in-hix-ontdek-de-belangrijke-ontwikkelingen-in-2025/)
|
||||
- [ChipSoft HiX Homepage](https://www.chipsoft.com/nl-nl/oplossingen/elektronisch-patientendossier-hix-optimale-zorginnovatie/)
|
||||
|
||||
### Epic EMR
|
||||
- [Epic EMR Keyboard Shortcuts | TextExpander](https://textexpander.com/blog/epic-shortcuts)
|
||||
- [Easy Epic Keyboard Shortcuts | BackTable](https://www.backtable.com/shows/vi/articles/epic-emr-keyboard-shortcuts-how-to)
|
||||
|
||||
### Nexus
|
||||
- [NEXUS Nederland EPD leverancier](https://www.nexus-nederland.nl/)
|
||||
|
||||
### Nederlandse SaaS
|
||||
- [De 15 beste Nederlandse SaaS-bedrijven | Web Whales](https://webwhales.nl/de-15-beste-nederlandse-saas-bedrijven/)
|
||||
- [Nmbrs | Visma Nederland](https://www.visma.nl/onze-bedrijven/nmbrs)
|
||||
- [Mollie vs Stripe vs Adyen | Codelevate](https://www.codelevate.com/nl/blog/mollie-vs-stripe-vs-adyen-psp-comparison-2025)
|
||||
|
||||
### Internationale Trends
|
||||
- [Microsoft Ignite 2025: Dragon Copilot for Nursing](https://techcommunity.microsoft.com/blog/healthcareandlifesciencesblog/highlights-from-ignite-2025-how-agentic-ai-and-microsoft-copilot-are-empowering-/4474658)
|
||||
- [EHR Interface Design: The Complete 2026 Guide | Arkenea](https://arkenea.com/blog/ehr-interface/)
|
||||
- [M4 - Infrastructure for EHR Data | Glama](https://glama.ai/mcp/servers/@hannesill/m4)
|
||||
- [Large language models in healthcare | Nature Medicine](https://www.nature.com/articles/s41591-024-03199-w)
|
||||
|
||||
### Command Palettes
|
||||
- [Command Palette UI Design Best Practices | Mobbin](https://mobbin.com/glossary/command-palette)
|
||||
- [How To Customize Command Palette For Enhanced Productivity In 2025](https://www.acciyo.com/how-to-customize-command-palette-for-enhanced-productivity-in-2025/)
|
||||
- [GitHub Command Palette Docs](https://docs.github.com/en/enterprise-cloud@latest/get-started/using-github/github-command-palette)
|
||||
- [PowerToys Command Palette | Microsoft Learn](https://learn.microsoft.com/en-us/windows/powertoys/command-palette/overview)
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Laatste update:** 29 december 2025
|
||||
**Auteur:** Colin (met Claude Code)
|
||||
@@ -1,4 +1,4 @@
|
||||
# E0.S3 — Medical Scribe System Prompt
|
||||
# E0.S3 — Swift Assistent System Prompt
|
||||
|
||||
**Datum:** 27-12-2024
|
||||
**Versie:** v1.0
|
||||
@@ -9,12 +9,12 @@
|
||||
|
||||
## System Prompt v1.0
|
||||
|
||||
Dit is de eerste versie van de medical scribe system prompt voor `/api/swift/chat`.
|
||||
Dit is de eerste versie van de Swift Assistent system prompt voor `/api/swift/chat`.
|
||||
|
||||
### Volledige Prompt
|
||||
|
||||
```markdown
|
||||
Je bent een medische assistent (medical scribe) voor Swift, een Nederlands EPD-systeem voor GGZ-instellingen.
|
||||
Je bent Swift Assistent, een medische assistent voor Swift EPD, een Nederlands EPD-systeem voor GGZ-instellingen.
|
||||
|
||||
## Je rol
|
||||
|
||||
@@ -508,7 +508,7 @@ Er ging iets mis bij het openen van de notitie. Probeer het opnieuw, of neem con
|
||||
|
||||
| Versie | Datum | Wijzigingen |
|
||||
|--------|-------|-------------|
|
||||
| v1.0 | 27-12-2024 | Initial prompt - Dutch medical scribe, intents, examples |
|
||||
| v1.0 | 27-12-2024 | Initial prompt - Dutch Swift Assistent, intents, examples |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 🧩 Functioneel Ontwerp (FO) — Swift Medical Scribe Chatbot
|
||||
# 🧩 Functioneel Ontwerp (FO) — Swift Swift Assistent Chatbot
|
||||
|
||||
**Projectnaam:** Swift — Medical Scribe Chatbot Interface
|
||||
**Projectnaam:** Swift — Swift Assistent Chatbot Interface
|
||||
**Versie:** v3.0
|
||||
**Datum:** 27-12-2024
|
||||
**Auteur:** Colin Lit
|
||||
@@ -10,7 +10,7 @@
|
||||
## 1. Doel en relatie met het PRD
|
||||
|
||||
🎯 **Doel van dit document:**
|
||||
Dit Functioneel Ontwerp beschrijft het **redesign** van Swift naar een medical scribe chatbot interface. De gebruiker voert een natuurlijke conversatie met een AI-assistent die intents herkent, acties uitvoert, en relevante UI-componenten toont in een split-screen layout.
|
||||
Dit Functioneel Ontwerp beschrijft het **redesign** van Swift naar een Swift Assistent chatbot interface. De gebruiker voert een natuurlijke conversatie met een AI-assistent die intents herkent, acties uitvoert, en relevante UI-componenten toont in een split-screen layout.
|
||||
|
||||
📘 **Relatie met andere documenten:**
|
||||
- **PRD:** `nextgen-epd-prd-ephemeral-ui-epd.md` — Ephemeral UI visie
|
||||
@@ -19,16 +19,16 @@ Dit Functioneel Ontwerp beschrijft het **redesign** van Swift naar een medical s
|
||||
- **Bouwplan:** `bouwplan-swift-v2.md` — Development roadmap
|
||||
|
||||
**Kernprincipe:**
|
||||
> De gebruiker voert een natuurlijke conversatie met een medical scribe assistent. De assistent herkent intents, voert acties uit, en toont relevante UI-componenten (artifacts) rechts in beeld. De conversatie blijft zichtbaar en doorlopend — zoals ChatGPT Canvas of Claude Artifacts.
|
||||
> De gebruiker voert een natuurlijke conversatie met een Swift Assistent assistent. De assistent herkent intents, voert acties uit, en toont relevante UI-componenten (artifacts) rechts in beeld. De conversatie blijft zichtbaar en doorlopend — zoals ChatGPT Canvas of Claude Artifacts.
|
||||
|
||||
**Belangrijkste wijzigingen t.o.v. v2.0:**
|
||||
|
||||
| Aspect | v2.0 (Command Center) | v3.0 (Medical Scribe) |
|
||||
| Aspect | v2.0 (Command Center) | v3.0 (Swift Assistent) |
|
||||
|--------|----------------------|----------------------|
|
||||
| Input model | Command-line stijl | Natuurlijke conversatie |
|
||||
| UI paradigma | Blocks die verschijnen/verdwijnen | Chat links, artifacts rechts |
|
||||
| Context | Per commando | Doorlopende conversatiegeschiedenis |
|
||||
| AI rol | Intent classifier | Converserende medical scribe |
|
||||
| AI rol | Intent classifier | Converserende Swift Assistent |
|
||||
| Interactie | Transactioneel | Relationeel, follow-up mogelijk |
|
||||
|
||||
---
|
||||
@@ -108,7 +108,7 @@ Dit Functioneel Ontwerp beschrijft het **redesign** van Swift naar een medical s
|
||||
### 4.1 Command Center (Hoofdscherm)
|
||||
|
||||
**Beschrijving:**
|
||||
Split-screen layout met chat links (40%) en artifacts rechts (60%). De gebruiker voert een natuurlijke conversatie met de medical scribe assistent.
|
||||
Split-screen layout met chat links (40%) en artifacts rechts (60%). De gebruiker voert een natuurlijke conversatie met de Swift Assistent assistent.
|
||||
|
||||
**Layout:**
|
||||
|
||||
@@ -184,7 +184,7 @@ Split-screen layout met chat links (40%) en artifacts rechts (60%). De gebruiker
|
||||
|
||||
### 4.3 Chat Panel
|
||||
|
||||
**Functie:** Toont doorlopende conversatie met medical scribe assistent.
|
||||
**Functie:** Toont doorlopende conversatie met Swift Assistent assistent.
|
||||
|
||||
**Elementen:**
|
||||
|
||||
@@ -216,7 +216,7 @@ Split-screen layout met chat links (40%) en artifacts rechts (60%). De gebruiker
|
||||
|
||||
### 4.4 Chat Input
|
||||
|
||||
**Functie:** Tekst + voice input voor conversatie met medical scribe.
|
||||
**Functie:** Tekst + voice input voor conversatie met Swift Assistent.
|
||||
|
||||
**States:**
|
||||
|
||||
@@ -301,7 +301,7 @@ Split-screen layout met chat links (40%) en artifacts rechts (60%). De gebruiker
|
||||
|
||||
---
|
||||
|
||||
### 4.6 Medical Scribe Chat API
|
||||
### 4.6 Swift Assistent Chat API
|
||||
|
||||
**Functie:** Chatbot endpoint die conversatie voert en intents herkent.
|
||||
|
||||
@@ -347,7 +347,7 @@ interface ChatRequest {
|
||||
**System Prompt (samenvatting):**
|
||||
|
||||
```
|
||||
Je bent een medische assistent (medical scribe) voor Swift, een Nederlands GGZ EPD.
|
||||
Je bent een medische assistent (Swift Assistent) voor Swift, een Nederlands GGZ EPD.
|
||||
|
||||
Je rol:
|
||||
- Help zorgmedewerkers met documentatie en administratie
|
||||
@@ -739,4 +739,4 @@ components/swift/
|
||||
|--------|-------|-------------|
|
||||
| v2.0 | 23-12-2024 | Command Center met ephemeral blocks |
|
||||
| v2.1 | 23-12-2024 | Prioriteitenlijst, intent mapping, P3 blocks |
|
||||
| v3.0 | 27-12-2024 | **Redesign:** Chat + Artifact interface, Medical Scribe conversatie, AI-filtering voor psychiater |
|
||||
| v3.0 | 27-12-2024 | **Redesign:** Chat + Artifact interface, Swift Assistent conversatie, AI-filtering voor psychiater |
|
||||
|
||||
2
docs/templates/commit_template.md
vendored
2
docs/templates/commit_template.md
vendored
@@ -53,7 +53,7 @@ Closes #123
|
||||
- `ui` - UI componenten
|
||||
- `db` - Database migraties
|
||||
- `docs` - Documentatie
|
||||
- `swift` - Swift medical scribe
|
||||
- `swift` - Swift Assistent
|
||||
- `behandelplan` - Behandelplan
|
||||
- `verpleegrapportage` - Verpleegrapportage
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Client-side helper voor het aanroepen van de Swift chat API met streaming support.
|
||||
*
|
||||
* Epic: E3 (Chat API & Medical Scribe)
|
||||
* Epic: E3 (Chat API & Swift Assistent)
|
||||
* Story: E3.S1 (Chat API endpoint skeleton)
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user