Drug interaction network for patient safety
Objective
Pharmacists and clinical decision-support systems must flag patients on combinations of drugs known to interact dangerously. Modeling Patient → Drug and Drug → Drug interactions as a graph turns the screening query into one MATCH that looks for triangles in the network — sub-second even across a hospital's entire prescription history.
Step 1: Create the graph
// Patients
MERGE (p1:Patient {mrn: "MRN-101", name: "Sarah Chen", age: 67})
MERGE (p2:Patient {mrn: "MRN-102", name: "Raj Patel", age: 54})
MERGE (p3:Patient {mrn: "MRN-103", name: "Mia Rossi", age: 71})
MERGE (p4:Patient {mrn: "MRN-104", name: "Leo Park", age: 49})
MERGE (p5:Patient {mrn: "MRN-105", name: "Eli Tanaka", age: 62})
// Drugs
MERGE (warf:Drug {name: "Warfarin", class: "Anticoagulant"})
MERGE (asp:Drug {name: "Aspirin", class: "NSAID"})
MERGE (clop:Drug {name: "Clopidogrel", class: "Antiplatelet"})
MERGE (sim:Drug {name: "Simvastatin", class: "Statin"})
MERGE (ery:Drug {name: "Erythromycin", class: "Antibiotic"})
MERGE (met:Drug {name: "Metformin", class: "Antidiabetic"})
MERGE (lis:Drug {name: "Lisinopril", class: "ACE Inhibitor"})
// Prescriptions
MERGE (p1)-[:PRESCRIBED {since: "2026-01-12"}]->(warf)
MERGE (p1)-[:PRESCRIBED {since: "2026-03-04"}]->(asp)
MERGE (p2)-[:PRESCRIBED {since: "2025-11-22"}]->(sim)
MERGE (p2)-[:PRESCRIBED {since: "2026-04-15"}]->(ery)
MERGE (p3)-[:PRESCRIBED {since: "2026-02-08"}]->(warf)
MERGE (p3)-[:PRESCRIBED {since: "2026-02-08"}]->(clop)
MERGE (p4)-[:PRESCRIBED {since: "2025-06-10"}]->(met)
MERGE (p4)-[:PRESCRIBED {since: "2025-06-10"}]->(lis)
MERGE (p5)-[:PRESCRIBED {since: "2026-03-30"}]->(asp)
MERGE (p5)-[:PRESCRIBED {since: "2026-03-30"}]->(clop)
// Known interactions (severity 1=mild, 2=moderate, 3=severe)
MERGE (warf)-[:INTERACTS_WITH {severity: 3, effect: "Major bleeding risk"}]->(asp)
MERGE (warf)-[:INTERACTS_WITH {severity: 3, effect: "Major bleeding risk"}]->(clop)
MERGE (asp)-[:INTERACTS_WITH {severity: 2, effect: "Increased bleeding"}]->(clop)
MERGE (sim)-[:INTERACTS_WITH {severity: 3, effect: "Rhabdomyolysis risk"}]->(ery);
Step 2: Find at-risk patients across the entire panel
// Triangle: Patient -> drugA, Patient -> drugB, drugA <-> drugB interacts.
MATCH (p:Patient)-[:PRESCRIBED]->(a:Drug)-[ix:INTERACTS_WITH]-(b:Drug)<-[:PRESCRIBED]-(p)
WHERE id(a) < id(b) // de-duplicate the unordered drug pair
RETURN p.mrn AS mrn,
p.name AS patient,
a.name AS drug_a,
b.name AS drug_b,
ix.severity AS severity,
ix.effect AS effect
ORDER BY severity DESC, patient;
What's happening
- The pattern matches a triangle: same patient, two drugs they take, one interaction edge between the drugs. Cypher unifies the patient on both sides of the pattern automatically.
id(a) < id(b)is a standard idiom to avoid returning each interaction twice.- The interaction edge is undirected (
-[ix:INTERACTS_WITH]-) because pharmacological interactions are symmetric — a single relationship covers both directions. - In SQL this is a 4-way join (patient_drug × drug_interactions × patient_drug × drugs) plus a filter to ignore the same drug — the graph version is shorter and runs in linear time per prescribed drug, not quadratic in the prescription table.
- Severity 3 alerts feed directly into prescribing UIs as "do not dispense without override".
Try this next
MATCH (p:Patient)-[:PRESCRIBED]->(d:Drug)
RETURN p.name AS patient, collect(d.name) AS regimen, count(d) AS poly_count
ORDER BY poly_count DESC;
MATCH (d1:Drug)-[ix:INTERACTS_WITH]-(d2:Drug)
WHERE id(d1) < id(d2)
RETURN d1.name, d2.name, ix.severity, ix.effect
ORDER BY ix.severity DESC;
MATCH (p:Patient)-[:PRESCRIBED]->(:Drug)-[:INTERACTS_WITH]-(:Drug)<-[:PRESCRIBED]-(p)
RETURN p.mrn, p.name, count(*)/2 AS conflicting_pairs;