All projects

ShelterPulse: Simulating Kitten Season for Shelter Budget Allocation

mlbackend-apidashboard
ShelterPulse: Simulating Kitten Season for Shelter Budget Allocation hero

A simulation + optimization lab for shelter budget allocation under uncertainty.

ShelterPulse plays out a shelter's kitten season on a computer and tries different ways of splitting one fixed budget across foster support, clinic hours, isolation space, and adoption events, to see which split leaves the fewest cats with nowhere to go. Every plan runs through the same simulated conditions so the comparison is fair, gets checked against simple rule-of-thumb plans, and is ranked — a way to test a budget before spending real money on it. The numbers come from a made-up shelter, so they show the method rather than a real-world result.

Situation

Every spring, kitten season floods cat shelters: intake surges two to three times, isolation queues fill, and housing overflows. Managers face a fixed budget split across four interventions (foster support, extra clinic hours, temporary isolation capacity, adoption events) with no way to model outcomes before committing real staff and dollars, so allocation stays gut-feel and routinely leaves overflow on the table.

Task

Build a fast, honest, reproducible lab that answers one question: given a shelter's constraints and budget, what allocation minimizes overflow? The requirements were concrete: a full experiment in under five minutes, comparison against honest baselines, quantified uncertainty, and open source.

Action

  • Modeled the full cat lifecycle (intake, isolation, medical clearance, housing, foster, adoption) as a SimPy discrete-event simulation, with intake as a non-homogeneous Poisson process that spikes for kitten season. Every run is fully seeded and reproducible.
  • Paired every allocation against the same seed set and pre-generated intake schedule (common random numbers) so comparisons see identical exogenous variation, then evaluated five named baselines (equal split, all-in foster, all-in events, a domain heuristic, and zero) alongside the search so a baseline can honestly win.
  • Searched the budget-share simplex with Gaussian-process + Expected-Improvement Bayesian optimization when JAX/jaxbo is available, falling back to a seeded Dirichlet random search otherwise.
  • Shipped it behind one HTTPS URL: nginx + uvicorn in a single ECS Fargate task serving a Next.js UI (tornado sensitivity chart, day-by-day housing timeline, ranked results) over a FastAPI backend, with BO sweeps dispatched to background workers through a queue abstraction (RabbitMQ locally, SQS + Lambda in production). Pushing to main auto-releases and deploys.

How it works

nginx + uvicorn in one ECS Fargate task behind a single HTTPS URL. The sync request path and the async BO-sweep lane (queue → worker) reuse the same pure simulation core, which imports nothing from the API or CLI — enforced by a CI test.

Result

Live at shelter-pulse.com. A recorded sweep evaluated 5 baselines plus 20 BO candidates at 32 replications each in 243.2 s. On the Whisker Haven scenario the all-events baseline reached 50.2 mean overflow cat-days, the best BO candidate 82.1, and an equal split 874.4. Backed by 133 unit tests and 73% coverage on the core package, with a CI test that fails the build if the simulation core ever imports the API or CLI layers.

Horizontal bar chart of mean overflow cat-days: all-in adoption events 50.2, best Bayesian-optimization candidate 82.1, equal split 874.4. Lower is better.
Only the budget split changes across runs (same seeds). The all-in-adoption baseline (50.2) beats the optimizer's best (82.1) and an equal split (874.4) by ~17× — allocation, not model sophistication, is what moves overflow.
Cat lifecycle from intake to adoption, with housing marked as the full-queue bottleneck and adoption as the only exit; three upstream levers add capacity while only adoption events raise the exit rate.
Why the baseline wins: of the four levers, only adoption events change the rate cats leave. Relieving an upstream resource just feeds a housing queue that is already full — bottleneck displacement, confirmed (not caused) by the optimizer.

Learning

The most valuable result was a negative one worth keeping: of the four levers, only adoption events change the rate at which cats leave the shelter; the other three only add capacity upstream of that exit. So concentrating budget on adoption events consistently beat everything the project tested, including what Bayesian optimization found. That is a structural property of the levers, not a search failure, and the honest move was to label the winning baseline as a baseline instead of dressing the optimizer up as the hero.

Apache-2.0, built for #hackthekitty 2026.

Tech Stack

SimPyJAX / jaxboFastAPINext.jsTypeScriptDockerAWS ECS FargateRabbitMQ / SQS

Services

Discrete-Event SimulationBayesian OptimizationBackend APICloud DeploymentDashboard & Reporting

Status

Live