Skip to content

fix(medications): reach lithium, retire the dead z-drugs term, make lexicon review flags evidence-bearing - #1923

Merged
BigSimmo merged 11 commits into
mainfrom
claude/patient-interactions-drug-alerts-3tztvw
Aug 13, 2026
Merged

fix(medications): reach lithium, retire the dead z-drugs term, make lexicon review flags evidence-bearing#1923
BigSimmo merged 11 commits into
mainfrom
claude/patient-interactions-drug-alerts-3tztvw

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

  • Removed the dead z-drugs lexicon term. Its phrasing appears nowhere in the corpus — no interaction row says "Z-drugs", "zolpidem-type hypnotics" or even "hypnotics" — so it could never fire while implying a coverage the tool does not have. Zolpidem and zopiclone are named only through "CNS depressants" (10 rows) and "sedatives" (2 rows), both deliberately unenumerable mechanism terms, so those rows stay unresolved and the medication holds at grey. Enumerating them would turn a fail-safe grey into a confident red across an ill-defined class, which is a clinical decision rather than a lexicon edit, so it is recorded in the review sheet instead.

  • Verified antipsychotics and acei/arbs; both were correct, and the flags that raised them were the defect. All 26 antipsychotics the term resolves to are genuine, and the catalogue holds exactly one ACE inhibitor (perindopril) and one ARB (candesartan), so neither is a narrow selector. Those two heuristics asked questions instead of answering them, which spent clinical-reviewer attention on non-issues and buried the one real finding beneath them. The single-drug flag is replaced by a missedClassMembers check that looks for catalogue drugs whose own class or subclass names a term's phrase but were left out of it — the missed-alert direction, which is the dangerous one. It finds none, and the sheet now states that under a new "checks that ran and found nothing" heading so a reviewer can tell "checked, clean" from "never checked". The breadth flag is scoped to substring selectors, because a raw count is a poor signal for a hand-written classes list and every catalogue term above 14 drugs today is one.

  • Fixed lithium being unreachable — the substantive finding. A new coverage section in the review sheet lists the medications that no interaction row names, and lithium was sitting in it. The catalogue record is "Lithium carbonate (IR/SR)"; drug-name matching derives its surfaces from the record name and stripDosageForm does not strip a parenthesised suffix, so nothing ever matched the bare "Lithium" that all eight referring rows write: NSAIDs (diclofenac, meloxicam, naproxen), diuretics (frusemide, hydrochlorothiazide, indapamide), psyllium and iodine. Lithium plus an NSAID and lithium plus a thiazide are textbook lithium-toxicity interactions and the tool was silent on both. Fixed with an explicit name alias in the lexicon rather than by loosening the matcher: the generic first-word fallback that would also catch this matches "Sodium" in a row about sodium content, "Vitamin" against Vitamin K in the warfarin rows, and "Potassium" against hyperkalaemia prose. An alias is precise and, unlike a matcher change, appears in the review sheet for a clinician to confirm.

  • Added a coverage section stating the feature's honest boundary. 127 of the catalogue's 328 medications are named by no interaction row at all, so entering one produces silence — which on screen is indistinguishable from "checked, nothing found". The section groups them by class. Most of it is genuine corpus coverage (antibiotics, antidiabetics, aperients, vitamins) that can only be widened by writing interaction rows, not by editing the lexicon; lithium was the exception, content that existed and could not be reached.

Net effect on resolution: 355 → 358 rows fully read, 417 → 420 drug-matched, and 149 → 146 medications holding at grey. No alert wording changed; every note still renders verbatim catalogue text.

Verification

  • npm run verify:pr-local — 18/18 stages on the merged tree, failed: (none), not reached: (none), 586 test files / 6395 tests passed with no collection errors. Run again after merging the 67 intervening commits from main, not only before.
  • npm run check:production-readiness — ran. Its 2 FAILs are missing NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY and OPENAI_API_KEY in this container, which is the expected provider-capability gap for an offline environment and is not caused by this diff.

