Files
triqura-ecd/lib/docs/client-context-loader.ts
2025-12-02 13:37:45 +01:00

264 lines
6.4 KiB
TypeScript

/**
* Client Context Loader
*
* Loads client-specific data from Supabase for the AI Client Assistant.
* Uses direct database queries for performance (not HTTP APIs).
*/
import { supabaseAdmin } from '@/lib/supabase/server'
import type { Database } from '@/lib/supabase/database.types'
type Patient = Database['public']['Tables']['patients']['Row']
type Report = Database['public']['Tables']['reports']['Row']
type Intake = Database['public']['Tables']['intakes']['Row']
type Screening = Database['public']['Tables']['screenings']['Row']
type RiskAssessment = Database['public']['Tables']['risk_assessments']['Row']
/**
* Simplified patient info for AI context
*/
export interface ClientPatient {
name: string
birthDate: string
status: string | null
}
/**
* Simplified report for AI context
*/
export interface ClientReport {
type: string
content: string
date: string
}
/**
* Simplified intake for AI context
*/
export interface ClientIntake {
title: string
department: string
status: string
treatmentAdvice: {
advice?: string
outcome?: string
program?: string
department?: string
} | null
notes: string | null
}
/**
* Simplified screening for AI context
*/
export interface ClientScreening {
requestForHelp: string | null
decision: string | null
decisionNotes: string | null
}
/**
* Simplified risk assessment for AI context
*/
export interface ClientRiskAssessment {
type: string
level: string
rationale: string
date: string
}
/**
* Complete client context for AI prompt
*/
export interface ClientContext {
patient: ClientPatient
reports: ClientReport[]
intakes: ClientIntake[]
screening: ClientScreening | null
riskAssessments: ClientRiskAssessment[]
}
/**
* Format patient name from database fields
*/
function formatPatientName(patient: Patient): string {
const givenNames = patient.name_given?.join(' ') || ''
const prefix = patient.name_prefix ? `${patient.name_prefix} ` : ''
return `${givenNames} ${prefix}${patient.name_family}`.trim()
}
/**
* Format date for display (Dutch format)
*/
function formatDate(dateString: string): string {
const date = new Date(dateString)
return date.toLocaleDateString('nl-NL', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
/**
* Load patient basic info
*/
async function loadPatient(clientId: string): Promise<ClientPatient | null> {
const { data, error } = await supabaseAdmin
.from('patients')
.select('name_given, name_family, name_prefix, birth_date, status')
.eq('id', clientId)
.single()
if (error || !data) {
console.error('Error loading patient:', error)
return null
}
return {
name: formatPatientName(data as Patient),
birthDate: formatDate(data.birth_date),
status: data.status,
}
}
/**
* Load recent reports (max 5, newest first)
*/
async function loadReports(clientId: string): Promise<ClientReport[]> {
const { data, error } = await supabaseAdmin
.from('reports')
.select('type, content, created_at')
.eq('patient_id', clientId)
.is('deleted_at', null)
.order('created_at', { ascending: false })
.limit(5)
if (error || !data) {
console.error('Error loading reports:', error)
return []
}
return data.map((report) => ({
type: report.type === 'behandeladvies' ? 'Behandeladvies' : 'Vrije notitie',
content: report.content,
date: formatDate(report.created_at!),
}))
}
/**
* Load recent intakes with treatment advice (max 3, newest first)
*/
async function loadIntakes(clientId: string): Promise<ClientIntake[]> {
const { data, error } = await supabaseAdmin
.from('intakes')
.select('title, department, status, treatment_advice, notes')
.eq('patient_id', clientId)
.order('created_at', { ascending: false })
.limit(3)
if (error || !data) {
console.error('Error loading intakes:', error)
return []
}
return data.map((intake) => ({
title: intake.title,
department: intake.department,
status: intake.status === 'afgerond' ? 'Afgerond' : 'Bezig',
treatmentAdvice: intake.treatment_advice as ClientIntake['treatmentAdvice'],
notes: intake.notes,
}))
}
/**
* Load most recent screening
*/
async function loadScreening(clientId: string): Promise<ClientScreening | null> {
const { data, error } = await supabaseAdmin
.from('screenings')
.select('request_for_help, decision, decision_notes')
.eq('patient_id', clientId)
.order('created_at', { ascending: false })
.limit(1)
.single()
if (error || !data) {
// No screening is a valid state, not an error
return null
}
return {
requestForHelp: data.request_for_help,
decision: data.decision === 'geschikt' ? 'Geschikt' : data.decision === 'niet_geschikt' ? 'Niet geschikt' : null,
decisionNotes: data.decision_notes,
}
}
/**
* Load risk assessments via intakes (max 5, newest first)
*/
async function loadRiskAssessments(clientId: string): Promise<ClientRiskAssessment[]> {
// First get intake IDs for this patient
const { data: intakes, error: intakesError } = await supabaseAdmin
.from('intakes')
.select('id')
.eq('patient_id', clientId)
if (intakesError || !intakes || intakes.length === 0) {
return []
}
const intakeIds = intakes.map((i) => i.id)
// Then get risk assessments for those intakes
const { data, error } = await supabaseAdmin
.from('risk_assessments')
.select('risk_type, risk_level, rationale, assessment_date')
.in('intake_id', intakeIds)
.order('assessment_date', { ascending: false })
.limit(5)
if (error || !data) {
console.error('Error loading risk assessments:', error)
return []
}
return data.map((ra) => ({
type: ra.risk_type,
level: ra.risk_level,
rationale: ra.rationale,
date: formatDate(ra.assessment_date),
}))
}
/**
* Load complete client context for AI assistant
* Loads all data in parallel for performance
*
* @param clientId - UUID of the patient
* @returns ClientContext or null if patient not found
*/
export async function loadClientContext(clientId: string): Promise<ClientContext | null> {
// Load all data in parallel
const [patient, reports, intakes, screening, riskAssessments] = await Promise.all([
loadPatient(clientId),
loadReports(clientId),
loadIntakes(clientId),
loadScreening(clientId),
loadRiskAssessments(clientId),
])
// Patient must exist
if (!patient) {
return null
}
return {
patient,
reports,
intakes,
screening,
riskAssessments,
}
}