Skip to content

About

AI-assisted clinical call documentation prototype: place a phone call, get a diarized transcript and an EHR-ready draft note. Twilio + ElevenLabs Scribe v2 + Claude.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

CHIPPER Dialer

An open-source prototype that turns a phone call into a draft clinical note.

Place a call from your browser to any ordinary phone. When you hang up, the call is transcribed, the speakers are separated, and a language model drafts a structured encounter note — plain text, ready to paste into an EHR.

⚠️ This is a prototype, not a product. It is not HIPAA compliant. There are no BAAs with Twilio, ElevenLabs, or Anthropic on a personal account. Use it with role-played scenarios only. Never call a real patient or family, and never put real PHI through it. See Before you go anywhere near real patients.

Independent personal project. This is my own side project, built on personal equipment with personal accounts. It is not affiliated with, endorsed by, funded by, or connected to the Children's Hospital of Philadelphia (CHOP) or any other employer or institution, and it does not represent their views, systems, or practices. No patient data, protected health information, institutional systems, or proprietary information were used in building or testing it — every sample in this repository is synthetic. Opinions here are mine alone.


Why I built this

Telephone work is some of the most under-documented labor in medicine. A triage nurse calls a family about a febrile toddler, spends nine minutes gathering a history, giving red-flag precautions, and agreeing on a plan — then spends another four minutes typing it into the chart from memory. The documentation is always thinner than the conversation, and the parts that get lost first are exactly the parts that matter in retrospect: what precautions were actually given, and what the parent actually said.

Commercial products solve this. Doximity Dialer AI does it well. But when a tool is a black box, it's hard to answer the questions that decide whether it belongs in a clinical workflow:

  • How good does the transcript have to be before the note is trustworthy?
  • What happens when the model hears something ambiguous — does it hedge, or invent?
  • Where does speaker attribution break down, and how much does that matter?
  • What does this actually cost per call at scale?

This project exists to make those questions answerable with open components. It is small enough to read in an afternoon and cheap enough to run on a personal account (well under $0.50 for a ten-minute call, end to end). If you're evaluating ambient documentation for your organization, this is a way to develop intuition using something you fully control.

The design bet: most "AI phone" projects chase a real-time conversational agent — streaming speech-to-text into an LLM into text-to-speech, fighting latency at every hop. That's a genuinely hard engineering problem, and it's the wrong problem here. A nurse doesn't need an AI to talk to the family. They need the conversation documented. Moving all the AI work to after the call removes the latency budget entirely, which means better models, better accuracy, and far less code. Everything here is batch.


What it looks like

The call detail view puts the diarized transcript beside the generated note. You edit the note if needed, then copy it into the chart.

Transcript beside the generated encounter note

Everything above came from a synthetic call — no real patient audio. Notes are generated as plain text, not markdown, because EHR text fields render ## and ** literally; producing markdown and converting later just moves the problem around.

The dialer offers two modes. Simulated has Twilio call you and play a scripted nurse voice, which produces a real two-sided recording without needing a working computer microphone — the fastest way to see the whole pipeline work.

Dialer with live and simulated call modes

A Demo mode toggle in the header masks every phone number, so you can screen-share or record a walkthrough without exposing them.


How it works

Browser softphone ──WebRTC──► Twilio ──PSTN──► any ordinary phone
                                 │
                                 │ dual-channel recording
                                 ▼
                        POST /api/recording-done  (signature-verified webhook)
                                 │
                                 ▼
                    ElevenLabs Scribe v2  →  diarized transcript
                                 │
                                 ▼
                        Claude Sonnet  →  plain-text encounter note
                                 │
                                 ▼
                        SQLite + local disk

Why dual-channel recording matters. Twilio's record-from-answer-dual puts each party on a separate stereo channel. That means speaker attribution is a fact about the audio, not a guess from voice similarity — the nurse is channel 0 because they were on channel 0. Diarization models are good and getting better, but they still stumble on crosstalk, similar voices, and poor phone audio. Free ground truth beats a good model.

The one wrinkle: which channel is "the nurse" depends on how the call was placed. On a browser softphone call, channel 0 is the nurse's browser leg. On a simulated call (where Twilio dials you and plays scripted audio), channel 0 is the human who answers. The app tracks this per call rather than assuming — see is_simulated in src/lib/pipeline.ts.

Stack

