diff --git a/data/medication-interaction-index.json b/data/medication-interaction-index.json index 0013838ae..093049e62 100644 --- a/data/medication-interaction-index.json +++ b/data/medication-interaction-index.json @@ -365,7 +365,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -397,7 +396,6 @@ "fentanyl", "gabapentin", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -492,7 +490,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -719,7 +716,6 @@ "hyoscine-hydrobromide", "imipramine", "levomepromazine", - "loperamide", "lorazepam", "lurasidone", "methadone", @@ -891,7 +887,6 @@ "hyoscine-hydrobromide", "imipramine", "levomepromazine", - "loperamide", "lorazepam", "lurasidone", "methadone", @@ -943,7 +938,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -1130,7 +1124,6 @@ "fentanyl", "gabapentin", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -1293,7 +1286,6 @@ "fentanyl", "fluoxetine", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -3729,7 +3721,6 @@ "hyoscine-hydrobromide", "imipramine", "levomepromazine", - "loperamide", "lorazepam", "lurasidone", "methadone", @@ -3836,7 +3827,6 @@ "hyoscine-butylbromide", "imipramine", "levomepromazine", - "loperamide", "lorazepam", "lurasidone", "methadone", @@ -3995,7 +3985,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -4081,23 +4070,8 @@ "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"], + "counterparties": ["verapamil"], + "termIds": ["azole-antifungals", "pgp"], "resolved": false, "note": "MODERATE — P-gp inhibitors (e.g., Verapamil, Ketoconazole) can theoretically push loperamide across the blood-brain barrier, causing opioid sedation, but rare at normal doses." } @@ -5932,7 +5906,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -6152,7 +6125,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -6407,7 +6379,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -6550,7 +6521,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -6658,7 +6628,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -6696,7 +6665,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -6734,7 +6702,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -6772,7 +6739,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -6824,7 +6790,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -6862,7 +6827,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -6900,7 +6864,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -7132,7 +7095,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -7251,7 +7213,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -7352,7 +7313,6 @@ "codeine", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "methadone", "morphine-ir-iv", "morphine-sr-mr", @@ -7400,7 +7360,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -7437,7 +7396,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -7474,7 +7432,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -7755,7 +7712,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -7946,7 +7902,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -8303,7 +8258,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -8500,7 +8454,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -8624,7 +8577,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -8847,7 +8799,6 @@ "diazepam", "fentanyl", "hydromorphone-ir-iv", - "loperamide", "lorazepam", "methadone", "midazolam", @@ -8946,7 +8897,6 @@ "hydromorphone-ir-iv", "indapamide", "labetalol", - "loperamide", "lorazepam", "methadone", "metoprolol", diff --git a/data/outstanding-issues-snapshot.json b/data/outstanding-issues-snapshot.json index 2dca9ad43..b35932a90 100644 --- a/data/outstanding-issues-snapshot.json +++ b/data/outstanding-issues-snapshot.json @@ -10,7 +10,7 @@ "p2": 43, "p3": 33, "queued": 11, - "pending": 31, + "pending": 32, "resolved": 359 }, "queue": [ @@ -871,6 +871,12 @@ "summary": "#HDYCQP: Resolved 2026-08-21: mergeability classifies merged/closed PRs as successful skips before unknown-state retry; both workflow call sites pass state and merged, with offline self-test coverage.", "created_at": "2026-08-21" }, + { + "request_id": "296db779-44cb-4817-9592-3ebf22aca38b", + "action": "update", + "summary": "#1YPV51: detail → PARTIAL PROGRESS 2026-08-22, NOT a clinical sign-off. The sign-off block in docs/medication-interaction-lexicon-review.md is deliberately still empty and the document still reads Status: UNREVIEWED, because a Clinical Lead read of the 37-term sheet has still not happened. What DID happen: the owner reviewed seven specific questions raised from the report and one real defect was found and fixed. FIXED: loperamide was resolving as a member of the opioids term because the selector matches the substring Opioid and the catalogue classifies it Peripheral Opioid Agonist. It is P-gp-restricted at therapeutic doses and does not contribute to the sedation and respiratory-depression rows that term drives (35 of 36 rows CRITICAL or HIGH). It is now deny-listed alongside naltrexone and naloxone, which were already excluded for the same class of reason. Proven, not asserted: regenerating the index with and without the change alters 35 medications, removes loperamide as a counterparty from every one of them, adds nothing anywhere, and leaves resolvedRows/unresolvedRows/rowsWithCatalogueTarget unchanged at 392/133/440. FOUR OTHER CHANGES WERE PROPOSED AND WITHDRAWN after reading the implementation: adding moclobemide to maois, removing atomoxetine from snris, folding the Z-drugs into benzodiazepines, and splitting antihistamines by sedation. All four would have overridden deliberate catalogue classifications (moclobemide is subclass RIMA not MAOI; atomoxetine is subclass SNRI (Non-stimulant); zolpidem and zopiclone are class Sedative subclass Z-Drug) or broken a term's meaning. The sedation concern they were aimed at is carried by the cns-depressants term, which is kind: mechanism and deliberately unenumerable, so it warns on no specific drug by design. TWO QUESTIONS REMAIN FOR THE OWNER, both catalogue-level rather than lexicon-level: (1) should moclobemide's RIMA classification make it fire serotonergic interaction warnings alongside the irreversible MAOIs; (2) is atomoxetine correctly classed SNRI for interaction purposes. STILL OPEN: the full clinical read. Do not close this row without explicit owner confirmation that the review was actually carried out.", + "created_at": "2026-08-22" + }, { "request_id": "2b98cd3b-a7be-454b-a819-9d6a42d52423", "action": "done", diff --git a/docs/medication-interaction-lexicon-review.md b/docs/medication-interaction-lexicon-review.md index a32480f22..e81edebed 100644 --- a/docs/medication-interaction-lexicon-review.md +++ b/docs/medication-interaction-lexicon-review.md @@ -27,7 +27,7 @@ CRITICAL or HIGH. Start at the top — the table is sorted by severe usage. | Term | Matches these phrases | Rows | Severe | Resolves to | | -------------------------- | -------------------------------------------------------------------------------------------------------- | ---- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `nsaids` | nsaids, nsaid | 40 | 39 | **8** — Aspirin, Celecoxib, Diclofenac, Ibuprofen, Ketorolac, Meloxicam, Naproxen, Parecoxib | -| `opioids` | opioids, opioid, opioid analgesia, opiates, full agonists | 36 | 35 | **14** — Buprenorphine (SL/depot), 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 | +| `opioids` | opioids, opioid, opioid analgesia, opiates, full agonists | 35 | 35 | **13** — Buprenorphine (SL/depot), Buprenorphine + naloxone, Buprenorphine patch, Codeine, Fentanyl, Hydromorphone (IR/IV), Methadone, Morphine (IR/IV), Morphine SR / MR, Oxycodone IR, Oxycodone SR / MR, Tapentadol SR, Tramadol IR | | `benzodiazepines` | benzodiazepines, benzodiazepine, benzos, benzo | 32 | 32 | **8** — Alprazolam, Clonazepam, Diazepam, Lorazepam, Midazolam, Nitrazepam, Oxazepam, Temazepam | | `beta-blockers` | beta-blockers, beta blockers, beta-blocker, beta blocker, non-selective beta-blockers | 23 | 22 | **7** — Atenolol, Bisoprolol, Carvedilol, Labetalol, Metoprolol, Propranolol, Sotalol | | `ssris` | ssris, ssri | 22 | 20 | **6** — Citalopram, Escitalopram, Fluoxetine, Fluvoxamine, Paroxetine, Sertraline | @@ -74,6 +74,7 @@ Drugs a selector would otherwise have swept in. Each is a clinical claim worth c | `benzodiazepines` | Olanzapine (wafer/ODT) | class `Antipsychotic`, subclass `SGA / Thienobenzodiazepine` | | `opioids` | Naltrexone | class `Addiction`, subclass `Opioid Antagonist` | | `opioids` | Naloxone | class `Antidote`, subclass `Opioid Antagonist` | +| `opioids` | Loperamide | class `Antidiarrhoeal`, subclass `Peripheral Opioid Agonist` | | `anticoagulants` | Aspirin | class `Anticoagulant`, subclass `Antiplatelet / NSAID` | ## Terms that resolve to no catalogue drug diff --git a/docs/outstanding-issues-inbox/296db779-44cb-4817-9592-3ebf22aca38b.json b/docs/outstanding-issues-inbox/296db779-44cb-4817-9592-3ebf22aca38b.json new file mode 100644 index 000000000..79c59dde6 --- /dev/null +++ b/docs/outstanding-issues-inbox/296db779-44cb-4817-9592-3ebf22aca38b.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "id": "296db779-44cb-4817-9592-3ebf22aca38b", + "createdOn": "2026-08-22", + "action": "update", + "payload": { + "id": "#1YPV51", + "detail": "PARTIAL PROGRESS 2026-08-22, NOT a clinical sign-off. The sign-off block in docs/medication-interaction-lexicon-review.md is deliberately still empty and the document still reads Status: UNREVIEWED, because a Clinical Lead read of the 37-term sheet has still not happened. What DID happen: the owner reviewed seven specific questions raised from the report and one real defect was found and fixed. FIXED: loperamide was resolving as a member of the opioids term because the selector matches the substring Opioid and the catalogue classifies it Peripheral Opioid Agonist. It is P-gp-restricted at therapeutic doses and does not contribute to the sedation and respiratory-depression rows that term drives (35 of 36 rows CRITICAL or HIGH). It is now deny-listed alongside naltrexone and naloxone, which were already excluded for the same class of reason. Proven, not asserted: regenerating the index with and without the change alters 35 medications, removes loperamide as a counterparty from every one of them, adds nothing anywhere, and leaves resolvedRows/unresolvedRows/rowsWithCatalogueTarget unchanged at 392/133/440. FOUR OTHER CHANGES WERE PROPOSED AND WITHDRAWN after reading the implementation: adding moclobemide to maois, removing atomoxetine from snris, folding the Z-drugs into benzodiazepines, and splitting antihistamines by sedation. All four would have overridden deliberate catalogue classifications (moclobemide is subclass RIMA not MAOI; atomoxetine is subclass SNRI (Non-stimulant); zolpidem and zopiclone are class Sedative subclass Z-Drug) or broken a term's meaning. The sedation concern they were aimed at is carried by the cns-depressants term, which is kind: mechanism and deliberately unenumerable, so it warns on no specific drug by design. TWO QUESTIONS REMAIN FOR THE OWNER, both catalogue-level rather than lexicon-level: (1) should moclobemide's RIMA classification make it fire serotonergic interaction warnings alongside the irreversible MAOIs; (2) is atomoxetine correctly classed SNRI for interaction purposes. STILL OPEN: the full clinical read. Do not close this row without explicit owner confirmation that the review was actually carried out.", + "baseRowFingerprint": "474e5feba5b3f5d8fc1cd5230994a22d6d9c642c22498922e2b286f6b43b75b4" + } +} diff --git a/scripts/build-medication-interaction-index.ts b/scripts/build-medication-interaction-index.ts index 7e664a6a2..e48038818 100644 --- a/scripts/build-medication-interaction-index.ts +++ b/scripts/build-medication-interaction-index.ts @@ -235,6 +235,7 @@ function main(): void { const consumed: string[] = []; for (const { surface, term } of LEXICON_SURFACES_BY_LENGTH) { if (!mentions(segment, surface)) continue; + if (term.sourceDenySlugs?.includes(record.slug)) continue; if (consumed.some((taken) => taken.includes(surface))) continue; consumed.push(surface); segmentTermIds.add(term.id); diff --git a/src/lib/medication-interaction-lexicon.ts b/src/lib/medication-interaction-lexicon.ts index a6e272b88..06f5d62e0 100644 --- a/src/lib/medication-interaction-lexicon.ts +++ b/src/lib/medication-interaction-lexicon.ts @@ -26,6 +26,8 @@ export type LexiconTerm = { surfaces: string[]; kind: LexiconTermKind; select?: CatalogueSelector; + /** Source records that can mention this term without declaring a matching counterparty. */ + sourceDenySlugs?: string[]; note?: string; }; @@ -40,7 +42,17 @@ const CATALOGUE_TERMS: LexiconTerm[] = [ id: "opioids", surfaces: ["opioids", "opioid", "opioid analgesia", "opiates", "full agonists"], kind: "catalogue", - select: { subclassIncludes: ["Opioid"], denySlugs: ["naltrexone", "naloxone"] }, + // `loperamide` is denied for the same reason as the antagonists: the subclass + // match is a substring one, and the catalogue classifies it "Peripheral Opioid + // Agonist" — P-gp keeps it out of the CNS at therapeutic doses, so it does not + // add to the sedation and respiratory-depression rows this term drives (35 of + // its 36 rows are CRITICAL or HIGH). Its real risk, QTc prolongation in + // overdose or with P-gp inhibitors, is carried by the catalogue's own per-drug + // QTc data and the `qtc-prolonging` mechanism term, not by this one. + select: { subclassIncludes: ["Opioid"], denySlugs: ["naltrexone", "naloxone", "loperamide"] }, + // Loperamide's own P-gp row describes possible opioid sedation, but does + // not declare every opioid as a counterparty. + sourceDenySlugs: ["loperamide"], }, { id: "ssris", surfaces: ["ssris", "ssri"], kind: "catalogue", select: { subclassIncludes: ["SSRI"] } }, { id: "snris", surfaces: ["snris", "snri"], kind: "catalogue", select: { subclassIncludes: ["SNRI"] } }, diff --git a/tests/medication-interactions.test.ts b/tests/medication-interactions.test.ts index 658ec0fd6..c6c82f4d4 100644 --- a/tests/medication-interactions.test.ts +++ b/tests/medication-interactions.test.ts @@ -39,6 +39,11 @@ describe("evaluateMedicationInteractions", () => { expect(result.highestTone).toBe("danger"); }); + it("does not reintroduce loperamide's excluded opioid term through a reverse row", () => { + const result = evaluateMedicationInteractions("morphine-ir-iv", ["loperamide"]); + expect(result.interactions.some((item) => item.counterpartySlug === "loperamide")).toBe(false); + }); + it("warns on lithium plus an NSAID or a thiazide", () => { // Both are textbook lithium-toxicity interactions and both were silent: the // catalogue record is "Lithium carbonate (IR/SR)", so no name-derived @@ -234,3 +239,30 @@ describe("interactionNoteBody", () => { expect(severityLabel("unknown")).toBe("Unknown"); }); }); + +describe("loperamide over-match: the other directions", () => { + // Complements the reverse-row test above. The over-match had two halves, fixed by + // two different mechanisms, so each needs its own guard: `denySlugs` stops + // loperamide being pulled INTO the opioids term (its catalogue subclass is + // "Peripheral Opioid Agonist", which the term matches as a substring), and + // `sourceDenySlugs` stops loperamide's OWN P-gp row — "…causing opioid + // sedation…" — expanding to all 14 opioids. + it("stays silent when loperamide is the medication being viewed", () => { + const result = evaluateMedicationInteractions("loperamide", ["morphine-ir-iv"]); + expect(result.interactions.some((item) => item.counterpartySlug === "morphine-ir-iv")).toBe(false); + }); + + it("keeps the genuine P-gp counterparty on loperamide's own row", () => { + // Verapamil really is a P-gp inhibitor, which is what that row is about. + // Losing it would mean the fix had over-corrected in the dangerous direction. + const result = evaluateMedicationInteractions("loperamide", ["verapamil"]); + expect(result.interactions.some((item) => item.counterpartySlug === "verapamil")).toBe(true); + }); + + it("leaves the sedation alerts a real opioid still triggers", () => { + // Guards the other over-correction: the deny-list must remove loperamide only, + // not weaken the term for opioids that do cross the blood-brain barrier. + const result = evaluateMedicationInteractions("diazepam", ["morphine-ir-iv"]); + expect(result.interactions.length).toBeGreaterThan(0); + }); +});