Skip to content

Repository files navigation

OneCall

A voice hotline that turns "something's wrong and I don't know who to call" into a specialist appointment request — inside one phone call.

Built in 24 hours at the YC × Medplum Agentic Healthcare Hackathon (Y Combinator office, San Francisco).


The problem

Patients are routinely handed homework they cannot do. "See a specialist" means: figure out which specialty, find one nearby who takes your insurance, confirm that's actually true, and get on their calendar.

The system loses people at every step:

~35% of specialist referral scheduling attempts end in a completed appointment (Duke, 103k referrals, JGIM 2018)
52% of Medicare Advantage directory listings had at least one inaccuracy (CMS audit, 2018)
33% of in-network phone numbers were dead or wrong; only 18% of calls reached a bookable appointment (Senate "ghost networks" secret-shopper study, 2023)
~27% of Gen Z adults can define "deductible" (NAIC, 2024)

Existing voice AI answers one clinic's phone — which assumes you already know where to call. OneCall is the call that starts one step earlier.

What it does

A caller describes a symptom out loud. Within a single conversation the agent:

  1. Screens for emergencies with a deterministic red-flag check — before any model reasoning runs
  2. Selects a specialty from a retrieved referral-criteria corpus, and reads the matching criteria back to the caller
  3. Runs a real 270/271 benefits transaction and reports precisely what it did and did not verify
  4. Finds real nearby clinics from the CMS NPPES registry, then enriches them with public-web research in the background
  5. Writes an appointment request to Medplum as FHIR R4 — and calls it proposed, never booked

Design decisions worth defending

These are the parts I'd want to talk through in an interview.

Emergency screening never touches the LLM

checkRedFlags() (lib/triage/redflags.ts) is pure deterministic regex over the transcript — nine patterns covering MI, stroke, respiratory, hemorrhage, and suicidal-ideation presentations. It runs before every other tool and re-runs whenever the caller adds symptoms.

The reasoning: the one judgment where a hallucination is catastrophic is the one judgment that should not be probabilistic. Patterns are also written to catch how people actually talk — MI-referred pain is matched on "pain going down my arm" without requiring the word "chest", because callers frequently never say it.

A triggered red flag still produces a clinical record: the route writes a FHIR Encounter with an emergency disposition. Even the abort path leaves an audit trail.

The eligibility check refuses to overclaim

This is the piece I'm most careful about. The Stedi test key accepts only published synthetic fixtures, so the transaction is real 270/271 wire traffic against a synthetic Aetna policy — not the caller's actual coverage.

Rather than paper over that, the response object carries the distinction explicitly:

{
  network: "unknown",              // a 270/271 does not establish provider participation
  callerCoverageVerified: false,
  requestedPayerUsedInTransaction: false,
  providerParticipation: {
    status: "unknown-not-verified",
    reason: "A standard 270/271 does not reliably establish provider participation.",
  },
}

The voice agent is prompted to say "Stedi's test transaction ran successfully. Its synthetic Aetna fixture returned a sample office-visit benefit; it did not check your insurance policy or this doctor's network" — and is explicitly forbidden from saying "your copay" or "you're covered."

A demo that lies about insurance coverage is worse than no demo. The 271 parsing is genuinely real; the scope of the claim is what's constrained.

Providers are real; availability is honestly labeled

Provider identity comes from live CMS NPPES lookups — real NPIs, real addresses, Haversine-filtered against ZIP centroids within the caller's stated radius. Every provider card links back to its NPPES record.

Appointment availability, however, is not public data for anyone. So the API returns availability: { status: "not-public" } and the agent says so, rather than inventing slots that look convincing on stage.

Never claim a booking that didn't happen

Appointments are written as FHIR status: "proposed", not "booked", with the clinic participant marked needs-action. The agent's prompt bans the words booked, scheduled, confirmed, and reserved.

Writes are idempotent via createResourceIfNoneExist() keyed on a deterministic identifier, and Medplum errors are deliberately allowed to propagate — lib/fhir/transaction.ts documents this as intentional, so the API can never report a booking that didn't persist.

No invented medical codes

LLMs hallucinate SNOMED, LOINC, and ICD-10 codes fluently and invisibly. Every clinical concept here uses a display-only CodeableConcept ({ text: "..." }) instead of a fabricated code. An unfilled code is an honest gap; a wrong one is a silent data-integrity bug that a clinical reviewer would catch immediately.

