feat(swift): implement agenda planning module (Epic 4 UI)
- Add AgendaBlock core component with list, create, cancel, reschedule modes - Implement AgendaListView with patient/type/location details and actions - Implement AgendaCreateForm with fuzzy patient search and validation - Implement AgendaCancelView with disambiguation support - Implement AgendaRescheduleForm with date/time picker - Integrate with server actions (create, cancel, reschedule) - Add radio-group UI component - Update documentation and status
This commit is contained in:
@@ -7,3 +7,4 @@ export * from './use-swift-voice';
|
||||
export * from './intent-classifier';
|
||||
export * from './intent-classifier-ai';
|
||||
export * from './entity-extractor';
|
||||
export * from './date-time-parser';
|
||||
|
||||
@@ -183,18 +183,25 @@ export async function classifyIntentWithAI(input: string): Promise<AIClassificat
|
||||
|
||||
const processingTimeMs = performance.now() - startTime;
|
||||
|
||||
// Build entities object with backward compatibility
|
||||
const entities: ExtractedEntities = {
|
||||
patientName: validated.entities?.patientName,
|
||||
category: validated.entities?.category as VerpleegkundigCategory | undefined,
|
||||
content: validated.entities?.content,
|
||||
query: validated.entities?.query,
|
||||
// Legacy fields for backward compatibility
|
||||
date: validated.entities?.date,
|
||||
time: validated.entities?.time,
|
||||
};
|
||||
|
||||
// For agenda intents, we'll rely on local entity extraction
|
||||
// AI just provides the basic fields (patientName, date, time)
|
||||
// and the local extractor will structure them properly
|
||||
|
||||
return {
|
||||
intent: validated.intent as SwiftIntent,
|
||||
confidence: validated.confidence,
|
||||
entities: {
|
||||
patientName: validated.entities?.patientName,
|
||||
category: validated.entities?.category as VerpleegkundigCategory | undefined,
|
||||
content: validated.entities?.content,
|
||||
query: validated.entities?.query,
|
||||
date: validated.entities?.date,
|
||||
time: validated.entities?.time,
|
||||
identifier: validated.entities?.identifier,
|
||||
},
|
||||
entities,
|
||||
source: 'ai',
|
||||
processingTimeMs,
|
||||
reasoning: validated.reasoning,
|
||||
|
||||
@@ -32,14 +32,46 @@ export interface IntentClassificationResult {
|
||||
|
||||
// Extracted entities from user input
|
||||
export interface ExtractedEntities {
|
||||
// Common entities
|
||||
patientName?: string;
|
||||
patientId?: string;
|
||||
|
||||
// Dagnotitie entities
|
||||
category?: VerpleegkundigCategory;
|
||||
content?: string;
|
||||
|
||||
// Search entities
|
||||
query?: string;
|
||||
|
||||
// Agenda entities
|
||||
dateRange?: {
|
||||
start: Date;
|
||||
end: Date;
|
||||
label: 'vandaag' | 'morgen' | 'deze week' | 'volgende week' | 'custom';
|
||||
};
|
||||
datetime?: {
|
||||
date: Date;
|
||||
time: string; // "HH:mm" format
|
||||
};
|
||||
appointmentType?: 'intake' | 'behandeling' | 'follow-up' | 'telefonisch' |
|
||||
'huisbezoek' | 'online' | 'crisis' | 'overig';
|
||||
location?: 'praktijk' | 'online' | 'thuis';
|
||||
identifier?: {
|
||||
type: 'patient' | 'time' | 'both';
|
||||
patientName?: string;
|
||||
patientId?: string;
|
||||
time?: string;
|
||||
date?: Date;
|
||||
encounterId?: string;
|
||||
};
|
||||
newDatetime?: {
|
||||
date: Date;
|
||||
time: string;
|
||||
};
|
||||
|
||||
// Legacy fields (for backward compatibility)
|
||||
date?: string;
|
||||
time?: string;
|
||||
identifier?: string;
|
||||
}
|
||||
|
||||
// Block sizes
|
||||
|
||||
102
lib/swift/verify-entity-extraction.ts
Normal file
102
lib/swift/verify-entity-extraction.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Manual verification script for entity extraction with date/time parser
|
||||
* Run with: pnpm tsx lib/swift/verify-entity-extraction.ts
|
||||
*/
|
||||
|
||||
import { extractEntities } from './entity-extractor';
|
||||
import { format } from 'date-fns';
|
||||
import { nl } from 'date-fns/locale';
|
||||
|
||||
console.log('🧪 Testing Entity Extraction with Date/Time Parser\n');
|
||||
|
||||
// Test cases for each agenda intent
|
||||
const testCases = [
|
||||
{
|
||||
intent: 'agenda_query' as const,
|
||||
inputs: [
|
||||
'afspraken vandaag',
|
||||
'agenda morgen',
|
||||
'wat is volgende afspraak',
|
||||
'afspraken deze week',
|
||||
],
|
||||
},
|
||||
{
|
||||
intent: 'create_appointment' as const,
|
||||
inputs: [
|
||||
'maak afspraak jan morgen 14:00',
|
||||
'plan intake marie vrijdag 10:00',
|
||||
'afspraak met piet twee uur',
|
||||
'maak behandeling lisa dinsdag half drie',
|
||||
],
|
||||
},
|
||||
{
|
||||
intent: 'cancel_appointment' as const,
|
||||
inputs: [
|
||||
'annuleer afspraak jan',
|
||||
'cancel de 14:00 afspraak',
|
||||
'annuleer jan morgen',
|
||||
],
|
||||
},
|
||||
{
|
||||
intent: 'reschedule_appointment' as const,
|
||||
inputs: [
|
||||
'verzet 14:00 naar 15:00',
|
||||
'verzet jan naar dinsdag',
|
||||
'verplaats de afspraak naar morgen 10:00',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach(({ intent, inputs }) => {
|
||||
console.log(`\n📋 Testing ${intent}:\n`);
|
||||
|
||||
inputs.forEach((input) => {
|
||||
const entities = extractEntities(input, intent);
|
||||
console.log(` Input: "${input}"`);
|
||||
|
||||
// Display extracted entities
|
||||
if (entities.patientName) {
|
||||
console.log(` 👤 Patient: ${entities.patientName}`);
|
||||
}
|
||||
|
||||
if (entities.dateRange) {
|
||||
const { start, end, label } = entities.dateRange;
|
||||
console.log(
|
||||
` 📅 Date Range: ${format(start, 'dd MMM', { locale: nl })} - ${format(end, 'dd MMM', { locale: nl })} (${label})`
|
||||
);
|
||||
}
|
||||
|
||||
if (entities.datetime) {
|
||||
const { date, time } = entities.datetime;
|
||||
const dateStr = format(date, 'dd MMMM yyyy', { locale: nl });
|
||||
console.log(` 🕐 Datetime: ${dateStr} om ${time || '(tijd niet gespecificeerd)'}`);
|
||||
}
|
||||
|
||||
if (entities.appointmentType) {
|
||||
console.log(` 📝 Type: ${entities.appointmentType}`);
|
||||
}
|
||||
|
||||
if (entities.location) {
|
||||
console.log(` 📍 Location: ${entities.location}`);
|
||||
}
|
||||
|
||||
if (entities.identifier) {
|
||||
const { type, patientName, time, date } = entities.identifier;
|
||||
let identifierStr = ` 🔍 Identifier: type=${type}`;
|
||||
if (patientName) identifierStr += `, patient=${patientName}`;
|
||||
if (time) identifierStr += `, time=${time}`;
|
||||
if (date) identifierStr += `, date=${format(date, 'dd MMM', { locale: nl })}`;
|
||||
console.log(identifierStr);
|
||||
}
|
||||
|
||||
if (entities.newDatetime) {
|
||||
const { date, time } = entities.newDatetime;
|
||||
const dateStr = format(date, 'dd MMMM yyyy', { locale: nl });
|
||||
console.log(` 🔄 New Datetime: ${dateStr} om ${time || '(tijd niet gespecificeerd)'}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
});
|
||||
});
|
||||
|
||||
console.log('✅ Verification complete!');
|
||||
Reference in New Issue
Block a user