# Project export: Otomedi

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: We turn clinic voicemails into one-click approvals: AI does the eligibility legwork, a health worker approves, and the request actually gets done.
- Devpost: https://devpost.com/software/otomedi
- GitHub: https://github.com/ricardo-pc/Berkeley-AI-Hackathon
- Demo: https://dashboard-azure-six-79.vercel.app/
- Team: 5 GitHub contributor(s) — Ricardo Pérez Castillo (24 commits), Jay (19 commits), cgk (13 commits), Robert Janekarnkit (12 commits), Claude Opus 4.8 (6 commits)

## Devpost submission (written by the team)

### Inspiration

Walk into any primary care clinic at 8 a.m. and the voicemail light is already blinking. Refill requests, "can I move my Thursday appointment," messages for the doctor — dozens of them, each requiring a staff member to pull a chart, check insurance, decide, act, and call back. The burden is well-documented: physicians spend only 27% of their day on direct patient care versus 49% on EHR and desk work — roughly two hours of administrative work for every hour with patients (Sinsky et al., Annals of Internal Medicine, 2016). Administrative activity accounts for an estimated 25% of U.S. hospital costs (Himmelstein et al., Health Affairs, 2014). Here's the insight that started Otomedi: most of these voicemails are routine and mechanically verifiable. Whether a refill is allowed, whether a slot is open, whether insurance is accepted — these are checks against data, not judgment calls. The genuinely hard cases ("chest pain") are the minority. So why is a human doing all of it by hand? ¹ Sinsky C, et al. "Allocation of Physician Time in Ambulatory Practice: A Time and Motion Study in 4 Specialties." Annals of Internal Medicine, 2016. doi:10.7326/M16-0961. ² Himmelstein DU, et al. "A Comparison of Hospital Administrative Costs in Eight Nations." Health Affairs, 2014. doi:10.1377/hlthaff.2013.1327.

### What it does

Otomedi turns a clinic's voicemails into a one-click approval queue. A patient leaves a voicemail. It's transcribed automatically. An intake agent extracts the structured request — who, what, insurance, preferred times. Eligibility agents verify against the patient's record: insurance acceptance, visit recency, dosage match, drug conflicts, schedule availability. The system produces a concrete proposed action ("Refill Lisinopril 10mg, send to pharmacy") and routes the task: ✅ Clear → ready to approve with one click ⚠️ Iffy (e.g. last visit > 6 months ago) → flagged for judgment, with full context surfaced 🚨 Emergency (e.g. "chest pain") → escalated immediately, bypasses automation entirely ✅ Clear → ready to approve with one click ⚠️ Iffy (e.g. last visit > 6 months ago) → flagged for judgment, with full context surfaced 🚨 Emergency (e.g. "chest pain") → escalated immediately, bypasses automation entirely A certified health worker reviews and approves in one click. Otomedi then executes: writes the prescription, books the appointment, or relays the message to the provider's inbox — and texts the patient a confirmation. Rejected items get an auto-sent "please call us" text. Everything is logged for audit. A human is always the approver — Otomedi does the legwork, not the deciding.

### How we built it

Otomedi is built as a plugin to the EHRs clinics already use, not a replacement. Claude Code was the primary development environment and orchestration engine throughout the build. We used it to scaffold the multi-agent pipeline, iterate on the eligibility logic, and wire together the full stack under time pressure. The agents themselves run on Claude's API: the intake agent parses raw transcripts into structured requests, the eligibility agents reason over patient record fields to produce a proposed action and a triage classification, and the message relay agent composes provider-inbox notes from unstructured voicemail content. Tasks that don't fit a known pattern are flagged rather than guessed — a behavior we prompted and tested explicitly with Claude. Speech & intake — Deepgram for speech-to-text; Claude (intake agent) for structured extraction and emergency classification Orchestrator (Python) — routes each voicemail through type-specific eligibility gates (prescription / scheduling / message relay); Claude agents handle the reasoning at each gate Backend (FastAPI) — single source of truth and the only holder of DB credentials; exposes one decision executor that dispatches approved actions and chains the confirmation SMS CHW dashboard (Next.js / React / Tailwind) — review queue, history log, one-click decisions; talks only to the backend over HTTP with no DB credentials in the frontend berkapp — a mock eClinicalWorks-style EHR reading the same database, so an approved refill or relayed message appears in the chart and provider inbox automatically, closing the loop end-to-end

### Challenges we ran into

Three correctness problems we had to solve — not paper over: Idempotency. An early double-approve inserted two prescriptions. We added a guard so any already-completed task is a no-op. Credential separation. Our first version had the frontend writing directly to the database — a service-role key living in a web app. We re-architected so the backend owns all reads and writes and the dashboard holds no credentials. Schema alignment. Live tables didn't match our mental model (patient names were joined, not stored on tasks; audit columns were missing). We aligned the data model to reality so wiring became a thin mapping layer. Safe human-in-the-loop. It's tempting to fully automate. We deliberately built the opposite: emergencies bypass automation, "iffy" cases demand judgment, and nothing clinical executes without a person's click. Reproducible demo. Testing against a shared database kept dirtying the data. We built a snapshot-based reset so any scenario reruns cleanly.

### Accomplishments we're proud of

A working end-to-end pipeline: voicemail → transcription → eligibility → review → one-click execution → patient confirmation → reflected in the EHR Safety-first triage that correctly separates routine work from emergencies across every demo scenario we tested A clean architecture with one executor and a backend as the single source of truth — no duplicated logic, no credentials in the frontend A polished, accessible dashboard themed to feel like a native part of the EHR

### What we learned

Automation in healthcare is about trust, not just throughput. The product only works if the worker can approve in one glance and believe the escalation logic has their back. The boring infrastructure decisions matter most. Idempotency, a single source of truth, and a clean data contract made the demo solid — more than any single feature. The right unit of automation is the check, not the decision. Agents verify; humans decide. That's what makes this both fast and safe. The payoff At conservative inputs (40 voicemails/day, 70% routine, 4 minutes saved per task), Otomedi recovers roughly 1.9 staff-hours per clinic per day. Over a 250-day working year at a median medical-assistant wage (~$20/hr), that's ~$9,500 recovered per clinic annually — before counting faster turnaround for patients.

### What's next

The three request types in this build — refills, reschedules, message relays — are the beachhead. The real mission: become the automation layer for every routine, verifiable front-desk task, so staff spend their time on people instead of paperwork. Expand the playbook: prior-auth follow-ups, lab-result callbacks, referral coordination, billing questions — anything checkable against the record Configurable eligibility engine: clinic-defined rules (controlled substances, visit intervals, payer policies) without code changes Confidence scoring: transparent explanations for every routing decision, so the CHW always knows why a task was flagged A learning loop: CHW approve/reject decisions tune the eligibility gates to each clinic's real-world patterns Production EHR integrations: Epic, eClinicalWorks, athenahealth via native APIs — no rip-and-replace Multi-request handling: one voicemail asking for a refill and a message → two separately approvable tasks Multi-channel intake: patient portal messages, texts, web forms — one queue for every inbound request

## README (from the GitHub repository)

# Berkeley-AI-Hackathon

Project name: TBD

Cuts down unnecessary time on simple, repetitive tasks at a hospital front desk. Missed voicemails (prescription refills, schedule adjustments, message relays) pile up and are slow to triage by hand. This project turns voicemails into a dashboard where a certified healthcare worker (CHW) can approve repetitive tasks with one click, while edge cases are surfaced for manual review.

## Live Deployments

- `berkapp` EHR demo: https://berkapp-three.vercel.app
- `dashboard` CHW approval dashboard: https://dashboard-azure-six-79.vercel.app
- `demo` Otomedi audio upload demo: https://demoaudio-ecru.vercel.app

## Structure

- `agents/` — pipeline agents, one subfolder each:
  - `intake/` — extracts patient name, reason, preferred times, insurance, urgency from a voicemail
  - `eligibility/` — checks insurance acceptance, flags missing info
  - `prescription/` — checks refill eligibility (visit history, dosage match, conflicting meds)
  - `scheduling/` — finds an appointment slot given provider availability and urgency
  - `confirmation/` — generates outbound SMS/email/call-script confirmations
  - `triage/` — flags calls needing human escalation (may be cut from scope)
  - `summary/` — daily digest for the front desk
- `dashboard/` — CHW-facing UI for approving tasks and reviewing edge cases
- `backend/api/` — service layer tying agents to the dashboard
- `backend/db/` — patient/visit/prescription/scheduling records
- `demo/voicemails/` — uploaded audio files standing in for real inbound calls
- `docs/` — hackathon guide, architecture notes, work division
- `scripts/` — setup/dev/demo utilities
- `tests/` — regression tests (per mentor guidance)

