Spaces:
Sleeping
Sleeping
File size: 5,725 Bytes
cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d 5266fc5 cd5e33d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | import { generateResponse } from './llmClient.js';
import Warning, { WarningType, WarningSeverity } from '../models/Warning.js';
import Patient from '../models/Patient.js';
import dotenv from 'dotenv';
dotenv.config();
// Re-export enums for backward compatibility
export { WarningType, WarningSeverity };
class WarningService {
/**
* Get all warnings for a patient
*/
async getPatientWarnings(patientId, includeAcknowledged = false) {
const warnings = await Warning.findByPatient(patientId, includeAcknowledged);
return warnings.map(w => w.toResponse());
}
/**
* Get all unacknowledged warnings across all patients
*/
async getAllUnacknowledgedWarnings(limit = 50) {
const warnings = await Warning.findUnacknowledged(limit);
return warnings.map(w => w.toResponse());
}
/**
* Acknowledge a clinical warning
*/
async acknowledgeWarning(warningId, userId, notes = null) {
try {
const warning = await Warning.findById(warningId);
if (!warning) {
return null;
}
await warning.acknowledge(userId, notes);
return warning.toResponse();
} catch {
return null;
}
}
/**
* Create a new clinical warning
*/
async createWarning(warningData) {
const warning = new Warning(warningData);
await warning.save();
return warning.toResponse();
}
/**
* Delete all warnings for a patient
*/
async deletePatientWarnings(patientId) {
const result = await Warning.deleteMany({ patient_id: patientId });
return result.deletedCount;
}
/**
* Use AI to analyze patient data and generate clinical warnings
*/
async analyzePatientForWarnings(patientId, newMedication = null, newCondition = null) {
const patient = await Patient.findById(patientId);
if (!patient) {
return [];
}
// Build the analysis prompt
const medications = patient.medications || [];
const medNames = medications
.filter(m => typeof m === 'object')
.map(m => m.name || '');
let conditions = patient.medical_history || [];
if (newMedication) {
medNames.push(`${newMedication} (NEW)`);
}
if (newCondition) {
conditions = [...conditions, `${newCondition} (NEW)`];
}
const conditionsText = conditions.map(c => {
if (typeof c === 'string') return c;
return c.condition || 'Unknown';
}).join(', ');
const prompt = `You are a clinical decision support AI. Analyze this patient's data for potential clinical warnings.
PATIENT INFORMATION:
- Age: ${patient.age || 'Unknown'}
- Gender: ${patient.gender || 'Unknown'}
- Medical Conditions: ${conditionsText || 'None recorded'}
- Current Medications: ${medNames.join(', ') || 'None recorded'}
Analyze for:
1. DRUG INTERACTIONS - Check if any medications interact negatively with each other
2. CONTRAINDICATIONS - Check if any medications are contraindicated given the patient's conditions
3. ALLERGY RISKS - Common allergy cross-reactions
4. DOSAGE CONCERNS - Age-related dosage considerations
5. DUPLICATE THERAPY - Multiple drugs for the same purpose
Return a JSON array of warnings. Each warning should have:
- warning_type: one of "drug_interaction", "allergy", "contraindication", "abnormal_pattern", "dosage_alert", "duplicate_therapy"
- severity: one of "low", "medium", "high", "critical"
- title: short title (max 50 chars)
- description: detailed explanation
- related_medications: array of medication names involved
- related_conditions: array of conditions involved
- recommendation: suggested action
If there are no warnings, return an empty array [].
Return ONLY valid JSON, no markdown or other text.`;
try {
let responseText = await generateResponse(prompt);
responseText = responseText.trim();
// Remove markdown code blocks if present
if (responseText.startsWith('```')) {
responseText = responseText.split('```')[1];
if (responseText.startsWith('json')) {
responseText = responseText.substring(4);
}
}
responseText = responseText.trim();
const warningsData = JSON.parse(responseText);
if (!Array.isArray(warningsData)) {
return [];
}
// Create warnings in database
const createdWarnings = [];
for (const warningData of warningsData) {
try {
const warning = new Warning({
patient_id: patientId,
warning_type: warningData.warning_type || 'general',
severity: warningData.severity || 'medium',
title: (warningData.title || 'Clinical Warning').substring(0, 100),
description: warningData.description || '',
related_medications: warningData.related_medications || [],
related_conditions: warningData.related_conditions || [],
recommendations: warningData.recommendation ? [warningData.recommendation] : [],
source: 'ai_analysis'
});
await warning.save();
createdWarnings.push(warning.toResponse());
} catch (error) {
console.log(`Error creating warning: ${error.message}`);
continue;
}
}
return createdWarnings;
} catch (error) {
if (error instanceof SyntaxError) {
console.log(`JSON parse error: ${error.message}`);
} else {
console.log(`Error analyzing patient: ${error.message}`);
}
return [];
}
}
/**
* Get warning statistics
*/
async getWarningStats() {
return await Warning.getStats();
}
}
// Singleton instance
export const warningService = new WarningService();
|