The problem, and who it hurts

AI "ambient scribe" tools now sit in exam rooms, listening to the conversation between a doctor and a patient and drafting the clinical note automatically. They are one of the fastest-spreading categories of healthcare AI, and they are spreading fastest exactly where they're needed most — understaffed clinics, community health centers, rural practices, anywhere a doctor is seeing too many patients in too few hours to write everything up by hand.

Those tools hallucinate. Studies on AI-generated clinical documentation have found meaningful rates of fabricated symptoms, altered severity, wrong doses, and flipped negations — "no history of penicillin allergy" becoming "history of penicillin allergy," or the reverse.

A wrong note is not a typo. It is the document the next doctor reads. It follows the patient. And the person most likely to be harmed by one is the person whose doctor had the least time to proofread it.

Every clinical body agrees the fix is human review before signing. The open problem is that reviewing a whole note by eye — reading it through, hoping to notice what's wrong — is slow, and it's exactly the step that gets rushed when a clinic is behind. We built the systematic check that review can actually lean on.

This project does not diagnose patients or recommend treatment. It's a documentation-reliability tool that keeps clinical judgment with a human at every step. All data used anywhere in the project is synthetic — invented transcripts about fictional patients. No real patient data is used or needed anywhere.

What it does

Instead of trusting one model call to draft a note and hoping it's accurate, the pipeline breaks the note into individually-verifiable claims, checks each one against the source transcript independently, and flags anything unsupported — with the exact quote it checked against, so a reviewer can confirm a flag in seconds instead of re-reading the transcript.

It catches errors of commission (something in the note that was never said) and omission (something said that the note left out), across a taxonomy grounded in the ambient-scribe literature: fabrication, distortion, omission, misattribution, numeric/medication errors, and negation errors.

How we built it

Nine nodes, each doing one job, instead of asking a single prompt to do everything at once:

  1. Draft the note from the transcript (simulates the ambient scribe)
  2. Extract atomic claims from the note
  3. Entailment check — verify every claim against the transcript: supported / contradicted / not_mentioned, with a quoted excerpt
  4. Deterministic numeric/medication check — plain regex, no LLM, because exact doses and vitals don't need a model's judgment
  5. Omission check — the mirror of 2+3: pull facts from the transcript and check whether the note covers each one
  6. Classify every flag into the fixed taxonomy
  7. Human review checkpoint — required, not optional

A separate node (3b) handles audio mode by treating the transcript itself as a model output rather than ground truth — downgrading claims that rest on low-confidence transcription to "unverifiable" instead of silently trusting a bad transcript.

Multi-provider failover (Gemini → Groq → Featherless) keeps it running through free-tier rate limits, and the comparison against our single-prompt baseline is fairness-linked to the same model per case, so we're measuring the design, not accidentally measuring two different models.

We also built a 60-case synthetic benchmark, a blinded scorecard workflow for human grading, and an automated scorer for fast iteration.

The interface

The project site isn't a screenshot gallery — the guard actually runs in the browser on all 60 benchmark cases.

  • A working console, on the landing page. Drag it around, or expand it to full screen. Every control is real: the notification bell fires off live pipeline state, the customize panel changes the view, ⌘K focuses search. Nothing is a decorative placeholder.
  • Flags you can act on. Each one shows the claim, the verdict, the category, and the transcript quote it was checked against — the whole point being that a reviewer shouldn't have to take the model's word for it.
  • A nine-node timeline that walks the pipeline node by node, so you can see where a flag came from rather than being handed a verdict from a black box.
  • Built with React, Tailwind, and Framer Motion. Typography and colour are a fixed token system, and body text was checked against WCAG AA contrast (4.5:1) programmatically rather than by eye — a muted grey that looked fine measured 3.2:1 and got darkened.

Design goal: make an ML pipeline inspectable by a non-engineer. A clinician has to be able to see why something got flagged, or they won't trust the flag — and a tool nobody trusts doesn't get used.

Results

Across 56 cases, graded blind by a human — every case shown as "System A" and "System B", randomised per case, key sealed until scoring finished:

Recall Severity-weighted recall False positives (6 controls)
Pipeline 47/50 (94%) 93% 13
Baseline (single prompt) 41/50 (82%) 87% 9

The automated proxy we used during development predicted this almost exactly — 88% vs 76%, the same 12-point gap — which says the cheap scorer was a trustworthy instrument for the comparison it was used for, and harsher than a human on both systems in absolute terms.

The pipeline catches meaningfully more planted errors than a single open-ended prompt — even after we fixed our own baseline prompt to be omission-aware, so the comparison wasn't just measuring an unfair baseline. The trade-off is more false positives on clean notes: breaking a note into many atomic claims creates more independent chances for an over-literal judgment to misfire.

Then we tested our own claim on a stronger model, and it didn't hold

The result above ran on a 7B model. We re-ran everything with a 70B model pinned in its place and scored both with the same matcher, over the 44 cases that completed in both runs:

Core model Pipeline Baseline Gap
Qwen2.5-7B 39/44 (89%) 33/44 (75%) +14 pts
Llama-3.3-70B 39/44 (89%) 40/44 (91%) −2 pts

The pipeline scored identically on both. The entire change is on the baseline's side — it went from 33 to 40 once it had a competent model behind it.

The honest reading: decomposition compensates for a weak reasoner rather than adding capability on top of a strong one. Structure substituted for model quality here; it did not compound with it.

That narrows our claim instead of erasing it. "Worth a lot when your reasoner is weak" is the situation any team on a free tier is in — and, more to the point, the situation a clinic running a small local model for privacy reasons is in too. What it stops being is a general claim that workflow structure beats a good prompt. We'd rather report that than have a judge find it, and it was an open question in our own README before we ran it.

(Auto-scored rather than blind-graded, 44 of 60 cases — ten lost to rate limits, not difficulty — one run per model. Full detail in eval/runs/README.md.)

Challenges we ran into

  • An unfair baseline was inflating our own result. Our baseline prompt only asked about commission errors while being scored on omission cases the pipeline had a dedicated node for. Fixing it shrank our headline number. We kept the fix.
  • A too-small benchmark. At 18 cases, a 2-case swing moved the headline by 8–13 points — not a believable margin. We went to 60 specifically so the measured gap would be trustworthy instead of noise.
  • A provider withdrew two models mid-project. Groq retired both models we depended on, hours after a successful 60-case run. Our failover was built for providers going down, not models being removed — a retired model returns 404, which we'd classified as a caller bug worth surfacing rather than an availability problem worth routing around, so calls died with a working fallback sitting unused in the same chain. Fixed narrowly: 404s that name the model now fail over; a 404 from a bad URL still surfaces immediately. The replacement model's free tier caps at 8,000 tokens/minute against the ~580,000 a 60-case run needs, which makes a complete second evaluation arithmetically impossible on a free tier. We state that ceiling in eval/runs/README.md rather than leave it implied.
  • Free-tier rate limits mid-eval. Gemini's 20-request/day cap means a 60-case run burns the quota almost immediately, so most of a given run's core-reasoning judgments come from the failover model rather than Gemini. We built the router to fail over automatically and to log which provider actually handled each case, rather than silently assuming the intended model ran — which matters, because the alternative is reporting a number for a model that never saw most of the work.
  • Malformed JSON from smaller models mid-batch, and a single failing case discarding an entire eval run — both hit for real, both hardened against directly in run_eval.py.
  • The transcript isn't ground truth in audio mode. A verdict is only as good as the audio it was checked against, so node 3b calibrates a confidence threshold via a dedicated degradation experiment instead of trusting ASR output blindly. It's the only part of either system that questions its own input.

Accomplishments we're proud of

  • Node ablation, showing which nodes actually earn their keep — including one (the deterministic numeric check) that contributed zero additional recall on this benchmark. We reported it instead of hiding it.
  • Blind human grading. The headline number isn't a model scoring itself.
  • Running the experiment against our own claim and publishing the result that made our project look worse.
  • A full cost and latency measurement of pipeline vs. baseline — measured, not estimated. The pipeline costs ~5.5× the tokens and ~1.7× the latency, and we'd rather a judge read that from us than work it out themselves.
  • A reproducibility fix: pinning temperature, after discovering that temperature alone swung a run between very different results. Without it, any number in this write-up could have been the lucky sample.
  • A live site where the pipeline actually runs, rather than a video of it running.

What we learned

Decomposition-then-verify beats a single "find the errors" prompt when the underlying reasoner is weak — and stops beating it when the reasoner is strong. That wasn't the answer we expected, and finding it meant running the experiment against our claim rather than around it.

The other lesson is that fairness in an evaluation — same model per case, same information in both prompts, an honest baseline — matters as much as the system design. Twice, a number we were proud of turned out to be measuring an unfair comparison rather than a real advantage.

What's next

  • Tune node 5's notion of "clinically relevant." It currently weighs a genuinely important omission the same as a borderline one, and it's the main driver of our false-positive rate. In a real clinic, a tool that cries wolf gets ignored — this is the difference between useful and shelved.
  • Finish the stronger-model check properly — blind-grade it over all 60 cases instead of auto-scoring 44.
  • Test it on a local model. The clinics that most need this are the ones that can least afford to send patient audio to a third-party API. Our own robustness finding says decomposition helps most exactly where the model is weakest, which is the local-model case.

Built With

  • faster-whisper
  • featherless-ai
  • framer-motion
  • gemini-api
  • groq
  • javascript
  • llama
  • llm-as-judge
  • prompt-engineering
  • python
  • qwen
  • react
  • regex
  • tailwindcss
  • unittest
  • vite
  • whisper
Share this project:

Updates

Submission history