From d87f7b115deae465617b9748d05fd94619c26158 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 19:12:30 +0000 Subject: [PATCH 01/21] feat(medications): patient details dock pill and drug-interaction alerting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a docked "Patient details" pill to the prescribing results view and cross-checks each medication against the patient's current medication list, surfacing red/amber/green/grey verdicts on result rows and matched interactions on the medication detail page. The patient-considerations engine already existed (physiology only, session -scoped). This extends the same profile with a medication list and adds a second engine beside it: - `data/medication-interaction-index.json` (generated) resolves the catalogue's prose `Key Interactions` rows to catalogue targets using the curated `medication-interaction-lexicon.ts`. 400 of 523 rows resolve; the rest are recorded as unresolved rather than dropped. - `medication-interactions.ts` matches those rows against the entered list. - `composeMedicationVerdict` folds both engines into one tone, and makes green unreachable whenever either engine left something unassessed or unresolved — those degrade to a neutral "needs manual review" state. The pill portals into a new phone-dock addon slot, inheriting the composer's fixed positioning, safe area and scroll-hide transform rather than adding a second scroll owner. `data-footer-addon` is now derived from an addon kind instead of hardcoding the differentials claimant. Regulatory status is unchanged and still open: this materially widens the surface tracked in docs/samd-classification-medication-considerations.md, which is updated to say so. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X12okkARLhFQ2zgcPoFp54 --- data/medication-interaction-index.json | 8803 +++++++++++++++++ docs/codebase-index.md | 1 + docs/design-system/COMPONENTS.md | 2 +- docs/design-system/adoption-manifest.json | 2 + ...lassification-medication-considerations.md | 39 + docs/scripts-index.md | 2 +- docs/search-chrome-behaviour.md | 40 + package.json | 2 + scripts/build-medication-interaction-index.ts | 312 + src/app/globals.css | 122 + src/components/ClinicalDashboard.tsx | 18 +- .../global-search-shell.tsx | 7 + .../master-search-header.tsx | 10 +- .../medication-considerations.tsx | 132 +- .../medication-prescribing-workspace.tsx | 59 +- .../mobile-composer-reserve.ts | 14 + .../patient-details-dock-action.tsx | 130 + .../patient-profile-context.tsx | 22 +- .../patient-profile-panel.tsx | 102 +- src/lib/medication-interaction-lexicon.ts | 405 + src/lib/medication-interactions.ts | 269 + src/lib/medication-patient-alerts.ts | 7 + src/lib/mode-home-composer.ts | 19 + src/lib/patient-profile-storage.ts | 31 + tests/helpers/style-contracts.ts | 6 + ...ation-interaction-lexicon-coverage.test.ts | 189 + ...dication-interaction-surfaces.dom.test.tsx | 136 + tests/medication-interactions.test.ts | 133 + .../patient-details-dock-action.dom.test.tsx | 126 + .../patient-profile-medications.dom.test.tsx | 123 + tests/phone-dock-addon-contract.test.ts | 154 + 31 files changed, 11397 insertions(+), 20 deletions(-) create mode 100644 data/medication-interaction-index.json create mode 100644 scripts/build-medication-interaction-index.ts create mode 100644 src/components/clinical-dashboard/patient-details-dock-action.tsx create mode 100644 src/lib/medication-interaction-lexicon.ts create mode 100644 src/lib/medication-interactions.ts create mode 100644 tests/medication-interaction-lexicon-coverage.test.ts create mode 100644 tests/medication-interaction-surfaces.dom.test.tsx create mode 100644 tests/medication-interactions.test.ts create mode 100644 tests/patient-details-dock-action.dom.test.tsx create mode 100644 tests/patient-profile-medications.dom.test.tsx create mode 100644 tests/phone-dock-addon-contract.test.ts diff --git a/data/medication-interaction-index.json b/data/medication-interaction-index.json new file mode 100644 index 0000000000..b4cc5d6e8d --- /dev/null +++ b/data/medication-interaction-index.json @@ -0,0 +1,8803 @@ +{ + "version": 1, + "generatedFrom": "data/medications-snapshot.json", + "sourceRowCount": 523, + "stats": { + "resolvedRows": 400, + "unresolvedRows": 123, + "rowsWithCatalogueTarget": 381, + "medicationsWithUnresolvedRows": 116 + }, + "names": { + "acamprosate": "Acamprosate", + "acarbose": "Acarbose", + "acetylcysteine": "Acetylcysteine", + "aciclovir": "Aciclovir", + "adrenaline-epinephrine": "Adrenaline / Epinephrine", + "agomelatine": "Agomelatine", + "alendronate": "Alendronate", + "alimemazine": "Alimemazine", + "allopurinol": "Allopurinol", + "alprazolam": "Alprazolam", + "amikacin": "Amikacin", + "amiloride": "Amiloride", + "amiodarone": "Amiodarone", + "amisulpride": "Amisulpride", + "amitriptyline": "Amitriptyline", + "amlodipine": "Amlodipine", + "amoxicillin": "Amoxicillin", + "amoxicillin-clavulanate": "Amoxicillin/clavulanate", + "apixaban": "Apixaban", + "aripiprazole": "Aripiprazole", + "aripiprazole-lai": "Aripiprazole LAI", + "armodafinil": "Armodafinil", + "ascorbic-acid": "Ascorbic Acid", + "asenapine": "Asenapine", + "aspirin": "Aspirin", + "atenolol": "Atenolol", + "atomoxetine": "Atomoxetine", + "atorvastatin": "Atorvastatin", + "azithromycin": "Azithromycin", + "aztreonam": "Aztreonam", + "baclofen": "Baclofen", + "beclometasone": "Beclometasone", + "benzathine-benzylpenicillin": "Benzathine benzylpenicillin", + "benzatropine": "Benzatropine", + "betamethasone": "Betamethasone", + "bisacodyl": "Bisacodyl", + "bisoprolol": "Bisoprolol", + "brexpiprazole": "Brexpiprazole", + "budesonide": "Budesonide", + "bupivacaine": "Bupivacaine", + "buprenorphine-naloxone": "Buprenorphine + naloxone", + "buprenorphine-patch": "Buprenorphine patch", + "buprenorphine-sl-depot": "Buprenorphine (SL/depot)", + "bupropion-sr": "Bupropion SR", + "calcium-carbonate": "Calcium carbonate", + "candesartan": "Candesartan", + "capsaicin-cream": "Capsaicin cream", + "carbamazepine": "Carbamazepine", + "carbimazole": "Carbimazole", + "cariprazine": "Cariprazine", + "carvedilol": "Carvedilol", + "cefazolin": "Cefazolin", + "cefepime": "Cefepime", + "ceftriaxone": "Ceftriaxone", + "cefuroxime": "Cefuroxime", + "celecoxib": "Celecoxib", + "cephalexin": "Cephalexin", + "cetirizine": "Cetirizine", + "chlorpromazine": "Chlorpromazine", + "ciclesonide": "Ciclesonide", + "ciprofloxacin": "Ciprofloxacin", + "citalopram": "Citalopram", + "clarithromycin": "Clarithromycin", + "clindamycin": "Clindamycin", + "clobetasol": "Clobetasol", + "clomipramine": "Clomipramine", + "clonazepam": "Clonazepam", + "clonidine": "Clonidine", + "clopidogrel": "Clopidogrel", + "clotrimazole": "Clotrimazole", + "clozapine": "Clozapine", + "co-trimoxazole": "Co-trimoxazole", + "codeine": "Codeine", + "colchicine": "Colchicine", + "colistin": "Colistin", + "copper": "Copper", + "cyclizine": "Cyclizine", + "cyproheptadine": "Cyproheptadine", + "dabigatran": "Dabigatran", + "dalteparin": "Dalteparin", + "dantrolene": "Dantrolene", + "dapagliflozin": "Dapagliflozin", + "denosumab": "Denosumab", + "desvenlafaxine": "Desvenlafaxine", + "dexamethasone": "Dexamethasone", + "dexamfetamine": "Dexamfetamine", + "dexmedetomidine": "Dexmedetomidine", + "diazepam": "Diazepam", + "diclofenac": "Diclofenac", + "dicloxacillin": "Dicloxacillin", + "digoxin": "Digoxin", + "diltiazem": "Diltiazem", + "diphenhydramine": "Diphenhydramine", + "dipyridamole": "Dipyridamole", + "disulfiram": "Disulfiram", + "docusate-sodium": "Docusate sodium", + "domperidone": "Domperidone", + "donepezil": "Donepezil", + "dosulepin": "Dosulepin", + "doxepin": "Doxepin", + "doxycycline": "Doxycycline", + "droperidol": "Droperidol", + "dulaglutide": "Dulaglutide", + "duloxetine": "Duloxetine", + "dutasteride": "Dutasteride", + "edoxaban": "Edoxaban", + "empagliflozin": "Empagliflozin", + "enoxaparin": "Enoxaparin", + "eplerenone": "Eplerenone", + "ertapenem": "Ertapenem", + "erythromycin": "Erythromycin", + "escitalopram": "Escitalopram", + "esomeprazole": "Esomeprazole", + "ethinylestradiol": "Ethinylestradiol", + "exenatide": "Exenatide", + "ezetimibe": "Ezetimibe", + "famotidine": "Famotidine", + "felodipine": "Felodipine", + "fenofibrate": "Fenofibrate", + "fentanyl": "Fentanyl", + "fexofenadine": "Fexofenadine", + "finasteride": "Finasteride", + "flecainide": "Flecainide", + "flucloxacillin": "Flucloxacillin", + "fluconazole": "Fluconazole", + "fludrocortisone": "Fludrocortisone", + "flumazenil": "Flumazenil", + "fluoxetine": "Fluoxetine", + "flupentixol-decanoate": "Flupentixol decanoate", + "fluticasone": "Fluticasone", + "fluvoxamine": "Fluvoxamine", + "folic-acid": "Folic acid", + "formoterol": "Formoterol", + "fortrans-peg": "Fortrans/PEG", + "fosfomycin": "Fosfomycin", + "frusemide": "Frusemide", + "gabapentin": "Gabapentin", + "gentamicin": "Gentamicin", + "gliclazide": "Gliclazide", + "glipizide": "Glipizide", + "glycerol-suppository": "Glycerol suppository", + "glyceryl-trinitrate-gtn": "Glyceryl trinitrate (GTN)", + "guanfacine-mr": "Guanfacine MR", + "haloperidol-decanoate": "Haloperidol decanoate", + "heparin-iv-sc": "Heparin (IV/SC)", + "humalog-mix-25-50": "Humalog Mix 25/50", + "hydralazine": "Hydralazine", + "hydrochlorothiazide": "Hydrochlorothiazide", + "hydrocortisone": "Hydrocortisone", + "hydrocortisone-1": "Hydrocortisone 1%", + "hydromorphone-ir-iv": "Hydromorphone (IR/IV)", + "hydroxychloroquine": "Hydroxychloroquine", + "hyoscine-butylbromide": "Hyoscine butylbromide", + "hyoscine-hydrobromide": "Hyoscine hydrobromide", + "ibuprofen": "Ibuprofen", + "imipramine": "Imipramine", + "indapamide": "Indapamide", + "insulin-aspart": "Insulin aspart", + "insulin-degludec": "Insulin degludec", + "insulin-detemir": "Insulin detemir", + "insulin-glargine": "Insulin glargine", + "insulin-glulisine": "Insulin glulisine", + "insulin-lispro": "Insulin lispro", + "iodine": "Iodine", + "ipratropium": "Ipratropium", + "iron": "Iron", + "ispaghula-husk": "Ispaghula husk", + "ketamine": "Ketamine", + "ketorolac": "Ketorolac", + "labetalol": "Labetalol", + "lactulose": "Lactulose", + "lamotrigine": "Lamotrigine", + "levetiracetam": "Levetiracetam", + "levodopa-benserazide": "Levodopa/Benserazide", + "levomepromazine": "Levomepromazine", + "levonorgestrel": "Levonorgestrel", + "levothyroxine": "Levothyroxine", + "lidocaine-patch": "Lidocaine patch", + "lidocaine-viscous": "Lidocaine viscous", + "lignocaine-lidocaine-sc": "Lignocaine/Lidocaine SC", + "linagliptin": "Linagliptin", + "lisdexamfetamine": "Lisdexamfetamine", + "lithium-carbonate-ir-sr": "Lithium carbonate (IR/SR)", + "loperamide": "Loperamide", + "loratadine": "Loratadine", + "lorazepam": "Lorazepam", + "lurasidone": "Lurasidone", + "magnesium-oxide": "Magnesium oxide", + "magnesium-sulfate": "Magnesium sulfate", + "medroxyprogesterone": "Medroxyprogesterone", + "meloxicam": "Meloxicam", + "memantine": "Memantine", + "meropenem": "Meropenem", + "mesalazine": "Mesalazine", + "metaraminol": "Metaraminol", + "metformin": "Metformin", + "methadone": "Methadone", + "methotrexate": "Methotrexate", + "methylphenidate": "Methylphenidate", + "methylprednisolone": "Methylprednisolone", + "metoclopramide": "Metoclopramide", + "metoprolol": "Metoprolol", + "metronidazole": "Metronidazole", + "midazolam": "Midazolam", + "minocycline": "Minocycline", + "mirtazapine": "Mirtazapine", + "moclobemide": "Moclobemide", + "mometasone": "Mometasone", + "montelukast": "Montelukast", + "morphine-ir-iv": "Morphine (IR/IV)", + "morphine-sr-mr": "Morphine SR / MR", + "movicol-macrogol": "Movicol/Macrogol", + "moxifloxacin": "Moxifloxacin", + "naloxone": "Naloxone", + "naltrexone": "Naltrexone", + "naproxen": "Naproxen", + "niacin-nicotinamide": "Niacin / Nicotinamide", + "nicotine-gum": "Nicotine gum", + "nicotine-inhalator": "Nicotine inhalator", + "nicotine-lozenge": "Nicotine lozenge", + "nicotine-mouth-spray": "Nicotine mouth spray", + "nicotine-patch": "Nicotine patch", + "nicotine-sublingual-tablet": "Nicotine sublingual tablet", + "nifedipine-xr": "Nifedipine XR", + "nitrazepam": "Nitrazepam", + "nitrofurantoin": "Nitrofurantoin", + "noradrenaline": "Noradrenaline", + "norfloxacin": "Norfloxacin", + "nortriptyline": "Nortriptyline", + "novomix-30": "Novomix 30", + "nystatin": "Nystatin", + "olanzapine-pamoate-lai": "Olanzapine pamoate LAI", + "olanzapine-wafer-odt": "Olanzapine (wafer/ODT)", + "ondansetron": "Ondansetron", + "orphenadrine": "Orphenadrine", + "oseltamivir": "Oseltamivir", + "osmolax": "Osmolax", + "oxazepam": "Oxazepam", + "oxcarbazepine": "Oxcarbazepine", + "oxybutynin": "Oxybutynin", + "oxycodone-ir": "Oxycodone IR", + "oxycodone-sr-mr": "Oxycodone SR / MR", + "paliperidone-er": "Paliperidone ER", + "paliperidone-lai": "Paliperidone LAI", + "pantoprazole": "Pantoprazole", + "paracetamol": "Paracetamol", + "parecoxib": "Parecoxib", + "paroxetine": "Paroxetine", + "periciazine": "Periciazine", + "perindopril": "Perindopril", + "phenelzine": "Phenelzine", + "phenoxymethylpenicillin": "Phenoxymethylpenicillin", + "phenytoin": "Phenytoin", + "piperacillin-tazobactam": "Piperacillin/tazobactam", + "potassium-chloride": "Potassium chloride", + "prazosin": "Prazosin", + "prednisolone": "Prednisolone", + "prednisone": "Prednisone", + "pregabalin": "Pregabalin", + "probenecid": "Probenecid", + "prochlorperazine": "Prochlorperazine", + "promethazine": "Promethazine", + "propranolol": "Propranolol", + "psyllium": "Psyllium", + "pyridoxine": "Pyridoxine", + "quetiapine": "Quetiapine", + "reboxetine": "Reboxetine", + "riboflavin": "Riboflavin", + "risedronate": "Risedronate", + "risperidone": "Risperidone", + "risperidone-lai": "Risperidone LAI", + "rivaroxaban": "Rivaroxaban", + "rosuvastatin": "Rosuvastatin", + "roxithromycin": "Roxithromycin", + "salbutamol": "Salbutamol", + "selenium": "Selenium", + "semaglutide": "Semaglutide", + "senna": "Senna", + "sertraline": "Sertraline", + "sildenafil": "Sildenafil", + "sitagliptin": "Sitagliptin", + "sodium-chloride": "Sodium chloride", + "sodium-nitroprusside": "Sodium nitroprusside", + "sodium-phosphate-enema": "Sodium phosphate enema", + "sodium-picosulfate": "Sodium picosulfate", + "sodium-valproate-oral-iv": "Sodium valproate (oral/IV)", + "solifenacin": "Solifenacin", + "sotalol": "Sotalol", + "spironolactone": "Spironolactone", + "sulfasalazine": "Sulfasalazine", + "sumatriptan": "Sumatriptan", + "tadalafil": "Tadalafil", + "tamsulosin": "Tamsulosin", + "tapentadol-sr": "Tapentadol SR", + "temazepam": "Temazepam", + "thiamine": "Thiamine", + "ticagrelor": "Ticagrelor", + "tiotropium": "Tiotropium", + "topiramate": "Topiramate", + "tramadol-ir": "Tramadol IR", + "tranexamic-acid": "Tranexamic acid", + "tranylcypromine": "Tranylcypromine", + "triamcinolone": "Triamcinolone", + "trifluoperazine": "Trifluoperazine", + "trimethoprim": "Trimethoprim", + "valaciclovir": "Valaciclovir", + "vancomycin": "Vancomycin", + "varenicline": "Varenicline", + "vasopressin": "Vasopressin", + "venlafaxine-xr": "Venlafaxine XR", + "verapamil": "Verapamil", + "vitamin-a": "Vitamin A", + "vitamin-b12": "Vitamin B12", + "vitamin-d": "Vitamin D", + "vitamin-e": "Vitamin E", + "vitamin-k": "Vitamin K", + "vortioxetine": "Vortioxetine", + "warfarin-anticoagulant": "Warfarin", + "warfarin-vka": "Warfarin", + "zinc-sulfate": "Zinc sulfate", + "ziprasidone": "Ziprasidone", + "ziprasidone-im": "Ziprasidone IM", + "zoledronic-acid": "Zoledronic acid", + "zolpidem": "Zolpidem", + "zopiclone": "Zopiclone", + "zuclopenthixol": "Zuclopenthixol", + "zuclopenthixol-acetate": "Zuclopenthixol acetate", + "zuclopenthixol-decanoate": "Zuclopenthixol decanoate" + }, + "bySlug": { + "acamprosate": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["diazepam", "disulfiram", "imipramine"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "buprenorphine-sl-depot": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["cns-depressants", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "buprenorphine-naloxone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "gabapentin", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "pregabalin", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "cns-depressants", "gabapentinoids", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "temazepam" + ], + "termIds": ["alcohol", "benzodiazepines"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "bupropion-sr": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "disulfiram": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": ["alcohol"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "methadone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["carbamazepine", "phenytoin"], + "termIds": ["antiretrovirals", "cyp-inducers", "st-johns-wort"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "naltrexone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nicotine-gum": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "caution", + "counterparties": [ + "nicotine-lozenge", + "nicotine-mouth-spray", + "nicotine-patch", + "nicotine-sublingual-tablet" + ], + "termIds": ["acidic-drinks"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nicotine-inhalator": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "caution", + "counterparties": [ + "nicotine-lozenge", + "nicotine-mouth-spray", + "nicotine-patch", + "nicotine-sublingual-tablet" + ], + "termIds": ["acidic-drinks"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nicotine-lozenge": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "caution", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "nicotine-mouth-spray": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["acidic-drinks"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nicotine-patch": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["nicotine-lozenge", "nicotine-mouth-spray", "nicotine-sublingual-tablet"], + "termIds": ["smoking"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nicotine-sublingual-tablet": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["acidic-drinks"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "varenicline": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "caution", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 2 + }, + "adrenaline-epinephrine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "cetirizine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": [], + "termIds": ["alcohol", "cns-depressants"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "diphenhydramine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "amisulpride", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "brexpiprazole", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "cariprazine", + "chlorpromazine", + "clonazepam", + "clozapine", + "codeine", + "diazepam", + "fentanyl", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hydromorphone-ir-iv", + "levomepromazine", + "loperamide", + "lorazepam", + "lurasidone", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "tapentadol-sr", + "temazepam", + "tramadol-ir", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["alcohol", "antipsychotics", "barbiturates", "benzodiazepines", "cns-depressants", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clomipramine", + "clozapine", + "doxepin", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxybutynin", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antipsychotics", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "fexofenadine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["pgp"], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "loratadine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "promethazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "buprenorphine-patch": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "codeine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["bupropion-sr", "fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["cns-depressants"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "fentanyl": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "temazepam" + ], + "termIds": ["benzodiazepines"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "hydromorphone-ir-iv": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "temazepam" + ], + "termIds": ["benzodiazepines", "cns-depressants"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "morphine-ir-iv": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "gabapentin", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "pregabalin", + "temazepam" + ], + "termIds": ["benzodiazepines", "gabapentinoids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "morphine-sr-mr", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "morphine-sr-mr": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "temazepam" + ], + "termIds": ["benzodiazepines"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "oxycodone-ir": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "gabapentin", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "pregabalin", + "temazepam" + ], + "termIds": ["alcohol", "benzodiazepines", "gabapentinoids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["clarithromycin", "oxycodone-sr-mr"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "oxycodone-sr-mr": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "temazepam" + ], + "termIds": ["benzodiazepines"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["cyp-inhibitors"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "tapentadol-sr": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "alprazolam", + "amisulpride", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clonazepam", + "clozapine", + "diazepam", + "flupentixol-decanoate", + "haloperidol-decanoate", + "levomepromazine", + "lorazepam", + "lurasidone", + "midazolam", + "nitrazepam", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "oxazepam", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "temazepam", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["alcohol", "antipsychotics", "benzodiazepines"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "tramadol-ir": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amitriptyline", + "atomoxetine", + "citalopram", + "clomipramine", + "desvenlafaxine", + "doxepin", + "duloxetine", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "imipramine", + "nortriptyline", + "paroxetine", + "sertraline", + "venlafaxine-xr" + ], + "termIds": ["snris", "ssris", "st-johns-wort", "tcas"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "atomoxetine", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "desvenlafaxine", + "duloxetine", + "fentanyl", + "fluoxetine", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "paroxetine", + "tapentadol-sr", + "venlafaxine-xr" + ], + "termIds": ["cyp-inhibitors", "opioids", "snris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "celecoxib": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["fluconazole"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Triple Whammy", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "amiloride", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "diclofenac": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true + }, + { + "rowKey": "Triple Whammy", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "amiloride", + "candesartan", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "meropenem", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "diuretics"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "ibuprofen": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "aspirin", + "candesartan", + "diclofenac", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "ketorolac", + "meloxicam", + "meropenem", + "naproxen", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "diuretics", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "apixaban", + "aspirin", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "antiplatelets"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["aspirin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ketorolac": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "candesartan", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "meropenem", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "diuretics"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "meloxicam": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "aspirin", + "candesartan", + "diclofenac", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meropenem", + "naproxen", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "diuretics", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "naproxen": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "aspirin", + "candesartan", + "diclofenac", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "meropenem", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "diuretics", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "paracetamol": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["alcohol"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["flucloxacillin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "parecoxib": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "candesartan", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "meropenem", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "diuretics"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["fluconazole"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "bupivacaine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "capsaicin-cream": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "none", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "lidocaine-patch": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["amitriptyline", "pregabalin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "lidocaine-viscous": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["bupivacaine", "flecainide"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "lignocaine-lidocaine-sc": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amiodarone", + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "lidocaine-patch", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "amiodarone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["pgp"], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers", "qtc-prolonging"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "digoxin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiodarone", + "azithromycin", + "clarithromycin", + "erythromycin", + "roxithromycin", + "verapamil" + ], + "termIds": ["macrolides", "pgp"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amiloride", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "spironolactone" + ], + "termIds": ["diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "flecainide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["amiodarone", "fluoxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sotalol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amisulpride", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "azithromycin", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "citalopram", + "clarithromycin", + "clozapine", + "erythromycin", + "escitalopram", + "fluoxetine", + "flupentixol-decanoate", + "fluvoxamine", + "haloperidol-decanoate", + "levomepromazine", + "lurasidone", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "paliperidone-er", + "paliperidone-lai", + "paroxetine", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "roxithromycin", + "sertraline", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["antipsychotics", "macrolides", "qtc-prolonging", "ssris"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["verapamil"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "glyceryl-trinitrate-gtn": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["sildenafil"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["alcohol", "antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "amlodipine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "safe", + "counterparties": ["felodipine"], + "termIds": ["grapefruit"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "candesartan": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "aspirin", + "diclofenac", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "meropenem", + "naproxen", + "spironolactone" + ], + "termIds": ["arbs", "diuretics", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["spironolactone", "trimethoprim"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "diltiazem": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "felodipine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["azithromycin", "clarithromycin", "erythromycin", "roxithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors", "grapefruit", "macrolides"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "caution", + "counterparties": ["carbamazepine", "phenytoin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nifedipine-xr": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": ["cyp-inhibitors", "grapefruit"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "caution", + "counterparties": ["magnesium-sulfate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "perindopril": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "aspirin", + "diclofenac", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "naproxen", + "spironolactone" + ], + "termIds": ["acei", "diuretics", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amiloride", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "spironolactone" + ], + "termIds": ["diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "verapamil": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "atenolol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["diltiazem", "verapamil"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["amiodarone", "digoxin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "bisoprolol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["diltiazem", "verapamil"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["amiodarone", "digoxin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "carvedilol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["diltiazem", "verapamil"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": ["digoxin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "labetalol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "metoprolol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["diltiazem", "verapamil"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "amiloride": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["candesartan", "ertapenem", "meropenem", "perindopril", "spironolactone"], + "termIds": ["acei", "arbs"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "eplerenone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["clarithromycin"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["candesartan", "ertapenem", "meropenem", "perindopril"], + "termIds": ["acei", "arbs"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "frusemide": { + "rows": [ + { + "rowKey": "Triple Whammy", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "aspirin", + "candesartan", + "diclofenac", + "ertapenem", + "ibuprofen", + "ketorolac", + "meloxicam", + "meropenem", + "naproxen", + "perindopril" + ], + "termIds": ["acei", "arbs", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "hydrochlorothiazide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["indapamide"], + "termIds": ["thiazide-diuretics"], + "resolved": true + }, + { + "rowKey": "Triple Whammy", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "aspirin", + "diclofenac", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "naproxen", + "perindopril" + ], + "termIds": ["acei", "nsaids", "thiazide-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "indapamide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Triple Whammy", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen", "perindopril"], + "termIds": ["acei", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "spironolactone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["candesartan", "ertapenem", "meropenem", "perindopril"], + "termIds": ["acei", "arbs"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "caution", + "counterparties": ["digoxin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "atorvastatin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["rosuvastatin"], + "termIds": ["statins"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "ezetimibe": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "caution", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "caution", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 2 + }, + "fenofibrate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["atorvastatin", "rosuvastatin"], + "termIds": ["statins"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "rosuvastatin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["atorvastatin"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "clonidine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "alprazolam", + "amisulpride", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clonazepam", + "clozapine", + "diazepam", + "flupentixol-decanoate", + "haloperidol-decanoate", + "levomepromazine", + "lorazepam", + "lurasidone", + "midazolam", + "nitrazepam", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "oxazepam", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "temazepam", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["alcohol", "antipsychotics", "benzodiazepines"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "hydralazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "beneficial", + "counterparties": [ + "amiloride", + "atenolol", + "bisoprolol", + "carvedilol", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "propranolol", + "sotalol", + "spironolactone" + ], + "termIds": ["beta-blockers", "diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "prazosin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["sildenafil"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amiloride", + "atenolol", + "bisoprolol", + "carvedilol", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "propranolol", + "sotalol", + "spironolactone" + ], + "termIds": ["beta-blockers", "diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "metaraminol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "noradrenaline": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amitriptyline", + "clomipramine", + "doxepin", + "imipramine", + "nortriptyline", + "phenelzine", + "tranylcypromine" + ], + "termIds": ["maois", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sodium-nitroprusside": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["sildenafil"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "vasopressin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["noradrenaline"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "betamethasone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "none", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "clobetasol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "hydrocortisone-1": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "none", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "triamcinolone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "unknown", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "dulaglutide": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "exenatide": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["paracetamol"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "humalog-mix-25-50": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "beclometasone", + "betamethasone", + "bisoprolol", + "budesonide", + "carvedilol", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", + "labetalol", + "methylprednisolone", + "metoprolol", + "mometasone", + "prednisolone", + "prednisone", + "propranolol", + "sotalol", + "triamcinolone" + ], + "termIds": ["beta-blockers", "corticosteroids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "insulin-aspart": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "insulin-degludec": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "beclometasone", + "betamethasone", + "bisoprolol", + "budesonide", + "carvedilol", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", + "labetalol", + "methylprednisolone", + "metoprolol", + "mometasone", + "prednisolone", + "prednisone", + "propranolol", + "sotalol", + "triamcinolone" + ], + "termIds": ["beta-blockers", "corticosteroids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "insulin-detemir": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "insulin-glargine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "beclometasone", + "betamethasone", + "budesonide", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", + "methylprednisolone", + "mometasone", + "prednisolone", + "prednisone", + "triamcinolone" + ], + "termIds": ["corticosteroids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "insulin-glulisine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "beclometasone", + "betamethasone", + "bisoprolol", + "budesonide", + "carvedilol", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", + "labetalol", + "methylprednisolone", + "metoprolol", + "mometasone", + "prednisolone", + "prednisone", + "propranolol", + "sotalol", + "triamcinolone" + ], + "termIds": ["beta-blockers", "corticosteroids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "insulin-lispro": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "beclometasone", + "betamethasone", + "bisoprolol", + "budesonide", + "carvedilol", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", + "labetalol", + "methylprednisolone", + "metoprolol", + "mometasone", + "prednisolone", + "prednisone", + "propranolol", + "sotalol", + "triamcinolone" + ], + "termIds": ["beta-blockers", "corticosteroids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "novomix-30": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "beclometasone", + "betamethasone", + "bisoprolol", + "budesonide", + "carvedilol", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", + "labetalol", + "methylprednisolone", + "metoprolol", + "mometasone", + "prednisolone", + "prednisone", + "propranolol", + "sotalol", + "triamcinolone" + ], + "termIds": ["beta-blockers", "corticosteroids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "semaglutide": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "acarbose": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dapagliflozin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["hydrochlorothiazide", "indapamide"], + "termIds": ["thiazide-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "empagliflozin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["hydrochlorothiazide", "indapamide"], + "termIds": ["thiazide-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "gliclazide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "glipizide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["fluconazole"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "linagliptin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": ["pgp"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "metformin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["alcohol"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "sitagliptin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "dexamethasone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "fludrocortisone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "spironolactone" + ], + "termIds": ["diuretics", "thiazide-diuretics"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["digoxin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "hydrocortisone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["hydrochlorothiazide", "indapamide"], + "termIds": ["thiazide-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "methylprednisolone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["azithromycin", "clarithromycin", "erythromycin", "roxithromycin"], + "termIds": ["azole-antifungals", "macrolides"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "prednisolone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["ciprofloxacin", "moxifloxacin", "norfloxacin"], + "termIds": ["fluoroquinolones"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "prednisone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["ciprofloxacin", "moxifloxacin", "norfloxacin"], + "termIds": ["fluoroquinolones"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "alendronate": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["acidic-drinks", "antacids", "food"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "carbimazole": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["digoxin", "warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "denosumab": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "levothyroxine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "iron", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "risedronate": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["antacids", "food"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "zoledronic-acid": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["amikacin", "frusemide", "gentamicin"], + "termIds": ["aminoglycosides", "loop-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ethinylestradiol": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["carbamazepine", "phenytoin"], + "termIds": ["antiretrovirals", "cyp-inducers", "st-johns-wort"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["lamotrigine"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "levonorgestrel": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["cyp-inducers"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "medroxyprogesterone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["carbamazepine", "phenytoin"], + "termIds": ["cyp-inducers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "esomeprazole": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["clopidogrel"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "famotidine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "safe", + "counterparties": ["phenytoin", "warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["iron"], + "termIds": ["azole-antifungals"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "pantoprazole": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["iron"], + "termIds": ["azole-antifungals"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "cyclizine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "cariprazine", + "chlorpromazine", + "clomipramine", + "clonazepam", + "clozapine", + "codeine", + "diazepam", + "doxepin", + "fentanyl", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hydromorphone-ir-iv", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "loperamide", + "lorazepam", + "lurasidone", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxazepam", + "oxybutynin", + "oxycodone-ir", + "oxycodone-sr-mr", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "promethazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "tapentadol-sr", + "temazepam", + "tramadol-ir", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["alcohol", "anticholinergics", "antipsychotics", "benzodiazepines", "opioids", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "domperidone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "amiodarone", + "citalopram", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "sertraline", + "sotalol" + ], + "termIds": ["ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "hyoscine-hydrobromide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "cariprazine", + "chlorpromazine", + "clomipramine", + "clonazepam", + "clozapine", + "codeine", + "diazepam", + "doxepin", + "fentanyl", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hydromorphone-ir-iv", + "hyoscine-butylbromide", + "imipramine", + "levomepromazine", + "loperamide", + "lorazepam", + "lurasidone", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxazepam", + "oxybutynin", + "oxycodone-ir", + "oxycodone-sr-mr", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "promethazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "tapentadol-sr", + "temazepam", + "tramadol-ir", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["alcohol", "anticholinergics", "antipsychotics", "benzodiazepines", "opioids", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "metoclopramide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amisulpride", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clozapine", + "flupentixol-decanoate", + "haloperidol-decanoate", + "levomepromazine", + "lurasidone", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["antipsychotics"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amitriptyline", + "benzatropine", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "orphenadrine", + "oxybutynin", + "solifenacin" + ], + "termIds": ["anticholinergics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ondansetron": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiodarone", + "azithromycin", + "citalopram", + "clarithromycin", + "erythromycin", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "roxithromycin", + "sertraline" + ], + "termIds": ["macrolides", "qtc-prolonging", "ssris"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": [], + "termIds": ["serotonergic"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "prochlorperazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["metoclopramide"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "hyoscine-butylbromide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "alimemazine", + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "cetirizine", + "chlorpromazine", + "clomipramine", + "clozapine", + "cyclizine", + "diphenhydramine", + "doxepin", + "fexofenadine", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "loratadine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxybutynin", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "promethazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antihistamines", "antipsychotics", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "loperamide": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir", + "verapamil" + ], + "termIds": ["azole-antifungals", "opioids", "pgp"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "mesalazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "caution", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "low", + "counterparties": ["digoxin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sulfasalazine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["folic-acid"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": ["gliclazide"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "bisacodyl": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "esomeprazole", "magnesium-oxide", "pantoprazole"], + "termIds": ["antacids", "food", "ppis"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "docusate-sodium": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "fortrans-peg": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "glycerol-suppository": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "none", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "ispaghula-husk": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "lactulose": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "movicol-macrogol": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "osmolax": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "psyllium": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "senna": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["digoxin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sodium-phosphate-enema": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amiloride", + "aspirin", + "candesartan", + "diclofenac", + "eplerenone", + "ertapenem", + "frusemide", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "meropenem", + "naproxen", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "diuretics", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sodium-picosulfate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "apixaban": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "pgp"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "aspirin", + "citalopram", + "diclofenac", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "paroxetine", + "sertraline" + ], + "termIds": ["nsaids", "ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dabigatran": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amiodarone", "clarithromycin", "verapamil"], + "termIds": ["pgp"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "aspirin", + "citalopram", + "diclofenac", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "paroxetine", + "sertraline" + ], + "termIds": ["nsaids", "ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dalteparin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "apixaban", + "aspirin", + "clopidogrel", + "dabigatran", + "diclofenac", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "edoxaban": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amiodarone", + "azithromycin", + "clarithromycin", + "erythromycin", + "roxithromycin", + "verapamil" + ], + "termIds": ["macrolides", "pgp"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "aspirin", + "citalopram", + "diclofenac", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "paroxetine", + "sertraline" + ], + "termIds": ["nsaids", "ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "enoxaparin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "apixaban", + "aspirin", + "clopidogrel", + "dabigatran", + "dalteparin", + "diclofenac", + "dipyridamole", + "edoxaban", + "heparin-iv-sc", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "heparin-iv-sc": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "apixaban", + "aspirin", + "clopidogrel", + "dabigatran", + "dalteparin", + "diclofenac", + "dipyridamole", + "edoxaban", + "enoxaparin", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "rivaroxaban": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "pgp"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "aspirin", + "citalopram", + "diclofenac", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "paroxetine", + "sertraline" + ], + "termIds": ["nsaids", "ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "tranexamic-acid": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "warfarin-vka": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amiodarone", "fluconazole", "metronidazole"], + "termIds": ["cotrimoxazole"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["carbamazepine"], + "termIds": ["st-johns-wort"], + "resolved": true + }, + { + "rowKey": "Dietary", + "rowIndex": 2, + "severity": "high", + "counterparties": ["vitamin-k", "warfarin-anticoagulant"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "warfarin-anticoagulant": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amiodarone", "fluconazole", "metronidazole"], + "termIds": ["cotrimoxazole"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "aspirin", + "citalopram", + "diclofenac", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "paroxetine", + "sertraline" + ], + "termIds": ["nsaids", "ssris"], + "resolved": true + }, + { + "rowKey": "Dietary", + "rowIndex": 2, + "severity": "high", + "counterparties": ["vitamin-k"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "aspirin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "diclofenac", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "antiplatelets", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "caution", + "counterparties": ["ibuprofen"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "clopidogrel": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["esomeprazole"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "apixaban", + "aspirin", + "citalopram", + "dabigatran", + "dalteparin", + "diclofenac", + "dipyridamole", + "edoxaban", + "enoxaparin", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "heparin-iv-sc", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "paroxetine", + "rivaroxaban", + "sertraline", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "nsaids", "ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dipyridamole": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ticagrelor": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["aspirin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "gentamicin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "aspirin", + "diclofenac", + "frusemide", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "vancomycin" + ], + "termIds": ["loop-diuretics", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "fluconazole": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "apixaban", + "atorvastatin", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "phenytoin", + "rivaroxaban", + "rosuvastatin", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "statins"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nystatin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "none", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "aciclovir": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amikacin", + "aspirin", + "diclofenac", + "gentamicin", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen" + ], + "termIds": ["aminoglycosides", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "oseltamivir": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "valaciclovir": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amiloride", + "aspirin", + "diclofenac", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "naproxen", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "diuretics", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "clotrimazole": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "cefazolin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "cefepime": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["tramadol-ir"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ceftriaxone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "cefuroxime": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["calcium-carbonate", "esomeprazole", "magnesium-oxide", "pantoprazole"], + "termIds": ["antacids", "ppis"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "cephalexin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "ciprofloxacin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "iron", "magnesium-oxide"], + "termIds": ["antacids", "food"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "vancomycin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amikacin", + "aspirin", + "diclofenac", + "frusemide", + "gentamicin", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen" + ], + "termIds": ["aminoglycosides", "loop-diuretics", "nsaids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amikacin", + "aspirin", + "diclofenac", + "frusemide", + "gentamicin", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen" + ], + "termIds": ["aminoglycosides", "loop-diuretics", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "azithromycin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["clarithromycin", "erythromycin"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["amiodarone", "ondansetron", "sotalol"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "clarithromycin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["pgp"], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["amiodarone", "sotalol"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "erythromycin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "minocycline": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "iron", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "roxithromycin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["clarithromycin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "amikacin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["frusemide", "vancomycin"], + "termIds": ["loop-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "aztreonam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "clindamycin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "co-trimoxazole": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["candesartan", "ertapenem", "meropenem", "perindopril", "spironolactone"], + "termIds": ["acei", "arbs"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "colistin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amikacin", "gentamicin"], + "termIds": ["aminoglycosides"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen", "vancomycin"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ertapenem": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "meropenem": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "metronidazole": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["alcohol"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "moxifloxacin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "iron", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "amiodarone", + "citalopram", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "sertraline", + "sotalol" + ], + "termIds": ["ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "norfloxacin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "iron", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "amoxicillin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["allopurinol"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "amoxicillin-clavulanate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": ["warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "benzathine-benzylpenicillin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "dicloxacillin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": ["food"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["probenecid"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "flucloxacillin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["paracetamol"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "phenoxymethylpenicillin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": ["food"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["probenecid"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "piperacillin-tazobactam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["vancomycin"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "doxycycline": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["carbamazepine", "phenytoin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "fosfomycin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": ["metoclopramide"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nitrofurantoin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "trimethoprim": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["candesartan", "ertapenem", "meropenem", "perindopril", "spironolactone"], + "termIds": ["acei", "arbs"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "allopurinol": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["amoxicillin", "amoxicillin-clavulanate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "baclofen": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "colchicine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "azithromycin", + "clarithromycin", + "diltiazem", + "erythromycin", + "roxithromycin", + "verapamil" + ], + "termIds": ["azole-antifungals", "macrolides", "pgp"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["atorvastatin", "rosuvastatin"], + "termIds": ["statins"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dantrolene": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "hydroxychloroquine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["amiodarone", "azithromycin", "clarithromycin", "erythromycin", "roxithromycin"], + "termIds": ["macrolides", "qtc-prolonging"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "low", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "methotrexate": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["trimethoprim"], + "termIds": ["cotrimoxazole"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "aspirin", + "diclofenac", + "esomeprazole", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "pantoprazole" + ], + "termIds": ["nsaids", "ppis"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "orphenadrine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clomipramine", + "clozapine", + "doxepin", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "oxybutynin", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "promethazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antipsychotics", "tcas"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "probenecid": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["aspirin"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "armodafinil": { + "rows": [ + { + "rowKey": "Endocrine", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["diazepam", "phenytoin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "donepezil": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amitriptyline", + "benzatropine", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "orphenadrine", + "oxybutynin", + "promethazine", + "solifenacin" + ], + "termIds": ["anticholinergics"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amiodarone", + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "levodopa-benserazide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amisulpride", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clozapine", + "flupentixol-decanoate", + "haloperidol-decanoate", + "levomepromazine", + "lurasidone", + "metoclopramide", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["antipsychotics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "memantine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["ketamine"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "carbamazepine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 2 + }, + "gabapentin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["antacids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "lamotrigine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["carbamazepine", "phenytoin"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Endocrine", + "rowIndex": 2, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 2 + }, + "levetiracetam": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "phenytoin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 2 + }, + "pregabalin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sodium-valproate-oral-iv": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["lamotrigine"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["meropenem"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "topiramate": { + "rows": [ + { + "rowKey": "Endocrine", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "sumatriptan": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "caution", + "counterparties": [ + "atomoxetine", + "citalopram", + "desvenlafaxine", + "duloxetine", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "sertraline", + "tramadol-ir", + "venlafaxine-xr" + ], + "termIds": ["snris", "ssris"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "alprazolam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["alcohol", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "clonazepam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["alcohol", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "diazepam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["alcohol", "barbiturates", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["cyp-inhibitors"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "lorazepam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["alcohol", "barbiturates", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "midazolam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["fentanyl", "morphine-sr-mr"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "nitrazepam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["alcohol", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "oxazepam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["alcohol", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "temazepam": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["alcohol", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "zolpidem": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["alcohol"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["cyp-inhibitors"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "zopiclone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["alcohol", "cns-depressants"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["erythromycin"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "atomoxetine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["phenelzine", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dexamfetamine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "guanfacine-mr": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "lisdexamfetamine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "methylphenidate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amitriptyline", + "atomoxetine", + "clomipramine", + "desvenlafaxine", + "doxepin", + "duloxetine", + "imipramine", + "nortriptyline", + "tramadol-ir", + "venlafaxine-xr" + ], + "termIds": ["snris", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "alimemazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clomipramine", + "clozapine", + "doxepin", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxybutynin", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antipsychotics", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "benzatropine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alimemazine", + "amitriptyline", + "cetirizine", + "clomipramine", + "clozapine", + "cyclizine", + "diphenhydramine", + "doxepin", + "fexofenadine", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "loratadine", + "nortriptyline", + "orphenadrine", + "oxybutynin", + "promethazine", + "solifenacin" + ], + "termIds": ["anticholinergics", "antihistamines", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "cyproheptadine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clomipramine", + "clozapine", + "doxepin", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxybutynin", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antipsychotics", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dexmedetomidine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amiodarone", + "atenolol", + "bisoprolol", + "carvedilol", + "digoxin", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "codeine", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "methadone", + "morphine-ir-iv", + "morphine-sr-mr", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "tramadol-ir" + ], + "termIds": ["opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "droperidol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiodarone", + "azithromycin", + "clarithromycin", + "erythromycin", + "roxithromycin", + "sotalol" + ], + "termIds": ["macrolides"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ketamine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "levomepromazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "propranolol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["diltiazem", "verapamil"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["adrenaline-epinephrine"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ziprasidone-im": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiodarone", + "azithromycin", + "clarithromycin", + "droperidol", + "erythromycin", + "roxithromycin" + ], + "termIds": ["macrolides", "qtc-prolonging"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "agomelatine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["ciprofloxacin", "fluvoxamine"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["smoking"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "amitriptyline": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "benzatropine", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "orphenadrine", + "oxybutynin", + "promethazine", + "solifenacin" + ], + "termIds": ["anticholinergics"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["clomipramine", "doxepin", "fluoxetine", "imipramine", "nortriptyline", "paroxetine"], + "termIds": ["cyp-inhibitors", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "citalopram": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amiodarone", "sotalol"], + "termIds": ["qtc-prolonging"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "clomipramine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "citalopram", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "phenelzine", + "sertraline", + "tramadol-ir", + "tranylcypromine" + ], + "termIds": ["maois", "ssris"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "desvenlafaxine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois", "st-johns-wort"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "safe", + "counterparties": ["atorvastatin", "rosuvastatin", "venlafaxine-xr"], + "termIds": ["statins"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dosulepin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "doxepin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "duloxetine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["ciprofloxacin", "fluvoxamine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "escitalopram": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amiodarone", "sotalol"], + "termIds": ["qtc-prolonging"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "fluoxetine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "fluvoxamine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "imipramine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["fluoxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "mirtazapine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["alcohol", "benzodiazepines", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": [ + "citalopram", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "phenelzine", + "sertraline", + "tramadol-ir", + "tranylcypromine" + ], + "termIds": ["maois", "ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "moclobemide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["citalopram", "escitalopram", "fluoxetine", "fluvoxamine", "paroxetine", "sertraline"], + "termIds": ["ssris"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "nortriptyline": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["bupropion-sr", "fluoxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "paroxetine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "phenelzine": { + "rows": [ + { + "rowKey": "Dietary", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [ + "adrenaline-epinephrine", + "amitriptyline", + "citalopram", + "clomipramine", + "doxepin", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "imipramine", + "nortriptyline", + "paroxetine", + "sertraline", + "tramadol-ir" + ], + "termIds": ["ssris", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "reboxetine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tranylcypromine"], + "termIds": ["maois"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sertraline": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tapentadol-sr", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois", "st-johns-wort"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "tranylcypromine": { + "rows": [ + { + "rowKey": "Dietary", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["food"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "venlafaxine-xr": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], + "termIds": ["maois", "st-johns-wort"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], + "termIds": ["nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "vortioxetine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["bupropion-sr", "fluoxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["carbamazepine"], + "termIds": ["cyp-inducers"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 2, + "severity": "moderate", + "counterparties": ["tramadol-ir"], + "termIds": ["st-johns-wort"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "aripiprazole": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["aripiprazole-lai", "clarithromycin", "fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["aripiprazole-lai", "carbamazepine", "phenytoin"], + "termIds": ["cyp-inducers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "clozapine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["smoking"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["ciprofloxacin", "fluvoxamine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "olanzapine-wafer-odt": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "cns-depressants", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["smoking"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "quetiapine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["cyp-inhibitors"], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "risperidone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["bupropion-sr", "fluoxetine", "paroxetine", "risperidone-lai"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "aripiprazole-lai": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["clarithromycin", "fluoxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["carbamazepine"], + "termIds": ["cyp-inducers"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "flupentixol-decanoate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["qtc-prolonging"], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "haloperidol-decanoate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amiodarone", "azithromycin", "clarithromycin", "erythromycin", "roxithromycin"], + "termIds": ["macrolides", "qtc-prolonging"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["cyp-inhibitors"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "olanzapine-pamoate-lai": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["smoking"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "paliperidone-lai": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "risperidone-lai": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["fluoxetine", "paroxetine", "risperidone"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "zuclopenthixol-acetate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "clonazepam", + "diazepam", + "lorazepam", + "midazolam", + "nitrazepam", + "oxazepam", + "temazepam" + ], + "termIds": ["benzodiazepines"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "zuclopenthixol-decanoate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "lithium-carbonate-ir-sr": { + "rows": [ + { + "rowKey": "Triple Whammy (Lithium version)", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "aspirin", + "candesartan", + "diclofenac", + "ertapenem", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "meropenem", + "naproxen", + "perindopril" + ], + "termIds": ["acei", "arbs", "nsaids", "thiazide-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "oxcarbazepine": { + "rows": [ + { + "rowKey": "Endocrine", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 2 + }, + "amisulpride": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["levodopa-benserazide"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amiodarone", + "citalopram", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "sertraline" + ], + "termIds": ["ssris"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "safe", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "asenapine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["ciprofloxacin", "fluvoxamine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives", "cns-depressants"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "brexpiprazole": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["clarithromycin", "fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "cariprazine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "chlorpromazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amitriptyline", + "benzatropine", + "clomipramine", + "doxepin", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "nortriptyline", + "orphenadrine", + "oxybutynin", + "solifenacin" + ], + "termIds": ["anticholinergics", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "lurasidone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["clarithromycin", "diltiazem"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "paliperidone-er": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "safe", + "counterparties": ["fluoxetine", "paroxetine", "risperidone", "risperidone-lai"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "periciazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alprazolam", + "amlodipine", + "atenolol", + "bisoprolol", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "candesartan", + "carvedilol", + "clonazepam", + "codeine", + "diazepam", + "diltiazem", + "eplerenone", + "felodipine", + "fentanyl", + "frusemide", + "hydrochlorothiazide", + "hydromorphone-ir-iv", + "indapamide", + "labetalol", + "loperamide", + "lorazepam", + "methadone", + "metoprolol", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nifedipine-xr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "perindopril", + "spironolactone", + "tapentadol-sr", + "temazepam", + "tramadol-ir", + "verapamil" + ], + "termIds": ["antihypertensives", "benzodiazepines", "opioids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "trifluoperazine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["qtc-prolonging"], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["citalopram", "escitalopram", "fluoxetine", "fluvoxamine", "paroxetine", "sertraline"], + "termIds": ["ssris"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "ziprasidone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amitriptyline", + "citalopram", + "clomipramine", + "doxepin", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "imipramine", + "nortriptyline", + "paroxetine", + "sertraline" + ], + "termIds": ["ssris", "tcas"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "zuclopenthixol": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["bupropion-sr", "fluoxetine", "paroxetine"], + "termIds": ["cyp-inhibitors"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": ["cns-depressants"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "beclometasone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": ["fluticasone"], + "termIds": ["cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "budesonide": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": ["cyp-inhibitors"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "ciclesonide": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "fluticasone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": ["cyp-inhibitors"], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "formoterol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "beclometasone", + "betamethasone", + "budesonide", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrochlorothiazide", + "hydrocortisone", + "hydrocortisone-1", + "indapamide", + "methylprednisolone", + "mometasone", + "prednisolone", + "prednisone", + "triamcinolone" + ], + "termIds": ["corticosteroids", "thiazide-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ipratropium": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": [ + "alimemazine", + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "cetirizine", + "chlorpromazine", + "clomipramine", + "clozapine", + "cyclizine", + "diphenhydramine", + "doxepin", + "fexofenadine", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "loratadine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxybutynin", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "promethazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antihistamines", "antipsychotics", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "mometasone": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "montelukast": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": ["phenytoin"], + "termIds": ["barbiturates"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "salbutamol": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "atenolol", + "bisoprolol", + "carvedilol", + "labetalol", + "metoprolol", + "propranolol", + "sotalol" + ], + "termIds": ["beta-blockers"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [ + "amiloride", + "eplerenone", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "spironolactone" + ], + "termIds": ["diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "tiotropium": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "moderate", + "counterparties": [ + "amitriptyline", + "benzatropine", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "orphenadrine", + "oxybutynin", + "solifenacin" + ], + "termIds": ["anticholinergics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "acetylcysteine": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "flumazenil": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amitriptyline", "clomipramine", "doxepin", "imipramine", "nortriptyline"], + "termIds": ["tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "naloxone": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["buprenorphine-patch"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "dutasteride": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "finasteride": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "low", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "oxybutynin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "alimemazine", + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "cetirizine", + "chlorpromazine", + "clomipramine", + "clozapine", + "cyclizine", + "diphenhydramine", + "doxepin", + "fexofenadine", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "loratadine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "promethazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antihistamines", "antipsychotics", "tcas"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["metoclopramide"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "sildenafil": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["prazosin", "tamsulosin"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["cyp-inhibitors", "grapefruit"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "solifenacin": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": [ + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", + "cariprazine", + "chlorpromazine", + "clomipramine", + "clozapine", + "doxepin", + "flupentixol-decanoate", + "haloperidol-decanoate", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", + "lurasidone", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", + "oxybutynin", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" + ], + "termIds": ["anticholinergics", "antipsychotics", "tcas"], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "tadalafil": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["tamsulosin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "tamsulosin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "sildenafil", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inhibitors"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "ascorbic-acid": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "beneficial", + "counterparties": ["iron"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "calcium-carbonate": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["ciprofloxacin", "iron", "levothyroxine", "moxifloxacin", "norfloxacin"], + "termIds": ["fluoroquinolones"], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["hydrochlorothiazide", "indapamide"], + "termIds": ["thiazide-diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "copper": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "folic-acid": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["methotrexate"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["phenytoin"], + "termIds": ["barbiturates"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "iodine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "iron": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["calcium-carbonate", "magnesium-oxide"], + "termIds": ["acidic-drinks", "antacids", "food"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "critical", + "counterparties": ["ciprofloxacin", "doxycycline", "levodopa-benserazide"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 2, + "severity": "beneficial", + "counterparties": ["ascorbic-acid"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "magnesium-oxide": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["ciprofloxacin", "doxycycline", "moxifloxacin", "norfloxacin"], + "termIds": ["fluoroquinolones"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "magnesium-sulfate": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["amlodipine", "nifedipine-xr"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacodynamic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "niacin-nicotinamide": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["atorvastatin", "rosuvastatin"], + "termIds": ["statins"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "potassium-chloride": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "amiloride", + "aspirin", + "candesartan", + "diclofenac", + "ertapenem", + "ibuprofen", + "ketorolac", + "meloxicam", + "meropenem", + "naproxen", + "perindopril", + "spironolactone" + ], + "termIds": ["acei", "arbs", "nsaids"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "pyridoxine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": ["levodopa-benserazide"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "riboflavin": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "unknown", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "selenium": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "none", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "sodium-chloride": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "high", + "counterparties": [ + "amiloride", + "amlodipine", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", + "diltiazem", + "eplerenone", + "felodipine", + "frusemide", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", + "spironolactone", + "verapamil" + ], + "termIds": ["antihypertensives", "diuretics"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "thiamine": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "none", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "vitamin-a": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 2 + }, + "vitamin-b12": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": ["colchicine", "esomeprazole", "metformin", "pantoprazole"], + "termIds": ["ppis"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "vitamin-d": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "moderate", + "counterparties": ["carbamazepine", "phenytoin"], + "termIds": [], + "resolved": true + } + ], + "unresolvedRowCount": 1 + }, + "vitamin-e": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": [ + "apixaban", + "aspirin", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + }, + "vitamin-k": { + "rows": [ + { + "rowKey": "Pharmacodynamic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["warfarin-anticoagulant", "warfarin-vka"], + "termIds": [], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "zinc-sulfate": { + "rows": [ + { + "rowKey": "Pharmacokinetic", + "rowIndex": 0, + "severity": "critical", + "counterparties": ["ciprofloxacin", "doxycycline", "moxifloxacin", "norfloxacin"], + "termIds": ["fluoroquinolones"], + "resolved": true + } + ], + "unresolvedRowCount": 0 + } + } +} diff --git a/docs/codebase-index.md b/docs/codebase-index.md index d66b4b52a6..bf4d71e005 100644 --- a/docs/codebase-index.md +++ b/docs/codebase-index.md @@ -331,6 +331,7 @@ One shared composer (`master-search-header.tsx`) serves every mode. Placement: - **Intentionally composer-free routes**: `/differentials/presentations/*` and `/differentials/compare` (comparison workflow owns its chrome), `/documents/[id]` viewer (has its own in-document ask composer), `/documents/source/*` (document flow owns mobile chrome). Do not re-flag these in search-consistency audits. - **Shared in-page navigation**: `src/components/in-page-nav/` is the default template for section navigation on any mode page (`docs/search-chrome-behaviour.md`). `in-page-nav-header.tsx` (`InPageNavHeader`) owns the header row, both sheets and the `PhoneHeaderCollapsePortal` wrapper; `page-section-index.ts` (`PageSection`, `toDocumentSections`, `sectionTargetIds`) is the declaration shape; `use-resolved-page-sections.ts` narrows a declaration to the anchors actually rendered at this breakpoint; `use-in-page-section-nav.ts` composes that with `useDocumentSectionSpy` and `jumpToDocumentSection`; `use-page-section-weights.ts` measures segment weights; `use-in-page-chrome-metrics.ts` publishes `--inpage-anchor-offset`; `in-page-nav-classes.ts` holds the shared anchor (`inPageAnchor`) and actions-sheet row classes; `in-page-section-rail.tsx` (`InPageSectionRail`) is the optional visible second rail, opted into with `rail={{ label }}` by panel-swap routes with few sections (medications only) in place of the weighted track. Anchor measurement itself is `src/components/sticky-chrome-metrics.ts` (`useStickyChromeMetrics`), shared with the document viewer's `use-document-chrome-metrics.ts`. Mounted by `differentials/differential-detail-page.tsx`, `services/service-detail-page.tsx`, `forms/form-detail-page.tsx`, `dsm/dsm-differential-considerations-page.tsx`, and — through a colocated `"use client"` nav-header sibling that owns and exports the route's section table — `specifiers/specifier-nav-header.tsx`, `formulation/formulation-nav-header.tsx`, `dsm/dsm-diagnosis-nav-header.tsx`, `factsheets/factsheet-nav-header.tsx` and `clinical-dashboard/medication-nav-header.tsx`. The sibling is mandatory for the four Server Component pages (neither `onSelectSection` nor a `LucideIcon` crosses the RSC boundary) and the convention for the rest. Two adopters swap panels instead of scrolling — `differential-detail-page.tsx` and the medication record page — so they pass explicit weights, carry no `inPageAnchor`, and use neither `useResolvedPageSections` nor the scroll spy. Every declared section is pinned against rendered DOM by `tests/in-page-nav-route-sections.dom.test.tsx` (anchors for the scrolling routes, swapped-in panels for the tab routes). - **Shared secondary navigation**: `src/components/page-secondary-navigation.tsx` (`PageSecondaryNavigation`, mode destinations only). Mode destinations come from `src/lib/mode-secondary-navigation.ts` (`modeSecondaryNavigationRegistry`, no "Home" item). `GlobalSearchShell` renders it in normal flow at the top of `#main-content` for its owned namespaced modes; it self-suppresses on clean mode homes, on Therapy Compass, and on every information page — `hasLocalInformationPageNavigation` is now just `isInformationPage`, because each of those routes owns its own in-page navigation. The older shared `SecondaryNavigation` component was deleted here (`/issues #271`): its `section` kind and "On this page" pill rail went when the last six information routes moved onto `InPageNavHeader`, and the surviving `route`/`action` kinds had no production constructor left — `RegistryModeNav` renders `ModeNav`, not `SecondaryNavigation`, so the only remaining caller was its own test file, which went with it. +- **Patient context (decision support)**: the app's patient-specific surface, spanning two engines over one session-scoped profile. Store: `src/lib/patient-profile-storage.ts` (sessionStorage external store, anonymous physiology + catalogue medication slugs, cleared on tab close) behind `clinical-dashboard/patient-profile-context.tsx` (`PatientProfileProvider`, mounted for the whole shell). Engines: `src/lib/medication-patient-alerts.ts` (physiology — age/renal/hepatic/QTc/pregnancy/allergy, with an `unassessed` fail-safe) and `src/lib/medication-interactions.ts` (drug–drug, against the entered medication list). The interaction engine reads the generated `data/medication-interaction-index.json`, built by `scripts/build-medication-interaction-index.ts` (`npm run medications:interactions`, staleness-gated by `check:medication-interactions`) from the curated `src/lib/medication-interaction-lexicon.ts` — the catalogue's `Key Interactions` rows are prose, and that lexicon is what resolves their class/mechanism/non-drug terms to catalogue targets. `composeMedicationVerdict` folds both engines into the one tone a result row wears; **green is unreachable whenever either engine left something unassessed or unresolved**, degrading to a neutral "needs manual review" instead. UI: `clinical-dashboard/patient-profile-panel.tsx` (the form, including the medication picker), `medication-considerations.tsx` (both detail-page blocks and the verdict badge/edge helpers), and `patient-details-dock-action.tsx` (the phone dock pill — see the addon-slot section of `docs/search-chrome-behaviour.md`). Regulatory status is **open**: `docs/samd-classification-medication-considerations.md`. - **Local filter fields** (sidebar "Search chats", document drawer "Find a document"/"Find a source PDF") are scoped filters, not global search; they share the `fieldControlWithIcon`/`fieldIcon` primitives. - **Wiring conventions** for buttons and route navigation (and the gates that enforce them — the dead-button ESLint rule and the orphan-route reachability test) live in `docs/wiring-conventions.md`. diff --git a/docs/design-system/COMPONENTS.md b/docs/design-system/COMPONENTS.md index 2561da50d4..0686791cb8 100644 --- a/docs/design-system/COMPONENTS.md +++ b/docs/design-system/COMPONENTS.md @@ -1001,7 +1001,7 @@ This generated snapshot is a local source-derived inventory. It does not assert | `SearchField` | controls | yes | yes | no | yes | no | 0 | | `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 7 | | `Select` | controls | yes | yes | inherited-global-root | yes | no | 2 | -| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 25 | +| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 26 | | `Skeleton` | feedback | yes | yes | inherited-global-root | yes | no | 6 | | `SourceDesignationBadge` | source | yes | yes | inherited-global-root | yes | no | 1 | | `SourceProvenance` | source | yes | yes | inherited-global-root | yes | no | 1 | diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index e299498df4..dbc0563cfd 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -1486,6 +1486,7 @@ "src/components/clinical-dashboard/master-search-header.tsx", "src/components/clinical-dashboard/medication-record-page.tsx", "src/components/clinical-dashboard/mode-action-popup.tsx", + "src/components/clinical-dashboard/patient-details-dock-action.tsx", "src/components/clinical-dashboard/result-filter-control.tsx", "src/components/clinical-dashboard/settings-dialog.tsx", "src/components/differentials/diagnosis-map-panel.tsx", @@ -1514,6 +1515,7 @@ "src/components/clinical-dashboard/master-search-header.tsx", "src/components/clinical-dashboard/medication-record-page.tsx", "src/components/clinical-dashboard/mode-action-popup.tsx", + "src/components/clinical-dashboard/patient-details-dock-action.tsx", "src/components/clinical-dashboard/result-filter-control.tsx", "src/components/clinical-dashboard/settings-dialog.tsx", "src/components/differentials/diagnosis-map-panel.tsx", diff --git a/docs/samd-classification-medication-considerations.md b/docs/samd-classification-medication-considerations.md index dcd1a1035a..002ebbfa7d 100644 --- a/docs/samd-classification-medication-considerations.md +++ b/docs/samd-classification-medication-considerations.md @@ -19,6 +19,45 @@ This is the app's first **patient-specific decision-support surface**: output is tailored to individual patient parameters rather than presenting the same reference content to everyone. +### Scope widened: drug–drug interaction alerting + +A later change extended the same profile with the patient's **current medication +list** and added interaction alerting against it: + +- `PatientProfile.medications` (catalogue slugs, same session-scoped store). +- A generated, reviewable index (`data/medication-interaction-index.json`, built + by `scripts/build-medication-interaction-index.ts` from a curated lexicon) + resolving the catalogue's prose `Key Interactions` rows to catalogue targets. +- `src/lib/medication-interactions.ts`, which matches those rows against the + entered list and returns severity-toned findings. +- Red/amber/green/grey verdict edges on prescribing result rows, and a matched + -interactions block on the medication detail page. + +This is a **material widening** of the question this note is open on. Interaction +checking is a canonical clinical-decision-support function, and unlike the +physiology considerations it produces an alert about a specific _combination_ the +clinician has entered. The reviewer's questions below should be answered for this +surface too, not only for the original considerations feature. + +Mitigations specific to the interaction surface: + +- Green never means "safe". It means "no interaction found among the medications + you entered", and is unreachable whenever any interaction row on that + medication could not be machine-resolved — those degrade to a neutral + "N rows need manual review" state instead (`composeMedicationVerdict`). +- Every alert renders the **verbatim** catalogue row text; the tool never + paraphrases a clinical statement. +- The medication picker accepts catalogue drugs only, so a clinician cannot type + a drug the tool has no data for and read the resulting silence as an all-clear. +- Colour is never the only channel: each verdict also carries an icon and a text + label. + +Residual risk the reviewer should weigh: the lexicon is hand-curated, so a missed +term is a false negative. It is fail-safe by construction (unresolved → grey, not +green) but the resolution rate is not 100% — currently 400 of 523 rows resolve, +and `tests/medication-interaction-lexicon-coverage.test.ts` ratchets that figure +so it cannot silently regress. + ## Why this needs a classification decision Software that provides patient-specific treatment/prescribing recommendations diff --git a/docs/scripts-index.md b/docs/scripts-index.md index aa8d71bded..66cfdf2814 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -1,6 +1,6 @@ # Scripts index -Curated map of `scripts/` (227 files) and the `package.json` script surface (237 entries), +Curated map of `scripts/` (228 files) and the `package.json` script surface (239 entries), grouped by purpose. This is orientation, not an exhaustive per-file listing — the authoritative command list is `package.json`, and `npm run docs:check-scripts` verifies every `npm run ` referenced in docs resolves to a real script. `npm run docs:update` refreshes the exact counts above. diff --git a/docs/search-chrome-behaviour.md b/docs/search-chrome-behaviour.md index 43ba61f38f..b96f9094a2 100644 --- a/docs/search-chrome-behaviour.md +++ b/docs/search-chrome-behaviour.md @@ -580,6 +580,46 @@ Coverage: `tests/header-scroll-hide-contract.test.ts` (wiring), `tests/use-hide- Run `npm run verify:phone-chrome` for phone-chrome work. For executable changes its classifier checks installed/lock parity first, runs focused static contracts and only the browser/PWA owners and route journeys implicated by the changed files, then escalates to `npm run verify:ui` automatically for shared chrome foundations. Documentation-only scopes run only documentation guards. Use `-- --dry-run` to inspect the plan, `-- --files ` for an explicit scope, and `-- --full=always|never` only for a deliberate override. +## Phone dock addon slot (page-owned action above the pill) + +A page may dock **one** action row above the phone search pill. It is not a +floating element: it portals into a slot rendered _inside_ the dock's `
` +(`master-search-header.tsx`), so it inherits the dock's `position: fixed`, +z-index, safe-area padding and scroll-hide transform. There is no bottom-offset +arithmetic and no second scroll listener anywhere in an addon. + +Two claimants exist, and they are mutually exclusive by surface: + +| Addon kind | Slot id | Claimed by | +| ----------------------- | ----------------------------------------- | ----------------------------------------------------------- | +| `differentials-compare` | `differentials-mobile-compare-addon-slot` | Differentials submitted search / `/differentials/diagnoses` | +| `patient-details` | `patient-details-addon-slot` | Prescribing submitted search (dashboard-owned) | + +Rules: + +- **One addon at a time.** `data-footer-addon` is a single attribute value, and + the backdrop scrim height, the hide-transform overshoot and the content reserve + all key off it. Register a new kind in `PhoneDockAddonKind` + (`src/lib/mode-home-composer.ts`) and prove exclusivity in + `tests/phone-dock-addon-contract.test.ts`. +- **Four numbers move together** for every kind: the `--phone-dock--clearance` + and `-compact-clearance` tokens, the matching reserve constant and resolver + branches in `mobile-composer-reserve.ts`, the two backdrop scrim heights, and the + `[data-scroll-hidden="true"]` transform overshoot that stops a subpixel strip + peeping at the viewport edge. +- **Only claim the addon where the pill actually mounts.** The reserve inflates on + the claim, not on the render, so claiming a route whose component never mounts + opens a blank band at the bottom. `/medications` is a standalone mode home with + the composer in the hero and no dock at all; `/medications/[slug]` already opens + the patient sheet from its own nav header, so neither claims the addon. +- **Gate the portal at 639px**, matching `.phone-footer-layer`'s `sm:fixed`. The two + Compare bars gate at 1023px, which between 640–1023px portals into a slot on a + form that is not fixed. Do not copy that. + +Coverage: `tests/phone-dock-addon-contract.test.ts` (registry, exclusivity, CSS/TS +value parity), `tests/patient-details-dock-action.dom.test.tsx` (portal target, +breakpoint, sheet wiring). + ## Change checklist Before changing search bar behaviour: diff --git a/package.json b/package.json index ca12e453b4..1cc1efc038 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,8 @@ "differentials:seed": "node scripts/run-tsx.mjs scripts/seed-differential-records.ts", "medications:import": "node scripts/run-tsx.mjs scripts/import-medications-export.ts", "medications:seed": "node scripts/run-tsx.mjs scripts/seed-medication-records.ts", + "medications:interactions": "node scripts/run-tsx.mjs scripts/build-medication-interaction-index.ts", + "check:medication-interactions": "node scripts/run-tsx.mjs scripts/build-medication-interaction-index.ts --check", "reindex": "node scripts/run-tsx.mjs scripts/reindex.ts", "reindex:health": "node scripts/run-tsx.mjs scripts/reindex-health.ts", "ingestion:autopilot": "node scripts/run-tsx.mjs scripts/ingestion-autopilot.ts", diff --git a/scripts/build-medication-interaction-index.ts b/scripts/build-medication-interaction-index.ts new file mode 100644 index 0000000000..f657fd54ed --- /dev/null +++ b/scripts/build-medication-interaction-index.ts @@ -0,0 +1,312 @@ +// Builds `data/medication-interaction-index.json` from the medication catalogue +// snapshot plus the curated lexicon in `src/lib/medication-interaction-lexicon.ts`. +// +// Run with `npm run medications:interactions`. Pass `--check` to verify the +// committed artefact matches what the current snapshot + lexicon produce (used +// by `npm run check:medication-interactions` so the two cannot drift apart). +// +// WHY A BUILD STEP AT ALL +// The catalogue's `Key Interactions` rows are prose. Resolving them at runtime +// would mean shipping the parser and the lexicon to the browser and re-running +// 523 regex passes per render. More importantly it would make the resolution +// invisible: a generated artefact can be diffed, reviewed, and pinned by a test, +// which is what lets a clinician trust the colour on a result row. +// +// WHAT "UNRESOLVED" MEANS AND WHY IT IS EMITTED, NOT SWALLOWED +// A row whose counterparty cannot be resolved to catalogue targets is recorded +// with `resolved: false`. The UI turns any medication carrying such a row grey +// ("N rows need manual review") rather than green. Dropping these rows would +// manufacture a false all-clear, which is the single most dangerous failure +// this feature can have. + +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +import { + INTERACTION_LEXICON, + LEXICON_SURFACES_BY_LENGTH, + selectCatalogueSlugs, + type LexiconTerm, +} from "../src/lib/medication-interaction-lexicon"; +import type { MedicationRecord } from "../src/lib/medications"; + +const SNAPSHOT_PATH = "data/medications-snapshot.json"; +const OUTPUT_PATH = "data/medication-interaction-index.json"; + +/** Severity tokens seen in the catalogue, normalised to a closed vocabulary. */ +const SEVERITY_BY_TOKEN: Record = { + CRITICAL: "critical", + HIGH: "high", + MODERATE: "moderate", + CAUTION: "caution", + LOW: "low", + NONE: "none", + SAFE: "safe", + BENEFICIAL: "beneficial", +}; + +const SEVERITY_PATTERN = /^\s*([A-Z][A-Z \-/]{2,30}?)\s*[—–-]\s*([\s\S]*)$/; + +type IndexCounterparty = { slug: string; name: string; via: string }; + +/** + * The emitted row is deliberately lean: no `note`, no counterparty display + * names. Both are already available to every consumer — the verbatim text lives + * on the `MedicationRecord` the page has already loaded (addressed by + * `rowIndex`), and names come from the top-level `names` map. Duplicating them + * per row took the artefact to 661 KB, which is a bundle regression for a file + * that ships to the browser. + */ +type IndexRow = { + rowKey: string; + rowIndex: number; + severity: string; + /** Catalogue slugs this row's counterparty terms resolved to. */ + counterparties: string[]; + /** Lexicon term ids that fired, for auditability. */ + termIds: string[]; + resolved: boolean; +}; + +type IndexEntry = { rows: IndexRow[]; unresolvedRowCount: number }; + +type InteractionIndex = { + version: number; + generatedFrom: string; + sourceRowCount: number; + stats: { + resolvedRows: number; + unresolvedRows: number; + rowsWithCatalogueTarget: number; + medicationsWithUnresolvedRows: number; + }; + /** slug → display name, so a counterparty can be named without the snapshot. */ + names: Record; + bySlug: Record; +}; + +/** Dosage-form and route qualifiers that appear in catalogue names but not in prose. */ +const DOSAGE_FORM_TOKEN = + /^(ir|sr|xr|mr|cr|odt|lai|im|iv|sl|po|pr|depot|patch|wafer|nasal|mouth|spray|inhaler|topical|cream|gel|drops|syrup|liquid|injection|infusion|oral|buccal|sublingual|transdermal|suppository|tablet|capsule|lozenge|pamoate)$/i; + +/** + * "Tramadol IR" → "Tramadol"; "Oxycodone SR / MR" → "Oxycodone"; + * "Nicotine mouth spray" → "Nicotine". Strips trailing form tokens and the + * separators between them, iteratively, leaving the drug substance. + */ +function stripDosageForm(name: string): string { + const tokens = name.split(/\s+/).filter(Boolean); + while (tokens.length > 1) { + const last = tokens[tokens.length - 1]; + if (last === "/" || last === "-" || DOSAGE_FORM_TOKEN.test(last)) { + tokens.pop(); + continue; + } + break; + } + return tokens + .join(" ") + .replace(/[\s/-]+$/, "") + .trim(); +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +/** + * Whole-word-ish match. Plain `\b` fails on surfaces ending in a non-word + * character (e.g. "p-gp"), so the boundaries are asserted explicitly against + * characters that could continue a word. + */ +function mentions(haystack: string, needle: string): boolean { + const pattern = new RegExp(`(^|[^A-Za-z0-9])${escapeRegExp(needle)}(?![A-Za-z0-9])`, "i"); + return pattern.test(haystack); +} + +/** + * The counterparty segment is the FIRST sentence after the severity token. + * + * This is deliberate and load-bearing. Scanning the whole row over-matches: + * "HIGH — NSAIDs and Aspirin. SSRIs deplete platelet serotonin … Cover with PPI + * if combined." mentions PPIs only as the mitigation, and a patient on a PPI + * must not light up as HIGH-interacting. Restricting to the first sentence + * keeps the counterparty and drops the advice. + */ +function counterpartySegment(value: string): { severityToken: string | null; segment: string } { + const match = SEVERITY_PATTERN.exec(value); + const severityToken = match ? match[1].trim() : null; + const body = (match ? match[2] : value).replace(/\*\*/g, "").trim(); + const [first = ""] = body.split(/(?<=[.;])\s+/); + return { severityToken, segment: first }; +} + +function main(): void { + const checkOnly = process.argv.includes("--check"); + const snapshotPath = path.resolve(process.cwd(), SNAPSHOT_PATH); + const records = JSON.parse(readFileSync(snapshotPath, "utf8")) as MedicationRecord[]; + + // Pre-resolve every catalogue term once. + const termSlugs = new Map(); + for (const term of INTERACTION_LEXICON) { + if (term.kind === "catalogue" && term.select) { + termSlugs.set(term.id, selectCatalogueSlugs(term.select, records)); + } + } + + // Surface forms for individual catalogue drugs, longest-first so + // "Aripiprazole LAI" wins over "Aripiprazole". + // + // Catalogue names carry formulation qualifiers the prose never uses: the + // catalogue says "Tramadol IR" and "Oxycodone SR / MR" where an interaction + // row just says "Tramadol" or "Oxycodone". Indexing only the full name silently + // loses those matches — a false-negative class, i.e. exactly the direction that + // produces a wrong green. So each name also contributes its base form with the + // trailing dosage-form tokens stripped. + const drugSurfaces = records + .flatMap((record) => { + const parts = record.name + .split("/") + .map((part) => part.trim()) + .filter((part) => part.length >= 4); + const surfaces = new Set(parts); + for (const part of parts) surfaces.add(stripDosageForm(part)); + surfaces.add(stripDosageForm(record.name)); + return Array.from(surfaces) + .filter((surface) => surface.length >= 4) + .map((surface) => ({ surface, slug: record.slug, name: record.name })); + }) + .sort((a, b) => b.surface.length - a.surface.length); + + const nameBySlug = new Map(records.map((record) => [record.slug, record.name])); + const bySlug: Record = {}; + let sourceRowCount = 0; + let resolvedRows = 0; + let unresolvedRows = 0; + let rowsWithCatalogueTarget = 0; + + for (const record of records) { + const rows: IndexRow[] = []; + + for (const section of record.sections ?? []) { + if (section.type !== "inter") continue; + + section.rows?.forEach((row, rowIndex) => { + sourceRowCount += 1; + const value = row.val ?? ""; + const { severityToken, segment } = counterpartySegment(value); + const severity = severityToken ? (SEVERITY_BY_TOKEN[severityToken] ?? "unknown") : "unknown"; + + const counterparties = new Map(); + const termIds = new Set(); + let sawClassifiableTerm = false; + + // 1. Lexicon terms, longest surface first. + const consumed: string[] = []; + for (const { surface, term } of LEXICON_SURFACES_BY_LENGTH) { + if (!mentions(segment, surface)) continue; + // A longer surface already covering this text wins ("thiazide + // diuretics" must not also register the generic "diuretics"). + if (consumed.some((taken) => taken.includes(surface))) continue; + consumed.push(surface); + termIds.add(term.id); + sawClassifiableTerm = true; + addTermTargets(term, counterparties, record.slug); + } + + // 2. Individual catalogue drugs named outright. + for (const { surface, slug } of drugSurfaces) { + if (slug === record.slug) continue; // never self-interact + if (!mentions(segment, surface)) continue; + sawClassifiableTerm = true; + if (!counterparties.has(slug)) { + counterparties.set(slug, { slug, name: nameBySlug.get(slug) ?? slug, via: surface }); + } + } + + // A row is resolved when we could classify what it refers to — either + // catalogue targets, or a known external/non-drug entity. A bare + // mechanism class ("CYP3A4 inhibitors") with no named example is NOT + // resolved: it could include catalogue drugs we have not enumerated. + const onlyMechanism = + termIds.size > 0 && + counterparties.size === 0 && + Array.from(termIds).every((id) => termKind(id) === "mechanism"); + const resolved = sawClassifiableTerm && !onlyMechanism; + + if (resolved) resolvedRows += 1; + else unresolvedRows += 1; + if (counterparties.size > 0) rowsWithCatalogueTarget += 1; + + rows.push({ + rowKey: row.key, + rowIndex, + severity, + counterparties: Array.from(counterparties.keys()).sort(), + termIds: Array.from(termIds).sort(), + resolved, + }); + }); + } + + if (rows.length > 0) { + bySlug[record.slug] = { rows, unresolvedRowCount: rows.filter((row) => !row.resolved).length }; + } + } + + function termKind(id: string): string { + return INTERACTION_LEXICON.find((term) => term.id === id)?.kind ?? "mechanism"; + } + + function addTermTargets(term: LexiconTerm, into: Map, sourceSlug: string): void { + if (term.kind !== "catalogue") return; + for (const slug of termSlugs.get(term.id) ?? []) { + // A drug is never its own counterparty. Ibuprofen's own row says "NSAIDs", + // and the class resolves to every NSAID including ibuprofen — which would + // make the drug flag itself the moment the patient is taking it. + if (slug === sourceSlug || into.has(slug)) continue; + into.set(slug, { slug, name: nameBySlug.get(slug) ?? slug, via: term.id }); + } + } + + const index: InteractionIndex = { + version: 1, + generatedFrom: SNAPSHOT_PATH, + sourceRowCount, + stats: { + resolvedRows, + unresolvedRows, + rowsWithCatalogueTarget, + medicationsWithUnresolvedRows: Object.values(bySlug).filter((entry) => entry.unresolvedRowCount > 0).length, + }, + names: Object.fromEntries( + records.map((record) => [record.slug, record.name] as const).sort(([a], [b]) => a.localeCompare(b)), + ), + bySlug, + }; + + const serialised = `${JSON.stringify(index, null, 2)}\n`; + const outputPath = path.resolve(process.cwd(), OUTPUT_PATH); + + if (checkOnly) { + const current = readFileSync(outputPath, "utf8"); + if (current !== serialised) { + console.error( + `[medication-interactions] ${OUTPUT_PATH} is stale.\n` + + "Run `npm run medications:interactions` and commit the result.", + ); + process.exit(1); + } + console.log(`[medication-interactions] ${OUTPUT_PATH} is up to date (${sourceRowCount} rows).`); + return; + } + + writeFileSync(outputPath, serialised); + console.log( + `[medication-interactions] wrote ${OUTPUT_PATH}: ${sourceRowCount} rows, ` + + `${resolvedRows} resolved, ${unresolvedRows} unresolved, ` + + `${index.stats.medicationsWithUnresolvedRows} medications carry an unresolved row.`, + ); +} + +main(); diff --git a/src/app/globals.css b/src/app/globals.css index 482b5ba578..b2bfe80a75 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -481,6 +481,10 @@ --phone-dock-hidden-pad: 0rem; --phone-dock-differentials-compare-clearance: 12.5rem; --phone-dock-differentials-compare-compact-clearance: 12.25rem; + /* Patient details is a single ~3rem row + 0.5rem gap above the pill, so it + clears roughly 3.5rem less than the Compare bar. */ + --phone-dock-patient-details-clearance: 9rem; + --phone-dock-patient-details-compact-clearance: 8.75rem; /* Radius tokens are the single source of truth in @theme above (they also generate the rounded-* utilities); do not redefine them here or var() and the utilities drift apart. */ @@ -1128,6 +1132,83 @@ summary::-webkit-details-marker { font-weight: 800; } +/* Medication surfaces: Patient details sits above the search pill in the dock, + the same in-flow trick as the Compare bar so scroll-hide moves both together. + Deliberately one row shorter and quieter than Compare: Compare is the primary + call to action on its surface, this is a persistent context control. */ +.patient-details-fab { + pointer-events: none; + position: relative; + z-index: 10; + display: flex; + width: 100%; + justify-content: center; + margin-bottom: 0.5rem; +} + +.patient-details-fab__button { + pointer-events: auto; + display: inline-flex; + /* 3rem is --spacing-tap, the production tap floor. Never reduce below it. */ + min-height: 3rem; + max-width: min(100%, 36rem); + align-items: center; + gap: 0.5rem; + border-radius: 9999px; + border: 1px solid var(--border-strong); + background: color-mix(in srgb, var(--surface) 92%, transparent); + padding: 0.3125rem 0.5rem 0.3125rem 1rem; + color: var(--text-heading); + font-size: 0.9rem; + font-weight: 800; + letter-spacing: -0.01em; + box-shadow: + 0 14px 34px rgb(0 0 0 / 20%), + 0 0 0 1px color-mix(in srgb, var(--text-heading) 6%, transparent) inset; + touch-action: manipulation; + transition: + transform 160ms var(--ease-spring), + background-color 160ms var(--ease-out-soft), + border-color 160ms var(--ease-out-soft), + box-shadow 160ms var(--ease-out-soft); +} + +/* Populated profile: the control becomes the accent so "a patient is loaded" + is legible at a glance without reading the count. */ +.patient-details-fab__button--active { + border-color: color-mix(in srgb, var(--clinical-accent), white 14%); + background: + radial-gradient( + circle at 82% 0%, + color-mix(in srgb, var(--clinical-accent-contrast) 18%, transparent), + transparent 34% + ), + linear-gradient(135deg, color-mix(in srgb, var(--clinical-accent), white 7%), var(--clinical-accent)); + color: var(--clinical-accent-contrast); +} + +.patient-details-fab__button:active { + transform: translateY(1px) scale(0.99); + transition: transform 160ms var(--ease-spring); +} + +.patient-details-fab__button:focus-visible { + outline: 2px solid var(--focus); + outline-offset: 3px; +} + +.patient-details-fab__count { + display: grid; + height: 1.625rem; + min-width: 1.625rem; + place-items: center; + border-radius: 9999px; + background: color-mix(in srgb, var(--clinical-accent-contrast) 20%, transparent); + padding-inline: 0.375rem; + font-size: 0.75rem; + font-weight: 800; +} + /* Bottom-docked composer: hint row + upward dropdown grow above the pill. */ .answer-footer-search-edge { overflow: visible; @@ -1820,6 +1901,31 @@ summary::-webkit-details-marker { box-shadow: inset 3px 0 0 var(--clinical-accent); } + /* The patient verdict owns the card edge, including on the selected row. + These must sit AFTER the [data-selected] rule above: that rule is unlayered + and pins border-color, so a Tailwind border utility loses to it and the top + hit — the one most likely to be prescribed — would render its interaction + warning with a neutral edge. Equal specificity, later in source, wins. */ + .medication-mobile-result[data-verdict="danger"] { + border-color: var(--danger-border); + } + + .medication-mobile-result[data-verdict="warning"] { + border-color: var(--warning-border); + } + + .medication-mobile-result[data-verdict="success"] { + border-color: var(--success-border); + } + + .medication-mobile-result[data-verdict="clinical"] { + border-color: color-mix(in srgb, var(--clinical-accent) 40%, transparent); + } + + .medication-mobile-result[data-verdict="info"] { + border-color: var(--info-border); + } + .medication-mobile-result:focus-visible { outline-offset: -2px; } @@ -2286,6 +2392,17 @@ summary::-webkit-details-marker { height: max(var(--phone-dock-differentials-compare-compact-clearance), calc(var(--safe-area-bottom) + 11rem)); } + /* Medication surfaces: Patient details pill above the search pill in the dock. + Heights track --phone-dock-patient-details-* (same as content reserve). */ + .answer-footer-search-dock[data-footer-addon="patient-details"] .answer-footer-search-backdrop { + height: max(var(--phone-dock-patient-details-clearance), calc(var(--safe-area-bottom) + 7.75rem)); + } + + .answer-footer-search-dock.document-mobile-search-compact[data-footer-addon="patient-details"] + .answer-footer-search-backdrop { + height: max(var(--phone-dock-patient-details-compact-clearance), calc(var(--safe-area-bottom) + 7.5rem)); + } + .document-mobile-search-pill { min-height: 3.6rem; border-color: var(--border-strong); @@ -2348,6 +2465,11 @@ summary::-webkit-details-marker { transform: translateY(calc(100% + 0.75rem + var(--safe-area-bottom))); } + /* Same extra clearance for the Patient details row, scaled to its shorter height. */ + .answer-footer-search-dock.document-mobile-search-edge.answer-footer-search-edge[data-footer-addon="patient-details"][data-scroll-hidden="true"] { + transform: translateY(calc(100% + 0.5rem + var(--safe-area-bottom))); + } + /* Document viewer owns its floating composer (not the shared dock). Lift it with the same --keyboard-height signal so overlay/visual keyboards do not cover the focused input on document detail/source routes. */ diff --git a/src/components/ClinicalDashboard.tsx b/src/components/ClinicalDashboard.tsx index 1ec7998a20..379d3472d3 100644 --- a/src/components/ClinicalDashboard.tsx +++ b/src/components/ClinicalDashboard.tsx @@ -248,6 +248,7 @@ import { DashboardDesktopResultComposerSlot } from "@/components/clinical-dashbo import { desktopPageComposerSlotId, differentialsMobileCompareAddonSlotId, + patientDetailsAddonSlotId, modeHomeDesktopComposerSlotId, } from "@/lib/mode-home-composer"; import { toolCatalogRecords } from "@/lib/tools-catalog"; @@ -3082,6 +3083,9 @@ export function ClinicalDashboard({ ((searchMode === "services" || searchMode === "forms") && !modeSearchSubmitted && !query.trim() && !loading); const differentialsCompareAddonActive = searchMode === "differentials" && modeSearchSubmitted && Boolean(query.trim()); + // Prescribing submitted searches render here (there is no standalone results + // route), so this is where the Patient details pill docks for that mode. + const patientDetailsAddonActive = searchMode === "prescribing" && modeSearchSubmitted && Boolean(query.trim()); // Hidden dock pad must stay at 0rem — Safari toolbar safe-area recreates a blank band. const mobileComposerReserve = resolveMobileComposerReserve( bottomComposerHidden, @@ -3089,6 +3093,7 @@ export function ClinicalDashboard({ searchMode, hasAnswerFollowUps: answerFollowUpSuggestions.length > 0, differentialsCompareAddonActive, + patientDetailsAddonActive, heroOwnsPhoneComposer, }), ); @@ -3374,7 +3379,18 @@ export function ClinicalDashboard({ // maximum screen space (mode homes and result views alike). mobileBottomSearchVariant="compact" mobileBottomSearchAddonSlotId={ - differentialsCompareAddonActive ? differentialsMobileCompareAddonSlotId : undefined + differentialsCompareAddonActive + ? differentialsMobileCompareAddonSlotId + : patientDetailsAddonActive + ? patientDetailsAddonSlotId + : undefined + } + mobileBottomSearchAddonKind={ + differentialsCompareAddonActive + ? "differentials-compare" + : patientDetailsAddonActive + ? "patient-details" + : undefined } desktopHomeComposerSlotId={desktopHomeComposerSlotId} desktopPageComposerSlotId={desktopResultComposerSlotId} diff --git a/src/components/clinical-dashboard/global-search-shell.tsx b/src/components/clinical-dashboard/global-search-shell.tsx index 84247b396a..0ce1e29d21 100644 --- a/src/components/clinical-dashboard/global-search-shell.tsx +++ b/src/components/clinical-dashboard/global-search-shell.tsx @@ -416,6 +416,12 @@ function GlobalStandaloneSearchShellBody({ const differentialsCompareAddonActive = searchMode === "differentials" && (pathname === "/differentials/diagnoses" || (pathname === "/differentials" && hasSubmittedModeSearch)); + // No shell-owned route claims the Patient details dock addon. `/medications` + // is a standalone mode home (composer in the hero, no dock to portal into), + // and `/medications/[slug]` already opens the same sheet from its own nav + // header — a dock pill there would be a second entry point to one + // destination. The pill lives on the dashboard-owned prescribing results + // view; see ClinicalDashboard. // Registry and local decision-support modes own their submitted-search views on their // standalone routes; the shell must not swap them to the dashboard. On the // home route the dashboard always renders, so these exclusions only apply @@ -876,6 +882,7 @@ function GlobalStandaloneSearchShellBody({ mobileBottomSearchAddonSlotId={ differentialsCompareAddonActive ? differentialsMobileCompareAddonSlotId : undefined } + mobileBottomSearchAddonKind={differentialsCompareAddonActive ? "differentials-compare" : undefined} desktopSearchPlacement={desktopSearchPlacement === "hero" && isStandaloneModeHome ? "hero" : "default"} showPhoneSuggestionTickerOnHome={isStandaloneModeHome || pathname === "/"} searchComposerVisible={shouldShowSearchComposer} diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index e87b6c29a8..b18ec34d8b 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -78,6 +78,7 @@ import { isDesktopComposerSlotReady, phoneHeaderCollapseAddonSlotId, setModeHomeComposerReservePending, + type PhoneDockAddonKind, } from "@/lib/mode-home-composer"; import { resolveScrollBehavior } from "@/lib/scroll-behavior"; import type { CommandSurfacePlacement } from "@/lib/search-command-surface"; @@ -200,6 +201,7 @@ export function MasterSearchHeader({ desktopPageComposerSlotId, heroComposerBreakpoint = "all", mobileBottomSearchAddonSlotId, + mobileBottomSearchAddonKind, hideOnScroll, onBottomComposerHiddenChange, showDesktopNewChat = true, @@ -268,6 +270,8 @@ export function MasterSearchHeader({ heroComposerBreakpoint?: "all" | "sm-up"; /** Mobile/tablet slot rendered above the search pill for page-specific composer addons. */ mobileBottomSearchAddonSlotId?: string; + /** Which page-owned action occupies the dock addon slot. One at a time. */ + mobileBottomSearchAddonKind?: PhoneDockAddonKind; /** Phone-only hide-on-scroll for the universal header and bottom search dock. * "overlay" translates the sticky header away (host scrolls the document, * content already flows beneath); "collapse" also releases the header's @@ -1718,7 +1722,11 @@ export function MasterSearchHeader({ } }} data-footer-variant={usesPhoneFooterDock ? (usesCompactMobileBottomStyle ? "compact" : "default") : undefined} - data-footer-addon={usesPhoneFooterDock && mobileBottomSearchAddonSlotId ? "differentials-compare" : undefined} + data-footer-addon={ + usesPhoneFooterDock && mobileBottomSearchAddonSlotId + ? (mobileBottomSearchAddonKind ?? "differentials-compare") + : undefined + } data-command-open={ // Phones never show the command dropdown, so the dock scrim must not // grow for it — gate the open attribute to widths that can display it. diff --git a/src/components/clinical-dashboard/medication-considerations.tsx b/src/components/clinical-dashboard/medication-considerations.tsx index d72c1da6bb..7320ecf1f7 100644 --- a/src/components/clinical-dashboard/medication-considerations.tsx +++ b/src/components/clinical-dashboard/medication-considerations.tsx @@ -1,10 +1,11 @@ "use client"; -import { ClipboardList } from "lucide-react"; +import { ClipboardList, Pill } from "lucide-react"; import { useMemo } from "react"; import { BadgeCluster, type ClinicalBadgeItem } from "@/components/clinical-dashboard/clinical-badge"; import { usePatientProfile } from "@/components/clinical-dashboard/patient-profile-context"; +import { evaluateMedicationInteractions, type MedicationVerdict } from "@/lib/medication-interactions"; import { evaluatePatientAlerts, noticeToneForSemanticTone, @@ -14,6 +15,53 @@ import type { MedicationRecord } from "@/lib/medications"; import type { SemanticTone } from "@/lib/semantic-tone"; import { cn, InlineNotice } from "@/components/ui-primitives"; +/** + * Verdict edge for the DESKTOP results table rows, which sit in a divided list + * and already own their edge with an inset `ring-*` for selection. + * + * The phone cards do not use this. Their edge is set in `globals.css` via + * `.medication-mobile-result[data-verdict="…"]`, because the existing + * `[data-selected="true"]` rule there is unlayered and pins `border-color` — + * a Tailwind border utility loses to it on exactly the top hit. Two mechanisms, + * one per surface, each matching what that surface already used. + */ +export function medicationVerdictRingClass(tone: SemanticTone): string | null { + const byTone: Record = { + danger: "ring-1 ring-inset ring-[color:var(--danger-border)]", + warning: "ring-1 ring-inset ring-[color:var(--warning-border)]", + success: "ring-1 ring-inset ring-[color:var(--success-border)]", + clinical: "ring-1 ring-inset ring-[color:var(--clinical-accent)]/40", + info: "ring-1 ring-inset ring-[color:var(--info-border)]", + // Neutral keeps the list's own divider as its edge — see the border variant. + neutral: null, + }; + return byTone[tone]; +} + +/** + * One badge summarising the composed verdict for a result row. + * + * Returns null when there is nothing to say — no patient profile entered, or an + * unremarkable medication with no findings and nothing unread. + */ +export function verdictSummaryBadge(verdict: MedicationVerdict): ClinicalBadgeItem | null { + const findings = verdict.interactionCount + verdict.considerationCount; + if (findings > 0) { + const parts: string[] = []; + if (verdict.interactionCount > 0) { + parts.push(`${verdict.interactionCount} interaction${verdict.interactionCount === 1 ? "" : "s"}`); + } + if (verdict.considerationCount > 0) { + parts.push(`${verdict.considerationCount} alert${verdict.considerationCount === 1 ? "" : "s"}`); + } + return { id: "patient-verdict", label: parts.join(" · "), tone: verdict.tone }; + } + if (verdict.incomplete) { + return { id: "patient-verdict", label: "Needs manual review", tone: "neutral" }; + } + return { id: "patient-verdict", label: "No interaction found", tone: "success" }; +} + /** Badge for a result row summarising how many considerations apply. */ export function considerationSummaryBadge(count: number, highestTone: SemanticTone | null): ClinicalBadgeItem | null { if (!count || !highestTone) return null; @@ -91,6 +139,88 @@ export function MedicationConsiderations({ record, className }: { record: Medica Enter {result.unassessed.join(", ")} to fully assess this medication’s contraindications. ) : null} + + + + ); +} + +/** + * Interactions between this medication and the patient's current medication list. + * + * Three branches, mirroring the considerations block above — and a fourth that + * only this surface has: when the catalogue carries interaction rows whose + * counterparty could not be machine-resolved, that is stated plainly instead of + * being folded into the all-clear. "We found nothing" and "we could not read + * some of it" are different claims, and only the first is reassuring. + */ +function MedicationInteractionBlock({ record }: { record: MedicationRecord }) { + const { profile } = usePatientProfile(); + const result = useMemo( + () => evaluateMedicationInteractions(record.slug, profile.medications ?? [], record), + [record, profile.medications], + ); + + const hasList = (profile.medications ?? []).length > 0; + const medicationCount = profile.medications?.length ?? 0; + + return ( +
+
+
+ + {!hasList ? ( +
+ Add the patient’s current medications to cross-check them against this drug’s{" "} + {result.totalRowCount} catalogued interaction {result.totalRowCount === 1 ? "row" : "rows"}. +
+ ) : result.interactions.length === 0 ? ( + 0 ? "neutral" : "success"}> + No interaction found between this medication and the {medicationCount}{" "} + {medicationCount === 1 ? "medication" : "medications"} entered. Always confirm against source. + + ) : ( +
+ {result.interactions.map((interaction) => ( + +
+ + {/* Verbatim catalogue text — never a paraphrase. */} + {interaction.note ? ( +

{interaction.note}

+ ) : null} +
+
+ ))} +
+ )} + + {result.unresolvedRowCount > 0 ? ( + + {result.unresolvedRowCount} of this medication’s {result.totalRowCount} interaction{" "} + {result.totalRowCount === 1 ? "row" : "rows"} could not be matched automatically — review the Key Interactions + section below manually. This medication is not shown as clear. + + ) : null}
); } diff --git a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx index eb6e8d0c27..74076df12d 100644 --- a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx +++ b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx @@ -28,10 +28,19 @@ import { resultFilterGroup, } from "@/components/clinical-dashboard/result-filter-control"; import { UniversalSearchAlsoMatches } from "@/components/clinical-dashboard/universal-search-also-matches"; -import { considerationSummaryBadge } from "@/components/clinical-dashboard/medication-considerations"; +import { + medicationVerdictRingClass, + verdictSummaryBadge, +} from "@/components/clinical-dashboard/medication-considerations"; import { usePatientProfile } from "@/components/clinical-dashboard/patient-profile-context"; +import { PatientDetailsDockAction } from "@/components/clinical-dashboard/patient-details-dock-action"; import { PatientProfilePanel } from "@/components/clinical-dashboard/patient-profile-panel"; import { useMedicationCatalog } from "@/components/clinical-dashboard/use-medication-catalog"; +import { + composeMedicationVerdict, + evaluateMedicationInteractions, + type MedicationVerdict, +} from "@/lib/medication-interactions"; import { evaluatePatientAlerts } from "@/lib/medication-patient-alerts"; import { BadgeCluster, @@ -80,6 +89,8 @@ type MedicationRow = { badges: ClinicalBadgeItem[]; /** Per-medication (drug-class) identity accent hex, for a subtle icon tint. */ accent?: string; + /** Composed patient verdict; absent when no patient profile is entered. */ + verdict?: MedicationVerdict; }; type MedicationResultFilter = "best" | "indication" | "safety" | "monitoring"; @@ -370,12 +381,24 @@ function MedicationResults({ const toRow = (result: MedicationResult, medication?: MedicationRecord): MedicationRow => { const badges = medication ? medicationIdentityBadges(medication, governance?.[medication.slug]) : []; const accent = medication?.accent; - // Prepend a per-patient alert badge so the highest-severity consideration + // Prepend a per-patient verdict badge so the highest-severity signal // surfaces first in the row's badge cluster (priority-sorted by tone). + // The verdict folds BOTH engines together — physiology considerations and + // interactions against the entered medication list — and degrades a + // would-be green to grey whenever either engine left something unassessed. if (medication && !profileEmpty) { const alerts = evaluatePatientAlerts(medication, profile); - const alertBadge = considerationSummaryBadge(alerts.considerations.length, alerts.highestTone); - if (alertBadge) return { result, badges: [alertBadge, ...badges], accent }; + const interactions = evaluateMedicationInteractions(medication.slug, profile.medications ?? [], medication); + const verdict = composeMedicationVerdict({ + considerationTone: alerts.highestTone, + considerationCount: alerts.considerations.length, + unassessedCount: alerts.unassessed.length, + interactionTone: interactions.highestTone, + interactionCount: interactions.interactions.length, + unresolvedRowCount: interactions.unresolvedRowCount, + }); + const verdictBadge = verdictSummaryBadge(verdict); + return { result, badges: verdictBadge ? [verdictBadge, ...badges] : badges, accent, verdict }; } return { result, badges, accent }; }; @@ -487,7 +510,15 @@ function MedicationResults({ footerNote={`${resultCount} showing`} /> - + {/* Phone gets the docked pill + sheet instead of the in-flow strip: two + copies of one form would duplicate every `data-testid` while the sheet + is open, and the strip is exactly the list space the pill exists to + give back. Above the phone breakpoint the pill does not render, so the + strip is the only entry point and must stay. */} + + {/* Renders only where the phone dock exposes its addon slot (the submitted + prescribing view); a no-op elsewhere and above the phone breakpoint. */} + {/* The error branch moved into the band's fault panel, which carries the same message and announces it once. Loading copy stays here. */} @@ -539,13 +570,21 @@ function MedicationResults({ {rows.map((row, index) => { const result = row.result; const selected = index === 0 && Boolean(query.trim()); + // The verdict ring replaces the selected-row accent ring rather + // than stacking with it: one edge mechanism, one owner, and safety + // outranks "this is the top hit". + const verdictRing = row.verdict ? medicationVerdictRingClass(row.verdict.tone) : null; const rowClassName = cn( "group grid w-full grid-cols-[minmax(16rem,1.15fr)_minmax(6.5rem,0.42fr)_minmax(8rem,0.48fr)_minmax(16rem,1fr)_2rem] items-center gap-2.5 px-4 py-2.5 text-left transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-inset focus-visible:outline-[color:var(--focus)]", selected - ? "bg-[color:var(--clinical-accent-soft)]/35 shadow-[var(--shadow-rail-active)] ring-1 ring-inset ring-[color:var(--clinical-accent)]/35" - : result.href - ? "hover:bg-[color:var(--surface-subtle)]" - : "cursor-default opacity-80", + ? cn( + "bg-[color:var(--clinical-accent-soft)]/35 shadow-[var(--shadow-rail-active)]", + verdictRing ?? "ring-1 ring-inset ring-[color:var(--clinical-accent)]/35", + ) + : cn( + result.href ? "hover:bg-[color:var(--surface-subtle)]" : "cursor-default opacity-80", + verdictRing, + ), ); const rowContent = ( <> @@ -677,6 +716,7 @@ function MedicationResults({ href={result.href} data-testid={`medication-result-${result.id}-phone`} data-selected={selected ? "true" : "false"} + data-verdict={row.verdict?.tone} className={cardClassName} > {cardContent} @@ -689,6 +729,7 @@ function MedicationResults({ key={result.id} data-testid={`medication-result-${result.id}-phone`} data-selected={selected ? "true" : "false"} + data-verdict={row.verdict?.tone} className={cardClassName} > {cardContent} diff --git a/src/components/clinical-dashboard/mobile-composer-reserve.ts b/src/components/clinical-dashboard/mobile-composer-reserve.ts index 17fb59d61a..037b21780a 100644 --- a/src/components/clinical-dashboard/mobile-composer-reserve.ts +++ b/src/components/clinical-dashboard/mobile-composer-reserve.ts @@ -27,6 +27,13 @@ export const mobileComposerIdleReserve = "2rem"; export const mobileComposerDifferentialsCompareReserve = "calc(12.5rem + var(--safe-area-bottom) + var(--keyboard-height, 0px))"; +/** + * Medication surfaces: Patient details pill + compact search pill. + * Keep the rem figure equal to --phone-dock-patient-details-clearance in + * globals.css; tests/mobile-composer-reserve.test.ts pins the pair. + */ +export const mobileComposerPatientDetailsReserve = "calc(9rem + var(--safe-area-bottom) + var(--keyboard-height, 0px))"; + // Every phone dock is the compact single-row pill (mode homes and result views // alike); only the answer dock with a follow-up chip row is taller. The answer // values are derived from the dock constants so the pairs cannot silently @@ -41,6 +48,7 @@ export const mobileComposerVisibleReserve = { dashboardAnswer: dashboardCompactSingleRowReserve, dashboardDock: dashboardCompactSingleRowReserve, differentialsCompare: mobileComposerDifferentialsCompareReserve, + patientDetails: mobileComposerPatientDetailsReserve, } as const; export function resolveMobileComposerReserve(bottomComposerHidden: boolean, visibleReserve: string): string { @@ -81,6 +89,7 @@ export function resolveDashboardVisibleMobileComposerReserve(input: { searchMode: string; hasAnswerFollowUps: boolean; differentialsCompareAddonActive: boolean; + patientDetailsAddonActive?: boolean; /** Hero owns the phone composer (no fixed bottom dock) — match shell idle pad. */ heroOwnsPhoneComposer?: boolean; }): string { @@ -98,6 +107,9 @@ export function resolveDashboardVisibleMobileComposerReserve(input: { if (input.differentialsCompareAddonActive) { return mobileComposerVisibleReserve.differentialsCompare; } + if (input.patientDetailsAddonActive) { + return mobileComposerVisibleReserve.patientDetails; + } return mobileComposerVisibleReserve.dashboardDock; } @@ -109,6 +121,7 @@ export function resolveShellVisibleMobileComposerReserve(input: { isStandaloneModeHome: boolean; searchMode: string; differentialsCompareAddonActive: boolean; + patientDetailsAddonActive?: boolean; }): string { if (!input.shouldShowSearchComposer) { // Page-owned composers (DocumentViewer, Calculators) manage their own dock @@ -123,5 +136,6 @@ export function resolveShellVisibleMobileComposerReserve(input: { if (input.isStandaloneModeHome) return mobileComposerIdleReserve; if (input.searchMode === "answer") return mobileComposerVisibleReserve.shellAnswer; if (input.differentialsCompareAddonActive) return mobileComposerVisibleReserve.differentialsCompare; + if (input.patientDetailsAddonActive) return mobileComposerVisibleReserve.patientDetails; return mobileComposerVisibleReserve.shellDock; } diff --git a/src/components/clinical-dashboard/patient-details-dock-action.tsx b/src/components/clinical-dashboard/patient-details-dock-action.tsx new file mode 100644 index 0000000000..edd3781d67 --- /dev/null +++ b/src/components/clinical-dashboard/patient-details-dock-action.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { ChevronRight, UserRound } from "lucide-react"; +import { useEffect, useState } from "react"; +import { createPortal } from "react-dom"; + +import { PatientProfilePanel } from "@/components/clinical-dashboard/patient-profile-panel"; +import { usePatientProfile } from "@/components/clinical-dashboard/patient-profile-context"; +import { Sheet } from "@/components/ui/sheet"; +import { cn } from "@/components/ui-primitives"; +import { patientDetailsAddonSlotId } from "@/lib/mode-home-composer"; + +/** + * The phone "Patient details" pill, docked above the search composer on the + * medication and prescribing surfaces. + * + * It is NOT independently fixed-positioned. Like the Differentials Compare bar + * it portals into a slot *inside* the phone dock's form, so it inherits the + * dock's `position: fixed`, z-index, safe-area padding and — the point of the + * exercise — its scroll-hide transform. There is no bottom-offset arithmetic + * here and no second scroll listener; the composer hides, this hides with it. + * + * The media query is 639px to match the dock (`.phone-footer-layer` is + * `sm:fixed`), deliberately NOT the 1023px the two Compare bars use. Their + * wider gate means that between 640px and 1023px they render into a slot on a + * form that is not fixed — a latent bug this component does not copy. + */ +export function PatientDetailsDockAction() { + const { profile, isEmpty } = usePatientProfile(); + const [host, setHost] = useState(null); + const [open, setOpen] = useState(false); + + useEffect(() => { + const phoneMediaQuery = window.matchMedia("(max-width: 639px)"); + const sync = () => { + setHost(phoneMediaQuery.matches ? document.getElementById(patientDetailsAddonSlotId) : null); + }; + sync(); + phoneMediaQuery.addEventListener("change", sync); + // The dock (and therefore the slot) mounts and unmounts with the composer, + // so watch for it rather than assuming it exists on first paint. + const observer = new MutationObserver(sync); + observer.observe(document.body, { childList: true, subtree: true }); + return () => { + phoneMediaQuery.removeEventListener("change", sync); + observer.disconnect(); + }; + }, []); + + // Count the entered dimensions, not just the medications, so the badge means + // "how much context is loaded" rather than "how many drugs". + const enteredCount = countEnteredFields(profile); + + const sheet = ( + setOpen(false)} + title="Patient details" + description="Anonymous physiology and current medications. Cleared when the tab closes." + mobilePlacement="bottom" + testId="patient-details-sheet" + > + + + ); + + if (!host) { + // Above the phone breakpoint the panel is already inline on these surfaces, + // so there is nothing to render — but keep the sheet mounted if it is open + // so a resize mid-edit does not drop the dialog and the focus with it. + return open ? sheet : null; + } + + return ( + <> + {createPortal( +
+ +
, + host, + )} + {sheet} + + ); +} + +/** How many profile dimensions carry a value. Drives the pill's count badge. */ +function countEnteredFields(profile: { + ageYears?: number | null; + egfr?: number | null; + crcl?: number | null; + scr?: number | null; + qtc?: number | null; + hepatic?: string | null; + pregnant?: boolean; + breastfeeding?: boolean; + allergies?: string[]; + medications?: string[]; +}): number { + let count = 0; + for (const value of [profile.ageYears, profile.egfr, profile.crcl, profile.scr, profile.qtc]) { + if (typeof value === "number" && Number.isFinite(value)) count += 1; + } + if (profile.hepatic && profile.hepatic !== "none") count += 1; + if (profile.pregnant) count += 1; + if (profile.breastfeeding) count += 1; + count += profile.allergies?.length ?? 0; + count += profile.medications?.length ?? 0; + return count; +} diff --git a/src/components/clinical-dashboard/patient-profile-context.tsx b/src/components/clinical-dashboard/patient-profile-context.tsx index c1febbd25e..232eab5803 100644 --- a/src/components/clinical-dashboard/patient-profile-context.tsx +++ b/src/components/clinical-dashboard/patient-profile-context.tsx @@ -17,6 +17,7 @@ export type PatientProfileContextValue = { updateField: (key: K, value: PatientProfile[K]) => void; setScrUnit: (unit: ScrUnit) => void; toggleAllergy: (allergy: AllergyClass) => void; + toggleMedication: (slug: string) => void; clear: () => void; isEmpty: boolean; }; @@ -54,13 +55,28 @@ export function PatientProfileProvider({ children }: { children: React.ReactNode writePatientProfile({ ...current, allergies: next }); }, []); + const toggleMedication = useCallback((slug: string) => { + const current = getPatientProfileSnapshot(); + const medications = current.medications ?? []; + const next = medications.includes(slug) ? medications.filter((item) => item !== slug) : [...medications, slug]; + writePatientProfile({ ...current, medications: next }); + }, []); + const clear = useCallback(() => { - writePatientProfile({ ...EMPTY_PATIENT_PROFILE }); + writePatientProfile({ ...EMPTY_PATIENT_PROFILE, medications: [] }); }, []); const value = useMemo( - () => ({ profile, updateField, setScrUnit, toggleAllergy, clear, isEmpty: isProfileEmpty(profile) }), - [profile, updateField, setScrUnit, toggleAllergy, clear], + () => ({ + profile, + updateField, + setScrUnit, + toggleAllergy, + toggleMedication, + clear, + isEmpty: isProfileEmpty(profile), + }), + [profile, updateField, setScrUnit, toggleAllergy, toggleMedication, clear], ); return {children}; diff --git a/src/components/clinical-dashboard/patient-profile-panel.tsx b/src/components/clinical-dashboard/patient-profile-panel.tsx index a420cab262..3d647550fc 100644 --- a/src/components/clinical-dashboard/patient-profile-panel.tsx +++ b/src/components/clinical-dashboard/patient-profile-panel.tsx @@ -1,12 +1,13 @@ "use client"; -import { Eraser, UserRound } from "lucide-react"; -import { useState } from "react"; +import { Eraser, Plus, UserRound, X } from "lucide-react"; +import { useId, useState } from "react"; import { usePatientProfile } from "@/components/clinical-dashboard/patient-profile-context"; import { SegmentedControl } from "@/components/ui/segmented-control"; import { TextField } from "@/components/ui/text-field"; import { cn, fieldLabel, ToggleSwitch } from "@/components/ui-primitives"; +import { catalogueMedicationOptions, medicationDisplayName } from "@/lib/medication-interactions"; import { SCR_UMOL_PER_MGDL } from "@/lib/medication-patient-alerts"; import type { AllergyClass, HepaticSeverity, ScrUnit } from "@/lib/medication-patient-alerts"; import { PATIENT_PROFILE_NUMERIC_BOUNDS, PATIENT_PROFILE_SCR_UMOL_BOUNDS } from "@/lib/patient-profile-storage"; @@ -105,6 +106,99 @@ function NumberField({ ); } +/** + * Search-and-add list of what the patient is currently taking. + * + * Restricted to catalogue medications on purpose: an interaction can only be + * checked against a drug the catalogue actually carries, so accepting free text + * would let a clinician enter "warfarin", see no alert, and read that as an + * all-clear when nothing was ever checked. + */ +function MedicationPicker({ + selected, + onToggle, + resetNonce, +}: { + selected: string[]; + onToggle: (slug: string) => void; + resetNonce: number; +}) { + const [term, setTerm] = useState(""); + const listId = useId(); + + // Clearing the profile should empty the search box too. + const [syncedNonce, setSyncedNonce] = useState(resetNonce); + if (syncedNonce !== resetNonce) { + setSyncedNonce(resetNonce); + setTerm(""); + } + + const options = catalogueMedicationOptions(); + const selectedSet = new Set(selected); + const query = term.trim().toLowerCase(); + const matches = query + ? options.filter((option) => option.name.toLowerCase().includes(query) && !selectedSet.has(option.slug)).slice(0, 8) + : []; + + return ( +
+ Current medications + + {selected.length > 0 ? ( +
    + {selected.map((slug) => ( +
  • + +
  • + ))} +
+ ) : null} + + setTerm(event.target.value)} + placeholder="Search medications to add…" + aria-describedby={listId} + data-testid="patient-medication-search" + /> + +
+ {matches.map((option) => ( + + ))} + {query && matches.length === 0 ? ( +

+ No catalogue match. Only medications in this catalogue can be interaction-checked. +

+ ) : null} +
+
+ ); +} + export function PatientProfilePanel({ variant = "full", defaultOpen, @@ -115,7 +209,7 @@ export function PatientProfilePanel({ defaultOpen?: boolean; className?: string; }) { - const { profile, updateField, setScrUnit, toggleAllergy, clear, isEmpty } = usePatientProfile(); + const { profile, updateField, setScrUnit, toggleAllergy, toggleMedication, clear, isEmpty } = usePatientProfile(); const [open, setOpen] = useState(defaultOpen ?? variant === "full"); // Bumped on Clear to remount the numeric fields, so an out-of-range entry that // is showing a validation message (stored value already null) is reset too. @@ -241,6 +335,8 @@ export function PatientProfilePanel({ /> + +
Allergies
diff --git a/src/lib/medication-interaction-lexicon.ts b/src/lib/medication-interaction-lexicon.ts new file mode 100644 index 0000000000..f007efd60a --- /dev/null +++ b/src/lib/medication-interaction-lexicon.ts @@ -0,0 +1,405 @@ +// Curated counterparty lexicon for the drug-interaction index. +// +// The medication catalogue's `Key Interactions` rows are prose, not structured +// drug pairs: "CRITICAL — MAOIs, Tramadol, Tapentadol, St John's Wort. Massive +// risk of Serotonin Syndrome." Roughly 58% of rows name a catalogue drug +// literally; the rest name a class ("NSAIDs", "CNS depressants"), a mechanism +// ("CYP3A4 inhibitors"), or something that is not a medicine at all ("alcohol", +// "grapefruit juice", "smoking"). +// +// This module is the hand-maintained source of truth that turns those surface +// terms into catalogue targets. It is deliberately NOT derived by substring +// matching, because substring matching is wrong in ways that matter clinically: +// +// - Clozapine's subclass is "SGA / Dibenzodiazepine" and olanzapine's is +// "SGA / Thienobenzodiazepine". Both contain "benzodiazepine"; neither is a +// benzodiazepine. The real benzodiazepines are `class === "Benzodiazepine"`. +// - Naltrexone's subclass is "Opioid Antagonist". It is an opioid *blocker*, +// not an opioid, and must never satisfy the term "opioids". +// - The catalogue spells beta blockers both "Beta Blocker" and "Beta-Blocker". +// +// Every predicate therefore carries an explicit `denySlugs` escape hatch, and +// `tests/medication-interaction-lexicon-coverage.test.ts` pins the two traps +// above by name so a future edit cannot quietly reintroduce them. +// +// Resolution outcomes are three-valued on purpose — see `LexiconTermKind`. + +import type { MedicationRecord } from "@/lib/medications"; + +/** + * How a surface term resolves. + * + * - `catalogue` — resolves to one or more of the 328 catalogue medications, so + * it can genuinely match a patient's entered medication list. + * - `external` — a real drug/class that is deliberately outside this catalogue + * (ketoconazole, St John's Wort, barbiturates). A patient's list is + * catalogue-only by construction, so this can never match — but it is a + * *known* quantity, not a parsing failure, and so does not force the grey + * "needs manual review" verdict. + * - `nonDrug` — not a medicine (alcohol, grapefruit, coffee, smoking, dietary + * timing). Must never produce a patient-medication match. Still rendered as + * static content on the medication page. + * - `mechanism` — a pharmacokinetic class we cannot enumerate from catalogue + * data ("CYP3A4 inhibitors", "P-gp inhibitors"). Deliberately UNRESOLVED: a + * row whose only counterparty is a bare mechanism forces the grey verdict, + * because it could include catalogue drugs we have not enumerated. + */ +export type LexiconTermKind = "catalogue" | "external" | "nonDrug" | "mechanism"; + +export type CatalogueSelector = { + /** Explicit catalogue slugs. */ + slugs?: string[]; + /** Match on `record.class` (exact, case-insensitive). */ + classes?: string[]; + /** Match on `record.subclass` (substring, case-insensitive). */ + subclassIncludes?: string[]; + /** Removed after the selector runs — the trap guard. Always wins. */ + denySlugs?: string[]; +}; + +export type LexiconTerm = { + /** Canonical id, used in the generated index and in test pins. */ + id: string; + /** Surface forms found in the prose. Matched whole-word, case-insensitive. */ + surfaces: string[]; + kind: LexiconTermKind; + /** Required when `kind === "catalogue"`. */ + select?: CatalogueSelector; + /** Why this term is external/nonDrug/mechanism — kept for auditability. */ + note?: string; +}; + +// --------------------------------------------------------------------------- +// Catalogue classes +// --------------------------------------------------------------------------- + +const CATALOGUE_TERMS: LexiconTerm[] = [ + { + id: "benzodiazepines", + surfaces: ["benzodiazepines", "benzodiazepine", "benzos", "benzo"], + kind: "catalogue", + // `class`, NOT a subclass substring — see the Dibenzodiazepine trap above. + select: { classes: ["Benzodiazepine"], denySlugs: ["clozapine", "olanzapine-wafer-odt"] }, + }, + { + id: "opioids", + surfaces: ["opioids", "opioid", "opioid analgesia", "opiates", "full agonists"], + kind: "catalogue", + select: { + subclassIncludes: ["Opioid"], + // Antagonists block opioid receptors; they are not opioids. + denySlugs: ["naltrexone", "naloxone"], + }, + }, + { + id: "ssris", + surfaces: ["ssris", "ssri"], + kind: "catalogue", + select: { subclassIncludes: ["SSRI"] }, + }, + { + id: "snris", + surfaces: ["snris", "snri"], + kind: "catalogue", + select: { subclassIncludes: ["SNRI"] }, + }, + { + id: "maois", + surfaces: ["maois", "maoi", "monoamine oxidase inhibitors"], + kind: "catalogue", + select: { subclassIncludes: ["MAOI"] }, + }, + { + id: "tcas", + surfaces: ["tcas", "tca", "tricyclics", "tricyclic antidepressants", "anticholinergic tcas"], + kind: "catalogue", + select: { slugs: ["amitriptyline", "nortriptyline", "imipramine", "clomipramine", "doxepin", "dothiepin"] }, + }, + { + id: "antipsychotics", + surfaces: ["antipsychotics", "antipsychotic"], + kind: "catalogue", + select: { classes: ["Antipsychotic", "LAI Antipsychotic"] }, + }, + { + id: "nsaids", + surfaces: ["nsaids", "nsaid"], + kind: "catalogue", + select: { subclassIncludes: ["NSAID"] }, + }, + { + id: "beta-blockers", + surfaces: ["beta-blockers", "beta blockers", "beta-blocker", "beta blocker", "non-selective beta-blockers"], + kind: "catalogue", + select: { subclassIncludes: ["Beta Blocker", "Beta-Blocker"] }, + }, + { + id: "acei", + surfaces: ["acei", "aceis", "ace inhibitors", "ace inhibitor"], + kind: "catalogue", + select: { subclassIncludes: ["ACE Inhibitor"] }, + }, + { + id: "arbs", + surfaces: ["arbs", "arb"], + kind: "catalogue", + select: { subclassIncludes: ["ARB"] }, + }, + { + id: "diuretics", + surfaces: ["diuretics", "diuretic"], + kind: "catalogue", + select: { subclassIncludes: ["Diuretic"] }, + }, + { + id: "thiazide-diuretics", + surfaces: ["thiazide diuretics", "thiazides", "thiazide"], + kind: "catalogue", + select: { subclassIncludes: ["Thiazide"] }, + }, + { + id: "loop-diuretics", + surfaces: ["loop diuretics", "loop diuretic"], + kind: "catalogue", + select: { subclassIncludes: ["Loop Diuretic"] }, + }, + { + id: "antihypertensives", + surfaces: ["antihypertensives", "antihypertensive"], + kind: "catalogue", + select: { classes: ["Antihypertensive"] }, + }, + { + id: "anticoagulants", + surfaces: ["anticoagulants", "anticoagulant", "doacs", "doac"], + kind: "catalogue", + select: { classes: ["Anticoagulant"], denySlugs: ["aspirin"] }, + }, + { + id: "antiplatelets", + surfaces: ["antiplatelets", "antiplatelet"], + kind: "catalogue", + select: { subclassIncludes: ["Antiplatelet"] }, + }, + { + id: "macrolides", + surfaces: ["macrolides", "macrolide"], + kind: "catalogue", + select: { subclassIncludes: ["Macrolide"] }, + }, + { + id: "fluoroquinolones", + surfaces: ["fluoroquinolones", "fluoroquinolone", "quinolones"], + kind: "catalogue", + select: { subclassIncludes: ["Fluoroquinolone"] }, + }, + { + id: "aminoglycosides", + surfaces: ["aminoglycosides", "aminoglycoside"], + kind: "catalogue", + select: { subclassIncludes: ["Aminoglycoside"] }, + }, + { + id: "ppis", + surfaces: ["ppis", "ppi", "proton pump inhibitors"], + kind: "catalogue", + select: { subclassIncludes: ["Proton Pump"] }, + }, + { + id: "antacids", + surfaces: ["antacids", "antacid"], + kind: "catalogue", + select: { slugs: ["magnesium-oxide", "calcium-carbonate"] }, + }, + { + id: "statins", + surfaces: ["statins", "statin"], + kind: "catalogue", + select: { subclassIncludes: ["Statin", "HMG"] }, + }, + { + id: "gabapentinoids", + surfaces: ["gabapentinoids", "gabapentinoid"], + kind: "catalogue", + select: { slugs: ["gabapentin", "pregabalin"] }, + }, + { + id: "anticholinergics", + surfaces: ["anticholinergics", "anticholinergic", "atropine-like medicines"], + kind: "catalogue", + select: { subclassIncludes: ["Anticholinergic"] }, + }, + { + id: "antihistamines", + surfaces: ["antihistamines", "antihistamine"], + kind: "catalogue", + select: { subclassIncludes: ["Antihistamine"] }, + }, + { + id: "corticosteroids", + surfaces: ["corticosteroids", "corticosteroid", "steroids", "steroid"], + kind: "catalogue", + select: { classes: ["Steroid"] }, + }, + { + id: "z-drugs", + surfaces: ["z-drugs", "z drugs", "zolpidem-type hypnotics"], + kind: "catalogue", + select: { subclassIncludes: ["Z-Drug"] }, + }, +]; + +// --------------------------------------------------------------------------- +// Non-catalogue terms +// --------------------------------------------------------------------------- + +const NON_CATALOGUE_TERMS: LexiconTerm[] = [ + // --- Not medicines. Never a patient-medication match. --- + { + id: "alcohol", + surfaces: ["alcohol", "alcohol-containing preparations", "ethanol"], + kind: "nonDrug", + note: "Substance use, not a prescribable catalogue entry.", + }, + { + id: "grapefruit", + surfaces: ["grapefruit", "grapefruit juice"], + kind: "nonDrug", + note: "Dietary CYP3A4 inhibitor.", + }, + { + id: "acidic-drinks", + surfaces: ["acidic drinks", "coffee", "juice", "soft drinks", "cola"], + kind: "nonDrug", + note: "Buccal/oral absorption timing, not a drug interaction.", + }, + { + id: "smoking", + surfaces: ["smoking", "cigarette smoke", "tobacco smoke"], + kind: "nonDrug", + note: "CYP1A2 induction by polycyclic aromatic hydrocarbons, not by nicotine.", + }, + { + id: "food", + surfaces: ["food", "dairy", "milk", "high-fat meals", "tyramine"], + kind: "nonDrug", + note: "Dietary.", + }, + + // --- Real drugs deliberately outside this catalogue. --- + { + id: "st-johns-wort", + surfaces: ["st john's wort", "st johns wort", "hypericum"], + kind: "external", + note: "Herbal CYP3A4 inducer; not stocked in the catalogue.", + }, + { + id: "azole-antifungals", + surfaces: ["ketoconazole", "azoles", "azole antifungals"], + kind: "external", + note: "Ketoconazole is not in the catalogue; fluconazole/itraconazole resolve by name.", + }, + { + id: "barbiturates", + surfaces: ["barbiturates", "barbiturate", "phenobarbital", "phenobarbitone"], + kind: "external", + note: "Not stocked in the catalogue.", + }, + { + id: "antiretrovirals", + surfaces: ["antiretrovirals", "antiretroviral", "protease inhibitors"], + kind: "external", + note: "Ritonavir and relatives are outside the catalogue.", + }, + { + id: "cotrimoxazole", + surfaces: ["bactrim", "co-trimoxazole", "cotrimoxazole"], + kind: "external", + note: "Combination product; trimethoprim resolves by name.", + }, + + // --- Mechanism classes we cannot enumerate. Force the grey verdict. --- + { + id: "cyp-inhibitors", + surfaces: [ + "cyp inhibitors", + "cyp inhibitor", + "strong cyp inhibitors", + "cyp3a4 inhibitors", + "cyp2d6 inhibitors", + "cyp1a2 inhibitors", + "cyp2c19 inhibitors", + ], + kind: "mechanism", + note: "Enumerating inhibitors needs pharmacokinetic data the catalogue does not carry.", + }, + { + id: "cyp-inducers", + surfaces: ["cyp inducers", "cyp inducer", "strong cyp inducers", "cyp3a4 inducers", "enzyme inducers"], + kind: "mechanism", + note: "See cyp-inhibitors.", + }, + { + id: "cyp-substrates", + surfaces: ["cyp substrates", "cyp2d6 substrates", "cyp3a4 substrates", "narrow therapeutic index drugs"], + kind: "mechanism", + }, + { + id: "pgp", + surfaces: ["p-gp", "p-gp inhibitors", "p-glycoprotein", "oatp"], + kind: "mechanism", + }, + { + id: "cns-depressants", + surfaces: ["cns depressants", "cns depressant", "sedatives", "sedative", "sedating drugs"], + kind: "mechanism", + note: + "Deliberately unenumerable: the surface spans benzodiazepines, opioids, " + + "antihistamines, alcohol and more. Rows that mean a specific class name it.", + }, + { + id: "qtc-prolonging", + surfaces: [ + "qtc prolonging drugs", + "qt prolonging drugs", + "concurrent qtc prolonging drugs", + "qt-prolonging agents", + ], + kind: "mechanism", + note: "The catalogue carries QTc risk per drug but not a curated interacting set.", + }, + { + id: "serotonergic", + surfaces: ["serotonergic drugs", "serotonergic agents", "other serotonergics"], + kind: "mechanism", + }, +]; + +export const INTERACTION_LEXICON: readonly LexiconTerm[] = Object.freeze([...CATALOGUE_TERMS, ...NON_CATALOGUE_TERMS]); + +/** Surfaces sorted longest-first so "thiazide diuretics" wins over "diuretics". */ +export const LEXICON_SURFACES_BY_LENGTH: readonly { surface: string; term: LexiconTerm }[] = Object.freeze( + INTERACTION_LEXICON.flatMap((term) => + term.surfaces.map((surface) => ({ surface: surface.toLowerCase(), term })), + ).sort((a, b) => b.surface.length - a.surface.length), +); + +/** Resolve a catalogue selector against the medication catalogue. */ +export function selectCatalogueSlugs(select: CatalogueSelector, records: readonly MedicationRecord[]): string[] { + const deny = new Set(select.denySlugs ?? []); + const matched = new Set(); + + for (const slug of select.slugs ?? []) { + if (records.some((record) => record.slug === slug)) matched.add(slug); + } + for (const record of records) { + const recordClass = (record.class ?? "").toLowerCase(); + const recordSubclass = (record.subclass ?? "").toLowerCase(); + const classHit = (select.classes ?? []).some((value) => value.toLowerCase() === recordClass); + const subclassHit = (select.subclassIncludes ?? []).some((value) => recordSubclass.includes(value.toLowerCase())); + if (classHit || subclassHit) matched.add(record.slug); + } + + for (const slug of deny) matched.delete(slug); + return Array.from(matched).sort(); +} diff --git a/src/lib/medication-interactions.ts b/src/lib/medication-interactions.ts new file mode 100644 index 0000000000..e16e53de9d --- /dev/null +++ b/src/lib/medication-interactions.ts @@ -0,0 +1,269 @@ +// Patient-medication-list → drug-interaction evaluation. +// +// The companion to `medication-patient-alerts.ts`. That module answers "does +// this patient's *physiology* change how I use this drug?"; this one answers +// "does this drug collide with something the patient is *already taking*?". +// Both are pure and framework-free so the prescribing workspace, the medication +// detail page, and unit tests can share them. +// +// Input data is the generated `data/medication-interaction-index.json` (see +// `scripts/build-medication-interaction-index.ts`), which carries, per source +// medication, one entry per `Key Interactions` row: normalised severity, the +// resolved catalogue counterparties, and the verbatim source text. +// +// THE ONE INVARIANT THAT MATTERS +// `success` (green) is only reachable when every row on the medication resolved. +// A medication carrying an unresolved row returns `highestTone: null` with a +// non-zero `unresolvedRowCount`, and the render layer must show that as neutral +// grey with a "needs manual review" note — never as an all-clear. Missing +// analysis is not the same as a negative finding. + +import interactionIndex from "../../data/medication-interaction-index.json"; +import { SEMANTIC_TONE_PRIORITY, type SemanticTone } from "@/lib/semantic-tone"; + +export type InteractionSeverity = + "critical" | "high" | "moderate" | "caution" | "low" | "none" | "safe" | "beneficial" | "unknown"; + +export type MedicationInteraction = { + /** Stable id for React keys and test ids. */ + id: string; + counterpartySlug: string; + counterpartyName: string; + /** + * Lexicon term ids that fired on this row, for auditability. Row-level, not + * per-counterparty: a row naming "MAOIs, Tramadol" reports both terms for + * either match. Empty when the row resolved purely by drug name. + */ + matchedTerms: string[]; + severity: InteractionSeverity; + tone: SemanticTone; + /** `row.key` — "Pharmacodynamic", "Pharmacokinetic", "Triple Whammy", … */ + kind: string; + /** VERBATIM catalogue row text. Never paraphrased. */ + note: string; +}; + +export type MedicationInteractionResult = { + interactions: MedicationInteraction[]; + counts: Record; + /** Highest tone across matched interactions, or null when none matched. */ + highestTone: SemanticTone | null; + /** + * Interaction rows on this medication whose counterparty could not be + * machine-resolved. Non-zero must suppress any green verdict. + */ + unresolvedRowCount: number; + /** Total interaction rows the catalogue carries for this medication. */ + totalRowCount: number; +}; + +type IndexRow = { + rowKey: string; + rowIndex: number; + severity: string; + counterparties: string[]; + termIds: string[]; + resolved: boolean; +}; +type InteractionIndexShape = { + version: number; + sourceRowCount: number; + names: Record; + bySlug: Record; +}; + +const INDEX = interactionIndex as unknown as InteractionIndexShape; + +/** Display name for a catalogue slug, falling back to the slug itself. */ +export function medicationDisplayName(slug: string): string { + return INDEX.names[slug] ?? slug; +} + +export type MedicationOption = { slug: string; name: string }; + +let cachedOptions: MedicationOption[] | null = null; + +/** + * Every catalogue medication as a pickable `{ slug, name }`, name-sorted. + * + * Sourced from the interaction index rather than the 3.5 MB catalogue snapshot: + * the picker only needs slugs and names, and the index is already loaded on the + * surfaces that host the picker. + */ +export function catalogueMedicationOptions(): MedicationOption[] { + cachedOptions ??= Object.entries(INDEX.names) + .map(([slug, name]) => ({ slug, name })) + .sort((a, b) => a.name.localeCompare(b.name)); + return cachedOptions; +} + +/** True when the slug names a real catalogue medication. */ +export function isCatalogueMedicationSlug(slug: string): boolean { + return Object.hasOwn(INDEX.names, slug); +} + +export const SEVERITY_TONE: Record = { + critical: "danger", + high: "danger", + moderate: "warning", + caution: "warning", + low: "success", + none: "success", + safe: "success", + beneficial: "success", + // An unparsed severity token must not be optimistic. + unknown: "neutral", +}; + +const EMPTY_COUNTS: Record = { + danger: 0, + warning: 0, + clinical: 0, + success: 0, + neutral: 0, + info: 0, +}; + +const SEVERITY_ORDER: InteractionSeverity[] = [ + "critical", + "high", + "moderate", + "caution", + "unknown", + "low", + "none", + "safe", + "beneficial", +]; + +function normaliseSeverity(value: string): InteractionSeverity { + return (SEVERITY_ORDER as string[]).includes(value) ? (value as InteractionSeverity) : "unknown"; +} + +/** Total interaction rows the catalogue carries for a medication. */ +export function interactionRowCount(slug: string): number { + return INDEX.bySlug[slug]?.rows.length ?? 0; +} + +/** + * Evaluate one medication against the patient's current medication list. + * + * `patientMedicationSlugs` are catalogue slugs. The source medication is + * excluded automatically, so a patient already taking the drug being viewed does + * not produce a self-interaction. + */ +export function evaluateMedicationInteractions( + slug: string, + patientMedicationSlugs: readonly string[], + /** + * The medication's own record, when the caller has it. Used to recover the + * verbatim `Key Interactions` row text by index — that text is deliberately + * not duplicated into the generated index (see the build script). Without a + * record the interaction still reports correctly, just with an empty `note`. + */ + record?: { sections?: { type: string; rows?: { key: string; val: string }[] }[] } | null, +): MedicationInteractionResult { + const entry = INDEX.bySlug[slug]; + if (!entry) { + return { + interactions: [], + counts: { ...EMPTY_COUNTS }, + highestTone: null, + unresolvedRowCount: 0, + totalRowCount: 0, + }; + } + + const patient = new Set(patientMedicationSlugs.filter((value) => value !== slug)); + const interactions: MedicationInteraction[] = []; + const sourceRows = record?.sections?.find((section) => section.type === "inter")?.rows ?? []; + + for (const row of entry.rows) { + for (const counterpartySlug of row.counterparties) { + if (!patient.has(counterpartySlug)) continue; + const severity = normaliseSeverity(row.severity); + interactions.push({ + id: `${slug}:${row.rowIndex}:${counterpartySlug}`, + counterpartySlug, + counterpartyName: medicationDisplayName(counterpartySlug), + matchedTerms: row.termIds, + severity, + tone: SEVERITY_TONE[severity], + kind: row.rowKey, + note: sourceRows[row.rowIndex]?.val ?? "", + }); + } + } + + interactions.sort((a, b) => { + const byTone = SEMANTIC_TONE_PRIORITY[b.tone] - SEMANTIC_TONE_PRIORITY[a.tone]; + if (byTone !== 0) return byTone; + const bySeverity = SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity); + if (bySeverity !== 0) return bySeverity; + return a.counterpartyName.localeCompare(b.counterpartyName); + }); + + const counts: Record = { ...EMPTY_COUNTS }; + for (const interaction of interactions) counts[interaction.tone] += 1; + + return { + interactions, + counts, + highestTone: interactions[0]?.tone ?? null, + unresolvedRowCount: entry.unresolvedRowCount, + totalRowCount: entry.rows.length, + }; +} + +// --------------------------------------------------------------------------- +// Composition with the physiology engine +// --------------------------------------------------------------------------- + +export type MedicationVerdict = { + tone: SemanticTone; + /** True when something prevented a complete assessment (grey, not green). */ + incomplete: boolean; + interactionCount: number; + considerationCount: number; + unresolvedRowCount: number; +}; + +/** + * Fold the physiology considerations and the interaction matches into the single + * tone a result row's border wears. + * + * Precedence is the shared `SEMANTIC_TONE_PRIORITY` ladder (danger 6 → info 1), + * with one override that is the whole point of the function: if either engine + * reports something it could not assess, a would-be `success` degrades to + * `neutral`. Green must be unreachable on incomplete data — the same fail-safe + * `evaluatePatientAlerts` already applies through its `unassessed` list. + */ +export function composeMedicationVerdict(input: { + considerationTone: SemanticTone | null; + considerationCount: number; + unassessedCount: number; + interactionTone: SemanticTone | null; + interactionCount: number; + unresolvedRowCount: number; +}): MedicationVerdict { + const incomplete = input.unassessedCount > 0 || input.unresolvedRowCount > 0; + const candidates = [input.considerationTone, input.interactionTone].filter( + (tone): tone is SemanticTone => tone !== null, + ); + + let tone: SemanticTone = candidates.length + ? candidates.reduce((best, current) => + SEMANTIC_TONE_PRIORITY[current] > SEMANTIC_TONE_PRIORITY[best] ? current : best, + ) + : "success"; + + if (incomplete && tone === "success") tone = "neutral"; + + return { + tone, + incomplete, + interactionCount: input.interactionCount, + considerationCount: input.considerationCount, + unresolvedRowCount: input.unresolvedRowCount, + }; +} diff --git a/src/lib/medication-patient-alerts.ts b/src/lib/medication-patient-alerts.ts index 5a857c7606..8a513f5964 100644 --- a/src/lib/medication-patient-alerts.ts +++ b/src/lib/medication-patient-alerts.ts @@ -40,6 +40,12 @@ export type PatientProfile = { pregnant?: boolean; breastfeeding?: boolean; allergies?: AllergyClass[]; + /** + * Catalogue slugs for what the patient is currently taking. Consumed by + * `medication-interactions.ts`, not by this module's physiology rules — it + * lives on the same profile so one session-scoped record drives both engines. + */ + medications?: string[]; }; export type MedicationConsideration = { @@ -169,6 +175,7 @@ export function isProfileEmpty(profile: PatientProfile | null | undefined): bool if (profile.pregnant || profile.breastfeeding) return false; if (profile.hepatic && profile.hepatic !== "none") return false; if (profile.allergies && profile.allergies.length > 0) return false; + if (profile.medications && profile.medications.length > 0) return false; return true; } diff --git a/src/lib/mode-home-composer.ts b/src/lib/mode-home-composer.ts index bd9dc9c1f1..f1080023b6 100644 --- a/src/lib/mode-home-composer.ts +++ b/src/lib/mode-home-composer.ts @@ -30,6 +30,25 @@ export function setModeHomeComposerReservePending(slot: Element | null | undefin /** Mobile/tablet search-composer slot for differentials compare actions. */ export const differentialsMobileCompareAddonSlotId = "differentials-mobile-compare-addon-slot"; +/** Phone search-composer slot for the medication surfaces' Patient details pill. */ +export const patientDetailsAddonSlotId = "patient-details-addon-slot"; + +/** + * Which page-owned action currently occupies the phone dock's addon slot. + * + * The dock carries exactly ONE addon at a time — `data-footer-addon` is a single + * attribute value, and the scrim height, hide transform, and content reserve are + * all keyed off it. The two claimants are mutually exclusive by surface + * (differentials vs medications/prescribing) and `tests/phone-dock-addon-contract.test.ts` + * pins that, so the slot never has to hold two. + */ +export type PhoneDockAddonKind = "differentials-compare" | "patient-details"; + +export const phoneDockAddonSlotId: Record = { + "differentials-compare": differentialsMobileCompareAddonSlotId, + "patient-details": patientDetailsAddonSlotId, +}; + /** * The one page-owned phone navigation host inside `universal-header-collapse`. * Portaled chrome here shares the universal header's scroll signal, safe-area diff --git a/src/lib/patient-profile-storage.ts b/src/lib/patient-profile-storage.ts index ca52aa1faa..08bc91d10c 100644 --- a/src/lib/patient-profile-storage.ts +++ b/src/lib/patient-profile-storage.ts @@ -28,8 +28,24 @@ export const EMPTY_PATIENT_PROFILE: PatientProfile = { pregnant: false, breastfeeding: false, allergies: [], + medications: [], }; +/** + * Upper bound on the stored medication list. Not a clinical limit — a guard so a + * corrupted or hand-edited sessionStorage value cannot make every result row + * evaluate an unbounded list. + */ +export const PATIENT_PROFILE_MAX_MEDICATIONS = 40; + +// Slugs are validated by SHAPE here, not against the catalogue. Importing the +// catalogue (or the interaction index) into this module would pull it into the +// global shell bundle via PatientProfileProvider, which mounts on every route — +// a real weight regression for data only the medication surfaces need. An +// unknown slug is inert downstream: it can never match an interaction row, and +// the picker only ever offers real catalogue entries. +const MEDICATION_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,60}$/; + const SCR_UNITS: ScrUnit[] = ["umol/L", "mg/dL"]; const HEPATIC_LEVELS: HepaticSeverity[] = ["none", "mild", "moderate", "severe"]; const ALLERGY_CLASSES: AllergyClass[] = [ @@ -110,9 +126,24 @@ export function sanitizeProfile(raw: unknown): PatientProfile { pregnant: value.pregnant === true, breastfeeding: value.breastfeeding === true, allergies, + medications: sanitizeMedicationSlugs(value.medications), }; } +/** Shape-validate, de-duplicate, sort and cap the stored medication list. */ +export function sanitizeMedicationSlugs(raw: unknown): string[] { + if (!Array.isArray(raw)) return []; + const seen = new Set(); + for (const item of raw) { + if (typeof item !== "string") continue; + const slug = item.trim().toLowerCase(); + if (!MEDICATION_SLUG_PATTERN.test(slug)) continue; + seen.add(slug); + if (seen.size >= PATIENT_PROFILE_MAX_MEDICATIONS) break; + } + return Array.from(seen).sort(); +} + // Cache the parsed snapshot keyed by the raw string so `useSyncExternalStore` // receives a stable reference until the stored value actually changes. let cachedRaw: string | null = null; diff --git a/tests/helpers/style-contracts.ts b/tests/helpers/style-contracts.ts index 94ec9f8998..590f9aafec 100644 --- a/tests/helpers/style-contracts.ts +++ b/tests/helpers/style-contracts.ts @@ -361,6 +361,12 @@ export const STYLE_CONTRACT_EXEMPTIONS: Readonly> = { // Mode-specific surfaces. "differentials-mobile-compare-fab__button": "differentials compare FAB — no effect contract yet (#094)", "differentials-mobile-compare-fab__button--empty": "differentials compare FAB — no effect contract yet (#094)", + // Same shape as the compare FAB above, and the same gap: a rendered-effect + // contract needs a phone-viewport route with the medication catalogue loaded. + // The tap floor (min-height: 3rem) is the regression worth catching here. + "patient-details-fab__button": "patient details dock pill — no effect contract yet (#094)", + "patient-details-fab__button--active": "patient details dock pill, populated state — no effect contract yet (#094)", + "patient-details-fab__count": "patient details count badge — no effect contract yet (#094)", "medication-mobile-result": "prescribing phone results — no effect contract yet (#094)", "pwa-install-sheet": "install sheet — presence covered by ui-pwa, effect not contracted", "search-band-count": "count weight/colour; the zero-result state needs a deterministic empty fixture first", diff --git a/tests/medication-interaction-lexicon-coverage.test.ts b/tests/medication-interaction-lexicon-coverage.test.ts new file mode 100644 index 0000000000..12061b84fd --- /dev/null +++ b/tests/medication-interaction-lexicon-coverage.test.ts @@ -0,0 +1,189 @@ +// Regression pins for the curated interaction lexicon. +// +// The lexicon is hand-maintained, so the failure it invites is silent decay: an +// edit that narrows a selector, or a catalogue refresh that renames a subclass, +// quietly turns resolved rows into unresolved ones. Unresolved rows render grey +// rather than green, so decay is fail-safe — but it is still a loss of function +// nobody would notice without a pin. +// +// The counts below are therefore RATCHETS, not exact expectations: resolution +// may improve freely, and any regression has to be justified by editing this +// file deliberately. + +import { readFileSync } from "node:fs"; +import path from "node:path"; + +import { describe, expect, it } from "vitest"; + +import { INTERACTION_LEXICON, selectCatalogueSlugs, type LexiconTerm } from "@/lib/medication-interaction-lexicon"; +import { loadMedicationSnapshot } from "@/lib/medication-snapshot"; + +type IndexRow = { + rowKey: string; + rowIndex: number; + severity: string; + counterparties: string[]; + termIds: string[]; + resolved: boolean; +}; + +const index = JSON.parse( + readFileSync(path.resolve(process.cwd(), "data/medication-interaction-index.json"), "utf8"), +) as { + sourceRowCount: number; + stats: { resolvedRows: number; unresolvedRows: number }; + bySlug: Record; +}; + +const records = loadMedicationSnapshot(); + +function term(id: string): LexiconTerm { + const found = INTERACTION_LEXICON.find((item) => item.id === id); + if (!found) throw new Error(`Lexicon term "${id}" not found`); + return found; +} + +function slugsFor(id: string): string[] { + const found = term(id); + return found.select ? selectCatalogueSlugs(found.select, records) : []; +} + +describe("interaction lexicon coverage", () => { + it("does not regress the number of resolved rows", () => { + // Ratchet. Raise it when resolution genuinely improves; never lower it + // without recording why in the PR. + expect(index.stats.resolvedRows).toBeGreaterThanOrEqual(400); + expect(index.sourceRowCount).toBeGreaterThanOrEqual(523); + }); + + it("keeps every unresolved row visible rather than dropping it", () => { + const rowTotal = Object.values(index.bySlug).reduce((total, entry) => total + entry.rows.length, 0); + expect(index.stats.resolvedRows + index.stats.unresolvedRows).toBe(rowTotal); + expect(rowTotal).toBe(index.sourceRowCount); + }); + + it("marks a row unresolved when its only counterparty is an unenumerable mechanism", () => { + const unresolved = Object.values(index.bySlug) + .flatMap((entry) => entry.rows) + .filter((row) => !row.resolved); + expect(unresolved.length).toBeGreaterThan(0); + }); + + it("addresses a real catalogue row from every rowIndex it emits", () => { + // `note` is intentionally not duplicated into the index; the UI recovers the + // verbatim text by indexing into the record. That only works if every + // emitted rowIndex still addresses a real row, so pin it for all 523. + for (const [slug, entry] of Object.entries(index.bySlug)) { + const record = records.find((item) => item.slug === slug); + const sourceRows = record?.sections.find((section) => section.type === "inter")?.rows ?? []; + for (const row of entry.rows) { + const source = sourceRows[row.rowIndex]; + expect(source, `${slug} row ${row.rowIndex} does not address a catalogue row`).toBeDefined(); + expect(source?.key).toBe(row.rowKey); + expect((source?.val ?? "").trim().length).toBeGreaterThan(0); + } + } + }); +}); + +describe("lexicon deny-lists (the traps this module exists for)", () => { + it("never resolves clozapine or olanzapine as a benzodiazepine", () => { + // Their subclasses are "SGA / Dibenzodiazepine" and "SGA / Thienobenzodiazepine". + const benzos = slugsFor("benzodiazepines"); + expect(benzos).toContain("diazepam"); + expect(benzos).not.toContain("clozapine"); + expect(benzos).not.toContain("olanzapine-wafer-odt"); + expect(benzos.every((slug) => !slug.startsWith("clozapine"))).toBe(true); + }); + + it("never resolves an opioid antagonist as an opioid", () => { + const opioids = slugsFor("opioids"); + expect(opioids).toContain("methadone"); + expect(opioids).not.toContain("naltrexone"); + expect(opioids).not.toContain("naloxone"); + }); + + it("resolves beta blockers across both catalogue spellings", () => { + const betaBlockers = slugsFor("beta-blockers"); + // "Beta Blocker" (metoprolol) and "Beta-Blocker" (propranolol). + expect(betaBlockers).toContain("metoprolol"); + expect(betaBlockers).toContain("propranolol"); + }); + + it("prefers the specific class over the generic one", () => { + const thiazides = slugsFor("thiazide-diuretics"); + const loops = slugsFor("loop-diuretics"); + expect(thiazides.length).toBeGreaterThan(0); + expect(loops.length).toBeGreaterThan(0); + expect(thiazides.some((slug) => loops.includes(slug))).toBe(false); + }); +}); + +describe("non-drug and external terms", () => { + const nonMatching = INTERACTION_LEXICON.filter( + (item) => item.kind === "nonDrug" || item.kind === "external" || item.kind === "mechanism", + ); + + it("never resolves to a catalogue medication", () => { + for (const item of nonMatching) { + expect(item.select, `${item.id} must not carry a catalogue selector`).toBeUndefined(); + } + }); + + it("classifies alcohol, grapefruit and smoking as non-drug", () => { + for (const id of ["alcohol", "grapefruit", "smoking", "acidic-drinks"]) { + expect(term(id).kind).toBe("nonDrug"); + } + }); + + it("classifies bare CYP and P-gp classes as unenumerable mechanisms", () => { + for (const id of ["cyp-inhibitors", "cyp-inducers", "pgp", "cns-depressants"]) { + expect(term(id).kind).toBe("mechanism"); + } + }); + + it("produces no counterparties for a row whose only term is non-drug", () => { + // Clozapine's CRITICAL smoking row. + const smokingRows = (index.bySlug.clozapine?.rows ?? []).filter((row) => row.termIds.includes("smoking")); + expect(smokingRows.length).toBeGreaterThan(0); + expect(smokingRows.every((row) => row.counterparties.length === 0)).toBe(true); + }); + + it("resolves a non-drug-only row rather than leaving it for manual review", () => { + // "Smoking" is fully understood — it just is not a medicine. That is a + // different thing from "we could not read this row", and must not push the + // medication into the grey needs-review state. + const smokingRows = (index.bySlug.clozapine?.rows ?? []).filter((row) => row.termIds.includes("smoking")); + expect(smokingRows.every((row) => row.resolved)).toBe(true); + }); +}); + +describe("lexicon hygiene", () => { + it("has unique term ids", () => { + const ids = INTERACTION_LEXICON.map((item) => item.id); + expect(new Set(ids).size).toBe(ids.length); + }); + + it("has no duplicate surface across terms", () => { + const surfaces = INTERACTION_LEXICON.flatMap((item) => item.surfaces.map((s) => s.toLowerCase())); + expect(new Set(surfaces).size).toBe(surfaces.length); + }); + + it("gives every catalogue term a selector that actually matches something", () => { + for (const item of INTERACTION_LEXICON.filter((entry) => entry.kind === "catalogue")) { + expect(item.select, `${item.id} needs a selector`).toBeDefined(); + expect(slugsFor(item.id).length, `${item.id} resolved to nothing`).toBeGreaterThan(0); + } + }); + + it("never lets a source medication appear as its own counterparty", () => { + for (const [slug, entry] of Object.entries(index.bySlug)) { + for (const row of entry.rows) { + expect( + row.counterparties.every((c) => c !== slug), + `${slug} self-interacts`, + ).toBe(true); + } + } + }); +}); diff --git a/tests/medication-interaction-surfaces.dom.test.tsx b/tests/medication-interaction-surfaces.dom.test.tsx new file mode 100644 index 0000000000..fae6372acf --- /dev/null +++ b/tests/medication-interaction-surfaces.dom.test.tsx @@ -0,0 +1,136 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; + +import { render, screen } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { + MedicationConsiderations, + medicationVerdictRingClass, + verdictSummaryBadge, +} from "@/components/clinical-dashboard/medication-considerations"; +import { PatientProfileProvider } from "@/components/clinical-dashboard/patient-profile-context"; +import { getMedicationRecord } from "@/lib/medication-snapshot"; +import { PATIENT_PROFILE_STORAGE_KEY } from "@/lib/patient-profile-storage"; +import type { MedicationRecord } from "@/lib/medications"; + +function seedProfile(profile: Record) { + window.sessionStorage.setItem(PATIENT_PROFILE_STORAGE_KEY, JSON.stringify(profile)); +} + +function renderFor(slug: string) { + const record = getMedicationRecord(slug) as MedicationRecord; + return render( + + + , + ); +} + +beforeEach(() => { + window.sessionStorage.clear(); +}); + +afterEach(() => { + window.sessionStorage.clear(); +}); + +describe("interactions block on the medication detail page", () => { + it("prompts for a medication list when none is entered", () => { + renderFor("sertraline"); + expect(screen.getByText(/Add the patient’s current medications/i)).toBeInTheDocument(); + }); + + it("surfaces a matched interaction with its verbatim catalogue text", () => { + seedProfile({ medications: ["tramadol-ir"] }); + renderFor("sertraline"); + + expect(screen.getByText(/With Tramadol IR/i)).toBeInTheDocument(); + expect(screen.getByText(/Serotonin Syndrome/i)).toBeInTheDocument(); + }); + + it("says 'no interaction found' — scoped to what was entered, not a global all-clear", () => { + // Acamprosate: every interaction row resolved, and none names paracetamol. + seedProfile({ medications: ["paracetamol"] }); + renderFor("acamprosate"); + + const notice = screen.getByText(/No interaction found between this medication/i); + expect(notice).toBeInTheDocument(); + expect(notice.textContent).toMatch(/medication entered/i); + }); + + it("states unresolved rows plainly instead of folding them into the all-clear", () => { + // Bupropion carries a CYP2D6-substrate row with no enumerable counterparty. + seedProfile({ medications: ["paracetamol"] }); + renderFor("bupropion-sr"); + + expect(screen.getByText(/could not be matched automatically/i)).toBeInTheDocument(); + expect(screen.getByText(/is not shown as clear/i)).toBeInTheDocument(); + }); + + it("never claims an all-clear for a medication carrying unresolved rows", () => { + seedProfile({ medications: ["paracetamol"] }); + const { container } = renderFor("bupropion-sr"); + + // The success notice must not be the story here; the review notice is. + expect(container.textContent).toMatch(/could not be matched automatically/i); + }); +}); + +describe("result-row verdict presentation", () => { + const clean = { + tone: "success" as const, + incomplete: false, + interactionCount: 0, + considerationCount: 0, + unresolvedRowCount: 0, + }; + + it("labels a clean verdict as a finding, not a guarantee", () => { + expect(verdictSummaryBadge(clean)).toEqual({ + id: "patient-verdict", + label: "No interaction found", + tone: "success", + }); + }); + + it("labels an incomplete verdict for manual review rather than clear", () => { + const badge = verdictSummaryBadge({ ...clean, tone: "neutral", incomplete: true, unresolvedRowCount: 2 }); + expect(badge?.label).toBe("Needs manual review"); + expect(badge?.tone).toBe("neutral"); + }); + + it("counts interactions and physiology alerts separately in one label", () => { + const badge = verdictSummaryBadge({ + ...clean, + tone: "danger", + interactionCount: 2, + considerationCount: 1, + }); + expect(badge?.label).toBe("2 interactions · 1 alert"); + expect(badge?.tone).toBe("danger"); + }); + + it("keeps the desktop edge on ring-*, never mixing in a border on one surface", () => { + for (const tone of ["danger", "warning", "success", "clinical", "info"] as const) { + const ring = medicationVerdictRingClass(tone); + expect(ring).toMatch(/ring-/); + expect(ring).not.toMatch(/(^|\s)border-/); + } + }); + + it("sets the phone card edge from unlayered CSS, which is what beats [data-selected]", () => { + const css = readFileSync(path.resolve(process.cwd(), "src/app/globals.css"), "utf8"); + const selectedAt = css.indexOf('.medication-mobile-result[data-selected="true"]'); + const verdictAt = css.indexOf('.medication-mobile-result[data-verdict="danger"]'); + expect(selectedAt).toBeGreaterThan(-1); + expect(verdictAt).toBeGreaterThan(-1); + // Equal specificity, so source order decides. The verdict must come last or + // the top hit renders its interaction warning with a neutral edge. + expect(verdictAt).toBeGreaterThan(selectedAt); + }); + + it("leaves the neutral desktop row to the list divider rather than ringing it", () => { + expect(medicationVerdictRingClass("neutral")).toBeNull(); + }); +}); diff --git a/tests/medication-interactions.test.ts b/tests/medication-interactions.test.ts new file mode 100644 index 0000000000..a3bb000c1e --- /dev/null +++ b/tests/medication-interactions.test.ts @@ -0,0 +1,133 @@ +import { describe, expect, it } from "vitest"; + +import { getMedicationRecord } from "@/lib/medication-snapshot"; +import { + composeMedicationVerdict, + evaluateMedicationInteractions, + interactionRowCount, + medicationDisplayName, + SEVERITY_TONE, +} from "@/lib/medication-interactions"; + +describe("evaluateMedicationInteractions", () => { + it("matches a drug named outright in the counterparty sentence", () => { + // Sertraline's CRITICAL row: "MAOIs, Tramadol, Tapentadol, St John's Wort." + const result = evaluateMedicationInteractions("sertraline", ["tramadol-ir"]); + expect(result.interactions).toHaveLength(1); + expect(result.interactions[0]?.counterpartySlug).toBe("tramadol-ir"); + expect(result.interactions[0]?.severity).toBe("critical"); + expect(result.interactions[0]?.tone).toBe("danger"); + expect(result.highestTone).toBe("danger"); + }); + + it("matches through a resolved class term", () => { + // The same row names "MAOIs"; phenelzine is reached via the class, not by name. + const result = evaluateMedicationInteractions("sertraline", ["phenelzine"]); + expect(result.interactions[0]?.counterpartySlug).toBe("phenelzine"); + expect(result.interactions[0]?.counterpartyName).toBe("Phenelzine"); + expect(result.interactions[0]?.matchedTerms).toContain("maois"); + expect(result.interactions[0]?.tone).toBe("danger"); + }); + + it("renders the verbatim catalogue row text when given the record", () => { + const record = getMedicationRecord("sertraline"); + const result = evaluateMedicationInteractions("sertraline", ["ibuprofen"], record); + expect(result.interactions[0]?.note).toContain("NSAIDs"); + expect(result.interactions[0]?.note).toMatch(/^(HIGH|CRITICAL)/); + }); + + it("still reports the interaction without a record, just with no note text", () => { + const result = evaluateMedicationInteractions("sertraline", ["ibuprofen"]); + expect(result.interactions).toHaveLength(1); + expect(result.interactions[0]?.note).toBe(""); + }); + + it("returns no matches for a patient list with nothing in common", () => { + const result = evaluateMedicationInteractions("sertraline", ["paracetamol"]); + expect(result.interactions).toHaveLength(0); + expect(result.highestTone).toBeNull(); + }); + + it("never reports a medication as interacting with itself", () => { + const result = evaluateMedicationInteractions("ibuprofen", ["ibuprofen"]); + expect(result.interactions.every((item) => item.counterpartySlug !== "ibuprofen")).toBe(true); + }); + + it("sorts the most severe interaction first", () => { + const result = evaluateMedicationInteractions("sertraline", ["ibuprofen", "tramadol-ir"]); + expect(result.interactions.length).toBeGreaterThanOrEqual(2); + expect(result.interactions[0]?.severity).toBe("critical"); + }); + + it("carries the unresolved row count for a medication the parser could not fully read", () => { + // Bupropion's CYP2D6-substrate row has no enumerable counterparty. + const result = evaluateMedicationInteractions("bupropion-sr", []); + expect(result.unresolvedRowCount).toBeGreaterThan(0); + expect(result.totalRowCount).toBeGreaterThanOrEqual(result.unresolvedRowCount); + }); + + it("returns an empty result for an unknown slug rather than throwing", () => { + const result = evaluateMedicationInteractions("not-a-real-drug", ["sertraline"]); + expect(result.interactions).toHaveLength(0); + expect(result.totalRowCount).toBe(0); + expect(interactionRowCount("not-a-real-drug")).toBe(0); + }); + + it("names counterparties from the index, falling back to the slug", () => { + expect(medicationDisplayName("tramadol-ir")).toBe("Tramadol IR"); + expect(medicationDisplayName("not-a-real-drug")).toBe("not-a-real-drug"); + }); + + it("maps every severity to a tone, and never maps an unknown severity to success", () => { + expect(SEVERITY_TONE.critical).toBe("danger"); + expect(SEVERITY_TONE.moderate).toBe("warning"); + expect(SEVERITY_TONE.low).toBe("success"); + expect(SEVERITY_TONE.unknown).toBe("neutral"); + }); +}); + +describe("composeMedicationVerdict", () => { + const base = { + considerationTone: null, + considerationCount: 0, + unassessedCount: 0, + interactionTone: null, + interactionCount: 0, + unresolvedRowCount: 0, + }; + + it("is green when both engines ran clean", () => { + expect(composeMedicationVerdict(base).tone).toBe("success"); + expect(composeMedicationVerdict(base).incomplete).toBe(false); + }); + + it("degrades green to grey when an interaction row could not be resolved", () => { + const verdict = composeMedicationVerdict({ ...base, unresolvedRowCount: 2 }); + expect(verdict.tone).toBe("neutral"); + expect(verdict.incomplete).toBe(true); + }); + + it("degrades green to grey when a physiology gate was unassessed", () => { + expect(composeMedicationVerdict({ ...base, unassessedCount: 1 }).tone).toBe("neutral"); + }); + + it("keeps danger even when data is incomplete — a real finding outranks the caveat", () => { + const verdict = composeMedicationVerdict({ + ...base, + interactionTone: "danger", + interactionCount: 1, + unresolvedRowCount: 3, + }); + expect(verdict.tone).toBe("danger"); + expect(verdict.incomplete).toBe(true); + }); + + it("takes the highest tone across both engines", () => { + expect(composeMedicationVerdict({ ...base, considerationTone: "warning", interactionTone: "danger" }).tone).toBe( + "danger", + ); + expect(composeMedicationVerdict({ ...base, considerationTone: "danger", interactionTone: "warning" }).tone).toBe( + "danger", + ); + }); +}); diff --git a/tests/patient-details-dock-action.dom.test.tsx b/tests/patient-details-dock-action.dom.test.tsx new file mode 100644 index 0000000000..a51a77c168 --- /dev/null +++ b/tests/patient-details-dock-action.dom.test.tsx @@ -0,0 +1,126 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { PatientDetailsDockAction } from "@/components/clinical-dashboard/patient-details-dock-action"; +import { PatientProfileProvider } from "@/components/clinical-dashboard/patient-profile-context"; +import { patientDetailsAddonSlotId } from "@/lib/mode-home-composer"; +import { PATIENT_PROFILE_STORAGE_KEY } from "@/lib/patient-profile-storage"; + +/** Stand in for the phone dock's addon slot, which MasterSearchHeader renders. */ +function mountSlot(): HTMLElement { + const slot = document.createElement("div"); + slot.id = patientDetailsAddonSlotId; + document.body.append(slot); + return slot; +} + +function setPhoneViewport(isPhone: boolean) { + vi.stubGlobal( + "matchMedia", + vi.fn().mockImplementation((query: string) => ({ + matches: isPhone, + media: query, + onchange: null, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + addListener: vi.fn(), + removeListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + ); +} + +function renderAction() { + return render( + + + , + ); +} + +beforeEach(() => { + window.sessionStorage.clear(); + setPhoneViewport(true); +}); + +afterEach(() => { + window.sessionStorage.clear(); + document.getElementById(patientDetailsAddonSlotId)?.remove(); + vi.unstubAllGlobals(); +}); + +describe("PatientDetailsDockAction", () => { + it("portals into the dock's addon slot rather than positioning itself", () => { + const slot = mountSlot(); + renderAction(); + + const action = screen.getByTestId("patient-details-dock-action"); + expect(slot.contains(action)).toBe(true); + }); + + it("renders nothing when the dock exposes no slot", () => { + renderAction(); + expect(screen.queryByTestId("patient-details-dock-action")).not.toBeInTheDocument(); + }); + + it("renders nothing above the phone breakpoint, where the panel is already inline", () => { + setPhoneViewport(false); + mountSlot(); + renderAction(); + + expect(screen.queryByTestId("patient-details-dock-action")).not.toBeInTheDocument(); + }); + + it("is a real button that opens the patient sheet", () => { + mountSlot(); + renderAction(); + + const action = screen.getByTestId("patient-details-dock-action"); + expect(action).toHaveAttribute("type", "button"); + expect(action).toHaveAttribute("aria-haspopup", "dialog"); + expect(action).toHaveAttribute("aria-expanded", "false"); + + fireEvent.click(action); + + expect(screen.getByRole("dialog", { name: "Patient details" })).toBeInTheDocument(); + expect(screen.getByTestId("patient-details-dock-action")).toHaveAttribute("aria-expanded", "true"); + }); + + it("announces an empty profile without relying on the count badge", () => { + mountSlot(); + renderAction(); + + expect(screen.getByText("No patient details entered")).toBeInTheDocument(); + }); + + it("counts every entered dimension, not just medications", () => { + window.sessionStorage.setItem( + PATIENT_PROFILE_STORAGE_KEY, + JSON.stringify({ ageYears: 70, medications: ["sertraline", "aspirin"], allergies: ["penicillin"] }), + ); + mountSlot(); + renderAction(); + + // age + 2 medications + 1 allergy + expect(screen.getByText("4 details entered")).toBeInTheDocument(); + }); + + it("takes the accent treatment once a profile is loaded", () => { + window.sessionStorage.setItem(PATIENT_PROFILE_STORAGE_KEY, JSON.stringify({ medications: ["sertraline"] })); + mountSlot(); + renderAction(); + + expect(screen.getByTestId("patient-details-dock-action").className).toContain( + "patient-details-fab__button--active", + ); + }); + + it("stays visually quiet while the profile is empty", () => { + mountSlot(); + renderAction(); + + expect(screen.getByTestId("patient-details-dock-action").className).not.toContain( + "patient-details-fab__button--active", + ); + }); +}); diff --git a/tests/patient-profile-medications.dom.test.tsx b/tests/patient-profile-medications.dom.test.tsx new file mode 100644 index 0000000000..a478cb8e5d --- /dev/null +++ b/tests/patient-profile-medications.dom.test.tsx @@ -0,0 +1,123 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { PatientProfileProvider } from "@/components/clinical-dashboard/patient-profile-context"; +import { PatientProfilePanel } from "@/components/clinical-dashboard/patient-profile-panel"; +import { isProfileEmpty } from "@/lib/medication-patient-alerts"; +import { + PATIENT_PROFILE_MAX_MEDICATIONS, + PATIENT_PROFILE_STORAGE_KEY, + sanitizeMedicationSlugs, + sanitizeProfile, +} from "@/lib/patient-profile-storage"; + +function renderPanel() { + return render( + + + , + ); +} + +function storedProfile(): Record { + return JSON.parse(window.sessionStorage.getItem(PATIENT_PROFILE_STORAGE_KEY) ?? "{}"); +} + +beforeEach(() => { + window.sessionStorage.clear(); +}); + +afterEach(() => { + window.sessionStorage.clear(); +}); + +describe("current-medications picker", () => { + it("adds a catalogue medication and persists its slug", () => { + renderPanel(); + + fireEvent.change(screen.getByTestId("patient-medication-search"), { target: { value: "sertral" } }); + fireEvent.click(screen.getByTestId("patient-medication-add-sertraline")); + + expect(storedProfile().medications).toEqual(["sertraline"]); + expect(screen.getByTestId("patient-medication-sertraline")).toBeInTheDocument(); + }); + + it("clears the search box after adding, so the next drug starts fresh", () => { + renderPanel(); + const search = screen.getByTestId("patient-medication-search") as HTMLInputElement; + + fireEvent.change(search, { target: { value: "sertral" } }); + fireEvent.click(screen.getByTestId("patient-medication-add-sertraline")); + + expect(search.value).toBe(""); + }); + + it("removes a medication when its chip is activated again", () => { + renderPanel(); + + fireEvent.change(screen.getByTestId("patient-medication-search"), { target: { value: "sertral" } }); + fireEvent.click(screen.getByTestId("patient-medication-add-sertraline")); + fireEvent.click(screen.getByTestId("patient-medication-sertraline")); + + expect(storedProfile().medications).toEqual([]); + expect(screen.queryByTestId("patient-medication-sertraline")).not.toBeInTheDocument(); + }); + + it("gives the remove control a name that survives without the icon", () => { + renderPanel(); + fireEvent.change(screen.getByTestId("patient-medication-search"), { target: { value: "sertral" } }); + fireEvent.click(screen.getByTestId("patient-medication-add-sertraline")); + + expect(screen.getByTestId("patient-medication-sertraline")).toHaveAttribute("aria-label", "Remove Sertraline"); + }); + + it("offers nothing for a drug outside the catalogue, and says why", () => { + renderPanel(); + + fireEvent.change(screen.getByTestId("patient-medication-search"), { target: { value: "warfarinx" } }); + + expect(screen.getByText(/Only medications in this catalogue can be interaction-checked/i)).toBeInTheDocument(); + }); + + it("drops the medication list and the search term on Clear", () => { + renderPanel(); + fireEvent.change(screen.getByTestId("patient-medication-search"), { target: { value: "sertral" } }); + fireEvent.click(screen.getByTestId("patient-medication-add-sertraline")); + + fireEvent.click(screen.getByRole("button", { name: /clear/i })); + + expect(storedProfile().medications).toEqual([]); + expect((screen.getByTestId("patient-medication-search") as HTMLInputElement).value).toBe(""); + }); +}); + +describe("medication-list sanitisation", () => { + it("keeps well-formed slugs, de-duplicated and sorted", () => { + expect(sanitizeMedicationSlugs(["sertraline", "aspirin", "sertraline"])).toEqual(["aspirin", "sertraline"]); + }); + + it("rejects non-strings and malformed slugs rather than storing them", () => { + expect(sanitizeMedicationSlugs([1, null, {}, "Not A Slug", "-leading", "has_underscore", ""])).toEqual([]); + }); + + it("normalises case rather than dropping an otherwise valid slug", () => { + expect(sanitizeMedicationSlugs([" Sertraline "])).toEqual(["sertraline"]); + }); + + it("caps a corrupted list instead of evaluating it unbounded", () => { + const many = Array.from({ length: 200 }, (_, index) => `drug-${index}`); + expect(sanitizeMedicationSlugs(many).length).toBeLessThanOrEqual(PATIENT_PROFILE_MAX_MEDICATIONS); + }); + + it("returns an empty list for a non-array value", () => { + expect(sanitizeMedicationSlugs("sertraline")).toEqual([]); + expect(sanitizeProfile({ medications: "sertraline" }).medications).toEqual([]); + }); +}); + +describe("isProfileEmpty", () => { + it("treats a medication list as a populated profile", () => { + expect(isProfileEmpty({ medications: [] })).toBe(true); + expect(isProfileEmpty({ medications: ["sertraline"] })).toBe(false); + }); +}); diff --git a/tests/phone-dock-addon-contract.test.ts b/tests/phone-dock-addon-contract.test.ts new file mode 100644 index 0000000000..6f45f7a8cb --- /dev/null +++ b/tests/phone-dock-addon-contract.test.ts @@ -0,0 +1,154 @@ +// The phone dock's addon slot holds exactly ONE page-owned action at a time. +// +// `data-footer-addon` is a single attribute value, and three separate things key +// off it: the backdrop scrim height, the hide-transform overshoot, and the +// content reserve. Adding a second claimant (Patient details, alongside the +// original Differentials Compare bar) is only safe because the two are mutually +// exclusive by surface. Nothing in the type system enforces that, so it is +// pinned here — together with the requirement that every registered addon kind +// actually has the CSS and reserve wiring it needs. + +import { readFileSync } from "node:fs"; +import path from "node:path"; + +import { describe, expect, it } from "vitest"; + +import { + phoneDockAddonSlotId, + differentialsMobileCompareAddonSlotId, + patientDetailsAddonSlotId, + type PhoneDockAddonKind, +} from "@/lib/mode-home-composer"; +import { + mobileComposerVisibleReserve, + resolveDashboardVisibleMobileComposerReserve, + resolveShellVisibleMobileComposerReserve, +} from "@/components/clinical-dashboard/mobile-composer-reserve"; + +function read(relativePath: string): string { + return readFileSync(path.resolve(process.cwd(), relativePath), "utf8"); +} + +const globalsCss = read("src/app/globals.css"); +const dashboard = read("src/components/ClinicalDashboard.tsx"); +const shell = read("src/components/clinical-dashboard/global-search-shell.tsx"); +const header = read("src/components/clinical-dashboard/master-search-header.tsx"); + +const ADDON_KINDS: PhoneDockAddonKind[] = ["differentials-compare", "patient-details"]; + +describe("phone dock addon registry", () => { + it("maps every addon kind to a distinct slot id", () => { + const ids = ADDON_KINDS.map((kind) => phoneDockAddonSlotId[kind]); + expect(ids).toEqual([differentialsMobileCompareAddonSlotId, patientDetailsAddonSlotId]); + expect(new Set(ids).size).toBe(ids.length); + }); + + it("derives data-footer-addon from the addon kind rather than hardcoding one claimant", () => { + // The original code hardcoded "differentials-compare" whenever any slot id + // was present, which silently mislabels a second claimant's dock. + expect(header).toMatch(/data-footer-addon=\{[\s\S]{0,200}mobileBottomSearchAddonKind/); + }); +}); + +describe("addon kinds are mutually exclusive", () => { + it("keys the dashboard's two claimants on different search modes", () => { + expect(dashboard).toMatch(/differentialsCompareAddonActive\s*=\s*\n?\s*searchMode === "differentials"/); + expect(dashboard).toMatch(/patientDetailsAddonActive\s*=\s*\n?\s*searchMode === "prescribing"/); + }); + + it("leaves the shell with a single claimant", () => { + expect(shell).toMatch(/differentialsCompareAddonActive\s*=\s*\n?\s*searchMode === "differentials"/); + // No shell route claims the patient-details addon: `/medications` is a + // standalone mode home with no dock, and `/medications/[slug]` already opens + // the same sheet from its nav header. Claiming it here would inflate the + // dock reserve for a pill that never mounts — a blank band at the bottom. + expect(shell).not.toMatch(/mobileBottomSearchAddonKind=\{[\s\S]{0,200}"patient-details"/); + }); + + it("does not give the medication detail route a second entry point to one sheet", () => { + const recordPage = read("src/components/clinical-dashboard/medication-record-page.tsx"); + expect(recordPage).toContain("onOpenPatientDetails"); + expect(recordPage).not.toContain("PatientDetailsDockAction"); + }); +}); + +describe("every addon kind carries its dock wiring", () => { + it.each(ADDON_KINDS)("%s has a backdrop scrim height", (kind) => { + expect(globalsCss).toContain( + `.answer-footer-search-dock[data-footer-addon="${kind}"] .answer-footer-search-backdrop`, + ); + }); + + it.each(ADDON_KINDS)("%s has a hide-transform overshoot so no strip peeps at the edge", (kind) => { + expect(globalsCss).toMatch( + new RegExp(`data-footer-addon="${kind}"\\]\\[data-scroll-hidden="true"\\][\\s\\S]{0,120}translateY`), + ); + }); + + it.each(ADDON_KINDS)("%s has clearance tokens", (kind) => { + expect(globalsCss).toContain(`--phone-dock-${kind}-clearance:`); + expect(globalsCss).toContain(`--phone-dock-${kind}-compact-clearance:`); + }); +}); + +describe("reserve resolvers honour the patient-details addon", () => { + it("returns the patient-details reserve on the dashboard", () => { + expect( + resolveDashboardVisibleMobileComposerReserve({ + searchMode: "prescribing", + hasAnswerFollowUps: false, + differentialsCompareAddonActive: false, + patientDetailsAddonActive: true, + }), + ).toBe(mobileComposerVisibleReserve.patientDetails); + }); + + it("returns the patient-details reserve in the shell", () => { + expect( + resolveShellVisibleMobileComposerReserve({ + shouldShowSearchComposer: true, + isStandaloneModeHome: false, + searchMode: "prescribing", + differentialsCompareAddonActive: false, + patientDetailsAddonActive: true, + }), + ).toBe(mobileComposerVisibleReserve.patientDetails); + }); + + it("still lets compare win when both flags are somehow set", () => { + expect( + resolveShellVisibleMobileComposerReserve({ + shouldShowSearchComposer: true, + isStandaloneModeHome: false, + searchMode: "differentials", + differentialsCompareAddonActive: true, + patientDetailsAddonActive: true, + }), + ).toBe(mobileComposerVisibleReserve.differentialsCompare); + }); + + it("keeps the standalone mode home on the idle pad — no dock, no addon reserve", () => { + // `/medications` is a standalone mode home: the composer lives in the hero, + // so reserving dock-sized space there would open a blank bottom band. + expect( + resolveShellVisibleMobileComposerReserve({ + shouldShowSearchComposer: true, + isStandaloneModeHome: true, + searchMode: "prescribing", + differentialsCompareAddonActive: false, + patientDetailsAddonActive: true, + }), + ).toBe("2rem"); + }); +}); + +describe("CSS and TS reserve values stay in step", () => { + it.each([ + ["differentials-compare", mobileComposerVisibleReserve.differentialsCompare], + ["patient-details", mobileComposerVisibleReserve.patientDetails], + ] as const)("%s clearance token matches the TS constant", (kind, reserve) => { + const token = new RegExp(`--phone-dock-${kind}-clearance:\\s*([\\d.]+)rem`).exec(globalsCss); + expect(token, `missing --phone-dock-${kind}-clearance`).not.toBeNull(); + expect(reserve).toContain(`${token?.[1]}rem`); + }); +}); From 9bccbf433ccaa5243b287a97c756945a9fb9729b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 19:15:18 +0000 Subject: [PATCH 02/21] docs(ledger): record review for patient-interactions-drug-alerts Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X12okkARLhFQ2zgcPoFp54 --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 8ae7ffab76..bd1239f607 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -920,3 +920,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-12 | 1849 | 34e5581c4418ef8a08909dff9ecf91d4a8f622de | full PR diff and unresolved review feedback | P1 setup-only PAT remained accessible through gh credential storage; removed agent-phase PAT persistence and retained safe base/shim changes | check:codex-cloud PASS; docs:check-inventory PASS; focused Vitest blocked by active repository lease | | 2026-08-12 | claude/filter-facet-formulation | 9262d89701808c58e3812a60b982596bb3f2b218 | filter contract PR B: formulation facet adoption (derive domains, union counts, evict query-replacing presets) | PR #1858 opened; domain converted from 12 radios to 9 derived facet chips (Biological/Social/Cultural carried by 0 of 12 mechanisms, removed per derive-dont-declare); union counts verified monotonic and non-additive (Affect 9 OR Risk 4 = 10); zero-yield options render as focusable dead ends, never on an already-selected option; Pattern group evicted from the sheet to AnswerSuggestionChips (all 5 presets, old slice(0,4) left one unreachable); ResultFilterFacetChips exported so desktop rail and sheet share one renderer; desktop select of 13 retired. Shares result-filter-control.tsx with PR #1857 - land #1857 first, its accessible-name fix then covers these chips | verify:pr-local failed:(none) not reached:(none), all 15 steps green including build with the server stopped; full unit suite 558/558 files, 6101 passed 4 skipped, zero failures; formulation.test.ts 11 passed with 3 new contract tests; bundle-budget production 1296.1 KiB and mockups 285.1 KiB within tolerance on a freshness-verified build; browser proof 1440/390/320px, 0px overflow, 9 chips not 12, 0 selects, 5 suggestion chips, live dead-end-to-selectable transition on union widening, phone sheet radiogroup count 0 | | 2026-08-13 | PR-1845 | 795ce38e165ce44e167038839a914b2efdb77dae | current-main merge, CI repair, and open-comment review | preserved the current-main ledger; canonicalized whitespace-only q to the non-empty legacy query; replaced the stale clear-filter Playwright locator; no unresolved review threads remained | pending fresh GitHub CI | +| 2026-08-12 | claude/patient-interactions-drug-alerts-3tztvw | d87f7b115deae465617b9748d05fd94619c26158 | patient details dock pill + drug-interaction alerting (medications/prescribing) | self-reviewed; shipped PR #1874 | verify:pr-local all 16 stages green; test 567 files/6206 passed; phone-chrome static contracts 129 passed; bundle-budget production 1314.1 KiB within tolerance; verify:phone-chrome browser stages delegated to CI (Playwright 1194 vs 1234 drift, #255) | From 76a22e4a1106a205fe9c3055541c3404bf1021a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 19:34:37 +0000 Subject: [PATCH 03/21] feat(medications): surface interactions on the record page and ice every verdict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three follow-ups on the interaction work. Interactions are now stated on the medication page itself, not only behind the patient sheet. `MedicationInteractionCallout` renders above the record when the entered profile matches: the three highest-severity interactions, each naming the counterparty as a link to its own record, the verbatim catalogue wording, a "+N more" count, and buttons through to the full Key Interactions section and the patient sheet. Arriving from a result row flagged "2 interactions" no longer means hunting for them. The counterparty is also linked from each row of the in-sheet block. The tab the "See Key Interactions" control switches to is derived from the section-type table via `medicationTabForSectionType` rather than hardcoded, so a section that moves between tabs cannot leave the control pointing at the tab it used to be in. Every verdict tone now carries its own glyph on the result cards — shield -check for "no interaction found", help-circle for "needs manual review", alongside the existing ban and triangle. `ClinicalBadge` only auto-ices danger and warning to keep ordinary badges quiet; that default is right in general and wrong for a safety verdict, where "checked, nothing found" and "could not check" have to be told apart without reading the border colour. Overridden for this badge only, not in the shared default. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X12okkARLhFQ2zgcPoFp54 --- .../medication-considerations.tsx | 224 ++++++++++++++++-- .../medication-nav-header.tsx | 14 ++ .../medication-record-page.tsx | 20 +- tests/in-page-nav-route-sections.dom.test.tsx | 5 +- ...dication-interaction-surfaces.dom.test.tsx | 106 ++++++++- tests/medication-record-page.dom.test.tsx | 1 + 6 files changed, 346 insertions(+), 24 deletions(-) diff --git a/src/components/clinical-dashboard/medication-considerations.tsx b/src/components/clinical-dashboard/medication-considerations.tsx index 7320ecf1f7..fde5b3d017 100644 --- a/src/components/clinical-dashboard/medication-considerations.tsx +++ b/src/components/clinical-dashboard/medication-considerations.tsx @@ -1,11 +1,26 @@ "use client"; -import { ClipboardList, Pill } from "lucide-react"; +import { + Ban, + ChevronRight, + CircleHelp, + ClipboardList, + Info, + Pill, + ShieldCheck, + TriangleAlert, + type LucideIcon, +} from "lucide-react"; +import Link from "next/link"; import { useMemo } from "react"; -import { BadgeCluster, type ClinicalBadgeItem } from "@/components/clinical-dashboard/clinical-badge"; +import { BadgeCluster, ClinicalBadge, type ClinicalBadgeItem } from "@/components/clinical-dashboard/clinical-badge"; import { usePatientProfile } from "@/components/clinical-dashboard/patient-profile-context"; -import { evaluateMedicationInteractions, type MedicationVerdict } from "@/lib/medication-interactions"; +import { + evaluateMedicationInteractions, + type MedicationInteraction, + type MedicationVerdict, +} from "@/lib/medication-interactions"; import { evaluatePatientAlerts, noticeToneForSemanticTone, @@ -38,11 +53,35 @@ export function medicationVerdictRingClass(tone: SemanticTone): string | null { return byTone[tone]; } +/** + * Glyph for a verdict badge — one per state, including the two that the shared + * badge default deliberately leaves icon-free. + * + * `ClinicalBadge` only auto-ices danger and warning, to keep ordinary metadata + * badges quiet. That default is right in general and wrong here: this badge is + * the card's safety verdict, so "checked, nothing found" and "could not check" + * have to be distinguishable from each other — and from the two alert states — + * without relying on the border colour. Overridden for this badge only, by + * passing an explicit icon rather than by changing the shared default. + */ +export function verdictIcon(tone: SemanticTone): LucideIcon { + const byTone: Record = { + danger: Ban, + warning: TriangleAlert, + success: ShieldCheck, + neutral: CircleHelp, + clinical: ClipboardList, + info: Info, + }; + return byTone[tone]; +} + /** * One badge summarising the composed verdict for a result row. * - * Returns null when there is nothing to say — no patient profile entered, or an - * unremarkable medication with no findings and nothing unread. + * Never returns null once a profile exists: "no interaction found" and "needs + * manual review" are findings the clinician should see stated, not absences to + * be inferred from a missing badge. */ export function verdictSummaryBadge(verdict: MedicationVerdict): ClinicalBadgeItem | null { const findings = verdict.interactionCount + verdict.considerationCount; @@ -54,14 +93,159 @@ export function verdictSummaryBadge(verdict: MedicationVerdict): ClinicalBadgeIt if (verdict.considerationCount > 0) { parts.push(`${verdict.considerationCount} alert${verdict.considerationCount === 1 ? "" : "s"}`); } - return { id: "patient-verdict", label: parts.join(" · "), tone: verdict.tone }; + return { + id: "patient-verdict", + label: parts.join(" · "), + tone: verdict.tone, + icon: verdictIcon(verdict.tone), + }; } if (verdict.incomplete) { - return { id: "patient-verdict", label: "Needs manual review", tone: "neutral" }; + return { + id: "patient-verdict", + label: "Needs manual review", + tone: "neutral", + icon: verdictIcon("neutral"), + }; } - return { id: "patient-verdict", label: "No interaction found", tone: "success" }; + return { + id: "patient-verdict", + label: "No interaction found", + tone: "success", + icon: verdictIcon("success"), + }; } +/** + * "Open " — the other half of the interaction is a medication in + * this catalogue, and reading its own page is the obvious next step from an + * alert. Rendered as a `` per the repo's internal-navigation rule. + */ +function CounterpartyLink({ interaction }: { interaction: MedicationInteraction }) { + return ( + + Open {interaction.counterpartyName} +
)} - {result.unresolvedRowCount > 0 ? ( + {/* Two distinct reasons this medication cannot be shown as clear, and they + need different words. `dataAvailable === false` means the catalogue + carries no interaction rows for it at all, in which case + `unresolvedRowCount` is a synthetic 1 against a `totalRowCount` of 0 — + phrasing that as "1 of 0 rows" would be nonsense. */} + {!result.dataAvailable ? ( + + This catalogue carries no interaction data for this medication, so nothing could be cross-checked against the + entered list. Review manually. It is not shown as clear. + + ) : result.unresolvedRowCount > 0 ? ( {result.unresolvedRowCount} of this medication’s {result.totalRowCount} interaction{" "} {result.totalRowCount === 1 ? "row" : "rows"} could not be matched automatically — review the Key Interactions diff --git a/src/components/clinical-dashboard/patient-profile-panel.tsx b/src/components/clinical-dashboard/patient-profile-panel.tsx index d205ee1d31..2361feb8ef 100644 --- a/src/components/clinical-dashboard/patient-profile-panel.tsx +++ b/src/components/clinical-dashboard/patient-profile-panel.tsx @@ -1,7 +1,7 @@ "use client"; import { Eraser, Plus, UserRound, X } from "lucide-react"; -import { useEffect, useId, useState } from "react"; +import { useId, useState } from "react"; import { usePatientProfile } from "@/components/clinical-dashboard/patient-profile-context"; import { SegmentedControl } from "@/components/ui/segmented-control"; @@ -107,9 +107,15 @@ function MedicationPicker({ const [term, setTerm] = useState(""); const listId = useId(); - useEffect(() => { + // Clearing the profile empties the search box too. Done as React's sanctioned + // "adjust state during render" reconciliation rather than an effect: a + // setState inside useEffect renders once with the stale value and trips + // `react-hooks/set-state-in-effect`. + const [syncedNonce, setSyncedNonce] = useState(resetNonce); + if (syncedNonce !== resetNonce) { + setSyncedNonce(resetNonce); setTerm(""); - }, [resetNonce]); + } const options = catalogueMedicationOptions(); const selectedSet = new Set(selected); @@ -228,22 +234,82 @@ export function PatientProfilePanel({
- updateField("ageYears", value)} testId="patient-age" min={PATIENT_PROFILE_NUMERIC_BOUNDS.ageYears.min} max={PATIENT_PROFILE_NUMERIC_BOUNDS.ageYears.max} /> - updateField("egfr", value)} testId="patient-egfr" min={PATIENT_PROFILE_NUMERIC_BOUNDS.egfr.min} max={PATIENT_PROFILE_NUMERIC_BOUNDS.egfr.max} /> - updateField("crcl", value)} testId="patient-crcl" min={PATIENT_PROFILE_NUMERIC_BOUNDS.crcl.min} max={PATIENT_PROFILE_NUMERIC_BOUNDS.crcl.max} /> - updateField("qtc", value)} testId="patient-qtc" min={PATIENT_PROFILE_NUMERIC_BOUNDS.qtc.min} max={PATIENT_PROFILE_NUMERIC_BOUNDS.qtc.max} /> + updateField("ageYears", value)} + testId="patient-age" + min={PATIENT_PROFILE_NUMERIC_BOUNDS.ageYears.min} + max={PATIENT_PROFILE_NUMERIC_BOUNDS.ageYears.max} + /> + updateField("egfr", value)} + testId="patient-egfr" + min={PATIENT_PROFILE_NUMERIC_BOUNDS.egfr.min} + max={PATIENT_PROFILE_NUMERIC_BOUNDS.egfr.max} + /> + updateField("crcl", value)} + testId="patient-crcl" + min={PATIENT_PROFILE_NUMERIC_BOUNDS.crcl.min} + max={PATIENT_PROFILE_NUMERIC_BOUNDS.crcl.max} + /> + updateField("qtc", value)} + testId="patient-qtc" + min={PATIENT_PROFILE_NUMERIC_BOUNDS.qtc.min} + max={PATIENT_PROFILE_NUMERIC_BOUNDS.qtc.max} + />
- updateField("scr", value)} testId="patient-scr" min={scrBounds.min} max={scrBounds.max} /> + updateField("scr", value)} + testId="patient-scr" + min={scrBounds.min} + max={scrBounds.max} + />
- Creatinine unit - + + Creatinine unit + +
- Hepatic impairment - updateField("hepatic", value === "none" ? null : value)} options={HEPATIC_OPTIONS} layout="fit" /> + + Hepatic impairment + + updateField("hepatic", value === "none" ? null : value)} + options={HEPATIC_OPTIONS} + layout="fit" + />
@@ -254,7 +320,16 @@ export function PatientProfilePanel({ {ALLERGY_OPTIONS.map((option) => { const active = allergies.has(option.value); return ( - + ); })}
@@ -262,11 +337,19 @@ export function PatientProfilePanel({
- updateField("pregnant", !profile.pregnant)} aria-label="Pregnancy" /> + updateField("pregnant", !profile.pregnant)} + aria-label="Pregnancy" + /> Pregnancy - updateField("breastfeeding", !profile.breastfeeding)} aria-label="Breastfeeding" /> + updateField("breastfeeding", !profile.breastfeeding)} + aria-label="Breastfeeding" + /> Breastfeeding
diff --git a/src/lib/medication-interaction-lexicon.ts b/src/lib/medication-interaction-lexicon.ts index fc2f428fca..01f2b730af 100644 --- a/src/lib/medication-interaction-lexicon.ts +++ b/src/lib/medication-interaction-lexicon.ts @@ -59,45 +59,221 @@ const CATALOGUE_TERMS: LexiconTerm[] = [ kind: "catalogue", select: { subclassIncludes: ["Beta Blocker", "Beta-Blocker"] }, }, - { id: "acei", surfaces: ["acei", "aceis", "ace inhibitors", "ace inhibitor"], kind: "catalogue", select: { subclassIncludes: ["ACE Inhibitor"] } }, + { + id: "acei", + surfaces: ["acei", "aceis", "ace inhibitors", "ace inhibitor"], + kind: "catalogue", + select: { subclassIncludes: ["ACE Inhibitor"] }, + }, { id: "arbs", surfaces: ["arbs", "arb"], kind: "catalogue", select: { subclassIncludes: ["ARB"] } }, - { id: "diuretics", surfaces: ["diuretics", "diuretic"], kind: "catalogue", select: { subclassIncludes: ["Diuretic"] } }, - { id: "thiazide-diuretics", surfaces: ["thiazide diuretics", "thiazides", "thiazide"], kind: "catalogue", select: { subclassIncludes: ["Thiazide"] } }, - { id: "loop-diuretics", surfaces: ["loop diuretics", "loop diuretic"], kind: "catalogue", select: { subclassIncludes: ["Loop Diuretic"] } }, - { id: "antihypertensives", surfaces: ["antihypertensives", "antihypertensive"], kind: "catalogue", select: { classes: ["Antihypertensive"] } }, - { id: "anticoagulants", surfaces: ["anticoagulants", "anticoagulant", "doacs", "doac"], kind: "catalogue", select: { classes: ["Anticoagulant"], denySlugs: ["aspirin"] } }, - { id: "antiplatelets", surfaces: ["antiplatelets", "antiplatelet"], kind: "catalogue", select: { subclassIncludes: ["Antiplatelet"] } }, - { id: "macrolides", surfaces: ["macrolides", "macrolide"], kind: "catalogue", select: { subclassIncludes: ["Macrolide"] } }, - { id: "fluoroquinolones", surfaces: ["fluoroquinolones", "fluoroquinolone", "quinolones"], kind: "catalogue", select: { subclassIncludes: ["Fluoroquinolone"] } }, - { id: "aminoglycosides", surfaces: ["aminoglycosides", "aminoglycoside"], kind: "catalogue", select: { subclassIncludes: ["Aminoglycoside"] } }, - { id: "ppis", surfaces: ["ppis", "ppi", "proton pump inhibitors"], kind: "catalogue", select: { subclassIncludes: ["Proton Pump"] } }, - { id: "antacids", surfaces: ["antacids", "antacid"], kind: "catalogue", select: { slugs: ["magnesium-oxide", "calcium-carbonate"] } }, - { id: "statins", surfaces: ["statins", "statin"], kind: "catalogue", select: { subclassIncludes: ["Statin", "HMG"] } }, - { id: "gabapentinoids", surfaces: ["gabapentinoids", "gabapentinoid"], kind: "catalogue", select: { slugs: ["gabapentin", "pregabalin"] } }, - { id: "anticholinergics", surfaces: ["anticholinergics", "anticholinergic", "atropine-like medicines"], kind: "catalogue", select: { subclassIncludes: ["Anticholinergic"] } }, - { id: "antihistamines", surfaces: ["antihistamines", "antihistamine"], kind: "catalogue", select: { subclassIncludes: ["Antihistamine"] } }, - { id: "corticosteroids", surfaces: ["corticosteroids", "corticosteroid", "steroids", "steroid"], kind: "catalogue", select: { classes: ["Steroid"] } }, - { id: "z-drugs", surfaces: ["z-drugs", "z drugs", "zolpidem-type hypnotics"], kind: "catalogue", select: { subclassIncludes: ["Z-Drug"] } }, + { + id: "diuretics", + surfaces: ["diuretics", "diuretic"], + kind: "catalogue", + select: { subclassIncludes: ["Diuretic"] }, + }, + { + id: "thiazide-diuretics", + surfaces: ["thiazide diuretics", "thiazides", "thiazide"], + kind: "catalogue", + select: { subclassIncludes: ["Thiazide"] }, + }, + { + id: "loop-diuretics", + surfaces: ["loop diuretics", "loop diuretic"], + kind: "catalogue", + select: { subclassIncludes: ["Loop Diuretic"] }, + }, + { + id: "antihypertensives", + surfaces: ["antihypertensives", "antihypertensive"], + kind: "catalogue", + select: { classes: ["Antihypertensive"] }, + }, + { + id: "anticoagulants", + surfaces: ["anticoagulants", "anticoagulant", "doacs", "doac"], + kind: "catalogue", + select: { classes: ["Anticoagulant"], denySlugs: ["aspirin"] }, + }, + { + id: "antiplatelets", + surfaces: ["antiplatelets", "antiplatelet"], + kind: "catalogue", + select: { subclassIncludes: ["Antiplatelet"] }, + }, + { + id: "macrolides", + surfaces: ["macrolides", "macrolide"], + kind: "catalogue", + select: { subclassIncludes: ["Macrolide"] }, + }, + { + id: "fluoroquinolones", + surfaces: ["fluoroquinolones", "fluoroquinolone", "quinolones"], + kind: "catalogue", + select: { subclassIncludes: ["Fluoroquinolone"] }, + }, + { + id: "aminoglycosides", + surfaces: ["aminoglycosides", "aminoglycoside"], + kind: "catalogue", + select: { subclassIncludes: ["Aminoglycoside"] }, + }, + { + id: "ppis", + surfaces: ["ppis", "ppi", "proton pump inhibitors"], + kind: "catalogue", + select: { subclassIncludes: ["Proton Pump"] }, + }, + { + id: "antacids", + surfaces: ["antacids", "antacid"], + kind: "catalogue", + select: { slugs: ["magnesium-oxide", "calcium-carbonate"] }, + }, + { + id: "statins", + surfaces: ["statins", "statin"], + kind: "catalogue", + select: { subclassIncludes: ["Statin", "HMG"] }, + }, + { + id: "gabapentinoids", + surfaces: ["gabapentinoids", "gabapentinoid"], + kind: "catalogue", + select: { slugs: ["gabapentin", "pregabalin"] }, + }, + { + id: "anticholinergics", + surfaces: ["anticholinergics", "anticholinergic", "atropine-like medicines"], + kind: "catalogue", + select: { subclassIncludes: ["Anticholinergic"] }, + }, + { + id: "antihistamines", + surfaces: ["antihistamines", "antihistamine"], + kind: "catalogue", + select: { subclassIncludes: ["Antihistamine"] }, + }, + { + id: "corticosteroids", + surfaces: ["corticosteroids", "corticosteroid", "steroids", "steroid"], + kind: "catalogue", + select: { classes: ["Steroid"] }, + }, + { + id: "z-drugs", + surfaces: ["z-drugs", "z drugs", "zolpidem-type hypnotics"], + kind: "catalogue", + select: { subclassIncludes: ["Z-Drug"] }, + }, ]; const NON_CATALOGUE_TERMS: LexiconTerm[] = [ - { id: "alcohol", surfaces: ["alcohol", "alcohol-containing preparations", "ethanol"], kind: "nonDrug", note: "Substance use, not a prescribable catalogue entry." }, - { id: "grapefruit", surfaces: ["grapefruit", "grapefruit juice"], kind: "nonDrug", note: "Dietary CYP3A4 inhibitor." }, - { id: "acidic-drinks", surfaces: ["acidic drinks", "coffee", "juice", "soft drinks", "cola"], kind: "nonDrug", note: "Buccal/oral absorption timing, not a drug interaction." }, - { id: "smoking", surfaces: ["smoking", "cigarette smoke", "tobacco smoke"], kind: "nonDrug", note: "CYP1A2 induction by polycyclic aromatic hydrocarbons, not by nicotine." }, + { + id: "alcohol", + surfaces: ["alcohol", "alcohol-containing preparations", "ethanol"], + kind: "nonDrug", + note: "Substance use, not a prescribable catalogue entry.", + }, + { + id: "grapefruit", + surfaces: ["grapefruit", "grapefruit juice"], + kind: "nonDrug", + note: "Dietary CYP3A4 inhibitor.", + }, + { + id: "acidic-drinks", + surfaces: ["acidic drinks", "coffee", "juice", "soft drinks", "cola"], + kind: "nonDrug", + note: "Buccal/oral absorption timing, not a drug interaction.", + }, + { + id: "smoking", + surfaces: ["smoking", "cigarette smoke", "tobacco smoke"], + kind: "nonDrug", + note: "CYP1A2 induction by polycyclic aromatic hydrocarbons, not by nicotine.", + }, { id: "food", surfaces: ["food", "dairy", "milk", "high-fat meals", "tyramine"], kind: "nonDrug", note: "Dietary." }, - { id: "st-johns-wort", surfaces: ["st john's wort", "st johns wort", "hypericum"], kind: "external", note: "Herbal CYP3A4 inducer; not stocked in the catalogue." }, - { id: "azole-antifungals", surfaces: ["ketoconazole", "azoles", "azole antifungals"], kind: "external", note: "Ketoconazole is not in the catalogue; fluconazole/itraconazole resolve by name." }, - { id: "barbiturates", surfaces: ["barbiturates", "barbiturate", "phenobarbital", "phenobarbitone"], kind: "external", note: "Not stocked in the catalogue." }, - { id: "antiretrovirals", surfaces: ["antiretrovirals", "antiretroviral", "protease inhibitors"], kind: "external", note: "Ritonavir and relatives are outside the catalogue." }, - { id: "cotrimoxazole", surfaces: ["bactrim", "co-trimoxazole", "cotrimoxazole"], kind: "external", note: "Combination product; trimethoprim resolves by name." }, - { id: "cyp-inhibitors", surfaces: ["cyp inhibitors", "cyp inhibitor", "strong cyp inhibitors", "cyp3a4 inhibitors", "cyp2d6 inhibitors", "cyp1a2 inhibitors", "cyp2c19 inhibitors"], kind: "mechanism", note: "Enumerating inhibitors needs pharmacokinetic data the catalogue does not carry." }, - { id: "cyp-inducers", surfaces: ["cyp inducers", "cyp inducer", "strong cyp inducers", "cyp3a4 inducers", "enzyme inducers"], kind: "mechanism", note: "See cyp-inhibitors." }, - { id: "cyp-substrates", surfaces: ["cyp substrates", "cyp2d6 substrates", "cyp3a4 substrates", "narrow therapeutic index drugs"], kind: "mechanism" }, + { + id: "st-johns-wort", + surfaces: ["st john's wort", "st johns wort", "hypericum"], + kind: "external", + note: "Herbal CYP3A4 inducer; not stocked in the catalogue.", + }, + { + id: "azole-antifungals", + surfaces: ["ketoconazole", "azoles", "azole antifungals"], + kind: "external", + note: "Ketoconazole is not in the catalogue; fluconazole/itraconazole resolve by name.", + }, + { + id: "barbiturates", + surfaces: ["barbiturates", "barbiturate", "phenobarbital", "phenobarbitone"], + kind: "external", + note: "Not stocked in the catalogue.", + }, + { + id: "antiretrovirals", + surfaces: ["antiretrovirals", "antiretroviral", "protease inhibitors"], + kind: "external", + note: "Ritonavir and relatives are outside the catalogue.", + }, + { + id: "cotrimoxazole", + surfaces: ["bactrim", "co-trimoxazole", "cotrimoxazole"], + kind: "external", + note: "Combination product; trimethoprim resolves by name.", + }, + { + id: "cyp-inhibitors", + surfaces: [ + "cyp inhibitors", + "cyp inhibitor", + "strong cyp inhibitors", + "cyp3a4 inhibitors", + "cyp2d6 inhibitors", + "cyp1a2 inhibitors", + "cyp2c19 inhibitors", + ], + kind: "mechanism", + note: "Enumerating inhibitors needs pharmacokinetic data the catalogue does not carry.", + }, + { + id: "cyp-inducers", + surfaces: ["cyp inducers", "cyp inducer", "strong cyp inducers", "cyp3a4 inducers", "enzyme inducers"], + kind: "mechanism", + note: "See cyp-inhibitors.", + }, + { + id: "cyp-substrates", + surfaces: ["cyp substrates", "cyp2d6 substrates", "cyp3a4 substrates", "narrow therapeutic index drugs"], + kind: "mechanism", + }, { id: "pgp", surfaces: ["p-gp", "p-gp inhibitors", "p-glycoprotein", "oatp"], kind: "mechanism" }, - { id: "cns-depressants", surfaces: ["cns depressants", "cns depressant", "sedatives", "sedative", "sedating drugs"], kind: "mechanism", note: "Deliberately unenumerable: the surface spans benzodiazepines, opioids, antihistamines, alcohol and more." }, - { id: "qtc-prolonging", surfaces: ["qtc prolonging drugs", "qt prolonging drugs", "concurrent qtc prolonging drugs", "qt-prolonging agents"], kind: "mechanism", note: "The catalogue carries QTc risk per drug but not a curated interacting set." }, - { id: "serotonergic", surfaces: ["serotonergic drugs", "serotonergic agents", "other serotonergics"], kind: "mechanism" }, + { + id: "cns-depressants", + surfaces: ["cns depressants", "cns depressant", "sedatives", "sedative", "sedating drugs"], + kind: "mechanism", + note: "Deliberately unenumerable: the surface spans benzodiazepines, opioids, antihistamines, alcohol and more.", + }, + { + id: "qtc-prolonging", + surfaces: [ + "qtc prolonging drugs", + "qt prolonging drugs", + "concurrent qtc prolonging drugs", + "qt-prolonging agents", + ], + kind: "mechanism", + note: "The catalogue carries QTc risk per drug but not a curated interacting set.", + }, + { + id: "serotonergic", + surfaces: ["serotonergic drugs", "serotonergic agents", "other serotonergics"], + kind: "mechanism", + }, ]; export const INTERACTION_LEXICON: readonly LexiconTerm[] = Object.freeze([...CATALOGUE_TERMS, ...NON_CATALOGUE_TERMS]); @@ -106,9 +282,9 @@ export const UNENUMERATED_MECHANISM_TERM_IDS: ReadonlySet = new Set( ); export const LEXICON_SURFACES_BY_LENGTH: readonly { surface: string; term: LexiconTerm }[] = Object.freeze( - INTERACTION_LEXICON.flatMap((term) => term.surfaces.map((surface) => ({ surface: surface.toLowerCase(), term }))).sort( - (a, b) => b.surface.length - a.surface.length, - ), + INTERACTION_LEXICON.flatMap((term) => + term.surfaces.map((surface) => ({ surface: surface.toLowerCase(), term })), + ).sort((a, b) => b.surface.length - a.surface.length), ); export function selectCatalogueSlugs(select: CatalogueSelector, records: readonly MedicationRecord[]): string[] { diff --git a/src/lib/medication-interactions.ts b/src/lib/medication-interactions.ts index b011d07581..b8b9011bcf 100644 --- a/src/lib/medication-interactions.ts +++ b/src/lib/medication-interactions.ts @@ -1,18 +1,44 @@ // Patient-medication-list → drug-interaction evaluation. +// +// The companion to `medication-patient-alerts.ts`. That module answers "does +// this patient's *physiology* change how I use this drug?"; this one answers +// "does this drug collide with something the patient is *already taking*?". +// Both are pure and framework-free so the prescribing workspace, the medication +// detail page, and unit tests can share them. +// +// Input is the generated `data/medication-interaction-index.json` (see +// `scripts/build-medication-interaction-index.ts`): per source medication, one +// entry per `Key Interactions` row, carrying normalised severity and the +// resolved catalogue counterparties. The verbatim row text is NOT duplicated +// into the index — it is read back off the `MedicationRecord` by `rowIndex`. +// +// THE ONE INVARIANT THAT MATTERS +// `success` (green) must be unreachable on incomplete analysis. Three separate +// things can make the analysis incomplete, and each has to keep green off: +// +// 1. A row whose counterparty could not be resolved → `unresolvedRowCount`. +// 2. A medication the index has no entry for at all → `dataAvailable: false`, +// which reports a synthetic `unresolvedRowCount` of 1. Without this, a drug +// we know nothing about scored zero unresolved rows and composed straight +// to green — a false all-clear on absent data, the worst failure this +// module has. +// 3. An unparsed severity token → `SEVERITY_TONE.unknown` is `neutral`, never +// `success`. +// +// Missing analysis is not the same as a negative finding. `composeMedicationVerdict` +// is where that is finally enforced. +// +// SYMMETRY +// Interaction prose is not reliably symmetric: drug A's rows may name drug B +// while drug B's rows never mention A. Evaluating only the viewed drug's rows +// therefore misses real pairs in one direction. Both directions are scanned and +// the results de-duplicated, preferring the copy that carries source text. import interactionIndex from "../../data/medication-interaction-index.json"; import { SEMANTIC_TONE_PRIORITY, type SemanticTone } from "@/lib/semantic-tone"; export type InteractionSeverity = - | "critical" - | "high" - | "moderate" - | "caution" - | "low" - | "none" - | "safe" - | "beneficial" - | "unknown"; + "critical" | "high" | "moderate" | "caution" | "low" | "none" | "safe" | "beneficial" | "unknown"; export type MedicationInteraction = { id: string; diff --git a/tests/medication-interaction-lexicon-coverage.test.ts b/tests/medication-interaction-lexicon-coverage.test.ts index 19a5281b4a..ed11cdc61a 100644 --- a/tests/medication-interaction-lexicon-coverage.test.ts +++ b/tests/medication-interaction-lexicon-coverage.test.ts @@ -70,9 +70,7 @@ describe("interaction lexicon coverage", () => { .flatMap((entry) => entry.rows) .filter( (row) => - row.termIds.length > 0 && - row.counterparties.length === 0 && - row.termIds.every((id) => mechanismIds.has(id)), + row.termIds.length > 0 && row.counterparties.length === 0 && row.termIds.every((id) => mechanismIds.has(id)), ); expect(mechanismOnly.length).toBeGreaterThan(0); expect(mechanismOnly.every((row) => !row.resolved)).toBe(true); diff --git a/tests/medication-interaction-surfaces.dom.test.tsx b/tests/medication-interaction-surfaces.dom.test.tsx index 1fe73eb488..1af8cb380c 100644 --- a/tests/medication-interaction-surfaces.dom.test.tsx +++ b/tests/medication-interaction-surfaces.dom.test.tsx @@ -71,6 +71,23 @@ describe("interactions block on the medication detail page", () => { expect(screen.getByText(/is not shown as clear/i)).toBeInTheDocument(); }); + it("says so plainly when the catalogue has no interaction data at all", () => { + // `dataAvailable: false` reports a synthetic unresolvedRowCount of 1 against + // a totalRowCount of 0, so the ordinary "N of M rows" copy would read + // "1 of this medication's 0 interaction rows". Different reason, different words. + seedProfile({ medications: ["paracetamol"] }); + const record = { ...(getMedicationRecord("acamprosate") as MedicationRecord), slug: "not-in-the-index" }; + render( + + + , + ); + + expect(screen.getByText(/carries no interaction data for this medication/i)).toBeInTheDocument(); + expect(screen.queryByText(/of this medication.s 0 interaction/i)).not.toBeInTheDocument(); + expect(screen.getByText(/not shown as clear/i)).toBeInTheDocument(); + }); + it("never claims an all-clear for a medication carrying unresolved rows", () => { seedProfile({ medications: ["paracetamol"] }); const { container } = renderFor("bupropion-sr"); From ff417052ada202f1db7da358fb2ef35e43a5f04b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 21:22:11 +0000 Subject: [PATCH 20/21] fix(medications): regenerate stale interaction index and gate it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The committed `data/medication-interaction-index.json` no longer matched the lexicon and build script on this branch — several commits changed both without regenerating the artefact the UI actually reads, so the app was serving safety verdicts derived from an older resolution pass. Nothing caught it: I added `check:medication-interactions` when the artefact was introduced but never wired it into `verify:pr-local`, so a stale index sailed through a green gate. It now runs on every executable scope, alongside `check:rag:fixtures`, with the plan self-test updated to match. Regenerating moves two numbers in opposite directions, and both are improvements: - `resolvedRows` 400 → 355, because a row carrying any unenumerated mechanism is now unresolved even when it also matched a named drug. "CYP3A4 inhibitors (Clarithromycin, Ketoconazole)" used to resolve on clarithromycin and count as fully read, implying the whole class had been checked. More medications now hold at grey. - `rowsWithCatalogueTarget` 381 → 417, from the shorter catalogue-name matching floor. The coverage ratchet is re-anchored accordingly and now pins both numbers, because a single "resolved rows" figure conflates coverage with resolution strictness and would otherwise fight a legitimate tightening. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01X12okkARLhFQ2zgcPoFp54 --- data/medication-interaction-index.json | 978 ++++++++++++------ scripts/build-medication-interaction-index.ts | 27 +- scripts/verify-pr-local.mjs | 36 +- ...ation-interaction-lexicon-coverage.test.ts | 21 +- .../patient-details-dock-action.dom.test.tsx | 4 +- 5 files changed, 734 insertions(+), 332 deletions(-) diff --git a/data/medication-interaction-index.json b/data/medication-interaction-index.json index b4cc5d6e8d..349623cfa6 100644 --- a/data/medication-interaction-index.json +++ b/data/medication-interaction-index.json @@ -3,10 +3,10 @@ "generatedFrom": "data/medications-snapshot.json", "sourceRowCount": 523, "stats": { - "resolvedRows": 400, - "unresolvedRows": 123, - "rowsWithCatalogueTarget": 381, - "medicationsWithUnresolvedRows": 116 + "resolvedRows": 355, + "unresolvedRows": 168, + "rowsWithCatalogueTarget": 417, + "medicationsWithUnresolvedRows": 149 }, "names": { "acamprosate": "Acamprosate", @@ -345,7 +345,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "low", - "counterparties": ["diazepam", "disulfiram", "imipramine"], + "counterparties": ["diazepam", "disulfiram", "imipramine", "naltrexone"], "termIds": [], "resolved": true } @@ -374,10 +374,10 @@ "tramadol-ir" ], "termIds": ["cns-depressants", "opioids"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "buprenorphine-naloxone": { "rows": [ @@ -411,7 +411,7 @@ "tramadol-ir" ], "termIds": ["alcohol", "benzodiazepines", "cns-depressants", "gabapentinoids", "opioids"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -431,7 +431,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "bupropion-sr": { "rows": [ @@ -439,8 +439,8 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "high", - "counterparties": [], - "termIds": [], + "counterparties": ["levodopa-benserazide"], + "termIds": ["alcohol", "cyp-substrates"], "resolved": false } ], @@ -452,7 +452,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "high", - "counterparties": [], + "counterparties": ["amitriptyline", "chlorpromazine", "metronidazole", "phenytoin"], "termIds": ["alcohol"], "resolved": true } @@ -466,11 +466,11 @@ "rowIndex": 0, "severity": "high", "counterparties": ["carbamazepine", "phenytoin"], - "termIds": ["antiretrovirals", "cyp-inducers", "st-johns-wort"], - "resolved": true + "termIds": ["antiretrovirals", "cns-depressants", "cyp-inducers", "st-johns-wort"], + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "naltrexone": { "rows": [ @@ -507,12 +507,14 @@ "rowIndex": 0, "severity": "caution", "counterparties": [ + "clozapine", "nicotine-lozenge", "nicotine-mouth-spray", "nicotine-patch", - "nicotine-sublingual-tablet" + "nicotine-sublingual-tablet", + "olanzapine-pamoate-lai" ], - "termIds": ["acidic-drinks"], + "termIds": ["acidic-drinks", "smoking"], "resolved": true } ], @@ -525,12 +527,17 @@ "rowIndex": 0, "severity": "caution", "counterparties": [ + "clomipramine", + "clozapine", + "fluvoxamine", + "imipramine", "nicotine-lozenge", "nicotine-mouth-spray", "nicotine-patch", - "nicotine-sublingual-tablet" + "nicotine-sublingual-tablet", + "olanzapine-pamoate-lai" ], - "termIds": ["acidic-drinks"], + "termIds": ["acidic-drinks", "smoking"], "resolved": true } ], @@ -542,12 +549,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "caution", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": ["nicotine-mouth-spray", "nicotine-patch", "nicotine-sublingual-tablet"], + "termIds": ["acidic-drinks"], + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "nicotine-mouth-spray": { "rows": [ @@ -568,7 +575,13 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "high", - "counterparties": ["nicotine-lozenge", "nicotine-mouth-spray", "nicotine-sublingual-tablet"], + "counterparties": [ + "clozapine", + "nicotine-lozenge", + "nicotine-mouth-spray", + "nicotine-sublingual-tablet", + "olanzapine-pamoate-lai" + ], "termIds": ["smoking"], "resolved": true } @@ -638,10 +651,10 @@ "severity": "moderate", "counterparties": [], "termIds": ["alcohol", "cns-depressants"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "diphenhydramine": { "rows": [ @@ -652,23 +665,30 @@ "counterparties": [ "alprazolam", "amisulpride", + "amitriptyline", "aripiprazole", "aripiprazole-lai", "asenapine", + "benzatropine", "brexpiprazole", "buprenorphine-naloxone", "buprenorphine-patch", "buprenorphine-sl-depot", "cariprazine", "chlorpromazine", + "clomipramine", "clonazepam", "clozapine", "codeine", "diazepam", + "doxepin", "fentanyl", "flupentixol-decanoate", "haloperidol-decanoate", "hydromorphone-ir-iv", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", "levomepromazine", "loperamide", "lorazepam", @@ -678,20 +698,26 @@ "morphine-ir-iv", "morphine-sr-mr", "nitrazepam", + "nortriptyline", "olanzapine-pamoate-lai", "olanzapine-wafer-odt", + "orphenadrine", "oxazepam", + "oxybutynin", "oxycodone-ir", "oxycodone-sr-mr", "paliperidone-er", "paliperidone-lai", "periciazine", + "phenelzine", "quetiapine", "risperidone", "risperidone-lai", + "solifenacin", "tapentadol-sr", "temazepam", "tramadol-ir", + "tranylcypromine", "trifluoperazine", "ziprasidone", "ziprasidone-im", @@ -699,8 +725,18 @@ "zuclopenthixol-acetate", "zuclopenthixol-decanoate" ], - "termIds": ["alcohol", "antipsychotics", "barbiturates", "benzodiazepines", "cns-depressants", "opioids"], - "resolved": true + "termIds": [ + "alcohol", + "anticholinergics", + "antipsychotics", + "barbiturates", + "benzodiazepines", + "cns-depressants", + "maois", + "opioids", + "tcas" + ], + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -749,7 +785,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "fexofenadine": { "rows": [ @@ -758,7 +794,7 @@ "rowIndex": 0, "severity": "critical", "counterparties": [], - "termIds": ["pgp"], + "termIds": ["grapefruit", "pgp"], "resolved": false }, { @@ -793,29 +829,66 @@ "severity": "critical", "counterparties": [ "alprazolam", + "amisulpride", + "amitriptyline", + "aripiprazole", + "aripiprazole-lai", + "asenapine", + "benzatropine", + "brexpiprazole", "buprenorphine-naloxone", "buprenorphine-patch", "buprenorphine-sl-depot", + "cariprazine", + "chlorpromazine", + "clomipramine", "clonazepam", + "clozapine", "codeine", "diazepam", + "doxepin", "fentanyl", + "flupentixol-decanoate", + "haloperidol-decanoate", "hydromorphone-ir-iv", + "hyoscine-butylbromide", + "hyoscine-hydrobromide", + "imipramine", + "levomepromazine", "loperamide", "lorazepam", + "lurasidone", "methadone", "midazolam", "morphine-ir-iv", "morphine-sr-mr", "nitrazepam", + "nortriptyline", + "olanzapine-pamoate-lai", + "olanzapine-wafer-odt", + "orphenadrine", "oxazepam", + "oxybutynin", "oxycodone-ir", "oxycodone-sr-mr", + "paliperidone-er", + "paliperidone-lai", + "periciazine", + "quetiapine", + "risperidone", + "risperidone-lai", + "solifenacin", "tapentadol-sr", "temazepam", - "tramadol-ir" + "tramadol-ir", + "trifluoperazine", + "ziprasidone", + "ziprasidone-im", + "zuclopenthixol", + "zuclopenthixol-acetate", + "zuclopenthixol-decanoate" ], - "termIds": ["alcohol", "benzodiazepines", "opioids"], + "termIds": ["alcohol", "anticholinergics", "antipsychotics", "benzodiazepines", "opioids", "tcas"], "resolved": true } ], @@ -854,9 +927,9 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": ["bupropion-sr", "fluoxetine", "paroxetine"], + "counterparties": ["bupropion-sr", "fluoxetine", "morphine-sr-mr", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -867,7 +940,7 @@ "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "fentanyl": { "rows": [ @@ -877,7 +950,7 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -897,7 +970,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "hydromorphone-ir-iv": { "rows": [ @@ -916,10 +989,10 @@ "temazepam" ], "termIds": ["benzodiazepines", "cns-depressants"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "morphine-ir-iv": { "rows": [ @@ -1002,17 +1075,30 @@ "severity": "critical", "counterparties": [ "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", "clonazepam", + "codeine", "diazepam", + "fentanyl", "gabapentin", + "hydromorphone-ir-iv", + "loperamide", "lorazepam", + "methadone", "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", "nitrazepam", "oxazepam", + "oxycodone-sr-mr", "pregabalin", - "temazepam" + "tapentadol-sr", + "temazepam", + "tramadol-ir" ], - "termIds": ["alcohol", "benzodiazepines", "gabapentinoids"], + "termIds": ["alcohol", "benzodiazepines", "gabapentinoids", "opioids"], "resolved": true }, { @@ -1021,10 +1107,10 @@ "severity": "high", "counterparties": ["clarithromycin", "oxycodone-sr-mr"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "oxycodone-sr-mr": { "rows": [ @@ -1062,9 +1148,9 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "low", - "counterparties": [], + "counterparties": ["codeine", "tramadol-ir"], "termIds": [], - "resolved": false + "resolved": true }, { "rowKey": "Pharmacodynamic", @@ -1110,7 +1196,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "tramadol-ir": { "rows": [ @@ -1164,10 +1250,10 @@ "venlafaxine-xr" ], "termIds": ["cyp-inhibitors", "opioids", "snris"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "celecoxib": { "rows": [ @@ -1530,12 +1616,12 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["lidocaine-patch"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "capsaicin-cream": { "rows": [ @@ -1605,8 +1691,8 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": ["pgp"], + "counterparties": ["atorvastatin", "digoxin", "rosuvastatin", "warfarin-anticoagulant", "warfarin-vka"], + "termIds": ["pgp", "statins"], "resolved": false }, { @@ -1623,10 +1709,10 @@ "sotalol" ], "termIds": ["beta-blockers", "qtc-prolonging"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "digoxin": { "rows": [ @@ -1643,7 +1729,7 @@ "verapamil" ], "termIds": ["macrolides", "pgp"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -1661,7 +1747,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "flecainide": { "rows": [ @@ -1687,10 +1773,10 @@ "severity": "high", "counterparties": ["amiodarone", "fluoxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "sotalol": { "rows": [ @@ -1737,7 +1823,7 @@ "zuclopenthixol-decanoate" ], "termIds": ["antipsychotics", "macrolides", "qtc-prolonging", "ssris"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -1748,7 +1834,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "glyceryl-trinitrate-gtn": { "rows": [ @@ -1797,7 +1883,7 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -1808,7 +1894,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "candesartan": { "rows": [ @@ -1868,12 +1954,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "high", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": ["atorvastatin", "digoxin", "rosuvastatin"], + "termIds": ["statins"], + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "felodipine": { "rows": [ @@ -1883,7 +1969,7 @@ "severity": "critical", "counterparties": ["azithromycin", "clarithromycin", "erythromycin", "roxithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors", "grapefruit", "macrolides"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -1894,7 +1980,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "nifedipine-xr": { "rows": [ @@ -1904,7 +1990,7 @@ "severity": "high", "counterparties": [], "termIds": ["cyp-inhibitors", "grapefruit"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -1915,7 +2001,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "perindopril": { "rows": [ @@ -1968,9 +2054,12 @@ "atenolol", "bisoprolol", "carvedilol", + "hydromorphone-ir-iv", "labetalol", "metoprolol", + "morphine-ir-iv", "propranolol", + "sodium-valproate-oral-iv", "sotalol" ], "termIds": ["beta-blockers"], @@ -1980,12 +2069,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "high", - "counterparties": [], + "counterparties": ["atorvastatin", "digoxin"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "atenolol": { "rows": [ @@ -2035,7 +2124,7 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": ["diltiazem", "verapamil"], + "counterparties": ["diltiazem", "prazosin", "verapamil"], "termIds": [], "resolved": true }, @@ -2079,10 +2168,10 @@ "severity": "high", "counterparties": ["fluoxetine", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "amiloride": { "rows": [ @@ -2113,7 +2202,7 @@ "severity": "critical", "counterparties": ["clarithromycin"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -2124,7 +2213,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "frusemide": { "rows": [ @@ -2151,12 +2240,12 @@ "rowKey": "Pharmacodynamic", "rowIndex": 1, "severity": "high", - "counterparties": [], + "counterparties": ["digoxin"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "hydrochlorothiazide": { "rows": [ @@ -2236,9 +2325,9 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": ["amiodarone", "clarithromycin", "diltiazem"], + "termIds": ["grapefruit"], + "resolved": true }, { "rowKey": "Pharmacokinetic", @@ -2249,7 +2338,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "ezetimibe": { "rows": [ @@ -2637,7 +2726,7 @@ "propranolol", "sotalol" ], - "termIds": ["beta-blockers"], + "termIds": ["alcohol", "beta-blockers"], "resolved": true } ], @@ -2687,14 +2776,29 @@ "severity": "high", "counterparties": [ "atenolol", + "beclometasone", + "betamethasone", "bisoprolol", + "budesonide", "carvedilol", + "ciclesonide", + "clobetasol", + "dexamethasone", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", "labetalol", + "methylprednisolone", "metoprolol", + "mometasone", + "prednisolone", + "prednisone", "propranolol", - "sotalol" + "sotalol", + "triamcinolone" ], - "termIds": ["beta-blockers"], + "termIds": ["beta-blockers", "corticosteroids"], "resolved": true } ], @@ -3049,8 +3153,17 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "high", - "counterparties": ["hydrochlorothiazide", "indapamide"], - "termIds": ["thiazide-diuretics"], + "counterparties": [ + "aspirin", + "diclofenac", + "hydrochlorothiazide", + "ibuprofen", + "indapamide", + "ketorolac", + "meloxicam", + "naproxen" + ], + "termIds": ["nsaids", "thiazide-diuretics"], "resolved": true } ], @@ -3075,8 +3188,17 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], - "termIds": ["nsaids"], + "counterparties": [ + "aspirin", + "diclofenac", + "esomeprazole", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "pantoprazole" + ], + "termIds": ["nsaids", "ppis"], "resolved": true }, { @@ -3096,8 +3218,17 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], - "termIds": ["nsaids"], + "counterparties": [ + "aspirin", + "diclofenac", + "esomeprazole", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "pantoprazole" + ], + "termIds": ["nsaids", "ppis"], "resolved": true }, { @@ -3197,7 +3328,7 @@ "severity": "critical", "counterparties": ["carbamazepine", "phenytoin"], "termIds": ["antiretrovirals", "cyp-inducers", "st-johns-wort"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -3208,7 +3339,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "levonorgestrel": { "rows": [ @@ -3216,7 +3347,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["copper"], "termIds": ["cyp-inducers"], "resolved": false } @@ -3231,10 +3362,10 @@ "severity": "high", "counterparties": ["carbamazepine", "phenytoin"], "termIds": ["cyp-inducers"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "esomeprazole": { "rows": [ @@ -3242,8 +3373,8 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "high", - "counterparties": ["clopidogrel"], - "termIds": [], + "counterparties": ["aspirin", "clopidogrel", "dipyridamole", "pantoprazole", "ticagrelor"], + "termIds": ["antiplatelets", "ppis"], "resolved": true } ], @@ -3276,9 +3407,9 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "low", - "counterparties": [], + "counterparties": ["clopidogrel"], "termIds": [], - "resolved": false + "resolved": true }, { "rowKey": "Pharmacokinetic", @@ -3289,7 +3420,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "cyclizine": { "rows": [ @@ -3373,7 +3504,7 @@ "severity": "critical", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -3393,7 +3524,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "hyoscine-hydrobromide": { "rows": [ @@ -3544,7 +3675,7 @@ "sertraline" ], "termIds": ["macrolides", "qtc-prolonging", "ssris"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -3555,7 +3686,7 @@ "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "prochlorperazine": { "rows": [ @@ -3681,10 +3812,10 @@ "verapamil" ], "termIds": ["azole-antifungals", "opioids", "pgp"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "mesalazine": { "rows": [ @@ -3838,12 +3969,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "high", - "counterparties": [], + "counterparties": ["digoxin"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "senna": { "rows": [ @@ -3909,7 +4040,7 @@ "severity": "critical", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "pgp"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -3933,7 +4064,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "dabigatran": { "rows": [ @@ -3943,7 +4074,7 @@ "severity": "critical", "counterparties": ["amiodarone", "clarithromycin", "verapamil"], "termIds": ["pgp"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -3967,7 +4098,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "dalteparin": { "rows": [ @@ -4015,7 +4146,7 @@ "verapamil" ], "termIds": ["macrolides", "pgp"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -4039,7 +4170,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "enoxaparin": { "rows": [ @@ -4111,7 +4242,7 @@ "severity": "critical", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "pgp"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -4135,7 +4266,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "tranexamic-acid": { "rows": [ @@ -4156,7 +4287,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": ["amiodarone", "fluconazole", "metronidazole"], + "counterparties": ["amiodarone", "fluconazole", "metronidazole", "warfarin-anticoagulant"], "termIds": ["cotrimoxazole"], "resolved": true }, @@ -4164,7 +4295,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "critical", - "counterparties": ["carbamazepine"], + "counterparties": ["carbamazepine", "warfarin-anticoagulant"], "termIds": ["st-johns-wort"], "resolved": true }, @@ -4185,7 +4316,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": ["amiodarone", "fluconazole", "metronidazole"], + "counterparties": ["amiodarone", "fluconazole", "metronidazole", "warfarin-vka"], "termIds": ["cotrimoxazole"], "resolved": true }, @@ -4266,7 +4397,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": ["esomeprazole"], + "counterparties": ["esomeprazole", "pantoprazole"], "termIds": [], "resolved": true }, @@ -4312,19 +4443,36 @@ "rowIndex": 0, "severity": "high", "counterparties": [ + "amlodipine", "apixaban", + "atenolol", + "bisoprolol", + "candesartan", + "carvedilol", "clopidogrel", "dabigatran", "dalteparin", + "diltiazem", "edoxaban", "enoxaparin", + "eplerenone", + "felodipine", + "frusemide", "heparin-iv-sc", + "hydrochlorothiazide", + "indapamide", + "labetalol", + "metoprolol", + "nifedipine-xr", + "perindopril", "rivaroxaban", + "spironolactone", "ticagrelor", + "verapamil", "warfarin-anticoagulant", "warfarin-vka" ], - "termIds": ["anticoagulants"], + "termIds": ["anticoagulants", "antihypertensives"], "resolved": true } ], @@ -4336,9 +4484,9 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "high", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": ["clarithromycin"], + "termIds": ["azole-antifungals"], + "resolved": true }, { "rowKey": "Pharmacodynamic", @@ -4349,7 +4497,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "gentamicin": { "rows": [ @@ -4577,8 +4725,30 @@ "rowKey": "Pharmacodynamic", "rowIndex": 1, "severity": "high", - "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], - "termIds": ["nsaids"], + "counterparties": [ + "aspirin", + "beclometasone", + "betamethasone", + "budesonide", + "ciclesonide", + "clobetasol", + "dexamethasone", + "diclofenac", + "fludrocortisone", + "fluticasone", + "hydrocortisone", + "hydrocortisone-1", + "ibuprofen", + "ketorolac", + "meloxicam", + "methylprednisolone", + "mometasone", + "naproxen", + "prednisolone", + "prednisone", + "triamcinolone" + ], + "termIds": ["corticosteroids", "nsaids"], "resolved": true } ], @@ -4631,8 +4801,25 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "low", - "counterparties": ["clarithromycin", "erythromycin"], - "termIds": [], + "counterparties": [ + "apixaban", + "atorvastatin", + "clarithromycin", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "erythromycin", + "heparin-iv-sc", + "rivaroxaban", + "rosuvastatin", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "statins"], "resolved": true }, { @@ -4652,7 +4839,7 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["apixaban", "atorvastatin", "colchicine", "rivaroxaban"], "termIds": ["pgp"], "resolved": false }, @@ -4673,12 +4860,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["atorvastatin", "colchicine", "warfarin-anticoagulant", "warfarin-vka"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "minocycline": { "rows": [ @@ -4707,8 +4894,25 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "low", - "counterparties": ["clarithromycin"], - "termIds": [], + "counterparties": [ + "apixaban", + "atorvastatin", + "azithromycin", + "clarithromycin", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "erythromycin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "macrolides"], "resolved": true } ], @@ -4733,12 +4937,12 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "low", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": ["amikacin", "gentamicin"], + "termIds": ["aminoglycosides"], + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "clindamycin": { "rows": [ @@ -4835,7 +5039,7 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["disulfiram"], "termIds": ["alcohol"], "resolved": true }, @@ -5030,9 +5234,9 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": ["calcium-carbonate", "iron", "magnesium-oxide"], + "termIds": ["antacids", "food"], + "resolved": true }, { "rowKey": "Pharmacokinetic", @@ -5043,7 +5247,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "fosfomycin": { "rows": [ @@ -5198,7 +5402,7 @@ "verapamil" ], "termIds": ["azole-antifungals", "macrolides", "pgp"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -5209,7 +5413,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "dantrolene": { "rows": [ @@ -5232,7 +5436,7 @@ "severity": "high", "counterparties": ["amiodarone", "azithromycin", "clarithromycin", "erythromycin", "roxithromycin"], "termIds": ["macrolides", "qtc-prolonging"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -5243,7 +5447,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "methotrexate": { "rows": [ @@ -5392,9 +5596,9 @@ "rowKey": "Endocrine", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["copper"], "termIds": [], - "resolved": false + "resolved": true }, { "rowKey": "Pharmacokinetic", @@ -5405,7 +5609,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "donepezil": { "rows": [ @@ -5461,6 +5665,7 @@ "cariprazine", "chlorpromazine", "clozapine", + "domperidone", "flupentixol-decanoate", "haloperidol-decanoate", "levomepromazine", @@ -5468,6 +5673,7 @@ "metoclopramide", "olanzapine-pamoate-lai", "olanzapine-wafer-odt", + "ondansetron", "paliperidone-er", "paliperidone-lai", "periciazine", @@ -5514,9 +5720,28 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": [ + "apixaban", + "azithromycin", + "clarithromycin", + "clopidogrel", + "clozapine", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "erythromycin", + "heparin-iv-sc", + "quetiapine", + "rivaroxaban", + "roxithromycin", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants", "macrolides"], + "resolved": true }, { "rowKey": "Pharmacokinetic", @@ -5527,7 +5752,7 @@ "resolved": false } ], - "unresolvedRowCount": 2 + "unresolvedRowCount": 1 }, "gabapentin": { "rows": [ @@ -5600,12 +5825,25 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "low", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "phenytoin": { "rows": [ @@ -5613,24 +5851,38 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false - }, - { - "rowKey": "Pharmacokinetic", - "rowIndex": 1, - "severity": "high", - "counterparties": [], - "termIds": [], - "resolved": false - } - ], - "unresolvedRowCount": 2 - }, - "pregabalin": { - "rows": [ - { + "counterparties": [ + "apixaban", + "clopidogrel", + "dabigatran", + "dalteparin", + "dipyridamole", + "edoxaban", + "enoxaparin", + "heparin-iv-sc", + "quetiapine", + "rivaroxaban", + "ticagrelor", + "warfarin-anticoagulant", + "warfarin-vka" + ], + "termIds": ["anticoagulants"], + "resolved": true + }, + { + "rowKey": "Pharmacokinetic", + "rowIndex": 1, + "severity": "high", + "counterparties": [], + "termIds": [], + "resolved": false + } + ], + "unresolvedRowCount": 1 + }, + "pregabalin": { + "rows": [ + { "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", @@ -5713,11 +5965,13 @@ "fluoxetine", "fluvoxamine", "paroxetine", + "phenelzine", "sertraline", "tramadol-ir", + "tranylcypromine", "venlafaxine-xr" ], - "termIds": ["snris", "ssris"], + "termIds": ["maois", "snris", "ssris"], "resolved": true }, { @@ -5762,10 +6016,10 @@ "severity": "critical", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "clonazepam": { "rows": [ @@ -5798,10 +6052,10 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "diazepam": { "rows": [ @@ -5883,10 +6137,10 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "nitrazepam": { "rows": [ @@ -5919,10 +6173,10 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "oxazepam": { "rows": [ @@ -5953,12 +6207,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "low", - "counterparties": [], + "counterparties": ["amiodarone", "clarithromycin"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "temazepam": { "rows": [ @@ -6025,7 +6279,7 @@ "severity": "critical", "counterparties": [], "termIds": ["alcohol", "cns-depressants"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -6033,10 +6287,10 @@ "severity": "high", "counterparties": ["erythromycin"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 2 }, "atomoxetine": { "rows": [ @@ -6046,7 +6300,7 @@ "severity": "high", "counterparties": ["fluoxetine", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -6057,7 +6311,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "dexamfetamine": { "rows": [ @@ -6086,9 +6340,9 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "high", - "counterparties": ["clarithromycin"], + "counterparties": ["carbamazepine", "clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -6113,11 +6367,11 @@ "spironolactone", "verapamil" ], - "termIds": ["antihypertensives"], - "resolved": true + "termIds": ["antihypertensives", "cns-depressants"], + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 2 }, "lisdexamfetamine": { "rows": [ @@ -6133,12 +6387,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "high", - "counterparties": [], + "counterparties": ["ascorbic-acid"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "methylphenidate": { "rows": [ @@ -6507,12 +6761,35 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "propranolol": { "rows": [ @@ -6550,7 +6827,7 @@ "roxithromycin" ], "termIds": ["macrolides", "qtc-prolonging"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -6579,7 +6856,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "agomelatine": { "rows": [ @@ -6634,10 +6911,10 @@ "severity": "high", "counterparties": ["clomipramine", "doxepin", "fluoxetine", "imipramine", "nortriptyline", "paroxetine"], "termIds": ["cyp-inhibitors", "tcas"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "citalopram": { "rows": [ @@ -6647,7 +6924,7 @@ "severity": "critical", "counterparties": ["amiodarone", "sotalol"], "termIds": ["qtc-prolonging"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -6658,7 +6935,7 @@ "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "clomipramine": { "rows": [ @@ -6686,10 +6963,10 @@ "severity": "high", "counterparties": ["fluoxetine", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "desvenlafaxine": { "rows": [ @@ -6719,7 +6996,7 @@ "rowIndex": 0, "severity": "critical", "counterparties": ["phenelzine", "tramadol-ir", "tranylcypromine"], - "termIds": ["maois"], + "termIds": ["alcohol", "maois"], "resolved": true }, { @@ -6728,10 +7005,10 @@ "severity": "high", "counterparties": ["fluoxetine", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "doxepin": { "rows": [ @@ -6772,10 +7049,10 @@ "severity": "high", "counterparties": ["paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "duloxetine": { "rows": [ @@ -6793,10 +7070,10 @@ "severity": "high", "counterparties": ["ciprofloxacin", "fluvoxamine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "escitalopram": { "rows": [ @@ -6806,7 +7083,7 @@ "severity": "critical", "counterparties": ["amiodarone", "sotalol"], "termIds": ["qtc-prolonging"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -6825,7 +7102,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "fluoxetine": { "rows": [ @@ -6833,9 +7110,20 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": [ + "amitriptyline", + "clomipramine", + "codeine", + "doxepin", + "imipramine", + "metoprolol", + "nortriptyline", + "risperidone", + "risperidone-lai", + "tramadol-ir" + ], + "termIds": ["tcas"], + "resolved": true }, { "rowKey": "Pharmacodynamic", @@ -6846,7 +7134,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "fluvoxamine": { "rows": [ @@ -6854,12 +7142,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["agomelatine", "clozapine", "duloxetine"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "imipramine": { "rows": [ @@ -6877,10 +7165,10 @@ "severity": "high", "counterparties": ["fluoxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "mirtazapine": { "rows": [ @@ -6973,10 +7261,10 @@ "severity": "high", "counterparties": ["bupropion-sr", "fluoxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "paroxetine": { "rows": [ @@ -6984,12 +7272,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], + "counterparties": ["codeine", "metoprolol", "risperidone", "risperidone-lai", "tramadol-ir"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "phenelzine": { "rows": [ @@ -6997,9 +7285,9 @@ "rowKey": "Dietary", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": ["noradrenaline"], + "termIds": ["food"], + "resolved": true }, { "rowKey": "Pharmacodynamic", @@ -7024,7 +7312,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "reboxetine": { "rows": [ @@ -7042,10 +7330,10 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "sertraline": { "rows": [ @@ -7061,8 +7349,22 @@ "rowKey": "Pharmacodynamic", "rowIndex": 1, "severity": "high", - "counterparties": ["aspirin", "diclofenac", "ibuprofen", "ketorolac", "meloxicam", "naproxen"], - "termIds": ["nsaids"], + "counterparties": [ + "aspirin", + "citalopram", + "diclofenac", + "escitalopram", + "esomeprazole", + "fluoxetine", + "fluvoxamine", + "ibuprofen", + "ketorolac", + "meloxicam", + "naproxen", + "pantoprazole", + "paroxetine" + ], + "termIds": ["nsaids", "ppis", "ssris"], "resolved": true } ], @@ -7082,12 +7384,20 @@ "rowKey": "Pharmacodynamic", "rowIndex": 1, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": [ + "citalopram", + "escitalopram", + "fluoxetine", + "fluvoxamine", + "paroxetine", + "sertraline", + "tramadol-ir" + ], + "termIds": ["ssris"], + "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "venlafaxine-xr": { "rows": [ @@ -7118,7 +7428,7 @@ "severity": "high", "counterparties": ["bupropion-sr", "fluoxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -7126,7 +7436,7 @@ "severity": "high", "counterparties": ["carbamazepine"], "termIds": ["cyp-inducers"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -7137,7 +7447,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 2 }, "aripiprazole": { "rows": [ @@ -7147,7 +7457,7 @@ "severity": "high", "counterparties": ["aripiprazole-lai", "clarithromycin", "fluoxetine", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -7155,10 +7465,10 @@ "severity": "high", "counterparties": ["aripiprazole-lai", "carbamazepine", "phenytoin"], "termIds": ["cyp-inducers"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 2 }, "clozapine": { "rows": [ @@ -7176,10 +7486,10 @@ "severity": "high", "counterparties": ["ciprofloxacin", "fluvoxamine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "olanzapine-wafer-odt": { "rows": [ @@ -7212,18 +7522,18 @@ "tramadol-ir" ], "termIds": ["benzodiazepines", "cns-depressants", "opioids"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "high", - "counterparties": [], + "counterparties": ["olanzapine-pamoate-lai"], "termIds": ["smoking"], "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "quetiapine": { "rows": [ @@ -7231,8 +7541,8 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": ["cyp-inhibitors"], + "counterparties": ["phenytoin"], + "termIds": ["cyp-inducers", "cyp-inhibitors"], "resolved": false }, { @@ -7272,7 +7582,7 @@ "severity": "high", "counterparties": ["bupropion-sr", "fluoxetine", "paroxetine", "risperidone-lai"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -7301,7 +7611,7 @@ "resolved": true } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "aripiprazole-lai": { "rows": [ @@ -7311,7 +7621,7 @@ "severity": "high", "counterparties": ["clarithromycin", "fluoxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -7319,10 +7629,10 @@ "severity": "critical", "counterparties": ["carbamazepine"], "termIds": ["cyp-inducers"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 2 }, "flupentixol-decanoate": { "rows": [ @@ -7340,10 +7650,10 @@ "severity": "high", "counterparties": ["paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "haloperidol-decanoate": { "rows": [ @@ -7353,7 +7663,7 @@ "severity": "critical", "counterparties": ["amiodarone", "azithromycin", "clarithromycin", "erythromycin", "roxithromycin"], "termIds": ["macrolides", "qtc-prolonging"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacokinetic", @@ -7364,7 +7674,7 @@ "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "olanzapine-pamoate-lai": { "rows": [ @@ -7457,10 +7767,10 @@ "severity": "high", "counterparties": ["fluoxetine", "paroxetine", "risperidone"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "zuclopenthixol-acetate": { "rows": [ @@ -7586,12 +7896,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "high", - "counterparties": [], + "counterparties": ["phenytoin"], "termIds": [], - "resolved": false + "resolved": true } ], - "unresolvedRowCount": 2 + "unresolvedRowCount": 1 }, "amisulpride": { "rows": [ @@ -7638,7 +7948,7 @@ "severity": "high", "counterparties": ["ciprofloxacin", "fluvoxamine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -7664,10 +7974,10 @@ "verapamil" ], "termIds": ["antihypertensives", "cns-depressants"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 2 }, "brexpiprazole": { "rows": [ @@ -7677,10 +7987,10 @@ "severity": "high", "counterparties": ["clarithromycin", "fluoxetine", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "cariprazine": { "rows": [ @@ -7688,12 +7998,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 0, "severity": "critical", - "counterparties": ["clarithromycin"], - "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "counterparties": ["carbamazepine", "clarithromycin"], + "termIds": ["azole-antifungals", "cyp-inducers", "cyp-inhibitors"], + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "chlorpromazine": { "rows": [ @@ -7701,9 +8011,32 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": [], - "termIds": [], - "resolved": false + "counterparties": [ + "alprazolam", + "buprenorphine-naloxone", + "buprenorphine-patch", + "buprenorphine-sl-depot", + "clonazepam", + "codeine", + "diazepam", + "fentanyl", + "hydromorphone-ir-iv", + "loperamide", + "lorazepam", + "methadone", + "midazolam", + "morphine-ir-iv", + "morphine-sr-mr", + "nitrazepam", + "oxazepam", + "oxycodone-ir", + "oxycodone-sr-mr", + "tapentadol-sr", + "temazepam", + "tramadol-ir" + ], + "termIds": ["benzodiazepines", "opioids"], + "resolved": true }, { "rowKey": "Pharmacodynamic", @@ -7726,7 +8059,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "lurasidone": { "rows": [ @@ -7736,10 +8069,10 @@ "severity": "critical", "counterparties": ["clarithromycin", "diltiazem"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "paliperidone-er": { "rows": [ @@ -7854,12 +8187,12 @@ "rowKey": "Pharmacokinetic", "rowIndex": 1, "severity": "high", - "counterparties": [], + "counterparties": ["carbamazepine"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "zuclopenthixol": { "rows": [ @@ -7869,7 +8202,7 @@ "severity": "high", "counterparties": ["bupropion-sr", "fluoxetine", "paroxetine"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false }, { "rowKey": "Pharmacodynamic", @@ -7880,7 +8213,7 @@ "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "beclometasone": { "rows": [ @@ -7890,10 +8223,10 @@ "severity": "moderate", "counterparties": ["fluticasone"], "termIds": ["cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "budesonide": { "rows": [ @@ -8112,6 +8445,7 @@ "benzatropine", "hyoscine-butylbromide", "hyoscine-hydrobromide", + "ipratropium", "orphenadrine", "oxybutynin", "solifenacin" @@ -8141,8 +8475,22 @@ "rowKey": "Pharmacodynamic", "rowIndex": 0, "severity": "critical", - "counterparties": ["amitriptyline", "clomipramine", "doxepin", "imipramine", "nortriptyline"], - "termIds": ["tcas"], + "counterparties": [ + "alprazolam", + "amitriptyline", + "clomipramine", + "clonazepam", + "diazepam", + "doxepin", + "imipramine", + "lorazepam", + "midazolam", + "nitrazepam", + "nortriptyline", + "oxazepam", + "temazepam" + ], + "termIds": ["benzodiazepines", "tcas"], "resolved": true } ], @@ -8169,10 +8517,10 @@ "severity": "high", "counterparties": [], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "finasteride": { "rows": [ @@ -8276,10 +8624,10 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["cyp-inhibitors", "grapefruit"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 2 }, "solifenacin": { "rows": [ @@ -8288,8 +8636,8 @@ "rowIndex": 0, "severity": "high", "counterparties": [], - "termIds": [], - "resolved": false + "termIds": ["azole-antifungals"], + "resolved": true }, { "rowKey": "Pharmacodynamic", @@ -8337,7 +8685,7 @@ "resolved": true } ], - "unresolvedRowCount": 1 + "unresolvedRowCount": 0 }, "tadalafil": { "rows": [ @@ -8395,10 +8743,10 @@ "severity": "high", "counterparties": ["clarithromycin"], "termIds": ["azole-antifungals", "cyp-inhibitors"], - "resolved": true + "resolved": false } ], - "unresolvedRowCount": 0 + "unresolvedRowCount": 1 }, "ascorbic-acid": { "rows": [ diff --git a/scripts/build-medication-interaction-index.ts b/scripts/build-medication-interaction-index.ts index 9cd6ca38c5..6eef27e8f6 100644 --- a/scripts/build-medication-interaction-index.ts +++ b/scripts/build-medication-interaction-index.ts @@ -65,7 +65,10 @@ function stripDosageForm(name: string): string { } break; } - return tokens.join(" ").replace(/[\s/-]+$/, "").trim(); + return tokens + .join(" ") + .replace(/[\s/-]+$/, "") + .trim(); } function escapeRegExp(value: string): string { @@ -89,7 +92,10 @@ function counterpartySegments(value: string): { severityToken: string | null; se const match = SEVERITY_PATTERN.exec(value); const severityToken = match ? match[1].trim() : null; const body = (match ? match[2] : value).replace(/\*\*/g, "").trim(); - const sentences = body.split(/(?<=[.;])\s+/).map((sentence) => sentence.trim()).filter(Boolean); + const sentences = body + .split(/(?<=[.;])\s+/) + .map((sentence) => sentence.trim()) + .filter(Boolean); return { severityToken, segments: sentences.length ? sentences : [body] }; } @@ -121,7 +127,10 @@ function main(): void { // enforces alphanumeric boundaries, so dropping them here only creates // avoidable false negatives. Two-character route/form tokens remain below // the floor to avoid indexing abbreviations such as IR/IM/IV as drugs. - const parts = record.name.split("/").map((part) => part.trim()).filter((part) => part.length >= 3); + const parts = record.name + .split("/") + .map((part) => part.trim()) + .filter((part) => part.length >= 3); const surfaces = new Set(parts); for (const part of parts) surfaces.add(stripDosageForm(part)); surfaces.add(stripDosageForm(record.name)); @@ -215,7 +224,9 @@ function main(): void { rowsWithCatalogueTarget, medicationsWithUnresolvedRows: Object.values(bySlug).filter((entry) => entry.unresolvedRowCount > 0).length, }, - names: Object.fromEntries(records.map((record) => [record.slug, record.name] as const).sort(([a], [b]) => a.localeCompare(b))), + names: Object.fromEntries( + records.map((record) => [record.slug, record.name] as const).sort(([a], [b]) => a.localeCompare(b)), + ), bySlug, }; @@ -224,7 +235,9 @@ function main(): void { if (checkOnly) { const current = readFileSync(outputPath, "utf8"); if (!sameJson(current, serialised)) { - console.error(`[medication-interactions] ${OUTPUT_PATH} is stale.\nRun \`npm run medications:interactions\` and commit the result.`); + console.error( + `[medication-interactions] ${OUTPUT_PATH} is stale.\nRun \`npm run medications:interactions\` and commit the result.`, + ); process.exit(1); } console.log(`[medication-interactions] ${OUTPUT_PATH} is up to date (${sourceRowCount} rows).`); @@ -232,7 +245,9 @@ function main(): void { } writeFileSync(outputPath, serialised); - console.log(`[medication-interactions] wrote ${OUTPUT_PATH}: ${sourceRowCount} rows, ${resolvedRows} resolved, ${unresolvedRows} unresolved, ${index.stats.medicationsWithUnresolvedRows} medications carry an unresolved row.`); + console.log( + `[medication-interactions] wrote ${OUTPUT_PATH}: ${sourceRowCount} rows, ${resolvedRows} resolved, ${unresolvedRows} unresolved, ${index.stats.medicationsWithUnresolvedRows} medications carry an unresolved row.`, + ); } main(); diff --git a/scripts/verify-pr-local.mjs b/scripts/verify-pr-local.mjs index 9f2ada0534..43ef7fa3d1 100644 --- a/scripts/verify-pr-local.mjs +++ b/scripts/verify-pr-local.mjs @@ -120,6 +120,12 @@ export function selectedScripts(scope, extended) { // recognised docs and workflow-only changes avoid an unrelated RAG scan. if (scope.rag_eval_changed) add("eval:rag:offline"); else if (scope.static_heavy_changed) add("check:rag:fixtures"); + // `data/medication-interaction-index.json` is generated from the medication + // snapshot plus the curated lexicon, and the UI reads it to decide whether a + // drug can be shown as clear. A stale artefact therefore silently serves an + // old safety verdict. Cheap and deterministic, so it rides every executable + // scope rather than needing its own path classification. + if (scope.static_heavy_changed) add("check:medication-interactions"); if (extended && scope.ui_changed) add("verify:ui"); return scripts; } @@ -198,33 +204,55 @@ function selfTest() { workflow_changed: true, static_heavy_changed: true, }, - [...commonScripts, ...workflowScripts, ...staticHeavyScripts, "check:rag:fixtures"], + [ + ...commonScripts, + ...workflowScripts, + ...staticHeavyScripts, + "check:rag:fixtures", + "check:medication-interactions", + ], ); assertPlan("unknown-or-product-change-fails-heavy", { static_heavy_changed: true }, [ ...commonScripts, ...staticHeavyScripts, "check:rag:fixtures", + "check:medication-interactions", ]); assertPlan("rag-change", { static_heavy_changed: true, rag_eval_changed: true }, [ ...commonScripts, ...staticHeavyScripts, "eval:rag:offline", + "check:medication-interactions", ]); assertPlan( "ui-extended", { static_heavy_changed: true, ui_changed: true }, - [...commonScripts, ...staticHeavyScripts, "check:rag:fixtures", "verify:ui"], + [...commonScripts, ...staticHeavyScripts, "check:rag:fixtures", "check:medication-interactions", "verify:ui"], true, ); assertPlan( "lockfile-change-adds-npm-ci-dry-run", { lockfile_changed: true, static_heavy_changed: true, build_changed: true }, - [...commonScripts, "check:npm-ci-dry-run", ...staticHeavyScripts, "build", "check:rag:fixtures"], + [ + ...commonScripts, + "check:npm-ci-dry-run", + ...staticHeavyScripts, + "build", + "check:rag:fixtures", + "check:medication-interactions", + ], ); assertPlan( "package-json-change-adds-npm-ci-dry-run", { files: ["package.json"], static_heavy_changed: true, build_changed: true }, - [...commonScripts, "check:npm-ci-dry-run", ...staticHeavyScripts, "build", "check:rag:fixtures"], + [ + ...commonScripts, + "check:npm-ci-dry-run", + ...staticHeavyScripts, + "build", + "check:rag:fixtures", + "check:medication-interactions", + ], ); console.log("PR-local verification plan self-test passed."); } diff --git a/tests/medication-interaction-lexicon-coverage.test.ts b/tests/medication-interaction-lexicon-coverage.test.ts index ed11cdc61a..fe587be777 100644 --- a/tests/medication-interaction-lexicon-coverage.test.ts +++ b/tests/medication-interaction-lexicon-coverage.test.ts @@ -31,7 +31,7 @@ const index = JSON.parse( readFileSync(path.resolve(process.cwd(), "data/medication-interaction-index.json"), "utf8"), ) as { sourceRowCount: number; - stats: { resolvedRows: number; unresolvedRows: number }; + stats: { resolvedRows: number; unresolvedRows: number; rowsWithCatalogueTarget: number }; bySlug: Record; }; @@ -50,9 +50,22 @@ function slugsFor(id: string): string[] { describe("interaction lexicon coverage", () => { it("does not regress the number of resolved rows", () => { - // Ratchet. Raise it when resolution genuinely improves; never lower it - // without recording why in the PR. - expect(index.stats.resolvedRows).toBeGreaterThanOrEqual(400); + // Ratchet, but read it carefully: `resolvedRows` moves for TWO reasons, and + // only one of them is coverage. + // + // It fell from 400 to 355 when a row containing any unenumerated mechanism + // term became unresolved even if it also matched a named drug. Previously + // "CYP3A4 inhibitors (Clarithromycin, Ketoconazole)" resolved on + // clarithromycin and counted as fully read, quietly implying the whole + // mechanism class had been checked. Fewer resolved rows there means more + // medications held at grey — a tightening, not a loss. + // + // So: a drop is only acceptable alongside a deliberate semantics change, + // recorded here and in the PR. Track raw drug-matching separately, which + // rose 381 → 417 over the same period and is the number that must never + // fall without explanation. + expect(index.stats.resolvedRows).toBeGreaterThanOrEqual(355); + expect(index.stats.rowsWithCatalogueTarget).toBeGreaterThanOrEqual(417); expect(index.sourceRowCount).toBeGreaterThanOrEqual(523); }); diff --git a/tests/patient-details-dock-action.dom.test.tsx b/tests/patient-details-dock-action.dom.test.tsx index 36a34b821b..85ff67ca49 100644 --- a/tests/patient-details-dock-action.dom.test.tsx +++ b/tests/patient-details-dock-action.dom.test.tsx @@ -90,9 +90,7 @@ describe("PatientDetailsDockAction", () => { renderAction(); dock.dataset.scrollHidden = "true"; - await waitFor(() => - expect(dock.style.transform).toBe("translateY(calc(100% + 0.5rem + var(--safe-area-bottom)))"), - ); + await waitFor(() => expect(dock.style.transform).toBe("translateY(calc(100% + 0.5rem + var(--safe-area-bottom)))")); delete dock.dataset.scrollHidden; await waitFor(() => expect(dock.style.transform).toBe("")); From 550b986b5fc6b17dd7e00992c69e5b5a06b06131 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:47:21 +0800 Subject: [PATCH 21/21] Adapt responsive stress coverage to patient dock --- tests/ui-stress.spec.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/ui-stress.spec.ts b/tests/ui-stress.spec.ts index 98c624f2d7..50dff4eacb 100644 --- a/tests/ui-stress.spec.ts +++ b/tests/ui-stress.spec.ts @@ -491,6 +491,7 @@ test.describe("Medication responsive stress coverage", () => { workspaceRight: workspaceRect.right, patientLeft: patientRect.left, patientRight: patientRect.right, + patientVisible: patientRect.width > 0 && patientRect.height > 0, cardLeft: cardRect.left, cardRight: cardRect.right, cardPaddingLeft: Number.parseFloat(cardStyle.paddingLeft), @@ -505,10 +506,8 @@ test.describe("Medication responsive stress coverage", () => { if (viewport.width <= 639) { expect(metrics?.workspaceLeft ?? 0).toBeGreaterThanOrEqual(12); expect(metrics?.workspaceRight ?? viewport.width).toBeLessThanOrEqual(viewport.width - 12); - expect(Math.abs((metrics?.patientLeft ?? 0) - (metrics?.workspaceLeft ?? 0))).toBeLessThanOrEqual(1); - expect( - Math.abs((metrics?.patientRight ?? 0) - (metrics?.workspaceRight ?? viewport.width)), - ).toBeLessThanOrEqual(1); + // Phone replaces the in-flow patient strip with the dock pill. + await expect(page.getByTestId("patient-details-dock-action")).toBeVisible(); expect(Math.abs((metrics?.cardLeft ?? 0) - (metrics?.workspaceLeft ?? 0))).toBeLessThanOrEqual(1); expect(Math.abs((metrics?.cardRight ?? 0) - (metrics?.workspaceRight ?? viewport.width))).toBeLessThanOrEqual( 1, @@ -517,6 +516,11 @@ test.describe("Medication responsive stress coverage", () => { expect(metrics?.cardPaddingRight ?? 0).toBeGreaterThanOrEqual(15); expect(metrics?.filterLeft ?? 0).toBeGreaterThanOrEqual(15); } else { + expect(metrics?.patientVisible).toBe(true); + expect(Math.abs((metrics?.patientLeft ?? 0) - (metrics?.workspaceLeft ?? 0))).toBeLessThanOrEqual(1); + expect( + Math.abs((metrics?.patientRight ?? 0) - (metrics?.workspaceRight ?? viewport.width)), + ).toBeLessThanOrEqual(1); expect(metrics?.cardLeft ?? 0).toBeGreaterThanOrEqual(12); expect((metrics?.cardRight ?? viewport.width) + 12).toBeLessThanOrEqual(viewport.width); }