Loading air-quality data
Fetching the latest available snapshot.
Loading air-quality data
Loading air-quality data
Fetching the latest available snapshot.
Loading air-quality data
Method & limits
HazeWatch is an independent reader of official Malaysian data. This page documents the exact boundaries: what is observed, what is an official forecast, and what is our own analysis.
01 · Separation
Observation
Hourly API values published by DOE through APIMS, with station coordinates and a source timestamp.
Official forecast
MyCMAQ map products published by MetMalaysia. Linked as the authoritative forecast, never re-drawn as our own.
HazeWatch insight
Deterministic trend comparisons and plain-language briefings, explicitly labelled and limited to the observed window.
The interface labels each layer at the point of display. HazeWatch does not assign official categories, does not issue official warnings, and does not convert a concentration into an API value.
02 · Classification
| Band | API range | Meaning |
|---|---|---|
| Good | 0–50 | Good |
| Moderate | 51–100 | Moderate |
| Unhealthy | 101–200 | Unhealthy |
| Very unhealthy | 201–300 | Very unhealthy |
| Hazardous | Above 300 | Hazardous |
When APIMS publishes a class label for the hour, HazeWatch displays that class. The numeric band is still computed and any mismatch is surfaced on the station page. We never substitute US AQI, PM2.5 concentration thresholds or another country's scale for Malaysia's API.
03 · Analysis
These rules describe what already happened. They are not a statistical forecast, not a model, and not a prediction. A rise can reverse in the next hour, which is why every insight carries the observed timestamps.
04 · Time
APIMS also returns an epoch field that behaves as if the wall clock were UTC. HazeWatch deliberately ignores it as an observation instant rather than inventing an eight-hour shift.
05 · Provenance
Department of Environment — APIMS
Source ↗Air Pollutant Index of Malaysia, Department of Environment (DOE)
MetMalaysia — MyCMAQ
Source ↗Operational air quality forecast, Malaysian Meteorological Department
OpenDOSM — Monthly Air Pollution
Source ↗Monthly air pollution, Department of Statistics Malaysia (CC BY 4.0)
ASMC hotspot and haze information is not ingested: its published terms restrict automated collection and reuse without permission. We link to official sources instead of copying them.
06 · Forecast
MetMalaysia's MyCMAQ portal currently exposes forecast products as rendered map images and manifests. HazeWatch records the published cycle, retrieves the manifest to count available frames, and links to the official viewer.
MyCMAQ's LST frame labels are shown verbatim. Their numerical offset is not independently confirmed, so HazeWatch does not compare them with observation timestamps.
07 · Explanation
08 · Maps
The map uses MapLibre GL JS with OpenFreeMap tiles and the station coordinates published by APIMS. Markers show the current API value; selecting one opens the station name, state, observation time and a link to its detail page.
No map API key is required. OpenFreeMap, OpenMapTiles and OpenStreetMap attribution stays visible on the map. If tiles or WebGL are unavailable, the searchable station list and selection panel remain usable. A dash means no reading, not zero.
09 · Persistence
When Supabase is configured, observations are written with a uniqueness key of station, metric and observation time. Re-running collection updates the same row instead of duplicating it. Each source gets its own ingestion log with accepted and rejected counts, so one failing provider cannot hide another.
Public tables have row-level security with read-only policies. The service-role key is used only on the server. Without Supabase credentials the app runs in a clearly labelled live-preview mode: it reads APIMS for the current window and archives nothing.
A pinned station is stored in this browser's local storage. No account is created, no preference is sent to the server, and clearing site data removes it.
Recent collection runs
Server-side log
| Source | Status | Accepted | Rejected | Started |
|---|---|---|---|---|
| opendosm | success | 576 | 0 | 26 Oct 2026, 01:15 MYT |
| metmalaysia-mycmaq | success | 1 | 0 | 26 Oct 2026, 01:15 MYT |
| doe-apims | success | 1700 | 0 | 26 Oct 2026, 01:15 MYT |
| opendosm | success | 576 | 0 | 26 Oct 2026, 01:05 MYT |
| metmalaysia-mycmaq | success | 1 | 0 | 26 Oct 2026, 01:05 MYT |
| doe-apims | success | 1700 | 0 | 26 Oct 2026, 01:05 MYT |
10 · Configuration
Required for storage: NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY and CRON_SECRET. Optional: GEMINI_API_KEY, GEMINI_MODEL. Maps work without credentials; provider settings live in lib/map.ts.
Apply supabase/migrations/0001_init.sql in the Supabase SQL editor, then schedule an authenticated request to /api/ingest every hour. The endpoint accepts GET or POST with Authorization: Bearer <CRON_SECRET>. The repository README covers Supabase Cron, Vercel Cron and map configuration.
Credentials are never committed. The app remains usable without Google or Gemini configured; those layers degrade visibly rather than silently.
11 · Limits