LOADING
Node.js ESM wrapper for official Indonesian ISPU air-quality data (KemenLH). No API key — auto token, CLI + module.
#!/usr/bin/env node
/**
* ISPU Indonesia — Durable air-quality wrapper (Node.js ESM)
* ===========================================================
* Sumber resmi: Kementerian Lingkungan Hidup / BPLH
* Portal : https://ispu.kemenlh.go.id
* API : https://ispu.kemenlh.go.id/apiweb
*
* Token JWT diambil OTOMATIS dari bundle frontend website
* (tidak perlu copy manual). Cache token di memory.
*
* ── Module ──────────────────────────────────────────────────
* import ISPU, { ispuCategory } from './ispu.js';
* const aq = new ISPU();
* const all = await aq.all();
* const jkt = await aq.search('jakarta');
* const rank = await aq.ranking({ limit: 15, provinsi: 'Kalimantan' });
* const one = await aq.station('PALANGKARAYA');
*
* ── CLI ─────────────────────────────────────────────────────
* node ispu.js summary
* node ispu.js ranking --limit=20 --provinsi="Jawa Barat"
* node ispu.js search --q=bandung
* node ispu.js station --id=PALANGKARAYA
* node ispu.js provinsi --name="DKI Jakarta"
* node ispu.js stations
* node ispu.js list-provinsi
*/
const API_BASE = 'https://ispu.kemenlh.go.id/apiweb';
const APP_URL = 'https://ispu.kemenlh.go.id/app/';
/** Fallback jika auto-fetch gagal (token publik yang tertanam di frontend). */
const FALLBACK_TOKEN =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ0eXBlIjoiaXNwdS5rZW1lbmxoLmdvLmlkIiwia2V5IjoiRlB4ZnFSQzlKTzY1ZmpxRiIsInNlY3JldCI6Ijk1M2Q0NzZhLTllMDktNDQzNy05OGQ1LTg4ODQwYmIxOThmMCJ9.bkluNcKYjhgBor57qU04_LTvnKSSULUIJ8B2IOV20Yvx996vfWJReM7S-NKYIyycJ3WiORm8kqorzl0aTLrWCQ';
const UA =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
// ─── Helpers ────────────────────────────────────────────────
export class ISPUError extends Error {
constructor(message, { status, response } = {}) {
super(message);
this.name = 'ISPUError';
this.status = status ?? null;
this.response = response ?? null;
}
}
/**
* Kategori ISPU — Permen LHK No. 14 Tahun 2020
*/
export function ispuCategory(val) {
const n = Number(val);
if (val == null || Number.isNaN(n) || n < 0) return null;
if (n <= 50)
return {
code: 1,
name: 'Baik',
nameEn: 'Good',
color: '#00e400',
range: '0-50',
advice: 'Tidak ada dampak kesehatan.',
};
if (n <= 100)
return {
code: 2,
name: 'Sedang',
nameEn: 'Moderate',
color: '#0066ff',
range: '51-100',
advice: 'Masih dapat diterima; kelompok sensitif waspada.',
};
if (n <= 200)
return {
code: 3,
name: 'Tidak Sehat',
nameEn: 'Unhealthy',
color: '#ffcc00',
range: '101-200',
advice: 'Kurangi aktivitas di luar, terutama kelompok sensitif.',
};
if (n <= 300)
return {
code: 4,
name: 'Sangat Tidak Sehat',
nameEn: 'Very Unhealthy',
color: '#ff0000',
range: '201-300',
advice: 'Hindari aktivitas luar ruangan.',
};
return {
code: 5,
name: 'Berbahaya',
nameEn: 'Hazardous',
color: '#000000',
range: '≥301',
advice: 'Tetap di dalam ruangan; dampak kesehatan serius.',
};
}
function num(v) {
if (v === null || v === undefined || v === '') return null;
const n = Number(v);
return Number.isNaN(n) ? null : n;
}
function stripHtml(s) {
if (!s) return null;
return (
String(s)
.replace(/<\/?sub>/gi, '')
.replace(/<[^>]+>/g, '')
.replace(/ /g, ' ')
.trim() || null
);
}
function normalizeCategoryName(raw) {
if (!raw) return null;
const u = String(raw).toUpperCase();
if (u.includes('BAIK') && !u.includes('TIDAK')) return 'Baik';
if (u.includes('SEDANG')) return 'Sedang';
if (u.includes('SANGAT')) return 'Sangat Tidak Sehat';
if (u.includes('TIDAK SEHAT')) return 'Tidak Sehat';
if (u.includes('BERBAHAYA')) return 'Berbahaya';
return stripHtml(raw);
}
function tipeLabel(t) {
if (t === '0' || t === 0) return 'KLH/BPLH';
if (t === '1' || t === 1) return 'Integrasi';
return t != null ? String(t) : null;
}
function mapHist(arr) {
if (!Array.isArray(arr)) return null;
return arr.map((x) => ({
nilai: num(x?.nilai),
warna: x?.warna ?? null,
}));
}
function normalizeStation(row, { includeRaw = false } = {}) {
if (!row || typeof row !== 'object') return null;
const ispu = num(row.val);
const catName =
normalizeCategoryName(row.cat) ||
normalizeCategoryName(row.kategori?.nilai) ||
ispuCategory(ispu)?.name ||
null;
const out = {
id: row.id_stasiun ?? null,
nama: row.nama ?? null,
kota: row.kota ?? null,
provinsi: row.provinsi ?? null,
kelurahan: row.kelurahan || null,
kecamatan: row.kecamatan || null,
alamat: row.alamat || null,
kawasan: row.p3e || null,
lat: num(row.lat),
lon: num(row.lon),
ispu,
kategori: catName,
kategoriDetail: ispuCategory(ispu),
parameterKritis: stripHtml(row.param),
tipe: row.tipe_text || tipeLabel(row.tipe),
timezone: row.time_z || 'WIB',
waktu: row.waktu || null,
waktuText: row.waktu_text || null,
maintenance: row.is_maintenance === '1' || row.is_maintenance === 1,
autoValidation: row.auto_validation === '1' || row.auto_validation === 1,
konsentrasi: {
pm10: num(row.a_pm10),
pm25: num(row.a_pm25),
so2: num(row.a_so2),
co: num(row.a_co),
o3: num(row.a_o3),
no2: num(row.a_no2),
hc: num(row.a_hc),
},
ispuPerParameter: {
pm10: num(row.t_pm10) ?? num(row.pm10),
pm25: num(row.t_pm25) ?? num(row.pm25),
so2: num(row.t_so2) ?? num(row.so2),
co: num(row.t_co) ?? num(row.co),
o3: num(row.t_o3) ?? num(row.o3),
no2: num(row.t_no2) ?? num(row.no2),
hc: num(row.t_hc) ?? num(row.hc),
},
};
if (row.waktu_24 || row.pm25_24_rev || row.pm10_24_rev) {
out.histori24jam = {
waktu: Array.isArray(row.waktu_24) ? row.waktu_24 : null,
pm25: mapHist(row.pm25_24_rev),
pm10: mapHist(row.pm10_24_rev),
};
}
if (includeRaw) out.raw = row;
return out;
}
function stripRaw(list) {
return list.map(({ raw, ...rest }) => rest);
}
/**
* Ambil JWT dari bundle Angular frontend ISPU.
* Cari main.*.js di halaman app, lalu ekstrak string eyJ...
*/
async function fetchTokenFromFrontend(timeout = 15000) {
const ac = new AbortController();
const timer = setTimeout(() => ac.abort(), timeout);
try {
const htmlRes = await fetch(APP_URL, {
headers: {
Accept: 'text/html',
'User-Agent': UA,
},
signal: ac.signal,
redirect: 'follow',
});
const html = await htmlRes.text();
// main.<hash>.js
const mainMatch =
html.match(/src="(main\.[a-f0-9]+\.js)"/i) ||
html.match(/(main\.[a-f0-9]+\.js)/i);
if (!mainMatch) {
throw new Error('main.js tidak ditemukan di halaman app');
}
let mainUrl = mainMatch[1];
if (!mainUrl.startsWith('http')) {
mainUrl = new URL(mainUrl, APP_URL).href;
}
const jsRes = await fetch(mainUrl, {
headers: { Accept: '*/*', 'User-Agent': UA },
signal: ac.signal,
});
const js = await jsRes.text();
// JWT HS512 yang dipakai Authorization
const jwtRe =
/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
const found = js.match(jwtRe);
if (!found || !found.length) {
throw new Error('JWT tidak ditemukan di main.js');
}
// pilih yang payload-nya mengandung ispu / kemenlh
for (const tok of found) {
try {
const payload = JSON.parse(
Buffer.from(tok.split('.')[1], 'base64url').toString('utf8')
);
if (
String(payload.type || '').includes('ispu') ||
String(payload.type || '').includes('kemenlh')
) {
return tok;
}
} catch {
/* skip */
}
}
// fallback: JWT terpanjang (biasanya yang benar)
return found.sort((a, b) => b.length - a.length)[0];
} finally {
clearTimeout(timer);
}
}
// ─── Client ─────────────────────────────────────────────────
export class ISPU {
/**
* @param {object} [opts]
* @param {string} [opts.token] Paksa token (skip auto-fetch)
* @param {boolean} [opts.autoToken] default true — ambil dari frontend
* @param {number} [opts.timeout] ms
* @param {number} [opts.cacheTtl] ms cache data stasiun
* @param {number} [opts.tokenTtl] ms cache token (default 6 jam)
*/
constructor(opts = {}) {
this._manualToken = opts.token || process.env.ISPU_TOKEN || null;
this.autoToken = opts.autoToken !== false;
this.timeout = opts.timeout ?? 20_000;
this.cacheTtl = opts.cacheTtl ?? 5 * 60 * 1000;
this.tokenTtl = opts.tokenTtl ?? 6 * 60 * 60 * 1000;
this._token = this._manualToken;
this._tokenAt = this._manualToken ? Date.now() : 0;
this._tokenPromise = null;
this._cache = null;
this._cacheAt = 0;
}
/** Paksa set token manual */
setToken(token) {
this._token = token;
this._tokenAt = Date.now();
this._manualToken = token;
this._cache = null;
}
/**
* Pastikan ada token valid (auto-fetch jika perlu)
*/
async ensureToken({ force = false } = {}) {
if (this._manualToken && !force) {
this._token = this._manualToken;
return this._token;
}
const fresh =
this._token &&
Date.now() - this._tokenAt < this.tokenTtl &&
!force;
if (fresh) return this._token;
// dedupe concurrent fetch
if (this._tokenPromise) return this._tokenPromise;
this._tokenPromise = (async () => {
try {
if (this.autoToken) {
const tok = await fetchTokenFromFrontend(this.timeout);
this._token = tok;
this._tokenAt = Date.now();
return tok;
}
} catch (e) {
// fallback ke token bawaan
if (!this._token) {
this._token = FALLBACK_TOKEN;
this._tokenAt = Date.now();
}
return this._token;
} finally {
this._tokenPromise = null;
}
if (!this._token) {
this._token = FALLBACK_TOKEN;
this._tokenAt = Date.now();
}
return this._token;
})();
return this._tokenPromise;
}
/** @private */
async _get(path) {
await this.ensureToken();
const url = `${API_BASE}${path.startsWith('/') ? path : `/${path}`}`;
const ac = new AbortController();
const t = setTimeout(() => ac.abort(), this.timeout);
try {
let res = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
Authorization: `Bearer ${this._token}`,
Referer: APP_URL,
Origin: 'https://ispu.kemenlh.go.id',
'User-Agent': UA,
},
signal: ac.signal,
});
let body = await res.json().catch(() => ({}));
let code = body?.status?.code;
// 401 → refresh token sekali lalu retry
if (
(res.status === 401 || code === 401) &&
this.autoToken &&
!this._manualToken
) {
await this.ensureToken({ force: true });
res = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
Authorization: `Bearer ${this._token}`,
Referer: APP_URL,
Origin: 'https://ispu.kemenlh.go.id',
'User-Agent': UA,
},
signal: ac.signal,
});
body = await res.json().catch(() => ({}));
code = body?.status?.code;
}
if (res.status === 401 || code === 401) {
throw new ISPUError(
'Unauthorized (401) meski sudah refresh token. Coba set ISPU_TOKEN manual.',
{ status: 401, response: body }
);
}
if (!res.ok || (code != null && code !== 200)) {
throw new ISPUError(body?.status?.desc || `HTTP ${res.status}`, {
status: res.status,
response: body,
});
}
return body;
} catch (e) {
if (e.name === 'AbortError') {
throw new ISPUError(`Request timeout (${this.timeout}ms)`);
}
if (e instanceof ISPUError) throw e;
throw new ISPUError(e.message || 'Network error');
} finally {
clearTimeout(t);
}
}
// ── Public API ────────────────────────────────────────────
/**
* Semua stasiun realtime
* @param {{ force?: boolean, includeRaw?: boolean }} [opts]
*/
async all({ force = false, includeRaw = false } = {}) {
const now = Date.now();
if (!force && this._cache && now - this._cacheAt < this.cacheTtl) {
const stations = includeRaw
? this._cache.stations
: stripRaw(this._cache.stations);
return { ...this._cache, stations };
}
const data = await this._get('/stasiun');
const stations = (data.rows || []).map((r) =>
normalizeStation(r, { includeRaw: true })
);
this._cache = {
total: data.total ?? stations.length,
updated: stations.find((s) => s?.waktu)?.waktu ?? null,
stations,
source: 'https://ispu.kemenlh.go.id',
sourceApi: `${API_BASE}/stasiun`,
scrapedAt: new Date().toISOString(),
};
this._cacheAt = now;
return {
...this._cache,
stations: includeRaw ? stations : stripRaw(stations),
};
}
stations(opts) {
return this.all(opts);
}
/**
* Detail stasiun (+ histori 24 jam)
* @param {string} id
*/
async station(id, { includeRaw = false } = {}) {
if (!id) throw new ISPUError('id stasiun wajib');
const data = await this._get(
`/stasiun/detail/id/${encodeURIComponent(id)}`
);
const row = data.rows?.[0] ?? data.row ?? data;
const s = normalizeStation(row, { includeRaw });
if (!s) throw new ISPUError(`Stasiun tidak ditemukan: ${id}`);
return s;
}
/**
* Cari nama / kota / provinsi / id
* @param {string} query
*/
async search(query, { includeRaw = false } = {}) {
const q = String(query ?? '')
.toLowerCase()
.trim();
if (!q) throw new ISPUError('query wajib');
const all = await this.all({ includeRaw: true });
const matched = all.stations.filter((s) => {
const hay = [s.id, s.nama, s.kota, s.provinsi, s.alamat, s.kawasan]
.filter(Boolean)
.join(' ')
.toLowerCase();
return hay.includes(q);
});
return {
query: q,
total: matched.length,
updated: all.updated,
stations: includeRaw ? matched : stripRaw(matched),
scrapedAt: all.scrapedAt,
};
}
/**
* Ranking ISPU tertinggi
* @param {{ limit?: number, provinsi?: string, kota?: string }} [opts]
*/
async ranking({ limit = 20, provinsi, kota } = {}) {
let list = (await this.all({ includeRaw: true })).stations.filter(
(s) => s.ispu != null && !s.maintenance
);
if (provinsi) {
const p = provinsi.toLowerCase();
list = list.filter((s) => (s.provinsi || '').toLowerCase().includes(p));
}
if (kota) {
const k = kota.toLowerCase();
list = list.filter(
(s) =>
(s.kota || '').toLowerCase().includes(k) ||
(s.nama || '').toLowerCase().includes(k)
);
}
list.sort((a, b) => (b.ispu || 0) - (a.ispu || 0));
const sliced = list.slice(0, limit);
return {
total: list.length,
limit,
filter: { provinsi: provinsi || null, kota: kota || null },
updated: sliced[0]?.waktu || null,
stations: stripRaw(sliced.map((s, i) => ({ rank: i + 1, ...s }))),
scrapedAt: new Date().toISOString(),
};
}
async byProvinsi(name) {
if (!name) throw new ISPUError('nama provinsi wajib');
const p = name.toLowerCase();
const all = await this.all({ includeRaw: true });
const stations = all.stations.filter((s) =>
(s.provinsi || '').toLowerCase().includes(p)
);
return {
provinsi: name,
total: stations.length,
updated: all.updated,
stations: stripRaw(stations),
scrapedAt: all.scrapedAt,
};
}
async byKota(name) {
if (!name) throw new ISPUError('nama kota wajib');
const k = name.toLowerCase();
const all = await this.all({ includeRaw: true });
const stations = all.stations.filter(
(s) =>
(s.kota || '').toLowerCase().includes(k) ||
(s.nama || '').toLowerCase().includes(k)
);
return {
kota: name,
total: stations.length,
updated: all.updated,
stations: stripRaw(stations),
scrapedAt: all.scrapedAt,
};
}
async listProvinsi() {
const all = await this.all({ includeRaw: true });
const map = new Map();
for (const s of all.stations) {
const key = s.provinsi || 'Tidak diketahui';
if (!map.has(key)) {
map.set(key, {
provinsi: key,
count: 0,
ispuSum: 0,
ispuN: 0,
stations: [],
});
}
const g = map.get(key);
g.count++;
if (s.ispu != null) {
g.ispuSum += s.ispu;
g.ispuN++;
}
g.stations.push({
id: s.id,
nama: s.nama,
kota: s.kota,
ispu: s.ispu,
kategori: s.kategori,
});
}
const list = [...map.values()]
.map((g) => ({
provinsi: g.provinsi,
jumlahStasiun: g.count,
ispuRataRata: g.ispuN ? Math.round(g.ispuSum / g.ispuN) : null,
stations: g.stations.sort((a, b) => (b.ispu || 0) - (a.ispu || 0)),
}))
.sort((a, b) => (b.ispuRataRata || 0) - (a.ispuRataRata || 0));
return {
total: list.length,
updated: all.updated,
provinsi: list,
scrapedAt: all.scrapedAt,
};
}
async summary() {
const all = await this.all({ includeRaw: true });
const active = all.stations.filter((s) => s.ispu != null && !s.maintenance);
const sorted = [...active].sort((a, b) => (b.ispu || 0) - (a.ispu || 0));
const counts = {
Baik: 0,
Sedang: 0,
'Tidak Sehat': 0,
'Sangat Tidak Sehat': 0,
Berbahaya: 0,
};
for (const s of active) {
const k = s.kategori;
if (k && counts[k] !== undefined) counts[k]++;
}
const pick = (s, i) => ({
rank: i + 1,
id: s.id,
nama: s.nama,
kota: s.kota,
provinsi: s.provinsi,
ispu: s.ispu,
kategori: s.kategori,
parameterKritis: s.parameterKritis,
lat: s.lat,
lon: s.lon,
});
return {
total: active.length,
updated: all.updated,
kategoriCount: counts,
topPolusi: sorted.slice(0, 10).map(pick),
topBersih: [...sorted].reverse().slice(0, 10).map(pick),
source: all.source,
scrapedAt: all.scrapedAt,
};
}
}
export default ISPU;
// ═══════════════════════════════════════════════════════════
// CLI
// ═══════════════════════════════════════════════════════════
function parseArgs(argv) {
const args = { _: [] };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith('--')) {
const eq = a.indexOf('=');
if (eq > -1) {
args[a.slice(2, eq)] = a.slice(eq + 1);
} else {
const key = a.slice(2);
const next = argv[i + 1];
if (next && !next.startsWith('-')) {
args[key] = next;
i++;
} else {
args[key] = true;
}
}
} else {
args._.push(a);
}
}
return args;
}
function help() {
console.log(`
ISPU Indonesia — kualitas udara resmi (KemenLH/BPLH)
====================================================
Token diambil otomatis dari website (tidak perlu manual).
Commands:
summary Ringkasan nasional
stations [--force] Semua stasiun
station --id=PALANGKARAYA Detail + histori 24 jam
search --q=jakarta Cari nama/kota/provinsi
ranking [--limit=20] [--provinsi=...] [--kota=...]
provinsi --name="DKI Jakarta" Filter provinsi
kota --name=Bandung Filter kota
list-provinsi Daftar provinsi + rata-rata ISPU
Options:
--token=JWT Paksa token (skip auto)
--force Abaikan cache data
--help, -h
Env: ISPU_TOKEN (opsional)
Examples:
node ispu.js summary
node ispu.js ranking --limit=10 --provinsi="Kalimantan Tengah"
node ispu.js search --q=surabaya
node ispu.js station --id=PALANGKARAYA
node ispu.js list-provinsi
Sumber: https://ispu.kemenlh.go.id
`);
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const cmd = args._[0];
if (!cmd || args.help || args.h) {
help();
process.exit(cmd ? 0 : 1);
}
const client = new ISPU({ token: args.token || undefined });
let result;
try {
switch (cmd) {
case 'summary':
case 'ringkas':
result = await client.summary();
break;
case 'stations':
case 'stasiun':
case 'all':
result = await client.all({ force: !!args.force });
break;
case 'station':
case 'detail':
if (!args.id) throw new ISPUError('--id wajib');
result = await client.station(args.id);
break;
case 'search':
case 'cari':
if (!args.q && !args.query) throw new ISPUError('--q wajib');
result = await client.search(args.q || args.query);
break;
case 'ranking':
result = await client.ranking({
limit: args.limit ? Number(args.limit) : 20,
provinsi: args.provinsi,
kota: args.kota,
});
break;
case 'provinsi':
if (!args.name && !args.nama) throw new ISPUError('--name wajib');
result = await client.byProvinsi(args.name || args.nama);
break;
case 'kota':
if (!args.name && !args.nama) throw new ISPUError('--name wajib');
result = await client.byKota(args.name || args.nama);
break;
case 'list-provinsi':
case 'list_provinsi':
result = await client.listProvinsi();
break;
default:
console.error('Unknown command: ' + cmd);
help();
process.exit(1);
}
console.log(JSON.stringify(result, null, 2));
} catch (err) {
console.error(
JSON.stringify(
{ error: true, message: err.message, status: err.status ?? null },
null,
2
)
);
process.exit(1);
}
}
const isMain =
typeof process !== 'undefined' &&
process.argv[1] &&
(process.argv[1].endsWith('ispu.js') || process.argv[1].endsWith('/ispu'));
if (isMain) main();Wrapper Node.js ESM untuk data kualitas udara resmi Indonesia (ISPU) dari Kementerian Lingkungan Hidup / BPLH.
Sumber: ispu.kemenlh.go.id · Endpoint JSON publik · tanpa API key
Token JWT diambil otomatis dari bundle frontend website. Cocok untuk bot, dashboard, notifikasi, atau CLI.
bash# Node.js 18+ (native fetch) # cukup copy ispu.js — tanpa npm install
jsimport ISPU, { ispuCategory } from './ispu.js'; const aq = new ISPU(); // Ringkasan nasional const summary = await aq.summary(); // Cari const jkt = await aq.search('jakarta'); // Ranking const rank = await aq.ranking({ limit: 10, provinsi: 'Kalimantan Tengah' }); // Filter const jabar = await aq.byProvinsi('Jawa Barat'); const bdg = await aq.byKota('Bandung'); // Detail + histori 24 jam const dki1 = await aq.station('DKI1'); // Semua stasiun / daftar provinsi const all = await aq.all(); const prov = await aq.listProvinsi(); // Helper kategori console.log(ispuCategory(112)); // → { code: 3, name: 'Tidak Sehat', color: '#ffcc00', ... }
jsnew ISPU({ token: '...', // paksa token (skip auto-fetch) autoToken: true, // default: ambil JWT dari frontend timeout: 20_000, // ms cacheTtl: 300_000, // cache data stasiun (5 menit) tokenTtl: 6 * 3600_000, // cache token (6 jam) });
Env opsional: ISPU_TOKEN=...
bashnode ispu.js summary node ispu.js stations node ispu.js search --q=surabaya node ispu.js ranking --limit=15 --provinsi="Jawa Barat" node ispu.js ranking --kota=Palembang node ispu.js provinsi --name="DKI Jakarta" node ispu.js kota --name=Medan node ispu.js station --id=PALANGKARAYA node ispu.js list-provinsi
Output selalu JSON ke stdout (siap di-pipe / parse).
json{ "id": "DKI1", "nama": "DKI 1 Bundaran HI", "kota": "Jakarta", "provinsi": "DKI Jakarta", "alamat": "...", "lat": -6.19, "lon": 106.82, "ispu": 112, "kategori": "Tidak Sehat", "kategoriDetail": { "code": 3, "name": "Tidak Sehat", "nameEn": "Unhealthy", "color": "#ffcc00", "range": "101-200", "advice": "Kurangi aktivitas di luar..." }, "parameterKritis": "PM2.5", "konsentrasi": { "pm10": ..., "pm25": ..., "so2": ..., "co": ..., "o3": ..., "no2": ..., "hc": ... }, "ispuPerParameter": { "pm10": 70, "pm25": 112, ... }, "waktu": "2026-10-06 18:00:00", "tipe": "INTEGRASI" }
| ISPU | Kategori | Warna |
|---|---|---|
| 0–50 | Baik | #00e400 |
| 51–100 | Sedang | #0066ff |
| 101–200 | Tidak Sehat | #ffcc00 |
| 201–300 | Sangat Tidak Sehat | #ff0000 |
| ≥301 | Berbahaya | #000000 |
main.*.js di situs ISPU. Jika 401, wrapper refresh sekali; fallback token bawaan tetap ada.konsentrasi: 0 di list API, sementara nilai ISPU ada di ispu / ispuPerParameter — ini perilaku data sumber.MIT — bebas dipakai untuk proyek pribadi maupun komersial.
$ node ispu.js summary
{
"total": 117,
"updated": "2026-10-06 20:00:00",
"kategoriCount": {
"Baik": 33,
"Sedang": 46,
"Tidak Sehat": 16,
"Sangat Tidak Sehat": 6,
"Berbahaya": 16
},
"topPolusi": [
{
"rank": 1,
"id": "KABUPATEN_GUNUNG_MAS",
"nama": "Kabupaten Gunung Mas",
"kota": "Kabupaten Gunung Mas",
"provinsi": "Kalimantan Tengah",
"ispu": 1531,
"kategori": "Berbahaya",
"parameterKritis": "PM10",
"lat": -1.1311999559402466,
"lon": 113.85700225830078
},
{
"rank": 2,
"id": "KABUPATEN_KOTAWARINGINTIMUR",
"nama": "Kabupaten Kotawaringin Timur",
"kota": "Kabupaten Kotawaringin Timur",
"provinsi": "Kalimantan Tengah",
"ispu": 1308,
"kategori": "Berbahaya",
"parameterKritis": "PM10",
"lat": -2.535559892654419,
"lon": 112.87799835205078
},
{
"rank": 3,
"id": "PALANGKARAYA",
"nama": "Palangkaraya Jekan Raya",
"kota": "Palangkaraya",
"provinsi": "Kalimantan Tengah",
"ispu": 1299,
"kategori": "Berbahaya",
"parameterKritis": "PM2.5",
"lat": -2.2215399742126465,
"lon": 113.86000061035156
},
{
"rank": 4,
"id": "KABUPATEN_BARITOSELATAN",
"nama": "Kabupaten Barito Selatan Sanggu",
"kota": "Kabupaten Barito Selatan",
"provinsi": "Kalimantan Tengah",
"ispu": 1000,
"kategori": "Berbahaya",
"parameterKritis": "PM10",
"lat": -1.6793400049209595,
"lon": 114.90799713134766
},
{
"rank": 5,
"id": "KABUPATEN_MURUNG_RAYA",
"nama": "Kabupaten Murung Raya",
"kota": "Kabupaten Murung Raya",
"provinsi": "Kalimantan Tengah",
"ispu": 790,
"kategori": "Berbahaya",
"parameterKritis": "PM2.5",
"lat": -0.6390770077705383,
"lon": 114.53700256347656
},
{
"rank": 6,
"id": "PALEMBANG",
"nama": "Palembang Bukit Kecil",
"kota": "Palembang",
"provinsi": "Sumatera Selatan",
"ispu": 717,
"kategori": "Berbahaya",
"parameterKritis": "PM10",
"lat": -2.9803900718688965,
"lon": 104.74700164794922
},
{
"rank": 7,
"id": "PONTIANAK",
"nama": "Pontianak Tenggara",
"kota": "Pontianak",
"provinsi": "Kalimantan Barat",
"ispu": 575,
"kategori": "Berbahaya",
"parameterKritis": "PM10",
"lat": -0.03109000064432621,
"lon": 109.3219985961914
},
{
"rank": 8,
"id": "KABUPATEN_BARITO_KUALA",
"nama": "Kabupaten Barito Kuala",
"kota": "Kabupaten Barito Kuala",
"provinsi": "Kalimantan Selatan",
"ispu": 549,
"kategori": "Berbahaya",
"parameterKritis": "PM10",
"lat": -2.9836199283599854,
"lon": 114.73300170898438
},
{
"rank": 9,
"id": "KABUPATEN_KAMPAR",
"nama": "Kabupaten Kampar Bangkinang",
"kota": "Kabupaten Kampar",
"provinsi": "Riau",
"ispu": 429,
"kategori": "Berbahaya",
"parameterKritis": "PM2.5",
"lat": 0.3249180018901825,
"lon": 101.0250015258789
},
{
"rank": 10,
"id": "MOBILE_AQMS_BPBD_KALTENG",
"nama": "Mobile AQMS BPBD Kalteng...",
"kota": "Palangkaraya",
"provinsi": "Kalimantan Tengah",
"ispu": 416,
"kategori": "Berbahaya",
"parameterKritis": "PM2.5",
"lat": -2.1697499752044678,
"lon": 113.86100006103516
}
],
"topBersih": [
{
"rank": 1,
"id": "KENDARI",
"nama": "Kendari Mandonga",
"kota": "Kendari",
"provinsi": "Sulawesi Tenggara",
"ispu": 2,
"kategori": "Baik",
"parameterKritis": "PM2.5",
"lat": -3.9755001068115234,
"lon": 122.5199966430664
},
{
"rank": 2,
"id": "KABUPATEN_BANYUWANGI",
"nama": "Kabupaten Banyuwangi Mojopanggung",
"kota": "Kabupaten Banyuwangi",
"provinsi": "Jawa Timur",
"ispu": 11,
"kategori": "Baik",
"parameterKritis": "O3",
"lat": -8.211389541625977,
"lon": 114.35099792480469
},
{
"rank": 3,
"id": "KABUPATEN_ACEH_TENGAH",
"nama": "Kabupaten Aceh Tengah",
"kota": "Kabupaten Aceh Tengah",
"provinsi": "Aceh",
"ispu": 11,
"kategori": "Baik",
"parameterKritis": "PM2.5",
"lat": 4.624090194702148,
"lon": 96.84210205078125
},
{
"rank": 4,
"id": "KABUPATEN_TANGERANGCG",
"nama": "Kabupaten Tangerang Curug",
"kota": "Tangerang",
"provinsi": "Banten",
"ispu": 21,
"kategori": "Baik",
"parameterKritis": "SO2",
"lat": -6.2477898597717285,
"lon": 106.56300354003906
},
{
"rank": 5,
"id": "KABUPATEN_BOGOR",
"nama": "Kabupaten Bogor Tegar Beriman",
"kota": "Kabupaten Bogor",
"provinsi": "Jawa Barat",
"ispu": 22,
"kategori": "Baik",
"parameterKritis": "O3",
"lat": -6.481369972229004,
"lon": 106.8290023803711
},
{
"rank": 6,
"id": "DLH_MEDAN_02",
"nama": "DLH MEDAN 02",
"kota": "Medan",
"provinsi": "Sumatera Utara",
"ispu": 22,
"kategori": "Baik",
"parameterKritis": "SO2,CO",
"lat": 3.652869939804077,
"lon": 98.66580200195312
},
{
"rank": 7,
"id": "TERNATE",
"nama": "Ternate Marikurubu",
"kota": "Ternate",
"provinsi": "Maluku Utara",
"ispu": 24,
"kategori": "Baik",
"parameterKritis": "PM2.5",
"lat": 0.7872629761695862,
"lon": 127.3740005493164
},
{
"rank": 8,
"id": "KABUPATEN_LUMAJANG",
"nama": "Kabupaten Lumajang Ditrotunan",
"kota": "Kabupaten Lumajang",
"provinsi": "Jawa Timur",
"ispu": 26,
"kategori": "Baik",
"parameterKritis": "O3",
"lat": -8.135390281677246,
"lon": 113.2239990234375
},
{
"rank": 9,
"id": "MALANG",
"nama": "Malang Taman Riau",
"kota": "Malang",
"provinsi": "Jawa Timur",
"ispu": 27,
"kategori": "Baik",
"parameterKritis": "PM2.5",
"lat": -7.991559982299805,
"lon": 112.62899780273438
},
{
"rank": 10,
"id": "KOTA_MOJOKERTO",
"nama": "Kota Mojokerto Magersari",
"kota": "Mojokerto",
"provinsi": "Jawa Timur",
"ispu": 28,
"kategori": "Baik",
"parameterKritis": "PM2.5",
"lat": -7.4712300300598145,
"lon": 112.44100189208984
}
],
"source": "https://ispu.kemenlh.go.id",
"scrapedAt": "2026-10-06T13:27:00.440Z"
}
$ node ispu.js search --q=jakarta
{
"query": "jakarta",
"total": 7,
"updated": "2026-10-06 20:00:00",
"stations": [
{
"id": "DKI1",
"nama": "DKI 1 Bundaran HI",
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kelurahan": null,
"kecamatan": null,
"alamat": "Jl. M.H. Thamrin No.1, RT.1/RW.5, Menteng, Kec. Menteng, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10310",
"kawasan": "Jawa",
"lat": -6.194789886474609,
"lon": 106.822998046875,
"ispu": 112,
"kategori": "Tidak Sehat",
"kategoriDetail": {
"code": 3,
"name": "Tidak Sehat",
"nameEn": "Unhealthy",
"color": "#ffcc00",
"range": "101-200",
"advice": "Kurangi aktivitas di luar, terutama kelompok sensitif."
},
"parameterKritis": "PM2.5",
"tipe": "INTEGRASI",
"timezone": "WIB",
"waktu": "2026-10-06 18:00:00",
"waktuText": "06 Oktober 2026 18:00:00",
"maintenance": false,
"autoValidation": false,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 0,
"co": 0,
"o3": 0,
"no2": 0,
"hc": 0
},
"ispuPerParameter": {
"pm10": 70,
"pm25": 112,
"so2": 44,
"co": 13,
"o3": 16,
"no2": 75,
"hc": 0
}
},
{
"id": "DKI4",
"nama": "DKI 4 Lubang Buaya",
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kelurahan": null,
"kecamatan": null,
"alamat": "4, Jl. Monumen Pancasila Sakti No.1, RT.4/RW.9, Lubang Buaya, Kec. Cipayung, Kota Jakarta Timur, Daerah Khusus Ibukota Jakarta 13810",
"kawasan": "Jawa",
"lat": -6.290359973907471,
"lon": 106.90499877929688,
"ispu": 107,
"kategori": "Tidak Sehat",
"kategoriDetail": {
"code": 3,
"name": "Tidak Sehat",
"nameEn": "Unhealthy",
"color": "#ffcc00",
"range": "101-200",
"advice": "Kurangi aktivitas di luar, terutama kelompok sensitif."
},
"parameterKritis": "PM2.5",
"tipe": "INTEGRASI",
"timezone": "WIB",
"waktu": "2026-10-06 18:00:00",
"waktuText": "06 Oktober 2026 18:00:00",
"maintenance": false,
"autoValidation": false,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 0,
"co": 0,
"o3": 0,
"no2": 0,
"hc": 0
},
"ispuPerParameter": {
"pm10": 69,
"pm25": 107,
"so2": 26,
"co": 10,
"o3": 23,
"no2": 21,
"hc": 0
}
},
{
"id": "DKI5",
"nama": "DKI 5 Kebon Jeruk",
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kelurahan": null,
"kecamatan": null,
"alamat": "No.5-, Jl. Jeruk Kuning Blok J-7 No.6, RT.2/RW.12, Srengseng, Kec. Kembangan, Kota Jakarta Barat, Daerah Khusus Ibukota Jakarta 11630",
"kawasan": "Jawa",
"lat": -6.207409858703613,
"lon": 106.75,
"ispu": 105,
"kategori": "Tidak Sehat",
"kategoriDetail": {
"code": 3,
"name": "Tidak Sehat",
"nameEn": "Unhealthy",
"color": "#ffcc00",
"range": "101-200",
"advice": "Kurangi aktivitas di luar, terutama kelompok sensitif."
},
"parameterKritis": "PM2.5",
"tipe": "INTEGRASI",
"timezone": "WIB",
"waktu": "2026-10-06 18:00:00",
"waktuText": "06 Oktober 2026 18:00:00",
"maintenance": false,
"autoValidation": false,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 0,
"co": 0,
"o3": 0,
"no2": 0,
"hc": 0
},
"ispuPerParameter": {
"pm10": 57,
"pm25": 105,
"so2": 35,
"co": 10,
"o3": 57,
"no2": 8,
"hc": 0
}
},
{
"id": "DKI2",
"nama": "DKI 2 Kelapa Gading",
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kelurahan": null,
"kecamatan": null,
"alamat": "Jl. Boulevard Raya, RT.11/RW.18, Klp. Gading Tim., Kec. Klp. Gading, Kota Jkt Utara, Daerah Khusus Ibukota Jakarta 14240",
"kawasan": "Jawa",
"lat": -6.15880012512207,
"lon": 106.90399932861328,
"ispu": 88,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "PM2.5",
"tipe": "INTEGRASI",
"timezone": "WIB",
"waktu": "2026-10-06 18:00:00",
"waktuText": "06 Oktober 2026 18:00:00",
"maintenance": false,
"autoValidation": false,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 0,
"co": 0,
"o3": 0,
"no2": 0,
"hc": 0
},
"ispuPerParameter": {
"pm10": 0,
"pm25": 88,
"so2": 15,
"co": 7,
"o3": 28,
"no2": 23,
"hc": 0
}
},
{
"id": "DKI3",
"nama": "DKI 3 Jagakarsa",
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kelurahan": null,
"kecamatan": null,
"alamat": "Jl. Purwa II No.9, RT.6/RW.6, Cipedak, Kec. Jagakarsa, Kota Jakarta Selatan, Daerah Khusus Ibukota Jakarta 12630",
"kawasan": "Jawa",
"lat": -6.3577799797058105,
"lon": 106.8030014038086,
"ispu": 86,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "PM2.5",
"tipe": "INTEGRASI",
"timezone": "WIB",
"waktu": "2026-10-06 18:00:00",
"waktuText": "06 Oktober 2026 18:00:00",
"maintenance": false,
"autoValidation": false,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 0,
"co": 0,
"o3": 0,
"no2": 0,
"hc": 0
},
"ispuPerParameter": {
"pm10": 46,
"pm25": 86,
"so2": 44,
"co": 11,
"o3": 25,
"no2": 25,
"hc": 0
}
},
{
"id": "JAKARTA",
"nama": "Jakarta GBK",
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kelurahan": null,
"kecamatan": "Tanah Abang",
"alamat": "Lapangan ABC, Pintu 10 Gelora Bung Karno, Kota Jakarta Pusat",
"kawasan": "Jawa",
"lat": -6.2154998779296875,
"lon": 106.8030014038086,
"ispu": 55,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "PM2.5",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 28.79,
"pm25": 19.23,
"so2": 1.04,
"co": 47.1,
"o3": 116.48,
"no2": 43.27,
"hc": 0.69
},
"ispuPerParameter": {
"pm10": 57,
"pm25": 55,
"so2": 1,
"co": 1,
"o3": 77,
"no2": 72,
"hc": 0
}
},
{
"id": "KBN_MARUNDA",
"nama": "KBN Marunda Jakarta Utara",
"kota": "Jakarta Utara",
"provinsi": "DKI Jakarta",
"kelurahan": "Marunda",
"kecamatan": "Cilincing",
"alamat": "SBU Kawasan Marunda PT Kawasan Berikat Nusantara, Jl. Lampung No.1, RT.6/RW.2, Marunda, Kec. Cilincing, Jakarta Utara",
"kawasan": "Jawa",
"lat": -6.108980178833008,
"lon": 106.94400024414062,
"ispu": 47,
"kategori": "Baik",
"kategoriDetail": {
"code": 1,
"name": "Baik",
"nameEn": "Good",
"color": "#00e400",
"range": "0-50",
"advice": "Tidak ada dampak kesehatan."
},
"parameterKritis": "SO2",
"tipe": "INTEGRASI",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 19.55,
"pm25": 11.42,
"so2": 48.46,
"co": 866.06,
"o3": 100.17,
"no2": 0,
"hc": 0
},
"ispuPerParameter": {
"pm10": 47,
"pm25": 38,
"so2": 53,
"co": 22,
"o3": 66,
"no2": 0,
"hc": 0
}
}
],
"scrapedAt": "2026-10-06T13:27:02.673Z"
}
$ node ispu.js ranking --provinsi="Jawa Barat"
{
"total": 9,
"limit": 20,
"filter": {
"provinsi": "Jawa Barat",
"kota": null
},
"updated": "2026-10-06 20:00:00",
"stations": [
{
"rank": 1,
"id": "KABUPATEN_SUKABUMI",
"nama": "Kabupaten Sukabumi Pelabuhanratu",
"kota": "Kabupaten Sukabumi",
"provinsi": "Jawa Barat",
"kelurahan": "Pelabuhanratu",
"kecamatan": "Pelabuhanratu",
"alamat": "Taman Kota Cangehgar Palabuhanratu, Jl. Jend. Ahmad Yani, Kelurahan Palabuhanratu, Kecamatan Palabuh, Kabupaten Sukabumi, Jawa Barat",
"kawasan": "Jawa",
"lat": -6.992879867553711,
"lon": 106.55899810791016,
"ispu": 86,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "PM2.5",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 104.81,
"pm25": 44.42,
"so2": 0.01,
"co": 0.01,
"o3": 0.01,
"no2": 0.01,
"hc": 0.01
},
"ispuPerParameter": {
"pm10": 130,
"pm25": 87,
"so2": 0,
"co": 0,
"o3": 0,
"no2": 0,
"hc": 0
}
},
{
"rank": 2,
"id": "KABUPATEN_BOGORLL",
"nama": "Kabupaten Bogor Leuwiliang",
"kota": "Bogor",
"provinsi": "Jawa Barat",
"kelurahan": "Leuwiliang",
"kecamatan": "Leuwiliang",
"alamat": "Kantor Kecamatan Leuwiliang,\r\nKec. Leuwiliang, Kabupaten\r\nBogor, Jawa Barat 16640\r\n",
"kawasan": "Jawa",
"lat": -6.576610088348389,
"lon": 106.63700103759766,
"ispu": 83,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "O3",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 43.45,
"pm25": 24.15,
"so2": 19.75,
"co": 1666.26,
"o3": 195.64,
"no2": 8.96,
"hc": 0.01
},
"ispuPerParameter": {
"pm10": 71,
"pm25": 61,
"so2": 23,
"co": 42,
"o3": 143,
"no2": 17,
"hc": 0
}
},
{
"rank": 3,
"id": "BEKASI",
"nama": "Bekasi Kayuringin",
"kota": "Bekasi",
"provinsi": "Jawa Barat",
"kelurahan": "Kayuringin Jaya",
"kecamatan": "Bekasi Selatan",
"alamat": "Stadium Patriot Candrabaga, Kayuringin Jaya, Bekasi Selatan, Kota Bekasi, Jawa Barat 17144, Kota Bekasi, Propinsi Jawa Barat",
"kawasan": "Jawa",
"lat": -6.237450122833252,
"lon": 106.99299621582031,
"ispu": 79,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "PM2.5",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 58.38,
"pm25": 38.27,
"so2": 25.62,
"co": 425.98,
"o3": 15.48,
"no2": 106.39,
"hc": 7.25
},
"ispuPerParameter": {
"pm10": 85,
"pm25": 79,
"so2": 30,
"co": 11,
"o3": 10,
"no2": 114,
"hc": 5
}
},
{
"rank": 4,
"id": "BOGOR",
"nama": "Kota Bogor Tanah Sereal",
"kota": "Bogor",
"provinsi": "Jawa Barat",
"kelurahan": null,
"kecamatan": null,
"alamat": "Jl. Pemuda No.25, RT.01/RW.06, Tanah Sareal, Kec. Tanah Sereal, Kota Bogor, Jawa Barat 16161",
"kawasan": "Jawa",
"lat": -6.572700023651123,
"lon": 106.79199981689453,
"ispu": 68,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "SO2",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 97.23,
"co": 208.29,
"o3": 56.79,
"no2": 14.5,
"hc": null
},
"ispuPerParameter": {
"pm10": 0,
"pm25": 0,
"so2": 75,
"co": 5,
"o3": 37,
"no2": 28,
"hc": null
}
},
{
"rank": 5,
"id": "KABUPATEN_INDRAMAYU",
"nama": "Kabupaten Indramayu Margadadi",
"kota": "Indramayu",
"provinsi": "Jawa Barat",
"kelurahan": "Margadadi",
"kecamatan": "Indramayu",
"alamat": "Lingkungan Masjid Agung\r\nIndramayu\r\nDekat Jl. Letjen Sutoyo,\r\nMargadadi, Kec. Indramayu,\r\nKabupaten Indramayu, Jawa\r\nBarat 45212\r\n",
"kawasan": "Jawa",
"lat": -6.327040195465088,
"lon": 108.3219985961914,
"ispu": 58,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "PM2.5",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 28.2,
"pm25": 22.07,
"so2": 13.75,
"co": 3296.76,
"o3": 43.34,
"no2": 14.15,
"hc": 1.04
},
"ispuPerParameter": {
"pm10": 57,
"pm25": 59,
"so2": 16,
"co": 82,
"o3": 29,
"no2": 27,
"hc": 1
}
},
{
"rank": 6,
"id": "KABUPATEN_BANDUNGBB",
"nama": "Kabupaten Bandung Barat Saguling",
"kota": "Bandung Barat",
"provinsi": "Jawa Barat",
"kelurahan": "Bojonghaleuang",
"kecamatan": "Saguling",
"alamat": "Masjid As Sidiq, Kelurahan\r\nBojonghaleuang, Kec. Saguling,\r\nKabupaten Bandung Barat 40561",
"kawasan": "Jawa",
"lat": -6.839240074157715,
"lon": 107.51100158691406,
"ispu": 57,
"kategori": "Sedang",
"kategoriDetail": {
"code": 2,
"name": "Sedang",
"nameEn": "Moderate",
"color": "#0066ff",
"range": "51-100",
"advice": "Masih dapat diterima; kelompok sensitif waspada."
},
"parameterKritis": "PM2.5",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 39.44,
"pm25": 21.28,
"so2": 8.1,
"co": 2306.64,
"o3": 43.12,
"no2": 55.34,
"hc": 4.29
},
"ispuPerParameter": {
"pm10": 67,
"pm25": 58,
"so2": 9,
"co": 58,
"o3": 28,
"no2": 88,
"hc": 3
}
},
{
"rank": 7,
"id": "KABUPATEN_PURWAKARTA",
"nama": "Kabupaten Purwakarta Purnawarman",
"kota": "Kabupaten Purwakarta",
"provinsi": "Jawa Barat",
"kelurahan": "Purwamekar",
"kecamatan": "Purwakarta",
"alamat": "Jl. Purnawarman Timur, Sindangkasih, Kec. Purwakarta",
"kawasan": "Jawa",
"lat": -6.564449787139893,
"lon": 107.44599914550781,
"ispu": 48,
"kategori": "Baik",
"kategoriDetail": {
"code": 1,
"name": "Baik",
"nameEn": "Good",
"color": "#00e400",
"range": "0-50",
"advice": "Tidak ada dampak kesehatan."
},
"parameterKritis": "PM2.5",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 18.71,
"pm25": 14.94,
"so2": 9.26,
"co": 243.93,
"o3": 30.65,
"no2": 9.44,
"hc": 5.23
},
"ispuPerParameter": {
"pm10": 45,
"pm25": 50,
"so2": 11,
"co": 6,
"o3": 20,
"no2": 18,
"hc": 4
}
},
{
"rank": 8,
"id": "KABUPATEN_BEKASI",
"nama": "Kabupaten Bekasi Sukamahi",
"kota": "Kabupaten Bekasi",
"provinsi": "Jawa Barat",
"kelurahan": null,
"kecamatan": null,
"alamat": " Sukamahi, Kec. Cikarang Pusat, Kabupaten Bekasi, Jawa Barat 17530",
"kawasan": "Jawa",
"lat": -6.363800048828125,
"lon": 107.1719970703125,
"ispu": 40,
"kategori": "Baik",
"kategoriDetail": {
"code": 1,
"name": "Baik",
"nameEn": "Good",
"color": "#00e400",
"range": "0-50",
"advice": "Tidak ada dampak kesehatan."
},
"parameterKritis": "PM10",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 39.83,
"pm25": 7.77,
"so2": 4,
"co": 1787.73,
"o3": 87.29,
"no2": 6.12,
"hc": null
},
"ispuPerParameter": {
"pm10": 67,
"pm25": 26,
"so2": 5,
"co": 45,
"o3": 58,
"no2": 12,
"hc": null
}
},
{
"rank": 9,
"id": "KABUPATEN_BOGOR",
"nama": "Kabupaten Bogor Tegar Beriman",
"kota": "Kabupaten Bogor",
"provinsi": "Jawa Barat",
"kelurahan": null,
"kecamatan": null,
"alamat": " Jl. Tegar Beriman, Tengah, Kec. Cibinong, Kabupaten Bogor, Jawa Barat 16914",
"kawasan": "Jawa",
"lat": -6.481369972229004,
"lon": 106.8290023803711,
"ispu": 22,
"kategori": "Baik",
"kategoriDetail": {
"code": 1,
"name": "Baik",
"nameEn": "Good",
"color": "#00e400",
"range": "0-50",
"advice": "Tidak ada dampak kesehatan."
},
"parameterKritis": "O3",
"tipe": "KLH/BPLH",
"timezone": "WIB",
"waktu": "2026-10-06 20:00:00",
"waktuText": "06 Oktober 2026 20:00:00",
"maintenance": false,
"autoValidation": true,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 3,
"co": 1298,
"o3": 54,
"no2": null,
"hc": 1
},
"ispuPerParameter": {
"pm10": 0,
"pm25": 0,
"so2": 3,
"co": 32,
"o3": 36,
"no2": null,
"hc": 1
}
}
],
"scrapedAt": "2026-10-06T13:27:07.736Z"
}
$ node ispu.js station --id=DKI1
{
"id": "DKI1",
"nama": "DKI 1 Bundaran HI",
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kelurahan": null,
"kecamatan": null,
"alamat": "Jl. M.H. Thamrin No.1, RT.1/RW.5, Menteng, Kec. Menteng, Kota Jakarta Pusat, Daerah Khusus Ibukota Jakarta 10310",
"kawasan": "Jawa",
"lat": -6.194789886474609,
"lon": 106.822998046875,
"ispu": 112,
"kategori": "Tidak Sehat",
"kategoriDetail": {
"code": 3,
"name": "Tidak Sehat",
"nameEn": "Unhealthy",
"color": "#ffcc00",
"range": "101-200",
"advice": "Kurangi aktivitas di luar, terutama kelompok sensitif."
},
"parameterKritis": "PM2.5",
"tipe": "INTEGRASI",
"timezone": "WIB",
"waktu": "06 Oktober 2026 18:00:00",
"waktuText": null,
"maintenance": false,
"autoValidation": false,
"konsentrasi": {
"pm10": 0,
"pm25": 0,
"so2": 0,
"co": 0,
"o3": 0,
"no2": 0,
"hc": 0
},
"ispuPerParameter": {
"pm10": 70,
"pm25": 112,
"so2": 44,
"co": 13,
"o3": 16,
"no2": 75,
"hc": 0
},
"histori24jam": {
"waktu": [
"2026-10-05 17:00:00",
"2026-10-05 18:00:00",
"2026-10-05 19:00:00",
"2026-10-05 20:00:00",
"2026-10-05 21:00:00",
"2026-10-05 22:00:00",
"2026-10-05 23:00:00",
"2026-10-06 00:00:00",
"2026-10-06 01:00:00",
"2026-10-06 02:00:00",
"2026-10-06 03:00:00",
"2026-10-06 04:00:00",
"2026-10-06 05:00:00",
"2026-10-06 06:00:00",
"2026-10-06 08:00:00",
"2026-10-06 09:00:00",
"2026-10-06 10:00:00",
"2026-10-06 12:00:00",
"2026-10-06 13:00:00",
"2026-10-06 14:00:00",
"2026-10-06 15:00:00",
"2026-10-06 16:00:00",
"2026-10-06 17:00:00",
"2026-10-06 18:00:00"
],
"pm25": [
{
"nilai": 106,
"warna": "#cccc00"
},
{
"nilai": 106,
"warna": "#cccc00"
},
{
"nilai": 106,
"warna": "#cccc00"
},
{
"nilai": 106,
"warna": "#cccc00"
},
{
"nilai": 106,
"warna": "#cccc00"
},
{
"nilai": 105,
"warna": "#cccc00"
},
{
"nilai": 105,
"warna": "#cccc00"
},
{
"nilai": 106,
"warna": "#cccc00"
},
{
"nilai": 109,
"warna": "#cccc00"
},
{
"nilai": 110,
"warna": "#cccc00"
},
{
"nilai": 111,
"warna": "#cccc00"
},
{
"nilai": 111,
"warna": "#cccc00"
},
{
"nilai": 111,
"warna": "#cccc00"
},
{
"nilai": 110,
"warna": "#cccc00"
},
{
"nilai": 110,
"warna": "#cccc00"
},
{
"nilai": 111,
"warna": "#cccc00"
},
{
"nilai": 111,
"warna": "#cccc00"
},
{
"nilai": 112,
"warna": "#cccc00"
},
{
"nilai": 112,
"warna": "#cccc00"
},
{
"nilai": 112,
"warna": "#cccc00"
},
{
"nilai": 112,
"warna": "#cccc00"
},
{
"nilai": 111,
"warna": "#cccc00"
},
{
"nilai": 111,
"warna": "#cccc00"
},
{
"nilai": 112,
"warna": "#cccc00"
}
],
"pm10": [
{
"nilai": 65,
"warna": "#0000cc"
},
{
"nilai": 65,
"warna": "#0000cc"
},
{
"nilai": 65,
"warna": "#0000cc"
},
{
"nilai": 64,
"warna": "#0000cc"
},
{
"nilai": 64,
"warna": "#0000cc"
},
{
"nilai": 64,
"warna": "#0000cc"
},
{
"nilai": 64,
"warna": "#0000cc"
},
{
"nilai": 64,
"warna": "#0000cc"
},
{
"nilai": 64,
"warna": "#0000cc"
},
{
"nilai": 65,
"warna": "#0000cc"
},
{
"nilai": 65,
"warna": "#0000cc"
},
{
"nilai": 66,
"warna": "#0000cc"
},
{
"nilai": 66,
"warna": "#0000cc"
},
{
"nilai": 67,
"warna": "#0000cc"
},
{
"nilai": 68,
"warna": "#0000cc"
},
{
"nilai": 68,
"warna": "#0000cc"
},
{
"nilai": 68,
"warna": "#0000cc"
},
{
"nilai": 68,
"warna": "#0000cc"
},
{
"nilai": 68,
"warna": "#0000cc"
},
{
"nilai": 68,
"warna": "#0000cc"
},
{
"nilai": 68,
"warna": "#0000cc"
},
{
"nilai": 69,
"warna": "#0000cc"
},
{
"nilai": 69,
"warna": "#0000cc"
},
{
"nilai": 70,
"warna": "#0000cc"
}
]
}
}
$ node ispu.js list-provinsi
{
"total": 27,
"updated": "2026-10-06 20:00:00",
"provinsi": [
{
"provinsi": "Kalimantan Tengah",
"jumlahStasiun": 7,
"ispuRataRata": 935,
"stations": [
{
"id": "KABUPATEN_GUNUNG_MAS",
"nama": "Kabupaten Gunung Mas",
"kota": "Kabupaten Gunung Mas",
"ispu": 1531,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_KOTAWARINGINTIMUR",
"nama": "Kabupaten Kotawaringin Timur",
"kota": "Kabupaten Kotawaringin Timur",
"ispu": 1308,
"kategori": "Berbahaya"
},
{
"id": "PALANGKARAYA",
"nama": "Palangkaraya Jekan Raya",
"kota": "Palangkaraya",
"ispu": 1299,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_BARITOSELATAN",
"nama": "Kabupaten Barito Selatan Sanggu",
"kota": "Kabupaten Barito Selatan",
"ispu": 1000,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_MURUNG_RAYA",
"nama": "Kabupaten Murung Raya",
"kota": "Kabupaten Murung Raya",
"ispu": 790,
"kategori": "Berbahaya"
},
{
"id": "MOBILE_AQMS_BPBD_KALTENG",
"nama": "Mobile AQMS BPBD Kalteng...",
"kota": "Palangkaraya",
"ispu": 416,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_KAPUAS",
"nama": "Kabupaten Kapuas",
"kota": "Kabupaten Kapuas",
"ispu": 201,
"kategori": "Sangat Tidak Sehat"
}
]
},
{
"provinsi": "Jambi",
"jumlahStasiun": 4,
"ispuRataRata": 324,
"stations": [
{
"id": "KABUPATEN_MUARO_JAMBI",
"nama": "Kabupaten Muaro Jambi",
"kota": "Kabupaten Muaro Jambi",
"ispu": 411,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_TEBO",
"nama": "Kabupaten Tebo",
"kota": "Kabupaten Tebo",
"ispu": 343,
"kategori": "Berbahaya"
},
{
"id": "JAMBI",
"nama": "Jambi Paal Lima",
"kota": "Jambi",
"ispu": 342,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_TANJUNGJABUNGTIMUR",
"nama": "Kabupaten Tanjabtim Muara Sabak",
"kota": "Kabupaten Tanjung Jabung Timur",
"ispu": 198,
"kategori": "Tidak Sehat"
}
]
},
{
"provinsi": "Kalimantan Selatan",
"jumlahStasiun": 5,
"ispuRataRata": 272,
"stations": [
{
"id": "KABUPATEN_BARITO_KUALA",
"nama": "Kabupaten Barito Kuala",
"kota": "Kabupaten Barito Kuala",
"ispu": 549,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_TABALONG",
"nama": "Kabupaten Tabalong",
"kota": "Kabupaten Tabalong",
"ispu": 366,
"kategori": "Berbahaya"
},
{
"id": "BANJARMASIN",
"nama": "Banjarmasin Kayu Tangi",
"kota": "Banjarmasin",
"ispu": 175,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_HSS",
"nama": "Kabupaten Hulu Sungai Selatan",
"kota": "Kabupaten Hulu Sungai Selatan",
"ispu": 174,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_BANJAR",
"nama": "Kabupaten Banjar",
"kota": "Kabupaten Banjar",
"ispu": 97,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Riau",
"jumlahStasiun": 9,
"ispuRataRata": 217,
"stations": [
{
"id": "KABUPATEN_KAMPAR",
"nama": "Kabupaten Kampar Bangkinang",
"kota": "Kabupaten Kampar",
"ispu": 429,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_SIAK_BUNGARAYA",
"nama": "Kabupaten Siak Bunga Raya",
"kota": "Kabupaten Siak",
"ispu": 326,
"kategori": "Berbahaya"
},
{
"id": "PEKANBARU",
"nama": "Pekanbaru Tenayan Raya",
"kota": "Pekanbaru",
"ispu": 247,
"kategori": "Sangat Tidak Sehat"
},
{
"id": "KABUPATEN_INDRAGIRIHILIR",
"nama": "Kabupaten Indragiri Hilir",
"kota": "Kabupaten Indragiri Hilir",
"ispu": 242,
"kategori": "Sangat Tidak Sehat"
},
{
"id": "KABUPATEN_BENGKALIS",
"nama": "Kabupaten Bengkalis Sanggoro",
"kota": "Kabupaten Bengkalis",
"ispu": 239,
"kategori": "Sangat Tidak Sehat"
},
{
"id": "KABUPATEN_SIAK",
"nama": "Siak",
"kota": "Siak",
"ispu": 182,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_ROKANHILIR",
"nama": "Kabupaten Rokan Hilir Bangko",
"kota": "Rokan Hilir",
"ispu": 158,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_PELALAWAN",
"nama": "Kabupaten Pelalawan ",
"kota": "Kabupaten Pelalawan",
"ispu": 74,
"kategori": "Sedang"
},
{
"id": "DUMAI",
"nama": "Dumai Bukit Kapur",
"kota": "Dumai",
"ispu": 59,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Sumatera Selatan",
"jumlahStasiun": 9,
"ispuRataRata": 212,
"stations": [
{
"id": "PALEMBANG",
"nama": "Palembang Bukit Kecil",
"kota": "Palembang",
"ispu": 717,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_PALI",
"nama": "Kabupaten Penukal Abab Lematang...",
"kota": "Kabupaten Abab Lematang Ilir",
"ispu": 290,
"kategori": "Sangat Tidak Sehat"
},
{
"id": "KOTA_PRABUMULIH",
"nama": "Kota Prabumulih",
"kota": "Prabumulih",
"ispu": 204,
"kategori": "Sangat Tidak Sehat"
},
{
"id": "KABUPATEN_MURA",
"nama": "Kabupaten Musi Rawas",
"kota": "Kabupaten Musi Rawas",
"ispu": 181,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_MUSI_BANYUASIN",
"nama": "Kabupaten Musi Banyuasin Serasan...",
"kota": "Kabupaten Musi Banyuasin",
"ispu": 157,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_OGAN_ILIR",
"nama": "Kabupaten Ogan Ilir",
"kota": "Kabupaten Ogan Ilir",
"ispu": 140,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_LAHAT",
"nama": "Kabupaten Lahat",
"kota": "Kabupaten Lahat",
"ispu": 92,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_OKU_SELATAN",
"nama": "Kabupaten OKU Selatan Pelangki",
"kota": "Kabupaten Oku Selatan",
"ispu": 69,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_MUARA_ENIM",
"nama": "Kabupaten Muara Enim",
"kota": "Kabupaten Muara Enim",
"ispu": 60,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Papua Tengah",
"jumlahStasiun": 1,
"ispuRataRata": 176,
"stations": [
{
"id": "KABUPATEN_NABIRE",
"nama": "Kabupaten Nabire",
"kota": "Kabupaten Nabire",
"ispu": 176,
"kategori": "Tidak Sehat"
}
]
},
{
"provinsi": "Kalimantan Barat",
"jumlahStasiun": 6,
"ispuRataRata": 166,
"stations": [
{
"id": "PONTIANAK",
"nama": "Pontianak Tenggara",
"kota": "Pontianak",
"ispu": 575,
"kategori": "Berbahaya"
},
{
"id": "KABUPATEN_SINTANG",
"nama": "Kabupaten Sintang Baning Kota",
"kota": "Kabupaten Sintang",
"ispu": 117,
"kategori": "Tidak Sehat"
},
{
"id": "KABUPATEN_KUBU_RAYA",
"nama": "Kabupaten Kubu Raya Kapuas",
"kota": "Kabupaten Kubu Raya",
"ispu": 93,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_SEKADAU",
"nama": "Kabupaten Sekadau Sekadau Hilir",
"kota": "Kabupaten Sekadau",
"ispu": 88,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_BENGKAYANG",
"nama": "Kabupaten Bengkayang Sebalo",
"kota": "Kabupaten Bengkayang",
"ispu": 70,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_SAMBAS",
"nama": "Kabupaten Sambas Dalam Kaum",
"kota": "Kabupaten Sambas",
"ispu": 50,
"kategori": "Baik"
}
]
},
{
"provinsi": "Kalimantan Timur",
"jumlahStasiun": 11,
"ispuRataRata": 114,
"stations": [
{
"id": "KABUPATEN_KUTAIBARAT",
"nama": "Kabupaten Kutai Barat Sendawar",
"kota": "Kabupaten Kutai Barat",
"ispu": 414,
"kategori": "Berbahaya"
},
{
"id": "WT_KPIKN_IKN",
"nama": "Wonotirto KPIKN IKN",
"kota": "Kabupaten Kutai Kartanegara",
"ispu": 136,
"kategori": "Tidak Sehat"
},
{
"id": "MJT_KPIKN_IKN",
"nama": "Muara Jawa Tengah KPIKN...",
"kota": "Kabupaten Kutai Kartanegara",
"ispu": 110,
"kategori": "Tidak Sehat"
},
{
"id": "SNP_KPIKN_IKN",
"nama": "Sanipah KPIKN IKN",
"kota": "Kabupaten Kutai Kartanegara",
"ispu": 106,
"kategori": "Tidak Sehat"
},
{
"id": "PB_KIPP_IKN",
"nama": "Plaza Bhinneka KIPP IKN",
"kota": "Kabupaten Penajam Paser Utara",
"ispu": 80,
"kategori": "Sedang"
},
{
"id": "KOTA_BONTANG",
"nama": "Kota Bontang Tanjung Laut",
"kota": "Kota Bontang",
"ispu": 75,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_PENAJAM_PASER_UTARA",
"nama": "Kabutapen Penajam Paser Utara...",
"kota": "Kabupaten Penajam Paser Utara",
"ispu": 74,
"kategori": "Sedang"
},
{
"id": "SAMARINDA",
"nama": "Samarinda Taman Segiri",
"kota": "Samarinda",
"ispu": 74,
"kategori": "Sedang"
},
{
"id": "BALIKPAPAN_BB",
"nama": "BALIKPAPAN BARU",
"kota": "BALIKPAPAN",
"ispu": 69,
"kategori": "Sedang"
},
{
"id": "BALIKPAPAN",
"nama": "Balikpapan Sepinggan",
"kota": "Balikpapan",
"ispu": 66,
"kategori": "Sedang"
},
{
"id": "PS_KIPP_IKN",
"nama": "Plaza Seremoni KIPP IKN",
"kota": "Kabupaten Penajam Paser Utara",
"ispu": 53,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Sumatera Barat",
"jumlahStasiun": 1,
"ispuRataRata": 100,
"stations": [
{
"id": "KABUPATEN_PADANG_PARIAMAN",
"nama": "Kabupaten Padang Pariaman",
"kota": "Kabupaten Padang Pariaman",
"ispu": 100,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Bengkulu",
"jumlahStasiun": 1,
"ispuRataRata": 98,
"stations": [
{
"id": "BENGKULU",
"nama": "Bengkulu Singaran Pati",
"kota": "Bengkulu",
"ispu": 98,
"kategori": "Sedang"
}
]
},
{
"provinsi": "DKI Jakarta",
"jumlahStasiun": 7,
"ispuRataRata": 86,
"stations": [
{
"id": "DKI1",
"nama": "DKI 1 Bundaran HI",
"kota": "Jakarta",
"ispu": 112,
"kategori": "Tidak Sehat"
},
{
"id": "DKI4",
"nama": "DKI 4 Lubang Buaya",
"kota": "Jakarta",
"ispu": 107,
"kategori": "Tidak Sehat"
},
{
"id": "DKI5",
"nama": "DKI 5 Kebon Jeruk",
"kota": "Jakarta",
"ispu": 105,
"kategori": "Tidak Sehat"
},
{
"id": "DKI2",
"nama": "DKI 2 Kelapa Gading",
"kota": "Jakarta",
"ispu": 88,
"kategori": "Sedang"
},
{
"id": "DKI3",
"nama": "DKI 3 Jagakarsa",
"kota": "Jakarta",
"ispu": 86,
"kategori": "Sedang"
},
{
"id": "JAKARTA",
"nama": "Jakarta GBK",
"kota": "Jakarta",
"ispu": 55,
"kategori": "Sedang"
},
{
"id": "KBN_MARUNDA",
"nama": "KBN Marunda Jakarta Utara",
"kota": "Jakarta Utara",
"ispu": 47,
"kategori": "Baik"
}
]
},
{
"provinsi": "Kalimantan Utara",
"jumlahStasiun": 1,
"ispuRataRata": 85,
"stations": [
{
"id": "KOTA_TARAKAN",
"nama": "Kota Tarakan Pamusian",
"kota": "Kota Tarakan",
"ispu": 85,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Sulawesi Tengah",
"jumlahStasiun": 1,
"ispuRataRata": 75,
"stations": [
{
"id": "KABUPATEN_MOROWALI",
"nama": "Kabupaten Morowali",
"kota": "Kabupaten Morowali",
"ispu": 75,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Banten",
"jumlahStasiun": 9,
"ispuRataRata": 65,
"stations": [
{
"id": "KABUPATEN_TANGERANG",
"nama": "Kabupaten Tangerang Tigaraksa",
"kota": "Tangerang",
"ispu": 95,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_SERANG",
"nama": "Kabupaten Serang Binuang",
"kota": "Serang",
"ispu": 93,
"kategori": "Sedang"
},
{
"id": "TANGERANG",
"nama": "Tangerang Pasir Jaya",
"kota": "Tangerang",
"ispu": 81,
"kategori": "Sedang"
},
{
"id": "TANGERANG_KARANG_TENGAH",
"nama": "Tangerang Karang Tengah",
"kota": "Tangerang",
"ispu": 75,
"kategori": "Sedang"
},
{
"id": "KOTA_CILEGON",
"nama": "Kota Cilegon Pulomerak",
"kota": "Cilegon",
"ispu": 73,
"kategori": "Sedang"
},
{
"id": "TANGERANG_BENTENG_BETAWI",
"nama": "Tangerang Benteng Betawi",
"kota": "Tangerang",
"ispu": 63,
"kategori": "Sedang"
},
{
"id": "TANGERANG_KALI_PASIR",
"nama": "Tangerang Kali Pasir",
"kota": "Tangerang",
"ispu": 50,
"kategori": "Baik"
},
{
"id": "KIK2_PT_KSP",
"nama": "PT Krakatau Sarana Properti",
"kota": "Cilegon",
"ispu": 35,
"kategori": "Baik"
},
{
"id": "KABUPATEN_TANGERANGCG",
"nama": "Kabupaten Tangerang Curug",
"kota": "Tangerang",
"ispu": 21,
"kategori": "Baik"
}
]
},
{
"provinsi": "Jawa TImur",
"jumlahStasiun": 1,
"ispuRataRata": 64,
"stations": [
{
"id": "KABUPATEN_TUBAN",
"nama": "Kabupaten Tuban",
"kota": "Kabupaten Tuban",
"ispu": 64,
"kategori": "Sedang"
}
]
},
{
"provinsi": "Jawa Barat",
"jumlahStasiun": 9,
"ispuRataRata": 60,
"stations": [
{
"id": "KABUPATEN_SUKABUMI",
"nama": "Kabupaten Sukabumi Pelabuhanratu",
"kota": "Kabupaten Sukabumi",
"ispu": 86,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_BOGORLL",
"nama": "Kabupaten Bogor Leuwiliang",
"kota": "Bogor",
"ispu": 83,
"kategori": "Sedang"
},
{
"id": "BEKASI",
"nama": "Bekasi Kayuringin",
"kota": "Bekasi",
"ispu": 79,
"kategori": "Sedang"
},
{
"id": "BOGOR",
"nama": "Kota Bogor Tanah Sereal",
"kota": "Bogor",
"ispu": 68,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_INDRAMAYU",
"nama": "Kabupaten Indramayu Margadadi",
"kota": "Indramayu",
"ispu": 58,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_BANDUNGBB",
"nama": "Kabupaten Bandung Barat Saguling",
"kota": "Bandung Barat",
"ispu": 57,
"kategori": "Sedang"
},
{
"id": "KABUPATEN_PURWAKARTA",
"nama": "Kabupaten Purwakarta Purnawarman",
"kota": "Kabupaten Purwakarta",
"ispu": 48,
"kategori": "Baik"
},
{
"id": "KABUPATEN_BEKASI",
"nama": "Kabupaten Bekasi Sukamahi",
"kota": "Kabupaten Bekasi",
"ispu": 40,
"kategori": "Baik"
},
{
"id": "KABUPATEN_BOGOR",
"nama": "Kabupaten Bogor Tegar Beriman",
"kota": "Kabupaten Bogor",
"ispu": 22,
"kategori": "Baik"
}
]
},
{
"provinsi": "Papua",
"jumlahStasiun": 1,
"ispuRataRata": 50,
"stations": [
{
"id": "PAPUA",
"nama": "Jayapura Abepura",
"kota": "Jayapura",
"ispu": 50,
"kategori": "Baik"
}
]
},
{
"provinsi": "Jawa Timur",
"jumlahStasiun": 13,
"ispuRataRata": 48,
"stations": [
{
"id": "KOTA_PASURUAN",
"nama": "Kota Pasuruan Petamanan",
"kota": "Kota Pasuruan",
"ispu": 94,
"kategori": "Sedang"
},
{
"id": "KOTA_MADIUN",
"nama": "Kota Madiun Taman",
"kota": "Madiun",
"ispu": 73,
"kategori": "Sedang"
},
{
"id": "KOTA_PROBOLINGGO",
"nama": "Kota Probolinggo Kedopok",