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

1""" 

2Drug Interaction Safety Engine - CORRECTED VERSION 

3 

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 

10 

11Design: 

12- Deterministic 

13- Auditable 

14- Knowledge-Graph + Rule based 

15""" 

16 

17from typing import List, Dict, Any, Optional 

18from neo4j import GraphDatabase 

19from neo4j.exceptions import ServiceUnavailable, Neo4jError 

20import os 

21 

22 

23# ------------------------------------------------------------------ 

24# Neo4j connection 

25# ------------------------------------------------------------------ 

26 

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") 

35 

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}") 

42 

43 

44# ------------------------------------------------------------------ 

45# Public API (FACT EXTRACTOR) 

46# ------------------------------------------------------------------ 

47 

48def check_drug_interactions(medications: List[Dict[str, Any]]) -> Dict[str, Any]: 

49 """ 

50 Entry point used by Hybrid Graph-RAG pipeline. 

51 

52 Returns FACTS only: 

53 - drug-drug interactions 

54 - drug-condition interactions 

55 - drug-effect mechanisms 

56  

57 Args: 

58 medications: List of dicts with 'name' key, e.g. [{'name': 'metformin'}, ...] 

59  

60 Returns: 

61 Dict with interaction facts and metadata 

62 """ 

63 

64 # Validate input 

65 if not isinstance(medications, list): 

66 return _safe_response("Invalid input: medications must be a list") 

67 

68 drug_names = sorted( 

69 {str(m.get("name", "")).lower().strip() 

70 for m in medications if m.get("name")} 

71 ) 

72 

73 if not drug_names: 

74 return _safe_response("No medications provided") 

75 

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 } 

82 

83 

84# ------------------------------------------------------------------ 

85# FACT ENGINES 

86# ------------------------------------------------------------------ 

87 

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 = [] 

93 

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 }) 

103 

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 }) 

112 

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 }) 

121 

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 }) 

130 

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 }) 

140 

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 }) 

149 

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 }) 

158 

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 }) 

168 

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 }) 

177 

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 }) 

186 

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 }) 

196 

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 }) 

205 

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 }) 

214 

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 }) 

223 

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 }) 

233 

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 }) 

242 

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 }) 

251 

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 }) 

260 

261 return facts 

262 

263 

264def _check_drug_condition_facts(drugs: List[str]) -> List[Dict[str, Any]]: 

265 """ 

266 Drug–condition contraindication FACTS via Neo4j. 

267  

268 Raises: 

269 ConnectionError if Neo4j is unavailable 

270 """ 

271 facts = [] 

272 driver = None 

273 

274 try: 

275 driver = _get_driver() 

276 

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 """ 

283 

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 }) 

294 

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 }) 

303 

304 finally: 

305 if driver: 

306 driver.close() 

307 

308 return facts 

309 

310 

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 ] 

407 

408 facts = [] 

409 drug_set = set(drugs) 

410 

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 }) 

421 

422 return facts 

423 

424 

425# ------------------------------------------------------------------ 

426# Helpers 

427# ------------------------------------------------------------------ 

428 

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 }