Layer Choice Why
Telephony Twilio Programmable Voice Browser→PSTN, dual-channel recording, nothing to install on the far end
Transcription ElevenLabs Scribe v2 Strong accuracy on phone-quality audio, built-in diarization, ~$0.22/hr
Summarization Claude Sonnet Good clinical phrasing, follows "don't invent things" reliably
App Next.js (App Router) + SQLite One process, no infrastructure, runs on a laptop

Cost per call

Item Rate 10-min call
Twilio voice (both legs) ~$0.028/min ~$0.28
Twilio recording $0.0025/min ~$0.03
ElevenLabs Scribe v2 $0.22/hr ~$0.04
Claude Sonnet note ~$0.01–0.03 ~$0.02
Total ~$0.37

Plus ~$1.15/month for the phone number.


Recreate it yourself

You need about 30 minutes, three accounts, and roughly $2 to try this end to end.

1. Clone and install

Requires Node 20+. The optional sample-call generator also needs ffmpeg (brew install ffmpeg).

git clone https://github.com/YOUR_USERNAME/chipper-dialer.git
cd chipper-dialer
npm install
cp .env.example .env.local

2. Get the two keys you need for the AI half

ElevenLabs (elevenlabs.io) — free tier is enough to start. Profile → API Keys → create one with speech_to_text and text_to_speech permissions. Paste it into ELEVENLABS_API_KEY.

Anthropic (console.anthropic.com) → API Keys. Paste into ANTHROPIC_API_KEY.

3. Try the pipeline with no phone at all

npm run dev

Open http://localhost:3000, click Upload recording, and pick any two-person audio file. You'll watch it move through Transcribing → Summarizing → Ready, then get a diarized transcript and a draft note.

Don't have a recording handy? Generate a synthetic one — this script has two ElevenLabs voices perform a scripted pediatric triage call, so there's no real patient audio involved anywhere:

node scripts/generate-sample-call.mjs

It writes data/sample-call.mp3. Upload that. This is the fastest way to evaluate whether the summarization quality is good enough for your purposes, and it costs pennies. If the answer is no, you can stop here without ever touching Twilio.

4. Add real phone calls

Buy a number. Sign up at twilio.com, complete the Trust Hub compliance profile (required before a new account can buy US numbers), then Phone Numbers → Buy a number with Voice capability. ~$1.15/month.

Collect four values into .env.local:

Variable Where
TWILIO_ACCOUNT_SID Console dashboard, starts with AC
TWILIO_AUTH_TOKEN Console dashboard, next to the SID
TWILIO_API_KEY / TWILIO_API_SECRET Account → API keys & tokens → Create API key
TWILIO_PHONE_NUMBER The number you bought, E.164 (+13125551234)

Expose your machine so Twilio's webhooks can reach it:

ngrok http 3000

Copy the https://….ngrok-free.app URL into PUBLIC_BASE_URL in .env.local.

Create a TwiML App: Console → Voice → TwiML Apps → Create. Set the Voice Request URL to <your ngrok URL>/api/voice. Copy the App SID (starts with AP) into TWILIO_TWIML_APP_SID.

Restart npm run dev. The tunnel URL changes every time ngrok restarts — when it does, update both PUBLIC_BASE_URL and the TwiML App's Voice Request URL.

5. Make a call

Open http://localhost:3000/dialer. Two modes:

Simulated (recommended first) — Twilio calls you and plays a scripted nurse voice with pauses. You answer your phone and improvise as the parent. This produces a genuine two-sided recording and needs no working computer microphone, which sidesteps the most common setup problem. The audio ships with the repo, so this works out of the box; to change what the nurse says, edit src/lib/sim-script.json and re-run node scripts/generate-sim-lines.mjs.

Note the nurse is reading a fixed script and cannot react to you. If you report alarming symptoms, she'll still deliver the next reassuring line — the note documents that faithfully, which is itself a useful illustration of where a scripted demo ends.

Live call — the browser becomes a softphone and you dial out. Grant Chrome microphone access when prompted; if you skip it, the call connects but your side of the recording will be silent.

Either way: hang up, open the call from the Calls list, and watch it process.


Before you go anywhere near real patients

