Thirty-five mailboxes on one side. A fine-tuned caries model on the other. Every decision routed to a clinician for the final call. Krest One Dental's paperwork and imaging, on one operator, with a human between the model and the money.
Krest One Dental coordinates accounts-receivable across multiple clinics from a single receiver. Before DentalAuto, that meant a human triaging insurance-payment notifications, portal MFA codes, claim-status letters, and patient billing emails out of 35+ office inboxes. And it meant intraoral photos reviewed one screen at a time, no annotation layer beneath them.
Before we wrote a line of code, we sat with the people who would use it. Two roles. Two workflows. The product came out of the conversations, not the other way around.
Watching a morning of inbox flow past the AR coordinator's screen, six categories emerged naturally — insurance payments, portal logins, claim status, patient billing, office admin, and spam. Anything more granular would fragment the routing. Anything less would put money and access in the same bucket.
Sitting behind the operatory chair, watching one occlusal photograph after another get reviewed, the request was consistent. Not an AI that decides. Not a second opinion that overrides. A second reader that annotates — and leaves the verdict in the room.
Auto-forward what the model is sure about. Queue the maybes. Do not hide the low-confidence ones. That was the request, in three sentences. It is why the routing has two numbers instead of one — and why nothing that reads as low-confidence gets silently dropped.
DentalAuto is one AI operator with two halves. The email half sorts. The vision half annotates. A human clicks the button that matters.
The email half runs on Vercel. The vision half runs on Ollama, local or cloud. They share a client, a design, and a promise: nothing moves without human review. They do not share a database.
Every Krest One Dental office mailbox is subscribed to Microsoft Graph's ChangeNotifications. Inbound emails trigger the webhook — the platform never polls. Subscriptions auto-renew before expiry; after any downtime, a delta link fills the gap so no message is lost.
The receiver deduplicates on message ID — webhooks fire twice sometimes; that is fine. Then it inserts into the emails table and hands the message off to the classifier. Latency from mailbox to classified row is typically under two seconds.
Every inbound email lands on a single system prompt with six labels and a bias rule: false negatives on money are worse than false positives. The model responds with category, confidence, should_forward, and reasoning — four hundred tokens, one round-trip, per message.
Two thresholds per forwarding rule. Both editable. The defaults were tuned with Krest One's AR coordinator: 0.70 for auto-forward, 0.50 for the review queue floor. Anything below 0.50 is held, flagged, and not silently dropped.
The dashboard subscribes to two Postgres publications via Supabase Realtime — emails and activity_log. New rows appear without refresh. Row-level security enforces read-only for authenticated staff; writes come through service-role API routes only. The receiver never bypasses the audit log.
The vision half uses two models running in tandem. YOLO11m does the detection — a small, fast object detector, trained on 1,846 patient-safe occlusal-view intraoral photographs to localize dental caries with bounding boxes.
MedGemma 1.5 4B-IT is Google Health AI's medical-image language model. We fine-tuned it in-house with LoRA — rank 16, α 32, dropout 0.05, three epochs on Colab A100. Adapter is 131 MB. It runs alongside YOLO, not on top of it.
The result is a two-stage pipeline: YOLO decides where to look, MedGemma writes what it sees. Both outputs land on the clinician's screen. Neither has the final say.
On a single intraoral photograph, YOLO11m outputs a list of bounding boxes, each with a confidence score. Every box is cropped to a small region and fed through MedGemma, which returns a verdict — confirm, uncertain, or reject — and a short rationale, usually one sentence describing the lesion in clinical terms.
Every number below was measured on a patient-safe held-out test set — 332 intraoral photographs, 850 ground-truth caries, at IoU ≥ 0.5. The training pipeline never saw these mouths.
| Pipeline | TP | FP | FN | Precision | Recall | F1 |
|---|---|---|---|---|---|---|
| YOLO alone | 763 | 151 | 87 | 0.835 | 0.898 | 0.865 |
| YOLO + base MedGemma veto | 568 | 79 | 282 | 0.878 | 0.668 | 0.759 |
| YOLO + fine-tuned MedGemma veto | 599 | 108 | 251 | 0.847 | 0.705 | 0.769 |
The Zenodo dental caries corpus contains multiple photographs per patient — different angles, different visits, sometimes the same lesion twice. Splitting at the image level would leak the same mouth into both training and test. We split at the patient level, then dropped everything that wasn't a mandibular or maxillary occlusal view. What remained: 1,846 train, 327 val, 332 test — a clean partition, publishable metrics, no wishful thinking.
MedGemma runs on every YOLO detection. It receives the cropped region, and it returns a short clinical rationale — usually a single sentence describing the lesion in the language a dentist would use. Position on the arch. Character of the lesion. A note about confidence.
The rationale sits on the clinician's screen next to the YOLO box. The verdict stays with the clinician. The dentist reads the boxes. The dentist reads the notes. The dentist decides.
That is the shipping configuration: YOLO detects, MedGemma annotates, a human confirms. No autonomous rejection. No black-box gating. Every finding surfaces to the room where the decision is made.
Fine-tuned MedGemma is exported from Hugging Face safetensors, converted to GGUF via llama.cpp's convert_hf_to_gguf.py, then quantized to Q4_K_M — four-bit block quantization that trades a fraction of a percentage point for a 4× reduction in memory. The final artifact is about 1.5 GB at runtime and imports into Ollama with a single command from a Modelfile.
Tested on Apple Silicon M4 (MPS device), Colab A100-40GB for training, local CUDA Linux for cloud deploy. A Krest One clinic can, in principle, run the whole detector on the same Mac mini that runs their reception software.
DentalAuto is bundled because the practice runs bundled. The AR coordinator and the dentist are, on most days, seven meters apart. The two halves of the platform serve the two halves of the practice — the front office and the operatory — and both were built with the same discipline: nothing moves without a human click.
DentalAuto is not marketed as HIPAA-certified. It is marketed as a platform whose architecture matches the constraints of a practice that handles patient data. The distinction matters — one is a checkbox; the other is code.
No email body stored beyond about 200 characters. Full body fetched from Graph on demand only, and never persisted beyond the classification round-trip.
Authenticated staff can only see their own tenant's rows. Writes never come from the browser — they route through service-role API handlers so the audit story is complete.
Every event — received, classified, forwarded, reviewed, errored — has a row. Nothing is ever deleted. The log is the single source of truth for what the operator did.
The vision model was trained with patient-level isolation. Ground truth for evaluation cannot contaminate the training set — a rare thing to enforce, and the reason the eval numbers stand up.
Each half runs on the toolchain it was built for. The email half is web-native, deployed to Vercel. The vision half is Python-native, deployed via Ollama on M4 or CUDA Linux. They share only what they need to share.
Every HITL product lives or dies by whether the operator trusts the threshold. Have the conversation before you have the classifier. Ship a slider you can move, not a number you have to explain.
In dental imaging — and probably most medical imaging — the same mouth in train and test will inflate your F1 by ten points and lie to your face. Split at the patient. Publish only what survives.
Configuring MedGemma as an annotator instead of a veto was the honest choice. Saying so in the case study made the pipeline more trustworthy, not less. Restraint is a marketing asset.
We build AI operators that route decisions to humans, ship the models we tuned ourselves, and publish the metrics we measured on data we never trained on.
Two-week discovery · no prepayment.