Coverage for app \ knowledge_graph \ drug_interactions.py: 59%
86 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-24 13:18 +0530
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-24 13:18 +0530
1"""
2Drug Interaction Safety Engine - CORRECTED VERSION
4Purpose:
5- Extract VERIFIED drug interaction FACTS
6- Extract VERIFIED drug effect / mechanism FACTS
7- NO symptom inference
8- NO patient-specific reasoning
9- Used BEFORE calling the LLM
11Design:
12- Deterministic
13- Auditable
14- Knowledge-Graph + Rule based
15"""
17from typing import List, Dict, Any, Optional
18from neo4j import GraphDatabase
19from neo4j.exceptions import ServiceUnavailable, Neo4jError
20import os
23# ------------------------------------------------------------------
24# Neo4j connection
25# ------------------------------------------------------------------
27def _get_driver():
28 """
29 Creates and returns a Neo4j driver.
30 Should be used with context manager or closed explicitly.
31 """
32 uri = os.getenv("NEO4J_URI", "bolt://localhost:7687")
33 user = os.getenv("NEO4J_USER", "neo4j")
34 password = os.getenv("NEO4J_PASSWORD", "password")
36 try:
37 driver = GraphDatabase.driver(uri, auth=(user, password))
38 driver.verify_connectivity() # Verify connection works
39 return driver
40 except (ServiceUnavailable, Neo4jError) as e:
41 raise ConnectionError(f"Failed to connect to Neo4j at {uri}: {e}")
44# ------------------------------------------------------------------
45# Public API (FACT EXTRACTOR)
46# ------------------------------------------------------------------
48def check_drug_interactions(medications: List[Dict[str, Any]]) -> Dict[str, Any]:
49 """
50 Entry point used by Hybrid Graph-RAG pipeline.
52 Returns FACTS only:
53 - drug-drug interactions
54 - drug-condition interactions
55 - drug-effect mechanisms
57 Args:
58 medications: List of dicts with 'name' key, e.g. [{'name': 'metformin'}, ...]
60 Returns:
61 Dict with interaction facts and metadata
62 """
64 # Validate input
65 if not isinstance(medications, list):
66 return _safe_response("Invalid input: medications must be a list")
68 drug_names = sorted(
69 {str(m.get("name", "")).lower().strip()
70 for m in medications if m.get("name")}
71 )
73 if not drug_names:
74 return _safe_response("No medications provided")
76 return {
77 "checked_drugs": drug_names,
78 "drug_drug_interactions": _check_drug_drug_facts(drug_names),
79 "drug_condition_interactions": _check_drug_condition_facts(drug_names),
80 "drug_effect_facts": _check_drug_effect_facts(drug_names),
81 }
84# ------------------------------------------------------------------
85# FACT ENGINES
86# ------------------------------------------------------------------
88def _check_drug_effect_facts(drugs: List[str]) -> List[Dict[str, Any]]:
89 """
90 Extract drug effect facts from hardcoded knowledge base.
91 """
92 facts = []
94 # ── Patient 1: John Doe — Diabetes + Hypertension ──────────────
95 if "metformin" in drugs:
96 facts.append({
97 "type": "drug-effect", "drug": "metformin",
98 "effect": "reduced vitamin B12 absorption",
99 "mechanism": "Metformin interferes with calcium-dependent B12 absorption in the terminal ileum.",
100 "clinical_relevance": "Long-term use associated with B12 deficiency and peripheral neuropathy.",
101 "evidence": "well-established"
102 })
104 if "lisinopril" in drugs:
105 facts.append({
106 "type": "drug-effect", "drug": "lisinopril",
107 "effect": "orthostatic hypotension and dizziness",
108 "mechanism": "ACE inhibition reduces angiotensin II-mediated vasoconstriction, lowering systemic vascular resistance.",
109 "clinical_relevance": "Most pronounced at initiation, dose increases, or in volume-depleted patients.",
110 "evidence": "well-established"
111 })
113 if "amlodipine" in drugs:
114 facts.append({
115 "type": "drug-effect", "drug": "amlodipine",
116 "effect": "peripheral vasodilation causing dizziness and flushing",
117 "mechanism": "Calcium channel blockade causes smooth muscle relaxation and peripheral vasodilation.",
118 "clinical_relevance": "Dizziness risk is additive when combined with other antihypertensives.",
119 "evidence": "well-established"
120 })
122 if "lisinopril" in drugs and "amlodipine" in drugs:
123 facts.append({
124 "type": "drug-effect", "drug": "lisinopril + amlodipine",
125 "effect": "additive blood pressure lowering",
126 "mechanism": "Combined ACE inhibition and calcium channel blockade produces greater BP reduction than either alone.",
127 "clinical_relevance": "Monitor for symptomatic hypotension, especially on standing.",
128 "evidence": "clinical guidelines"
129 })
131 # ── Patient 2: Sarah Smith — Heart Disease ──────────────────────
132 if "atorvastatin" in drugs:
133 facts.append({
134 "type": "drug-effect", "drug": "atorvastatin",
135 "effect": "myopathy and elevated liver enzymes",
136 "mechanism": "HMG-CoA reductase inhibition can impair muscle cell energy metabolism at high doses.",
137 "clinical_relevance": "Risk increases with higher doses (40-80mg). Monitor for muscle pain and LFTs.",
138 "evidence": "well-established"
139 })
141 if "aspirin" in drugs:
142 facts.append({
143 "type": "drug-effect", "drug": "aspirin",
144 "effect": "GI irritation and bleeding risk",
145 "mechanism": "Irreversible COX-1 inhibition reduces prostaglandin-mediated gastric mucosal protection.",
146 "clinical_relevance": "Even low-dose (81mg) aspirin increases GI bleed risk, especially with age.",
147 "evidence": "well-established"
148 })
150 if "metoprolol" in drugs:
151 facts.append({
152 "type": "drug-effect", "drug": "metoprolol",
153 "effect": "bradycardia, fatigue, and masking of hypoglycemia symptoms",
154 "mechanism": "Beta-1 selective blockade reduces heart rate and blunts sympathetic response to low blood sugar.",
155 "clinical_relevance": "Particularly relevant in diabetic patients — hypoglycemia sweating is preserved but tachycardia is masked.",
156 "evidence": "well-established"
157 })
159 # ── Patient 3: Michael Johnson — Asthma ────────────────────────
160 if "albuterol inhaler" in drugs:
161 facts.append({
162 "type": "drug-effect", "drug": "albuterol inhaler",
163 "effect": "tachycardia and tremor with overuse",
164 "mechanism": "Beta-2 agonism causes bronchodilation but also stimulates cardiac beta-1 receptors at high doses.",
165 "clinical_relevance": "Frequent rescue inhaler use (>2x/week) indicates uncontrolled asthma requiring review.",
166 "evidence": "clinical guidelines"
167 })
169 if "fluticasone inhaler" in drugs:
170 facts.append({
171 "type": "drug-effect", "drug": "fluticasone inhaler",
172 "effect": "oral candidiasis and HPA axis suppression with high doses",
173 "mechanism": "Inhaled corticosteroid deposits in oropharynx; systemic absorption increases at high doses.",
174 "clinical_relevance": "Patients should rinse mouth after each use to prevent thrush.",
175 "evidence": "well-established"
176 })
178 if "montelukast" in drugs:
179 facts.append({
180 "type": "drug-effect", "drug": "montelukast",
181 "effect": "neuropsychiatric effects including mood changes and sleep disturbance",
182 "mechanism": "Leukotriene receptor antagonism in the CNS may affect neurological function.",
183 "clinical_relevance": "FDA black box warning for neuropsychiatric events. Monitor for anxiety, depression, and sleep issues.",
184 "evidence": "FDA black box warning"
185 })
187 # ── Patient 4: Emily Davis — Chronic Kidney Disease ────────────
188 if "losartan" in drugs:
189 facts.append({
190 "type": "drug-effect", "drug": "losartan",
191 "effect": "hyperkalemia and acute kidney injury risk",
192 "mechanism": "ARB blockade of angiotensin II reduces aldosterone, impairing renal potassium excretion.",
193 "clinical_relevance": "CKD patients already at risk for hyperkalemia — monitor potassium closely.",
194 "evidence": "well-established"
195 })
197 if "furosemide" in drugs:
198 facts.append({
199 "type": "drug-effect", "drug": "furosemide",
200 "effect": "electrolyte depletion (hypokalemia, hyponatremia) and dehydration",
201 "mechanism": "Loop diuretic inhibits Na-K-2Cl cotransporter in the thick ascending limb of Henle.",
202 "clinical_relevance": "Monitor electrolytes regularly. Dehydration worsens renal function in CKD.",
203 "evidence": "well-established"
204 })
206 if "furosemide" in drugs and "losartan" in drugs:
207 facts.append({
208 "type": "drug-effect", "drug": "furosemide + losartan",
209 "effect": "opposing potassium effects requiring close monitoring",
210 "mechanism": "Furosemide lowers potassium; losartan raises it. Net effect varies by dose and renal function.",
211 "clinical_relevance": "In CKD, losartan's hyperkalemic effect often dominates — monitor K+ levels frequently.",
212 "evidence": "clinical guidelines"
213 })
215 if "erythropoietin" in drugs:
216 facts.append({
217 "type": "drug-effect", "drug": "erythropoietin",
218 "effect": "hypertension and thrombotic events",
219 "mechanism": "Increased red cell mass raises blood viscosity and can activate platelet aggregation.",
220 "clinical_relevance": "Target hemoglobin should not exceed 11-12 g/dL in CKD to minimize cardiovascular risk.",
221 "evidence": "well-established"
222 })
224 # ── Patient 5: Robert Brown — Diabetes + Hypertension + Heart Disease ──
225 if "insulin glargine" in drugs:
226 facts.append({
227 "type": "drug-effect", "drug": "insulin glargine",
228 "effect": "hypoglycemia and weight gain",
229 "mechanism": "Basal insulin lowers fasting glucose but risks overcorrection, especially with missed meals.",
230 "clinical_relevance": "Elderly patients have heightened hypoglycemia risk and may not feel classic warning symptoms.",
231 "evidence": "well-established"
232 })
234 if "insulin glargine" in drugs and "metformin" in drugs:
235 facts.append({
236 "type": "drug-effect", "drug": "insulin glargine + metformin",
237 "effect": "additive glucose lowering with increased hypoglycemia risk",
238 "mechanism": "Insulin directly lowers glucose; metformin reduces hepatic glucose output — combined effect is synergistic.",
239 "clinical_relevance": "Monitor fasting glucose closely. Hypoglycemia risk is higher in elderly patients.",
240 "evidence": "clinical guidelines"
241 })
243 if "carvedilol" in drugs:
244 facts.append({
245 "type": "drug-effect", "drug": "carvedilol",
246 "effect": "bradycardia, hypotension, and masking of hypoglycemia",
247 "mechanism": "Non-selective beta blockade reduces HR and BP; blunts tachycardia response to hypoglycemia.",
248 "clinical_relevance": "High caution in diabetic patients on insulin — sweating is preserved but palpitations masked.",
249 "evidence": "well-established"
250 })
252 if "carvedilol" in drugs and "amlodipine" in drugs:
253 facts.append({
254 "type": "drug-effect", "drug": "carvedilol + amlodipine",
255 "effect": "additive hypotension and bradycardia",
256 "mechanism": "Beta blockade combined with calcium channel blockade produces compounded negative chronotropic and vasodilatory effects.",
257 "clinical_relevance": "Monitor BP and HR closely. Risk of symptomatic hypotension especially on standing.",
258 "evidence": "clinical guidelines"
259 })
261 return facts
264def _check_drug_condition_facts(drugs: List[str]) -> List[Dict[str, Any]]:
265 """
266 Drug–condition contraindication FACTS via Neo4j.
268 Raises:
269 ConnectionError if Neo4j is unavailable
270 """
271 facts = []
272 driver = None
274 try:
275 driver = _get_driver()
277 cypher = """
278 MATCH (d:Medication)
279 WHERE toLower(d.name) IN $drug_names
280 MATCH (d)-[:CONTRAINDICATED_IN]->(c:Disease)
281 RETURN d.name AS drug, c.name AS condition, c.severity AS severity
282 """
284 with driver.session() as session:
285 results = session.run(cypher, drug_names=drugs)
286 for r in results:
287 facts.append({
288 "type": "drug-condition-interaction",
289 "drug": r["drug"],
290 "condition": r["condition"],
291 "severity": r["severity"] or "moderate",
292 "evidence": "knowledge graph"
293 })
295 except (ConnectionError, Exception) as e:
296 # Log but don't crash — gracefully degrade
297 print(f"Warning: Could not query Neo4j for drug-condition facts: {e}")
298 facts.append({
299 "type": "error",
300 "message": "Drug-condition interaction check unavailable",
301 "reason": str(e)
302 })
304 finally:
305 if driver:
306 driver.close()
308 return facts
311def _check_drug_drug_facts(drugs: List[str]) -> List[Dict[str, Any]]:
312 """
313 Extract drug-drug interaction facts from hardcoded rules.
314 This function is deterministic and requires no external dependencies.
315 """
316 RULES = [
317 # Existing rules
318 {
319 "drugs": {"metformin", "contrast dye"},
320 "severity": "high",
321 "interaction": "Increased risk of lactic acidosis",
322 "mechanism": "Contrast agents may impair renal function, leading to metformin accumulation.",
323 "evidence": "clinical literature"
324 },
325 {
326 "drugs": {"metformin", "insulin"},
327 "severity": "moderate",
328 "interaction": "Increased risk of hypoglycemia",
329 "mechanism": "Both drugs lower blood glucose levels.",
330 "evidence": "clinical guidelines"
331 },
332 {
333 "drugs": {"metformin", "alcohol"},
334 "severity": "high",
335 "interaction": "Increased risk of lactic acidosis",
336 "mechanism": "Alcohol affects hepatic lactate metabolism.",
337 "evidence": "drug safety literature"
338 },
339 # Patient 2: Heart Disease
340 {
341 "drugs": {"aspirin", "atorvastatin"},
342 "severity": "low",
343 "interaction": "Minor increase in bleeding risk",
344 "mechanism": "Aspirin inhibits platelet aggregation; statins have mild antiplatelet properties.",
345 "evidence": "clinical literature"
346 },
347 {
348 "drugs": {"metoprolol", "aspirin"},
349 "severity": "low",
350 "interaction": "NSAIDs may reduce antihypertensive efficacy",
351 "mechanism": "Prostaglandin inhibition by aspirin can counteract beta-blocker BP effects.",
352 "evidence": "clinical guidelines"
353 },
354 # Patient 3: Asthma
355 {
356 "drugs": {"albuterol inhaler", "montelukast"},
357 "severity": "low",
358 "interaction": "Complementary mechanisms — no adverse interaction",
359 "mechanism": "Beta-2 agonist and leukotriene antagonist act on different pathways.",
360 "evidence": "clinical guidelines"
361 },
362 # Patient 4: CKD
363 {
364 "drugs": {"losartan", "furosemide"},
365 "severity": "moderate",
366 "interaction": "Risk of acute kidney injury and electrolyte imbalance",
367 "mechanism": "Volume depletion from furosemide activates RAAS; ARB blockade then impairs compensatory response.",
368 "evidence": "clinical guidelines"
369 },
370 {
371 "drugs": {"furosemide", "calcium carbonate"},
372 "severity": "low",
373 "interaction": "Reduced furosemide absorption",
374 "mechanism": "Calcium may bind to furosemide in the GI tract, reducing bioavailability.",
375 "evidence": "pharmacokinetic data"
376 },
377 # Patient 5: Multi-condition
378 {
379 "drugs": {"insulin glargine", "carvedilol"},
380 "severity": "moderate",
381 "interaction": "Masking of hypoglycemia symptoms",
382 "mechanism": "Non-selective beta blockade blunts tachycardia response to low blood sugar.",
383 "evidence": "well-established"
384 },
385 {
386 "drugs": {"metformin", "carvedilol"},
387 "severity": "low",
388 "interaction": "Carvedilol may impair glycemic control",
389 "mechanism": "Beta blockade can inhibit glycogenolysis and mask hypoglycemia signs.",
390 "evidence": "clinical literature"
391 },
392 {
393 "drugs": {"aspirin", "carvedilol"},
394 "severity": "low",
395 "interaction": "NSAIDs may attenuate beta-blocker antihypertensive effect",
396 "mechanism": "Prostaglandin inhibition reduces vasodilatory compensation.",
397 "evidence": "clinical guidelines"
398 },
399 {
400 "drugs": {"atorvastatin", "aspirin"},
401 "severity": "low",
402 "interaction": "Minor additive bleeding risk",
403 "mechanism": "Both have mild antiplatelet properties.",
404 "evidence": "clinical literature"
405 },
406 ]
408 facts = []
409 drug_set = set(drugs)
411 for rule in RULES:
412 if rule["drugs"].issubset(drug_set):
413 facts.append({
414 "type": "drug-drug-interaction",
415 "drugs_involved": sorted(rule["drugs"]),
416 "severity": rule["severity"],
417 "interaction": rule["interaction"],
418 "mechanism": rule["mechanism"],
419 "evidence": rule["evidence"],
420 })
422 return facts
425# ------------------------------------------------------------------
426# Helpers
427# ------------------------------------------------------------------
429def _safe_response(reason: str) -> Dict[str, Any]:
430 """
431 Return a standardized empty response with a note.
432 """
433 return {
434 "checked_drugs": [],
435 "drug_drug_interactions": [],
436 "drug_condition_interactions": [],
437 "drug_effect_facts": [],
438 "note": reason,
439 }