Architecture
Grades new patient calls with Gemini.Every five minutes, 6 AM to 6 PM.
Every five minutes from 6 AM to 6 PM, a Cloud Run Job pulls new recordings from the phone system's SFTP drop and keeps only real new patient calls on an NPC line. Gemini transcribes and judges each one with a quote per criterion, plain Python adds up the score, and the report lands in the NPC's inbox and on a dashboard.
14 min
median from hang up to a graded call, in October
1 poll
at a time, held by a Firestore lease since reports went out twice
690
calls graded out of 28,745 recordings read since June 17
16 days
from the first commit on Jun 2 to grading live calls on Jun 18
New York --:-- simulated
Hover a part to trace what it touches. Click it for its notes.
- Built by me
- Managed by Google
- Outside system
- Main data path
- On demand
- Credentials cross
When it breaks
What each failure does, and what it does not do.
-
Two runs overlap
A run that outlasts five minutes holds a Firestore lease, so the next one exits at once. A stale lease expires after 70 minutes and the next run takes it over.
src/processor/poll_lock.py:1
-
Gemini hits a rate limit
429 and 503 errors retry up to five times with backoff inside the adapter. A call that still fails is logged and picked up by the next run.
src/common/transient_retry.py:1
-
The model breaks the schema
The validation error goes back to the model for a repair try. If that fails too, the call is saved as not gradeable with the field that broke.
src/score/scorer.py:122
What I tuned
Small choices that keep it fast and cheap.
-
Names before network calls
A file name already in the table is skipped before any SFTP stat. Before this, a run made about 7,700 stat calls and could outlast the five minute schedule.
src/processor/poller.py:101
-
Filter before transcribing
Lines off the allow list, call park files and calls under 60 seconds never reach a model. Of 28,745 recordings, 3,334 were ever transcribed.
src/processor/poller.py:127
-
Math outside the model
Gemini returns six integers and three gate answers. A Python function does the 40/40/20 sum, so the same verdict always gives the same score.
src/score/formula.py:1
Guards
Rules the code enforces, not rules people remember.
-
NPC lines only
An allow list of NPC extensions decides what gets graded. Every other line is tagged and left alone, never transcribed.
src/processor/poller.py:93
-
Pinned SFTP host key
The poller checks the server's key fingerprint before it logs in and refuses a mismatch.
src/ingest_sftp/sftp_client.py:24
-
Signed remove links
The remove button in each email is a signed link for that one call. Opening it changes nothing; only the page's own POST removes the call, so mail scanners are harmless.
src/delivery/remove_link.py:1