Hackathon guide: https://docs.google.com/document/d/1jDaXilfjTSa9BbqRuAdPdshSbloZhGSzVslIkJORmc4/edit?usp=sharing


## Detected evidence (automated analysis)

Indexed codebase: 206 recognized source files, 684 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 267)

```
.DS_Store
.gitignore
agents/__init__.py
agents/confirmation/README.md
agents/eligibility/README.md
agents/intake/__init__.py
agents/intake/claude_extractor.py
agents/intake/cli.py
agents/intake/errors.py
agents/intake/README.md
agents/intake/schemas.py
agents/intake/service.py
agents/intake/stt.py
agents/prescription/README.md
agents/schedule_eligibility/README.md
agents/scheduling/README.md
agents/summary/README.md
agents/triage/README.md
backend/api/.env.example
backend/api/.python-version
backend/api/confirmation/cli.py
backend/api/confirmation/errors.py
backend/api/confirmation/service.py
backend/api/confirmation/templates.py
backend/api/confirmation/textbelt_client.py
backend/api/confirmation/twilio_client.py
backend/api/contracts/james_mock_transcription.json
backend/api/contracts/transcription_response.example.json
backend/api/intake/__init__.py
backend/api/intake/claude_extractor.py
backend/api/intake/errors.py
backend/api/intake/orchestrator.py
backend/api/intake/schemas.py
backend/api/intake/service.py
backend/api/intake/stt.py
backend/api/main.py
backend/api/prescription_fulfillment/cli.py
backend/api/prescription_fulfillment/errors.py
backend/api/prescription_fulfillment/repo.py
backend/api/prescription_fulfillment/schemas.py
backend/api/prescription_fulfillment/service.py
backend/api/Procfile
backend/api/README.md
backend/api/requirements.txt
backend/api/run_message_relay_examples.py
backend/api/scheduler/__init__.py
backend/api/scheduler/cli.py
backend/api/scheduler/errors.py
backend/api/scheduler/repo.py
backend/api/scheduler/schemas.py
backend/api/scheduler/service.py
backend/api/summary/claude_summary.py
backend/api/summary/cli.py
backend/api/summary/errors.py
backend/api/summary/repo.py
backend/api/summary/service.py
backend/api/tasks/__init__.py
backend/api/tasks/demo_baseline.json
backend/api/tasks/demo.py
backend/api/tasks/repo.py
backend/api/tasks/router.py
backend/api/tasks/service.py
backend/api/telephony/__init__.py
backend/api/telephony/config.py
backend/api/telephony/errors.py
backend/api/telephony/recordings.py
backend/api/telephony/service.py
backend/api/telephony/signature.py
backend/api/telephony/twiml.py
backend/api/transcription/cli.py
backend/api/transcription/deepgram_client.py
backend/api/transcription/errors.py
backend/api/transcription/schemas.py
backend/api/transcription/service.py
backend/db/README.md
backend/orchestrator/__init__.py
backend/orchestrator/.env.example
backend/orchestrator/.python-version
backend/orchestrator/demo_fixtures.py
backend/orchestrator/main_loop.py
backend/orchestrator/main.py
backend/orchestrator/message_relay/__init__.py
backend/orchestrator/message_relay/classifier.py
backend/orchestrator/message_relay/cli.py
backend/orchestrator/message_relay/errors.py
backend/orchestrator/message_relay/repo.py
backend/orchestrator/message_relay/schemas.py
backend/orchestrator/message_relay/service.py
backend/orchestrator/prescription_eligibility/checks.py
backend/orchestrator/prescription_eligibility/cli.py
backend/orchestrator/prescription_eligibility/constants.py
backend/orchestrator/prescription_eligibility/errors.py
backend/orchestrator/prescription_eligibility/repo.py
backend/orchestrator/prescription_eligibility/schemas.py
backend/orchestrator/prescription_eligibility/service.py
backend/orchestrator/Procfile
backend/orchestrator/README.md
backend/orchestrator/requirements.txt
backend/orchestrator/scheduling_eligibility/checks.py
backend/orchestrator/scheduling_eligibility/cli.py
backend/orchestrator/scheduling_eligibility/constants.py
backend/orchestrator/scheduling_eligibility/errors.py
backend/orchestrator/scheduling_eligibility/repo.py
backend/orchestrator/scheduling_eligibility/schemas.py
backend/orchestrator/scheduling_eligibility/service.py
berkapp/.env.example
berkapp/.gitignore
berkapp/AGENTS.md
berkapp/app/_components/AutoRefresh.tsx
berkapp/app/_components/BadgeBar.tsx
berkapp/app/_components/encounters/EncountersScreen.tsx
berkapp/app/_components/FrictionMeter.tsx
berkapp/app/_components/IconRail.tsx
berkapp/app/_components/PatientChartShell.tsx
berkapp/app/_components/PatientContext.tsx
berkapp/app/_components/PatientListTable.tsx
berkapp/app/_components/rx/ManageOrders.tsx
berkapp/app/_components/schedule/ScheduleScreen.tsx
berkapp/app/_components/Shell.tsx
berkapp/app/_components/ui.tsx
[147 more files omitted for size]
```

