Inspiration

Public health messaging during an outbreak is abstract: "avoid crowds," "improve ventilation." Students don't feel the tradeoff between canceling an event and shortening it, or between moving a class online and leaving it in a poorly ventilated room. We wanted a sandbox grounded in a place we know, the CCNY campus, where you can try an intervention, fork the timeline, and compare outcomes side by side instead of being told what to do.

What it does

CCNY Outbreak is an educational outbreak simulator for the CCNY campus. Synthetic students with persistent identities move through a campus-to-classroom day on real schedules. Transmission is driven by modeled shared-room time, not by decorative map proximity, so what you see spread has a stated mechanism behind it.

You intervene through a command box: create a gathering ("free pizza in NAC"), move an event outside, cancel or shorten it, improve ventilation, move a class online, or keep symptomatic students home. Each command produces a validated preview you have to Apply. Applying forks a branch that preserves history up to that point, so you can replay it and compare two timelines against each other.

We also added an optional natural-language layer: with a Google Gemini key configured, phrasing the deterministic parser doesn't recognize ("free cookies near the NAC!") gets translated into one supported command. Gemini is strictly a parser and router. It never sets infection counts, never claims an intervention worked, and never advances the model. Every result still re-enters the same validation and still requires an explicit Apply click.

How we built it

  • React 19, Vite, TypeScript for the app; Babylon.js for the campus and classroom rendering.
  • A deterministic simulation engine that is the only thing allowed to mutate the model. Rendering and camera state never advance it.
  • Zod contracts for every intervention and operation, so a command is validated before it can preview and again before it can apply.
  • Replay and paired comparison built on checkpoints that capture every state variable affecting future outcomes, plus recorded model, scenario, and RNG versions per run.
  • The optional language features (OpenAI voice, Google Gemini text) run behind a local loopback server so the API key stays server-side and never ships to the browser. The browser only learns a yes/no "configured" flag.
  • A fallback-first pipeline for the assistant: deterministic parse first (free), then a per-browser normalized cache (free), then Gemini only on a genuine miss, so demo phrases never cost a call.

Challenges we ran into

  • Drawing a hard line that no language model advances the world. It was tempting to let the LLM "just apply" a command, but that would let it fabricate outcomes. Keeping it a router that emits a string re-parsed by the existing deterministic path took discipline but preserved determinism and replay.
  • Requests with no honest answer. Users ask to "cure the sick students," but there is no cure or recovery action in the engine. Rather than fake an effect, the assistant returns an explicit "unsupported" with a reason.
  • Model and quota churn on the free Gemini tier. Model ids rotate and daily quotas are small; we hit retired-model 404s and quota 429s during testing. We made the model env-configurable, defaulted to the lite tier for the largest free quota, and made failures surface a clear message instead of a misleading one.
  • Keeping transmission honest. Shared-room time drives infection, so an event moved outdoors genuinely changes exposure, and we had to make sure the UI reflected that rather than implying map distance matters.

Accomplishments that we're proud of

  • A simulator where every number has a stated mechanism and no result is fabricated, even when an LLM is in the loop.
  • Fork-and-compare: you can branch a timeline at an intervention and replay both, which turns "should we cancel or shorten?" into something you watch instead of something you're told.
  • An optional natural-language command box that stays inside the safety boundary and costs almost nothing to run, thanks to the deterministic-first, cache-second design.
  • Secrets never touch the client; the key stays on a local server by construction.

What we learned

  • Where to put an LLM in a system that has to stay trustworthy: upstream of validation as a translator, never as the authority that decides outcomes.
  • Designing for cost from the start. A fallback-first pipeline with a normalized cache kept us inside a tight free-tier quota without changing the user experience.
  • Separating what's true (infection state) from what's observed, and labeling illustrative interiors and single-run examples as exactly that, matters for an educational tool that could otherwise be mistaken for a forecast.

What's next for CCNY Outbreakers

  • A recovery/cure layer with a real engine action, so interventions like treatment or vaccination have a modeled effect instead of being declined.
  • Richer read-only questions (per-building and per-time breakdowns) the assistant can answer from the engine.
  • Speech-to-text input so the command box is voice-driven end to end, feeding the same validated pipeline.
  • Uncertainty bands and multi-run examples so single runs aren't mistaken for predictions.

Built With

Share this project:

Updates

Submission history