Web Applications

Air quality readings for my city, on one page

Built by Maria Lourdes Pangilinan

A page that collects the public air sensor readings near where I live and plots them honestly, including the gaps.

  • Builder email verified

    Verification

Type
Personal
Category
Web Applications
Published
Jul 25, 2026
Updated
Jul 25, 2026
Built withPythonFastAPIPostgreSQLNext.jsVercel
TopicsClimateCivic Tech

About this project

What I built & why

There are a handful of public air quality sensors around the city and the official page shows the current reading for one station at a time, with no history and no sense of whether that number is unusual. I started pulling the readings into a spreadsheet during a bad week in February to see whether it was actually worse or I was imagining it. Once the spreadsheet hit a few thousand rows I turned it into something I could look at. It has been collecting since.

The problem

The problem is context. A single current number tells you nothing — 45 means nothing unless you know that the morning average here is usually 20 and last Tuesday it was 90. The page shows each station's reading against its own recent distribution, plus a day view so you can see the commute peaks, which are consistent and quite stark. It is for anyone in the city who wants to decide whether to run outside, and realistically it is used by me and about thirty other people.

What I owned

All of it: the collector, the storage, the API and the page. The part I put the most care into is how missing data is displayed — sensors drop out for hours or days, and the naive thing is to draw a line across the gap, which invents readings. My chart breaks the line and marks the gap, which makes the plot uglier and is the only honest option. I also handle the unit and scale differences between two sensor networks, which is fiddly and was where my first version was simply wrong.

Technical & product decisions

I collect at the source resolution and never downsample the stored data, only the display, because I did not want to make a decision now that I could not undo later. Storage is trivial at this volume. I chose not to compute or display any single air quality index, which was tempting because it is what people expect: the indices bundle several pollutants with a policy judgement baked in, and I did not want to be the person quietly asserting a threshold. Instead each pollutant is shown on its own with the official threshold drawn as a line, so the judgement stays visible and is not mine.

Hardest challenge

The two sensor networks in the city report at different intervals, in different units, and one of them silently reports its last known value when the sensor is offline rather than reporting nothing. That last one took me two months to notice, and I only caught it because a station read exactly 31 for nineteen hours straight. Now the collector detects repeated identical values beyond a plausible run and marks them as suspect rather than trusting them. It made me much more careful about the difference between a reading and the absence of one.

Result & impact

Eighteen months of continuous readings for six stations, which is more history than the official page offers and is the thing people actually come for. About thirty regular visitors, one of whom is a researcher who asked for a data export and now uses it. On the personal side it answered my original question — February was in fact worse, by roughly double the annual median at my nearest station — and my partner and I moved our running to mornings because of the commute peaks it made obvious.

Who else worked on it

Nobody, but a friend who does environmental work reviewed my handling of the thresholds and stopped me from displaying a combined index, which I am glad about.