Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Baton Rx

Cross-specialist medication ownership reconciliation for independent primary care — because every med handoff needs someone holding the baton.

Independent PCPs receive consult notes from outside specialists as unstructured faxes. Specialists change medications — but nobody reconciles who owns each medication afterward. Substitutions go uncommunicated, titration responsibility gets assumed by both sides (or neither), and cross-prescriber interactions slip through because no single system sees the whole regimen.

Baton Rx is an agentic pipeline that ingests consult notes, reconciles them against the patient's FHIR record, and maintains a live ledger: every active medication assigned to an accountable clinician, with provenance, confidence, and flags for orphaned meds and cross-specialist conflicts. It then drafts the corrective actions — specialist message, patient message, chart note — for physician approval.

Epic flags a drug–drug interaction when both scripts live in one system. Here they live in two systems connected only by a fax. That's the gap this closes.

Demo scenario

Base chart: Isreal Howell, 85M — richest cardiometabolic patient in the synthetic-ambient-fhir-25 dataset (ischemic heart disease s/p NSTEMI, HTN, new T2DM, hyperlipidemia). His source chart already contains real reconciliation debt: a duplicate simvastatin entry from an old hospitalization and a stale metoprolol 50 mg order.

Three synthetic consult notes arrive in sequence (all editable in data/scenario.js):

  1. Cardiology — post-PCI: starts clopidogrel, switches losartan → lisinopril, claims metoprolol titration, silent on the statin. The agent also flags what the cardiologist didn't address: a K⁺ of 5.16 against a new ACE inhibitor.
  2. GI — starts omeprazole for reflux. No mention of the cardiac regimen → clopidogrel × omeprazole CYP2C19 conflict across two prescribers.
  3. Endocrinology — "continue metformin per PCP" against a referral that said "please assume diabetes management" → a documented bidirectional orphan.

Architecture

flowchart TD
    A[Consult note arrives<br/><i>unstructured fax text</i>] --> B[1 · EXTRACT<br/>meds, actions, management<br/>statements, verbatim source spans]
    F[(FHIR chart<br/>MedicationRequests,<br/>conditions, labs, referrals)] --> C
    B --> C[2 · RECONCILE<br/>diff vs current ledger:<br/>confirmed / new / changed /<br/>stopped / chart-only / note-only]
    C --> D[3 · ASSIGN OWNERSHIP<br/>pcp_owned · specialist_owned ·<br/>comanaged · orphaned · retired<br/>+ source, confidence, rationale]
    D --> E[4 · CONFLICT SCAN<br/>cross-prescriber DDIs, duplications,<br/>substitution hazards, lab flags]
    E --> G[5 · ACTION DRAFTING<br/>specialist msg · patient msg ·<br/>chart note + on-approve effects]
    G --> H{Physician review}
    H -- approve --> I[Ledger state updated<br/>orphan resolved, conflict closed]
    H -- edit / dismiss --> H

    subgraph "Each stage = one schema-constrained Claude call"
        B; C; D; E; G
    end
Loading

Every stage is a distinct Claude call with a strict JSON schema enforced via structured outputs (output_config.format), validated and retried on failure. Stage inputs/outputs are logged and visible in the UI's Agent pipeline log pane. Every claim in the ledger carries a verbatim source span, a FHIR resource id, or an explicit inference label — click any note quote to open the original note with the sentence highlighted.

Stack

  • Backend — Node + Express (server/). Anthropic SDK, claude-sonnet-5 by default (ANTHROPIC_MODEL to override). State persisted as JSON on disk (server/state/state.json) — no database.
  • Frontend — React + Vite (web/). Single screen: the ledger is the interface. Live ingestion streams stage progress over SSE.
  • Datadata/chart.js (FHIR-derived chart) and data/scenario.js (the three consult notes). Edit these to change the clinical scenario.

Setup

Requires Node 18+ and an Anthropic API key.

export ANTHROPIC_API_KEY=sk-ant-...

# install
npm install --prefix server
npm install --prefix web

# run (two terminals)
npm start --prefix server        # API on :8787
npm run dev --prefix web         # UI  on :5173  (proxies /api)

Open http://localhost:5173.

Evaluation harness

Physician-designed eval harness in eval/: deterministic gates + gold-label scorer (offline, no API), variance runner, and an LLM judge for graded quality only. Gold labels live in eval/gold/ and are DRAFT pending two-physician adjudication — every report carries a watermark until then. Results render in the app at #/evals.

node eval/score.js note1              # gates checklist + per-stage scorecard (offline)
node eval/gates.js note1              # deterministic gates only (offline)
npm run eval:variance note1 -- --n 5  # replay the note 5x, report flip rates + worst run
npm run eval:judge note1              # LLM judge: rationale + patient-message quality

Design rules: gates are pass/fail and never averaged; per-stage scores with no composite; patient drafts scored as if they ship; unplanted findings adjudicated (required / acceptable / noise / harmful), never auto-penalized.

CLI (pipeline without the UI)

node server/cli.js --fresh                    # reset + ingest first note
node server/cli.js note-2-gi                  # ingest a specific note

Prints each stage's structured output, then the resulting ledger, conflicts, and pending actions.

Demo script (3 min)

  1. Open the mock EHR (default route) — a generic patient chart ("Meridian EHR") showing the raw 22-row medication list: current orders, stale duplicates, hospital-discharge imports, and three unfiled outside faxes. This is what the PCP actually sees. Click the Baton Rx tile in the SmartApps rail — a mock SMART on FHIR launch with patient context.
  2. The ledger opens on the stale chart: 9 "active" meds, including the duplicate simvastatin and the outdated metoprolol 50 mg, all low-confidence.
  3. Ingest the cardiology note — watch the five agent stages run live. Ledger updates with provenance: clopidogrel appears (specialist-owned), the losartan→lisinopril substitution is flagged, metoprolol becomes co-managed, and a patient-facing "stop taking losartan" action is drafted. The agent also raises the K⁺ 5.16 monitoring flag the cardiologist glossed over.
  4. Ingest the GI note — the conflict banner fires: clopidogrel (Dr. Vasquez) × omeprazole (Dr. Chen), CYP2C19 mechanism, with a drafted message recommending the pantoprazole swap.
  5. Ingest the endocrinology note — metformin turns ORPHANED: the referral said "assume diabetes management," the consult note says "continue per PCP." Approve the drafted specialist message and watch the state resolve.
  6. Click any row — the evidence chain: verbatim note sentence (click-through to the highlighted source), FHIR resource id, rationale, confidence.

Reset demo in the header returns to the stale-chart state at any time.

Data

Built on the synthetic-ambient-fhir-25 dataset (Abridge, hackathon-provided; fully synthetic — Synthea + LLM-generated). The dataset contains no specialist consult notes, so the three notes were authored for this scenario, grounded in the selected patient's actual FHIR medications, conditions, and labs. No real patient data anywhere.

Notes

  • All clinical content is synthetic and for demonstration only — not medical advice, not a medical device.
  • Secrets: the API key is read from the environment; nothing is committed.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages