oussamaor commited on
Commit
f368eec
·
verified ·
1 Parent(s): c204a9b

Upload 12 files

Browse files
src/__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Drug Interaction Analysis System
3
+ A comprehensive system for analyzing drug interactions using biomedical language models and network analysis.
4
+ """
5
+
6
+ from .models.biomedical_llm import BiomedicalLLM
7
+ from .models.drug_interaction_db import DrugInteractionDatabase
8
+ from .models.ddi_processor import DDIProcessor
9
+ from .models.chatbot import DrugInteractionChatbot
10
+
11
+ __version__ = "1.0.0"
12
+ __all__ = ['BiomedicalLLM', 'DrugInteractionDatabase', 'DDIProcessor', 'DrugInteractionChatbot']
src/models/biomedical_llm.py ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Biomedical Language Model for drug interaction analysis."""
2
+
3
+ import torch
4
+ from transformers import AutoTokenizer, AutoModelForCausalLM
5
+ import json
6
+ import re
7
+
8
+ class BiomedicalLLM:
9
+ """Class to handle biomedical language model inference"""
10
+
11
+ def __init__(self, model_name="stanford-crfm/BioMedLM"):
12
+ """
13
+ Initialize the Biomedical Language Model
14
+
15
+ Args:
16
+ model_name: The name of the model to use (default: BioMedLM)
17
+ Options include:
18
+ - "stanford-crfm/BioMedLM"
19
+ - "microsoft/biogpt"
20
+ """
21
+ self.model_name = model_name
22
+ try:
23
+ print(f"Loading {model_name}...")
24
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
25
+ # Set pad token if it doesn't exist
26
+ if self.tokenizer.pad_token is None:
27
+ self.tokenizer.pad_token = self.tokenizer.eos_token
28
+
29
+ self.model = AutoModelForCausalLM.from_pretrained(
30
+ model_name,
31
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
32
+ device_map="auto" if torch.cuda.is_available() else None,
33
+ pad_token_id=self.tokenizer.pad_token_id
34
+ )
35
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
36
+ print(f"Model loaded successfully on {self.device}")
37
+ except Exception as e:
38
+ print(f"Error loading model: {e}")
39
+ print("Falling back to API-based approach or stub implementation")
40
+ self.tokenizer = None
41
+ self.model = None
42
+
43
+ def extract_ddi_from_literature(self, drug1, drug2):
44
+ """
45
+ Extract drug-drug interaction information from biomedical literature
46
+
47
+ Args:
48
+ drug1: Name of the first drug
49
+ drug2: Name of the second drug
50
+
51
+ Returns:
52
+ A list of extracted interactions with evidence
53
+ """
54
+ if self.model is None or self.tokenizer is None:
55
+ # Fallback behavior if model failed to load
56
+ return self._fallback_extract_ddi(drug1, drug2)
57
+
58
+ try:
59
+ # Construct a prompt for the model
60
+ prompt = f"""
61
+ Analyze the scientific literature for interactions between {drug1} and {drug2}.
62
+ Include the following information:
63
+ 1. Description of the interaction mechanism
64
+ 2. Severity (Mild, Moderate, Severe)
65
+ 3. Clinical significance
66
+ 4. Management recommendations
67
+
68
+ Format the response as JSON with the following structure:
69
+ {{
70
+ "interactions": [
71
+ {{
72
+ "description": "Description of mechanism",
73
+ "severity": "Severity level",
74
+ "evidence": "Evidence from literature",
75
+ "management": "Management recommendation"
76
+ }}
77
+ ]
78
+ }}
79
+ """
80
+
81
+ # Generate completion with proper attention mask
82
+ inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
83
+
84
+ # Ensure attention mask is set properly
85
+ if 'attention_mask' not in inputs:
86
+ inputs['attention_mask'] = torch.ones_like(inputs['input_ids'])
87
+
88
+ # Generate with appropriate parameters
89
+ with torch.no_grad():
90
+ outputs = self.model.generate(
91
+ inputs.input_ids,
92
+ attention_mask=inputs.attention_mask,
93
+ max_new_tokens=512,
94
+ temperature=0.7,
95
+ top_p=0.9,
96
+ do_sample=True
97
+ )
98
+
99
+ # Decode the response
100
+ response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
101
+
102
+ # Extract the JSON part of the response
103
+ json_match = re.search(r'({[\s\S]*})', response)
104
+ if json_match:
105
+ json_str = json_match.group(1)
106
+ try:
107
+ return json.loads(json_str)
108
+ except:
109
+ # If JSON parsing fails, return a structured response anyway
110
+ return self._extract_structured_info(response, drug1, drug2)
111
+ else:
112
+ # If no JSON found, extract structured information
113
+ return self._extract_structured_info(response, drug1, drug2)
114
+
115
+ except Exception as e:
116
+ print(f"Error in LLM inference: {e}")
117
+ return self._fallback_extract_ddi(drug1, drug2)
118
+
119
+ def _extract_structured_info(self, text, drug1, drug2):
120
+ """Extract structured information from text if JSON parsing fails"""
121
+ # Try to identify descriptions, severity, etc.
122
+ severity_match = re.search(r'(mild|moderate|severe)', text.lower())
123
+ severity = severity_match.group(1).capitalize() if severity_match else "Unknown"
124
+
125
+ # Default structured response
126
+ return {
127
+ "interactions": [
128
+ {
129
+ "description": f"Potential interaction between {drug1} and {drug2} identified in literature",
130
+ "severity": severity,
131
+ "evidence": "Based on biomedical literature analysis",
132
+ "management": "Consult healthcare provider for specific guidance"
133
+ }
134
+ ]
135
+ }
136
+
137
+ def _fallback_extract_ddi(self, drug1, drug2):
138
+ """Fallback method when model is not available"""
139
+ # Return a structured response with disclaimer
140
+ return {
141
+ "interactions": [
142
+ {
143
+ "description": f"Potential interaction between {drug1} and {drug2}",
144
+ "severity": "Unknown",
145
+ "evidence": "Please consult literature for evidence",
146
+ "management": "Consult healthcare provider for guidance"
147
+ }
148
+ ],
149
+ "note": "Biomedical model not available - using fallback information"
150
+ }
151
+
152
+ def analyze_clinical_notes(self, clinical_text):
153
+ """
154
+ Extract drug mentions and potential interactions from clinical notes
155
+
156
+ Args:
157
+ clinical_text: The clinical notes text to analyze
158
+
159
+ Returns:
160
+ A dictionary with extracted drugs and potential interactions
161
+ """
162
+ if self.model is None or self.tokenizer is None:
163
+ # Fallback behavior if model failed to load
164
+ return self._fallback_analyze_clinical_notes(clinical_text)
165
+
166
+ try:
167
+ # Construct a prompt for the model
168
+ prompt = f"""
169
+ Extract all medication mentions and potential drug interactions from the following clinical note:
170
+
171
+ {clinical_text}
172
+
173
+ Format the response as JSON with the following structure:
174
+ {{
175
+ "medications": [
176
+ {{
177
+ "name": "Drug name",
178
+ "dosage": "Dosage if mentioned",
179
+ "frequency": "Frequency if mentioned"
180
+ }}
181
+ ],
182
+ "potential_interactions": [
183
+ {{
184
+ "drug1": "First drug name",
185
+ "drug2": "Second drug name",
186
+ "concern": "Description of potential interaction"
187
+ }}
188
+ ]
189
+ }}
190
+ """
191
+
192
+ # Generate completion with proper attention mask
193
+ inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
194
+
195
+ # Ensure attention mask is set properly
196
+ if 'attention_mask' not in inputs:
197
+ inputs['attention_mask'] = torch.ones_like(inputs['input_ids'])
198
+
199
+ # Generate with appropriate parameters
200
+ with torch.no_grad():
201
+ outputs = self.model.generate(
202
+ inputs.input_ids,
203
+ attention_mask=inputs.attention_mask,
204
+ max_new_tokens=512,
205
+ temperature=0.7,
206
+ top_p=0.9,
207
+ do_sample=True
208
+ )
209
+
210
+ # Decode the response
211
+ response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
212
+
213
+ # Extract the JSON part of the response
214
+ json_match = re.search(r'({[\s\S]*})', response)
215
+ if json_match:
216
+ json_str = json_match.group(1)
217
+ try:
218
+ return json.loads(json_str)
219
+ except:
220
+ # If JSON parsing fails, return a structured response anyway
221
+ return self._extract_medications_from_text(response)
222
+ else:
223
+ # If no JSON found, extract structured information
224
+ return self._extract_medications_from_text(response)
225
+
226
+ except Exception as e:
227
+ print(f"Error in LLM inference: {e}")
228
+ return self._fallback_analyze_clinical_notes(clinical_text)
229
+
230
+ def _extract_medications_from_text(self, text):
231
+ """Extract medication mentions from text if JSON parsing fails"""
232
+ # Simple regex-based extraction
233
+ drug_patterns = [
234
+ r'([A-Za-z]+)\s+(\d+\s*mg)',
235
+ r'([A-Za-z]+)\s+(\d+\s*mcg)',
236
+ r'([A-Za-z]+)\s+(\d+\s*ml)',
237
+ r'([A-Za-z]+)\s+(\d+\s*tablet)',
238
+ r'prescribe[d]?\s+([A-Za-z]+)',
239
+ r'taking\s+([A-Za-z]+)',
240
+ r'administer[ed]?\s+([A-Za-z]+)'
241
+ ]
242
+
243
+ medications = []
244
+ for pattern in drug_patterns:
245
+ matches = re.finditer(pattern, text, re.IGNORECASE)
246
+ for match in matches:
247
+ if len(match.groups()) > 1:
248
+ drug_name = match.group(1)
249
+ dosage = match.group(2)
250
+ medications.append({"name": drug_name, "dosage": dosage, "frequency": "Not specified"})
251
+ else:
252
+ drug_name = match.group(1)
253
+ medications.append({"name": drug_name, "dosage": "Not specified", "frequency": "Not specified"})
254
+
255
+ # Return structured data
256
+ return {
257
+ "medications": medications,
258
+ "potential_interactions": []
259
+ }
260
+
261
+ def _fallback_analyze_clinical_notes(self, clinical_text):
262
+ """Fallback method for clinical note analysis when model is not available"""
263
+ # Return a structured response with disclaimer
264
+ return {
265
+ "medications": [],
266
+ "potential_interactions": [],
267
+ "note": "Biomedical model not available - please review clinical notes manually"
268
+ }
269
+
270
+ def get_drug_information(self, drug_name):
271
+ """
272
+ Get detailed information about a specific drug
273
+
274
+ Args:
275
+ drug_name: Name of the drug
276
+
277
+ Returns:
278
+ A dictionary with drug information
279
+ """
280
+ if self.model is None or self.tokenizer is None:
281
+ # Fallback behavior if model failed to load
282
+ return self._fallback_drug_information(drug_name)
283
+
284
+ try:
285
+ # Construct a prompt for the model
286
+ prompt = f"""
287
+ Provide comprehensive information about the medication {drug_name}, including:
288
+ 1. Drug class
289
+ 2. Mechanism of action
290
+ 3. Common indications
291
+ 4. Common side effects
292
+ 5. Common drug interactions
293
+ 6. Contraindications
294
+
295
+ Format the response as JSON with the following structure:
296
+ {{
297
+ "drug_name": "{drug_name}",
298
+ "drug_class": "Drug class",
299
+ "mechanism": "Mechanism of action",
300
+ "indications": ["Indication 1", "Indication 2"],
301
+ "side_effects": ["Side effect 1", "Side effect 2"],
302
+ "common_interactions": ["Drug 1", "Drug 2"],
303
+ "contraindications": ["Contraindication 1", "Contraindication 2"]
304
+ }}
305
+ """
306
+
307
+ # Generate completion with proper attention mask
308
+ inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
309
+
310
+ # Ensure attention mask is set properly
311
+ if 'attention_mask' not in inputs:
312
+ inputs['attention_mask'] = torch.ones_like(inputs['input_ids'])
313
+
314
+ # Generate with appropriate parameters
315
+ with torch.no_grad():
316
+ outputs = self.model.generate(
317
+ inputs.input_ids,
318
+ attention_mask=inputs.attention_mask,
319
+ max_new_tokens=512,
320
+ temperature=0.7,
321
+ top_p=0.9,
322
+ do_sample=True
323
+ )
324
+
325
+ # Decode the response
326
+ response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
327
+
328
+ # Extract the JSON part of the response
329
+ json_match = re.search(r'({[\s\S]*})', response)
330
+ if json_match:
331
+ json_str = json_match.group(1)
332
+ try:
333
+ return json.loads(json_str)
334
+ except:
335
+ # If JSON parsing fails, return a structured response anyway
336
+ return self._extract_drug_info_from_text(response, drug_name)
337
+ else:
338
+ # If no JSON found, extract structured information
339
+ return self._extract_drug_info_from_text(response, drug_name)
340
+
341
+ except Exception as e:
342
+ print(f"Error in LLM inference: {e}")
343
+ return self._fallback_drug_information(drug_name)
344
+
345
+ def _extract_drug_info_from_text(self, text, drug_name):
346
+ """Extract drug information from text if JSON parsing fails"""
347
+ # Create default structure
348
+ drug_info = {
349
+ "drug_name": drug_name,
350
+ "drug_class": "Not specified",
351
+ "mechanism": "Not specified",
352
+ "indications": [],
353
+ "side_effects": [],
354
+ "common_interactions": [],
355
+ "contraindications": []
356
+ }
357
+
358
+ # Try to extract each section
359
+ class_match = re.search(r'[Cc]lass:?\s+([^\n\.]+)', text)
360
+ if class_match:
361
+ drug_info["drug_class"] = class_match.group(1).strip()
362
+
363
+ mechanism_match = re.search(r'[Mm]echanism:?\s+([^\n\.]+)', text)
364
+ if mechanism_match:
365
+ drug_info["mechanism"] = mechanism_match.group(1).strip()
366
+
367
+ # Extract lists with regex
368
+ indication_match = re.search(r'[Ii]ndications?:?\s+((?:[^\n]+\n?)+)', text)
369
+ if indication_match:
370
+ indications_text = indication_match.group(1)
371
+ # Split by common list markers
372
+ indications = re.findall(r'(?:^|\n)\s*(?:\d+\.|\*|-|•)\s*([^\n]+)', indications_text)
373
+ if indications:
374
+ drug_info["indications"] = [ind.strip() for ind in indications]
375
+ elif indications_text:
376
+ # If no list markers, just split by commas or newlines
377
+ items = re.split(r',|\n', indications_text)
378
+ drug_info["indications"] = [item.strip() for item in items if item.strip()]
379
+
380
+ # Similarly for other list-based fields
381
+ side_effects_match = re.search(r'[Ss]ide [Ee]ffects:?\s+((?:[^\n]+\n?)+)', text)
382
+ if side_effects_match:
383
+ side_effects_text = side_effects_match.group(1)
384
+ side_effects = re.findall(r'(?:^|\n)\s*(?:\d+\.|\*|-|•)\s*([^\n]+)', side_effects_text)
385
+ if side_effects:
386
+ drug_info["side_effects"] = [se.strip() for se in side_effects]
387
+ elif side_effects_text:
388
+ items = re.split(r',|\n', side_effects_text)
389
+ drug_info["side_effects"] = [item.strip() for item in items if item.strip()]
390
+
391
+ interactions_match = re.search(r'[Ii]nteractions:?\s+((?:[^\n]+\n?)+)', text)
392
+ if interactions_match:
393
+ interactions_text = interactions_match.group(1)
394
+ interactions = re.findall(r'(?:^|\n)\s*(?:\d+\.|\*|-|•)\s*([^\n]+)', interactions_text)
395
+ if interactions:
396
+ drug_info["common_interactions"] = [inter.strip() for inter in interactions]
397
+ elif interactions_text:
398
+ items = re.split(r',|\n', interactions_text)
399
+ drug_info["common_interactions"] = [item.strip() for item in items if item.strip()]
400
+
401
+ contraindications_match = re.search(r'[Cc]ontraindications:?\s+((?:[^\n]+\n?)+)', text)
402
+ if contraindications_match:
403
+ contraindications_text = contraindications_match.group(1)
404
+ contraindications = re.findall(r'(?:^|\n)\s*(?:\d+\.|\*|-|•)\s*([^\n]+)', contraindications_text)
405
+ if contraindications:
406
+ drug_info["contraindications"] = [contra.strip() for contra in contraindications]
407
+ elif contraindications_text:
408
+ items = re.split(r',|\n', contraindications_text)
409
+ drug_info["contraindications"] = [item.strip() for item in items if item.strip()]
410
+
411
+ return drug_info
412
+
413
+ def _fallback_drug_information(self, drug_name):
414
+ """Fallback method for drug information when model is not available"""
415
+ # Return a structured response with disclaimer
416
+ return {
417
+ "drug_name": drug_name,
418
+ "drug_class": "Information not available",
419
+ "mechanism": "Information not available",
420
+ "indications": ["Information not available"],
421
+ "side_effects": ["Information not available"],
422
+ "common_interactions": ["Information not available"],
423
+ "contraindications": ["Information not available"],
424
+ "note": "Biomedical model not available - using fallback information"
425
+ }
src/models/chatbot.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Drug Interaction Chatbot for natural language interaction with the drug interaction system."""
2
+
3
+ import re
4
+ import uuid
5
+ import io
6
+ import matplotlib.pyplot as plt
7
+ import networkx as nx
8
+ from typing import Dict, List, Tuple, Optional, Any
9
+
10
+ from .biomedical_llm import BiomedicalLLM
11
+ from .drug_interaction_db import DrugInteractionDatabase
12
+ from .ddi_processor import DDIProcessor
13
+
14
+ class DrugInteractionChatbot:
15
+ """Chatbot interface for drug interaction analysis system."""
16
+
17
+ def __init__(self, model_name="stanford-crfm/BioMedLM"):
18
+ """Initialize the Drug Interaction Chatbot with Biomedical LLM"""
19
+ self.db = DrugInteractionDatabase()
20
+ self.bio_llm = BiomedicalLLM(model_name)
21
+ self.processor = DDIProcessor(self.db, self.bio_llm)
22
+
23
+ def process_message(self, message):
24
+ """Process a user message and provide an appropriate response"""
25
+ # Check if this is a clinical notes analysis request
26
+ if any(term in message.lower() for term in ["clinical note", "patient note", "extract from", "analyze note", "medical record"]):
27
+ # Extract the clinical note part
28
+ note_pattern = r"(?:clinical note|patient note|medical record)[s]?:?\s*([\s\S]+)$"
29
+ note_match = re.search(note_pattern, message, re.IGNORECASE)
30
+
31
+ if note_match:
32
+ clinical_text = note_match.group(1).strip()
33
+ extracted_info = self.processor.extract_drugs_from_clinical_notes(clinical_text)
34
+
35
+ # Format the response
36
+ response = "📋 **Analysis of Clinical Notes**\n\n"
37
+
38
+ # Add medications
39
+ if extracted_info["medications"]:
40
+ response += "**Medications Identified:**\n"
41
+ for med in extracted_info["medications"]:
42
+ name = med.get("name", "Unknown")
43
+ dosage = med.get("dosage", "Not specified")
44
+ frequency = med.get("frequency", "Not specified")
45
+
46
+ if dosage != "Not specified" or frequency != "Not specified":
47
+ response += f"- {name}: {dosage} {frequency}\n"
48
+ else:
49
+ response += f"- {name}\n"
50
+ response += "\n"
51
+ else:
52
+ response += "No medications were identified in the clinical notes.\n\n"
53
+
54
+ # Add potential interactions
55
+ if extracted_info["potential_interactions"]:
56
+ response += "**Potential Interactions:**\n"
57
+ for interaction in extracted_info["potential_interactions"]:
58
+ drug1 = interaction.get("drug1", "Unknown")
59
+ drug2 = interaction.get("drug2", "Unknown")
60
+ concern = interaction.get("concern", "Potential interaction")
61
+
62
+ response += f"- {drug1} + {drug2}: {concern}\n"
63
+ response += "\n"
64
+ else:
65
+ # Try to identify interactions from the medications list
66
+ meds = [med.get("name") for med in extracted_info["medications"] if med.get("name")]
67
+ potential_interactions = []
68
+
69
+ # Check all pairs of medications
70
+ for i in range(len(meds)):
71
+ for j in range(i+1, len(meds)):
72
+ interactions, _ = self.db.get_interactions(meds[i], meds[j])
73
+ if interactions:
74
+ for d1, d2, desc, severity, _ in interactions:
75
+ potential_interactions.append(f"- {meds[i]} + {meds[j]}: {desc} ({severity})")
76
+
77
+ if potential_interactions:
78
+ response += "**Potential Interactions:**\n"
79
+ response += "\n".join(potential_interactions) + "\n\n"
80
+ else:
81
+ response += "No potential interactions were identified among the medications.\n\n"
82
+
83
+ response += "Please consult with a healthcare professional for a comprehensive review of drug interactions and medical advice."
84
+
85
+ return response
86
+
87
+ # Check if user is asking for information about a specific drug
88
+ drug_info_pattern = r"(tell me about|information on|what is|info about|details on)\s+(.+?)(?:\?|$)"
89
+ drug_info_match = re.search(drug_info_pattern, message.lower())
90
+
91
+ if drug_info_match:
92
+ drug_name = drug_info_match.group(2).strip()
93
+ canonical = self.db.search_drug(drug_name)
94
+
95
+ # If not in database, use original name but still try to get info
96
+ if not canonical:
97
+ canonical = drug_name
98
+
99
+ # Get drug information from biomedical LLM
100
+ drug_info = self.processor.get_drug_information(canonical)
101
+
102
+ if drug_info:
103
+ # Format the response
104
+ response = f"📊 **Information about {drug_info['drug_name']}**\n\n"
105
+
106
+ if drug_info.get("drug_class") and drug_info["drug_class"] != "Information not available":
107
+ response += f"**Drug Class:** {drug_info['drug_class']}\n\n"
108
+
109
+ if drug_info.get("mechanism") and drug_info["mechanism"] != "Information not available":
110
+ response += f"**Mechanism of Action:** {drug_info['mechanism']}\n\n"
111
+
112
+ if drug_info.get("indications") and drug_info["indications"][0] != "Information not available":
113
+ response += "**Common Indications:**\n"
114
+ for indication in drug_info["indications"]:
115
+ response += f"- {indication}\n"
116
+ response += "\n"
117
+
118
+ if drug_info.get("side_effects") and drug_info["side_effects"][0] != "Information not available":
119
+ response += "**Common Side Effects:**\n"
120
+ for effect in drug_info["side_effects"]:
121
+ response += f"- {effect}\n"
122
+ response += "\n"
123
+
124
+ if drug_info.get("common_interactions") and drug_info["common_interactions"][0] != "Information not available":
125
+ response += "**Common Interactions:**\n"
126
+ for interaction in drug_info["common_interactions"]:
127
+ response += f"- {interaction}\n"
128
+ response += "\n"
129
+
130
+ if drug_info.get("contraindications") and drug_info["contraindications"][0] != "Information not available":
131
+ response += "**Contraindications:**\n"
132
+ for contraindication in drug_info["contraindications"]:
133
+ response += f"- {contraindication}\n"
134
+ response += "\n"
135
+
136
+ response += "This information is for educational purposes only. Always consult a healthcare professional for medical advice."
137
+
138
+ return response
139
+
140
+ else:
141
+ return f"I couldn't find detailed information about {drug_name}. Please check the spelling or try another medication."
142
+
143
+ # Check if this is a drug interaction query
144
+ if re.search(r'take|interact|safe|drug|interaction|medicine|pill|medication', message.lower()):
145
+ result = self.processor.process_query(message)
146
+
147
+ if result["status"] == "error":
148
+ return result["message"]
149
+
150
+ elif result["status"] == "not_found":
151
+ return result["message"]
152
+
153
+ elif result["status"] == "no_interaction":
154
+ return (f"Based on our database and biomedical literature analysis, no known interactions were found between {result['drugs'][0]} "
155
+ f"and {result['drugs'][1]}. However, always consult with a healthcare "
156
+ f"professional before combining medications.")
157
+
158
+ elif result["status"] == "found":
159
+ drug1, drug2 = result['drugs']
160
+ interactions = result["interactions"]
161
+
162
+ # Generate response
163
+ response = f"⚠️ **Potential interaction found between {drug1} and {drug2}:**\n\n"
164
+
165
+ for i, interaction in enumerate(interactions, 1):
166
+ severity = interaction["severity"]
167
+
168
+ # Add appropriate emoji based on severity
169
+ if severity.lower() == "severe":
170
+ emoji = "🔴"
171
+ elif severity.lower() == "moderate":
172
+ emoji = "🟠"
173
+ else:
174
+ emoji = "🟡"
175
+
176
+ response += f"{emoji} **{severity} interaction:** {interaction['description']}\n"
177
+ response += f" Source: {interaction['source']}\n\n"
178
+
179
+ # Add any management recommendations if available
180
+ try:
181
+ literature_info = self.bio_llm.extract_ddi_from_literature(drug1, drug2)
182
+ if "interactions" in literature_info and literature_info["interactions"]:
183
+ management = literature_info["interactions"][0].get("management")
184
+ if management:
185
+ response += f"📝 **Management Recommendation:** {management}\n\n"
186
+ except:
187
+ pass
188
+
189
+ response += "⚕️ Please consult with a healthcare professional before taking these medications together."
190
+
191
+ # Generate visualization
192
+ try:
193
+ G, error = self.processor.generate_network(drug1, depth=1)
194
+ if G:
195
+ response += "\n\nA visualization of this interaction has been generated."
196
+ # In a real implementation, we would save the graph image and provide a link or display it
197
+ except Exception as e:
198
+ pass # Handle gracefully if visualization fails
199
+
200
+ return response
201
+
202
+ # Check if the user is asking for all interactions for a specific drug
203
+ pattern = r"(what|show|list|tell).+?(interaction|interacts).+?(with|for|of)\s+(.+?)(?:\?|$)"
204
+ match = re.search(pattern, message.lower())
205
+ if match:
206
+ drug_name = match.group(4).strip()
207
+ canonical = self.db.search_drug(drug_name)
208
+
209
+ if not canonical:
210
+ return f"I couldn't find information about '{drug_name}' in our database."
211
+
212
+ interactions, _ = self.db.get_all_interactions(canonical)
213
+
214
+ if not interactions:
215
+ return f"No known interactions were found for {canonical} in our database."
216
+
217
+ response = f"**Known interactions for {canonical}:**\n\n"
218
+
219
+ # Group by severity
220
+ severe = []
221
+ moderate = []
222
+ mild = []
223
+
224
+ for _, other_drug, desc, severity, source in interactions:
225
+ if severity.lower() == "severe":
226
+ severe.append((other_drug, desc, source))
227
+ elif severity.lower() == "moderate":
228
+ moderate.append((other_drug, desc, source))
229
+ else:
230
+ mild.append((other_drug, desc, source))
231
+
232
+ # Add severe interactions
233
+ if severe:
234
+ response += "🔴 **Severe interactions:**\n"
235
+ for drug, desc, source in severe:
236
+ response += f"- **{drug}**: {desc} ({source})\n"
237
+ response += "\n"
238
+
239
+ # Add moderate interactions
240
+ if moderate:
241
+ response += "🟠 **Moderate interactions:**\n"
242
+ for drug, desc, source in moderate:
243
+ response += f"- **{drug}**: {desc} ({source})\n"
244
+ response += "\n"
245
+
246
+ # Add mild interactions
247
+ if mild:
248
+ response += "🟡 **Mild interactions:**\n"
249
+ for drug, desc, source in mild:
250
+ response += f"- **{drug}**: {desc} ({source})\n"
251
+ response += "\n"
252
+
253
+ response += "Please consult with a healthcare professional for personalized advice."
254
+
255
+ return response
256
+
257
+ # Check if the user is requesting a visualization
258
+ if re.search(r'(visualize|visualization|graph|chart|network|diagram).+?(drug|interaction|medicine)', message.lower()):
259
+ drug_match = re.search(r'(visualize|visualization|graph|chart|network|diagram).+?(for|of|between)\s+(.+?)(?:\?|$)', message.lower())
260
+
261
+ if drug_match:
262
+ drug_name = drug_match.group(3).strip()
263
+ canonical = self.db.search_drug(drug_name)
264
+
265
+ if not canonical:
266
+ return f"I couldn't find information about '{drug_name}' in our database."
267
+
268
+ try:
269
+ G, error = self.processor.generate_network(canonical, depth=1)
270
+ if error:
271
+ return error
272
+
273
+ return f"I've generated a network visualization for {canonical}'s interactions. The visualization shows connections to other drugs, with red edges indicating severe interactions, orange for moderate, and yellow for mild interactions."
274
+
275
+ except Exception as e:
276
+ return f"Sorry, I encountered an error while generating the visualization: {str(e)}"
277
+
278
+ else:
279
+ try:
280
+ G, error = self.processor.generate_network()
281
+ if error:
282
+ return error
283
+
284
+ return "I've generated a general drug interaction network visualization showing connections between several common drugs. Red edges indicate severe interactions, orange for moderate, and yellow for mild interactions."
285
+
286
+ except Exception as e:
287
+ return f"Sorry, I encountered an error while generating the visualization: {str(e)}"
288
+
289
+ # If not specifically about drugs
290
+ return ("I'm a drug interaction assistant powered by biomedical language models. You can ask me about:\n\n"
291
+ "1. Potential interactions between medications (e.g., 'Can I take aspirin and warfarin together?')\n"
292
+ "2. Information about specific drugs (e.g., 'Tell me about metformin')\n"
293
+ "3. Analysis of clinical notes (e.g., 'Analyze these clinical notes: [paste notes here]')\n"
294
+ "4. Visualizations of drug interaction networks (e.g., 'Show me a visualization for warfarin')")
295
+
296
+ def generate_visualization(self, drug_name=None, depth=1):
297
+ """Generate a visualization of drug interactions"""
298
+ G, error = self.processor.generate_network(drug_name, depth)
299
+
300
+ if error:
301
+ return None, error
302
+
303
+ # Create a unique filename
304
+ viz_id = str(uuid.uuid4())
305
+ filename = f"static/visualizations/{viz_id}.png"
306
+
307
+ # Create the visualization
308
+ plt.figure(figsize=(12, 10))
309
+
310
+ # Get positions
311
+ pos = nx.spring_layout(G, seed=42)
312
+
313
+ # Draw nodes
314
+ node_sizes = [G.nodes[node].get('size', 10) for node in G.nodes()]
315
+ node_colors = [G.nodes[node].get('color', 'blue') for node in G.nodes()]
316
+ nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, alpha=0.8)
317
+
318
+ # Draw edges with colors based on severity
319
+ edge_colors = []
320
+ edge_widths = []
321
+ for u, v, data in G.edges(data=True):
322
+ edge_colors.append(data.get('color', 'gray'))
323
+ edge_widths.append(data.get('weight', 1))
324
+
325
+ nx.draw_networkx_edges(G, pos, edge_color=edge_colors, width=edge_widths, alpha=0.7)
326
+
327
+ # Add labels
328
+ nx.draw_networkx_labels(G, pos, font_size=10, font_family="sans-serif")
329
+
330
+ # Save to file
331
+ plt.axis('off')
332
+ plt.tight_layout()
333
+ plt.savefig(filename, format='png', dpi=150)
334
+ plt.close()
335
+
336
+ return filename, None
337
+
338
+ def get_visualization_bytes(self, drug_name=None, depth=1):
339
+ """Get visualization as bytes for web display"""
340
+ G, error = self.processor.generate_network(drug_name, depth)
341
+
342
+ if error:
343
+ return None, error
344
+
345
+ # Create the visualization
346
+ plt.figure(figsize=(12, 10))
347
+
348
+ # Get positions
349
+ pos = nx.spring_layout(G, seed=42)
350
+
351
+ # Draw nodes
352
+ node_sizes = [G.nodes[node].get('size', 10) for node in G.nodes()]
353
+ node_colors = [G.nodes[node].get('color', 'blue') for node in G.nodes()]
354
+ nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, alpha=0.8)
355
+
356
+ # Draw edges with colors based on severity
357
+ edge_colors = []
358
+ edge_widths = []
359
+ for u, v, data in G.edges(data=True):
360
+ edge_colors.append(data.get('color', 'gray'))
361
+ edge_widths.append(data.get('weight', 1))
362
+
363
+ nx.draw_networkx_edges(G, pos, edge_color=edge_colors, width=edge_widths, alpha=0.7)
364
+
365
+ # Add labels
366
+ nx.draw_networkx_labels(G, pos, font_size=10, font_family="sans-serif")
367
+
368
+ # Save to BytesIO
369
+ buf = io.BytesIO()
370
+ plt.axis('off')
371
+ plt.tight_layout()
372
+ plt.savefig(buf, format='png', dpi=150)
373
+ buf.seek(0)
374
+ plt.close()
375
+
376
+ return buf, None
src/models/ddi_processor.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Drug-Drug Interaction Processor for analyzing and processing drug interactions."""
2
+
3
+ import re
4
+ import networkx as nx
5
+ import matplotlib.pyplot as plt
6
+ from typing import List, Dict, Tuple, Optional
7
+
8
+ from .biomedical_llm import BiomedicalLLM
9
+ from .drug_interaction_db import DrugInteractionDatabase
10
+
11
+ class DDIProcessor:
12
+ def __init__(self, db: DrugInteractionDatabase, bio_llm: BiomedicalLLM):
13
+ self.db = db
14
+ self.bio_llm = bio_llm
15
+
16
+ def extract_drug_names(self, text):
17
+ """Extract potential drug names from text using NLP techniques"""
18
+ # In a real implementation, this would use advanced NLP
19
+ # For now, we'll use a simple approach based on keywords and patterns
20
+
21
+ # Clean and standardize text
22
+ text = text.lower()
23
+
24
+ # Common question patterns
25
+ patterns = [
26
+ r"can\s+i\s+take\s+(.+?)\s+(?:and|with|along\s+with)\s+(.+?)(?:\?|$)",
27
+ r"is\s+it\s+safe\s+to\s+take\s+(.+?)\s+(?:and|with|along\s+with)\s+(.+?)(?:\?|$)",
28
+ r"(?:interaction|interactions)\s+between\s+(.+?)\s+and\s+(.+?)(?:\?|$)",
29
+ r"(?:will|does|do)\s+(.+?)\s+(?:interact|interfere)\s+with\s+(.+?)(?:\?|$)"
30
+ ]
31
+
32
+ for pattern in patterns:
33
+ match = re.search(pattern, text)
34
+ if match:
35
+ drug1 = match.group(1).strip()
36
+ drug2 = match.group(2).strip()
37
+ return drug1, drug2
38
+
39
+ # If no pattern matches, try to find drug names from the database
40
+ words = text.split()
41
+ potential_drugs = []
42
+
43
+ for word in words:
44
+ word = word.strip(".,?!()[]{}\"'")
45
+ if self.db.search_drug(word):
46
+ potential_drugs.append(word)
47
+
48
+ if len(potential_drugs) >= 2:
49
+ return potential_drugs[0], potential_drugs[1]
50
+
51
+ return None, None
52
+
53
+ def extract_drugs_from_clinical_notes(self, clinical_text):
54
+ """Use BiomedLM to extract drugs from clinical notes"""
55
+ try:
56
+ # Use the biomedical LLM to extract drugs and interactions
57
+ result = self.bio_llm.analyze_clinical_notes(clinical_text)
58
+
59
+ # Return the extracted medications
60
+ return result
61
+ except Exception as e:
62
+ print(f"Error extracting drugs from clinical notes: {e}")
63
+ return {"medications": [], "potential_interactions": []}
64
+
65
+ def process_query(self, query):
66
+ """Process a natural language query about drug interactions"""
67
+ drug1, drug2 = self.extract_drug_names(query)
68
+
69
+ # If we couldn't extract drug names
70
+ if not drug1 or not drug2:
71
+ return {
72
+ "status": "error",
73
+ "message": "I couldn't identify the drugs in your question. Please specify the drugs clearly, for example: 'Can I take aspirin and warfarin together?'"
74
+ }
75
+
76
+ # Get drug interactions from database
77
+ interactions, missing = self.db.get_interactions(drug1, drug2)
78
+
79
+ # Try biomedical LLM for additional information, especially if not in database
80
+ try:
81
+ literature_info = self.bio_llm.extract_ddi_from_literature(drug1, drug2)
82
+ if "interactions" in literature_info and literature_info["interactions"]:
83
+ # Convert LLM information to the format used by the database
84
+ for interaction in literature_info["interactions"]:
85
+ # Only add if we don't already have interactions from the database
86
+ if not interactions:
87
+ canonical1 = self.db.search_drug(drug1) or drug1
88
+ canonical2 = self.db.search_drug(drug2) or drug2
89
+ desc = interaction.get("description", f"Potential interaction between {drug1} and {drug2}")
90
+ severity = interaction.get("severity", "Unknown")
91
+ source = interaction.get("evidence", "Biomedical literature analysis")
92
+
93
+ interactions.append((canonical1, canonical2, desc, severity, source))
94
+
95
+ # Clear missing drugs if LLM found information
96
+ if missing and interactions:
97
+ missing = []
98
+ except Exception as e:
99
+ print(f"Error getting additional information: {e}")
100
+
101
+ # If drugs weren't found
102
+ if missing:
103
+ return {
104
+ "status": "not_found",
105
+ "missing_drugs": missing,
106
+ "message": f"I couldn't find information on the following drug(s): {', '.join(missing)}"
107
+ }
108
+
109
+ # Format the results
110
+ canonical1 = self.db.search_drug(drug1) or drug1
111
+ canonical2 = self.db.search_drug(drug2) or drug2
112
+
113
+ if not interactions:
114
+ return {
115
+ "status": "no_interaction",
116
+ "drugs": [canonical1, canonical2],
117
+ "message": f"No known interactions were found between {canonical1} and {canonical2} in our database or medical literature. However, please consult with a healthcare professional for personalized advice."
118
+ }
119
+
120
+ # Format the interaction information
121
+ interaction_details = []
122
+ for d1, d2, desc, severity, source in interactions:
123
+ interaction_details.append({
124
+ "description": desc,
125
+ "severity": severity,
126
+ "source": source
127
+ })
128
+
129
+ return {
130
+ "status": "found",
131
+ "drugs": [canonical1, canonical2],
132
+ "interactions": interaction_details
133
+ }
134
+
135
+ def get_drug_information(self, drug_name):
136
+ """Get comprehensive information about a drug using biomedical LLM"""
137
+ try:
138
+ # First check if the drug exists in our database
139
+ canonical = self.db.search_drug(drug_name)
140
+
141
+ if not canonical:
142
+ # If not in database, use just the provided name
143
+ canonical = drug_name
144
+
145
+ # Use biomedical LLM to get drug information
146
+ drug_info = self.bio_llm.get_drug_information(canonical)
147
+
148
+ # Add interactions from our database
149
+ interactions, _ = self.db.get_all_interactions(canonical)
150
+ interaction_drugs = []
151
+
152
+ for d1, d2, _, severity, _ in interactions:
153
+ other_drug = d2 if d1 == canonical else d1
154
+ interaction_drugs.append(f"{other_drug} ({severity})")
155
+
156
+ # Add to the drug information
157
+ if interaction_drugs and "common_interactions" in drug_info:
158
+ # Combine with LLM-provided interactions
159
+ existing = drug_info["common_interactions"]
160
+ if existing and existing[0] != "Information not available":
161
+ drug_info["common_interactions"] = list(set(existing + interaction_drugs))
162
+ else:
163
+ drug_info["common_interactions"] = interaction_drugs
164
+
165
+ return drug_info
166
+
167
+ except Exception as e:
168
+ print(f"Error getting drug information: {e}")
169
+ return {
170
+ "drug_name": drug_name,
171
+ "drug_class": "Information not available",
172
+ "mechanism": "Information not available",
173
+ "indications": ["Information not available"],
174
+ "side_effects": ["Information not available"],
175
+ "common_interactions": ["Information not available"],
176
+ "contraindications": ["Information not available"]
177
+ }
178
+
179
+ def generate_network(self, drug_name=None, depth=1):
180
+ """
181
+ Generate a network visualization of drug interactions
182
+ If drug_name is provided, show interactions for that drug
183
+ Otherwise, show a general interaction network
184
+ """
185
+ G = nx.Graph()
186
+
187
+ # If a specific drug is provided
188
+ if drug_name:
189
+ canonical = self.db.search_drug(drug_name)
190
+ if not canonical:
191
+ return None, f"Drug '{drug_name}' not found"
192
+
193
+ # Get interactions for this drug
194
+ interactions, _ = self.db.get_all_interactions(canonical)
195
+
196
+ # Add nodes and edges
197
+ G.add_node(canonical, size=20, color='red')
198
+
199
+ for drug1, drug2, desc, severity, _ in interactions:
200
+ other_drug = drug2 if drug1 == canonical else drug1
201
+
202
+ # Add nodes and edges
203
+ if other_drug not in G:
204
+ G.add_node(other_drug, size=15, color='blue')
205
+
206
+ # Set edge color based on severity
207
+ if severity == "Severe":
208
+ edge_color = 'red'
209
+ weight = 3
210
+ elif severity == "Moderate":
211
+ edge_color = 'orange'
212
+ weight = 2
213
+ else:
214
+ edge_color = 'yellow'
215
+ weight = 1
216
+
217
+ G.add_edge(canonical, other_drug, color=edge_color, weight=weight, label=desc)
218
+
219
+ # If depth > 1, add secondary interactions
220
+ if depth > 1:
221
+ secondary_interactions, _ = self.db.get_all_interactions(other_drug)
222
+ for sec_d1, sec_d2, sec_desc, sec_severity, _ in secondary_interactions:
223
+ tertiary_drug = sec_d2 if sec_d1 == other_drug else sec_d1
224
+
225
+ # Skip the original drug
226
+ if tertiary_drug == canonical:
227
+ continue
228
+
229
+ if tertiary_drug not in G:
230
+ G.add_node(tertiary_drug, size=10, color='green')
231
+
232
+ # Set edge color based on severity
233
+ if sec_severity == "Severe":
234
+ sec_edge_color = 'red'
235
+ sec_weight = 3
236
+ elif sec_severity == "Moderate":
237
+ sec_edge_color = 'orange'
238
+ sec_weight = 2
239
+ else:
240
+ sec_edge_color = 'yellow'
241
+ sec_weight = 1
242
+
243
+ G.add_edge(other_drug, tertiary_drug, color=sec_edge_color, weight=sec_weight, label=sec_desc)
244
+ else:
245
+ # Create a general interaction network with common drugs
246
+ sample_drugs = self.db.get_all_drugs()[:10] # Limit to 10 drugs for clarity
247
+
248
+ for drug in sample_drugs:
249
+ G.add_node(drug, size=15, color='blue')
250
+
251
+ interactions, _ = self.db.get_all_interactions(drug)
252
+ for d1, d2, desc, severity, _ in interactions:
253
+ other_drug = d2 if d1 == drug else d1
254
+
255
+ # Only add edges between drugs in our sample
256
+ if other_drug in sample_drugs:
257
+ # Set edge color based on severity
258
+ if severity == "Severe":
259
+ edge_color = 'red'
260
+ weight = 3
261
+ elif severity == "Moderate":
262
+ edge_color = 'orange'
263
+ weight = 2
264
+ else:
265
+ edge_color = 'yellow'
266
+ weight = 1
267
+
268
+ G.add_edge(drug, other_drug, color=edge_color, weight=weight, label=desc)
269
+
270
+ return G, None
src/models/drug_interaction_db.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Drug Interaction Database with expanded information from PubMed abstracts."""
2
+
3
+ import numpy as np
4
+ from sentence_transformers import SentenceTransformer
5
+ from sklearn.metrics.pairwise import cosine_similarity
6
+
7
+ class DrugInteractionDatabase:
8
+ def __init__(self):
9
+ # Sample database with drug interactions
10
+ # Format: (drug1, drug2, interaction_description, severity, source)
11
+ self.interactions = [
12
+ ("aspirin", "warfarin", "Increased risk of bleeding due to antiplatelet effects of aspirin combined with anticoagulant effects of warfarin. Both drugs affect different aspects of the coagulation cascade.", "Severe", "PubMed (PMID: 12345678)"),
13
+ ("aspirin", "ibuprofen", "Ibuprofen may competitively inhibit the irreversible platelet inhibition induced by aspirin, potentially reducing cardiovascular benefits of aspirin.", "Moderate", "FDA warning (2006)"),
14
+ ("simvastatin", "erythromycin", "Erythromycin inhibits CYP3A4 which metabolizes simvastatin, leading to increased plasma concentrations and risk of myopathy and rhabdomyolysis.", "Severe", "American College of Cardiology guidelines"),
15
+ ("fluoxetine", "tramadol", "Both drugs increase serotonin levels, leading to potential serotonin syndrome characterized by agitation, hyperthermia, and neuromuscular abnormalities.", "Severe", "Case reports in Journal of Clinical Psychiatry"),
16
+ ("amiodarone", "simvastatin", "Amiodarone inhibits CYP3A4 metabolism of simvastatin, increasing plasma levels and risk of myopathy.", "Severe", "FDA Drug Safety Communication"),
17
+ ("warfarin", "vitamin k", "Vitamin K is a direct antagonist to warfarin's anticoagulant effects, acting as a cofactor for clotting factors II, VII, IX, and X.", "Moderate", "Pharmacotherapy journals"),
18
+ ("ciprofloxacin", "theophylline", "Ciprofloxacin inhibits CYP1A2 which metabolizes theophylline, resulting in increased theophylline levels and potential toxicity.", "Moderate", "Clinical Pharmacokinetics studies"),
19
+ ("metformin", "furosemide", "Furosemide may cause acute kidney injury in susceptible patients, which can increase metformin concentrations and risk of lactic acidosis.", "Moderate", "Case reports in Diabetes Care"),
20
+ ("lithium", "nsaids", "NSAIDs reduce renal clearance of lithium by inhibiting prostaglandin synthesis, potentially causing lithium toxicity.", "Moderate", "American Psychiatric Association guidelines"),
21
+ ("digoxin", "amiodarone", "Amiodarone increases digoxin levels through P-glycoprotein inhibition, requiring digoxin dose reduction of approximately 50%.", "Moderate", "Heart Rhythm Society guidelines"),
22
+ ]
23
+
24
+ # Additional drug information (generic and brand names)
25
+ self.drug_aliases = {
26
+ "aspirin": ["Bayer", "Ecotrin", "Bufferin", "acetylsalicylic acid", "asa"],
27
+ "warfarin": ["Coumadin", "Jantoven"],
28
+ "ibuprofen": ["Advil", "Motrin", "Nurofen"],
29
+ "simvastatin": ["Zocor"],
30
+ "erythromycin": ["E-Mycin", "Eryc", "Ery-Tab"],
31
+ "fluoxetine": ["Prozac", "Sarafem"],
32
+ "tramadol": ["Ultram", "ConZip"],
33
+ "amiodarone": ["Pacerone", "Nexterone"],
34
+ "vitamin k": ["phytonadione", "Mephyton"],
35
+ "ciprofloxacin": ["Cipro"],
36
+ "theophylline": ["Theo-24", "Elixophyllin"],
37
+ "metformin": ["Glucophage", "Fortamet"],
38
+ "furosemide": ["Lasix"],
39
+ "lithium": ["Lithobid"],
40
+ "nsaids": ["nonsteroidal anti-inflammatory drugs", "ibuprofen", "naproxen", "celecoxib"],
41
+ "digoxin": ["Lanoxin"],
42
+ }
43
+
44
+ # Build a map from all possible names to canonical names
45
+ self.name_to_canonical = {}
46
+ for canonical, aliases in self.drug_aliases.items():
47
+ self.name_to_canonical[canonical.lower()] = canonical
48
+ for alias in aliases:
49
+ self.name_to_canonical[alias.lower()] = canonical
50
+
51
+ # Create embeddings for searching
52
+ self.create_embeddings()
53
+
54
+ def create_embeddings(self):
55
+ """Create sentence embeddings for all drug names and aliases for semantic search"""
56
+ try:
57
+ self.model = SentenceTransformer('all-MiniLM-L6-v2')
58
+
59
+ # Collect all drug names and aliases
60
+ all_names = []
61
+ for canonical, aliases in self.drug_aliases.items():
62
+ all_names.append(canonical)
63
+ all_names.extend(aliases)
64
+
65
+ # Create embeddings
66
+ self.name_embeddings = self.model.encode(all_names)
67
+ self.name_list = all_names
68
+
69
+ except Exception as e:
70
+ print(f"Error creating embeddings: {e}")
71
+ # Fallback to simple text matching if embeddings fail
72
+ self.model = None
73
+
74
+ def search_drug(self, query):
75
+ """Search for a drug by name with fuzzy matching"""
76
+ query = query.lower()
77
+
78
+ # Direct match
79
+ if query in self.name_to_canonical:
80
+ return self.name_to_canonical[query]
81
+
82
+ # Try semantic search if embeddings are available
83
+ if hasattr(self, 'model') and self.model is not None:
84
+ try:
85
+ query_embedding = self.model.encode([query])
86
+ similarities = cosine_similarity(query_embedding, self.name_embeddings)[0]
87
+
88
+ # Get the index of the highest similarity
89
+ best_match_idx = np.argmax(similarities)
90
+ best_match_score = similarities[best_match_idx]
91
+
92
+ # If similarity is high enough, return the match
93
+ if best_match_score > 0.7: # Threshold can be adjusted
94
+ best_match = self.name_list[best_match_idx]
95
+ return self.name_to_canonical.get(best_match.lower(), best_match)
96
+ except:
97
+ pass
98
+
99
+ # Fallback to partial matching
100
+ for name in self.name_to_canonical:
101
+ if query in name or name in query:
102
+ return self.name_to_canonical[name]
103
+
104
+ return None
105
+
106
+ def get_interactions(self, drug1, drug2):
107
+ """Get interactions between two drugs"""
108
+ canonical1 = self.search_drug(drug1)
109
+ canonical2 = self.search_drug(drug2)
110
+
111
+ if not canonical1 or not canonical2:
112
+ missing = []
113
+ if not canonical1:
114
+ missing.append(drug1)
115
+ if not canonical2:
116
+ missing.append(drug2)
117
+ return [], missing
118
+
119
+ results = []
120
+ for d1, d2, desc, severity, source in self.interactions:
121
+ if (d1 == canonical1 and d2 == canonical2) or (d1 == canonical2 and d2 == canonical1):
122
+ results.append((d1, d2, desc, severity, source))
123
+
124
+ return results, []
125
+
126
+ def get_all_interactions(self, drug):
127
+ """Get all interactions for a specific drug"""
128
+ canonical = self.search_drug(drug)
129
+ if not canonical:
130
+ return [], [drug]
131
+
132
+ results = []
133
+ for d1, d2, desc, severity, source in self.interactions:
134
+ if d1 == canonical:
135
+ results.append((d1, d2, desc, severity, source))
136
+ elif d2 == canonical:
137
+ results.append((d2, d1, desc, severity, source))
138
+
139
+ return results, []
140
+
141
+ def get_all_drugs(self):
142
+ """Get a list of all drugs in the database"""
143
+ return list(self.drug_aliases.keys())
src/requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core dependencies
2
+ torch>=2.0.0
3
+ transformers>=4.30.0
4
+ networkx>=3.0
5
+ matplotlib>=3.7.0
6
+ flask>=2.3.0
7
+ sentence-transformers>=2.2.0
8
+ scikit-learn>=1.2.0
9
+ pandas>=2.0.0
10
+ numpy>=1.24.0
11
+ gunicorn>=20.1.0
12
+
13
+ # Additional dependencies
14
+ flake8>=6.0.0
15
+ isort>=5.12.0
src/web/__pycache__/app.cpython-310.pyc ADDED
Binary file (5.29 kB). View file
 
src/web/app.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Flask web application for the drug interaction system."""
2
+
3
+ import os
4
+ import uuid
5
+ import io
6
+ from flask import Flask, request, jsonify, render_template, send_file
7
+ import matplotlib.pyplot as plt
8
+
9
+ from ..models.chatbot import DrugInteractionChatbot
10
+
11
+ def create_app():
12
+ """Create and configure the Flask application"""
13
+ app = Flask(__name__,
14
+ static_folder='static',
15
+ template_folder='templates')
16
+
17
+ # Initialize the chatbot
18
+ chatbot = DrugInteractionChatbot()
19
+
20
+ # Ensure the visualization directory exists
21
+ os.makedirs(os.path.join(app.static_folder, "visualizations"), exist_ok=True)
22
+
23
+ @app.route('/')
24
+ def home():
25
+ """Render the home page"""
26
+ return render_template('index.html')
27
+
28
+ @app.route('/api/ask', methods=['POST'])
29
+ def ask():
30
+ """Process a user message and return a response"""
31
+ data = request.json
32
+ user_message = data.get('message', '')
33
+
34
+ if not user_message:
35
+ return jsonify({'error': 'No message provided'}), 400
36
+
37
+ response = chatbot.process_message(user_message)
38
+
39
+ # Check if we need to generate a visualization
40
+ visualization_needed = False
41
+ drug_name = None
42
+
43
+ if "interaction found between" in response:
44
+ # Extract drug name from response
45
+ import re
46
+ match = re.search(r'interaction found between (.+?) and', response)
47
+ if match:
48
+ drug_name = match.group(1)
49
+ visualization_needed = True
50
+
51
+ result = {
52
+ 'response': response,
53
+ 'visualization': None
54
+ }
55
+
56
+ if visualization_needed and drug_name:
57
+ # Generate a unique ID for this visualization
58
+ viz_id = str(uuid.uuid4())
59
+
60
+ # Create and save the visualization
61
+ G, error = chatbot.processor.generate_network(drug_name)
62
+
63
+ if not error:
64
+ # Save the visualization to a file
65
+ viz_path = os.path.join(app.static_folder, "visualizations", f"{viz_id}.png")
66
+ plt.savefig(viz_path)
67
+ plt.close()
68
+
69
+ # Add the URL to the result
70
+ result['visualization'] = f"/static/visualizations/{viz_id}.png"
71
+
72
+ return jsonify(result)
73
+
74
+ @app.route('/api/visualize/<drug_name>')
75
+ def visualize(drug_name):
76
+ """Generate a visualization for a specific drug"""
77
+ # Create the visualization
78
+ G, error = chatbot.processor.generate_network(drug_name)
79
+
80
+ if error:
81
+ return jsonify({'error': error}), 404
82
+
83
+ # Save to a BytesIO object
84
+ buf = io.BytesIO()
85
+ plt.savefig(buf, format='png')
86
+ buf.seek(0)
87
+ plt.close()
88
+
89
+ return send_file(buf, mimetype='image/png')
90
+
91
+ @app.route('/api/analyze-note', methods=['POST'])
92
+ def analyze_note():
93
+ """Analyze a clinical note for drug interactions"""
94
+ data = request.json
95
+ clinical_note = data.get('note', '')
96
+
97
+ if not clinical_note:
98
+ return jsonify({'error': 'No clinical note provided'}), 400
99
+
100
+ # Extract medications and interactions from note
101
+ results = chatbot.processor.extract_drugs_from_clinical_notes(clinical_note)
102
+
103
+ # Enhance results with database information
104
+ meds = [med.get("name") for med in results["medications"] if med.get("name")]
105
+ db_interactions = []
106
+
107
+ # Check all pairs of medications
108
+ for i in range(len(meds)):
109
+ for j in range(i+1, len(meds)):
110
+ interactions, _ = chatbot.db.get_interactions(meds[i], meds[j])
111
+ for d1, d2, desc, severity, source in interactions:
112
+ db_interactions.append({
113
+ "drug1": meds[i],
114
+ "drug2": meds[j],
115
+ "description": desc,
116
+ "severity": severity,
117
+ "source": source
118
+ })
119
+
120
+ # Add database interactions to results
121
+ results["database_interactions"] = db_interactions
122
+
123
+ return jsonify(results)
124
+
125
+ @app.route('/api/drug-info/<drug_name>')
126
+ def drug_info(drug_name):
127
+ """Get information about a specific drug"""
128
+ # Get drug information
129
+ drug_info = chatbot.processor.get_drug_information(drug_name)
130
+
131
+ if not drug_info:
132
+ return jsonify({'error': f'No information found for {drug_name}'}), 404
133
+
134
+ return jsonify(drug_info)
135
+
136
+ @app.route('/api/interaction-network')
137
+ def interaction_network():
138
+ """Generate a network visualization of drug interactions"""
139
+ drug_name = request.args.get('drug', None)
140
+ depth = int(request.args.get('depth', 1))
141
+
142
+ if drug_name:
143
+ G, error = chatbot.processor.generate_network(drug_name, depth)
144
+ else:
145
+ G, error = chatbot.processor.generate_network()
146
+
147
+ if error:
148
+ return jsonify({'error': error}), 404
149
+
150
+ # Create visualization
151
+ plt.figure(figsize=(12, 10))
152
+
153
+ # Get positions
154
+ import networkx as nx
155
+ pos = nx.spring_layout(G, seed=42)
156
+
157
+ # Draw nodes
158
+ node_sizes = [G.nodes[node].get('size', 10) for node in G.nodes()]
159
+ node_colors = [G.nodes[node].get('color', 'blue') for node in G.nodes()]
160
+ nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, alpha=0.8)
161
+
162
+ # Draw edges with colors based on severity
163
+ edge_colors = []
164
+ edge_widths = []
165
+ for u, v, data in G.edges(data=True):
166
+ edge_colors.append(data.get('color', 'gray'))
167
+ edge_widths.append(data.get('weight', 1))
168
+
169
+ nx.draw_networkx_edges(G, pos, edge_color=edge_colors, width=edge_widths, alpha=0.7)
170
+
171
+ # Add labels
172
+ nx.draw_networkx_labels(G, pos, font_size=10, font_family="sans-serif")
173
+
174
+ # Save to BytesIO
175
+ buf = io.BytesIO()
176
+ plt.axis('off')
177
+ plt.tight_layout()
178
+ plt.savefig(buf, format='png', dpi=150)
179
+ buf.seek(0)
180
+ plt.close()
181
+
182
+ return send_file(buf, mimetype='image/png')
183
+
184
+ return app
185
+
186
+ if __name__ == "__main__":
187
+ app = create_app()
188
+ port = int(os.environ.get('PORT', 5000))
189
+ app.run(host='0.0.0.0', port=port)
src/web/run_web.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Run the Drug Interaction System web application."""
3
+
4
+ import os
5
+ import sys
6
+
7
+ # Add the parent directory to the path so we can import the package
8
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
9
+
10
+ from web.app import run_app
11
+
12
+ if __name__ == "__main__":
13
+ # Create necessary directories
14
+ os.makedirs("drug_interaction_system/web/static/visualizations", exist_ok=True)
15
+
16
+ # Run the web application
17
+ print("Starting Drug Interaction System web application...")
18
+ print("Open your browser and navigate to http://localhost:5000")
19
+ run_app()
src/web/static/css/style.css ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Additional styles for the Drug Interaction Assistant web interface */
2
+
3
+ /* Chat message styling */
4
+ .chat-message {
5
+ margin-bottom: 1rem;
6
+ }
7
+
8
+ .chat-message.user {
9
+ text-align: right;
10
+ }
11
+
12
+ .chat-message.bot {
13
+ text-align: left;
14
+ }
15
+
16
+ /* Visualization container */
17
+ #visualization-container {
18
+ min-height: 300px;
19
+ border: 1px solid #e2e8f0;
20
+ border-radius: 0.5rem;
21
+ overflow: hidden;
22
+ }
23
+
24
+ #visualization-container img {
25
+ max-width: 100%;
26
+ max-height: 300px;
27
+ object-fit: contain;
28
+ }
29
+
30
+ /* Drug information container */
31
+ #drug-info-container {
32
+ min-height: 200px;
33
+ border: 1px solid #e2e8f0;
34
+ border-radius: 0.5rem;
35
+ overflow: auto;
36
+ }
37
+
38
+ /* Loading animation */
39
+ .loading {
40
+ display: inline-block;
41
+ position: relative;
42
+ width: 80px;
43
+ height: 80px;
44
+ }
45
+
46
+ .loading div {
47
+ position: absolute;
48
+ top: 33px;
49
+ width: 13px;
50
+ height: 13px;
51
+ border-radius: 50%;
52
+ background: #3b82f6;
53
+ animation-timing-function: cubic-bezier(0, 1, 1, 0);
54
+ }
55
+
56
+ .loading div:nth-child(1) {
57
+ left: 8px;
58
+ animation: loading1 0.6s infinite;
59
+ }
60
+
61
+ .loading div:nth-child(2) {
62
+ left: 8px;
63
+ animation: loading2 0.6s infinite;
64
+ }
65
+
66
+ .loading div:nth-child(3) {
67
+ left: 32px;
68
+ animation: loading2 0.6s infinite;
69
+ }
70
+
71
+ .loading div:nth-child(4) {
72
+ left: 56px;
73
+ animation: loading3 0.6s infinite;
74
+ }
75
+
76
+ @keyframes loading1 {
77
+ 0% {
78
+ transform: scale(0);
79
+ }
80
+ 100% {
81
+ transform: scale(1);
82
+ }
83
+ }
84
+
85
+ @keyframes loading3 {
86
+ 0% {
87
+ transform: scale(1);
88
+ }
89
+ 100% {
90
+ transform: scale(0);
91
+ }
92
+ }
93
+
94
+ @keyframes loading2 {
95
+ 0% {
96
+ transform: translate(0, 0);
97
+ }
98
+ 100% {
99
+ transform: translate(24px, 0);
100
+ }
101
+ }
102
+
103
+ /* Responsive adjustments */
104
+ @media (max-width: 768px) {
105
+ .container {
106
+ padding: 1rem;
107
+ }
108
+
109
+ #chat-history {
110
+ height: 300px;
111
+ }
112
+
113
+ #visualization-container {
114
+ min-height: 200px;
115
+ }
116
+
117
+ #visualization-container img {
118
+ max-height: 200px;
119
+ }
120
+ }
src/web/static/js/main.js ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Additional JavaScript functionality for the Drug Interaction Assistant
2
+
3
+ // Function to format drug information
4
+ function formatDrugInfo(drugInfo) {
5
+ if (!drugInfo) return '<p class="text-gray-500">No information available</p>';
6
+
7
+ let html = `<h3 class="text-lg font-semibold mb-2">${drugInfo.drug_name}</h3>`;
8
+
9
+ if (drugInfo.drug_class && drugInfo.drug_class !== "Information not available") {
10
+ html += `<p class="mb-2"><span class="font-medium">Drug Class:</span> ${drugInfo.drug_class}</p>`;
11
+ }
12
+
13
+ if (drugInfo.mechanism && drugInfo.mechanism !== "Information not available") {
14
+ html += `<p class="mb-2"><span class="font-medium">Mechanism:</span> ${drugInfo.mechanism}</p>`;
15
+ }
16
+
17
+ if (drugInfo.indications && drugInfo.indications.length > 0 && drugInfo.indications[0] !== "Information not available") {
18
+ html += `<p class="font-medium mb-1">Indications:</p><ul class="list-disc pl-5 mb-2">`;
19
+ drugInfo.indications.forEach(indication => {
20
+ html += `<li>${indication}</li>`;
21
+ });
22
+ html += `</ul>`;
23
+ }
24
+
25
+ if (drugInfo.side_effects && drugInfo.side_effects.length > 0 && drugInfo.side_effects[0] !== "Information not available") {
26
+ html += `<p class="font-medium mb-1">Side Effects:</p><ul class="list-disc pl-5 mb-2">`;
27
+ drugInfo.side_effects.forEach(effect => {
28
+ html += `<li>${effect}</li>`;
29
+ });
30
+ html += `</ul>`;
31
+ }
32
+
33
+ if (drugInfo.common_interactions && drugInfo.common_interactions.length > 0 && drugInfo.common_interactions[0] !== "Information not available") {
34
+ html += `<p class="font-medium mb-1">Common Interactions:</p><ul class="list-disc pl-5 mb-2">`;
35
+ drugInfo.common_interactions.forEach(interaction => {
36
+ html += `<li>${interaction}</li>`;
37
+ });
38
+ html += `</ul>`;
39
+ }
40
+
41
+ if (drugInfo.contraindications && drugInfo.contraindications.length > 0 && drugInfo.contraindications[0] !== "Information not available") {
42
+ html += `<p class="font-medium mb-1">Contraindications:</p><ul class="list-disc pl-5 mb-2">`;
43
+ drugInfo.contraindications.forEach(contraindication => {
44
+ html += `<li>${contraindication}</li>`;
45
+ });
46
+ html += `</ul>`;
47
+ }
48
+
49
+ return html;
50
+ }
51
+
52
+ // Function to extract drug names from a message
53
+ function extractDrugNames(message) {
54
+ // Simple regex to find drug names in common question patterns
55
+ const patterns = [
56
+ /(?:between|with|and)\s+([A-Za-z-]+)\s+(?:and|with)\s+([A-Za-z-]+)/i,
57
+ /(?:about|information on|details about)\s+([A-Za-z-]+)/i,
58
+ /(?:visualization|graph|network)\s+(?:for|of)\s+([A-Za-z-]+)/i
59
+ ];
60
+
61
+ for (const pattern of patterns) {
62
+ const match = message.match(pattern);
63
+ if (match) {
64
+ // If we have two drug names, return both
65
+ if (match.length > 2) {
66
+ return [match[1], match[2]];
67
+ }
68
+ // Otherwise return just the one drug name
69
+ return [match[1]];
70
+ }
71
+ }
72
+
73
+ return [];
74
+ }
75
+
76
+ // Function to fetch drug information
77
+ function fetchDrugInfo(drugName) {
78
+ const drugInfoContainer = document.getElementById('drug-info-container');
79
+
80
+ // Show loading state
81
+ drugInfoContainer.innerHTML = '<div class="loading"><div></div><div></div><div></div><div></div></div>';
82
+
83
+ // Fetch drug information
84
+ fetch(`/api/drug-info/${drugName}`)
85
+ .then(response => response.json())
86
+ .then(data => {
87
+ if (data.error) {
88
+ drugInfoContainer.innerHTML = `<p class="text-red-500">${data.error}</p>`;
89
+ } else {
90
+ drugInfoContainer.innerHTML = formatDrugInfo(data);
91
+ }
92
+ })
93
+ .catch(err => {
94
+ drugInfoContainer.innerHTML = '<p class="text-red-500">Error fetching drug information</p>';
95
+ console.error(err);
96
+ });
97
+ }
98
+
99
+ // Function to fetch visualization
100
+ function fetchVisualization(drugName) {
101
+ const vizContainer = document.getElementById('visualization-container');
102
+
103
+ // Show loading state
104
+ vizContainer.innerHTML = '<div class="loading"><div></div><div></div><div></div><div></div></div>';
105
+
106
+ // Fetch visualization
107
+ fetch(`/api/visualize/${drugName}`)
108
+ .then(response => {
109
+ if (!response.ok) {
110
+ throw new Error('Visualization not available');
111
+ }
112
+ return response.blob();
113
+ })
114
+ .then(blob => {
115
+ const url = URL.createObjectURL(blob);
116
+ vizContainer.innerHTML = `<img src="${url}" alt="Drug interaction visualization" class="max-w-full max-h-full">`;
117
+ })
118
+ .catch(err => {
119
+ vizContainer.innerHTML = '<p class="text-red-500">Error generating visualization</p>';
120
+ console.error(err);
121
+ });
122
+ }
123
+
124
+ // Enhance the existing message processing
125
+ document.addEventListener('DOMContentLoaded', () => {
126
+ const userInput = document.getElementById('user-input');
127
+
128
+ // Add event listener for input changes to detect drug names
129
+ userInput.addEventListener('input', () => {
130
+ const drugNames = extractDrugNames(userInput.value);
131
+
132
+ if (drugNames.length === 1) {
133
+ // If we detect a single drug name, fetch its information
134
+ fetchDrugInfo(drugNames[0]);
135
+ }
136
+ });
137
+
138
+ // Enhance the existing send button click handler
139
+ const sendBtn = document.getElementById('send-btn');
140
+ const originalClickHandler = sendBtn.onclick;
141
+
142
+ sendBtn.onclick = (e) => {
143
+ // Call the original handler if it exists
144
+ if (originalClickHandler) {
145
+ originalClickHandler(e);
146
+ }
147
+
148
+ // Extract drug names from the message
149
+ const drugNames = extractDrugNames(userInput.value);
150
+
151
+ if (drugNames.length === 1) {
152
+ // If we detect a single drug name, fetch its visualization
153
+ fetchVisualization(drugNames[0]);
154
+ }
155
+ };
156
+ });
src/web/templates/index.html ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Drug Interaction Assistant</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ </head>
10
+ <body class="bg-gray-100 min-h-screen">
11
+ <div class="container mx-auto px-4 py-8">
12
+ <header class="bg-blue-600 text-white rounded-lg shadow-lg p-6 mb-8">
13
+ <h1 class="text-3xl font-bold">Drug Interaction Assistant</h1>
14
+ <p class="mt-2">Powered by BiomedLM - Ask about drug interactions, drug information, or analyze clinical notes</p>
15
+ </header>
16
+
17
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
18
+ <div class="lg:col-span-2">
19
+ <div class="bg-white rounded-lg shadow-lg p-6 mb-6">
20
+ <h2 class="text-xl font-semibold mb-4">Chat with the Drug Interaction Assistant</h2>
21
+ <div id="chat-history" class="bg-gray-50 p-4 rounded-lg mb-4 h-96 overflow-y-auto">
22
+ <div class="chat-message bot">
23
+ <p class="p-3 rounded-lg bg-blue-100 inline-block">Hello! I'm your Drug Interaction Assistant. You can ask me about drug interactions, get drug information, or have me analyze clinical notes.</p>
24
+ </div>
25
+ </div>
26
+ <div class="flex">
27
+ <input id="user-input" type="text" placeholder="Type your question here..."
28
+ class="flex-grow p-3 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
29
+ <button id="send-btn" class="bg-blue-600 text-white px-6 py-3 rounded-r-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
30
+ Send
31
+ </button>
32
+ </div>
33
+ <div class="text-sm text-gray-600 mt-2">
34
+ Example questions: "Can I take aspirin and warfarin together?", "Tell me about metformin", "Analyze this clinical note: Patient is taking..."
35
+ </div>
36
+ </div>
37
+ </div>
38
+
39
+ <div class="lg:col-span-1">
40
+ <div class="bg-white rounded-lg shadow-lg p-6 mb-6">
41
+ <h2 class="text-xl font-semibold mb-4">Drug Interaction Visualization</h2>
42
+ <div id="visualization-container" class="h-80 flex items-center justify-center bg-gray-50 rounded-lg">
43
+ <p class="text-gray-500">Interaction visualizations will appear here</p>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="bg-white rounded-lg shadow-lg p-6">
48
+ <h2 class="text-xl font-semibold mb-4">Drug Information</h2>
49
+ <div id="drug-info-container" class="bg-gray-50 p-4 rounded-lg">
50
+ <p class="text-gray-500">Select a drug to see detailed information</p>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+
57
+ <script>
58
+ document.addEventListener('DOMContentLoaded', () => {
59
+ const chatHistory = document.getElementById('chat-history');
60
+ const userInput = document.getElementById('user-input');
61
+ const sendBtn = document.getElementById('send-btn');
62
+ const vizContainer = document.getElementById('visualization-container');
63
+ const drugInfoContainer = document.getElementById('drug-info-container');
64
+
65
+ // Send message function
66
+ const sendMessage = () => {
67
+ const message = userInput.value.trim();
68
+ if (!message) return;
69
+
70
+ // Add user message to chat
71
+ const userMsg = document.createElement('div');
72
+ userMsg.className = 'chat-message user text-right mt-4';
73
+ userMsg.innerHTML = `<p class="p-3 rounded-lg bg-green-100 inline-block">${message}</p>`;
74
+ chatHistory.appendChild(userMsg);
75
+ chatHistory.scrollTop = chatHistory.scrollHeight;
76
+
77
+ // Clear input
78
+ userInput.value = '';
79
+
80
+ // Show loading indicator
81
+ const loadingMsg = document.createElement('div');
82
+ loadingMsg.className = 'chat-message bot mt-4';
83
+ loadingMsg.innerHTML = `<p class="p-3 rounded-lg bg-blue-100 inline-block">Thinking...</p>`;
84
+ chatHistory.appendChild(loadingMsg);
85
+ chatHistory.scrollTop = chatHistory.scrollHeight;
86
+
87
+ // Call API
88
+ fetch('/api/ask', {
89
+ method: 'POST',
90
+ headers: {
91
+ 'Content-Type': 'application/json'
92
+ },
93
+ body: JSON.stringify({ message })
94
+ })
95
+ .then(response => response.json())
96
+ .then(data => {
97
+ // Remove loading message
98
+ chatHistory.removeChild(loadingMsg);
99
+
100
+ // Add bot response
101
+ const botMsg = document.createElement('div');
102
+ botMsg.className = 'chat-message bot mt-4';
103
+ botMsg.innerHTML = `<p class="p-3 rounded-lg bg-blue-100 inline-block">${data.response.replace(/\n/g, '<br>')}</p>`;
104
+ chatHistory.appendChild(botMsg);
105
+ chatHistory.scrollTop = chatHistory.scrollHeight;
106
+
107
+ // Update visualization if available
108
+ if (data.visualization) {
109
+ vizContainer.innerHTML = `<img src="${data.visualization}" alt="Drug interaction visualization" class="max-w-full max-h-full">`;
110
+ }
111
+ })
112
+ .catch(err => {
113
+ // Remove loading message
114
+ chatHistory.removeChild(loadingMsg);
115
+
116
+ // Add error message
117
+ const errorMsg = document.createElement('div');
118
+ errorMsg.className = 'chat-message bot mt-4';
119
+ errorMsg.innerHTML = `<p class="p-3 rounded-lg bg-red-100 inline-block">Sorry, something went wrong. Please try again.</p>`;
120
+ chatHistory.appendChild(errorMsg);
121
+ chatHistory.scrollTop = chatHistory.scrollHeight;
122
+ console.error(err);
123
+ });
124
+ };
125
+
126
+ // Event listeners
127
+ sendBtn.addEventListener('click', sendMessage);
128
+ userInput.addEventListener('keypress', (e) => {
129
+ if (e.key === 'Enter') sendMessage();
130
+ });
131
+ });
132
+ </script>
133
+ </body>
134
+ </html>