Skip to content

fix(medication-safety): fail-safe physiological bounds for patient-profile inputs (FV-03) - #1045

Merged
BigSimmo merged 5 commits into
mainfrom
claude/patient-profile-input-bounds-123366
Jul 21, 2026
Merged

fix(medication-safety): fail-safe physiological bounds for patient-profile inputs (FV-03)#1045
BigSimmo merged 5 commits into
mainfrom
claude/patient-profile-input-bounds-123366

Conversation

@BigSimmo

@BigSimmoBigSimmo commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Summary

Fail-safe input validation for the patient-considerations profile numeric fields (age / eGFR / CrCl / QTc / serum creatinine) that feed the medication-safety alert engine (finding FV-03).

  • Reject-to-null physiological bounds at the sanitize chokepoint (sanitizeProfile in patient-profile-storage.ts): a physiologically impossible or out-of-range entry becomes null — never clamped — so the alert engine treats it as a missing input and surfaces a contraindication row as "unassessed" rather than reading garbage as a false all-clear. Bounds are input-validity only (age 0–130, eGFR 0–250, CrCl 0–400, QTc 240–800, SCr 15–3000 µmol/L, unit-aware for mg/dL) and are kept strictly separate from the clinical firing thresholds.
  • Closes a latent false-all-clear hole in the bare-renal factor (medication-patient-alerts.ts): the "eGFR or CrCl" unassessed signal now fires when either renal input is missing and neither fired (was: only when both were missing). A renal contraindication therefore clears only when both eGFR and CrCl are present and non-firing. No behaviour change on the current corpus (0 bare-renal contraindication rows) — pure future-proofing.
  • Visible form feedback (patient-profile-panel.tsx): min/max + aria-invalid + an inline out-of-range message; the typed text stays visible for correction, and the invalid value is never committed to the shared store.

The fix design was adversarially verified before implementation by an independent clinical-governance review, which confirmed that (1) null-routing can never convert a firing contraindication into a false all-clear once the bare-renal hole is closed, and (2) the bounds never reject a legitimate clinical value (neonate age 0, anuric eGFR/CrCl 0, short-QT ~250 ms, augmented renal clearance CrCl ~350, severe-AKI creatinine ~2200 µmol/L all pass).

Verification

  • npm run typecheck, npm run lint, npm run format:check
  • npm run test — full unit + jsdom suite: 349 files / 3120 passed / 0 failed, including the new FV-03 tests (storage bounds, the end-to-end garbage→unassessed regression guard for the adversarial counterexample, the engine bare-renal fail-safe, and the form validation/Clear behaviour)
  • npm run check:design-system-contract (baselines unchanged: raw colors 9, literal shadows 1, legacy tap 28), npm run check:type-scale, npm run check:icon-scale
  • npm run check:production-readinessREADY (demo-mode env warnings only; a UI-input validation change cannot affect env/config)
  • UI verification not run: npm run verify:ui runs in CI (Chromium). The change is jsdom-tested and adds no layout/token/routing change; the only rendered difference (a danger border + inline message) appears solely on out-of-range input.

Risk and rollout

  • Risk: low. Anonymous physiology only (no PHI). The bounds are validity-only and never relax the clinical firing thresholds (RENAL_IMPAIRMENT_EGFR / QTC_PROLONGED_MS / age cut-offs). Reject-to-null (never clamp) preserves the existing fail-closed unassessed path. The engine &&|| change is a proven no-op on the current medication corpus.
  • Rollback: revert this commit; the profile falls back to the prior unbounded parsing and the engine's prior bare-renal condition.
  • Provider or production effects: None. No provider, Supabase, or CI calls were made; verification was fully offline.

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 — this change makes existing decision-support strictly more conservative (surfaces "unassessed" instead of a silent clear) and does not expand SaMD scope

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Tightened numeric validation for patient profile fields with inline, accessible min–max alerts; out-of-range inputs are saved as blank (no clamping).
    • Added unit-aware serum creatinine bounds and conversion when switching units, with correct acceptance/rejection per unit.
  • Bug Fixes
    • Improved renal contraindication “fail-safe” so partial eGFR/CrCl input no longer causes a false all-clear.
    • Enhanced “Clear” to reliably reset validation and remove any inline alerts.
  • Tests
    • Added automated coverage for validation, unit switching, and the fail-safe alert behavior.

