feat(cortex): add Intake Blocks MVP for voice/text commands

Adds 4 new intents for intake workflow:
- intake_status: shows completion percentage and section checklist
- intake_navigeer: navigation to specific intake tabs
- risico_query: displays risk assessments with severity indicators
- diagnose_query: shows primary/secondary diagnoses

New components:
- IntakeStatusBlock, RisicoBlock, DiagnoseBlock
- Shared block components (BlockLoading, BlockError, BlockEmpty, BlockSection, BlockItem, BlockFooter)
- useBlockData and useIntakeContext hooks

New API routes:
- GET /api/cortex/intake/status
- GET /api/cortex/intake/risico
- GET /api/cortex/intake/diagnose

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
colinislit
2026-02-03 23:21:18 +01:00
parent 05836c5c6a
commit b095b1e492
22 changed files with 2415 additions and 15 deletions

View File

@@ -123,6 +123,14 @@ export function extractEntities(
return extractCancelAppointmentEntities(trimmedInput, input, referenceDate);
case 'reschedule_appointment':
return extractRescheduleAppointmentEntities(trimmedInput, input, referenceDate);
// Intake intents (MVP)
case 'intake_navigeer':
return extractIntakeNavigeerEntities(trimmedInput);
case 'intake_status':
case 'risico_query':
case 'diagnose_query':
// These intents use patient context from store, no entity extraction needed
return entities;
default:
return entities;
}
@@ -665,3 +673,83 @@ function extractDateLabel(input: string): DateRange['label'] {
if (normalized.includes('volgende week')) return 'volgende week';
return 'custom';
}
// ============================================================================
// Intake Entity Extraction (MVP)
// ============================================================================
/**
* Intake tab targets for navigation.
*/
export type IntakeTab =
| 'contacts'
| 'kindcheck'
| 'risk'
| 'anamnese'
| 'examination'
| 'rom'
| 'diagnosis'
| 'behandeladvies';
/**
* Mapping of keywords to intake tabs.
*/
const INTAKE_TAB_KEYWORDS: Record<string, IntakeTab> = {
// Risk
'risico': 'risk',
'risicotaxatie': 'risk',
'risicos': 'risk',
// Diagnosis
'diagnose': 'diagnosis',
'diagnoses': 'diagnosis',
'dsm': 'diagnosis',
// Anamnese
'anamnese': 'anamnese',
'voorgeschiedenis': 'anamnese',
'geschiedenis': 'anamnese',
// Contacts
'contact': 'contacts',
'contacten': 'contacts',
'contactmomenten': 'contacts',
// Kindcheck
'kindcheck': 'kindcheck',
'kinderen': 'kindcheck',
// Examination
'onderzoek': 'examination',
'psychiatrisch': 'examination',
// ROM
'rom': 'rom',
'meetinstrumenten': 'rom',
'vragenlijst': 'rom',
'vragenlijsten': 'rom',
// Behandeladvies
'behandeladvies': 'behandeladvies',
'advies': 'behandeladvies',
'behandeling': 'behandeladvies',
'samenvatting': 'behandeladvies',
// Doelen (maps to behandeladvies as it's part of that tab in MVP)
'doelen': 'behandeladvies',
'doelstellingen': 'behandeladvies',
// Netwerk (maps to contacts as it's closest in MVP)
'netwerk': 'contacts',
'sociaal': 'contacts',
};
/**
* Extract entities for intake_navigeer intent.
* Determines which intake tab to navigate to.
*/
function extractIntakeNavigeerEntities(lowerInput: string): ExtractedEntities & { navigationTarget?: IntakeTab } {
const words = lowerInput.split(/\s+/);
// Find the navigation target
for (const word of words) {
const target = INTAKE_TAB_KEYWORDS[word];
if (target) {
return { navigationTarget: target } as ExtractedEntities & { navigationTarget?: IntakeTab };
}
}
// Default to risk (most common navigation target in intake context)
return { navigationTarget: 'risk' } as ExtractedEntities & { navigationTarget?: IntakeTab };
}