HAAM-produkter

HAAM produktudgave · What I breathed, next to the city

Air

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.

HAAM Air landing page with the signed-out upload area next to the city illustration.
Personal hoursCity hours, labelled

compare metric

PM2.5

Not AQI. The join skips hours without a city background.

city background

CAMS

ECMWF CAMS via Open-Meteo. Source labelled on the hour.

Atmotube export

CSV

Parser handles aliases and outliers. Fixture tests in air-core.

personal tool

Live

air.haam.co is the published web dashboard. Observed use is personal.

The job

Put my hour next to the city’s hour.

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

Parse the CSV. Place the hours. Join the city. Read the gap.

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

Parse the export

Atmotube CSV aliases and outliers are handled in air-core. Fixture tests cover parse without live APIs.

02

Place the hours

GPS points fall into place boxes. Travel stays fill windows where the wearable has no fix.

03

Join the city

Each personal hour looks up CAMS background for that place. Hours without ambient are omitted from compare.

04

Read personal versus city

The dashboard shows the two PM2.5 series hour by hour, with CAMS or optional station labelled as the city source.

Design decisions

What had to stay true in the interface.

air.haam.co

Metric

Compare stays on PM2.5.

AQI would have mixed pollutants and indexes. I kept one particle measure so personal and city hours are actually the same quantity.

  • PM2.5 only on the compare
  • Outdoorish filter is explicit
  • Hours without ambient are omitted, not invented

Place

Stays interview the gaps GPS cannot fill.

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.

  • Place boxes and region boxes in air-core
  • Stays as GPS-less travel windows
  • Hourly join covered by fixture tests

Canonical surface

The web dashboard is the product.

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.

  • air-core tests for parse, places, and compare
  • Clerk, Supabase stays, and Blob on air.haam.co
  • Expo / Streamlit unverified as suite evidence

The working stack

What actually runs.

Core

air-core

CSV parse, place boxes, hourly join. pnpm test against fixtures.

Ambient

Open-Meteo CAMS

ECMWF city PM2.5 background. Optional OpenAQ overlay undocumented as production.

Account

Clerk + Supabase + Blob

Signed-in CSVs and stay windows on the live host. Secrets gitignored.

Host

air.haam.co

Published personal tool. Web is canonical.

In evidence

What a reviewer can check.

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

Personal use, one particle, no medical reading.

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

I wanted my hours next to the city’s hours.

PM2.5-only compare, stays interview, HAAM chrome, air-core join, web app, and the live host wiring. Direct work, 2026.

Help improve this website?

Optional analytics. Google Analytics and Clarity load only if allowed; form, email, and chat content are excluded.