…ofile inputs (FV-03)
Patient-considerations profile numeric fields (age/eGFR/CrCl/QTc/serum
creatinine) fed the medication-safety alert engine with no validation, so a
garbage-but-present value in a gate's non-firing direction (e.g. a negative
QTc) could silently flip a contraindication from the fail-safe "unassessed"
state to a false all-clear.
- sanitizeProfile now rejects physiologically impossible / out-of-range values
to null (never clamps), routing them into the existing fail-closed unassessed
path. Bounds are input-validity only and kept separate from the clinical
firing thresholds. Serum creatinine is unit-aware (µmol/L canonical, mg/dL
normalised x88.4).
- Close a latent false-all-clear hole in the bare-renal factor: the
"eGFR or CrCl" unassessed signal now fires when EITHER renal input is missing
and neither fired (was: both missing). No behaviour change on the current
corpus (0 bare-renal contraindication rows) — pure hardening.
- Patient profile form gains min/max + aria-invalid + an inline out-of-range
message; the invalid entry is never committed to the shared store.
Fix design adversarially verified before implementation: null-routing can never
convert a firing contraindication into a false all-clear (once the bare-renal
hole is closed), and the bounds never reject a legitimate clinical value.
Verified offline: typecheck, lint, format:check, full unit+jsdom suite
(349 files / 3120 passed / 0 failed), design-system-contract (baselines
unchanged), type-scale, icon-scale, check:production-readiness (READY).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@supabase

supabaseBot commented Jul 21, 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 ↗︎.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Patient profile numeric fields now enforce physiological bounds with reject-to-null storage, unit-aware serum creatinine validation, accessible dashboard errors, and reset behavior. Renal contraindication evaluation marks partial eGFR/CrCl input as unassessed, with tests and review documentation covering the changes.

Changes

Patient Profile Safety

Layer / File(s)Summary
Profile sanitization and bounds
src/lib/patient-profile-storage.ts, tests/patient-profile-storage.test.ts
Profile fields use inclusive physiological bounds, unit-aware serum creatinine normalization, and null for invalid values; storage and alert integration tests cover these rules.
Dashboard numeric field validation
src/components/clinical-dashboard/patient-profile-panel.tsx, src/components/clinical-dashboard/patient-profile-context.tsx, tests/patient-profile-panel.dom.test.tsx
Numeric inputs expose bounds validation, preserve invalid typed text while storing null, convert creatinine values by display unit, and reset validation state on Clear.
Renal alert fail-safe evaluation
src/lib/medication-patient-alerts.ts, tests/medication-patient-alerts.test.ts
Bare renal contraindication rows remain unassessed when either eGFR or CrCl is missing, while complete and firing cases retain their existing outcomes.
Verification record
docs/branch-review-ledger.md
The review ledger records the input-bounds verification and renal null-routing correction.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
participant Clinician
participant PatientProfilePanel
participant PatientProfileContext
participant PatientProfileStorage
participant evaluatePatientAlerts
Clinician->>PatientProfilePanel: enter profile values
PatientProfilePanel->>PatientProfileContext: persist profile update
PatientProfileContext->>PatientProfileStorage: sanitize and store profile
PatientProfileStorage-->>evaluatePatientAlerts: provide sanitized profile
evaluatePatientAlerts-->>Clinician: contraindication or unassessed result
Loading

Possibly related PRs