Tool latency never blocks the conversation

Deep provider research can take tens of seconds. Instead of leaving dead air, research_providers returns immediately with NPPES identity data plus a publicWebResearch: { status: "running" } marker; enrichment continues in the background and streams in when ready. A 45-second cap degrades to registry-only results.

Conversation state is tracked in a oneCallWorkflow object attached to every tool result (lib/voice/workflow.ts), which pins the agent to the next unfinished step. This is what stops the classic voice-agent failure of re-asking for a ZIP code it was already given.

Architecture

Browser (WebSocket audio)
        │
        ▼
Deepgram Voice Agent ──── nova-3 STT · aura-2 TTS · function calling
        │
        ├─ check_red_flags ──────── deterministic regex ──┐
        ├─ determine_specialty ──── Moss retrieval ───────┤
        │                           (local fallback)      │
        ├─ check_eligibility ────── Stedi 270/271 ────────┤
        ├─ research_providers ───── CMS NPPES + web ──────┤
        └─ request_appointment ────────────────────────── ▼
                                                     Medplum
                                                    (FHIR R4)
Layer Choice Notes
Voice Deepgram Voice Agent API Single WebSocket for STT, LLM turn-taking, and TTS
Retrieval Moss Indexes the referral corpus; falls back to a local token-scored retriever if unavailable, so triage degrades instead of failing
Eligibility Stedi Synchronous JSON 270/271, with typed error classes per failure mode
Records Medplum FHIR R4 system of record — chosen because it's built for compliant PHI
Directory CMS NPPES Public provider registry; no API key required
App Next.js 16 (App Router), TypeScript, Tailwind Deployed on Vercel

Notable resilience detail: every external dependency has a defined degradation path. Moss down → local retrieval. Stedi unconfigured → typed 503, flow continues. Telephony unconfigured → "booking request sent." Web research times out → registry-only results. Nothing in the demo path is a single point of failure.

Repository map

app/
  call/          Voice call UI — live transcript, evidence panel, provider comparison
  api/           triage · eligibility · providers · appointment-request · confirm-call
lib/
  triage/        Deterministic red flags + grounded specialty selection
  eligibility/   Stedi client, typed errors, 271 → summary mapping
  directory/     NPPES search, distance filtering, background web enrichment
  fhir/          Typed R4 resource builders and transactional writes
  voice/         Deepgram agent config, prompt, and workflow state machine
data/            Synthetic referral-criteria corpus
docs/internal/   Hackathon working notes: research, decisions, run-of-show

Running locally

Requires Node 24 (see .nvmrc).

npm install
cp .env.example .env.local   # fill in credentials
npm run dev                  # http://localhost:3000
npm run check                # typecheck · lint · build

Unit tests are Node's built-in runner, no framework:

node --test lib/**/*.test.mjs

Deepgram and Medplum credentials are required for the full call flow. Without Moss, Stedi, or telephony keys the app still runs on its documented fallback paths.

Known limitations

Stated plainly, because a demo that hides its edges isn't worth showing.

  • Appointment availability is not real. No public API exposes clinic slots; that's a business-development problem, not an algorithmic one.
  • Eligibility runs against a synthetic fixture. Stedi's production endpoint is the same API — the 271 parsing is already real; the test key is what constrains it.
  • No identity or insurance-card verification. Card OCR is commodity tech that wasn't the interesting part.
  • Triage routes; it does not diagnose. Specialty selection comes from retrieved referral criteria. The only severity judgment in the system is the deterministic 911 screen. A real deployment would be a navigation tool under clinical oversight, not a medical device.
  • Single-session state. lib/eligibility/pending.ts holds one demo session in process memory; concurrent callers need session keying.
  • HIPAA posture is incomplete. Medplum is the system of record precisely because it's built for compliant PHI, but BAAs with the voice and retrieval vendors would be required before real patient data touched this.

Credits

Built by Jayden and Tanay at the YC × Medplum Agentic Healthcare Hackathon, August 2026.

Sponsor technologies: Medplum · Deepgram · Stedi · Moss

About

Voice hotline that turns a spoken symptom into a specialist appointment request in one call — FHIR R4, real 270/271 eligibility, live CMS provider data. Built at the YC × Medplum hackathon.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages