From c6616d85b296b19dd8cbc8a25b237ceac81c4632 Mon Sep 17 00:00:00 2001 From: colinislit Date: Sat, 27 Dec 2025 17:44:09 +0100 Subject: [PATCH] feat(swift): implementeer AI-filtering psychiater voor overdracht (E5.S1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E5.S1 compleet: AI filtert op behandelrelevantie voor psychiaters. Voortgang: 56 SP / 72 SP (78%) - Epic 5 in progress! Implementatie: - Twee-staps filtering: Server (include_in_handover=true) → AI (psychiater: behandelrelevant) - Role toggle UI: Verpleegkundige/Psychiater knoppen in OverdrachtBlock - buildSystemPrompt(role) functie met psychiater-specifieke filtering regels - filterForRole parameter toegevoegd aan GenerateOverdrachtSchema - API route verwerkt role parameter en stuurt door naar Claude AI Psychiater filtering regels: ✅ WEL RELEVANT: - Medicatie-issues: weigering, bijwerkingen, dosisaanpassingen - Stemming/gedrag: veranderingen, afwijkend gedrag, agitatie - Risico-signalen: suïcidaliteit, automutilatie, agressie - Psychotische symptomen: wanen, hallucinaties - Crisis/dwang: separatie, fixatie, dwangmedicatie - Afwijkende vitals: HH, LL met behandelimpact ❌ FILTER UIT: - Routine medicatie ("volgens schema", "zonder problemen") - ADL activiteiten (douchen, eten) tenzij significant afwijkend - Sociale activiteiten ("groepstherapie", "gesprek gehad") - Standaard observaties ("rustige dag", "geen bijzonderheden") - Routine vitals (normaal bereik, N interpretatie) - Dagstructuur ("dagprogramma gevolgd") UI/UX verbeteringen: - Role toggle met 2 knoppen (verpleegkundige/psychiater) - Visual hints: "Alle gemarkeerde items" vs "Behandelrelevante items" - Dynamic description tekst onder toggle - filterRole state + useCallback dependency update Files Changed: - lib/types/overdracht.ts: +1 line (filterForRole in schema) - lib/ai/overdracht-prompt.ts: +52 lines (buildSystemPrompt functie) - app/api/overdracht/generate/route.ts: +8/-8 lines (role parameter) - components/swift/blocks/overdracht-block.tsx: +43 lines (UI + logic) Technische details: - filterForRole default: 'verpleegkundige' - Psychiater max: 3 aandachtspunten, 2 actiepunten - Verpleegkundige max: 5 aandachtspunten, 3 actiepunten - Role parameter propageert van UI → API → Claude prompt Testing: - Build succesvol (pnpm build) - Geen nieuwe type errors - UI toggle functioneel Voortgang: 56 SP / 72 SP (78%) - E5.S1 compleet (5 SP) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- app/api/overdracht/generate/route.ts | 16 +++--- components/swift/blocks/overdracht-block.tsx | 43 +++++++++++++++- lib/ai/overdracht-prompt.ts | 52 ++++++++++++++++++++ lib/types/overdracht.ts | 1 + 4 files changed, 104 insertions(+), 8 deletions(-) diff --git a/app/api/overdracht/generate/route.ts b/app/api/overdracht/generate/route.ts index a7df45f..08baa44 100644 --- a/app/api/overdracht/generate/route.ts +++ b/app/api/overdracht/generate/route.ts @@ -9,7 +9,7 @@ import { createClient } from '@/lib/auth/server'; import { NextRequest, NextResponse } from 'next/server'; import { z } from 'zod'; import { - OVERDRACHT_SYSTEM_PROMPT, + buildSystemPrompt, buildOverdrachtUserPrompt, calculateAge, formatPatientName, @@ -154,7 +154,10 @@ async function loadOverdrachtContext( /** * Call Claude API */ -async function callClaudeAPI(context: OverdrachtContext): Promise<{ +async function callClaudeAPI( + context: OverdrachtContext, + role: 'psychiater' | 'verpleegkundige' = 'verpleegkundige' +): Promise<{ samenvatting: string; aandachtspunten: Aandachtspunt[]; actiepunten: string[]; @@ -164,6 +167,7 @@ async function callClaudeAPI(context: OverdrachtContext): Promise<{ throw new Error('ANTHROPIC_API_KEY ontbreekt in environment'); } + const systemPrompt = buildSystemPrompt(role); const userPrompt = buildOverdrachtUserPrompt(context); const response = await fetch('https://api.anthropic.com/v1/messages', { @@ -177,7 +181,7 @@ async function callClaudeAPI(context: OverdrachtContext): Promise<{ model: 'claude-sonnet-4-20250514', max_tokens: 2048, temperature: 0.3, - system: OVERDRACHT_SYSTEM_PROMPT, + system: systemPrompt, messages: [{ role: 'user', content: userPrompt }], }), }); @@ -277,7 +281,7 @@ export async function POST(request: NextRequest) { ); } - const { patientId, period } = result.data; + const { patientId, period, filterForRole } = result.data; const supabase = await createClient(); // Check auth @@ -289,8 +293,8 @@ export async function POST(request: NextRequest) { // Load context with period filter const context = await loadOverdrachtContext(supabase, patientId, period); - // Call Claude API - const aiResult = await callClaudeAPI(context); + // Call Claude API with role-specific filtering + const aiResult = await callClaudeAPI(context, filterForRole); const durationMs = Date.now() - startTime; diff --git a/components/swift/blocks/overdracht-block.tsx b/components/swift/blocks/overdracht-block.tsx index c1a9ccb..e824b27 100644 --- a/components/swift/blocks/overdracht-block.tsx +++ b/components/swift/blocks/overdracht-block.tsx @@ -56,6 +56,7 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) { const { toast } = useToast(); const [period, setPeriod] = useState('1d'); + const [filterRole, setFilterRole] = useState<'verpleegkundige' | 'psychiater'>('verpleegkundige'); const [patients, setPatients] = useState([]); const [isLoadingPatients, setIsLoadingPatients] = useState(true); const [patientSummaries, setPatientSummaries] = useState>(new Map()); @@ -139,7 +140,7 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) { { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ patientId, period }), + body: JSON.stringify({ patientId, period, filterForRole: filterRole }), }, { operation: 'Overdracht genereren' } ), @@ -182,7 +183,7 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) { return updated; }); } - }, [period]); + }, [period, filterRole]); // Filter patients: if activePatient is set, only show that one const displayPatients = activePatient @@ -230,6 +231,44 @@ export function OverdrachtBlock({ prefill }: OverdrachtBlockProps) {

+ {/* Role Filter Toggle */} +
+ +
+ + +
+

+ {filterRole === 'psychiater' + ? 'AI filtert op behandelrelevantie voor psychiater' + : 'Toont alle door verpleegkundige gemarkeerde items'} +

+
+ {/* Patients List */} {isLoadingPatients ? (
diff --git a/lib/ai/overdracht-prompt.ts b/lib/ai/overdracht-prompt.ts index c537fc9..59c8079 100644 --- a/lib/ai/overdracht-prompt.ts +++ b/lib/ai/overdracht-prompt.ts @@ -68,6 +68,58 @@ Je taak: Maak een beknopte, relevante overdracht voor de opvolgende dienst. Geef je antwoord als PURE JSON, zonder markdown code blocks.`; +/** + * Build system prompt based on target role + * For psychiater: adds strict filtering on treatment relevance + * For verpleegkundige: uses base prompt (all marked items) + */ +export function buildSystemPrompt(role: 'psychiater' | 'verpleegkundige' = 'verpleegkundige'): string { + if (role === 'psychiater') { + return `${OVERDRACHT_SYSTEM_PROMPT} + +## PSYCHIATER FILTERING +Je maakt overdracht voor een PSYCHIATER. Filter STRIKT op behandelrelevantie. + +De verpleegkundige heeft al items geselecteerd (include_in_handover=true), maar jij moet VERDER FILTEREN. + +✅ WEL RELEVANT (include in samenvatting): +- Medicatie-issues: weigering, bijwerkingen, dosisaanpassingen, therapietrouw +- Stemming/gedrag: veranderingen van baseline, afwijkend gedrag, agitatie +- Risico-signalen: suïcidale uitingen, automutilatie, agressie +- Psychotische symptomen: wanen, hallucinaties, desorganisatie, paranoia +- Crisis/dwang: separatie, fixatie, dwangmedicatie, vrijheidsbeperkende maatregelen +- Afwijkende vitals: HH (kritiek hoog), LL (kritiek laag) met behandelimpact + +❌ FILTER UIT (niet in samenvatting): +- Routine medicatie: "medicatie volgens schema", "zonder problemen", "ingenomen conform afspraak" +- ADL activiteiten: douchen, aankleden, eten, drinken (tenzij significant afwijkend/weigering) +- Sociale activiteiten: "deelgenomen aan groepstherapie", "gesprek gehad", "koffie gedronken" +- Standaard observaties: "rustige dag", "geen bijzonderheden", "normaal functioneren" +- Routine vitals: bloeddruk/pols binnen normaalwaarden (N interpretatie) +- Dagstructuur: "dagprogramma gevolgd", "aanwezig bij activiteit" + +VUISTREGEL: Include ALLEEN als een psychiater op basis van deze info een BEHANDELBESLISSING kan nemen. + +Beperkingen: +- Maximum 3 aandachtspunten (ALLEEN behandelrelevant, geen routine items) +- Maximum 2 actiepunten (ALLEEN actionable voor psychiater) +- Bij twijfel of iets relevant is → FILTER UIT + +Voorbeelden: +✅ INCLUDE: "Jan weigerde haloperidol, zegt dat medicatie hem controleert" → Medicatie-compliance issue +✅ INCLUDE: "Marie uitte suïcidale gedachten tijdens gesprek" → Risicosignaal, urgent +✅ INCLUDE: "Piet verbaal en fysiek agressief, separatie 30 min" → Crisis, gedragsverandering +❌ FILTER: "Jan heeft goed gegeten, ontbijt en lunch zonder problemen" → Routine ADL +❌ FILTER: "Marie deelgenomen aan groepstherapie" → Sociale activiteit, geen issues +❌ FILTER: "Piet heeft medicatie ingenomen volgens schema" → Routine medicatie + +Geef je antwoord als PURE JSON, zonder markdown code blocks.`; + } + + // Verpleegkundige uses base prompt (no filtering) + return OVERDRACHT_SYSTEM_PROMPT; +} + /** * Format timestamp to Dutch date format */ diff --git a/lib/types/overdracht.ts b/lib/types/overdracht.ts index 709833b..5a1e144 100644 --- a/lib/types/overdracht.ts +++ b/lib/types/overdracht.ts @@ -102,6 +102,7 @@ export interface Aandachtspunt { export const GenerateOverdrachtSchema = z.object({ patientId: z.string().uuid('Patient ID moet een geldige UUID zijn'), period: z.enum(['1d', '3d', '7d', '14d']).optional().default('1d'), + filterForRole: z.enum(['psychiater', 'verpleegkundige']).optional().default('verpleegkundige'), }); export type GenerateOverdrachtInput = z.infer;