Suggested labels:codex

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly matches the main change: fail-safe physiological bounds and related patient-profile input handling for medication safety.
Description check✅ PassedThe description follows the template with Summary, Verification, Risk and rollout, Clinical Governance Preflight, and Notes, and the key details are filled in.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/patient-profile-input-bounds-123366

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/clinical-dashboard/patient-profile-panel.tsx`:
- Around line 139-149: Update the scrUnit change handling around updateField so
the existing scr value is converted to the newly selected unit or cleared when
conversion is invalid, rather than persisted unchanged. Ensure the profile’s
serum creatinine is normalized immediately during a unit switch and remains
consistent with scrBounds and the storage-layer validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cbc6aba3-67d6-4461-804f-9347876867d9

📥 Commits

Reviewing files that changed from the base of the PR and between cde6c5c and 99c688c.

📒 Files selected for processing (7)
  • docs/branch-review-ledger.md
  • src/components/clinical-dashboard/patient-profile-panel.tsx
  • src/lib/medication-patient-alerts.ts
  • src/lib/patient-profile-storage.ts
  • tests/medication-patient-alerts.test.ts
  • tests/patient-profile-panel.dom.test.tsx
  • tests/patient-profile-storage.test.ts

Comment threadsrc/components/clinical-dashboard/patient-profile-panel.tsx
@BigSimmo
BigSimmo enabled auto-merge (squash) July 21, 2026 20:21
…e-input-bounds-123366
# Conflicts:
#	docs/branch-review-ledger.md
@BigSimmo
BigSimmo disabled auto-merge July 21, 2026 20:24
@BigSimmo
BigSimmo enabled auto-merge (squash) July 21, 2026 20:24
…03 follow-up)
Toggling the serum-creatinine unit (µmol/L ↔ mg/dL) previously called
updateField("scrUnit", …) but left the stored `scr` number unchanged, so
the alert engine re-read the same value on the new scale — a silent
misread of the renal input (a narrow window can flip a firing renal
contraindication to a false all-clear; the sanitiser fail-safes the rest
to "unassessed").
Add `convertScrValue` (µmol/L canonical, same ×88.4 factor the engine
uses) and a `setScrUnit` context action that converts `scr` and changes
the unit in a single atomic write, so the sanitiser never reads the
mismatched intermediate. The panel's unit buttons now call `setScrUnit`.
Tests: storage-level conversion + round-trip/validity, and a DOM test
proving a switch converts the stored value (90 µmol/L ↔ 1.02 mg/dL) and
round-trips.
Addresses the CodeRabbit review finding on PR #1045.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LRZ1EyBZW1ADXrZvMxEEsC

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/clinical-dashboard/patient-profile-panel.tsx (2)

221-229: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include the active unit in serum-creatinine validation messaging.

NumberField only appends a unit when unit is provided, but the serum-creatinine call omits it. The error currently renders as Enter 0.17–33.93. without indicating whether the values are mg/dL or µmol/L.

Proposed fix
 <NumberField
key={`scr-${resetNonce}-${scrUnit}`}
label="Serum creatinine"
+ unit={scrUnit}
value={profile.scr}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/clinical-dashboard/patient-profile-panel.tsx` around lines 221
- 229, Update the serum-creatinine NumberField in the patient profile panel to
pass the active scrUnit through its unit prop, so validation messages identify
whether bounds are in mg/dL or µmol/L. Preserve the existing scrBounds, value,
and change-handling behavior.

91-101: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep Clear available for invalid buffered input.

An out-of-range value is committed as null, so a profile containing only that buffered value becomes isEmpty and disables Clear at Line 317. The new reset/remount path is therefore unreachable until the user manually edits the field. Keep Clear enabled while any numeric field has a validation error, or lift that transient state to the panel.

Also applies to: 313-316

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/clinical-dashboard/patient-profile-panel.tsx` around lines 91
- 101, Update the patient profile panel’s empty-state/Clear availability logic
near the relevant isEmpty check and the numeric field change handler so buffered
invalid or out-of-range input keeps Clear enabled even when the committed value
is null. Track or lift transient validation-error state from the numeric field
handler, preserve it until the field is corrected or reset, and use it when
determining whether the profile is empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/components/clinical-dashboard/patient-profile-panel.tsx`:
- Around line 221-229: Update the serum-creatinine NumberField in the patient
profile panel to pass the active scrUnit through its unit prop, so validation
messages identify whether bounds are in mg/dL or µmol/L. Preserve the existing
scrBounds, value, and change-handling behavior.
- Around line 91-101: Update the patient profile panel’s empty-state/Clear
availability logic near the relevant isEmpty check and the numeric field change
handler so buffered invalid or out-of-range input keeps Clear enabled even when
the committed value is null. Track or lift transient validation-error state from
the numeric field handler, preserve it until the field is corrected or reset,
and use it when determining whether the profile is empty.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 636b3e71-0794-4951-9bf5-88bd1800f5ae

📥 Commits

Reviewing files that changed from the base of the PR and between 8132a5e and 676e1d9.

📒 Files selected for processing (5)
  • src/components/clinical-dashboard/patient-profile-context.tsx
  • src/components/clinical-dashboard/patient-profile-panel.tsx
  • src/lib/patient-profile-storage.ts
  • tests/patient-profile-panel.dom.test.tsx
  • tests/patient-profile-storage.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/patient-profile-storage.test.ts
  • tests/patient-profile-panel.dom.test.tsx
  • src/lib/patient-profile-storage.ts

@BigSimmo
BigSimmo merged commit fdc5c51 into mainJul 21, 2026
17 checks passed
@BigSimmo
BigSimmo deleted the claude/patient-profile-input-bounds-123366 branch July 21, 2026 21:08
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