Target interaction specification

Air

Was the air around me different from the city background at the same place and hour?

Air may compare personal and ambient PM2.5 only when metric, place, and time align. Missing or unresolved matches must remain missing, every ambient value must expose its source and age, and the product must not turn an exposure difference into a medical conclusion.

Case-study thesis

What the finished product should prove.

This is the behavior to build toward. The implementation receipts below remain the record of what exists today.

The interaction problem is joining two imperfect worlds without hiding the gaps: a wearable that knows what was near the body and a city model that knows the background around a place.

State model

The interface has to make these states real.

If two states would look identical to the person, the implementation is not finished.

01

No personal data

The interface explains the required export and what will happen before asking for a file.

02

Parsing

Valid, rejected, duplicate, and outlier rows are counted visibly.

03

Place unresolved

An hour has a measurement but no trustworthy location, so comparison waits.

04

Matched

Personal and ambient PM2.5 share the same hour and place context, with both sources visible.

05

Unmatched

A personal hour has no ambient counterpart and remains visibly outside the comparison.

06

Stay corrected

A travel stay supplies location context for a GPS-less window and recomputes affected hours.

07

Source detail

The person can inspect raw personal value, ambient source, model or station, location basis, and timestamp.

08

Stale or failed

Unavailable ambient data, failed fetch, or stale background is represented directly rather than interpolated without disclosure.

Edge cases

The boring failures are part of the product.

  • The wearable has PM2.5 but no GPS
  • A flight or timezone change creates ambiguous local hours
  • Indoor exposure is much higher than the outdoor city background
  • The Atmotube export contains duplicates or outliers
  • CAMS has no value for the hour
  • A station and model disagree
  • A travel stay is corrected after comparison was already viewed

Definition of done

The future case study needs these receipts.

  • Clicking an compared hour reveals the personal measurement, place basis, ambient source, timestamp, and any filtering applied
  • Hours without a valid ambient match are excluded or marked unmatched, never silently estimated
  • Editing a travel stay visibly changes only the hours affected by that place correction
  • The comparison remains PM2.5 to PM2.5 instead of mixing AQI systems
  • No view converts a personal-versus-city difference into diagnosis, risk score, or treatment advice
HAAMin tuotteet

HAAMin tuoteversio · 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.

How we learned

Research

Personal sensing

The useful comparison was personal exposure next to the city background.

Research paired hour-by-hour Atmotube PM2.5 readings with the CAMS city background. Looking at both together makes it possible to ask when personal exposure diverges from the broader environment instead of treating either sensor stream as the whole story.

  • Atmotube sensing
  • CAMS comparison
  • Hourly alignment
  • Exposure review

Help improve this website?

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