This prototype deliberately stops short of clinical use. To go further you would need, at minimum:

  • BAAs with every vendor. Twilio and ElevenLabs offer them on enterprise plans; Anthropic offers them via commercial agreements. Consumer-tier accounts are not covered, full stop.
  • Authentication and audit logging. There is no login. Every call is visible to anyone who opens the app.
  • Encryption at rest for recordings and transcripts. Right now they're plain files and an unencrypted SQLite database in data/.
  • A real retention policy. You can delete an individual call (removing its recording, transcript, and note) or wipe everything from the Calls page, but deletion is manual — there is no automatic expiry, scheduled purge, or enforced retention window, and nothing is deleted unless you ask for it.
  • Clinical validation. Someone has to answer how often the note misses or distorts something clinically meaningful, on your patient population and your call types. That question is the whole ballgame and no amount of good engineering answers it.
  • Two-party consent. Call recording law varies by state. Many require all-party consent.

Security notes

Running a dev server behind a public tunnel is the risky part of this setup: the tunnel URL is guessable, and everything behind it would otherwise be world-readable. The default posture is therefore deny by default.

src/proxy.ts allows exactly four path prefixes to be reachable from the internet — the three Twilio genuinely needs plus the audio it plays:

/api/voice           /api/recording-done           (both signature-verified)
/api/simulate/twiml  /sim/*.mp3

Everything else — the UI, /api/calls, /api/token, /api/ingest, /api/simulate — returns 403 unless the request originated on your machine. Without this, a stranger with the tunnel URL could read every transcript and note, rewrite a note before it's pasted into a chart, or pull a Voice token and place calls billed to you.

Also in place:

  • Twilio webhooks are signature-verified (X-Twilio-Signature), so callbacks can't be fabricated. Mismatches log the URL they were validated against — the usual cause is a stale PUBLIC_BASE_URL after the tunnel restarted.
  • The locality check doesn't rely on the Host header alone, which is client-supplied. It inspects forwarding headers, which a remote caller cannot forge past the tunnel edge. See src/lib/localOnly.ts.
  • Recording downloads are restricted to twilio.com over HTTPS with redirects disabled — the URL arrives in webhook data and is treated as untrusted.
  • Uploads are capped at 200 MB with an audio-extension allowlist.
  • Processing is claimed atomically, so a webhook retry and a manual reprocess can't both transcribe the same recording.
  • Secrets live only in .env.local, gitignored along with data/.
  • Calls can be deleted individually or all at once, which removes the audio file from disk as well as the database row. Worth doing after a demo.

What is not here: there is no authentication. Anyone with access to your machine has access to every call. That's acceptable for a single-operator prototype and unacceptable for anything else.

If you fork this, rotate any credential that has ever been pasted somewhere shared.


Project layout

src/
  app/
    page.tsx                 Calls list
    dialer/page.tsx          Softphone + simulated-call UI
    calls/[id]/page.tsx      Transcript beside the editable note
    api/
      token/                 Twilio Voice access token for the browser SDK
      voice/                 TwiML for outbound softphone calls
      simulate/              Places a scripted call (local-only)
      recording-done/        Twilio webhook → kicks off processing
      ingest/                Upload a recording, skip telephony entirely
      calls/                 Read/update call records
  lib/
    pipeline.ts              transcribe → label speakers → summarize
    transcribe.ts            ElevenLabs Scribe v2 + channel handling
    summarize.ts             Clinical note prompt + markdown stripping
    guard.ts                 Webhook signature + local-only checks
    db.ts                    SQLite schema and queries
scripts/
  generate-sample-call.mjs   Synthetic two-voice call for offline testing
  generate-sim-lines.mjs     Renders the simulated nurse's lines

Things I'd do next

  • Verify the note against the transcript with a second model pass and flag unsupported claims
  • Compare summarization models head-to-head on the same transcripts
  • Structured output (FHIR DocumentReference) instead of a text blob
  • Let clinicians correct speaker labels and feed that back into evaluation

License

MIT — see LICENSE.

Disclaimer

An independent personal project by Stephon Proctor, developed on personal time and equipment using personal accounts. Not affiliated with, endorsed by, or representing the Children's Hospital of Philadelphia (CHOP) or any other employer or institution. Nothing here reflects the views, systems, policies, or practices of any organization I am associated with. No patient data, PHI, institutional systems, or proprietary information were used — all examples are synthetic.

Also not affiliated with or endorsed by Doximity, Twilio, ElevenLabs, or Anthropic.

Not a medical device. Not validated for clinical use. Provided as-is for demonstration and research purposes only.

About

AI-assisted clinical call documentation prototype: place a phone call, get a diarized transcript and an EHR-ready draft note. Twilio + ElevenLabs Scribe v2 + Claude.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages