audit fixes (P1): medication reference correctness — H1, H2, M22 - #2580
Conversation
…e a concentration (H1)
The formulation badge matched `(\d+)\s*mg`, which captured only the integer run
immediately before "mg". A decimal strength was therefore read from its
fractional digits and a per-volume concentration from its numerator, so the
identity badge on the record hero — the most glanceable line on the page —
stated strengths that do not exist: varenicline "Tablets (0.5 mg, 1 mg)" and
colchicine badged "5 mg tablet" (tenfold), meloxicam "Tablets (7.5 mg, 15 mg)"
badged "5 mg tablet", methadone "Oral liquid syrup (5 mg/mL)" badged "5 mg
tablet", and buprenorphine/naloxone films "2/0.5 mg" badged "5 mg". 80 of the
committed snapshot's route/formulation values produced a label that is not a
strength the text states.
Trigger: any record whose Route/formulation quick row or Oral routes form row
states a decimal strength, a per-mL concentration, or a combination product
before the first plain integer-mg strength.
The scan is now decimal-aware and skips two token shapes that are never a unit
strength — a concentration ("5 mg/mL", "80 mg/2 mL") and a combination
("2/0.5 mg", "500/125 mg"). When a strength token exists but none of it is a
per-unit strength, no badge is emitted rather than a wrong one; the verbatim
formulation text is unchanged in the sections below. No first-number fallback,
per the generator comment in scripts/build-medication-interaction-index.ts.
Proved by tests/medication-badges.test.ts "formulation strength badge": unit
cases for 0.5 mg, 7.5 mg, 5 mg/mL, 2/0.5 mg and the unchanged 333 mg EC tablet,
the five committed records above, and a corpus-wide invariant that a badged
number must appear in the source as a whole strength token — not the tail of a
longer number and not a concentration. All eight failed before the fix.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
… verdict (H2)
SEVERITY_TONE mapped the catalogue's four documented-but-not-alerting severities
(low, none, safe, beneficial) to `success` — the same tone that means "checked,
nothing found". A patient list containing one of the 51 such rows therefore
composed to the success verdict: the band printed a green shield headed
"NO ALERT FOUND / From entered details" with the line "1 interaction" directly
underneath, and the row's own wording rendered below it inside a success-toned
notice with a ShieldCheck.
Trigger: any pair reaching a LOW/NONE/SAFE/BENEFICIAL row — mesalazine +
digoxin ("Decreases absorption of Digoxin"), cefepime + tramadol (additive
neurotoxicity), montelukast + phenytoin, and 34 aperient/antibiotic pairs.
Those four severities now map to `info`, which the verdict band already prints
as "Patient information / Review for this patient" and the detail panel already
renders as an informational notice, so a documented row is stated as documented
and never as an absence. `unknown` stays `neutral`. Because the documented
severities used to arrive at composeMedicationVerdict as `success`, the
incompleteness guard there caught them; it now degrades `info` to `neutral` too,
so "could not finish the check" still outranks "documented, no alert level" and
the manual-review headline is not lost. This module's header invariant — green
is unreachable on incomplete analysis, added by #2538 for checks that could not
run — is unchanged and extended: green is now also unreachable over a finding.
Proved by tests/medication-interactions.test.ts "a documented low-severity
interaction is never an all-clear" (tone mapping, the mesalazine + digoxin and
cefepime + tramadol pipelines, and that an alerting row still ranks above a
documented low one), the new composeMedicationVerdict incomplete case, and
tests/medication-low-severity-verdict.dom.test.tsx, which renders the verdict
band and asserts the headline is not "No alert found" while the count is still
stated — and that a genuinely clean, complete check still says it. All failed
before the fix.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
The review sheet led with "Status: reviewed 2026-08-22" over mappings that had changed since. The generator renders that line from whatever sign-off block the committed file already carries, carries the block forward verbatim on write, and compares only the generated half in --check — so nothing detected that the sign-off predated the table it appears to vouch for. Trigger: commit 0e70217 (#2448, 2026-08-28) added two CV medications and two deny-lists, moving `acei` from Perindopril to Perindopril + Ramipril, `statins` to three drugs and `fibrates` from two rows to one. The sheet was regenerated in that same commit, the sign-off block was untouched, and check:medication-lexicon-report reported "up to date (37 catalogue terms)". The sign-off now carries a Mappings hash: a sha256 over the catalogue terms' phrases, deny-lists and resolved drug lists — the scope the sign-off block itself claims, and both halves of each mapping, so a catalogue edit that widens a class is caught as well as a lexicon edit. It hashes the mappings, not the rendered markdown, so reflowing a table cannot invalidate a clinician's sign-off. When the recorded hash is absent or differs, the status line says the sign-off is NOT current and names the current hash, and both the write and check paths print a warning. The scoping caveat sentence is unchanged in the covered case. Deliberately a warning, not a non-zero exit: this gate runs in verify:pr-local and CI, and today's sign-off is already unbound, so failing would turn every PR red until a clinician re-attests. That is the reviewer's decision, not a side effect of adding the binding. The regenerated sheet states the stale status in its own first line; the human sign-off block is untouched. Proved by tests/medication-lexicon-report-signoff.test.ts: the hash is stable under meaningless ordering and moves when a term's resolved drugs, deny-list or phrases change (including the exact 2026-08-28 shape); parseSignOff reports an empty hash for a pre-binding sign-off and a placeholder as no sign-off; the status line keeps the reviewed wording only when the hash matches; and the committed sheet's status line must equal what the current mappings render. The design-system adoption manifest is the pre-commit hook's regeneration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_7fbe73b3-758e-4205-b15f-77c901ee1ee7) |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…r-policy exactly The seven checked lines carried an inline evidence note after an em dash, so scripts/pr-policy.mjs could not match the item text and PR #2580 failed the PR policy check once it left draft. The note now sits on its own line under the exact item. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a29874017f
ℹ️ 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".
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_1227a24d-034c-4103-a6fc-998b353a8123) |
There was a problem hiding this comment.
🟢 Approval recommended
The clinical-safety behavior changes are well-scoped and heavily test-pinned, with only minor wording/test-robustness nits identified.
Pull request overview
Implements the P1 audit remediation package for medication reference correctness (findings H1, H2, M22) by tightening medication strength badge parsing, preventing documented low-severity interactions from composing into an “all-clear” verdict headline, and binding lexicon clinical sign-off to the exact catalogue mappings via a stable hash (with supporting tests).
Changes:
- Fix formulation strength badges to preserve decimal strengths and avoid badging concentrations (mg/mL, mg/2 mL) or combination ratios (e.g. 500/125 mg).
- Re-map documented low/none/safe/beneficial interaction severities away from
successand ensure incomplete-check “manual review” messaging still outranksinfo. - Add lexicon report sign-off binding via a SHA-256 mappings hash, update the review sheet status line accordingly, and add new tests to pin the behavior.
File summaries
| File | Description |
|---|---|
| src/lib/medication-badges.ts | Parse first unit strength robustly (decimals), and suppress badges for concentrations/combination products. |
| src/lib/medication-interactions.ts | Move documented low-severity tones off success; ensure incomplete-check verdicts degrade to neutral even from info. |
| scripts/build-medication-lexicon-report.ts | Compute a stable mappings hash, render status line from sign-off + current hash, and warn when sign-off is stale. |
| docs/medication-interaction-lexicon-review.md | Regenerated review sheet with “NOT current” status line when sign-off isn’t hash-bound. |
| tests/medication-badges.test.ts | Add pinned cases + corpus-wide invariant to ensure badged strengths are real unit-strength tokens in source text. |
| tests/medication-interactions.test.ts | Pin tone mapping for documented severities and protect manual-review headline behavior when incomplete. |
| tests/medication-low-severity-verdict.dom.test.tsx | DOM-level guard that a documented interaction never headlines as “No alert found”. |
| tests/medication-lexicon-report-signoff.test.ts | Unit tests for hash stability/changes, sign-off parsing, and committed sheet status line correctness. |
| docs/design-system/adoption-manifest.json | Include the new test in the design-system adoption manifest list. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… too catalogueMappingsHash hashed a selector's own denySlugs and its resolved drug list, but not sourceDenySlugs — the field build-medication-interaction-index.ts uses to suppress otherwise-generated alert rows (the statin and fibrate exclusions are the live examples). A sign-off recorded before a sourceDenySlugs edit would still read as current after the edit changed which alerts the index emits. Include it in the hashed payload so that edit invalidates the sign-off like any other mappings change, and regenerate the review sheet's hash line to match. Addresses review thread on PR #2580 (chatgpt-codex-connector, M22 finding). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0137etKe6X2p9HkXsXoYY8TA
…t-agnostic
The DOM test pinned the literal interaction count ("1 interaction") for
mesalazine+digoxin. The assertion only needs to prove a finding is stated at
all (not merely un-reassured), so a future catalogue change that adds or
removes an interaction for this pair would fail the test for an unrelated
reason. Match \d+ interactions? instead.
Addresses review thread on PR #2580 (copilot-pull-request-reviewer).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0137etKe6X2p9HkXsXoYY8TA
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0137etKe6X2p9HkXsXoYY8TA
…guard Codex raised both halves as P2 on #2585, and both check out against origin/main at cc5ebcf. The row asserted "H1 fixed the decimal and concentration cases" as settled fact. It is not settled on main: medication-badges.ts:104 still reads /(\d+)\s*mg/i, so varenicline's "Tablets (0.5 mg, 1 mg)" still badges as "5 mg" there. The fix lives only on PR #2580. H1 is an audit finding, not a ledger row, so if #2580 were closed unmerged the tenfold misread would be tracked nowhere while this lesser row sat open above it. The row now leads with that dependency and says to raise H1 as its own P1 first. The row also pointed a future implementer at a corpus-wide invariant in tests/medication-badges.test.ts. Main's nearest case checks badge-list stability, not strength parsing; the strength invariant arrives with #2580. The row now says to confirm the guard exists and to write the focused assertion if it does not. Amended in place rather than superseded by a second request: immutability protects requests already on main, and this one is still unmerged on its own branch, so one correct record beats two contradictory ones. Confirmed check:ledger-write-discipline passes over the edit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TXW5uh3xxxJWunb88yN6ha
…cisely PR #2580 merged as 60eca45 while the previous commit was being written, so the conditional caveat it added is now answerable with evidence instead. Verified on origin/main after the merge: medication-badges.ts:120 reads matchAll(/(\d+(?:\.\d+)?)\s*mg\b/gi), so H1's decimal and concentration cases are closed, and the corpus-wide strength invariant is present in tests/medication-badges.test.ts. The row now states both as verified facts with the commit that carries them, rather than as an assumption or as a branch of "if #2580 does not land". The guard sentence is kept but sharpened rather than dropped: the invariant constrains any change here, but it does not pin WHICH of several listed strengths is chosen, so this work still needs its own focused assertion on the multi-strength shape. That was the substance of Codex's second point and it survives the merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TXW5uh3xxxJWunb88yN6ha
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t
Summary
Audit remediation package P1 — Medication reference correctness, from
docs/audit/full-repository-audit-2026-09-02.md(PR #2573). Findings fixed:H1,H2,M22. Each fix landed test-first as its own commit and was adversarially reviewed by a second agent before the gate ran.Findings in this package not fixed here, with reasons:
Decisions needed from the owner:
| Mappings hash | f789156464c90d26b99a81c871db216bdedc2cd1de07a07f3fac9cbe88e0ea54 |in the Sign-off block. Until then the sheet's first line says the sign-off is not current.check:medication-lexicon-reportshould exit non-zero (not just warn) once a hash-bound sign-off exists. Making it fail today would turn every PR red until the re-attestation above, which is why it warns.src/lib/semantic-tone.ts, which this package does not own).RAG impact: no retrieval behaviour change — medication reference rendering only; no RAG file touched
Verification
npm run verify:pr-local— heavy plan (check:runtime, check:installed-lock-parity, format:changed, check:diff-integrity, sitemap:check, check:repo-awareness-snapshot, docs:check-index, docs:check-inventory, docs:check-scripts, docs:check-links, check:branch-review-ledger, check:outstanding-issues, check:ledger-write-discipline, lint, typecheck, test, build, check:rag:fixtures, check:medication-interactions, check:medication-lexicon-report); closing summary- failed: (none)·- not reached: (none)(EXIT=0). The unit suite completed inside the run; the reviewer's independent run of the same suite reportedTest Files 955 passed/Tests 12367 passed.npm run check:diff-integrity—[diff-integrity] PASS — 4 changed test file(s), 62 -> 90 test case(s), against base 705c2f64a.npm run check:medication-interactions—up to date (531 rows);npm run check:medication-lexicon-report—up to date (37 catalogue terms)followed by the new sign-off WARNING line, exit 0.Test Files 4 passed (4) / Tests 105 passed (105); all 19 medication/badge/cross-mode/prescribing test files19 passed / 330 passed; eslint and prettier clean on every touched file; typecheck pass.Verification not run:
npm run verify:ui— browser proof left to CI (the pinned Chromium is not installed in this container); no narrowed browser run is claimed as the full gate.Verification not run:
npm run verify:release— no release or handoff confidence is claimed.Verification not run: provider-backed gates (
check:production-readiness, everyeval:*) — nothing here touches OpenAI, Supabase, Railway or Sentry at run time; all work was offline.Risk and rollout
composeMedicationVerdictnow degrades info to neutral when the analysis is incomplete so the manual-review headline is never lost (the Never show an all-clear over a medication check that could not run #2538 guard is kept and extended). M22 warns rather than exits non-zero, so the gate stays green while the review sheet itself now says the sign-off is not current; the sheet's human Outcome row was not edited.Clinical Governance Preflight
Each item confirmed against this package's diff (findings
H1,H2,M22).Clinical KB Database(sjrfecxgysukkwxsowpy)Notes
src/lib/medication-badges.ts,src/lib/medication-interactions.ts,scripts/build-medication-lexicon-report.ts,docs/medication-interaction-lexicon-review.md,tests/medication-badges.test.ts,tests/medication-interactions.test.ts,tests/medication-low-severity-verdict.dom.test.tsx(new),tests/medication-lexicon-report-signoff.test.ts(new),docs/design-system/adoption-manifest.json(regenerated by the pre-commit hook).catalogueMappingsHashhashed only the selector's own deny-list and the resolved targets, so asourceDenySlugsedit changed which source rows raise an alert while the signed payload stayed byte-identical and a recorded sign-off would still read as current. The livestatinsandfibratesexclusions are exactly that shape. medications: render the lexicon's source-side exclusions on the review sheet #2582 putssourceDenySlugsinto the signed payload, pins it with a new case intests/medication-lexicon-report-signoff.test.ts, and moves the sheet's hash from4365ee59…tof7891564…. Merge medications: render the lexicon's source-side exclusions on the review sheet #2582 into this branch before merging this PR, or the Mappings hash quoted above is the pre-fix one.scripts/pr-policy.mjsmatches each required item by exact string, so the previous "item — rationale" one-liners satisfied only 2 of 7 and failed the requiredPR policycheck. Re-evaluated locally against the committed evaluator:ok: true,errors: [],warnings: [].origin/main(the old regex yields "5 mg tablet" for varenicline and "5 mg" for the buprenorphine/naloxone film), so every new case fails without the fix; a corpus-wide invariant now asserts a badged number must be a whole strength token that is not a concentration;infosits belowsuccessinSEMANTIC_TONE_PRIORITY, but no current code path can emit asuccesscandidate beside a documented interaction, so the gap is latent — a dedicated tone ranked above success is the owner-decision item above. Pre-existing, outside this finding: list-tail shapes still badge the last listed strength (morphine "Tablets IR (10, 20 mg)" → "20 mg tablet"), which is a real strength and was the same before this branch; worth a follow-up finding.🤖 Generated with Claude Code
https://claude.ai/code/session_01TXW5uh3xxxJWunb88yN6ha