Voice agent
Simulation

How it works

From a spreading fire to a van at the right door.

Five parts, each simple on its own. This page describes what the code actually does, including what it leaves out. Everything runs on synthetic people.

Pipeline from fire model to risk clock to dispatch, with voice triage feeding dispatch and Monte Carlo re-running the loop01Fire modelarrival time per 50 m cell02Risk clocktime to impact, every home03Dispatchmatch + route, re-plan04Voice triageGemini call to needs05Monte Carlopaired seeds, 95% CI
Click a stage to jump to its explanation.
  1. 01

    Fire model

    We need to know when the fire will reach each place, not just where it is now. The engine lays a 50 m grid over the town and works out the fire's arrival time for every cell: faster downwind, slower backing into the wind, with patchy fuel.

    Under the hood

    • Arrival times come from a minimum-time (Dijkstra) spread over the 8-neighbour grid. Same burn order as stepping a cellular model, but any point can be looked up instantly.
    • No-wind spread 0.25 m/s on average fuel; each mph of wind speeds the downwind front by 18%. Fuel varies smoothly from 0.7x to 1.3x (seeded noise).
    • Simplified on purpose: no terrain, spotting or firebreaks. It is not an operational fire forecast.

    engine/src/sim/fire.ts

  2. 02

    Risk clock

    Every household gets a countdown: the seconds until the fire reaches its door. That clock, not distance, decides who is most urgent, and it updates as the fire grows.

    Under the hood

    • 600 synthetic households are placed along real Amherst streets (OpenStreetMap, baked offline). Needs such as no vehicle, wheelchair, oxygen or limited English are drawn at random from stated rates.
    • An official alert goes out shortly after ignition. Each home reacts after a random delay, longer for landline-only, limited-English or no-signal homes.
    • Homes with a car leave on their own after packing. Homes without one wait for a relative, if they have one, or for help. Only homes the fire reaches within 2 hours count as at risk.

    engine/src/world/population.ts · engine/src/policy/alertsOnly.ts · engine/src/sim/engine.ts

  3. 03

    Dispatch

    When a household asks for help, the dispatcher finds a helper who can actually carry them and get there in time: a wheelchair user only goes in a wheelchair-capable van or bus. Routes avoid roads the fire is about to cut, and the whole plan is redone as the fire moves. Every assignment comes with a plain-English reason.

    Under the hood

    • Routing: Dijkstra on the road graph. A road closes 60 s before the fire reaches it; roads near the front are slowed by smoke (75% speed within 15 min, 50% within 5 min).
    • Deadline for each home = its time to impact minus a 120 s safety margin. Boarding takes 90 s, 240 s for a wheelchair.
    • Plan: greedy insertion in deadline order, then local search that moves pickups between helpers, then every trip is re-checked with exact route times. Hard rules: capacity, wheelchair capability, every pickup before its deadline.
    • Homes no trip can reach in time are flagged, never silently dropped. The engine re-plans after changes (at most every few simulated seconds).

    engine/src/dispatch/index.ts · engine/src/routing/index.ts · engine/src/policy/lastMile.ts

  4. 04

    Voice triage

    Many of the people who most need help never see an app alert. Last Mile calls them. The voice agent asks how many people are home, whether they have a ride, and about mobility or medical needs, in their own language, and turns the answers into structured needs on the dispatch map. It never promises rescue and always says: if you are in immediate danger, call 911.

    Under the hood

    • Gemini returns JSON that must match the shared schema (needs from a fixed list, ride, can walk to the road, residents, urgency 1 to 5).
    • With no API key, or if Gemini fails, a deterministic keyword parser answers instead, so the demo never breaks.
    • When the call is done, the result is posted to the live scenario: no ride, cannot walk to the road, or urgency 4 or more flips that home to "needs help" and the dispatcher picks it up.

    packages/ai/src/triage.ts · web/app/api/triage/route.ts · web/app/call/page.tsx

  5. 05

    Monte Carlo

    One good-looking run proves nothing. For each random seed we build one town, one fire and one set of people, then run it twice: alerts only, and Last Mile. Repeating that over many seeds gives an average difference with a 95% confidence interval. Whatever it says, we show.

    Under the hood

    • Paired design: the population and fire depend only on the seed, never on the policy, so both runs face identical conditions.
    • The baseline is not a strawman: Last Mile keeps every alerts-only behaviour (self-evacuation, family pickups) and only adds dispatched help on top.
    • Default 200 seeds, run headless across worker threads. Intervals are percentile bootstrap intervals (2,000 resamples). A zero-helper sanity check gives zero lift.

    engine/src/montecarlo/index.ts · engine/src/montecarlo/stats.ts · engine/src/sim/headless.ts

Simulation

What this does not prove

The results depend on stated assumptions: how fast help requests arrive, where helpers start, how quickly people react. Change them and the numbers change. A real deployment would need real registries of people who need help, vetted helpers, and coordination with emergency services. The full assumptions list sits beside every result.