### Dependencies

- backend/api/requirements.txt: anthropic, fastapi, httpx, pytest, python-dotenv, python-multipart, respx, supabase, twilio, uvicorn[standard]
- backend/orchestrator/requirements.txt: anthropic, fastapi, httpx, python-dotenv, supabase, uvicorn[standard]
- berkapp/package.json: @supabase/supabase-js@^2.108.2, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@16.2.9, next@16.2.9, react@19.2.4, react-dom@19.2.4, tailwindcss@^4, typescript@^5
- dashboard/package.json: @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@16.2.9, lucide-react@^1.21.0, next@16.2.9, react@19.2.4, react-dom@19.2.4, tailwindcss@^4, typescript@^5
- demoaudio/package.json: @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@16.2.9, next@16.2.9, react@19.2.4, react-dom@19.2.4, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- Apply refills in place so the EHR reflects approvals
- Prevent duplicate review tasks
- Persist voicemail transcript and link it to tasks
- Add valid girl demo audio
- community to certified healthcare wrker
- triage --> otomedi
- Persist failed intake tasks for follow-up
- added logs to show orchestrator response to demo page
- ui
- Lazy import local orchestrator fallback
- fixed the birthday for noah foster
- audio file for valid person in db
- Add Otomedi audio demo deployment link to README
- Label the Approve button by the action it actually triggers
- added call file for a valid person in db
- Persist a new task per voicemail after eligibility checks
- Add dashboard action details panel
- more audio files for different scenarios
- Route all dashboard DB access through the backend (Option B)
- Add Otomedi audio demo: upload -> waveform + STT transcript

## Key source files (fetched from GitHub, selected and truncated for size)

### berkapp/CLAUDE.md

```markdown
@AGENTS.md

```

### dashboard/CLAUDE.md

```markdown
@AGENTS.md

```

### demoaudio/package.json

```
{
  "name": "demoaudio",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "next": "16.2.9",
    "react": "19.2.4",
    "react-dom": "19.2.4"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### dashboard/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "lucide-react": "^1.21.0",
    "next": "16.2.9",
    "react": "19.2.4",
    "react-dom": "19.2.4"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### berkapp/package.json

```
{
  "name": "berkapp",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@supabase/supabase-js": "^2.108.2",
    "next": "16.2.9",
    "react": "19.2.4",
    "react-dom": "19.2.4"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### backend/orchestrator/requirements.txt

```
fastapi
uvicorn[standard]
python-dotenv
supabase
anthropic
httpx

```

### backend/api/requirements.txt

```
fastapi
uvicorn[standard]
python-multipart
httpx
python-dotenv
pytest
respx
supabase
anthropic
twilio


```

### berkapp/app/page.tsx

```typescript
import { redirect } from "next/navigation";

// No sign-in step in the demo — land straight on the patient lookup.
export default function Home() {
  redirect("/ehr");
}

```

### dashboard/app/page.tsx

```typescript
import DashboardClient from "@/components/DashboardClient";
import { fetchTasks } from "@/lib/backend";

// Always read live task state (no static caching of the queue).
export const dynamic = "force-dynamic";

export default async function Home() {
  const initialTasks = await fetchTasks();

  return <DashboardClient initialTasks={initialTasks} usingFixtures={false} />;
}

```

### dashboard/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Voicemail Work Queue — CHW Dashboard",
  description:
    "Approval-ready voicemail tasks for healthcare front desks: prescription refills, schedule changes, and doctor message relays in one queue.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
      <body className="min-h-full">{children}</body>
    </html>
  );
}

```

[176 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]