fix(medications): stop loperamide resolving as an opioid for interaction alerts - #2278
Conversation
…ion alerts The `opioids` lexicon term selects `subclassIncludes: ["Opioid"]`, which is a substring match, and the catalogue classifies loperamide "Peripheral Opioid Agonist" (class Antidiarrhoeal). So loperamide was being treated as a member of a term whose rows are 35 CRITICAL or HIGH out of 36 - almost all additive sedation and respiratory depression. P-gp keeps loperamide out of the CNS at therapeutic doses, so it does not contribute to those, and the alert was noise in the direction that teaches people to click through real warnings. Its actual risk, QTc prolongation in overdose or with P-gp inhibitors, is carried by the catalogue's per-drug QTc data and the `qtc-prolonging` mechanism term, not by this one. It now sits in denySlugs beside naltrexone and naloxone, which were already excluded for the same class of reason - a substring match pulling in a drug the term does not mean. Measured rather than asserted. Regenerating the interaction 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 and rowsWithCatalogueTarget unchanged at 392/133/440. The diff is 35 deletions for that reason. Note for anyone regenerating these artefacts: the generator writes unformatted JSON while the committed copy is Prettier-formatted, so a bare `npm run medications:interactions` produces ~3,100 lines of pure serialisation churn. Run prettier on the file afterwards and the diff collapses to the real change. Both --check gates compare semantically and pass either way, so the churn is invisible to them. 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. Every one would have overridden a deliberate catalogue classification (moclobemide is subclass RIMA, not MAOI; atomoxetine is subclass "SNRI (Non-stimulant)"; zolpidem and zopiclone are class Sedative, subclass Z-Drug) or destroyed a term's meaning. The sedation concern they were aimed at is carried by `cns-depressants`, which is `kind: "mechanism"` and deliberately unenumerable - it warns on no specific drug by design. This is NOT the clinical sign-off. The sign-off block stays empty and the document still reads "Status: UNREVIEWED", because a Clinical Lead read of the 37-term sheet has not happened. Recording one now would repeat exactly the false closure that reopened #1YPV51. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 20 minutes Limit details: You’ve used the included review currently available. Your 90 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?Wait for the limit to reset, then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
Comment |
CI triageCI failed on this PR. Automated classification of the 2 failed job(s):
Compared with main CI run #13133 (success). Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77e5f73b4a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Unblock update Final head: Blocker and disposition: exact-head Static PR checks found Sync and merge-tree: already current with Targeted verification: compared every medication report term’s Rows/Severe values to the final interaction index; all match. Confirmed loperamide’s P-gp row contains only verapamil and no Required CI: exact-head CI run 13155 is in progress. Static PR checks, Build, Unit coverage, Production UI critical, and Production UI shards 1–3 are all running; Review threads: no unresolved blocking thread; the P2 reverse-row finding was replied to and resolved. Ledger record deferred by anti-churn policy. Residual risk: required CI must complete green on this final SHA. Merge left to the user. UNBLOCKED — CI PENDING |
…est misses The branch already fixes both halves of the over-match and the reverse-row test covers the forward case (morphine viewed, loperamide in the patient's list). Three gaps remain, and each maps to a way this could regress silently: - loperamide as the VIEWED medication. `sourceDenySlugs` stops its own P-gp row expanding "…causing opioid sedation…" to all 14 opioids; without that guard the row quietly re-acquires them and the reverse direction alerts again. - loperamide + verapamil must STILL alert. Verapamil is a genuine P-gp inhibitor and is what that row is actually about, so losing it would mean the fix had over-corrected in the missed-alert direction. - diazepam + morphine must still alert, proving `denySlugs` removed loperamide only and did not weaken the opioids term for drugs that do cross the blood-brain barrier. Tests only. No source, data or generated file is touched: the index on this tip is already correct, verified against origin/main as 36 medications changed, removals only, exactly `loperamide` plus the 13 spurious opioids from its own row, verapamil retained, all 328 medications intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… #1YPV51 update
`Static PR checks` failed on "Outstanding-issues ledger integrity". The ledger
itself was fine - writer self-test, inbox self-test and the 437-row guard all
passed. The snapshot was the stale part: queueing the #1YPV51 update earlier in
this PR took pending from 7 to 8, and data/outstanding-issues-snapshot.json was
never regenerated to match.
[snapshot] The committed snapshot is behind docs/outstanding-issues.md:
- counts.pending: committed 7 vs regenerated 8
Regenerated with scripts/generate-outstanding-issues-snapshot.mjs; the check now
reports "in step with data/outstanding-issues-snapshot.json (78 open, 8 pending)".
Worth knowing for anyone using `npm run issues:add|update|done` inside a PR that
also carries code: the queue command does not refresh the snapshot, and nothing
warns at commit time, so the failure surfaces only in CI under a job name that
points at the ledger rather than at the snapshot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mide-overmatch # Conflicts: # data/outstanding-issues-snapshot.json
…solution Resolving the snapshot conflict regenerated data/medication-interaction-index.json with the generator's unformatted JSON, leaving one counterparty array expanded across three lines. Prettier collapses it back; parsed content is byte-identical, and check:medication-interactions still reports 525 rows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Loperamide was resolving as a member of the
opioidsinteraction term. The term selectssubclassIncludes: ["Opioid"]— a substring match — and the catalogue classifies loperamide "Peripheral Opioid Agonist" (classAntidiarrhoeal). So a P-gp-restricted antidiarrhoeal was firing a term whose rows are 35 CRITICAL or HIGH out of 36, almost all additive sedation and respiratory depression.That is a false alert in the direction that does real harm: alert fatigue is how genuine warnings stop being read. Loperamide's actual risk — QTc prolongation in overdose or with P-gp inhibitors — is carried by the catalogue's per-drug QTc data and the
qtc-prolongingmechanism term, not by this one.It now sits in
denySlugsbesidenaltrexoneandnaloxone, which were already excluded for exactly the same reason: a substring match pulling in a drug the term does not mean.Measured, not asserted. Regenerating the index with and without the change:
resolvedRows/unresolvedRows/rowsWithCatalogueTargetunchanged at 392 / 133 / 440.Hence a 35-line deletion in the index, one per affected medication.
Four changes proposed and withdrawn
Raised from the review sheet, then withdrawn after reading the implementation. Recorded so they are not re-proposed:
maoisRIMA; phenelzine and tranylcypromine areIrreversible MAOI. The term is honouring a deliberate distinction, not forgetting a drug.snrisSNRI (Non-stimulant). Removing it would contradict the catalogue.benzodiazepinesSedative, subclassZ-Drug. They are not benzodiazepines and the term selects the benzodiazepine class.antihistaminesby sedationThe sedation concern all four were aimed at is carried by
cns-depressants, which iskind: "mechanism"and deliberately unenumerable — it warns on no specific drug by design, and says so in its own note.This is NOT the clinical sign-off
#1YPV51exists because#318was closed on 2026-08-18 claiming a Clinical Lead review that never happened. This PR does not repeat that.The sign-off block stays empty and the document still reads "Status: UNREVIEWED", because the 37-term clinical read has still not been done. What happened here is narrower and is recorded as such: seven questions were put to the owner, one real defect was found, and it is fixed. The queued ledger update states that explicitly and instructs that the row not be closed without confirmation the review actually occurred.
Two questions remain for the owner, both catalogue-level rather than lexicon-level: whether moclobemide's
RIMAclassification should still fire serotonergic warnings alongside the irreversible MAOIs, and whether atomoxetine is correctly classedSNRIfor interaction purposes.RAG impact: no retrieval behaviour change — this changes which drugs one interaction term resolves to; no retrieval, ranking, selection, ordering, or answer-generation surface is touched. The alert wording shown to a clinician remains verbatim from the catalogue.
Verification
npx vitest run tests/medication-interaction-lexicon-coverage.test.ts tests/medication-entities.test.ts tests/medication-badges.test.ts— "Test Files 3 passed (3) / Tests 58 passed (58)".npm run check:medication-interactions— "data/medication-interaction-index.json is up to date (525 rows)."npm run check:medication-lexicon-report— "docs/medication-interaction-lexicon-review.md is up to date (37 catalogue terms)."npx prettier --checkon all three changed files — clean.Worth knowing for anyone regenerating these artefacts: the generator writes unformatted JSON while the committed copy is Prettier-formatted, so a bare
npm run medications:interactionsproduces ~3,100 lines of pure serialisation churn. Running Prettier afterwards collapses the diff to the real change — 3,151 lines became 47. Both--checkgates compare semantically and pass either way, so the churn is invisible to them and easy to commit by accident.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)Notes