fix(medications): restore Dosulepin to the TCA class and repair the missed-member guard (#318) - #2015
Conversation
…issed-member guard (#318) Two mechanical fixes. Neither changes a mapping that needed a clinical answer, and the sign-off block in docs/medication-interaction-lexicon-review.md is untouched — the sheet is still UNREVIEWED, which is the only thing that closes `#318`. **Dosulepin fired none of the 20 severe TCA rows.** The `tcas` selector listed slug `dothiepin`; the catalogue keys the drug as `dosulepin` — the same drug under its current INN. A search of data/medications-snapshot.json for "dothiepin" returns zero records, so the slug matched nothing and a TCA whose own record flags `Toxicity in OD: FATAL` was absent from every one of that term's CRITICAL/HIGH rows. This restores the author's evident intent rather than making a new clinical determination: they wrote `dothiepin`, and the catalogue already files the drug as `subclass: TCA`. Measured effect after regenerating the interaction index — 22 rows now name `dosulepin` as a counterparty, 20 of them CRITICAL/HIGH, up from 0 via this term. Aggregate resolution is unchanged (523 rows, 362 resolved, 161 unresolved, 423 with a catalogue target) because those rows already resolved through other TCAs, so this widens counterparties inside already-resolved rows rather than resolving new ones. Durable guard: the coverage test now fails on ANY selector slug or denySlug that resolves to no catalogue record. The pre-existing test only required a TERM to resolve to *some* drug, so `tcas` stayed green on five of its six slugs — which is exactly how this shipped. **The guard that should have caught it could not run.** `missedClassMembers()` skipped any surface stem shorter than four characters, disabling the check entirely for `tcas` and `arbs` (`ppis` was rescued by its long surface), and it read only `class`/`subclass`, never `tag`. The sheet's printed "Checks that ran and found nothing" line was therefore false for two terms. A printed clean result that could not have found anything is worse than no line at all, because it retires the question. Both closed: the floor is 3, the shortest stem any real surface produces, and the haystack includes `tag`. The sheet now raises the Celecoxib/Parecoxib coxib gap itself — 2 flagged, up from 1. Worth recording because the first attempt was wrong: the acronym fix originally matched short stems as whole tokens. Mutation testing showed that branch did no protective work — the leading `\b` already stops `arb` reaching inside `Carbapenem` — while it would newly MISS a subclass spelled `TCAs`, a regression in the dangerous direction. It is a plain prefix match, pinned by a pluralised-subclass test. Gate: `npm run verify:pr-local` completed 18 checks with "failed: (none)", including lint, typecheck, the full unit suite, build, check:medication-interactions and check:medication-lexicon-report. Focused suite 33 passed (was 30). Mutation-verified four ways: reverting the slug fails 2 tests, restoring the `< 4` floor fails 2, dropping `tag` from the haystack fails 1, and anchoring the stem tail fails 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PZL59KVSBWURMKvfc9mbJr
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PZL59KVSBWURMKvfc9mbJr
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
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: 49 minutes Limit details: You’ve used all 1 included review currently available under your plan. You completed 95 included PR reviews in the past 7 days; at that activity level, included reviews refill 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?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
Comment |
CI triageCI failed on this PR. Automated classification of the 2 failed job(s):
Compared with main CI run #11311 (cancelled). Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
Two pending outstanding-issues update requests targeted #318: 46750cbf (2026-08-15, from PR #1991) and this PR's own db498cc1 (2026-08-17), which restates and extends the former with the fixes landed here. docs:check-links' mutationConflicts guard correctly refused to pick a winner between two pending mutations on the same row, so queue the explicit cancellation the guard's own error message asks for.
…uards' into claude/p1-318-lexicon-slug-and-guards
Summary
Two mechanical fixes to the medication interaction lexicon. Neither changes a mapping that needed a clinical answer, and the sign-off block in
docs/medication-interaction-lexicon-review.mdis untouched — the sheet is still UNREVIEWED, which remains the only thing that closes#318.Dosulepin fired none of the 20 severe TCA rows. The
tcasselector listed slugdothiepin; the catalogue keys the drug asdosulepin— the same drug under its current INN. A search ofdata/medications-snapshot.jsonfor"dothiepin"returns zero records, so the slug matched nothing and a TCA whose own record flagsToxicity in OD: FATALandAnticholinergic: HIGHwas absent from every one of that term's CRITICAL/HIGH rows.This restores the author's evident intent rather than making a new clinical determination: they wrote
dothiepin, and the catalogue already files the drug assubclass: TCAwith an overview reading "Tricyclic Antidepressant (also known as Dothiepin)". Only the spelling had drifted.The guard that should have caught it could not run.
missedClassMembers()inscripts/build-medication-lexicon-report.tsskipped any surface whose singular stem was shorter than four characters, which disabled the check entirely fortcasandarbs(ppiswas rescued by its long surfaceproton pump inhibitors), and it read onlyclassandsubclass, nevertag. The sheet's printed "Checks that ran and found nothing" line was therefore false for two terms. A printed clean result that could not have found anything is worse than no line at all, because it retires the question.Measured clinical effect
After regenerating
data/medication-interaction-index.json:dosulepinas a counterpartytcastcasresolves toAggregate resolution is unchanged — 523 rows, 362 resolved, 161 unresolved, 423 with a catalogue target — because those rows already resolved through other TCAs. So this widens the counterparty set inside already-resolved rows rather than resolving new ones. The whole index diff is 30 insertions and 1 deletion, every one a
dosulepinaddition; the single deletion is prettier expanding a compact array to fit the insertion.The sheet now also raises the Celecoxib/Parecoxib coxib gap itself, where previously it could not.
Two durable guards, so neither failure recurs
slugordenySlugthat resolves to no catalogue record. The pre-existing test only required a term to resolve to some drug, sotcasstayed green on five of its six slugs — which is exactly how this shipped.missedClassMembers()is now exported and directly tested, matching its siblingsubstringTraps. It was neither before, which is why its blind spots were invisible.One thing I got wrong first, recorded because it matters
The acronym fix originally matched short stems as whole tokens (
\btca\b). Mutation testing showed that branch did no protective work — the leading\balready stopsarbreaching insideCarbapenem, the trapsubstringTrapsexists for — while it would newly miss a subclass spelledTCAs, a regression in the dangerous direction. It is a plain prefix match with the floor lowered to 3, the shortest stem any real surface produces, pinned by a pluralised-subclass test.Also queues one immutable inbox request (
db498cc1) updating#318. It is cumulative over the still-pending46750cbffrom #1991, sinceupdate --detailreplaces the cell rather than appending.Verification
npm run verify:pr-local— completed 18 checks:tests/medication-interaction-lexicon-coverage.test.ts: 33 passed (was 30).npm run check:production-readiness— run because this is clinical scope: 2 PASS, 5 WARN, 2 FAIL. Both FAILs are the documented offline provider-capability gap (absentNEXT_PUBLIC_SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY,OPENAI_API_KEYin this container), not regressions from this diff.< 4stem floor fails 2; droppingtagfrom the haystack fails 1; anchoring the stem tail fails 1.UI verification not run: no UI, routing, or styling changed. Independently, this container cannot run Chromium gates at all — Playwright ships chromium-1194 against a pinned 1234 (
#255/#312), socheck:playwright-browser-revisionfails closed andverify:ui/verify:phone-chromeare unavailable. No browser coverage is implied.No live Supabase or OpenAI call was made, and no retrieval or ranking surface was touched.
Risk and rollout
git revertthe fix commit, then re-runnpm run medications:interactionsandnpm run medications:lexicon-reportto regenerate both artefacts and commit them. The two commits are separately revertible while this PR is open; after squash merge, revert the relevant hunks of the squash commit instead.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)Notes on the items that actually bite here. Source verification and wording: unchanged — the alert text is verbatim catalogue prose in both cases, and this PR only alters which catalogue drugs a phrase resolves to. Conservative failure behaviour: preserved and improved — unresolved rows still render grey rather than green, the
#318sheet still declares itself UNREVIEWED, and the repaired guard removes a printed "checks ran clean" line that was false for two terms, which was the least conservative thing in the file. SaMD impact: considered. This changes interaction-alert reach, which is decision-support behaviour, so it is not exempt; the change restores a mapping the catalogue metadata already asserted rather than introducing new clinical logic, and it adds no new clinical claim, so the existing classification indocs/samd-classification-medication-considerations.mdis unaffected. Privacy, service-role keys, demo separation: not touched by any file in this diff.Notes
#318stays open. Five clinical questions remain, mappings deliberately unchanged: the coxibs excluded fromnsaids(38 severe rows, now auto-flagged); Moclobemide excluded frommaois(17 severe rows) — which the sheet structurally cannot surface, because Moclobemide'stagis alsoRIMA, andRIMA/MAOIare synonyms in pharmacology but unrelated as strings; Loperamide included inopioids(35 severe rows, false-alert direction);acei/arbsresolving to one drug each, which I verified is catalogue coverage rather than a narrow selector; and three antiplatelets insideanticoagulantswhile Aspirin is deliberately excluded on identical class metadata.src/lib/medication-interaction-lexicon.tson its own classifiesclinicalRisk: false. Only the generateddata/medication-interaction-index.jsonmakes a lexicon PR clinical-risk. So a lexicon edit that changes which drugs a CRITICAL phrase resolves to would skip this preflight entirely if the index were not regenerated in the same PR.npm run issues:reconcileon its own serialized branch after this lands. There are now pending requests from docs(medications): clinician reading worklist for the lexicon review (#318) #1991, fix(audit): repair the merge-loss reconciliation exemption and classify how each loss happened (#324) #2014 and this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01PZL59KVSBWURMKvfc9mbJr
Generated by Claude Code