Inspiration
Every big storm in Brooklyn ends the same way: water in the street, water on the subway stairs, and a clogged catch basin somewhere that nobody got to in time. The city already has the data to prevent a lot of it. DEP knows where all 2,092 drains in our district are and which ones it found blocked. 311 has 7,472 complaints. FloodNet sensors measured 83 real floods. DEP even published a flood map. It's just spread across five agencies, and a complaint sitting in 311 looks the same as a confirmed blockage.
We wanted an agent that joins that data, watches the rain, and decides when to act, while being honest about what it actually knows.
What it does
Swale is a flood-readiness agent for Brooklyn's catch basins and subway entrances.
- It watches the forecast. Every 10 minutes it reads the National Weather Service. When rain reaches watch or warning level, it decides on its own to run a storm simulation, re-rank the drains, write a crew briefing, and read it aloud in English, Spanish and Chinese.
- It ranks which drains to clear first, with every score showing its factors and evidence. Tested against last year: 80% of our top 25 drains had a real problem in the following twelve months, against 15% of drains overall.
- It answers what-ifs. Clearing the drains DEP found clogged, or adding rain gardens to 5% of each block (265 deep-flooding drains down to 74), each run in its own Daytona sandbox.
- It listens. A crew member can say "debris found, leaves on the grate" into their phone. ElevenLabs transcribes it, the inspection is saved, and every open screen updates within a second.
- It watches the subway. Live trains and delays, flood exposure scored at the 73 station entrances where water gets in, and elevator outages, so riders who need step-free access aren't left behind.
- It labels everything as observed, reported, predicted or inferred. A 311 complaint is never shown as proof.
How we built it
- SurrealDB (embedded) is the only database: geometry for the map, graph links from each complaint to its nearest drain, simulation results, briefings, inspections, and change feeds that push live updates to browsers. Bounding-box queries run in 22.8 ms at p95.
- EPA SWMM 5.2 runs the storm simulations. Each drain gets its own catchment; drains DEP found blocked get a narrowed inlet.
- Daytona sandboxes run the simulations. A sweep of nine storms and blockage levels finishes in about 20 seconds, six at a time, 4.3x faster than running them one after another.
- Claude Opus 5.5 on Amazon Bedrock writes the crew briefings from an evidence pack, translates them, and acts as a judge that scores each briefing's faithfulness.
- ElevenLabs reads briefings aloud in five languages and transcribes voice inspections with Scribe.
- Arize traces every briefing and holds the evaluations per prompt version.
- FastAPI backend, React and deck.gl 3D frontend, deployed on AWS (EC2 behind CloudFront).
Data: NYC DEP catch basins and Stormwater Flood Maps, NYC 311, FloodNet, NWS forecasts, NOAA MRMS radar, MTA GTFS-realtime, schedules, and elevator feeds.
Challenges we ran into
- Making the model honest. The simulation has no sewer pipe network (NYC doesn't publish it) and no terrain, so it over-predicts flooding. We compared it against DEP's own flood map (55% agreement), lowered its weight in the ranking, and label it an uncalibrated demonstrator everywhere.
- Keeping the LLM truthful. Claude is good at writing briefings and occasionally wrong about which evidence supports them. We made code the gatekeeper: every basin and evidence ID it cites is checked against the pack, forbidden phrases like "is flooding" are blocked, and a failed check falls back to a deterministic template. Our judge then caught a stricter prompt scoring 75% faithful against 100% for the original, so the original stayed.
- NOAA's radar service says inches but returns millimetres. We only found out by checking it against three rain gauges.
- Delays from MTA. Realtime trip IDs are the tail of static schedule IDs with a different format, and the SurrealDB SDK can't deliver live queries in embedded mode, so we built on change feeds instead.
- A deadlock from holding a database lock across a FastAPI dependency's yield, which runs on a different thread on teardown.
Accomplishments that we're proud of
- An agent that genuinely decides for itself, with a full audit trail of why, and never pretends it's raining (a dry-day run is labelled a drill).
- The ranking holds up against real outcomes, not just a demo.
- Voice in, voice out, in the languages the neighborhood speaks, with translations that can't change the facts (IDs are copied from the checked English).
- 286 backend tests, 304 frontend tests, and a smoke check that passes 17 of 17 against the live site.
What we learned
The hard part of an AI agent isn't getting the model to produce an answer. It's building the checks around it so you can trust the answer, measuring quality so you can change the prompt safely, and labelling what's measured versus what's guessed. A model's draft that fails a check is worth less than a template that passes one.
What's next for Swale
- Calibrate the simulation against FloodNet's measured floods and add terrain.
- Expand from one community district to all of Brooklyn, then the city.
- Connect to DEP's actual work-order system so a ranking becomes a dispatched crew.
- Push alerts to riders: which stations to avoid, and which step-free routes still work.
Built With
- amazon-cloudfront
- amazon-ec2
- arize
- aws-bedrock
- claude
- daytona
- deck.gl
- elevenlabs
- epa-swmm
- fastapi
- floodnet
- maplibre
- mta-gtfs-realtime
- noaa-mrms
- nws-api
- nyc-open-data
- python
- react
- remotion
- surrealdb
- typescript
- vite
Log in or sign up for Devpost to join the conversation.