UI verification not run: this change touches the lexicon, the generated index, the report generator and documentation. No component, route, style, or browser behaviour is modified, so there is no changed UI path for verify:ui to exercise.

Live retrieval evals not run: pr-policy classifies these paths as ragRanking: false, and nothing under src/lib/rag/**, clinical-search, retrieval-selection, ranking-config or answer-ranking is touched. The medication interaction index is a separate offline artefact from the RAG retrieval path.

Risk and rollout

  • Risk: this changes which drug-drug interaction alerts fire. The direction is additive — lithium gains eight HIGH counterparty rows it previously missed — so the risk of the change itself is a new alert appearing rather than an existing one disappearing. The z-drugs removal cannot suppress an alert, because the term matched zero rows before removal; resolved-row count is unchanged by it. The fail-safe invariant is untouched: green remains unreachable whenever any interaction row on a medication could not be machine-resolved, and unresolved rows still degrade to a neutral "needs manual review" state.
  • Rollback: revert this commit. The generated index is rebuilt deterministically from the lexicon by npm run medications:interactions, and npm run check:medication-interactions fails if the committed artefact and the lexicon disagree, so a revert cannot leave a stale index behind.
  • Provider or production effects: None. No provider call, no schema change, no environment change.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use — every alert renders the verbatim catalogue row text and links to the counterparty medication record; nothing is paraphrased or summarised, and the review sheet states plainly that the mappings are unvalidated until signed off.
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval — the patient medication list remains anonymous catalogue slugs in the existing session-scoped store, cleared on tab close, with no identifiers added by this change.
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy) — no Supabase configuration, migration, or environment value is touched by this change.
  • Service-role keys and private document access remain server-only — no key handling, authentication, or document access path is modified; the lexicon and generated index are static offline artefacts.
  • Demo/synthetic content remains clearly separated from real clinical sources — no demo corpus or synthetic fixture is involved; the index is built solely from data/medications-snapshot.json.
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative — the change makes the conservative behaviour more visible rather than less. Unresolved rows still hold a medication at grey, the review sheet is still marked UNREVIEWED pending clinician sign-off, and the new coverage section states explicitly which medications the tool can never warn about so that silence is not read as reassurance.
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed — docs/samd-classification-medication-considerations.md is updated in this change. That note remains OPEN and unassigned; this change adds the lithium finding and the coverage boundary to it rather than narrowing the open question.

Notes

  • The review sheet's remaining flag is unresolved on purpose: the catalogue holds two records both named "Warfarin" (warfarin-vka, warfarin-anticoagulant) that share no interaction rows at all — three each, zero in common — so which record a clinician opens decides which warnings they see. That is a catalogue data defect rather than a lexicon fault, and reconciling the records is a clinical content decision, so it is reported and pinned by a test rather than silently patched. It still needs an owner.
  • Three regression tests were added: lithium reachable from all six referring drug rows, lithium warning end-to-end through the evaluator for an NSAID and three diuretics, and a check that no lexicon term can ever fire on zero rows so a dead entry cannot reappear.
  • The lexicon review sheet remains UNREVIEWED. Until the sign-off block is filled in, every interaction alert is an unvalidated mapping over source-backed text.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X12okkARLhFQ2zgcPoFp54


Generated by Claude Code

…iew flags evidence-bearing
Follow-up on the three items the lexicon review sheet raised. Two were false
alarms, one was real, and chasing them surfaced a worse defect underneath.
**`z-drugs` was dead.** Its phrasing appears nowhere in the corpus — no row says
"Z-drugs", "zolpidem-type hypnotics" or even "hypnotics" — so it could never
fire, while implying a coverage the tool does not have. Removed. Zolpidem and
zopiclone are named only via "CNS depressants" and "sedatives", both deliberately
unenumerable `mechanism` terms, so those rows stay unresolved and the medication
holds at grey. Enumerating them would turn a fail-safe grey into a confident red
across an ill-defined class, which is a clinical decision rather than a lexicon
edit.
**`antipsychotics` and `acei`/`arbs` were both correct.** All 26 antipsychotics
are genuine; the catalogue holds exactly one ACE inhibitor and one ARB. The flags
that raised them asked questions instead of answering them, which spent reviewer
attention on non-issues and buried the one real finding. So:
- The single-drug flag is replaced by `missedClassMembers`, which looks for
catalogue drugs whose own class or subclass names a term's phrase but were left
out of it — the missed-alert direction. It finds none, and the sheet now says
so under "checks that ran and found nothing" rather than leaving a reviewer
unable to tell "checked, clean" from "never checked".
- The breadth flag is scoped to substring selectors. A count is a poor signal for
a hand-written `classes` list; every catalogue term above 14 drugs today is one.
**Lithium was unreachable, and that is the real finding.** The new coverage
section lists the medications no interaction row names, which exposed lithium
sitting in it. The record is "Lithium carbonate (IR/SR)" and drug-name matching
does not strip a parenthesised suffix, so nothing matched the bare "Lithium" that
all eight referring rows write: NSAIDs (diclofenac, meloxicam, naproxen),
diuretics (frusemide, hydrochlorothiazide, indapamide), psyllium and iodine.
Lithium plus an NSAID and lithium plus a thiazide are textbook toxicity
interactions and the tool was silent on both.
Fixed with an explicit name alias rather than by loosening `stripDosageForm`:
the generic first-word fallback that would catch this also matches "Sodium" in a
row about sodium content, "Vitamin" against Vitamin K in the warfarin rows, and
"Potassium" against hyperkalaemia prose. An alias is precise and, unlike a
matcher change, appears in the review sheet for a clinician to confirm.
Resolution 355 -> 358 rows, 420 drug-matched, 149 -> 146 medications holding at
grey. 127 of 328 medications remain unreachable; that residue is genuine corpus
coverage and is now stated as the feature's honest boundary.
Pinned by three new tests: lithium reachable from all six drug rows and warned
end-to-end through the evaluator, and no lexicon term that can never fire.
@coderabbitai

coderabbitaiBot commented Aug 13, 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 recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in:26 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8f67c63f-693f-432c-94f6-5ecb15d10c1d

📥 Commits

Reviewing files that changed from the base of the PR and between db274b8 and 630b414.

📒 Files selected for processing (18)
  • data/medication-interaction-index.json
  • docs/branch-review-records/391382c28f5fc43b1b7d8431141b1d93508d4082b9d44d6117ce2e92409a407b.record.md
  • docs/branch-review-records/7e4c7356d18ff24114e7666fb83b869513525f74d9ccc2d5d3d5ae56a6bc925a.record.md
  • docs/branch-review-records/e1f0256afe336c5f770289f51ee5c1e6ebeab67c19506620ecf3317979301fbd.record.md
  • docs/medication-interaction-lexicon-review.md
  • docs/outstanding-issues-inbox/1bfaf0ef-e169-4a0c-8ad0-ca391f5f6024.json
  • docs/outstanding-issues-inbox/4b95979b-65e8-474d-bc53-6f69e7eb5acf.json
  • docs/outstanding-issues-inbox/5c830d16-5076-4435-9903-6f3e7a71daa4.json
  • docs/samd-classification-medication-considerations.md
  • scripts/build-medication-interaction-index.ts
  • scripts/build-medication-lexicon-report.ts
  • src/components/clinical-dashboard/medication-considerations.tsx
  • src/components/clinical-dashboard/medication-prescribing-workspace.tsx
  • src/lib/medication-interaction-lexicon.ts
  • src/lib/medication-interactions.ts
  • tests/medication-interaction-lexicon-coverage.test.ts
  • tests/medication-interaction-unreachable-notice.dom.test.tsx
  • tests/medication-interactions.test.ts

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

@supabase

supabaseBot commented Aug 13, 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 ↗︎.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:4b5f9b0da8

ℹ️ 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 threadscripts/build-medication-lexicon-report.ts
Comment threadscripts/build-medication-lexicon-report.ts Outdated
BigSimmoand others added 6 commits August 13, 2026 23:21
…ing as an all-clear
Three follow-ups, smallest fix each.
**The parenthesised-name gap was live, not latent.** I previously reported it as
latent on the strength of a check that asked whether the affected drugs were
reachable *anywhere* rather than reachable *from the rows that name them*. They
were not. Nine rows across four more drugs were silently missing a counterparty
for exactly the reason lithium was: naloxone naming Buprenorphine, codeine and
midazolam naming Morphine, the carbapenems naming Sodium valproate, and four
rows naming Olanzapine.
Fixed at the root rather than with four more aliases: surfaces are now derived
from the parenthetical-free form as well as the raw name, and splitting debris
carrying an unbalanced bracket is dropped. The lithium alias stays, because rows
write the bare "Lithium" and the record is "Lithium carbonate (IR/SR)".
Pinned by the general invariant instead of by drug name: **if a row writes a
catalogue drug's name, that row must resolve to that drug.** Deliberately a
statement about content that exists — it says nothing about drugs the corpus
never mentions, which is coverage rather than a defect. Resolution 358 -> 360
rows, 423 drug-matched.
**Silence on an uncheckable drug composed to green.** 127 of 328 catalogue
medications are named by no interaction row, so entering one produces no alert —
and the guard against reading that as reassurance only covered the drug being
VIEWED (`dataAvailable: false`), never the drugs in the PATIENT's list. A patient
on such a drug produced zero interactions, zero unresolved rows, and a confident
green.
`evaluateMedicationInteractions` now reports `unreachableCounterparties`,
`composeMedicationVerdict` treats it as a fourth route to incomplete so green
becomes unreachable, and the considerations block names the uncovered drugs and
states that the absence of a warning is not evidence of safety for them. The set
is derived from the index at module load rather than baked into the artefact, so
the two cannot drift.
Two things kept narrow on purpose: a drug that did produce a finding is excluded
from the notice, since telling a clinician a drug "was not cross-checked"
directly above an alert about that same drug is worse than saying nothing; and
the compact callout still renders nothing at zero interactions, so the notice
lives in the always-rendered considerations section rather than inventing a new
visual state for a control whose entire job is to summarise findings.
**Captured three durable items** as outstanding-issue inbox requests: the
unfilled lexicon sign-off (P1), the divergent duplicate Warfarin records needing
a clinical owner (P2), and the 127-medication corpus coverage gap (P2). Each is
merge-safe and applies on the next `npm run issues:reconcile`.
…-alerts-3tztvw' into claude/patient-interactions-drug-alerts-3tztvw
…able
`triamcinolone` and `riboflavin` each carry one interaction row whose entire
text is "NONE." — the catalogue stating explicitly that nothing interacts.
`SEVERITY_PATTERN` requires a dash, so both parsed as severity `unknown` and
counted unresolved, which held each drug at "needs manual review" over a row
that says exactly one thing. Fail-safe direction, but the tool was claiming it
could not read something it read perfectly well.
This is the one change in this branch that moves a verdict TOWARDS green, so it
is deliberately narrow: only tokens asserting ABSENCE (`NONE`, `SAFE`) may
resolve a row that names no counterparty and matches no term. A bare "CRITICAL."
states a severity without stating what interacts — genuinely unreadable, and it
stays unresolved. Both directions are pinned, the second by an invariant rather
than by drug name, so the carve-out cannot widen unnoticed.
Resolution 360 -> 362 rows; medications that can ever show green 184 -> 186 of
328; unparsed severity tokens now zero.
@BigSimmo
BigSimmo enabled auto-merge (squash) August 13, 2026 16:38
@BigSimmo
BigSimmo merged commit eac9c4d into mainAug 13, 2026
42 checks passed
@BigSimmo
BigSimmo deleted the claude/patient-interactions-drug-alerts-3tztvw branch August 13, 2026 20:45
Sign up for freeto 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.

2 participants

@BigSimmo@claude