← Index fig. 6

Smoke Aware

real-time wildfire smoke from 200+ ground sensors and satellites, with a retrieval-augmented analyst

Year
2024
Kind
Software
Role
solo
Tools
Next.js, TypeScript, Python, FastAPI, PyTorch, XGBoost, CMAQ, RAG
Smoke Aware's Daily Summary page, a dark map of the western US with monitor pins above a table of readings

Wildfire smoke data is public, but it’s scattered. The temporary smoke monitors that get dropped near fires report to the Western Regional Climate Center, and their site shows you one station and one day at a time. If you want to know what the air looked like across the West on a given day, you’re opening a lot of tabs.

Smoke Aware puts it in one place: a map of every monitor, a daily summary, hourly charts for any station, an export button, and an assistant you can ask about the data.

TLDR: a Next.js app that pulls readings from 200+ ground sensors, caches them in Firestore, and shows them on a map with daily and hourly views. Air quality for your ZIP code comes from Google’s Air Quality API. Exported data can be handed to a retrieval-augmented assistant that answers questions from the files. It’s been used internally at Emory, and it took second place in the Congressional App Challenge.

Daily Summary: expand the map, zoom in, click a monitor, scroll the table, turn optional columns on.

Getting the data

There’s no WRCC API in the friendly sense. Each station has a data endpoint that returns a page you have to pick apart. So the first real piece of code was a scraper: build the URL for a station and date, fetch it, cut the readings out of the response.

JavaScript source for the function that fetches and parses a station's daily summary from WRCC
The scraper in fetch-methods.js. Most of it is string surgery on the response.

Scraping on every page load is slow and rude, so daily summaries go into Firestore the first time anyone asks for a day. Past days are served straight from there. Today’s summary gets refreshed every four hours.

JavaScript source for the day-summaries API route: read Firestore, refresh today's data every four hours, otherwise scrape and store
The day-summaries route. Read from Firestore if it's there; scrape and store if it isn't.
Diagram: public data from WRCC and EPA, Firebase and Google Maps on the backend; Next.js, React, Tailwind and Python on the frontend
How I drew the stack at the time.

Early on I also wrote a Python script to convert the raw files between formats: text, JSON, CSV, even PDF. The PDF route was a mistake (one row, “Unnamed” headers, columns falling off the page). CSV won, and it’s still what the export produces.

Before and after

The app first went live in October 2024. This is how it looked in January 2026: everything worked, but each page was a form, a map and a chart stacked on top of each other.

Earlier design of the Smoke Monitoring page: a date picker and a map of the US with monitor pins
The monitor map, January 2026.
Earlier design of the hourly chart page with a dropdown of data categories open
Hourly view, January 2026. Every sensor channel in one long dropdown.

In September 2026 I redid the interface: a proper sidebar, the map and table sharing one screen, cleaner charts with tooltips. The data underneath didn’t change.

Current Smoke Monitoring page with a sidebar, a map of 99 monitor stations and a popup for one station
Same page after the redesign. Click a pin, get the station and a link to its hourly view.
Current hourly page with concentration and humidity charts and a tooltip on a 290 micrograms per cubic metre peak
Hourly charts for one monitor. The tooltip is on a 290 µg/m³ spike at 2 pm. That's very unhealthy air.

It works on phones too, with the sidebar folding into a drawer.

Mobile layout of the daily summary with a top bar, a small map and the station table
Daily Summary on a phone.

Around You

Not everyone wants a monitor network. Some people just want to know if they should go for a run. The Around You page takes a ZIP code and shows current AQI, pollutant levels and a 48-hour forecast from Google’s Air Quality API.

Around You page for ZIP 30322 showing an AQI of 52, pollutant cards and an hourly forecast table
ZIP 30322 (Emory's campus): AQI 52, moderate.

Export and the assistant

Researchers mostly want the raw numbers. Fetch and Export takes a date range and builds a zip of CSVs, one per day, with a preview before you download.

Pick dates, export, preview.
An exported smoke summary CSV opened in Excel over the earlier Fetch page
What you get: one CSV per day, ready for Excel or pandas.

Those exports are also what the assistant reads. Upload a few days of summaries and ask about them. It searches the files and answers from what’s in them.

Assistant reply: the maximum smoke concentration on 10/15/2024 was 1019 µg/m³ at station s456
Asked for the worst reading on 10/15/2024, it found 1019 µg/m³ at station s456, which matches the raw data.

When I moved from OpenAI’s Assistants API to the newer Responses API, retrieval became optional: it only switches on when a vector store is configured.

What I’d change

The live routes that scrape WRCC are failing right now, and WRCC has started redirecting to an archive domain, which is the likely cause. The scraper needs to follow that, and it should have had a fallback source from the start.