PYTHON · NLP · AUTOMATION · Private clinic group, Leinster
TriageLens IllustrativeNLP Triage of Inbound Clinic Enquiries
A private clinic group in Leinster received about 1,900 enquiries a week by email and web form into one shared inbox. Reception read every one to decide where it went; urgent items waited behind appointment-change requests. I built a triage classifier that routes the routine 70% automatically and escalates anything clinical or uncertain to a person straight away — deliberately not the other way round.
This is an anonymised, illustrative scenario — representative of the shape of engagement and the way I work, not a named client or a delivered set of results. The real, named work on this site is labelled as such.
- −74% median first-response time on routine enquiries
- 96.2% routing accuracy on the held-out sample
- 100% of clinical-flag enquiries routed to a human
Value returned
€13k
Annualised reception cost moved to patient-facing work
14 hours a week at a conservative €18/hour loaded cost — redeployed, not removed.
Time returned
14h
Reception hours returned each week
Time previously spent sorting an undifferentiated shared inbox.
Effort invested
11 weeks
Effort to build
Consulting data scientist — labelling design, model and the routing service.
Quality
96.2%
Routing accuracy
On a held-out sample of 600 enquiries. Every clinical flag reached a human on first touch.
01 — Context
The problem.
One shared inbox, nine intents and a strictly first-in-first-out reading order. A prescription query about worsening symptoms sat behind fourteen requests to move an appointment, because nobody could tell them apart without opening each one. Median first response on routine enquiries was 6.2 hours; on the enquiries that mattered most it was no better, which was the real problem.
Any solution had to be conservative by construction. In a clinical setting the cost of misrouting an urgent enquiry is not symmetrical with the cost of a receptionist reading one extra email, and the design had to encode that rather than optimise a single accuracy number.
02 — Method
The approach.
The build, in the order it happened.
-
Defined the intents with reception, not for them
Nine intents drawn from a sample of 3,000 historical enquiries, labelled by two reception staff with a third resolving disagreements. Inter-annotator agreement was measured before any modelling began — if two humans cannot agree on a label, no model will learn it.
-
A clinical-flag layer that runs first
Before classification, a high-recall rule and embedding layer screens for symptom, medication, safeguarding and distress language. Anything it touches goes to a human queue immediately and is never auto-routed, whatever the classifier subsequently says.
-
Classification with an abstain threshold
A linear classifier over sentence embeddings, tuned for precision rather than accuracy. Below the confidence threshold the model abstains and the enquiry goes to the general human queue — abstention is a valid, logged outcome, not a failure.
-
Routing as a reversible service
A FastAPI service applies a queue label via the Graph API and writes an audit row. Every routing decision is visible, reversible by reception in one click, and every reversal is reviewed weekly as training signal.
03 — Outcome
What changed.
- Median first-response time on routine enquiries fell from 6.2 hours to 1.6 hours — a 74% reduction — and urgent items no longer queue behind them.
- Routing accuracy of 96.2% on a held-out sample of 600 enquiries, measured on the 89% the model did not abstain on.
- Every enquiry caught by the clinical-flag layer reached a human on first touch across the twelve-week evaluation, with no auto-routing exceptions.
- Reception time spent sorting the inbox fell by around 14 hours a week and moved to patient-facing work.
04 — Before & after
The same measures, either side of the work.
Each pair is scaled against its own larger value, so the comparison is honest rather than flattering.
Median first-response time on routine enquiries
−74.2%- Before
- 6.2 hours
- After
- 1.6 hours
Reception hours sorting the inbox each week
−77.8%- Before
- 18 hours
- After
- 4 hours
Enquiries triaged without a human first pass
new- Before
- 0 %
- After
- 89 %
05 — The numbers
Median first-response time on routine enquiries.
Tracked as hours, from Wk 1 through Wk 12 — low 1.6, high 6.2.
06 — Screens
What it looks like in use.
Confusion across the nine intents, with abstentions counted separately — a model that declines to answer is behaving correctly here.
The audit log. Every decision is reversible in one click, and every reversal feeds the weekly review.
07 — Stack & role
Built with.
- Python (spaCy, scikit-learn, sentence-transformers)
- FastAPI
- PostgreSQL
- Microsoft Graph API
- Role
- Consulting data scientist — labelling design, model, routing service
- Duration
- 11 weeks
- Client
- Private clinic group, Leinster
- Period
- 2026
08 — Questions
The questions I get asked about this one.
Processing runs inside the clinic’s own tenancy, no enquiry text leaves it, and the training corpus was pseudonymised before labelling. Retention on the audit log matches the clinic’s existing policy rather than adding a new one.
Because a wrong route costs more than an unrouted enquiry. Abstention keeps precision high on the queues that are automated and puts the genuinely ambiguous cases where they belong — in front of a person.
No. The system routes and prioritises; it does not reply. Every patient response is written by a human.