Inspiration

Blood availability is unusually time-sensitive, but the information needed to locate it often lives behind phone calls rather than APIs. When a clinic needs a specific blood group and rhesus factor, a laboratory scientist may have to call several blood banks one after another, wait through holds and transfers, repeat the same questions, and manually compare incomplete answers. Stock can change while that calling loop is still in progress.

That made this a compelling phone-agent problem, a narrow workflow where CALL-E can do something a person physically cannot do alone contact several authorized facilities in parallel and turn their answers into one comparable result. The goal was to reduce the phone chase while keeping every clinical and operational decision with qualified humans.

What it does

Blood Bank Dispatch lets an authenticated clinic operator maintain a registry of blood banks and file one request containing the blood group, rhesus factor, units needed, and target facilities. Registry targets and an optional authorized ad hoc number can be included in the same run.

The application creates a visible result card for every target before dialing begins, then fans out CALL-E calls with bounded concurrency. Each voice agent asks the same ordered questions about stock, screening and cross-match status, release policy, cost, transport time, the correct contact person, and possible alternatives. As calls finish, the cards fill with structured results and the application produces a shortlist ranked by units available and then time to bedside.

Uncertain or hedged answers remain unknown instead of being forced into a confident value. The raw CALL-E response and transcript are retained beside the parsed fields for auditability. Blood Bank Dispatch only gathers and reports: it never reserves blood, dispatches units, promises availability, or makes a clinical decision.

How we built it

We built a single Python service with FastAPI, server-rendered Jinja templates, htmx polling, and Postgres. The deliberately small architecture keeps the live workflow easy to run and understand: the browser files a request, one database transaction creates the run and all of its queued target rows, and an asynchronous dispatcher processes those targets behind a configurable semaphore.

For every target, the dispatcher invokes the CALL-E Python SDK at runtime. Because the SDK is synchronous, the application runs its create and wait operations through asyncio.to_thread so the FastAPI event loop remains responsive. Each target receives a durable idempotency key that is stored before the external request; a retry can recover the existing CALL-E task instead of placing a duplicate call.

The CALL-E task prompt contains a fixed question order and explicit end conditions. Its strict JSON schema captures availability, release and screening states, transport time, cost, contact details, alternatives, and callback requests. Postgres stores both the normalized fields and the raw evidence. The UI polls a small HTML fragment every two seconds, so queued cards, active calls, completed results, and the ranked shortlist update without a separate frontend framework.

We also built a no-call mode that simulates varied outcomes through the complete persistence and UI pipeline. Live demonstrations use consenting VAPI mock lines representing stock, alternatives, uncertainty, callbacks, and no-answer behavior; no real blood bank is contacted for the demo.

Challenges we ran into

  • Making parallel calls reliable. External phone calls are slow and failure-prone. We had to combine bounded concurrency, queued-first database writes, early call-ID persistence, idempotency keys, and a recovery path so a network or process failure would remain visible and would not silently double-dial a facility.
  • Turning messy speech into honest data. Real callers hedge, pause, go on hold, offer alternatives, or ask to call back. The extraction schema needed explicit unknown values and a distinct callback state so ambiguity remained information rather than becoming a fabricated answer or generic failure.
  • Keeping the voice agent focused. Early CALL-E experiments showed that a loose task could repeat questions. We added an ordered question set, a one-paraphrase limit, clear completion conditions, and instructions never to reserve or promise anything.
  • Building safely in a medical-adjacent domain. Phone destinations, credentials, and patient context required strong boundaries. The app is password-gated, masks numbers in rendered views, validates E.164 numbers, limits request rates and target counts, uses no patient names, and runs with phone calls disabled by default.
  • Designing an honest demonstration. Calling real clinical services without consent would be unacceptable. We created controlled mock lines with deliberately imperfect answers so the demo can prove the workflow without pretending the data came from real facilities.

Accomplishments that we're proud of

  • CALL-E is not a label on top of the project; it is imported and called at runtime for every live target, and its structured result drives the product experience.
  • Every intended result exists before dialing starts, so the operator never loses sight of a target even when a call or worker fails.
  • The same path supports registry and ad hoc targets, safe dry runs, live mock-line calls, structured extraction, persistent raw evidence, and a useful ranked shortlist.
  • The project treats safety as product behavior: human decision authority, no reservations, masked destinations, explicit authorization boundaries, cost controls, and mock-line disclosure are present in the code, interface, documentation, and demo plan.
  • The runnable app is open source and has a public contribution pull request to CALL-E's awesome-phone-call-agents repository.

What we learned

Phone agents need much tighter completion rules than text assistants. A good prompt must say what to ask, how often to clarify, when to stop, and what the agent is never allowed to promise.

We also learned that uncertainty deserves a first-class representation. An unknown enum value, a callback state, and stored raw evidence produce a more trustworthy system than a schema that forces every conversation into a clean answer.

Finally, the hard part of parallel automation is not starting several tasks—it is preserving visibility and control when one of them fails. Transactions, idempotency, bounded concurrency, and safe defaults were as important to the product as the voice interaction itself.

What's next for bb-dispatch

The next step is a supervised pilot with consenting facilities, using real operational feedback to refine the question order, terminology, and shortlist. We would add automated integration tests, stronger background-job durability, clearer per-call audit views, and monitoring for stuck or unusually long runs.

We also want to support human-confirmed follow-up workflows: a staff member could select an option from the shortlist and initiate the facility's approved reservation or collection process without allowing the agent to make that commitment itself. Callback reconciliation, facility-specific scripts, regional routing, and verified inventory integrations could further reduce manual work while preserving the same human safety boundary.

Built With

Share this project:

Updates

Submission history