compare metric
PM2.5Not AQI. The join skips hours without a city background.
HAAMi tooteväljaanne · What I breathed, next to the city
I carry an Atmotube. HAAM Air joins those personal PM2.5 hours to ECMWF CAMS city background, with travel stays filling the gaps where GPS drops. Compare is PM2.5 only. Hours without a city background are omitted.

compare metric
PM2.5Not AQI. The join skips hours without a city background.
city background
CAMSECMWF CAMS via Open-Meteo. Source labelled on the hour.
Atmotube export
CSVParser handles aliases and outliers. Fixture tests in air-core.
personal tool
Liveair.haam.co is the published web dashboard. Observed use is personal.
The job
A wearable PM2.5 number without a matched city hour is hard to read. I wanted the two series on the same clock, with the source labelled, and with travel stays filling GPS-less windows.
Medical interpretation would have been a different product. AQI as the compare metric would have mixed indexes. Multi-tenant SaaS was never the job.
The web dashboard is canonical. Expo and Streamlit exist as companions.
The compare
Signed-in CSVs go to Blob on the live host. Stays go to Supabase. Ambient fetch is Open-Meteo. Secrets stay out of Git.
01
Atmotube CSV aliases and outliers are handled in air-core. Fixture tests cover parse without live APIs.
02
GPS points fall into place boxes. Travel stays fill windows where the wearable has no fix.
03
Each personal hour looks up CAMS background for that place. Hours without ambient are omitted from compare.
04
The dashboard shows the two PM2.5 series hour by hour, with CAMS or optional station labelled as the city source.
Design decisions
air.haam.co
Metric
AQI would have mixed pollutants and indexes. I kept one particle measure so personal and city hours are actually the same quantity.
Place
Travel windows without a fix still need a city. The stays flow is how those hours get a place box instead of disappearing from the join.
Canonical surface
Parser, places, and join live in air-core so the web app is not a one-off script. Expo and Streamlit are companions. OpenAQ station overlay is optional and not claimed as production-verified.
The working stack
Core
CSV parse, place boxes, hourly join. pnpm test against fixtures.
Ambient
ECMWF city PM2.5 background. Optional OpenAQ overlay undocumented as production.
Account
Signed-in CSVs and stay windows on the live host. Secrets gitignored.
Host
Published personal tool. Web is canonical.
In evidence
01Personal Atmotube hours sit next to CAMS city hours
02The join is tested without live APIs
03Travel stays can fill GPS-less gaps
04Hours without city background are omitted from compare
Where it stops
There is no medical interpretation and no live AQI compare. Observed use is personal, not a public multi-user study.
Expo and Streamlit are companions. Accessibility is not recorded in this pass. Do not call it a city-wide air-quality product.
HAAM’s role
PM2.5-only compare, stays interview, HAAM chrome, air-core join, web app, and the live host wiring. Direct work, 2026.
Optional analytics. Google Analytics and Clarity load only if allowed; form, email, and chat content are excluded.