Skip to content

fix(medications): stop loperamide resolving as an opioid for interaction alerts - #2278

Merged
BigSimmo merged 11 commits into
mainfrom
claude/lexicon-loperamide-overmatch
Aug 22, 2026
Merged

fix(medications): stop loperamide resolving as an opioid for interaction alerts#2278
BigSimmo merged 11 commits into
mainfrom
claude/lexicon-loperamide-overmatch

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

Loperamide was resolving as a member of the opioids interaction term. The term selects subclassIncludes: ["Opioid"] — a substring match — and the catalogue classifies loperamide "Peripheral Opioid Agonist" (class Antidiarrhoeal). 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-prolonging mechanism term, not by this one.

It now sits in denySlugs beside naltrexone and naloxone, 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:

  • 35 medications change — alprazolam, clonazepam, diazepam, lorazepam, ketamine, gabapentin, the buprenorphine forms, the sedating antihistamines, and the rest of the CNS-depressant partners.
  • Loperamide is removed as a counterparty from every one of them.
  • Nothing is added anywhere — verified by diffing counterparty sets in both directions.
  • resolvedRows / unresolvedRows / rowsWithCatalogueTarget unchanged 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:

Proposed Why it was wrong
Add moclobemide to maois Catalogue subclass is RIMA; phenelzine and tranylcypromine are Irreversible MAOI. The term is honouring a deliberate distinction, not forgetting a drug.
Remove atomoxetine from snris Catalogue subclass is SNRI (Non-stimulant). Removing it would contradict the catalogue.
Fold the Z-drugs into benzodiazepines Zolpidem and zopiclone are class Sedative, subclass Z-Drug. They are not benzodiazepines and the term selects the benzodiazepine class.
Split antihistamines by sedation The term selects the antihistamine subclass; splitting it would mean the term no longer means "antihistamines".

The sedation concern all four were aimed at is carried by cns-depressants, which is kind: "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

#1YPV51 exists because #318 was 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 RIMA classification should still fire serotonergic warnings alongside the irreversible MAOIs, and whether atomoxetine is correctly classed SNRI for 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 --check on all three changed files — clean.
  • Counterparty-set diff computed in both directions to prove removals only.

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:interactions produces ~3,100 lines of pure serialisation churn. Running Prettier afterwards collapses the diff to the real change — 3,151 lines became 47. Both --check gates compare semantically and pass either way, so the churn is invisible to them and easy to commit by accident.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Notes

  • This removes an alert rather than adding one, which is the direction that deserves scrutiny. The justification is that the alert was never clinically applicable: the catalogue itself records loperamide as peripherally acting, and the term it was firing is about central effects.
  • No migration, no production change, no Supabase access.

…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>
@supabase

supabase Bot commented Aug 22, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

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 @coderabbitai review or push new commits to the PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a205244-d70d-4880-890f-b40e3c040a11

📥 Commits

Reviewing files that changed from the base of the PR and between 24a2d07 and f4263db.

📒 Files selected for processing (7)
  • data/medication-interaction-index.json
  • data/outstanding-issues-snapshot.json
  • docs/medication-interaction-lexicon-review.md
  • docs/outstanding-issues-inbox/296db779-44cb-4817-9592-3ebf22aca38b.json
  • scripts/build-medication-interaction-index.ts
  • src/lib/medication-interaction-lexicon.ts
  • tests/medication-interactions.test.ts

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 2 failed job(s):

  • Static PR checksneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/lib/medication-interaction-lexicon.ts
@BigSimmo
BigSimmo enabled auto-merge (squash) August 22, 2026 08:56

BigSimmo commented Aug 22, 2026

Copy link
Copy Markdown
Owner Author

Unblock update

Final head: 3400b719707cbbf8bcb5a2e9fe3c2baa65c4c321.

Blocker and disposition: exact-head Static PR checks found docs/medication-interaction-lexicon-review.md stale after the resolved loperamide reverse-match fix. The generated report’s opioids usage is now 35 rows / 35 severe; the committed sheet incorrectly said 36 rows. Updated that one generated-artifact value.

Sync and merge-tree: already current with main (0 behind, 6 ahead); git merge-tree --write-tree origin/main <head> is clean (4844ea58fa71dfc2cda074c84c58693786f9f8d0). GitHub reports mergeable.

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 opioids term. git diff --check passed. The isolated checkout has no tsx, so the generator command was not run locally and dependencies were not installed.

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; PR required is awaiting those selected upstream jobs. No old-head result is being treated as green.

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

BigSimmo and others added 9 commits August 22, 2026 17:09
…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>
@BigSimmo
BigSimmo merged commit 4cbac0c into main Aug 22, 2026
26 checks passed
@BigSimmo
BigSimmo deleted the claude/lexicon-loperamide-overmatch branch August 22, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant