Fast-ER

What inspired us

The theme was Time, so we listed the places where people lose hours for no good reason. A&E came up first. Everyone had a story: four hours with a wrist that turned out to be broken, a parent sent across town because the nearest hospital had nobody who could read an X-ray that night. The data backs it up. Patients are kept in the dark about how long they will wait, and hospitals only see demand once it is already in the waiting room.

What struck us was that both sides are guessing about the same queue with the same missing information. The patient picks the nearest door. The hospital staffs for an average day. Neither knows what the queue will look like in an hour.

So the idea was not an app that tells you the wait. It was: predict the queue at the moment you would actually arrive, send you where you will be seen first, and give the hospital that same prediction as arrivals it can staff for.

What we learned

Queues are simple to write and hard to feed.

$$Q(t) = \max(0,\; Q(t-1) + A(t) - \mu(t))$$

The equation is one line. Everything hard is in $A(t)$ and $\mu(t)$: forecasting arrivals from history, calibrating a service rate from data that never recorded one, and knowing where the patient is.

Throughput is not capacity. The average rate at which doctors clear patients understates what they can do, because at night the queue runs dry. Calibrating on the busiest windows, where staff are fully occupied, is what made the model stable over 48 hours.

Score the forecast at the resolution of the decision. Hourly counts around twelve are dominated by Poisson noise. The rota is planned in four-hour blocks, so block totals are what we evaluate and report, with the hourly figure kept alongside.

Structured LLM output needs a contract. Gemini is only useful here because it returns a fixed JSON schema: category, urgency, emergency flag, follow-up question. We validate every field server-side and the patient confirms the category before anything downstream runs.

Nearest and soonest come apart. In our data the closest hospital is second or third for most of the evening, because its queue is already building when you set off.

The patient must never see the maths. Early screens printed the formula under every hospital. The kept version says “12 minutes by car, then about 33 minutes to be seen” and leaves the model in the hospital view.

How we built it

Intake. Speech, text or a photo goes to Gemini with a schema-constrained prompt. Audio is recorded on the device, converted to 16 kHz WAV and transcribed server-side. An emergency flag replaces the whole flow with a 999 screen.

Travel. openrouteservice for geocoding and road routing, the TfL journey planner for bus and tube with real lines and stops, a map pin for any origin in London. Modes that make no sense for an injury are excluded.

The queue engine. One server-side module used by both screens. Arrivals are forecast from four weeks of hourly history with a bias-corrected seasonal blend and an empirical error band. App users who said “I’m going here” are added at a 0.7 show-up probability in their arrival hour. The queue rolls forward hour by hour under the rota’s doctor counts and the patient’s wait is interpolated at their arrival time.

Ranking. Filter first: general cover, the specialist team the injury needs on shift, a free treatment space when the injury needs one. Then rank by urgency-weighted travel plus wait on arrival, with a weight that falls from 1.0 to 0.5 as urgency rises.

The hospital side. The dashboard reads the same projection: a 48-hour look-ahead, alerts derived from rules on that state, a suggested staffing plan per role per four-hour block, and the inbound list. A doctor-count change in one block re-ranks the phone within seconds through a version-checked scenario store.

Data. A synthetic dataset of five real London hospitals, 42,000 visits and 840 staffing windows, reduced by a deterministic script to the hourly history and baselines the model needs and shipped with the app. Next.js on Vercel, typed contract shared by both frontends, fifty-odd tests on the engine.

Challenges we faced

The data disagreed with itself. Queue snapshots and recorded waits came from different generators, so Little’s law gave one answer and the referral counts gave another. A textbook calibration blew the evening queue up to five-hour waits before we switched to busiest-window capacity.

Free APIs have small quotas. Three routing calls per comparison exhausted a day’s allowance in an afternoon. We cut it to one road call, derive walking and cycling from road distance, and say plainly when the app is on estimates.

Phones are not mock-ups. The first layout assumed a 640-pixel frame with a fake status bar; inside Safari the screens overlapped. We rebuilt around the real viewport and safe-area insets.

Two sides, one truth. The patient app and the dashboard had to agree to the minute. Putting the model behind the API and making both screens read it was the decision that made everything else possible.

Knowing what to leave out. No invented triage priorities, no bed occupancy we do not have, no accuracy claim the data cannot support. Where something is missing, the screen says so.

Built With

Share this project:

Updates

Submission history