# Always-On Front Desk

An AI receptionist for dental practices. Answers calls and texts 24/7, books appointments into the
practice schedule, answers insurance questions, and pages the on-call doctor when triage says the
call is an emergency rather than a booking.

Built by Mike Rodgers. Sold at $2,500 setup + $890/month against a ~$4,700/month front desk seat.

```
front-desk-ai/
├── app/                 working demo console (open this first)
│   ├── index.html
│   ├── styles.css
│   ├── engine.js        the real logic: scheduler, triage, NLU, insurance, dialog manager
│   ├── app.js           UI controller + optional fleet LLM call
│   ├── test-engine.js   58 assertions against the engine, runs in node
│   └── serve.sh
├── landing.html         sales page with a working ROI calculator
├── icon.svg             hand-authored 512x512
├── icon-256.png / icon-512.png
├── GTM.md               ICP, trigger events, 5-email sequence, 3 LinkedIn posts, objections, prospect sourcing
├── PRICING.md           price, what it replaces, COGS, margin at scale
└── README.md
```

---

## Run it

```bash
cd app
./serve.sh            # http://localhost:8080
```

Then open `http://localhost:8080` for the demo console, or
`http://localhost:8080/../landing.html` if you serve from the project root instead:

```bash
cd front-desk-ai
python3 -m http.server 8080
# http://localhost:8080/landing.html
# http://localhost:8080/app/index.html
```

**Serve it over http, do not open the app with `file://`.** The Ollama fleet nodes reject
`Origin: null`, so the optional LLM toggle will fail from a file URL. Everything else works either
way.

Run the engine test suite:

```bash
cd app && node test-engine.js     # 58 passed, 0 failed
```

---

## What the demo actually does

Click a scenario in the left column, or type in the conversation. Watch the right panel.

- **New patient, 9:40pm Saturday** — insurance question answered from the plan table, then a real
  booking with intake forms.
- **Knocked-out tooth, 11pm** — triage overrides booking, pages the on-call doctor, gives the
  avulsion protocol.
- **Facial swelling with fever** — escalates instead of scheduling.
- **Insurance we do not take** — refuses honestly and quotes the self-pay fee.
- **Text message reschedule** — finds the existing appointment by phone, releases the slot, rebooks.

The **Schedule** tab is the same data the conversation is booking into. Appointments the AI books
appear in teal. Switch days to see the whole two-week book.

---

## What is real

Everything in this list runs. No mocks, no `setTimeout` pretending to be work.

**Scheduling engine (`engine.js`)** — Real. 5 operatories, per-weekday hours including a Thursday
7-3 and a Friday half day, lunch blocks, 15-minute grid, per-appointment-type durations, and
provider-to-operatory constraints (hygienists only in OP4/OP5, doctors in OP1-3). Conflict
detection is interval-overlap on (day, operatory), so double-booking is structurally impossible
rather than checked afterward. Booking, cancellation, release-and-rebook, lookup by phone, and
per-day utilization all work. State persists to `localStorage` and re-seeds daily.

**Emergency triage** — Real, and it runs *before* scheduling. Encodes actual dental urgency rules:
avulsed permanent tooth (with the ~60-minute reimplantation window and the milk/socket instruction),
facial swelling with systemic signs, uncontrolled post-extraction bleeding, facial trauma → red,
page the on-call doctor. Severe pain / abscess → orange. Broken or chipped tooth, lost crown →
yellow. Red escalation cannot be overridden by a later booking request in the same call.

**Insurance knowledge base** — Real. 12 carrier/plan rows with in-network status, preventive/basic/
major percentages, annual maximum, deductible and waiting periods. Carrier alias matching handles
"met life", "uhc", "delta". Correctly refuses plans the practice does not accept and offers the
self-pay fee instead of booking someone into a surprise bill.

**Intent classification and entity extraction** — Real, deterministic, offline. Scored multi-pattern
classifier across 12 intents. Extracts name, phone, date of birth, carrier, relative day
("tomorrow", "Friday"), time of day, explicit clock times ("at 3" resolves to 3pm, not 3am),
ordinal slot choice ("the second one"), and new-patient signals. Stopword filtering stops "I'm
looking for a new dentist" from being parsed as the name "Looking For".

**Dialog manager** — Real slot-filling state machine with stages for offering, name capture, phone
capture and account lookup. Handles side questions mid-booking without losing the held slots.

**Fleet LLM** — Real network call. The "Local engine / Fleet LLM" toggle calls
`http://100.67.126.117:8000/v1/chat/completions` (Blackwell, 3x RTX PRO 6000, vLLM serving
Qwen3-Coder-30B-A3B). Verified working in-browser at **~550ms per turn**. Blackwell sends
`access-control-allow-origin: *` so the browser can reach it directly.

The important design constraint: **the model is never allowed to invent a booking.** The
deterministic engine decides everything — intent, triage, which slots exist, what gets written.
The LLM only rewrites the approved reply into warmer phrasing, and a validation pass rejects the
rewrite and falls back to the deterministic text if any time was dropped or altered. The LLM
handles language, the scheduler handles truth.

**ROI calculator on the landing page** — Real. Live arithmetic on 4 inputs, and it tells you
honestly when the volume is too low for the revenue case to work rather than always printing a big
green number.

**Landing page and icon** — Real, hand-authored. Zero console errors, verified rendering at 1440px
and 414px.

---

## What is stubbed

Stated plainly, because the demo is convincing enough to be misleading if I do not.

| Component | Status | What real deployment requires |
|---|---|---|
| **Telephony** | Not built. There is no phone number and no call. The demo is a text console. | Twilio or Telnyx SIP trunk, call forwarding on no-answer from the practice's carrier |
| **Speech to text** | Not built. You type instead of speak. | Streaming ASR (Deepgram or Whisper on the fleet) |
| **Text to speech** | Not built. Replies are text on screen. | ElevenLabs or a local TTS, with barge-in support |
| **PMS write-back** | **Simulated.** Bookings are real inside the demo's own scheduler, but nothing is written to Open Dental / Dentrix / Eaglesoft. | Open Dental API is the realistic first integration; Dentrix and Eaglesoft need a middleware agent on the practice's server |
| **SMS sending** | Simulated. The confirmation text is composed with the real content and shown in the action panel, but never sent. | Twilio Messaging, A2P 10DLC registration |
| **Insurance eligibility verification** | Simulated. The plan *table* is real; the live eligibility check is queued as an action, not performed. | An eligibility clearinghouse (Availity, Change Healthcare) |
| **Intake forms** | Simulated. The action fires, no link is sent. | Form provider + HIPAA-compliant storage |
| **Practice data** | Synthetic. "Cedar Ridge Family Dental" is invented. The schedule is randomly seeded each day. | Real practice config loaded during the 2-hour setup session |
| **Multi-tenancy, auth, audit log** | Not built. Single hardcoded practice, no login. | Required before any real patient data touches this |
| **HIPAA posture** | **Not addressed.** No BAA, no encryption at rest, no access controls, no audit trail. | Mandatory before a single real call. This is the largest gap between demo and product. |

**Customer proof:** there is none. No practice is using this. Nothing in `GTM.md` or `landing.html`
claims a customer, a case study, or a satisfaction statistic, because none exist. The landing page
sells the mechanism and a 30-day pilot, not results I cannot show.

---

## Honest read on effort to production

The hard and differentiated part — triage safety, the scheduling engine, the constraint that the
LLM cannot invent a booking — is built and tested. That is the part that makes this defensible and
it is roughly 41KB of tested logic.

The remaining work is integration, not invention: telephony, ASR/TTS, one PMS connector, SMS, and
the compliance layer. Realistically **4 to 6 weeks** to a first live practice, with HIPAA posture
and the Open Dental connector on the critical path. Nothing in the remaining list is a research
problem.

---

## Verification performed on this build

- `node test-engine.js` → **58 passed, 0 failed**, covering scheduling, conflict detection,
  insurance lookup, triage levels, intent classification, entity extraction, 8 full dialog flows,
  and post-run integrity checks (no double-booking, nothing booked outside business hours, nothing
  booked over lunch).
- Chromium at 1680x1000 and 430x940: app renders, all 8 scenarios execute, booking appears in the
  schedule grid, escalation fires without booking, **0 console errors**.
- Live call to the Blackwell fleet endpoint from the browser, response rendered, **539ms**.
- Chromium at 1440x1000 and 414x900: landing page renders, ROI calculator recomputes on input,
  **0 console errors**.
- Responsive layout checked at 430x932, 768x1024 and 1680x1000 with programmatic
  bounding-box overlap assertions, not eyeballing. All three pass.

**Eight bugs were found by these gates and fixed.** Listing them because "it rendered" is not
"it worked":

1. `/swell/i` never matched the word "swollen", so the red triage rule for facial swelling with
   fever was dead code that silently never fired.
2. "am I talking to a robot" did not match the human-handoff intent.
3. The name regex was case-sensitive on its prefix, so "My name is..." never matched. Adding a
   case-insensitive flag then required stopword filtering so "I'm looking for a dentist" was not
   parsed as the name "Looking For".
4. `reschedule` and `cancel` asked for a phone number but had no conversation stage to catch the
   answer, so the caller's reply fell through to a generic fallback and the flow died.
5. Turns after an emergency escalation dropped back to "are you trying to book an appointment?"
   instead of staying in the emergency.
6. A chipped tooth (yellow triage) did not route to booking because "chipped" was in the triage
   table but not the intent classifier.
7. A new patient asking for a cleaning was booked as plain hygiene ($210) instead of a new patient
   exam with x-rays ($950), which is both clinically wrong and the wrong ROI number.
8. On mobile, `.grid` had a definite flex height so its three implicit rows stretched to equal
   thirds while the panels were 3-5x taller, causing all three panels to overlap each other. The
   desktop fix for that then exposed a 27px footer overlap from a `calc(100vh - 130px)` magic
   number that ignored the footer. Both replaced with a self-correcting stretch layout.

Screenshots from these runs are in `.verify/`.
