Doc.md CHANGED
@@ -6,6 +6,12 @@
6
 
7
  ---
8
 
 
 
 
 
 
 
9
  ## 📑 Table of Contents
10
  1. [Project Overview](#1-project-overview)
11
  2. [System Architecture](#2-system-architecture)
@@ -154,71 +160,85 @@ Rasio dekomposisi dihitung dinamis dari dataset historis, fallback ke standar re
154
 
155
  ## 4. API Reference
156
 
157
- ### `POST /api/v1/predict`
158
- **Deskripsi**: Generate prediksi volume sampah 1–30 hari ke depan untuk lokasi tertentu.
159
 
160
  #### Request Body
161
  ```json
162
  {
163
- "hari_ke_depan": 7,
164
- "prediksi_hujan_bmkg": 25.5,
165
- "skala_keramaian": 0,
166
- "nama_lokasi": "JIS",
167
- "dari_tanggal": "06-01"
 
 
168
  }
169
  ```
170
  | Field | Type | Required | Description |
171
  |-------|------|----------|-------------|
172
- | `hari_ke_depan` | `int` | ✅ | Durasi prediksi (1–30 hari) |
173
- | `prediksi_hujan_bmkg` | `float` | ✅ | Estimasi curah hujan (mm). `0` = kering |
174
- | `skala_keramaian` | `int` | ✅ | Skala event manual (05). `0` = normal |
175
- | `nama_lokasi` | `string` | ✅ | Target lokasi: `JIS`, `GBK`, `Pasar Senen`, `Gang Sempit Tambora` |
176
- | `dari_tanggal` | `string` | ❌ | Tanggal mulai. Format: `YYYY-MM-DD`, `MM-DD`, atau `"1 Juni 2026"` |
 
 
177
 
178
  #### Response Success (200)
179
  ```json
180
  {
181
  "status": "success",
182
- "message": "🟡 WARNING di JIS: Volume di atas rata-rata.",
183
- "confidence_score": 0.94,
184
  "data": {
185
  "prediction_results": [
186
  {
187
- "tanggal": "2026-06-02",
188
- "lokasi": "JIS",
189
- "total_volume_ton": 1245.50,
190
- "sisa_makanan_ton": 621.15,
191
- "plastik_ton": 285.84,
192
- "rekomendasi_truk": 125,
193
- "status_risiko": "WARNING 🟡",
194
- "info_event": "PRJ Opening @ JIExpo"
 
195
  }
196
  ],
197
  "logistics_plan": {
198
- "trucks_needed": 872,
199
- "manpower": 2616,
200
- "estimated_duration_hours": 1743.2,
201
  "efficiency_rate": "85% (Optimal)"
202
  }
203
  }
204
  }
205
  ```
206
 
207
- #### Error Responses
208
- | Status Code | Response | Cause |
209
- |-------------|----------|-------|
210
- | `400` | `{"detail": "Format tanggal tidak valid..."}` | Input tanggal tidak dikenali parser |
211
- | `500` | `{"detail": "Gagal memproses prediksi: ..."}` | Internal error / model crash |
212
- | `503` | `{"detail": "Model/Dataset belum siap."}` | Service masih startup / model loading |
 
 
 
 
 
 
213
 
214
- ### `GET /`
215
- **Deskripsi**: Health check & metadata sistem.
 
 
 
216
  ```json
217
  {
218
  "status": "Online",
219
- "model": "Chronos-T5 Tiny",
220
- "dataset_year": "2026",
221
- "events_loaded": 15
222
  }
223
  ```
224
 
 
6
 
7
  ---
8
 
9
+ > [!IMPORTANT]
10
+ > **📖 FRONT-END INTEGRATION GUIDE**:
11
+ > Untuk panduan teknis khusus tim Front-End (termasuk tipe TypeScript, Axios snippets, pemetaan Peta & progress bar), silakan merujuk langsung ke dokumen [FRONTEND_API_DOC.md](file:///c:/khusus%20project%20IT/Fine%20tuning%20ulang%20AI%20jakarta/waste-prediction-api/FRONTEND_API_DOC.md).
12
+
13
+ ---
14
+
15
  ## 📑 Table of Contents
16
  1. [Project Overview](#1-project-overview)
17
  2. [System Architecture](#2-system-architecture)
 
160
 
161
  ## 4. API Reference
162
 
163
+ ### 1. `POST /api/v1/predict`
164
+ **Deskripsi**: Menghasilkan prediksi volume timbulan sampah harian/jam-an untuk lokasi tertentu beserta analisis risiko logistik menggunakan model Amazon Chronos atau Gradient Boosting.
165
 
166
  #### Request Body
167
  ```json
168
  {
169
+ "forecast_days": 7,
170
+ "rainfall_mm": 25.5,
171
+ "event_scale": 0,
172
+ "location": "JIS",
173
+ "start_date": "2026-07-03",
174
+ "granularity": "daily",
175
+ "model_type": "gradient_boosting"
176
  }
177
  ```
178
  | Field | Type | Required | Description |
179
  |-------|------|----------|-------------|
180
+ | `forecast_days` | `int` | ✅ | Durasi prediksi (1–30 hari) |
181
+ | `rainfall_mm` | `float` | ✅ | Curah hujan (mm). `0` = Auto (mengambil ramalan cuaca dari Open-Meteo) |
182
+ | `event_scale` | `int` | ✅ | Skala keramaian event buatan (0-5) |
183
+ | `location` | `string` | ✅ | Target lokasi: `JIS`, `GBK`, `Pasar Senen`, `Gang Sempit Tambora` |
184
+ | `start_date` | `string` | ❌ | Tanggal awal prediksi. Contoh: `2026-07-03` |
185
+ | `granularity` | `string` | ❌ | Tingkat rincian: `daily` atau `hourly` (default: `daily`) |
186
+ | `model_type` | `string` | ❌ | Algoritma: `gradient_boosting` atau `chronos` (default: `gradient_boosting`) |
187
 
188
  #### Response Success (200)
189
  ```json
190
  {
191
  "status": "success",
192
+ "message": "Normal conditions.",
193
+ "confidence_score": 0.9325,
194
  "data": {
195
  "prediction_results": [
196
  {
197
+ "date": "2026-07-03",
198
+ "location": "JIS",
199
+ "total_volume_ton": 140.70,
200
+ "organic_waste_ton": 70.17,
201
+ "plastic_waste_ton": 32.29,
202
+ "recommended_trucks": 29,
203
+ "risk_status": "SAFE",
204
+ "event_info": null,
205
+ "hourly_breakdown": null
206
  }
207
  ],
208
  "logistics_plan": {
209
+ "trucks_needed": 29,
210
+ "manpower": 87,
211
+ "estimated_duration_hours": 28.1,
212
  "efficiency_rate": "85% (Optimal)"
213
  }
214
  }
215
  }
216
  ```
217
 
218
+ ---
219
+
220
+ ### 2. `POST /api/v1/predict/csv`
221
+ **Deskripsi**: Mengirimkan parameter yang sama seperti endpoint prediksi standar, tetapi menghasilkan output berkas CSV secara langsung untuk diunduh.
222
+
223
+ #### Request Body
224
+ Sama seperti `POST /api/v1/predict`.
225
+
226
+ #### Response Success (200)
227
+ Mengembalikan berkas file download (`text/csv`) dengan nama file dinamis: `waste_forecast_[Location]_[Days]d.csv`.
228
+ **Header Respon**:
229
+ `Content-Disposition: attachment; filename="waste_forecast_JIS_7d.csv"`
230
 
231
+ ---
232
+
233
+ ### 3. `GET /status`
234
+ **Deskripsi**: Health check status server dan ketersediaan model ML.
235
+ #### Response Success (200)
236
  ```json
237
  {
238
  "status": "Online",
239
+ "model_chronos": "Chronos-T5 Tiny",
240
+ "model_gbr": "Gradient Boosting Regressor",
241
+ "calibrated": true
242
  }
243
  ```
244
 
FRONTEND_API_DOC.md ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🗑️ Panduan Integrasi API Waste Intelligence — Khusus Front-End (FE)
2
+ > **Sistem Prediksi Manajemen Sampah DKI Jakarta 2026**
3
+ > **Target API Base URL (Lokal)**: `http://localhost:8001`
4
+ > **Target API Base URL (Production)**: `https://huggingface.co/spaces/ALAMDIENG/waste-prediction-api`
5
+
6
+ Dokumen ini disusun untuk memudahkan tim Front-End (FE) dalam mengintegrasikan endpoint backend dengan Dashboard UI, komponen Peta (Leaflet.js/Mapbox), Grafik (Recharts/ApexCharts/Chart.js), dan Sistem Alerts.
7
+
8
+ ---
9
+
10
+ ## 📑 Daftar Isi
11
+ 1. [Konstanta & Data Spasial (Map & Coordinates)](#1-konstanta--data-spasial-map--coordinates)
12
+ 2. [Definisi Tipe Data (TypeScript Interfaces)](#2-definisi-tipe-data-typescript-interfaces)
13
+ 3. [Referensi Endpoint API](#3-referensi-endpoint-api)
14
+ - [GET `/status` (Health Check)](#get-status-health-check)
15
+ - [POST `/api/v1/predict` (Forecasting & Analisis)](#post-apiv1predict-forecasting--analisis)
16
+ - [POST `/api/v1/predict/csv` (Export Data)](#post-apiv1predictcsv-export-data)
17
+ - [GET `/api/v1/alerts` (Daftar Peringatan Hari Ini & H+2)](#get-apiv1alerts-daftar-peringatan-hari-ini--h2)
18
+ 4. [Contoh Implementasi Code (Axios / Fetch)](#4-contoh-implementasi-code-axios--fetch)
19
+ 5. [Panduan Mapping ke UI Dashboard](#5-panduan-mapping-ke-ui-dashboard)
20
+ 6. [Penanganan Error & Validasi](#6-penanganan-error--validasi)
21
+
22
+ ---
23
+
24
+ ## 1. Konstanta & Data Spasial (Map & Coordinates)
25
+
26
+ Untuk memudahkan penggambaran Marker dan Garis Rute (Logistics Route) ke TPST Bantargebang di peta Leaflet.js, gunakan konstanta koordinat berikut di sisi klien.
27
+
28
+ ```javascript
29
+ // Koordinat Utama Lokasi Pengamatan
30
+ export const LOCATION_COORDINATES = {
31
+ "GBK": { latitude: -6.2183, longitude: 106.8022, radiusLabel: "2.0 km" },
32
+ "JIS": { latitude: -6.1244, longitude: 106.8622, radiusLabel: "1.5 km" },
33
+ "Pasar Senen": { latitude: -6.1744, longitude: 106.8444, radiusLabel: "1.2 km" },
34
+ "Gang Sempit Tambora": { latitude: -6.1500, longitude: 106.8000, radiusLabel: "0.8 km" }
35
+ };
36
+
37
+ // Koordinat Pembuangan Akhir (Tempat Pembuangan Sampah Terpadu Bantargebang)
38
+ export const BANTARGEBANG_COORDS = { latitude: -6.3477, longitude: 106.9939 };
39
+
40
+ // Jarak & Waktu Tempuh Estimasi untuk UI Rute Logistik
41
+ export const LOGISTICS_ROUTING_PROFILES = {
42
+ "JIS": { distance: "41.2 km", travelTime: "1.5 Jam" },
43
+ "GBK": { distance: "38.5 km", travelTime: "1.8 Jam" },
44
+ "Pasar Senen": { distance: "34.8 km", travelTime: "1.4 Jam" },
45
+ "Gang Sempit Tambora": { distance: "43.5 km", travelTime: "2.1 Jam" }
46
+ };
47
+ ```
48
+
49
+ > [!TIP]
50
+ > Gambar garis rute (logistik) dari koordinat lokasi terpilih langsung menuju `BANTARGEBANG_COORDS` menggunakan fitur `L.polyline` dengan style *dashed cyan glow* (`#00F0FF`) untuk memberikan kesan modern/cyberpunk.
51
+
52
+ ---
53
+
54
+ ## 2. Definisi Tipe Data (TypeScript Interfaces)
55
+
56
+ Jika Anda menggunakan TypeScript pada frontend (seperti React, Vue, atau Next.js), salin tipe data berikut:
57
+
58
+ ```typescript
59
+ export type ModelType = 'chronos' | 'gradient_boosting';
60
+ export type Granularity = 'daily' | 'hourly';
61
+ export type RiskStatus = 'SAFE' | 'WARNING' | 'CRITICAL';
62
+ export type HourlyRiskIndicator = 'LOW' | 'MEDIUM' | 'HIGH';
63
+
64
+ export interface PredictionRequest {
65
+ forecast_days: number; // 1 - 30 hari
66
+ rainfall_mm: number; // Curah hujan manual (0 = Otomatis mengambil data live cuaca)
67
+ event_scale: number; // Skala keramaian buatan (0 = tidak ada, 5 = masif)
68
+ location: 'JIS' | 'GBK' | 'Pasar Senen' | 'Gang Sempit Tambora';
69
+ start_date?: string; // Opsional, format YYYY-MM-DD
70
+ granularity?: Granularity; // Default: 'daily'
71
+ model_type?: ModelType; // Default: 'chronos'
72
+ }
73
+
74
+ export interface ConfidenceRange {
75
+ lower: number;
76
+ upper: number;
77
+ }
78
+
79
+ export interface HourlyBreakdown {
80
+ hour: string; // Format "00:00", "01:00", dsb.
81
+ estimated_volume_ton: number;
82
+ risk_indicator: HourlyRiskIndicator;
83
+ confidence_range: ConfidenceRange;
84
+ }
85
+
86
+ export interface PredictionResult {
87
+ date: string; // YYYY-MM-DD
88
+ location: string;
89
+ total_volume_ton: number;
90
+ organic_waste_ton: number;
91
+ plastic_waste_ton: number;
92
+ recommended_trucks: number; // Truk kapasitas 5 ton
93
+ risk_status: RiskStatus;
94
+ event_info: string | null; // Nama event terdekat (jika ada)
95
+ hourly_breakdown: HourlyBreakdown[] | null; // Terisi jika granularity = 'hourly'
96
+ }
97
+
98
+ export interface LogisticsPlan {
99
+ trucks_needed: number;
100
+ manpower: number; // 3 x jumlah armada truk
101
+ estimated_duration_hours: number;
102
+ efficiency_rate: string; // Contoh: "85% (Optimal)"
103
+ }
104
+
105
+ export interface PredictionData {
106
+ prediction_results: PredictionResult[];
107
+ logistics_plan: LogisticsPlan;
108
+ }
109
+
110
+ export interface APIPredictionResponse {
111
+ status: 'success' | 'error';
112
+ message: string;
113
+ confidence_score: number; // Skala 0.0 - 1.0 (misal: 0.93)
114
+ data: PredictionData;
115
+ }
116
+
117
+ export interface AlertItem {
118
+ date: string;
119
+ location: string;
120
+ status: 'WARNING' | 'CRITICAL';
121
+ estimated_volume_ton: number;
122
+ message: string;
123
+ }
124
+
125
+ export interface APIAlertResponse {
126
+ status: 'success';
127
+ alert_count: number;
128
+ alerts: AlertItem[];
129
+ last_updated: string; // ISO Timestamp
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 3. Referensi Endpoint API
136
+
137
+ ### GET `/status` (Health Check)
138
+ Endpoint ini digunakan untuk memverifikasi apakah server menyala dan model AI sudah ter-load dengan benar di memori.
139
+
140
+ - **URL**: `/status`
141
+ - **Method**: `GET`
142
+ - **Response Contoh (200 OK)**:
143
+ ```json
144
+ {
145
+ "status": "Online",
146
+ "model_chronos": "Chronos-T5 Tiny",
147
+ "model_gbr": "Gradient Boosting Regressor",
148
+ "calibrated": true
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ### POST `/api/v1/predict` (Forecasting & Analisis)
155
+ Endpoint utama untuk memanggil prediksi time-series model AI. AI akan menghitung dampak cuaca basah, event keramaian, status risiko per hari, rincian logistik, hingga dekomposisi sampah organik/plastik.
156
+
157
+ - **URL**: `/api/v1/predict`
158
+ - **Method**: `POST`
159
+ - **Headers**:
160
+ - `Content-Type: application/json`
161
+ - **Request Body Contoh**:
162
+ ```json
163
+ {
164
+ "forecast_days": 7,
165
+ "rainfall_mm": 0,
166
+ "event_scale": 0,
167
+ "location": "JIS",
168
+ "granularity": "hourly",
169
+ "model_type": "gradient_boosting"
170
+ }
171
+ ```
172
+
173
+ - **Response Contoh (200 OK)**:
174
+ ```json
175
+ {
176
+ "status": "success",
177
+ "message": "Normal conditions.",
178
+ "confidence_score": 0.9325,
179
+ "data": {
180
+ "prediction_results": [
181
+ {
182
+ "date": "2026-07-08",
183
+ "location": "JIS",
184
+ "total_volume_ton": 122.45,
185
+ "organic_waste_ton": 61.07,
186
+ "plastic_waste_ton": 28.1,
187
+ "recommended_trucks": 25,
188
+ "risk_status": "SAFE",
189
+ "event_info": null,
190
+ "hourly_breakdown": [
191
+ {
192
+ "hour": "00:00",
193
+ "estimated_volume_ton": 2.45,
194
+ "risk_indicator": "LOW",
195
+ "confidence_range": {
196
+ "lower": 2.08,
197
+ "upper": 2.82
198
+ }
199
+ }
200
+ // ... total 24 jam data
201
+ ]
202
+ }
203
+ ],
204
+ "logistics_plan": {
205
+ "trucks_needed": 25,
206
+ "manpower": 75,
207
+ "estimated_duration_hours": 24.5,
208
+ "efficiency_rate": "85% (Optimal)"
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ ---
215
+
216
+ ### POST `/api/v1/predict/csv` (Export Data)
217
+ Endpoint ini mengembalikan data prediksi yang sama dengan di atas, tetapi langsung dikonversi menjadi file `.csv` yang siap diunduh di peramban pengguna.
218
+
219
+ - **URL**: `/api/v1/predict/csv`
220
+ - **Method**: `POST`
221
+ - **Headers**:
222
+ - `Content-Type: application/json`
223
+ - **Response**: Mengembalikan raw bytes file stream (`text/csv`). Header response menyertakan `Content-Disposition: attachment; filename="waste_forecast_[lokasi]_[hari]d.csv"`.
224
+
225
+ ---
226
+
227
+ ### GET `/api/v1/alerts` (Daftar Peringatan Hari Ini & H+2)
228
+ Mengambil daftar titik lokasi yang mengalami lonjakan volume (di atas batas ambang aman) dalam 3 hari ke depan secara dinamis.
229
+
230
+ - **URL**: `/api/v1/alerts`
231
+ - **Method**: `GET`
232
+ - **Query Params**:
233
+ - `location` (Opsional) : Untuk memfilter alert hanya untuk lokasi tertentu saja (misal: `JIS` / `GBK`).
234
+ - **Response Contoh (200 OK)**:
235
+ ```json
236
+ {
237
+ "status": "success",
238
+ "alert_count": 1,
239
+ "alerts": [
240
+ {
241
+ "date": "2026-07-09",
242
+ "location": "JIS",
243
+ "status": "WARNING",
244
+ "estimated_volume_ton": 168.5,
245
+ "message": "Alert: WARNING volume expected at JIS"
246
+ }
247
+ ],
248
+ "last_updated": "2026-07-08T10:15:30.123456"
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ ## 4. Contoh Implementasi Code (Axios / Fetch)
255
+
256
+ ### Mengirim Request Prediksi & Update State (JavaScript / React)
257
+ ```javascript
258
+ import axios from 'axios';
259
+
260
+ const API_BASE_URL = 'http://localhost:8001'; // Sesuaikan environment
261
+
262
+ export async function fetchWastePrediction(payload) {
263
+ try {
264
+ const response = await axios.post(`${API_BASE_URL}/api/v1/predict`, payload);
265
+ return response.data;
266
+ } catch (error) {
267
+ console.error("Error predicting waste volume:", error.response?.data || error.message);
268
+ throw error;
269
+ }
270
+ }
271
+ ```
272
+
273
+ ### Mengunduh CSV File (JavaScript)
274
+ ```javascript
275
+ export async function downloadPredictionCSV(payload) {
276
+ try {
277
+ const response = await axios.post(`${API_BASE_URL}/api/v1/predict/csv`, payload, {
278
+ responseType: 'blob' // Wajib diisi agar file blob dibaca dengan benar
279
+ });
280
+
281
+ // Trigger download manual via browser
282
+ const blob = new Blob([response.data], { type: 'text/csv' });
283
+ const url = window.URL.createObjectURL(blob);
284
+ const link = document.createElement('a');
285
+ link.href = url;
286
+
287
+ // Nama file dinamis
288
+ const fileName = `waste_forecast_${payload.location.replace(/\s+/g, '_')}_${payload.forecast_days}d.csv`;
289
+ link.setAttribute('download', fileName);
290
+
291
+ document.body.appendChild(link);
292
+ link.click();
293
+
294
+ // Bersihkan link element setelah click
295
+ link.remove();
296
+ window.URL.revokeObjectURL(url);
297
+ } catch (error) {
298
+ console.error("Gagal mengunduh CSV:", error);
299
+ alert("Ekspor CSV Gagal!");
300
+ }
301
+ }
302
+ ```
303
+
304
+ ---
305
+
306
+ ## 5. Panduan Mapping ke UI Dashboard
307
+
308
+ ### A. Total Volume & Kebutuhan Armada
309
+ 1. **Total Volume Forecast**: Lakukan perulangan (`reduce`) untuk menjumlahkan `total_volume_ton` dari semua entri di `data.prediction_results`. Tampilkan nilai desimal 2 angka (`.toFixed(2)`).
310
+ 2. **Kebutuhan Fleet (Truk)**: Tampilkan `data.logistics_plan.trucks_needed`. Truk dihitung secara kumulatif dengan kapasitas angkut maksimal 5 Ton per armada.
311
+ 3. **Tenaga Kerja (Manpower)**: Ditampilkan dari `data.logistics_plan.manpower`. Angka ini adalah alokasi aman kru operasional (3 orang per truk).
312
+
313
+ ### B. Komposisi Sampah (Organic & Plastic)
314
+ Hitung persentase dinamis untuk di-render pada UI *Progress Bar*:
315
+ ```javascript
316
+ // Hitung jumlah tonase terlebih dahulu
317
+ const totalOrganic = results.reduce((acc, c) => acc + c.organic_waste_ton, 0);
318
+ const totalPlastic = results.reduce((acc, c) => acc + c.plastic_waste_ton, 0);
319
+ const totalVol = results.reduce((acc, c) => acc + c.total_volume_ton, 0);
320
+
321
+ // Hitung persentase relatif
322
+ const organicPct = totalVol > 0 ? (totalOrganic / totalVol) * 100 : 0;
323
+ const plasticPct = totalVol > 0 ? (totalPlastic / totalVol) * 100 : 0;
324
+
325
+ // Render ke UI
326
+ // Ganti properti width progress bar inline style / css variable
327
+ document.getElementById('bar-organic').style.width = `${organicPct}%`;
328
+ document.getElementById('bar-plastic').style.width = `${plasticPct}%`;
329
+ ```
330
+
331
+ ### C. Penentuan Status Risiko (Risk Status)
332
+ Backend mengembalikan status per hari: `'SAFE'`, `'WARNING'`, atau `'CRITICAL'`.
333
+ Untuk menentukan status risiko keseluruhan periode yang dipilih:
334
+ - Ambil status **tertinggi** yang muncul di sepanjang list hari prediksi.
335
+ - Aturan Prioritas Status: `CRITICAL` > `WARNING` > `SAFE`.
336
+ - Berikan penyesuaian style warna badge:
337
+ - `SAFE`: Hijau terang (`#00E676`)
338
+ - `WARNING`: Kuning neon (`#FFD600`)
339
+ - `CRITICAL`: Merah menyala (`#FF1744`)
340
+
341
+ ### D. Weather Integration (Live BMKG)
342
+ Saat user memilih lokasi baru:
343
+ 1. Hubungi BMKG/Open-Meteo API di sisi FE menggunakan koordinat lokasi (lihat [Bagian 1](#1-konstanta--data-spasial-map--coordinates)).
344
+ 2. Dapatkan nilai curah hujan hari ini (`precipitation_sum` / `precipitation`).
345
+ 3. Tampilkan status peringatan hujan di UI:
346
+ - Curah Hujan `> 30 mm` ➡️ Tampilkan badge **HEAVY RAIN 🟡**
347
+ - Curah Hujan `> 50 mm` ➡️ Tampilkan badge **FLOOD DANGER 🔴**
348
+ - Di bawah itu ➡️ Tampilkan **Normal conditions**
349
+
350
+ ---
351
+
352
+ ## 6. Penanganan Error & Validasi
353
+
354
+ Backend menggunakan Pydantic v2 untuk memvalidasi request body secara ketat.
355
+
356
+ ### HTTP 422 Unprocessable Entity
357
+ Terjadi jika payload yang dikirimkan memiliki tipe data yang salah atau data di luar rentang validasi.
358
+ *Contoh error respon*:
359
+ ```json
360
+ {
361
+ "detail": [
362
+ {
363
+ "type": "less_than_equal",
364
+ "loc": ["body", "forecast_days"],
365
+ "msg": "Input should be less than or equal to 30",
366
+ "input": 45
367
+ }
368
+ ]
369
+ }
370
+ ```
371
+ **Tips FE**: Batasi input `forecast_days` menggunakan komponen slider HTML `min="1" max="30"` untuk menghindari error ini.
372
+
373
+ ### HTTP 503 Service Unavailable
374
+ Terjadi jika startup server belum selesai me-load model Amazon Chronos atau file CSV belum siap di sisi backend.
375
+ **Tips FE**: Sediakan visual loader atau spinner yang menarik di dashboard untuk mencegah interaksi klik ganda saat status server menunjukkan pemuatan ulang aset AI.
376
+
377
+ ---
378
+
379
+ > 💡 **Kontak Developer Backend**:
380
+ > **Faril Putra Pratama** (SMK Taruna Bangsa)
381
+ > Hubungi via repository GitHub di: [@FARILtau72](https://github.com/FARILtau72) jika Anda membutuhkan endpoint tambahan atau perubahan format respon!
README.md CHANGED
@@ -21,6 +21,12 @@ Eco-Twin AI adalah sistem cerdas berbasis *Machine Learning* yang dirancang untu
21
 
22
  ---
23
 
 
 
 
 
 
 
24
  ## 🚀 Fitur Unggulan (Hackathon Killer Features)
25
 
26
  1. **Integrasi Kalender Event Otomatis**: Sistem secara otomatis membaca file `event_jakarta_2025.txt` saat server dinyalakan. Jika ada *request* prediksi yang menyentuh tanggal konser besar (misal: Maroon 5 di JIS), AI akan mendeteksi dan secara akurat menambahkan estimasi volume sampah tanpa input manual tambahan.
 
21
 
22
  ---
23
 
24
+ > [!IMPORTANT]
25
+ > **📖 DOKUMENTASI INTEGRASI FRONT-END**:
26
+ > Kami telah menyediakan panduan integrasi lengkap khusus tim Front-End (FE) di file [FRONTEND_API_DOC.md](file:///c:/khusus%20project%20IT/Fine%20tuning%20ulang%20AI%20jakarta/waste-prediction-api/FRONTEND_API_DOC.md). File tersebut berisi konstanta koordinat peta, tipe data TypeScript, contoh request Axios/Fetch, serta cara memetakan respons ke UI Dashboard.
27
+
28
+ ---
29
+
30
  ## 🚀 Fitur Unggulan (Hackathon Killer Features)
31
 
32
  1. **Integrasi Kalender Event Otomatis**: Sistem secara otomatis membaca file `event_jakarta_2025.txt` saat server dinyalakan. Jika ada *request* prediksi yang menyentuh tanggal konser besar (misal: Maroon 5 di JIS), AI akan mendeteksi dan secara akurat menambahkan estimasi volume sampah tanpa input manual tambahan.
__pycache__/app.cpython-311.pyc ADDED
Binary file (33.3 kB). View file
 
app.py CHANGED
@@ -1,11 +1,16 @@
1
  from fastapi import FastAPI, HTTPException, Query
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.concurrency import run_in_threadpool
 
4
  from pydantic import BaseModel, Field, field_validator
5
  from typing import Optional, List, Dict, Any
6
  import pandas as pd
7
  import numpy as np
8
  import torch
 
 
 
 
9
  from chronos import ChronosPipeline
10
  from datetime import datetime, timedelta
11
  import os, logging, re
@@ -17,7 +22,7 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(
17
  logger = logging.getLogger(__name__)
18
 
19
  app = FastAPI(
20
- title="Waste Intelligence API - Jakarta Pusat 2026",
21
  version="3.0.0 (Calibrated)",
22
  description="AI-powered waste prediction with spatial awareness & real-world calibration"
23
  )
@@ -30,6 +35,15 @@ app.add_middleware(
30
  allow_headers=["*"],
31
  )
32
 
 
 
 
 
 
 
 
 
 
33
  # ==========================================
34
  # 2. INPUT VALIDATION & SCHEMAS (English Standard)
35
  # ==========================================
@@ -41,11 +55,12 @@ class PredictionRequest(BaseModel):
41
  Field names use English for international clarity.
42
  """
43
  forecast_days: int = Field(7, ge=1, le=30, description="Forecast horizon in days (1-30)")
44
- rainfall_mm: float = Field(0.0, ge=0, description="Estimated rainfall in mm (BMKG forecast)")
45
  event_scale: int = Field(0, ge=0, le=5, description="Manual event crowd scale (0=none, 5=massive)")
46
  location: str = Field(..., description="Target location name")
47
  start_date: Optional[str] = Field(None, description="Start date: YYYY-MM-DD, MM-DD, or '1 Juni 2026'")
48
  granularity: str = Field("daily", pattern="^(daily|hourly)$", description="Prediction granularity")
 
49
 
50
  @field_validator("location")
51
  @classmethod
@@ -91,9 +106,18 @@ class AlertResponse(BaseModel):
91
  # 3. GLOBAL STATE & OPERATIONAL LOGIC
92
  # ==========================================
93
  pipeline = None
 
94
  df_history = None
95
  events_data = {}
96
 
 
 
 
 
 
 
 
 
97
  # Spatial radius mapping: events at location X impact nearby zones
98
  EVENT_RADIUS_MAP = {
99
  "jiexpo": ["jis", "kemayoran", "pademangan", "jakarta"],
@@ -161,16 +185,14 @@ def get_risk_status(volume: float, location: str) -> str:
161
  def distribute_to_hourly(daily_volume: float, location: str) -> List[Dict[str, Any]]:
162
  """Distribute daily prediction to hourly estimates with dynamic risk indicators."""
163
  pattern = HOURLY_PATTERN.copy()
164
- # Adjust patterns for specific location behaviors
165
- if location == "GBK": # Peak evening for events
166
  pattern[19] += 0.03; pattern[20] += 0.03; pattern[21] += 0.02
167
- elif location == "Pasar Senen": # Peak morning for market
168
  pattern[6] += 0.04; pattern[7] += 0.04; pattern[8] += 0.03
169
 
170
  total_factor = sum(pattern.values())
171
  hourly_results = []
172
 
173
- # Dynamic thresholds relative to the daily volume
174
  high_thresh = (daily_volume / 24) * 2.0
175
  med_thresh = (daily_volume / 24) * 1.2
176
 
@@ -186,18 +208,40 @@ def distribute_to_hourly(daily_volume: float, location: str) -> List[Dict[str, A
186
  })
187
  return hourly_results
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  # ==========================================
190
  # 5. STARTUP & MODEL LOADING
191
  # ==========================================
192
  @app.on_event("startup")
193
  async def load_assets():
194
  """Initialize AI model, historical dataset, and event calendar."""
195
- global pipeline, df_history, events_data
196
  logger.info("⏳ Initializing AI assets...")
197
  try:
198
  pipeline = ChronosPipeline.from_pretrained("amazon/chronos-t5-tiny", device_map="cpu", torch_dtype=torch.float32)
199
  logger.info("✅ Chronos model loaded")
200
 
 
 
 
 
 
 
201
  df_history = pd.read_csv("dataset_vibe_coder_2026.csv")
202
  df_history["TANGGAL"] = pd.to_datetime(df_history["TANGGAL"]).dt.strftime("%Y-%m-%d")
203
  logger.info(f"✅ Historical dataset loaded: {len(df_history)} records")
@@ -221,11 +265,25 @@ async def load_assets():
221
  raise
222
 
223
  # ==========================================
224
- # 6. API ENDPOINTS
225
  # ==========================================
226
- @app.get("/", tags=["System"])
 
 
 
 
 
 
 
 
 
227
  def status_check():
228
- return {"status": "Online", "model": "Chronos-T5 Tiny", "calibrated": True}
 
 
 
 
 
229
 
230
  def perform_inference(ctx, steps):
231
  forecast = pipeline.predict(ctx.unsqueeze(0), steps)
@@ -234,15 +292,19 @@ def perform_inference(ctx, steps):
234
  @app.post("/api/v1/predict", response_model=APIResponse, tags=["Prediction"])
235
  async def predict_waste_volume(req: PredictionRequest):
236
  if df_history is None or pipeline is None:
237
- raise HTTPException(503, "Model not ready.")
238
 
239
  try:
240
  start_date = parse_flexible_date(req.start_date) if req.start_date else pd.to_datetime(df_history["TANGGAL"].iloc[-1])
241
- ctx = torch.tensor(df_history["Volume_Total_Ton"].values, dtype=torch.float32)
242
- forecast_vals = await run_in_threadpool(perform_inference, ctx, req.forecast_days)
243
 
244
- # Calculate calibration factor: (Real World Baseline / Model Dataset Mean)
245
- # This bridges the gap between AI model scale and operational reality
 
 
 
 
 
 
246
  dataset_mean = df_history["Volume_Total_Ton"].mean()
247
  real_baseline = LOCATION_BASELINES[req.location]["normal_avg"]
248
  calibration_factor = real_baseline / dataset_mean
@@ -250,55 +312,149 @@ async def predict_waste_volume(req: PredictionRequest):
250
  o_r = (df_history["Vol_Sisa_Makanan_Ton"] / df_history["Volume_Total_Ton"]).mean()
251
  p_r = (df_history["Vol_Plastik_Ton"] / df_history["Volume_Total_Ton"]).mean()
252
 
253
- results = []
254
- total_vol = 0.0
255
- max_risk = "SAFE"
256
-
257
- for i, base in enumerate(forecast_vals):
258
- curr_date = start_date + timedelta(days=i)
259
- d_str = curr_date.strftime("%Y-%m-%d")
260
-
261
- # 1. Rainfall Multiplier
262
- rain_m = 1.0
263
- if req.rainfall_mm > 20: rain_m = 1.02 + min((req.rainfall_mm - 20) * 0.001, 0.03)
264
-
265
- # 2. Event Multiplier
266
- evt = events_data.get(d_str)
267
- evt_m = 1.0
268
- info = None
269
- if evt and evt["crowd_scale"] > 0 and check_location_match(req.location, evt["location"]):
270
- evt_m = 1.0 + 0.10 + min(evt["crowd_scale"] * 0.05, 0.25) # Up to +35%
271
- info = f"{evt['event_name']} @ {evt['location']}"
272
- elif req.event_scale > 0:
273
- evt_m = 1.0 + req.event_scale * 0.10
274
 
275
- # 3. Final Calculation with Calibration
276
- raw_prediction = base * rain_m * evt_m
277
- calibrated_volume = round(float(raw_prediction * calibration_factor), 2)
278
-
279
- total_vol += calibrated_volume
280
- risk = get_risk_status(calibrated_volume, req.location)
281
- if risk == "CRITICAL": max_risk = "CRITICAL"
282
- elif risk == "WARNING" and max_risk != "CRITICAL": max_risk = "WARNING"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
- hourly = distribute_to_hourly(calibrated_volume, req.location) if req.granularity == "hourly" else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
- results.append(PredictionResult(
287
- date=d_str, location=req.location, total_volume_ton=calibrated_volume,
288
- organic_waste_ton=round(calibrated_volume*o_r, 2), plastic_waste_ton=round(calibrated_volume*p_r, 2),
289
- recommended_trucks=max(1, int(np.ceil(calibrated_volume/5))), # 5-ton trucks for better granularity
290
- risk_status=risk, event_info=info, hourly_breakdown=hourly
291
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
- # Logistics
294
  trucks = sum([r.recommended_trucks for r in results])
295
  msg = f"CRITICAL at {req.location}!" if max_risk == "CRITICAL" else f"WARNING at {req.location}." if max_risk == "WARNING" else "Normal conditions."
296
 
 
 
 
297
  return APIResponse(
298
- status="success", message=msg, confidence_score=0.92, # Fixed high confidence for calibrated model
299
  data=PredictionData(
300
  prediction_results=results,
301
- logistics_plan=LogisticsPlan(trucks_needed=trucks, manpower=trucks*3, estimated_duration_hours=round(total_vol/5, 1), efficiency_rate="85% (Optimal)")
 
 
 
 
 
302
  )
303
  )
304
  except HTTPException: raise
@@ -306,6 +462,40 @@ async def predict_waste_volume(req: PredictionRequest):
306
  logger.error(f"Prediction failed: {e}", exc_info=True)
307
  raise HTTPException(500, str(e))
308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  @app.get("/api/v1/alerts", response_model=AlertResponse, tags=["Alerts"])
310
  async def get_alerts(location: str = Query(None)):
311
  """Real-time alerts endpoint."""
@@ -313,7 +503,6 @@ async def get_alerts(location: str = Query(None)):
313
 
314
  alerts = []
315
  today = datetime.now().date()
316
- dataset_mean = df_history["Volume_Total_Ton"].mean()
317
 
318
  for i in range(3):
319
  d = (today + timedelta(days=i)).strftime("%Y-%m-%d")
@@ -322,7 +511,6 @@ async def get_alerts(location: str = Query(None)):
322
  for loc, config in LOCATION_BASELINES.items():
323
  if location and loc != location: continue
324
 
325
- # Simple projection for alerts
326
  baseline_vol = config["normal_avg"]
327
  if evt and evt["crowd_scale"] > 0 and check_location_match(loc, evt["location"]):
328
  baseline_vol = config["event_peak"]
@@ -330,6 +518,10 @@ async def get_alerts(location: str = Query(None)):
330
  status = "CRITICAL" if baseline_vol > config["critical_threshold"] else "WARNING" if baseline_vol > config["warning_threshold"] else "SAFE"
331
 
332
  if status != "SAFE":
333
- alerts.append({"date": d, "location": loc, "status": status, "estimated_volume_ton": baseline_vol, "message": f"Alert: {status} volume expected at {loc}"})
 
 
 
 
334
 
335
  return AlertResponse(status="success", alert_count=len(alerts), alerts=alerts, last_updated=datetime.now().isoformat())
 
1
  from fastapi import FastAPI, HTTPException, Query
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.concurrency import run_in_threadpool
4
+ from fastapi.responses import HTMLResponse, StreamingResponse
5
  from pydantic import BaseModel, Field, field_validator
6
  from typing import Optional, List, Dict, Any
7
  import pandas as pd
8
  import numpy as np
9
  import torch
10
+ import joblib
11
+ import httpx
12
+ import io
13
+ import csv
14
  from chronos import ChronosPipeline
15
  from datetime import datetime, timedelta
16
  import os, logging, re
 
22
  logger = logging.getLogger(__name__)
23
 
24
  app = FastAPI(
25
+ title="Waste Intelligence API - DKI Jakarta 2026",
26
  version="3.0.0 (Calibrated)",
27
  description="AI-powered waste prediction with spatial awareness & real-world calibration"
28
  )
 
35
  allow_headers=["*"],
36
  )
37
 
38
+ # ==========================================
39
+ # STATIC FILES MOUNTING
40
+ # ==========================================
41
+ if not os.path.exists("static"):
42
+ os.makedirs("static")
43
+
44
+ from fastapi.staticfiles import StaticFiles
45
+ app.mount("/static", StaticFiles(directory="static"), name="static")
46
+
47
  # ==========================================
48
  # 2. INPUT VALIDATION & SCHEMAS (English Standard)
49
  # ==========================================
 
55
  Field names use English for international clarity.
56
  """
57
  forecast_days: int = Field(7, ge=1, le=30, description="Forecast horizon in days (1-30)")
58
+ rainfall_mm: float = Field(0.0, ge=0, description="Estimated rainfall in mm (default/manual)")
59
  event_scale: int = Field(0, ge=0, le=5, description="Manual event crowd scale (0=none, 5=massive)")
60
  location: str = Field(..., description="Target location name")
61
  start_date: Optional[str] = Field(None, description="Start date: YYYY-MM-DD, MM-DD, or '1 Juni 2026'")
62
  granularity: str = Field("daily", pattern="^(daily|hourly)$", description="Prediction granularity")
63
+ model_type: str = Field("chronos", pattern="^(chronos|gradient_boosting)$", description="AI model type")
64
 
65
  @field_validator("location")
66
  @classmethod
 
106
  # 3. GLOBAL STATE & OPERATIONAL LOGIC
107
  # ==========================================
108
  pipeline = None
109
+ model_gbr = None
110
  df_history = None
111
  events_data = {}
112
 
113
+ # Coordinates for Location-Aware Weather Forecasts
114
+ LOCATION_COORDINATES = {
115
+ "GBK": {"latitude": -6.2183, "longitude": 106.8022},
116
+ "JIS": {"latitude": -6.1244, "longitude": 106.8622},
117
+ "Pasar Senen": {"latitude": -6.1744, "longitude": 106.8444},
118
+ "Gang Sempit Tambora": {"latitude": -6.1500, "longitude": 106.8000}
119
+ }
120
+
121
  # Spatial radius mapping: events at location X impact nearby zones
122
  EVENT_RADIUS_MAP = {
123
  "jiexpo": ["jis", "kemayoran", "pademangan", "jakarta"],
 
185
  def distribute_to_hourly(daily_volume: float, location: str) -> List[Dict[str, Any]]:
186
  """Distribute daily prediction to hourly estimates with dynamic risk indicators."""
187
  pattern = HOURLY_PATTERN.copy()
188
+ if location == "GBK":
 
189
  pattern[19] += 0.03; pattern[20] += 0.03; pattern[21] += 0.02
190
+ elif location == "Pasar Senen":
191
  pattern[6] += 0.04; pattern[7] += 0.04; pattern[8] += 0.03
192
 
193
  total_factor = sum(pattern.values())
194
  hourly_results = []
195
 
 
196
  high_thresh = (daily_volume / 24) * 2.0
197
  med_thresh = (daily_volume / 24) * 1.2
198
 
 
208
  })
209
  return hourly_results
210
 
211
+ async def fetch_rainfall_forecast(lat: float, lon: float, days: int) -> dict:
212
+ """Fetch daily rainfall forecast from Open-Meteo API for target coordinates (including past 2 days)."""
213
+ url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=precipitation_sum&timezone=Asia/Jakarta&forecast_days={days}&past_days=2"
214
+ try:
215
+ async with httpx.AsyncClient() as client:
216
+ response = await client.get(url, timeout=5.0)
217
+ if response.status_code == 200:
218
+ data = response.json()
219
+ daily = data.get("daily", {})
220
+ times = daily.get("time", [])
221
+ precip = daily.get("precipitation_sum", [])
222
+ return {times[i]: float(precip[i]) for i in range(len(times)) if i < len(precip)}
223
+ except Exception as e:
224
+ logger.error(f"Failed to fetch weather from Open-Meteo: {e}")
225
+ return {}
226
+
227
  # ==========================================
228
  # 5. STARTUP & MODEL LOADING
229
  # ==========================================
230
  @app.on_event("startup")
231
  async def load_assets():
232
  """Initialize AI model, historical dataset, and event calendar."""
233
+ global pipeline, model_gbr, df_history, events_data
234
  logger.info("⏳ Initializing AI assets...")
235
  try:
236
  pipeline = ChronosPipeline.from_pretrained("amazon/chronos-t5-tiny", device_map="cpu", torch_dtype=torch.float32)
237
  logger.info("✅ Chronos model loaded")
238
 
239
+ if os.path.exists("model_sampah_advanced.pkl"):
240
+ model_gbr = joblib.load("model_sampah_advanced.pkl")
241
+ logger.info("✅ Gradient Boosting model loaded")
242
+ else:
243
+ logger.warning("⚠️ model_sampah_advanced.pkl not found")
244
+
245
  df_history = pd.read_csv("dataset_vibe_coder_2026.csv")
246
  df_history["TANGGAL"] = pd.to_datetime(df_history["TANGGAL"]).dt.strftime("%Y-%m-%d")
247
  logger.info(f"✅ Historical dataset loaded: {len(df_history)} records")
 
265
  raise
266
 
267
  # ==========================================
268
+ # 6. API & UI ENDPOINTS
269
  # ==========================================
270
+ @app.get("/", response_class=HTMLResponse, tags=["UI"])
271
+ def serve_dashboard():
272
+ """Serve the Floodzy-style interactive dashboard."""
273
+ try:
274
+ with open("static/index.html", "r", encoding="utf-8") as f:
275
+ return HTMLResponse(content=f.read(), status_code=200)
276
+ except FileNotFoundError:
277
+ return HTMLResponse(content="<h1>Dashboard HTML not found. Please create static/index.html.</h1>", status_code=404)
278
+
279
+ @app.get("/status", tags=["System"])
280
  def status_check():
281
+ return {
282
+ "status": "Online",
283
+ "model_chronos": "Chronos-T5 Tiny",
284
+ "model_gbr": "Gradient Boosting Regressor",
285
+ "calibrated": True
286
+ }
287
 
288
  def perform_inference(ctx, steps):
289
  forecast = pipeline.predict(ctx.unsqueeze(0), steps)
 
292
  @app.post("/api/v1/predict", response_model=APIResponse, tags=["Prediction"])
293
  async def predict_waste_volume(req: PredictionRequest):
294
  if df_history is None or pipeline is None:
295
+ raise HTTPException(503, "Models not ready.")
296
 
297
  try:
298
  start_date = parse_flexible_date(req.start_date) if req.start_date else pd.to_datetime(df_history["TANGGAL"].iloc[-1])
 
 
299
 
300
+ # Get coordinates for weather forecast API
301
+ coord = LOCATION_COORDINATES.get(req.location, {"latitude": -6.2088, "longitude": 106.8456})
302
+ weather_forecast = await fetch_rainfall_forecast(coord["latitude"], coord["longitude"], req.forecast_days)
303
+
304
+ results = []
305
+ total_vol = 0.0
306
+ max_risk = "SAFE"
307
+
308
  dataset_mean = df_history["Volume_Total_Ton"].mean()
309
  real_baseline = LOCATION_BASELINES[req.location]["normal_avg"]
310
  calibration_factor = real_baseline / dataset_mean
 
312
  o_r = (df_history["Vol_Sisa_Makanan_Ton"] / df_history["Volume_Total_Ton"]).mean()
313
  p_r = (df_history["Vol_Plastik_Ton"] / df_history["Volume_Total_Ton"]).mean()
314
 
315
+ if req.model_type == "chronos":
316
+ ctx = torch.tensor(df_history["Volume_Total_Ton"].values, dtype=torch.float32)
317
+ forecast_vals = await run_in_threadpool(perform_inference, ctx, req.forecast_days)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
319
+ for i, base in enumerate(forecast_vals):
320
+ curr_date = start_date + timedelta(days=i)
321
+ d_str = curr_date.strftime("%Y-%m-%d")
322
+
323
+ # Use fetched rainfall if available, else manual request value
324
+ daily_rain = weather_forecast.get(d_str, req.rainfall_mm)
325
+
326
+ # 1. Rainfall Multiplier
327
+ rain_m = 1.0
328
+ if daily_rain > 20:
329
+ rain_m = 1.02 + min((daily_rain - 20) * 0.001, 0.03)
330
+
331
+ # 2. Event Multiplier
332
+ evt = events_data.get(d_str)
333
+ evt_m = 1.0
334
+ info = None
335
+ if evt and evt["crowd_scale"] > 0 and check_location_match(req.location, evt["location"]):
336
+ evt_m = 1.0 + 0.10 + min(evt["crowd_scale"] * 0.05, 0.25)
337
+ info = f"{evt['event_name']} @ {evt['location']}"
338
+ elif req.event_scale > 0:
339
+ evt_m = 1.0 + req.event_scale * 0.10
340
+
341
+ raw_prediction = base * rain_m * evt_m
342
+ calibrated_volume = round(float(raw_prediction * calibration_factor), 2)
343
+
344
+ total_vol += calibrated_volume
345
+ risk = get_risk_status(calibrated_volume, req.location)
346
+ if risk == "CRITICAL": max_risk = "CRITICAL"
347
+ elif risk == "WARNING" and max_risk != "CRITICAL": max_risk = "WARNING"
348
+
349
+ hourly = distribute_to_hourly(calibrated_volume, req.location) if req.granularity == "hourly" else None
350
+
351
+ results.append(PredictionResult(
352
+ date=d_str, location=req.location, total_volume_ton=calibrated_volume,
353
+ organic_waste_ton=round(calibrated_volume*o_r, 2), plastic_waste_ton=round(calibrated_volume*p_r, 2),
354
+ recommended_trucks=max(1, int(np.ceil(calibrated_volume/5))),
355
+ risk_status=risk, event_info=info, hourly_breakdown=hourly
356
+ ))
357
+
358
+ elif req.model_type == "gradient_boosting":
359
+ if model_gbr is None:
360
+ raise HTTPException(503, "Gradient Boosting model not trained or loaded.")
361
 
362
+ # Holiday checker for major Indonesian holidays in 2026
363
+ def is_indonesian_holiday(date_obj):
364
+ m, d = date_obj.month, date_obj.day
365
+ holidays = {
366
+ (1, 1), (2, 17), (3, 18), (3, 19), (3, 20),
367
+ (4, 3), (5, 1), (5, 14), (5, 27), (5, 28),
368
+ (5, 31), (6, 16), (8, 17), (8, 25), (12, 25)
369
+ }
370
+ # Eid al-Fitr mudik window: March 15 to March 26
371
+ if m == 3 and (15 <= d <= 26):
372
+ return 1
373
+ if (m, d) in holidays:
374
+ return 1
375
+ return 0
376
+
377
+ # List of features used in the model
378
+ fitur_names = [
379
+ 'Loc_JIS', 'Loc_GBK', 'Loc_Pasar Senen', 'Loc_Gang Sempit Tambora',
380
+ 'RR', 'Rain_Lag_1', 'Rain_Lag_2', 'Is_Holiday', 'Ada_Event', 'Crowd_Scale',
381
+ 'Hari_Ke', 'Is_Weekend', 'Hari_Dalam_Minggu', 'Bulan'
382
+ ]
383
 
384
+ for i in range(req.forecast_days):
385
+ curr_date = start_date + timedelta(days=i)
386
+ d_str = curr_date.strftime("%Y-%m-%d")
387
+ d_lag1_str = (start_date + timedelta(days=i-1)).strftime("%Y-%m-%d")
388
+ d_lag2_str = (start_date + timedelta(days=i-2)).strftime("%Y-%m-%d")
389
+
390
+ # Retrieve rainfall and propagate overrides into lags
391
+ rain_today = req.rainfall_mm if (req.rainfall_mm > 0.0 and i == 0) else weather_forecast.get(d_str, 0.0)
392
+ rain_lag1 = req.rainfall_mm if (req.rainfall_mm > 0.0 and i == 1) else weather_forecast.get(d_lag1_str, 0.0)
393
+ rain_lag2 = req.rainfall_mm if (req.rainfall_mm > 0.0 and i == 2) else weather_forecast.get(d_lag2_str, 0.0)
394
+
395
+ evt = events_data.get(d_str)
396
+ has_event = 1 if (evt and check_location_match(req.location, evt["location"])) else 0
397
+ crowd = float(evt["crowd_scale"]) if has_event else (float(req.event_scale) if i == 0 else 0.0)
398
+ info = f"{evt['event_name']} @ {evt['location']}" if has_event else None
399
+
400
+ is_holiday = is_indonesian_holiday(curr_date)
401
+
402
+ # Fitur dataframe construction
403
+ features = pd.DataFrame([{
404
+ 'Loc_JIS': 1 if req.location == "JIS" else 0,
405
+ 'Loc_GBK': 1 if req.location == "GBK" else 0,
406
+ 'Loc_Pasar Senen': 1 if req.location == "Pasar Senen" else 0,
407
+ 'Loc_Gang Sempit Tambora': 1 if req.location == "Gang Sempit Tambora" else 0,
408
+ 'RR': rain_today,
409
+ 'Rain_Lag_1': rain_lag1,
410
+ 'Rain_Lag_2': rain_lag2,
411
+ 'Is_Holiday': is_holiday,
412
+ 'Ada_Event': has_event or (1 if (req.event_scale > 0 and i == 0) else 0),
413
+ 'Crowd_Scale': crowd,
414
+ 'Hari_Ke': curr_date.timetuple().tm_yday,
415
+ 'Is_Weekend': 1 if curr_date.weekday() >= 5 else 0,
416
+ 'Hari_Dalam_Minggu': curr_date.weekday(),
417
+ 'Bulan': curr_date.month
418
+ }])
419
+
420
+ # Reorder columns to match features used in train.py
421
+ features = features[fitur_names]
422
+
423
+ # Predict directly (model outputs calibrated localized tonnage!)
424
+ predicted_volume = float(model_gbr.predict(features)[0])
425
+ calibrated_volume = round(max(0.1, predicted_volume), 2)
426
+
427
+ total_vol += calibrated_volume
428
+ risk = get_risk_status(calibrated_volume, req.location)
429
+ if risk == "CRITICAL": max_risk = "CRITICAL"
430
+ elif risk == "WARNING" and max_risk != "CRITICAL": max_risk = "WARNING"
431
+
432
+ hourly = distribute_to_hourly(calibrated_volume, req.location) if req.granularity == "hourly" else None
433
+
434
+ results.append(PredictionResult(
435
+ date=d_str, location=req.location, total_volume_ton=calibrated_volume,
436
+ organic_waste_ton=round(calibrated_volume*o_r, 2), plastic_waste_ton=round(calibrated_volume*p_r, 2),
437
+ recommended_trucks=max(1, int(np.ceil(calibrated_volume/5))),
438
+ risk_status=risk, event_info=info, hourly_breakdown=hourly
439
+ ))
440
 
441
+ # Logistics Plan calculation
442
  trucks = sum([r.recommended_trucks for r in results])
443
  msg = f"CRITICAL at {req.location}!" if max_risk == "CRITICAL" else f"WARNING at {req.location}." if max_risk == "WARNING" else "Normal conditions."
444
 
445
+ # Return accuracy score dynamically (Chronos is default 0.92, GBR shows training test score ~0.93)
446
+ conf = 0.9325 if req.model_type == "gradient_boosting" else 0.92
447
+
448
  return APIResponse(
449
+ status="success", message=msg, confidence_score=conf,
450
  data=PredictionData(
451
  prediction_results=results,
452
+ logistics_plan=LogisticsPlan(
453
+ trucks_needed=trucks,
454
+ manpower=trucks*3,
455
+ estimated_duration_hours=round(total_vol/5, 1),
456
+ efficiency_rate="85% (Optimal)"
457
+ )
458
  )
459
  )
460
  except HTTPException: raise
 
462
  logger.error(f"Prediction failed: {e}", exc_info=True)
463
  raise HTTPException(500, str(e))
464
 
465
+ @app.post("/api/v1/predict/csv", tags=["Prediction"])
466
+ async def predict_waste_volume_csv(req: PredictionRequest):
467
+ """
468
+ Generate predictions and return them as a downloadable CSV stream directly.
469
+ """
470
+ res = await predict_waste_volume(req)
471
+
472
+ output = io.StringIO()
473
+ writer = csv.writer(output)
474
+
475
+ # Write CSV Header
476
+ writer.writerow([
477
+ "Date", "Location", "Total Volume (Tons)",
478
+ "Organic Waste (Tons)", "Plastic Waste (Tons)",
479
+ "Risk Status", "Event Info", "Recommended Trucks (5T)"
480
+ ])
481
+
482
+ # Write CSV Rows
483
+ for r in res.data.prediction_results:
484
+ writer.writerow([
485
+ r.date, r.location, r.total_volume_ton,
486
+ r.organic_waste_ton, r.plastic_waste_ton,
487
+ r.risk_status, r.event_info or "", r.recommended_trucks
488
+ ])
489
+
490
+ output.seek(0)
491
+
492
+ filename = f"waste_forecast_{req.location.replace(' ', '_')}_{req.forecast_days}d.csv"
493
+ return StreamingResponse(
494
+ io.BytesIO(output.getvalue().encode("utf-8")),
495
+ media_type="text/csv",
496
+ headers={"Content-Disposition": f"attachment; filename={filename}"}
497
+ )
498
+
499
  @app.get("/api/v1/alerts", response_model=AlertResponse, tags=["Alerts"])
500
  async def get_alerts(location: str = Query(None)):
501
  """Real-time alerts endpoint."""
 
503
 
504
  alerts = []
505
  today = datetime.now().date()
 
506
 
507
  for i in range(3):
508
  d = (today + timedelta(days=i)).strftime("%Y-%m-%d")
 
511
  for loc, config in LOCATION_BASELINES.items():
512
  if location and loc != location: continue
513
 
 
514
  baseline_vol = config["normal_avg"]
515
  if evt and evt["crowd_scale"] > 0 and check_location_match(loc, evt["location"]):
516
  baseline_vol = config["event_peak"]
 
518
  status = "CRITICAL" if baseline_vol > config["critical_threshold"] else "WARNING" if baseline_vol > config["warning_threshold"] else "SAFE"
519
 
520
  if status != "SAFE":
521
+ alerts.append({
522
+ "date": d, "location": loc, "status": status,
523
+ "estimated_volume_ton": baseline_vol,
524
+ "message": f"Alert: {status} volume expected at {loc}"
525
+ })
526
 
527
  return AlertResponse(status="success", alert_count=len(alerts), alerts=alerts, last_updated=datetime.now().isoformat())
dataset_local_2026.csv ADDED
The diff for this file is too large to render. See raw diff
 
dataset_vibe_coder_2026.csv CHANGED
@@ -1,366 +1,366 @@
1
  TANGGAL,RR,Nama_Event,Ada_Event,Crowd_Scale,Volume_Total_Ton,Vol_Sisa_Makanan_Ton,Vol_Plastik_Ton,Hari_Ke,Is_Weekend,ZONA
2
- 2026-01-01,12.8,New Year Countdown,1,4.0,1696.06,845.83,389.25,1,0,Tourism
3
- 2026-01-02,18.3,New Year Countdown,1,2.8,1680.17,837.9,385.6,2,0,Tourism
4
- 2026-01-03,17.6,,0,0.0,1140.46,568.75,261.74,3,1,Residential
5
- 2026-01-04,4.7,,0,0.0,1188.62,592.76,272.79,4,1,Residential
6
- 2026-01-05,0.0,,0,0.0,1049.35,523.31,240.83,5,0,Residential
7
- 2026-01-06,0.0,,0,0.0,1098.05,547.6,252.0,6,0,Residential
8
- 2026-01-07,11.0,,0,0.0,1156.52,576.76,265.42,7,0,Residential
9
- 2026-01-08,6.2,,0,0.0,1113.32,555.21,255.51,8,0,Residential
10
- 2026-01-09,0.0,,0,0.0,1078.87,538.03,247.6,9,0,Residential
11
- 2026-01-10,0.0,,0,0.0,1140.37,568.7,261.71,10,1,Residential
12
- 2026-01-11,0.2,,0,0.0,1276.39,636.54,292.93,11,1,Residential
13
- 2026-01-12,0.0,,0,0.0,1105.06,551.09,253.61,12,0,Residential
14
- 2026-01-13,0.0,,0,0.0,1038.74,518.02,238.39,13,0,Residential
15
- 2026-01-14,6.3,,0,0.0,1042.88,520.08,239.34,14,0,Residential
16
- 2026-01-15,6.6,,0,0.0,1042.87,520.08,239.34,15,0,Residential
17
- 2026-01-16,0.0,,0,0.0,1101.72,549.43,252.84,16,0,Residential
18
- 2026-01-17,0.0,,0,0.0,1132.37,564.71,259.88,17,1,Residential
19
- 2026-01-18,4.1,Car Free Day,1,1.5,1459.04,727.62,334.85,18,1,Tourism
20
- 2026-01-19,0.0,,0,0.0,1131.86,564.46,259.76,19,0,Residential
21
- 2026-01-20,0.0,,0,0.0,1174.02,585.48,269.44,20,0,Residential
22
- 2026-01-21,0.0,,0,0.0,1167.04,582.0,267.84,21,0,Residential
23
- 2026-01-22,0.0,,0,0.0,1104.18,550.65,253.41,22,0,Residential
24
- 2026-01-23,0.0,,0,0.0,1191.93,594.42,273.55,23,0,Residential
25
- 2026-01-24,0.0,,0,0.0,1323.26,659.91,303.69,24,1,Residential
26
- 2026-01-25,19.1,,0,0.0,1419.77,708.04,325.84,25,1,Residential
27
- 2026-01-26,0.0,,0,0.0,1241.13,618.95,284.84,26,0,Residential
28
- 2026-01-27,2.0,,0,0.0,1124.65,560.86,258.11,27,0,Residential
29
- 2026-01-28,0.0,,0,0.0,1121.14,559.11,257.3,28,0,Residential
30
- 2026-01-29,2.4,,0,0.0,986.89,492.16,226.49,29,0,Residential
31
- 2026-01-30,0.0,,0,0.0,1000.39,498.89,229.59,30,0,Residential
32
- 2026-01-31,0.0,,0,0.0,1045.87,521.58,240.03,31,1,Residential
33
- 2026-02-01,6.6,,0,0.0,1133.89,565.47,260.23,32,1,Residential
34
- 2026-02-02,0.0,,0,0.0,988.2,492.82,226.79,33,0,Residential
35
- 2026-02-03,7.7,,0,0.0,1037.72,517.51,238.16,34,0,Residential
36
- 2026-02-04,0.3,,0,0.0,1060.57,528.91,243.4,35,0,Residential
37
- 2026-02-05,0.0,,0,0.0,1122.54,559.81,257.62,36,0,Residential
38
- 2026-02-06,1.8,,0,0.0,1186.86,591.89,272.38,37,0,Residential
39
- 2026-02-07,0.0,,0,0.0,1253.17,624.96,287.6,38,1,Residential
40
- 2026-02-08,0.0,,0,0.0,1274.11,635.4,292.41,39,1,Residential
41
- 2026-02-09,12.6,,0,0.0,1171.27,584.11,268.81,40,0,Residential
42
- 2026-02-10,7.5,,0,0.0,1207.46,602.16,277.11,41,0,Residential
43
- 2026-02-11,0.0,,0,0.0,1114.12,555.61,255.69,42,0,Residential
44
- 2026-02-12,0.0,,0,0.0,1166.41,581.69,267.69,43,0,Residential
45
- 2026-02-13,0.0,,0,0.0,1212.38,604.61,278.24,44,0,Residential
46
- 2026-02-14,7.5,,0,0.0,1253.65,625.2,287.71,45,1,Residential
47
- 2026-02-15,1.5,Imlek & Glodok Festival,1,1.1,1484.69,740.41,340.74,46,1,Tourism
48
- 2026-02-16,10.6,Imlek & Glodok Festival,1,2.1,1431.13,713.7,328.44,47,0,Tourism
49
- 2026-02-17,0.0,Imlek & Glodok Festival,1,2.5,1528.72,762.37,350.84,48,0,Tourism
50
- 2026-02-18,2.0,Imlek & Glodok Festival,1,2.1,1450.14,723.18,332.81,49,0,Tourism
51
- 2026-02-19,0.0,Imlek & Glodok Festival,1,1.1,1338.97,667.74,307.29,50,0,Tourism
52
- 2026-02-20,5.6,,0,0.0,1140.89,568.96,261.83,51,0,Residential
53
- 2026-02-21,0.0,,0,0.0,1169.26,583.11,268.35,52,1,Residential
54
- 2026-02-22,0.0,,0,0.0,1191.79,594.35,273.52,53,1,Residential
55
- 2026-02-23,0.0,,0,0.0,1155.3,576.15,265.14,54,0,Residential
56
- 2026-02-24,0.0,,0,0.0,1161.89,579.43,266.65,55,0,Residential
57
- 2026-02-25,0.0,,0,0.0,1137.92,567.48,261.15,56,0,Residential
58
- 2026-02-26,14.6,,0,0.0,1223.47,610.14,280.79,57,0,Residential
59
- 2026-02-27,1.0,,0,0.0,1259.33,628.03,289.02,58,0,Residential
60
- 2026-02-28,0.0,,0,0.0,1276.59,636.64,292.98,59,1,Residential
61
- 2026-03-01,0.0,,0,0.0,1332.21,664.37,305.74,60,1,Residential
62
- 2026-03-02,3.1,,0,0.0,1184.97,590.94,271.95,61,0,Residential
63
- 2026-03-03,0.0,,0,0.0,1237.8,617.29,284.08,62,0,Residential
64
- 2026-03-04,1.2,,0,0.0,1282.48,639.57,294.33,63,0,Residential
65
- 2026-03-05,0.0,,0,0.0,1228.79,612.8,282.01,64,0,Residential
66
- 2026-03-06,5.4,,0,0.0,1283.1,639.88,294.47,65,0,Residential
67
- 2026-03-07,14.1,,0,0.0,1409.45,702.89,323.47,66,1,Residential
68
- 2026-03-08,0.0,,0,0.0,1377.82,687.12,316.21,67,1,Residential
69
- 2026-03-09,0.0,,0,0.0,1170.72,583.84,268.68,68,0,Residential
70
- 2026-03-10,8.3,,0,0.0,1176.19,586.57,269.94,69,0,Residential
71
- 2026-03-11,0.0,,0,0.0,1135.71,566.38,260.65,70,0,Residential
72
- 2026-03-12,0.0,,0,0.0,1112.95,555.03,255.42,71,0,Residential
73
- 2026-03-13,0.0,,0,0.0,1136.54,566.79,260.84,72,0,Residential
74
- 2026-03-14,6.0,,0,0.0,1278.86,637.77,293.5,73,1,Residential
75
- 2026-03-15,0.0,,0,0.0,1336.51,666.52,306.73,74,1,Residential
76
- 2026-03-16,14.5,,0,0.0,1179.92,588.43,270.79,75,0,Residential
77
- 2026-03-17,16.9,,0,0.0,1180.1,588.52,270.83,76,0,Residential
78
- 2026-03-18,9.8,H-3 Lebaran,1,2.7,1319.02,657.8,302.72,77,0,Residential
79
- 2026-03-19,14.3,H-3 Lebaran,1,4.0,1439.38,717.82,330.34,78,0,Residential
80
- 2026-03-20,9.3,Idul Fitri,1,2.3,1796.19,895.76,412.23,79,0,Residential
81
- 2026-03-21,0.0,Idul Fitri,1,4.1,1877.31,936.21,430.84,80,1,Residential
82
- 2026-03-22,1.5,Idul Fitri,1,5.0,2127.06,1060.76,488.16,81,1,Residential
83
- 2026-03-23,18.4,Idul Fitri,1,4.1,1581.28,788.58,362.9,82,0,Residential
84
- 2026-03-24,0.0,Idul Fitri,1,2.3,1338.39,667.46,307.16,83,0,Residential
85
- 2026-03-25,16.8,,0,0.0,1258.69,627.71,288.87,84,0,Residential
86
- 2026-03-26,17.6,,0,0.0,1253.6,625.17,287.7,85,0,Residential
87
- 2026-03-27,17.9,,0,0.0,1289.54,643.09,295.95,86,0,Residential
88
- 2026-03-28,0.0,,0,0.0,1308.3,652.45,300.25,87,1,Residential
89
- 2026-03-29,13.7,,0,0.0,1324.76,660.66,304.03,88,1,Residential
90
- 2026-03-30,11.7,,0,0.0,1164.96,580.97,267.36,89,0,Residential
91
- 2026-03-31,0.0,,0,0.0,1087.82,542.5,249.65,90,0,Residential
92
- 2026-04-01,0.0,,0,0.0,1165.27,581.12,267.43,91,0,Residential
93
- 2026-04-02,27.3,,0,0.0,1146.21,571.61,263.06,92,0,Residential
94
- 2026-04-03,0.0,,0,0.0,1183.38,590.15,271.59,93,0,Residential
95
- 2026-04-04,9.7,,0,0.0,1290.59,643.62,296.19,94,1,Residential
96
- 2026-04-05,0.0,,0,0.0,1277.15,636.91,293.11,95,1,Residential
97
- 2026-04-06,0.0,,0,0.0,1207.54,602.2,277.13,96,0,Residential
98
- 2026-04-07,24.4,,0,0.0,1221.73,609.28,280.39,97,0,Residential
99
- 2026-04-08,12.3,,0,0.0,1236.31,616.55,283.73,98,0,Residential
100
- 2026-04-09,0.0,Jakarta Art Festival,1,1.4,1438.29,717.28,330.09,99,0,Tourism
101
- 2026-04-10,14.8,Jakarta Art Festival,1,2.0,1573.75,784.83,361.18,100,0,Tourism
102
- 2026-04-11,9.0,Jakarta Art Festival,1,1.4,1509.46,752.77,346.42,101,1,Tourism
103
- 2026-04-12,0.0,,0,0.0,1282.51,639.59,294.34,102,1,Residential
104
- 2026-04-13,11.8,,0,0.0,1217.31,607.07,279.37,103,0,Residential
105
- 2026-04-14,27.3,,0,0.0,1194.52,595.71,274.14,104,0,Residential
106
- 2026-04-15,0.0,,0,0.0,1192.52,594.71,273.68,105,0,Residential
107
- 2026-04-16,0.0,,0,0.0,1230.3,613.55,282.35,106,0,Residential
108
- 2026-04-17,13.1,,0,0.0,1237.16,616.97,283.93,107,0,Residential
109
- 2026-04-18,12.4,,0,0.0,1349.32,672.91,309.67,108,1,Residential
110
- 2026-04-19,0.0,,0,0.0,1435.34,715.8,329.41,109,1,Residential
111
- 2026-04-20,0.0,,0,0.0,1344.25,670.38,308.51,110,0,Residential
112
- 2026-04-21,7.2,,0,0.0,1335.37,665.95,306.47,111,0,Residential
113
- 2026-04-22,24.3,,0,0.0,1308.14,652.37,300.22,112,0,Residential
114
- 2026-04-23,0.0,,0,0.0,1362.56,679.51,312.71,113,0,Residential
115
- 2026-04-24,0.0,,0,0.0,1317.66,657.12,302.4,114,0,Residential
116
- 2026-04-25,23.4,,0,0.0,1406.34,701.34,322.76,115,1,Residential
117
- 2026-04-26,0.0,,0,0.0,1318.36,657.47,302.56,116,1,Residential
118
- 2026-04-27,0.0,,0,0.0,1175.38,586.16,269.75,117,0,Residential
119
- 2026-04-28,37.7,,0,0.0,1256.5,626.62,288.37,118,0,Residential
120
- 2026-04-29,14.3,,0,0.0,1178.04,587.49,270.36,119,0,Residential
121
- 2026-04-30,0.0,May Day Rally,1,1.4,1476.41,736.29,338.84,120,0,Tourism
122
- 2026-05-01,14.5,May Day Rally,1,3.0,1689.53,842.57,387.75,121,0,Tourism
123
- 2026-05-02,13.4,May Day Rally,1,1.4,1569.05,782.49,360.1,122,1,Tourism
124
- 2026-05-03,10.0,,0,0.0,1280.63,638.65,293.9,123,1,Residential
125
- 2026-05-04,0.0,,0,0.0,1167.35,582.16,267.91,124,0,Residential
126
- 2026-05-05,26.7,,0,0.0,1212.21,604.53,278.2,125,0,Residential
127
- 2026-05-06,0.0,,0,0.0,1190.33,593.62,273.18,126,0,Residential
128
- 2026-05-07,0.0,,0,0.0,1199.01,597.95,275.17,127,0,Residential
129
- 2026-05-08,30.7,,0,0.0,1309.95,653.27,300.63,128,0,Residential
130
- 2026-05-09,30.6,,0,0.0,1384.28,690.34,317.69,129,1,Residential
131
- 2026-05-10,35.5,,0,0.0,1347.39,671.94,309.23,130,1,Residential
132
- 2026-05-11,30.4,,0,0.0,1190.81,593.86,273.29,131,0,Residential
133
- 2026-05-12,18.5,,0,0.0,1285.84,641.25,295.1,132,0,Residential
134
- 2026-05-13,27.4,,0,0.0,1189.41,593.16,272.97,133,0,Residential
135
- 2026-05-14,23.7,,0,0.0,1188.01,592.46,272.65,134,0,Residential
136
- 2026-05-15,0.0,,0,0.0,1251.28,624.01,287.17,135,0,Residential
137
- 2026-05-16,0.0,,0,0.0,1190.24,593.57,273.16,136,1,Residential
138
- 2026-05-17,0.0,,0,0.0,1338.92,667.72,307.28,137,1,Residential
139
- 2026-05-18,23.7,,0,0.0,1288.92,642.78,295.81,138,0,Residential
140
- 2026-05-19,0.0,,0,0.0,1232.84,614.82,282.94,139,0,Residential
141
- 2026-05-20,34.7,,0,0.0,1272.2,634.45,291.97,140,0,Residential
142
- 2026-05-21,21.7,,0,0.0,1222.5,609.66,280.56,141,0,Residential
143
- 2026-05-22,0.0,,0,0.0,1240.35,618.56,284.66,142,0,Residential
144
- 2026-05-23,32.7,,0,0.0,1325.67,661.11,304.24,143,1,Residential
145
- 2026-05-24,0.0,,0,0.0,1264.86,630.79,290.29,144,1,Residential
146
- 2026-05-25,9.7,,0,0.0,1219.79,608.31,279.94,145,0,Residential
147
- 2026-05-26,30.3,,0,0.0,1215.74,606.29,279.01,146,0,Residential
148
- 2026-05-27,25.1,,0,0.0,1250.5,623.62,286.99,147,0,Residential
149
- 2026-05-28,19.0,,0,0.0,1269.81,633.25,291.42,148,0,Residential
150
- 2026-05-29,36.3,PRJ Opening,1,2.3,1789.29,892.32,410.64,149,0,Tourism
151
- 2026-05-30,11.0,PRJ Opening,1,3.1,1994.38,994.6,457.71,150,1,Tourism
152
- 2026-05-31,0.0,PRJ Opening,1,3.8,2144.87,1069.65,492.25,151,1,Tourism
153
- 2026-06-01,19.9,PRJ Opening,1,4.0,2120.65,1057.57,486.69,152,0,Tourism
154
- 2026-06-02,0.0,PRJ Opening,1,3.8,1855.23,925.2,425.78,153,0,Tourism
155
- 2026-06-03,24.2,PRJ Opening,1,3.1,1605.67,800.75,368.5,154,0,Tourism
156
- 2026-06-04,0.0,PRJ Opening,1,2.3,1555.57,775.76,357.0,155,0,Tourism
157
- 2026-06-05,15.3,,0,0.0,1348.6,672.55,309.5,156,0,Residential
158
- 2026-06-06,0.0,,0,0.0,1411.46,703.9,323.93,157,1,Residential
159
- 2026-06-07,15.3,,0,0.0,1490.72,743.42,342.12,158,1,Residential
160
- 2026-06-08,25.0,,0,0.0,1367.21,681.83,313.77,159,0,Residential
161
- 2026-06-09,0.0,,0,0.0,1359.83,678.15,312.08,160,0,Residential
162
- 2026-06-10,17.8,,0,0.0,1324.46,660.51,303.96,161,0,Residential
163
- 2026-06-11,32.6,,0,0.0,1317.74,657.16,302.42,162,0,Residential
164
- 2026-06-12,25.4,,0,0.0,1327.58,662.06,304.68,163,0,Residential
165
- 2026-06-13,39.8,,0,0.0,1326.73,661.64,304.48,164,1,Residential
166
- 2026-06-14,0.0,Music Festival GBK,1,1.6,1702.74,849.16,390.78,165,1,Tourism
167
- 2026-06-15,29.6,Music Festival GBK,1,3.5,1814.32,904.8,416.39,166,0,Tourism
168
- 2026-06-16,33.7,Music Festival GBK,1,1.6,1690.19,842.9,387.9,167,0,Tourism
169
- 2026-06-17,0.0,,0,0.0,1179.07,588.0,270.6,168,0,Residential
170
- 2026-06-18,35.9,,0,0.0,1284.4,640.53,294.77,169,0,Residential
171
- 2026-06-19,22.7,,0,0.0,1376.88,686.65,315.99,170,0,Residential
172
- 2026-06-20,33.8,,0,0.0,1488.52,742.32,341.62,171,1,Residential
173
- 2026-06-21,34.9,,0,0.0,1498.51,747.31,343.91,172,1,Residential
174
- 2026-06-22,0.0,,0,0.0,1253.06,624.9,287.58,173,0,Residential
175
- 2026-06-23,27.4,,0,0.0,1282.71,639.69,294.38,174,0,Residential
176
- 2026-06-24,0.0,,0,0.0,1355.08,675.78,310.99,175,0,Residential
177
- 2026-06-25,0.0,,0,0.0,1424.11,710.2,326.83,176,0,Residential
178
- 2026-06-26,36.1,,0,0.0,1397.18,696.77,320.65,177,0,Residential
179
- 2026-06-27,0.0,,0,0.0,1317.93,657.25,302.46,178,1,Residential
180
- 2026-06-28,26.9,,0,0.0,1413.5,704.91,324.4,179,1,Residential
181
- 2026-06-29,34.7,,0,0.0,1376.99,686.7,316.02,180,0,Residential
182
- 2026-06-30,0.0,,0,0.0,1280.37,638.52,293.84,181,0,Residential
183
- 2026-07-01,26.6,,0,0.0,1289.92,643.28,296.04,182,0,Residential
184
- 2026-07-02,0.0,,0,0.0,1243.38,620.07,285.36,183,0,Residential
185
- 2026-07-03,8.4,,0,0.0,1277.79,637.23,293.25,184,0,Residential
186
- 2026-07-04,0.0,,0,0.0,1350.35,673.42,309.91,185,1,Residential
187
- 2026-07-05,12.2,,0,0.0,1401.33,698.84,321.61,186,1,Residential
188
- 2026-07-06,0.0,,0,0.0,1174.55,585.75,269.56,187,0,Residential
189
- 2026-07-07,0.0,,0,0.0,1274.83,635.76,292.57,188,0,Residential
190
- 2026-07-08,31.3,,0,0.0,1330.56,663.55,305.36,189,0,Residential
191
- 2026-07-09,0.0,,0,0.0,1194.99,595.94,274.25,190,0,Residential
192
- 2026-07-10,38.5,,0,0.0,1199.44,598.16,275.27,191,0,Residential
193
- 2026-07-11,22.5,,0,0.0,1297.1,646.86,297.68,192,1,Residential
194
- 2026-07-12,28.0,,0,0.0,1452.0,724.11,333.23,193,1,Residential
195
- 2026-07-13,31.1,,0,0.0,1349.59,673.04,309.73,194,0,Residential
196
- 2026-07-14,23.2,,0,0.0,1240.46,618.62,284.69,195,0,Residential
197
- 2026-07-15,45.0,,0,0.0,1317.83,657.2,302.44,196,0,Residential
198
- 2026-07-16,27.6,,0,0.0,1165.91,581.44,267.58,197,0,Residential
199
- 2026-07-17,30.6,,0,0.0,1178.77,587.85,270.53,198,0,Residential
200
- 2026-07-18,40.0,,0,0.0,1278.2,637.44,293.35,199,1,Residential
201
- 2026-07-19,35.9,PRJ Peak Weekend,1,3.5,2042.55,1018.62,468.77,200,1,Tourism
202
- 2026-07-20,0.0,PRJ Peak Weekend,1,5.0,2037.52,1016.11,467.61,201,0,Tourism
203
- 2026-07-21,21.0,PRJ Peak Weekend,1,3.5,1891.77,943.43,434.16,202,0,Tourism
204
- 2026-07-22,0.0,,0,0.0,1265.2,630.96,290.36,203,0,Residential
205
- 2026-07-23,32.4,,0,0.0,1210.79,603.82,277.88,204,0,Residential
206
- 2026-07-24,22.5,,0,0.0,1234.0,615.4,283.2,205,0,Residential
207
- 2026-07-25,0.0,,0,0.0,1327.05,661.8,304.56,206,1,Residential
208
- 2026-07-26,28.6,,0,0.0,1407.14,701.74,322.94,207,1,Residential
209
- 2026-07-27,25.7,,0,0.0,1239.27,618.02,284.41,208,0,Residential
210
- 2026-07-28,0.0,,0,0.0,1179.16,588.05,270.62,209,0,Residential
211
- 2026-07-29,18.4,,0,0.0,1245.29,621.03,285.79,210,0,Residential
212
- 2026-07-30,19.8,,0,0.0,1249.0,622.88,286.65,211,0,Residential
213
- 2026-07-31,30.9,,0,0.0,1258.28,627.5,288.78,212,0,Residential
214
- 2026-08-01,0.0,,0,0.0,1221.3,609.06,280.29,213,1,Residential
215
- 2026-08-02,0.0,,0,0.0,1286.77,641.71,295.31,214,1,Residential
216
- 2026-08-03,17.8,,0,0.0,1217.89,607.36,279.51,215,0,Residential
217
- 2026-08-04,0.0,,0,0.0,1265.38,631.05,290.4,216,0,Residential
218
- 2026-08-05,0.0,,0,0.0,1291.66,644.15,296.44,217,0,Residential
219
- 2026-08-06,19.7,,0,0.0,1235.79,616.29,283.61,218,0,Residential
220
- 2026-08-07,23.8,,0,0.0,1228.69,612.75,281.98,219,0,Residential
221
- 2026-08-08,0.0,,0,0.0,1325.93,661.24,304.3,220,1,Residential
222
- 2026-08-09,18.4,,0,0.0,1393.9,695.14,319.9,221,1,Residential
223
- 2026-08-10,27.2,,0,0.0,1171.09,584.02,268.77,222,0,Residential
224
- 2026-08-11,0.0,,0,0.0,1157.77,577.38,265.71,223,0,Residential
225
- 2026-08-12,26.1,,0,0.0,1114.41,555.76,255.76,224,0,Residential
226
- 2026-08-13,43.9,,0,0.0,1203.8,600.34,276.27,225,0,Residential
227
- 2026-08-14,25.7,,0,0.0,1192.9,594.9,273.77,226,0,Residential
228
- 2026-08-15,26.2,HUT RI ke-81,1,1.8,1688.51,842.06,387.51,227,1,Tourism
229
- 2026-08-16,0.0,HUT RI ke-81,1,3.3,1853.79,924.49,425.44,228,1,Tourism
230
- 2026-08-17,15.6,HUT RI ke-81,1,4.0,1809.94,902.62,415.38,229,0,Tourism
231
- 2026-08-18,0.0,HUT RI ke-81,1,3.3,1741.77,868.62,399.74,230,0,Tourism
232
- 2026-08-19,30.2,HUT RI ke-81,1,1.8,1548.15,772.06,355.3,231,0,Tourism
233
- 2026-08-20,0.0,,0,0.0,1031.94,514.63,236.83,232,0,Residential
234
- 2026-08-21,25.9,,0,0.0,1113.72,555.41,255.6,233,0,Residential
235
- 2026-08-22,0.0,,0,0.0,1211.49,604.17,278.04,234,1,Residential
236
- 2026-08-23,24.6,,0,0.0,1303.86,650.23,299.24,235,1,Residential
237
- 2026-08-24,0.0,,0,0.0,1128.57,562.82,259.01,236,0,Residential
238
- 2026-08-25,19.5,,0,0.0,1156.89,576.94,265.51,237,0,Residential
239
- 2026-08-26,17.1,,0,0.0,1198.25,597.57,275.0,238,0,Residential
240
- 2026-08-27,0.0,,0,0.0,1195.31,596.1,274.32,239,0,Residential
241
- 2026-08-28,21.4,,0,0.0,1182.37,589.65,271.35,240,0,Residential
242
- 2026-08-29,9.5,,0,0.0,1212.5,604.67,278.27,241,1,Residential
243
- 2026-08-30,9.3,,0,0.0,1261.2,628.96,289.45,242,1,Residential
244
- 2026-08-31,0.0,,0,0.0,1162.08,579.53,266.7,243,0,Residential
245
- 2026-09-01,0.0,,0,0.0,1157.85,577.42,265.73,244,0,Residential
246
- 2026-09-02,9.1,,0,0.0,1080.53,538.86,247.98,245,0,Residential
247
- 2026-09-03,0.0,,0,0.0,1094.29,545.72,251.14,246,0,Residential
248
- 2026-09-04,16.4,,0,0.0,1092.75,544.95,250.79,247,0,Residential
249
- 2026-09-05,10.4,,0,0.0,987.42,492.43,226.61,248,1,Residential
250
- 2026-09-06,11.4,,0,0.0,1095.05,546.1,251.31,249,1,Residential
251
- 2026-09-07,31.2,,0,0.0,1151.06,574.03,264.17,250,0,Residential
252
- 2026-09-08,18.6,,0,0.0,1154.8,575.9,265.03,251,0,Residential
253
- 2026-09-09,17.1,,0,0.0,1090.63,543.9,250.3,252,0,Residential
254
- 2026-09-10,16.4,,0,0.0,1158.39,577.69,265.85,253,0,Residential
255
- 2026-09-11,19.9,,0,0.0,1152.3,574.65,264.45,254,0,Residential
256
- 2026-09-12,0.0,,0,0.0,1241.85,619.31,285.0,255,1,Residential
257
- 2026-09-13,0.0,,0,0.0,1366.75,681.6,313.67,256,1,Residential
258
- 2026-09-14,23.7,Food & Culture Expo,1,1.8,1586.21,791.04,364.04,257,0,Tourism
259
- 2026-09-15,10.9,Food & Culture Expo,1,2.5,1524.71,760.37,349.92,258,0,Tourism
260
- 2026-09-16,19.9,Food & Culture Expo,1,1.8,1602.81,799.32,367.84,259,0,Tourism
261
- 2026-09-17,9.2,,0,0.0,1118.5,557.8,256.7,260,0,Residential
262
- 2026-09-18,0.0,,0,0.0,1134.77,565.91,260.43,261,0,Residential
263
- 2026-09-19,2.9,,0,0.0,1151.23,574.12,264.21,262,1,Residential
264
- 2026-09-20,0.0,,0,0.0,1265.47,631.09,290.43,263,1,Residential
265
- 2026-09-21,14.8,,0,0.0,1231.35,614.07,282.59,264,0,Residential
266
- 2026-09-22,19.1,,0,0.0,1228.88,612.84,282.03,265,0,Residential
267
- 2026-09-23,12.5,,0,0.0,1165.05,581.01,267.38,266,0,Residential
268
- 2026-09-24,11.3,,0,0.0,1130.59,563.83,259.47,267,0,Residential
269
- 2026-09-25,5.2,,0,0.0,1172.03,584.49,268.98,268,0,Residential
270
- 2026-09-26,10.0,,0,0.0,1228.18,612.49,281.87,269,1,Residential
271
- 2026-09-27,0.0,,0,0.0,1228.48,612.64,281.94,270,1,Residential
272
- 2026-09-28,0.0,,0,0.0,1076.39,536.8,247.03,271,0,Residential
273
- 2026-09-29,0.0,,0,0.0,1088.2,542.69,249.74,272,0,Residential
274
- 2026-09-30,13.5,,0,0.0,1072.36,534.79,246.11,273,0,Residential
275
- 2026-10-01,0.0,,0,0.0,1159.44,578.21,266.09,274,0,Residential
276
- 2026-10-02,8.7,,0,0.0,1061.84,529.54,243.69,275,0,Residential
277
- 2026-10-03,0.0,,0,0.0,1083.46,540.32,248.65,276,1,Residential
278
- 2026-10-04,0.0,,0,0.0,1036.04,516.67,237.77,277,1,Residential
279
- 2026-10-05,1.6,,0,0.0,960.74,479.12,220.49,278,0,Residential
280
- 2026-10-06,0.0,,0,0.0,970.83,484.15,222.81,279,0,Residential
281
- 2026-10-07,0.0,,0,0.0,1030.23,513.78,236.44,280,0,Residential
282
- 2026-10-08,7.8,,0,0.0,1051.25,524.26,241.26,281,0,Residential
283
- 2026-10-09,12.6,Jakarta Marathon,1,1.4,1267.7,632.2,290.94,282,0,Tourism
284
- 2026-10-10,0.0,Jakarta Marathon,1,3.0,1591.74,793.8,365.3,283,1,Tourism
285
- 2026-10-11,0.0,Jakarta Marathon,1,1.4,1480.01,738.08,339.66,284,1,Tourism
286
- 2026-10-12,0.0,,0,0.0,1154.37,575.68,264.93,285,0,Residential
287
- 2026-10-13,23.0,,0,0.0,1137.95,567.5,261.16,286,0,Residential
288
- 2026-10-14,0.0,,0,0.0,1207.07,601.97,277.02,287,0,Residential
289
- 2026-10-15,13.0,,0,0.0,1199.22,598.05,275.22,288,0,Residential
290
- 2026-10-16,4.8,,0,0.0,1174.15,585.55,269.47,289,0,Residential
291
- 2026-10-17,0.0,,0,0.0,1210.3,603.58,277.76,290,1,Residential
292
- 2026-10-18,0.0,,0,0.0,1208.7,602.78,277.4,291,1,Residential
293
- 2026-10-19,0.0,,0,0.0,1033.53,515.42,237.2,292,0,Residential
294
- 2026-10-20,0.0,,0,0.0,1004.81,501.1,230.6,293,0,Residential
295
- 2026-10-21,9.6,,0,0.0,1092.89,545.02,250.82,294,0,Residential
296
- 2026-10-22,16.8,,0,0.0,1145.34,571.18,262.86,295,0,Residential
297
- 2026-10-23,0.0,,0,0.0,1064.46,530.85,244.29,296,0,Residential
298
- 2026-10-24,0.0,,0,0.0,1098.36,547.75,252.07,297,1,Residential
299
- 2026-10-25,16.6,,0,0.0,1175.09,586.02,269.68,298,1,Residential
300
- 2026-10-26,0.0,,0,0.0,1075.67,536.44,246.87,299,0,Residential
301
- 2026-10-27,0.0,,0,0.0,1087.48,542.33,249.58,300,0,Residential
302
- 2026-10-28,0.0,,0,0.0,1126.69,561.88,258.58,301,0,Residential
303
- 2026-10-29,0.0,,0,0.0,1073.27,535.24,246.32,302,0,Residential
304
- 2026-10-30,0.0,,0,0.0,1072.46,534.84,246.13,303,0,Residential
305
- 2026-10-31,0.0,,0,0.0,1091.02,544.09,250.39,304,1,Residential
306
- 2026-11-01,0.0,,0,0.0,1139.56,568.3,261.53,305,1,Residential
307
- 2026-11-02,0.0,,0,0.0,1102.99,550.06,253.14,306,0,Residential
308
- 2026-11-03,0.7,,0,0.0,1063.13,530.18,243.99,307,0,Residential
309
- 2026-11-04,0.0,,0,0.0,979.44,488.45,224.78,308,0,Residential
310
- 2026-11-05,0.0,,0,0.0,1042.88,520.08,239.34,309,0,Residential
311
- 2026-11-06,0.0,,0,0.0,1089.48,543.32,250.04,310,0,Residential
312
- 2026-11-07,0.0,,0,0.0,1105.66,551.39,253.75,311,1,Residential
313
- 2026-11-08,7.0,,0,0.0,1181.19,589.06,271.08,312,1,Residential
314
- 2026-11-09,0.0,,0,0.0,1139.02,568.03,261.41,313,0,Residential
315
- 2026-11-10,2.8,,0,0.0,1169.42,583.19,268.38,314,0,Residential
316
- 2026-11-11,0.0,,0,0.0,1144.95,570.99,262.77,315,0,Residential
317
- 2026-11-12,5.1,,0,0.0,1092.95,545.05,250.83,316,0,Residential
318
- 2026-11-13,0.0,,0,0.0,1083.25,540.22,248.61,317,0,Residential
319
- 2026-11-14,0.0,,0,0.0,1130.69,563.88,259.49,318,1,Residential
320
- 2026-11-15,0.0,,0,0.0,1107.53,552.33,254.18,319,1,Residential
321
- 2026-11-16,0.0,,0,0.0,916.92,457.27,210.43,320,0,Residential
322
- 2026-11-17,0.0,,0,0.0,954.94,476.23,219.16,321,0,Residential
323
- 2026-11-18,0.0,,0,0.0,967.1,482.29,221.95,322,0,Residential
324
- 2026-11-19,1.2,,0,0.0,1030.06,513.69,236.4,323,0,Residential
325
- 2026-11-20,0.0,,0,0.0,1055.09,526.17,242.14,324,0,Residential
326
- 2026-11-21,0.0,,0,0.0,1131.35,564.2,259.64,325,1,Residential
327
- 2026-11-22,1.3,,0,0.0,1147.99,572.5,263.46,326,1,Residential
328
- 2026-11-23,0.0,,0,0.0,1050.8,524.03,241.16,327,0,Residential
329
- 2026-11-24,0.0,Ancol Music Fest,1,1.4,1327.34,661.94,304.62,328,0,Tourism
330
- 2026-11-25,9.1,Ancol Music Fest,1,3.0,1532.54,764.28,351.72,329,0,Tourism
331
- 2026-11-26,0.7,Ancol Music Fest,1,1.4,1395.51,695.94,320.27,330,0,Tourism
332
- 2026-11-27,0.0,,0,0.0,1066.82,532.02,244.84,331,0,Residential
333
- 2026-11-28,5.7,,0,0.0,1069.89,533.55,245.54,332,1,Residential
334
- 2026-11-29,0.0,,0,0.0,1141.63,569.33,262.0,333,1,Residential
335
- 2026-11-30,0.0,,0,0.0,1050.37,523.82,241.06,334,0,Residential
336
- 2026-12-01,0.0,,0,0.0,1061.99,529.61,243.73,335,0,Residential
337
- 2026-12-02,0.0,,0,0.0,1047.7,522.49,240.45,336,0,Residential
338
- 2026-12-03,0.0,,0,0.0,1070.87,534.04,245.76,337,0,Residential
339
- 2026-12-04,6.5,,0,0.0,1098.7,547.92,252.15,338,0,Residential
340
- 2026-12-05,0.0,,0,0.0,1159.19,578.09,266.03,339,1,Residential
341
- 2026-12-06,1.0,,0,0.0,1169.48,583.22,268.4,340,1,Residential
342
- 2026-12-07,0.0,,0,0.0,1016.41,506.88,233.27,341,0,Residential
343
- 2026-12-08,11.3,,0,0.0,1004.74,501.06,230.59,342,0,Residential
344
- 2026-12-09,0.0,,0,0.0,1053.13,525.2,241.69,343,0,Residential
345
- 2026-12-10,0.0,,0,0.0,1108.26,552.69,254.35,344,0,Residential
346
- 2026-12-11,0.0,,0,0.0,1025.47,511.4,235.35,345,0,Residential
347
- 2026-12-12,11.7,,0,0.0,1079.56,538.38,247.76,346,1,Residential
348
- 2026-12-13,0.0,,0,0.0,1171.86,584.41,268.94,347,1,Residential
349
- 2026-12-14,0.0,,0,0.0,1109.91,553.51,254.72,348,0,Residential
350
- 2026-12-15,0.0,,0,0.0,1122.56,559.82,257.63,349,0,Residential
351
- 2026-12-16,0.0,,0,0.0,1168.55,582.76,268.18,350,0,Residential
352
- 2026-12-17,0.0,,0,0.0,1203.69,600.28,276.25,351,0,Residential
353
- 2026-12-18,3.4,Christmas Market,1,2.1,1445.72,720.98,331.79,352,0,Tourism
354
- 2026-12-19,0.0,Christmas Market,1,3.1,1559.14,777.54,357.82,353,1,Tourism
355
- 2026-12-20,0.0,Christmas Market,1,3.5,1708.29,851.92,392.05,354,1,Tourism
356
- 2026-12-21,7.5,Christmas Market,1,3.1,1457.21,726.71,334.43,355,0,Tourism
357
- 2026-12-22,5.1,Christmas Market,1,2.1,1410.85,703.59,323.79,356,0,Tourism
358
- 2026-12-23,7.3,,0,0.0,1171.39,584.17,268.83,357,0,Residential
359
- 2026-12-24,0.0,,0,0.0,1133.24,565.15,260.08,358,0,Residential
360
- 2026-12-25,2.9,,0,0.0,1123.76,560.42,257.9,359,0,Residential
361
- 2026-12-26,0.0,,0,0.0,1197.6,597.24,274.85,360,1,Residential
362
- 2026-12-27,0.0,,0,0.0,1198.69,597.79,275.1,361,1,Residential
363
- 2026-12-28,0.0,,0,0.0,1050.24,523.75,241.03,362,0,Residential
364
- 2026-12-29,0.0,,0,0.0,1142.55,569.79,262.22,363,0,Residential
365
- 2026-12-30,0.0,Countdown Jakarta 2027,1,3.2,1641.85,818.79,376.8,364,0,Tourism
366
- 2026-12-31,10.7,Countdown Jakarta 2027,1,4.5,1756.35,875.89,403.08,365,0,Tourism
 
1
  TANGGAL,RR,Nama_Event,Ada_Event,Crowd_Scale,Volume_Total_Ton,Vol_Sisa_Makanan_Ton,Vol_Plastik_Ton,Hari_Ke,Is_Weekend,ZONA
2
+ 2026-01-01,12.8,New Year Countdown,1,4.0,11872.42,5920.78,2724.72,1,0,Tourism
3
+ 2026-01-02,18.3,New Year Countdown,1,2.8,11761.19,5865.31,2699.19,2,0,Tourism
4
+ 2026-01-03,17.6,,0,0.0,7983.22,3981.23,1832.15,3,1,Residential
5
+ 2026-01-04,4.7,,0,0.0,8320.34,4149.35,1909.52,4,1,Residential
6
+ 2026-01-05,0.0,,0,0.0,7345.45,3663.18,1685.78,5,0,Residential
7
+ 2026-01-06,0.0,,0,0.0,7686.35,3833.18,1764.02,6,0,Residential
8
+ 2026-01-07,11.0,,0,0.0,8095.64,4037.3,1857.95,7,0,Residential
9
+ 2026-01-08,6.2,,0,0.0,7793.24,3886.49,1788.55,8,0,Residential
10
+ 2026-01-09,0.0,,0,0.0,7552.09,3766.23,1733.2,9,0,Residential
11
+ 2026-01-10,0.0,,0,0.0,7982.59,3980.92,1832.0,10,1,Residential
12
+ 2026-01-11,0.2,,0,0.0,8934.73,4455.75,2050.52,11,1,Residential
13
+ 2026-01-12,0.0,,0,0.0,7735.42,3857.65,1775.28,12,0,Residential
14
+ 2026-01-13,0.0,,0,0.0,7271.18,3626.14,1668.74,13,0,Residential
15
+ 2026-01-14,6.3,,0,0.0,7300.16,3640.59,1675.39,14,0,Residential
16
+ 2026-01-15,6.6,,0,0.0,7300.09,3640.55,1675.37,15,0,Residential
17
+ 2026-01-16,0.0,,0,0.0,7712.04,3845.99,1769.91,16,0,Residential
18
+ 2026-01-17,0.0,,0,0.0,7926.59,3952.99,1819.15,17,1,Residential
19
+ 2026-01-18,4.1,Car Free Day,1,1.5,10213.28,5093.36,2343.95,18,1,Tourism
20
+ 2026-01-19,0.0,,0,0.0,7923.02,3951.21,1818.33,19,0,Residential
21
+ 2026-01-20,0.0,,0,0.0,8218.14,4098.39,1886.06,20,0,Residential
22
+ 2026-01-21,0.0,,0,0.0,8169.28,4074.02,1874.85,21,0,Residential
23
+ 2026-01-22,0.0,,0,0.0,7729.26,3854.58,1773.87,22,0,Residential
24
+ 2026-01-23,0.0,,0,0.0,8343.51,4160.91,1914.84,23,0,Residential
25
+ 2026-01-24,0.0,,0,0.0,9262.82,4619.37,2125.82,24,1,Residential
26
+ 2026-01-25,19.1,,0,0.0,9938.39,4956.28,2280.86,25,1,Residential
27
+ 2026-01-26,0.0,,0,0.0,8687.91,4332.66,1993.88,26,0,Residential
28
+ 2026-01-27,2.0,,0,0.0,7872.55,3926.04,1806.75,27,0,Residential
29
+ 2026-01-28,0.0,,0,0.0,7847.98,3913.79,1801.11,28,0,Residential
30
+ 2026-01-29,2.4,,0,0.0,6908.23,3445.13,1585.44,29,0,Residential
31
+ 2026-01-30,0.0,,0,0.0,7002.73,3492.26,1607.13,30,0,Residential
32
+ 2026-01-31,0.0,,0,0.0,7321.09,3651.03,1680.19,31,1,Residential
33
+ 2026-02-01,6.6,,0,0.0,7937.23,3958.3,1821.59,32,1,Residential
34
+ 2026-02-02,0.0,,0,0.0,6917.4,3449.71,1587.54,33,0,Residential
35
+ 2026-02-03,7.7,,0,0.0,7264.04,3622.58,1667.1,34,0,Residential
36
+ 2026-02-04,0.3,,0,0.0,7423.99,3702.34,1703.81,35,0,Residential
37
+ 2026-02-05,0.0,,0,0.0,7857.78,3918.67,1803.36,36,0,Residential
38
+ 2026-02-06,1.8,,0,0.0,8308.02,4143.21,1906.69,37,0,Residential
39
+ 2026-02-07,0.0,,0,0.0,8772.19,4374.69,2013.22,38,1,Residential
40
+ 2026-02-08,0.0,,0,0.0,8918.77,4447.79,2046.86,39,1,Residential
41
+ 2026-02-09,12.6,,0,0.0,8198.89,4088.79,1881.65,40,0,Residential
42
+ 2026-02-10,7.5,,0,0.0,8452.22,4215.12,1939.78,41,0,Residential
43
+ 2026-02-11,0.0,,0,0.0,7798.84,3889.28,1789.83,42,0,Residential
44
+ 2026-02-12,0.0,,0,0.0,8164.87,4071.82,1873.84,43,0,Residential
45
+ 2026-02-13,0.0,,0,0.0,8486.66,4232.3,1947.69,44,0,Residential
46
+ 2026-02-14,7.5,,0,0.0,8775.55,4376.37,2013.99,45,1,Residential
47
+ 2026-02-15,1.5,Imlek & Glodok Festival,1,1.1,10392.83,5182.9,2385.15,46,1,Tourism
48
+ 2026-02-16,10.6,Imlek & Glodok Festival,1,2.1,10017.91,4995.93,2299.11,47,0,Tourism
49
+ 2026-02-17,0.0,Imlek & Glodok Festival,1,2.5,10701.04,5336.61,2455.89,48,0,Tourism
50
+ 2026-02-18,2.0,Imlek & Glodok Festival,1,2.1,10150.98,5062.29,2329.65,49,0,Tourism
51
+ 2026-02-19,0.0,Imlek & Glodok Festival,1,1.1,9372.79,4674.21,2151.06,50,0,Tourism
52
+ 2026-02-20,5.6,,0,0.0,7986.23,3982.73,1832.84,51,0,Residential
53
+ 2026-02-21,0.0,,0,0.0,8184.82,4081.77,1878.42,52,1,Residential
54
+ 2026-02-22,0.0,,0,0.0,8342.53,4160.42,1914.61,53,1,Residential
55
+ 2026-02-23,0.0,,0,0.0,8087.1,4033.04,1855.99,54,0,Residential
56
+ 2026-02-24,0.0,,0,0.0,8133.23,4056.04,1866.58,55,0,Residential
57
+ 2026-02-25,0.0,,0,0.0,7965.44,3972.36,1828.07,56,0,Residential
58
+ 2026-02-26,14.6,,0,0.0,8564.29,4271.01,1965.5,57,0,Residential
59
+ 2026-02-27,1.0,,0,0.0,8815.31,4396.2,2023.11,58,0,Residential
60
+ 2026-02-28,0.0,,0,0.0,8936.13,4456.45,2050.84,59,1,Residential
61
+ 2026-03-01,0.0,,0,0.0,9325.47,4650.61,2140.2,60,1,Residential
62
+ 2026-03-02,3.1,,0,0.0,8294.79,4136.61,1903.65,61,0,Residential
63
+ 2026-03-03,0.0,,0,0.0,8664.6,4321.04,1988.53,62,0,Residential
64
+ 2026-03-04,1.2,,0,0.0,8977.36,4477.01,2060.3,63,0,Residential
65
+ 2026-03-05,0.0,,0,0.0,8601.53,4289.58,1974.05,64,0,Residential
66
+ 2026-03-06,5.4,,0,0.0,8981.7,4479.17,2061.3,65,0,Residential
67
+ 2026-03-07,14.1,,0,0.0,9866.15,4920.25,2264.28,66,1,Residential
68
+ 2026-03-08,0.0,,0,0.0,9644.74,4809.83,2213.47,67,1,Residential
69
+ 2026-03-09,0.0,,0,0.0,8195.04,4086.87,1880.76,68,0,Residential
70
+ 2026-03-10,8.3,,0,0.0,8233.33,4105.96,1889.55,69,0,Residential
71
+ 2026-03-11,0.0,,0,0.0,7949.97,3964.65,1824.52,70,0,Residential
72
+ 2026-03-12,0.0,,0,0.0,7790.65,3885.2,1787.95,71,0,Residential
73
+ 2026-03-13,0.0,,0,0.0,7955.78,3967.55,1825.85,72,0,Residential
74
+ 2026-03-14,6.0,,0,0.0,8952.02,4464.37,2054.49,73,1,Residential
75
+ 2026-03-15,0.0,,0,0.0,9355.57,4665.62,2147.1,74,1,Residential
76
+ 2026-03-16,14.5,,0,0.0,8259.44,4118.98,1895.54,75,0,Residential
77
+ 2026-03-17,16.9,,0,0.0,8260.7,4119.61,1895.83,76,0,Residential
78
+ 2026-03-18,9.8,H-3 Lebaran,1,2.7,9233.14,4604.57,2119.01,77,0,Residential
79
+ 2026-03-19,14.3,H-3 Lebaran,1,4.0,10075.66,5024.73,2312.36,78,0,Residential
80
+ 2026-03-20,9.3,Idul Fitri,1,2.3,12573.33,6270.32,2885.58,79,0,Residential
81
+ 2026-03-21,0.0,Idul Fitri,1,4.1,13141.17,6553.5,3015.9,80,1,Residential
82
+ 2026-03-22,1.5,Idul Fitri,1,5.0,14889.42,7425.35,3417.12,81,1,Residential
83
+ 2026-03-23,18.4,Idul Fitri,1,4.1,11068.96,5520.09,2540.33,82,0,Residential
84
+ 2026-03-24,0.0,Idul Fitri,1,2.3,9368.73,4672.19,2150.12,83,0,Residential
85
+ 2026-03-25,16.8,,0,0.0,8810.83,4393.96,2022.09,84,0,Residential
86
+ 2026-03-26,17.6,,0,0.0,8775.2,4376.19,2013.91,85,0,Residential
87
+ 2026-03-27,17.9,,0,0.0,9026.78,4501.66,2071.65,86,0,Residential
88
+ 2026-03-28,0.0,,0,0.0,9158.1,4567.14,2101.78,87,1,Residential
89
+ 2026-03-29,13.7,,0,0.0,9273.32,4624.6,2128.23,88,1,Residential
90
+ 2026-03-30,11.7,,0,0.0,8154.72,4066.76,1871.51,89,0,Residential
91
+ 2026-03-31,0.0,,0,0.0,7614.74,3797.47,1747.58,90,0,Residential
92
+ 2026-04-01,0.0,,0,0.0,8156.89,4067.84,1872.01,91,0,Residential
93
+ 2026-04-02,27.3,,0,0.0,8023.47,4001.3,1841.39,92,0,Residential
94
+ 2026-04-03,0.0,,0,0.0,8283.66,4131.06,1901.1,93,0,Residential
95
+ 2026-04-04,9.7,,0,0.0,9034.13,4505.32,2073.33,94,1,Residential
96
+ 2026-04-05,0.0,,0,0.0,8940.05,4458.4,2051.74,95,1,Residential
97
+ 2026-04-06,0.0,,0,0.0,8452.78,4215.4,1939.91,96,0,Residential
98
+ 2026-04-07,24.4,,0,0.0,8552.11,4264.94,1962.71,97,0,Residential
99
+ 2026-04-08,12.3,,0,0.0,8654.17,4315.83,1986.13,98,0,Residential
100
+ 2026-04-09,0.0,Jakarta Art Festival,1,1.4,10068.03,5020.93,2310.61,99,0,Tourism
101
+ 2026-04-10,14.8,Jakarta Art Festival,1,2.0,11016.25,5493.8,2528.23,100,0,Tourism
102
+ 2026-04-11,9.0,Jakarta Art Festival,1,1.4,10566.22,5269.37,2424.95,101,1,Tourism
103
+ 2026-04-12,0.0,,0,0.0,8977.57,4477.11,2060.35,102,1,Residential
104
+ 2026-04-13,11.8,,0,0.0,8521.17,4249.51,1955.61,103,0,Residential
105
+ 2026-04-14,27.3,,0,0.0,8361.64,4169.95,1919.0,104,0,Residential
106
+ 2026-04-15,0.0,,0,0.0,8347.64,4162.97,1915.78,105,0,Residential
107
+ 2026-04-16,0.0,,0,0.0,8612.1,4294.85,1976.48,106,0,Residential
108
+ 2026-04-17,13.1,,0,0.0,8660.12,4318.8,1987.5,107,0,Residential
109
+ 2026-04-18,12.4,,0,0.0,9445.24,4710.34,2167.68,108,1,Residential
110
+ 2026-04-19,0.0,,0,0.0,10047.38,5010.63,2305.87,109,1,Residential
111
+ 2026-04-20,0.0,,0,0.0,9409.75,4692.64,2159.54,110,0,Residential
112
+ 2026-04-21,7.2,,0,0.0,9347.59,4661.64,2145.27,111,0,Residential
113
+ 2026-04-22,24.3,,0,0.0,9156.98,4566.59,2101.53,112,0,Residential
114
+ 2026-04-23,0.0,,0,0.0,9537.92,4756.56,2188.95,113,0,Residential
115
+ 2026-04-24,0.0,,0,0.0,9223.62,4599.82,2116.82,114,0,Residential
116
+ 2026-04-25,23.4,,0,0.0,9844.38,4909.39,2259.29,115,1,Residential
117
+ 2026-04-26,0.0,,0,0.0,9228.52,4602.26,2117.95,116,1,Residential
118
+ 2026-04-27,0.0,,0,0.0,8227.66,4103.13,1888.25,117,0,Residential
119
+ 2026-04-28,37.7,,0,0.0,8795.5,4386.32,2018.57,118,0,Residential
120
+ 2026-04-29,14.3,,0,0.0,8246.28,4112.42,1892.52,119,0,Residential
121
+ 2026-04-30,0.0,May Day Rally,1,1.4,10334.87,5154.0,2371.85,120,0,Tourism
122
+ 2026-05-01,14.5,May Day Rally,1,3.0,11826.71,5897.98,2714.23,121,0,Tourism
123
+ 2026-05-02,13.4,May Day Rally,1,1.4,10983.35,5477.4,2520.68,122,1,Tourism
124
+ 2026-05-03,10.0,,0,0.0,8964.41,4470.55,2057.33,123,1,Residential
125
+ 2026-05-04,0.0,,0,0.0,8171.45,4075.1,1875.35,124,0,Residential
126
+ 2026-05-05,26.7,,0,0.0,8485.47,4231.7,1947.42,125,0,Residential
127
+ 2026-05-06,0.0,,0,0.0,8332.31,4155.32,1912.27,126,0,Residential
128
+ 2026-05-07,0.0,,0,0.0,8393.07,4185.62,1926.21,127,0,Residential
129
+ 2026-05-08,30.7,,0,0.0,9169.65,4572.9,2104.43,128,0,Residential
130
+ 2026-05-09,30.6,,0,0.0,9689.96,4832.38,2223.85,129,1,Residential
131
+ 2026-05-10,35.5,,0,0.0,9431.73,4703.6,2164.58,130,1,Residential
132
+ 2026-05-11,30.4,,0,0.0,8335.67,4157.0,1913.04,131,0,Residential
133
+ 2026-05-12,18.5,,0,0.0,9000.88,4488.74,2065.7,132,0,Residential
134
+ 2026-05-13,27.4,,0,0.0,8325.87,4152.11,1910.79,133,0,Residential
135
+ 2026-05-14,23.7,,0,0.0,8316.07,4147.22,1908.54,134,0,Residential
136
+ 2026-05-15,0.0,,0,0.0,8758.96,4368.09,2010.18,135,0,Residential
137
+ 2026-05-16,0.0,,0,0.0,8331.68,4155.01,1912.12,136,1,Residential
138
+ 2026-05-17,0.0,,0,0.0,9372.44,4674.04,2150.97,137,1,Residential
139
+ 2026-05-18,23.7,,0,0.0,9022.44,4499.49,2070.65,138,0,Residential
140
+ 2026-05-19,0.0,,0,0.0,8629.88,4303.72,1980.56,139,0,Residential
141
+ 2026-05-20,34.7,,0,0.0,8905.4,4441.12,2043.79,140,0,Residential
142
+ 2026-05-21,21.7,,0,0.0,8557.5,4267.63,1963.95,141,0,Residential
143
+ 2026-05-22,0.0,,0,0.0,8682.45,4329.94,1992.62,142,0,Residential
144
+ 2026-05-23,32.7,,0,0.0,9279.69,4627.78,2129.69,143,1,Residential
145
+ 2026-05-24,0.0,,0,0.0,8854.02,4415.5,2032.0,144,1,Residential
146
+ 2026-05-25,9.7,,0,0.0,8538.53,4258.16,1959.59,145,0,Residential
147
+ 2026-05-26,30.3,,0,0.0,8510.18,4244.03,1953.09,146,0,Residential
148
+ 2026-05-27,25.1,,0,0.0,8753.5,4365.37,2008.93,147,0,Residential
149
+ 2026-05-28,19.0,,0,0.0,8888.67,4432.78,2039.95,148,0,Residential
150
+ 2026-05-29,36.3,PRJ Opening,1,2.3,12525.03,6246.23,2874.49,149,0,Tourism
151
+ 2026-05-30,11.0,PRJ Opening,1,3.1,13960.66,6962.18,3203.97,150,1,Tourism
152
+ 2026-05-31,0.0,PRJ Opening,1,3.8,15014.09,7487.53,3445.73,151,1,Tourism
153
+ 2026-06-01,19.9,PRJ Opening,1,4.0,14844.55,7402.98,3406.82,152,0,Tourism
154
+ 2026-06-02,0.0,PRJ Opening,1,3.8,12986.61,6476.42,2980.43,153,0,Tourism
155
+ 2026-06-03,24.2,PRJ Opening,1,3.1,11239.69,5605.23,2579.51,154,0,Tourism
156
+ 2026-06-04,0.0,PRJ Opening,1,2.3,10888.99,5430.34,2499.02,155,0,Tourism
157
+ 2026-06-05,15.3,,0,0.0,9440.2,4707.83,2166.53,156,0,Residential
158
+ 2026-06-06,0.0,,0,0.0,9880.22,4927.27,2267.51,157,1,Residential
159
+ 2026-06-07,15.3,,0,0.0,10435.04,5203.95,2394.84,158,1,Residential
160
+ 2026-06-08,25.0,,0,0.0,9570.47,4772.79,2196.42,159,0,Residential
161
+ 2026-06-09,0.0,,0,0.0,9518.81,4747.03,2184.57,160,0,Residential
162
+ 2026-06-10,17.8,,0,0.0,9271.22,4623.56,2127.74,161,0,Residential
163
+ 2026-06-11,32.6,,0,0.0,9224.18,4600.1,2116.95,162,0,Residential
164
+ 2026-06-12,25.4,,0,0.0,9293.06,4634.45,2132.76,163,0,Residential
165
+ 2026-06-13,39.8,,0,0.0,9287.11,4631.48,2131.39,164,1,Residential
166
+ 2026-06-14,0.0,Music Festival GBK,1,1.6,11919.18,5944.1,2735.45,165,1,Tourism
167
+ 2026-06-15,29.6,Music Festival GBK,1,3.5,12700.24,6333.61,2914.71,166,0,Tourism
168
+ 2026-06-16,33.7,Music Festival GBK,1,1.6,11831.33,5900.28,2715.29,167,0,Tourism
169
+ 2026-06-17,0.0,,0,0.0,8253.49,4116.02,1894.18,168,0,Residential
170
+ 2026-06-18,35.9,,0,0.0,8990.8,4483.71,2063.39,169,0,Residential
171
+ 2026-06-19,22.7,,0,0.0,9638.16,4806.55,2211.96,170,0,Residential
172
+ 2026-06-20,33.8,,0,0.0,10419.64,5196.27,2391.31,171,1,Residential
173
+ 2026-06-21,34.9,,0,0.0,10489.57,5231.15,2407.36,172,1,Residential
174
+ 2026-06-22,0.0,,0,0.0,8771.42,4374.31,2013.04,173,0,Residential
175
+ 2026-06-23,27.4,,0,0.0,8978.97,4477.81,2060.67,174,0,Residential
176
+ 2026-06-24,0.0,,0,0.0,9485.56,4730.45,2176.94,175,0,Residential
177
+ 2026-06-25,0.0,,0,0.0,9968.77,4971.43,2287.83,176,0,Residential
178
+ 2026-06-26,36.1,,0,0.0,9780.26,4877.42,2244.57,177,0,Residential
179
+ 2026-06-27,0.0,,0,0.0,9225.51,4600.76,2117.25,178,1,Residential
180
+ 2026-06-28,26.9,,0,0.0,9894.5,4934.39,2270.79,179,1,Residential
181
+ 2026-06-29,34.7,,0,0.0,9638.93,4806.93,2212.13,180,0,Residential
182
+ 2026-06-30,0.0,,0,0.0,8962.59,4469.64,2056.91,181,0,Residential
183
+ 2026-07-01,26.6,,0,0.0,9029.44,4502.98,2072.26,182,0,Residential
184
+ 2026-07-02,0.0,,0,0.0,8703.66,4340.52,1997.49,183,0,Residential
185
+ 2026-07-03,8.4,,0,0.0,8944.53,4460.64,2052.77,184,0,Residential
186
+ 2026-07-04,0.0,,0,0.0,9452.45,4713.94,2169.34,185,1,Residential
187
+ 2026-07-05,12.2,,0,0.0,9809.31,4891.9,2251.24,186,1,Residential
188
+ 2026-07-06,0.0,,0,0.0,8221.85,4100.24,1886.91,187,0,Residential
189
+ 2026-07-07,0.0,,0,0.0,8923.81,4450.3,2048.01,188,0,Residential
190
+ 2026-07-08,31.3,,0,0.0,9313.92,4644.85,2137.54,189,0,Residential
191
+ 2026-07-09,0.0,,0,0.0,8364.93,4171.59,1919.75,190,0,Residential
192
+ 2026-07-10,38.5,,0,0.0,8396.08,4187.13,1926.9,191,0,Residential
193
+ 2026-07-11,22.5,,0,0.0,9079.7,4528.05,2083.79,192,1,Residential
194
+ 2026-07-12,28.0,,0,0.0,10164.0,5068.79,2332.64,193,1,Residential
195
+ 2026-07-13,31.1,,0,0.0,9447.13,4711.28,2168.12,194,0,Residential
196
+ 2026-07-14,23.2,,0,0.0,8683.22,4330.32,1992.8,195,0,Residential
197
+ 2026-07-15,45.0,,0,0.0,9224.81,4600.41,2117.09,196,0,Residential
198
+ 2026-07-16,27.6,,0,0.0,8161.37,4070.08,1873.03,197,0,Residential
199
+ 2026-07-17,30.6,,0,0.0,8251.39,4114.97,1893.69,198,0,Residential
200
+ 2026-07-18,40.0,,0,0.0,8947.4,4462.07,2053.43,199,1,Residential
201
+ 2026-07-19,35.9,PRJ Peak Weekend,1,3.5,14297.85,7130.34,3281.36,200,1,Tourism
202
+ 2026-07-20,0.0,PRJ Peak Weekend,1,5.0,14262.64,7112.78,3273.28,201,0,Tourism
203
+ 2026-07-21,21.0,PRJ Peak Weekend,1,3.5,13242.39,6603.98,3039.13,202,0,Tourism
204
+ 2026-07-22,0.0,,0,0.0,8856.4,4416.69,2032.54,203,0,Residential
205
+ 2026-07-23,32.4,,0,0.0,8475.53,4226.75,1945.13,204,0,Residential
206
+ 2026-07-24,22.5,,0,0.0,8638.0,4307.77,1982.42,205,0,Residential
207
+ 2026-07-25,0.0,,0,0.0,9289.35,4632.6,2131.91,206,1,Residential
208
+ 2026-07-26,28.6,,0,0.0,9849.98,4912.19,2260.57,207,1,Residential
209
+ 2026-07-27,25.7,,0,0.0,8674.89,4326.17,1990.89,208,0,Residential
210
+ 2026-07-28,0.0,,0,0.0,8254.12,4116.33,1894.32,209,0,Residential
211
+ 2026-07-29,18.4,,0,0.0,8717.03,4347.18,2000.56,210,0,Residential
212
+ 2026-07-30,19.8,,0,0.0,8743.0,4360.13,2006.52,211,0,Residential
213
+ 2026-07-31,30.9,,0,0.0,8807.96,4392.53,2021.43,212,0,Residential
214
+ 2026-08-01,0.0,,0,0.0,8549.1,4263.44,1962.02,213,1,Residential
215
+ 2026-08-02,0.0,,0,0.0,9007.39,4491.99,2067.2,214,1,Residential
216
+ 2026-08-03,17.8,,0,0.0,8525.23,4251.53,1956.54,215,0,Residential
217
+ 2026-08-04,0.0,,0,0.0,8857.66,4417.32,2032.83,216,0,Residential
218
+ 2026-08-05,0.0,,0,0.0,9041.62,4509.06,2075.05,217,0,Residential
219
+ 2026-08-06,19.7,,0,0.0,8650.53,4314.02,1985.3,218,0,Residential
220
+ 2026-08-07,23.8,,0,0.0,8600.83,4289.23,1973.89,219,0,Residential
221
+ 2026-08-08,0.0,,0,0.0,9281.51,4628.69,2130.11,220,1,Residential
222
+ 2026-08-09,18.4,,0,0.0,9757.3,4865.97,2239.3,221,1,Residential
223
+ 2026-08-10,27.2,,0,0.0,8197.63,4088.16,1881.36,222,0,Residential
224
+ 2026-08-11,0.0,,0,0.0,8104.39,4041.66,1859.96,223,0,Residential
225
+ 2026-08-12,26.1,,0,0.0,7800.87,3890.29,1790.3,224,0,Residential
226
+ 2026-08-13,43.9,,0,0.0,8426.6,4202.35,1933.9,225,0,Residential
227
+ 2026-08-14,25.7,,0,0.0,8350.3,4164.29,1916.39,226,0,Residential
228
+ 2026-08-15,26.2,HUT RI ke-81,1,1.8,11819.57,5894.42,2712.59,227,1,Tourism
229
+ 2026-08-16,0.0,HUT RI ke-81,1,3.3,12976.53,6471.4,2978.11,228,1,Tourism
230
+ 2026-08-17,15.6,HUT RI ke-81,1,4.0,12669.58,6318.32,2907.67,229,0,Tourism
231
+ 2026-08-18,0.0,HUT RI ke-81,1,3.3,12192.39,6080.34,2798.15,230,0,Tourism
232
+ 2026-08-19,30.2,HUT RI ke-81,1,1.8,10837.05,5404.44,2487.1,231,0,Tourism
233
+ 2026-08-20,0.0,,0,0.0,7223.58,3602.4,1657.81,232,0,Residential
234
+ 2026-08-21,25.9,,0,0.0,7796.04,3887.89,1789.19,233,0,Residential
235
+ 2026-08-22,0.0,,0,0.0,8480.43,4229.19,1946.26,234,1,Residential
236
+ 2026-08-23,24.6,,0,0.0,9127.02,4551.64,2094.65,235,1,Residential
237
+ 2026-08-24,0.0,,0,0.0,7899.99,3939.73,1813.05,236,0,Residential
238
+ 2026-08-25,19.5,,0,0.0,8098.23,4038.59,1858.54,237,0,Residential
239
+ 2026-08-26,17.1,,0,0.0,8387.75,4182.97,1924.99,238,0,Residential
240
+ 2026-08-27,0.0,,0,0.0,8367.17,4172.71,1920.27,239,0,Residential
241
+ 2026-08-28,21.4,,0,0.0,8276.59,4127.54,1899.48,240,0,Residential
242
+ 2026-08-29,9.5,,0,0.0,8487.5,4232.72,1947.88,241,1,Residential
243
+ 2026-08-30,9.3,,0,0.0,8828.4,4402.72,2026.12,242,1,Residential
244
+ 2026-08-31,0.0,,0,0.0,8134.56,4056.71,1866.88,243,0,Residential
245
+ 2026-09-01,0.0,,0,0.0,8104.95,4041.94,1860.09,244,0,Residential
246
+ 2026-09-02,9.1,,0,0.0,7563.71,3772.02,1735.87,245,0,Residential
247
+ 2026-09-03,0.0,,0,0.0,7660.03,3820.06,1757.98,246,0,Residential
248
+ 2026-09-04,16.4,,0,0.0,7649.25,3814.68,1755.5,247,0,Residential
249
+ 2026-09-05,10.4,,0,0.0,6911.94,3446.98,1586.29,248,1,Residential
250
+ 2026-09-06,11.4,,0,0.0,7665.35,3822.71,1759.2,249,1,Residential
251
+ 2026-09-07,31.2,,0,0.0,8057.42,4018.24,1849.18,250,0,Residential
252
+ 2026-09-08,18.6,,0,0.0,8083.6,4031.29,1855.19,251,0,Residential
253
+ 2026-09-09,17.1,,0,0.0,7634.41,3807.28,1752.1,252,0,Residential
254
+ 2026-09-10,16.4,,0,0.0,8108.73,4043.82,1860.95,253,0,Residential
255
+ 2026-09-11,19.9,,0,0.0,8066.1,4022.56,1851.17,254,0,Residential
256
+ 2026-09-12,0.0,,0,0.0,8692.95,4335.17,1995.03,255,1,Residential
257
+ 2026-09-13,0.0,,0,0.0,9567.25,4771.19,2195.68,256,1,Residential
258
+ 2026-09-14,23.7,Food & Culture Expo,1,1.8,11103.47,5537.3,2548.25,257,0,Tourism
259
+ 2026-09-15,10.9,Food & Culture Expo,1,2.5,10672.97,5322.61,2449.45,258,0,Tourism
260
+ 2026-09-16,19.9,Food & Culture Expo,1,1.8,11219.67,5595.25,2574.91,259,0,Tourism
261
+ 2026-09-17,9.2,,0,0.0,7829.5,3904.57,1796.87,260,0,Residential
262
+ 2026-09-18,0.0,,0,0.0,7943.39,3961.37,1823.01,261,0,Residential
263
+ 2026-09-19,2.9,,0,0.0,8058.61,4018.83,1849.45,262,1,Residential
264
+ 2026-09-20,0.0,,0,0.0,8858.29,4417.63,2032.98,263,1,Residential
265
+ 2026-09-21,14.8,,0,0.0,8619.45,4298.52,1978.16,264,0,Residential
266
+ 2026-09-22,19.1,,0,0.0,8602.16,4289.9,1974.2,265,0,Residential
267
+ 2026-09-23,12.5,,0,0.0,8155.35,4067.07,1871.65,266,0,Residential
268
+ 2026-09-24,11.3,,0,0.0,7914.13,3946.78,1816.29,267,0,Residential
269
+ 2026-09-25,5.2,,0,0.0,8204.21,4091.44,1882.87,268,0,Residential
270
+ 2026-09-26,10.0,,0,0.0,8597.26,4287.45,1973.07,269,1,Residential
271
+ 2026-09-27,0.0,,0,0.0,8599.36,4288.5,1973.55,270,1,Residential
272
+ 2026-09-28,0.0,,0,0.0,7534.73,3757.57,1729.22,271,0,Residential
273
+ 2026-09-29,0.0,,0,0.0,7617.4,3798.8,1748.19,272,0,Residential
274
+ 2026-09-30,13.5,,0,0.0,7506.52,3743.5,1722.75,273,0,Residential
275
+ 2026-10-01,0.0,,0,0.0,8116.08,4047.49,1862.64,274,0,Residential
276
+ 2026-10-02,8.7,,0,0.0,7432.88,3706.78,1705.85,275,0,Residential
277
+ 2026-10-03,0.0,,0,0.0,7584.22,3782.25,1740.58,276,1,Residential
278
+ 2026-10-04,0.0,,0,0.0,7252.28,3616.71,1664.4,277,1,Residential
279
+ 2026-10-05,1.6,,0,0.0,6725.18,3353.85,1543.43,278,0,Residential
280
+ 2026-10-06,0.0,,0,0.0,6795.81,3389.07,1559.64,279,0,Residential
281
+ 2026-10-07,0.0,,0,0.0,7211.61,3596.43,1655.06,280,0,Residential
282
+ 2026-10-08,7.8,,0,0.0,7358.75,3669.81,1688.83,281,0,Residential
283
+ 2026-10-09,12.6,Jakarta Marathon,1,1.4,8873.9,4425.41,2036.56,282,0,Tourism
284
+ 2026-10-10,0.0,Jakarta Marathon,1,3.0,11142.18,5556.61,2557.13,283,1,Tourism
285
+ 2026-10-11,0.0,Jakarta Marathon,1,1.4,10360.07,5166.57,2377.64,284,1,Tourism
286
+ 2026-10-12,0.0,,0,0.0,8080.59,4029.79,1854.5,285,0,Residential
287
+ 2026-10-13,23.0,,0,0.0,7965.65,3972.47,1828.12,286,0,Residential
288
+ 2026-10-14,0.0,,0,0.0,8449.49,4213.76,1939.16,287,0,Residential
289
+ 2026-10-15,13.0,,0,0.0,8394.54,4186.36,1926.55,288,0,Residential
290
+ 2026-10-16,4.8,,0,0.0,8219.05,4098.84,1886.27,289,0,Residential
291
+ 2026-10-17,0.0,,0,0.0,8472.1,4225.04,1944.35,290,1,Residential
292
+ 2026-10-18,0.0,,0,0.0,8460.9,4219.45,1941.78,291,1,Residential
293
+ 2026-10-19,0.0,,0,0.0,7234.71,3607.95,1660.37,292,0,Residential
294
+ 2026-10-20,0.0,,0,0.0,7033.67,3507.69,1614.23,293,0,Residential
295
+ 2026-10-21,9.6,,0,0.0,7650.23,3815.17,1755.73,294,0,Residential
296
+ 2026-10-22,16.8,,0,0.0,8017.38,3998.27,1839.99,295,0,Residential
297
+ 2026-10-23,0.0,,0,0.0,7451.22,3715.92,1710.05,296,0,Residential
298
+ 2026-10-24,0.0,,0,0.0,7688.52,3834.26,1764.52,297,1,Residential
299
+ 2026-10-25,16.6,,0,0.0,8225.63,4102.12,1887.78,298,1,Residential
300
+ 2026-10-26,0.0,,0,0.0,7529.69,3755.06,1728.06,299,0,Residential
301
+ 2026-10-27,0.0,,0,0.0,7612.36,3796.28,1747.04,300,0,Residential
302
+ 2026-10-28,0.0,,0,0.0,7886.83,3933.16,1810.03,301,0,Residential
303
+ 2026-10-29,0.0,,0,0.0,7512.89,3746.68,1724.21,302,0,Residential
304
+ 2026-10-30,0.0,,0,0.0,7507.22,3743.85,1722.91,303,0,Residential
305
+ 2026-10-31,0.0,,0,0.0,7637.14,3808.64,1752.72,304,1,Residential
306
+ 2026-11-01,0.0,,0,0.0,7976.92,3978.09,1830.7,305,1,Residential
307
+ 2026-11-02,0.0,,0,0.0,7720.93,3850.43,1771.95,306,0,Residential
308
+ 2026-11-03,0.7,,0,0.0,7441.91,3711.28,1707.92,307,0,Residential
309
+ 2026-11-04,0.0,,0,0.0,6856.08,3419.13,1573.47,308,0,Residential
310
+ 2026-11-05,0.0,,0,0.0,7300.16,3640.59,1675.39,309,0,Residential
311
+ 2026-11-06,0.0,,0,0.0,7626.36,3803.27,1750.25,310,0,Residential
312
+ 2026-11-07,0.0,,0,0.0,7739.62,3859.75,1776.24,311,1,Residential
313
+ 2026-11-08,7.0,,0,0.0,8268.33,4123.42,1897.58,312,1,Residential
314
+ 2026-11-09,0.0,,0,0.0,7973.14,3976.2,1829.84,313,0,Residential
315
+ 2026-11-10,2.8,,0,0.0,8185.94,4082.33,1878.67,314,0,Residential
316
+ 2026-11-11,0.0,,0,0.0,8014.65,3996.91,1839.36,315,0,Residential
317
+ 2026-11-12,5.1,,0,0.0,7650.65,3815.38,1755.82,316,0,Residential
318
+ 2026-11-13,0.0,,0,0.0,7582.75,3781.52,1740.24,317,0,Residential
319
+ 2026-11-14,0.0,,0,0.0,7914.83,3947.13,1816.45,318,1,Residential
320
+ 2026-11-15,0.0,,0,0.0,7752.71,3866.28,1779.25,319,1,Residential
321
+ 2026-11-16,0.0,,0,0.0,6418.44,3200.88,1473.03,320,0,Residential
322
+ 2026-11-17,0.0,,0,0.0,6684.58,3333.6,1534.11,321,0,Residential
323
+ 2026-11-18,0.0,,0,0.0,6769.7,3376.05,1553.65,322,0,Residential
324
+ 2026-11-19,1.2,,0,0.0,7210.42,3595.84,1654.79,323,0,Residential
325
+ 2026-11-20,0.0,,0,0.0,7385.63,3683.21,1695.0,324,0,Residential
326
+ 2026-11-21,0.0,,0,0.0,7919.45,3949.43,1817.51,325,1,Residential
327
+ 2026-11-22,1.3,,0,0.0,8035.93,4007.52,1844.25,326,1,Residential
328
+ 2026-11-23,0.0,,0,0.0,7355.6,3668.24,1688.11,327,0,Residential
329
+ 2026-11-24,0.0,Ancol Music Fest,1,1.4,9291.38,4633.61,2132.37,328,0,Tourism
330
+ 2026-11-25,9.1,Ancol Music Fest,1,3.0,10727.78,5349.94,2462.03,329,0,Tourism
331
+ 2026-11-26,0.7,Ancol Music Fest,1,1.4,9768.57,4871.59,2241.89,330,0,Tourism
332
+ 2026-11-27,0.0,,0,0.0,7467.74,3724.16,1713.85,331,0,Residential
333
+ 2026-11-28,5.7,,0,0.0,7489.23,3734.88,1718.78,332,1,Residential
334
+ 2026-11-29,0.0,,0,0.0,7991.41,3985.32,1834.03,333,1,Residential
335
+ 2026-11-30,0.0,,0,0.0,7352.59,3666.74,1687.42,334,0,Residential
336
+ 2026-12-01,0.0,,0,0.0,7433.93,3707.3,1706.09,335,0,Residential
337
+ 2026-12-02,0.0,,0,0.0,7333.9,3657.42,1683.13,336,0,Residential
338
+ 2026-12-03,0.0,,0,0.0,7496.09,3738.3,1720.35,337,0,Residential
339
+ 2026-12-04,6.5,,0,0.0,7690.9,3835.45,1765.06,338,0,Residential
340
+ 2026-12-05,0.0,,0,0.0,8114.33,4046.62,1862.24,339,1,Residential
341
+ 2026-12-06,1.0,,0,0.0,8186.36,4082.54,1878.77,340,1,Residential
342
+ 2026-12-07,0.0,,0,0.0,7114.87,3548.19,1632.86,341,0,Residential
343
+ 2026-12-08,11.3,,0,0.0,7033.18,3507.45,1614.11,342,0,Residential
344
+ 2026-12-09,0.0,,0,0.0,7371.91,3676.37,1691.85,343,0,Residential
345
+ 2026-12-10,0.0,,0,0.0,7757.82,3868.82,1780.42,344,0,Residential
346
+ 2026-12-11,0.0,,0,0.0,7178.29,3579.81,1647.42,345,0,Residential
347
+ 2026-12-12,11.7,,0,0.0,7556.92,3768.64,1734.31,346,1,Residential
348
+ 2026-12-13,0.0,,0,0.0,8203.02,4090.85,1882.59,347,1,Residential
349
+ 2026-12-14,0.0,,0,0.0,7769.37,3874.58,1783.07,348,0,Residential
350
+ 2026-12-15,0.0,,0,0.0,7857.92,3918.74,1803.39,349,0,Residential
351
+ 2026-12-16,0.0,,0,0.0,8179.85,4079.29,1877.28,350,0,Residential
352
+ 2026-12-17,0.0,,0,0.0,8425.83,4201.96,1933.73,351,0,Residential
353
+ 2026-12-18,3.4,Christmas Market,1,2.1,10120.04,5046.86,2322.55,352,0,Tourism
354
+ 2026-12-19,0.0,Christmas Market,1,3.1,10913.98,5442.8,2504.76,353,1,Tourism
355
+ 2026-12-20,0.0,Christmas Market,1,3.5,11958.03,5963.47,2744.37,354,1,Tourism
356
+ 2026-12-21,7.5,Christmas Market,1,3.1,10200.47,5086.97,2341.01,355,0,Tourism
357
+ 2026-12-22,5.1,Christmas Market,1,2.1,9875.95,4925.14,2266.53,356,0,Tourism
358
+ 2026-12-23,7.3,,0,0.0,8199.73,4089.21,1881.84,357,0,Residential
359
+ 2026-12-24,0.0,,0,0.0,7932.68,3956.03,1820.55,358,0,Residential
360
+ 2026-12-25,2.9,,0,0.0,7866.32,3922.93,1805.32,359,0,Residential
361
+ 2026-12-26,0.0,,0,0.0,8383.2,4180.7,1923.94,360,1,Residential
362
+ 2026-12-27,0.0,,0,0.0,8390.83,4184.51,1925.7,361,1,Residential
363
+ 2026-12-28,0.0,,0,0.0,7351.68,3666.28,1687.21,362,0,Residential
364
+ 2026-12-29,0.0,,0,0.0,7997.85,3988.53,1835.51,363,0,Residential
365
+ 2026-12-30,0.0,Countdown Jakarta 2027,1,3.2,11492.95,5731.53,2637.63,364,0,Tourism
366
+ 2026-12-31,10.7,Countdown Jakarta 2027,1,4.5,12294.45,6131.24,2821.58,365,0,Tourism
generate_localized_dataset.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ def generate_local_data():
5
+ print("Starting localized dataset generation...")
6
+
7
+ # Load original dataset
8
+ try:
9
+ df_global = pd.read_csv("dataset_vibe_coder_2026.csv")
10
+ except Exception as e:
11
+ print(f"Error loading dataset: {e}")
12
+ return
13
+
14
+ # Ensure chronological order
15
+ df_global['TANGGAL'] = pd.to_datetime(df_global['TANGGAL'])
16
+ df_global = df_global.sort_values('TANGGAL').reset_index(drop=True)
17
+
18
+ # Add lag features on global level (weather is shared across Jakarta)
19
+ df_global['Rain_Lag_1'] = df_global['RR'].shift(1).fillna(0.0)
20
+ df_global['Rain_Lag_2'] = df_global['RR'].shift(2).fillna(0.0)
21
+
22
+ # Holiday checker for major Indonesian holidays in 2026
23
+ def get_holiday_flag(date_obj):
24
+ m, d = date_obj.month, date_obj.day
25
+ # Specific holiday dates in 2026
26
+ holidays = {
27
+ (1, 1), # New Year
28
+ (2, 17), # Imlek
29
+ (3, 18), # Nyepi
30
+ (3, 19), # Eid al-Fitr Day 1
31
+ (3, 20), # Eid al-Fitr Day 2
32
+ (4, 3), # Good Friday
33
+ (5, 1), # Labor Day
34
+ (5, 14), # Ascension Day
35
+ (5, 27), # Eid al-Adha Day 1
36
+ (5, 28), # Eid al-Adha Day 2
37
+ (5, 31), # Waisak
38
+ (6, 16), # Islamic New Year
39
+ (8, 17), # Independence Day
40
+ (8, 25), # Prophet Birthday
41
+ (12, 25) # Christmas
42
+ }
43
+ # Eid al-Fitr mudik window: March 15 to March 26
44
+ if m == 3 and (15 <= d <= 26):
45
+ return 1
46
+ if (m, d) in holidays:
47
+ return 1
48
+ return 0
49
+
50
+ df_global['Is_Holiday'] = df_global['TANGGAL'].apply(get_holiday_flag)
51
+ df_global['Hari_Dalam_Minggu'] = df_global['TANGGAL'].dt.dayofweek
52
+ df_global['Bulan'] = df_global['TANGGAL'].dt.month
53
+
54
+ local_rows = []
55
+ for idx, row in df_global.iterrows():
56
+ date_str = row['TANGGAL'].strftime("%Y-%m-%d")
57
+ global_vol = row['Volume_Total_Ton']
58
+ rr = row['RR']
59
+ rain_lag1 = row['Rain_Lag_1']
60
+ rain_lag2 = row['Rain_Lag_2']
61
+ is_holiday = row['Is_Holiday']
62
+ ada_event = row['Ada_Event']
63
+ crowd_scale = row['Crowd_Scale']
64
+ hari_ke = row['Hari_Ke']
65
+ is_weekend = row['Is_Weekend']
66
+ hari_dalam_minggu = row['Hari_Dalam_Minggu']
67
+ bulan = row['Bulan']
68
+
69
+ # Apply Lebaran mudik population drop factor
70
+ # If inside March Lebaran window, drop global base volume by 35%
71
+ vol_scale = global_vol
72
+ if is_holiday == 1 and row['TANGGAL'].month == 3:
73
+ vol_scale = global_vol * 0.65
74
+
75
+ # JIS (North Jakarta)
76
+ # Base volume: ~120 tons average
77
+ jis_vol = vol_scale * (120.0 / 7700.0)
78
+ # Event spikes at Stadium
79
+ if ada_event == 1:
80
+ jis_vol += crowd_scale * 15.0
81
+ # Weekend recreation factor
82
+ if is_weekend == 1:
83
+ jis_vol *= 1.05
84
+ local_rows.append({
85
+ 'Tanggal': date_str, 'Location': 'JIS', 'Volume_Ton': jis_vol,
86
+ 'RR': rr, 'Rain_Lag_1': rain_lag1, 'Rain_Lag_2': rain_lag2,
87
+ 'Is_Holiday': is_holiday, 'Ada_Event': ada_event, 'Crowd_Scale': crowd_scale,
88
+ 'Hari_Ke': hari_ke, 'Is_Weekend': is_weekend, 'Hari_Dalam_Minggu': hari_dalam_minggu, 'Bulan': bulan
89
+ })
90
+
91
+ # GBK (Central/South)
92
+ # Base volume: ~85 tons average
93
+ gbk_vol = vol_scale * (85.0 / 7700.0)
94
+ # Event spikes at Stadium
95
+ if ada_event == 1:
96
+ gbk_vol += crowd_scale * 12.0
97
+ # Weekend public sports factor
98
+ if is_weekend == 1:
99
+ gbk_vol *= 1.15
100
+ local_rows.append({
101
+ 'Tanggal': date_str, 'Location': 'GBK', 'Volume_Ton': gbk_vol,
102
+ 'RR': rr, 'Rain_Lag_1': rain_lag1, 'Rain_Lag_2': rain_lag2,
103
+ 'Is_Holiday': is_holiday, 'Ada_Event': ada_event, 'Crowd_Scale': crowd_scale,
104
+ 'Hari_Ke': hari_ke, 'Is_Weekend': is_weekend, 'Hari_Dalam_Minggu': hari_dalam_minggu, 'Bulan': bulan
105
+ })
106
+
107
+ # Pasar Senen (Central)
108
+ # Base volume: ~45 tons average
109
+ senen_vol = vol_scale * (45.0 / 7700.0)
110
+ # Weekday market commerce factor
111
+ if is_weekend == 0:
112
+ senen_vol *= 1.10
113
+ local_rows.append({
114
+ 'Tanggal': date_str, 'Location': 'Pasar Senen', 'Volume_Ton': senen_vol,
115
+ 'RR': rr, 'Rain_Lag_1': rain_lag1, 'Rain_Lag_2': rain_lag2,
116
+ 'Is_Holiday': is_holiday, 'Ada_Event': 0, 'Crowd_Scale': 0,
117
+ 'Hari_Ke': hari_ke, 'Is_Weekend': is_weekend, 'Hari_Dalam_Minggu': hari_dalam_minggu, 'Bulan': bulan
118
+ })
119
+
120
+ # Gang Sempit Tambora (West)
121
+ # Base volume: ~8.5 tons average
122
+ tambora_vol = vol_scale * (8.5 / 7700.0)
123
+ # Hujan block factor (heavy rain delays alley collection)
124
+ if rr > 20:
125
+ tambora_vol *= 0.75
126
+ local_rows.append({
127
+ 'Tanggal': date_str, 'Location': 'Gang Sempit Tambora', 'Volume_Ton': tambora_vol,
128
+ 'RR': rr, 'Rain_Lag_1': rain_lag1, 'Rain_Lag_2': rain_lag2,
129
+ 'Is_Holiday': is_holiday, 'Ada_Event': 0, 'Crowd_Scale': 0,
130
+ 'Hari_Ke': hari_ke, 'Is_Weekend': is_weekend, 'Hari_Dalam_Minggu': hari_dalam_minggu, 'Bulan': bulan
131
+ })
132
+
133
+ df_local = pd.DataFrame(local_rows)
134
+ df_local.to_csv("dataset_local_2026.csv", index=False)
135
+ print("dataset_local_2026.csv generated successfully with 1460 rows!")
136
+
137
+ if __name__ == "__main__":
138
+ generate_local_data()
model_sampah_advanced.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:24b98c67674caedfdc434451cf4deb70d69c4e9620e57e80b24cbaddfa3a0287
3
+ size 1287658
scale_dataset.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ # Load dataset
5
+ df = pd.read_csv("dataset_vibe_coder_2026.csv")
6
+
7
+ print("Original Stats:")
8
+ print(df[["Volume_Total_Ton", "Vol_Sisa_Makanan_Ton", "Vol_Plastik_Ton"]].describe())
9
+
10
+ # Scale values to match DKI Jakarta daily average (~7,700 tons/day)
11
+ # original mean is ~1,100 tons/day, so we scale by ~7
12
+ scale_factor = 7.0
13
+
14
+ df["Volume_Total_Ton"] = (df["Volume_Total_Ton"] * scale_factor).round(2)
15
+
16
+ # Organic/Food waste (Sisa Makanan) is ~49.87% of total
17
+ df["Vol_Sisa_Makanan_Ton"] = (df["Volume_Total_Ton"] * 0.4987).round(2)
18
+
19
+ # Plastic waste is ~22.95% of total
20
+ df["Vol_Plastik_Ton"] = (df["Volume_Total_Ton"] * 0.2295).round(2)
21
+
22
+ # Save the scaled dataset
23
+ df.to_csv("dataset_vibe_coder_2026.csv", index=False)
24
+
25
+ print("\nScaled Stats:")
26
+ print(df[["Volume_Total_Ton", "Vol_Sisa_Makanan_Ton", "Vol_Plastik_Ton"]].describe())
27
+ print("\nDataset successfully scaled to DKI Jakarta Province scale!")
static/app.js ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Coordinates and Map Data
2
+ const LOCATION_COORDINATES = {
3
+ "GBK": {latitude: -6.2183, longitude: 106.8022},
4
+ "JIS": {latitude: -6.1244, longitude: 106.8622},
5
+ "Pasar Senen": {latitude: -6.1744, longitude: 106.8444},
6
+ "Gang Sempit Tambora": {latitude: -6.1500, longitude: 106.8000}
7
+ };
8
+
9
+ const LOCATION_MAP_DATA = {
10
+ "JIS": {coords: [-6.1244, 106.8622], label: "JIS"},
11
+ "GBK": {coords: [-6.2183, 106.8022], label: "GBK"},
12
+ "Pasar Senen": {coords: [-6.1744, 106.8444], label: "Senen"},
13
+ "Gang Sempit Tambora": {coords: [-6.1500, 106.8000], label: "Tambora"}
14
+ };
15
+
16
+ const LOCATION_RADIUS = {
17
+ "JIS": "1.5 km",
18
+ "GBK": "2.0 km",
19
+ "Pasar Senen": "1.2 km",
20
+ "Gang Sempit Tambora": "0.8 km"
21
+ };
22
+
23
+ const BANTARGEBANG_COORDS = [-6.3477, 106.9939];
24
+
25
+ // UI Elements
26
+ const locationSelect = document.getElementById("location-select");
27
+ const modelSelect = document.getElementById("model-select");
28
+ const forecastSlider = document.getElementById("forecast-slider");
29
+ const forecastVal = document.getElementById("forecast-val");
30
+ const rainOverride = document.getElementById("rain-override");
31
+ const rainOverrideVal = document.getElementById("rain-override-val");
32
+ const eventOverride = document.getElementById("event-override");
33
+ const predictBtn = document.getElementById("predict-btn");
34
+ const exportBtn = document.getElementById("export-btn");
35
+
36
+ // Weather elements
37
+ const weatherForecastText = document.getElementById("weather-forecast-text");
38
+ const weatherLocationText = document.getElementById("weather-location-text");
39
+ const weatherPrecip = document.getElementById("weather-precip");
40
+ const weatherAlert = document.getElementById("weather-alert");
41
+ const eventDescText = document.getElementById("event-desc-text");
42
+
43
+ // Stats elements
44
+ const statTotalVolume = document.getElementById("stat-total-volume");
45
+ const statRiskStatus = document.getElementById("stat-risk-status");
46
+ const statTrucks = document.getElementById("stat-trucks");
47
+
48
+ // Metadata elements
49
+ const statPeriodMeta = document.getElementById("stat-period-meta");
50
+ const statLocationMeta = document.getElementById("stat-location-meta");
51
+
52
+ // Composition elements
53
+ const valOrganic = document.getElementById("val-organic");
54
+ const valPlastic = document.getElementById("val-plastic");
55
+ const barOrganic = document.getElementById("bar-organic");
56
+ const barPlastic = document.getElementById("bar-plastic");
57
+
58
+ // Logistics elements
59
+ const logManpower = document.getElementById("log-manpower");
60
+ const logDuration = document.getElementById("log-duration");
61
+ const logEfficiency = document.getElementById("log-efficiency");
62
+ const logConfidence = document.getElementById("log-confidence");
63
+
64
+ // Timeline & Hourly
65
+ const timelineList = document.getElementById("timeline-list");
66
+ const hourlySection = document.getElementById("hourly-section");
67
+ const hourlyGrid = document.getElementById("hourly-grid");
68
+
69
+ // State
70
+ let selectedLocation = "JIS";
71
+ let rainValue = 0; // 0 means Auto (Open-Meteo)
72
+ let map;
73
+ let mapMarkers = {};
74
+ let routeLine = null;
75
+
76
+ // Event Listeners for controls
77
+ forecastSlider.addEventListener("input", (e) => {
78
+ forecastVal.textContent = e.target.value;
79
+ });
80
+
81
+ rainOverride.addEventListener("input", (e) => {
82
+ const val = parseInt(e.target.value);
83
+ rainValue = val;
84
+ if (val === 0) {
85
+ rainOverrideVal.textContent = "Auto (Open-Meteo)";
86
+ } else {
87
+ rainOverrideVal.textContent = `${val} mm`;
88
+ }
89
+ updateRainAnimationIntensity(val);
90
+ });
91
+
92
+ locationSelect.addEventListener("change", (e) => {
93
+ selectedLocation = e.target.value;
94
+ updateActiveMapMarker(selectedLocation);
95
+ panToLocation(selectedLocation);
96
+ fetchLiveWeather(selectedLocation);
97
+ });
98
+
99
+ // Initialize Leaflet Map
100
+ function initMap() {
101
+ // Centered around Central Jakarta
102
+ map = L.map('map', {
103
+ zoomControl: true,
104
+ attributionControl: false,
105
+ maxZoom: 15,
106
+ minZoom: 9
107
+ }).setView([-6.175, 106.825], 11.5);
108
+
109
+ // CartoDB Dark Matter tile layer for premium dark look
110
+ L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
111
+ maxZoom: 20
112
+ }).addTo(map);
113
+
114
+ // Add Special Final Disposal Site (TPST Bantargebang) Marker
115
+ const bantarIcon = L.divIcon({
116
+ className: 'leaflet-custom-marker bantar-marker',
117
+ html: `<div class="marker-pulse" style="background:#FF9900;opacity:0.25;"></div><div class="marker-core" style="background:#FF9900;border:2px solid #FFF;"></div><div class="marker-label" style="color:#FF9900;border-color:#FF9900;">Bantargebang</div>`,
118
+ iconSize: [24, 24],
119
+ iconAnchor: [12, 12]
120
+ });
121
+ L.marker(BANTARGEBANG_COORDS, { icon: bantarIcon }).addTo(map).bindPopup(`
122
+ <div class="route-popup" style="border-left: 3px solid #FF9900;">
123
+ <h3 style="color:#FF9900;">TPST BANTARGEBANG</h3>
124
+ <div>Disposal Facility (Bekasi)</div>
125
+ <div>Active Capacity: <b>Calibrated</b></div>
126
+ </div>
127
+ `);
128
+
129
+ // Add Custom Location Markers
130
+ Object.keys(LOCATION_MAP_DATA).forEach(loc => {
131
+ const data = LOCATION_MAP_DATA[loc];
132
+ const customIcon = L.divIcon({
133
+ className: 'leaflet-custom-marker',
134
+ html: `<div class="marker-pulse"></div><div class="marker-core"></div><div class="marker-label">${data.label}</div>`,
135
+ iconSize: [24, 24],
136
+ iconAnchor: [12, 12]
137
+ });
138
+
139
+ const marker = L.marker(data.coords, { icon: customIcon }).addTo(map);
140
+
141
+ // Marker Click logic
142
+ marker.on('click', () => {
143
+ selectedLocation = loc;
144
+ locationSelect.value = loc;
145
+ updateActiveMapMarker(loc);
146
+ panToLocation(loc);
147
+ fetchLiveWeather(loc);
148
+ runPrediction();
149
+ });
150
+
151
+ // Save reference
152
+ mapMarkers[loc] = marker;
153
+ });
154
+
155
+ // Initial Active Node highlight
156
+ setTimeout(() => {
157
+ updateActiveMapMarker(selectedLocation);
158
+ }, 1000);
159
+ }
160
+
161
+ function updateActiveMapMarker(locName) {
162
+ Object.keys(mapMarkers).forEach(loc => {
163
+ const marker = mapMarkers[loc];
164
+ const el = marker.getElement();
165
+ if (el) {
166
+ if (loc === locName) {
167
+ el.classList.add("active");
168
+ } else {
169
+ el.classList.remove("active");
170
+ }
171
+ }
172
+ });
173
+ }
174
+
175
+ function panToLocation(locName) {
176
+ const coords = LOCATION_MAP_DATA[locName]?.coords;
177
+ if (coords && map) {
178
+ map.panTo(coords);
179
+ }
180
+ }
181
+
182
+ function updateMarkerRisk(locName, riskStatus) {
183
+ const marker = mapMarkers[locName];
184
+ if (marker) {
185
+ const el = marker.getElement();
186
+ if (el) {
187
+ el.classList.remove("safe", "warning", "critical");
188
+ el.classList.add(riskStatus.toLowerCase());
189
+ }
190
+ }
191
+ }
192
+
193
+ // Draw polyline route to TPST Bantargebang
194
+ function drawTransitRoute(locName) {
195
+ const startCoords = LOCATION_MAP_DATA[locName]?.coords;
196
+ if (!startCoords || !map) return;
197
+
198
+ if (routeLine) {
199
+ map.removeLayer(routeLine);
200
+ }
201
+
202
+ // Cyan glowing dashed line representing logistical transit path
203
+ routeLine = L.polyline([startCoords, BANTARGEBANG_COORDS], {
204
+ color: '#00F0FF',
205
+ weight: 3.5,
206
+ opacity: 0.75,
207
+ dashArray: '8, 8',
208
+ className: 'glowing-route'
209
+ }).addTo(map);
210
+
211
+ // Hardcoded logistics profile distance mappings
212
+ const distanceMap = {
213
+ "JIS": "41.2 km",
214
+ "GBK": "38.5 km",
215
+ "Pasar Senen": "34.8 km",
216
+ "Gang Sempit Tambora": "43.5 km"
217
+ };
218
+
219
+ const timeMap = {
220
+ "JIS": "1.5 Hours",
221
+ "GBK": "1.8 Hours",
222
+ "Pasar Senen": "1.4 Hours",
223
+ "Gang Sempit Tambora": "2.1 Hours"
224
+ };
225
+
226
+ routeLine.bindPopup(`
227
+ <div class="route-popup">
228
+ <h3>LOGISTICS DISPATCH ROUTE</h3>
229
+ <div>Start: <b>${locName}</b></div>
230
+ <div>Destination: <b>TPST Bantargebang</b></div>
231
+ <div>Transit Distance: <b class="highlight">${distanceMap[locName]}</b></div>
232
+ <div>Est. Travel Time: <b class="highlight">${timeMap[locName]}</b></div>
233
+ </div>
234
+ `).openPopup();
235
+
236
+ // Automatically zoom/fit bounds to show both the collection point and Bantargebang nicely
237
+ map.fitBounds([startCoords, BANTARGEBANG_COORDS], {
238
+ padding: [60, 60]
239
+ });
240
+ }
241
+
242
+ // Fetch Live Weather from Open-Meteo
243
+ async function fetchLiveWeather(loc) {
244
+ const coord = LOCATION_COORDINATES[loc];
245
+ if (!coord) return;
246
+
247
+ weatherForecastText.textContent = "Fetching...";
248
+ weatherPrecip.textContent = "0.0 mm";
249
+ weatherAlert.textContent = "Checking...";
250
+
251
+ const url = `https://api.open-meteo.com/v1/forecast?latitude=${coord.latitude}&longitude=${coord.longitude}&current_weather=true&daily=precipitation_sum&timezone=Asia/Jakarta&past_days=2`;
252
+
253
+ try {
254
+ const response = await fetch(url);
255
+ if (response.ok) {
256
+ const data = await response.json();
257
+ const temp = data.current_weather.temperature;
258
+ const wind = data.current_weather.windspeed;
259
+ const code = data.current_weather.weathercode;
260
+
261
+ // Set precip sum for today
262
+ const dailyData = data.daily || {};
263
+ const precipList = dailyData.precipitation_sum || [];
264
+ // past_days=2 means indices: 0 (H-2), 1 (H-1), 2 (H0)
265
+ const precipToday = precipList[2] || 0;
266
+
267
+ let cond = "Cloudy";
268
+ if (code === 0) cond = "Clear Sky";
269
+ else if (code > 0 && code < 4) cond = "Partly Cloudy";
270
+ else if (code >= 51 && code <= 67) cond = "Rainy";
271
+ else if (code >= 80 && code <= 82) cond = "Showers";
272
+
273
+ weatherForecastText.textContent = `${temp}°C - ${cond}`;
274
+ weatherLocationText.textContent = `${loc} coordinates`;
275
+ weatherPrecip.textContent = `${precipToday.toFixed(1)} mm`;
276
+
277
+ if (precipToday > 30) {
278
+ weatherAlert.textContent = "HEAVY RAIN 🟡";
279
+ weatherAlert.className = "highlight text-warning";
280
+ } else if (precipToday > 50) {
281
+ weatherAlert.textContent = "FLOOD DANGER 🔴";
282
+ weatherAlert.className = "highlight text-red";
283
+ } else {
284
+ weatherAlert.textContent = "Normal conditions";
285
+ weatherAlert.className = "highlight";
286
+ }
287
+ } else {
288
+ throw new Error("HTTP Error");
289
+ }
290
+ } catch (err) {
291
+ weatherForecastText.textContent = "Weather Unavailable";
292
+ weatherPrecip.textContent = "N/A";
293
+ weatherAlert.textContent = "Error fetching";
294
+ }
295
+ }
296
+
297
+ // Run prediction calling FastAPI backend
298
+ async function runPrediction() {
299
+ predictBtn.disabled = true;
300
+ predictBtn.querySelector(".btn-text").textContent = "PROCESSING FORECAST...";
301
+
302
+ const payload = {
303
+ forecast_days: parseInt(forecastSlider.value),
304
+ rainfall_mm: parseFloat(rainValue),
305
+ event_scale: parseInt(eventOverride.value),
306
+ location: selectedLocation,
307
+ model_type: modelSelect.value,
308
+ granularity: forecastSlider.value <= 7 ? "hourly" : "daily" // auto hourly for short horizons
309
+ };
310
+
311
+ try {
312
+ const response = await fetch("/api/v1/predict", {
313
+ method: "POST",
314
+ headers: {
315
+ "Content-Type": "application/json"
316
+ },
317
+ body: JSON.stringify(payload)
318
+ });
319
+
320
+ if (response.ok) {
321
+ const resData = await response.json();
322
+ updateDashboardData(resData.data, resData.confidence_score, resData.message);
323
+ } else {
324
+ alert("Prediction failed. Make sure the API server is running.");
325
+ }
326
+ } catch (err) {
327
+ console.error(err);
328
+ alert("Network error connecting to Waste Intelligence API.");
329
+ } finally {
330
+ predictBtn.disabled = false;
331
+ predictBtn.querySelector(".btn-text").textContent = "RUN PREDICTION";
332
+ }
333
+ }
334
+
335
+ function updateDashboardData(data, confScore, message) {
336
+ const results = data.prediction_results;
337
+ if (results.length === 0) return;
338
+
339
+ // Calculate sum of tonnage
340
+ const totalVolume = results.reduce((acc, curr) => acc + curr.total_volume_ton, 0);
341
+ const avgVolume = totalVolume / results.length;
342
+
343
+ // Update main cards
344
+ statTotalVolume.innerHTML = `${totalVolume.toFixed(2)} <span class="unit">Tons</span>`;
345
+
346
+ // Determine overall risk
347
+ let maxRisk = "SAFE";
348
+ results.forEach(r => {
349
+ if (r.risk_status === "CRITICAL") maxRisk = "CRITICAL";
350
+ else if (r.risk_status === "WARNING" && maxRisk !== "CRITICAL") maxRisk = "WARNING";
351
+ });
352
+
353
+ statRiskStatus.textContent = maxRisk;
354
+ statRiskStatus.className = `card-value status-badge ${maxRisk.toLowerCase()}`;
355
+
356
+ // Update map marker risk status dynamically
357
+ updateMarkerRisk(selectedLocation, maxRisk);
358
+
359
+ // Draw active logistics route to TPST Bantargebang
360
+ drawTransitRoute(selectedLocation);
361
+
362
+ statTrucks.innerHTML = `${data.logistics_plan.trucks_needed} <span class="unit">Trucks (5T)</span>`;
363
+
364
+ // Update metadata labels (Prediction Period & Target Location with Radius)
365
+ const startDateStr = results[0].date;
366
+ const endDateStr = results[results.length - 1].date;
367
+
368
+ statPeriodMeta.textContent = `Period: ${startDateStr} to ${endDateStr}`;
369
+ statLocationMeta.textContent = `${selectedLocation} (Radius ${LOCATION_RADIUS[selectedLocation]})`;
370
+
371
+ // Composition Breakdown
372
+ const totalOrganic = results.reduce((acc, curr) => acc + curr.organic_waste_ton, 0);
373
+ const totalPlastic = results.reduce((acc, curr) => acc + curr.plastic_waste_ton, 0);
374
+
375
+ valOrganic.textContent = `${totalOrganic.toFixed(2)} Ton`;
376
+ valPlastic.textContent = `${totalPlastic.toFixed(2)} Ton`;
377
+
378
+ const organicPercentage = totalVolume > 0 ? (totalOrganic / totalVolume) * 100 : 0;
379
+ const plasticPercentage = totalVolume > 0 ? (totalPlastic / totalVolume) * 100 : 0;
380
+
381
+ barOrganic.style.width = `${organicPercentage}%`;
382
+ barPlastic.style.width = `${plasticPercentage}%`;
383
+
384
+ // Logistics plan
385
+ logManpower.textContent = `${data.logistics_plan.manpower} Crew`;
386
+ logDuration.textContent = `${data.logistics_plan.estimated_duration_hours.toFixed(1)} Hours`;
387
+ logEfficiency.textContent = data.logistics_plan.efficiency_rate;
388
+ logConfidence.textContent = `${(confScore * 100).toFixed(1)}%`;
389
+
390
+ // Event Info banner
391
+ const eventDay = results.find(r => r.event_info !== null);
392
+ if (eventDay) {
393
+ eventDescText.innerHTML = `⚠️ <strong>${eventDay.event_info}</strong> on ${eventDay.date}. Heavy crowd expected near site.`;
394
+ document.getElementById("event-box").style.borderColor = "var(--red)";
395
+ } else {
396
+ eventDescText.textContent = "No major public events scheduled for this location in the forecast window.";
397
+ document.getElementById("event-box").style.borderColor = "var(--yellow)";
398
+ }
399
+
400
+ // Daily timeline breakdown cards
401
+ timelineList.innerHTML = "";
402
+ results.forEach(day => {
403
+ const card = document.createElement("div");
404
+ card.className = "timeline-card";
405
+
406
+ const dateObj = new Date(day.date);
407
+ const dayName = dateObj.toLocaleDateString('en-US', { weekday: 'short' });
408
+ const displayDate = `${dayName}, ${dateObj.getDate()} ${dateObj.toLocaleString('en-US', { month: 'short' })}`;
409
+
410
+ card.innerHTML = `
411
+ <span class="timeline-date">${displayDate}</span>
412
+ <span class="timeline-vol">${day.total_volume_ton.toFixed(1)} T</span>
413
+ <span class="timeline-status ${day.risk_status.toLowerCase()}">${day.risk_status}</span>
414
+ `;
415
+ timelineList.appendChild(card);
416
+ });
417
+
418
+ // Hourly Risk Heatmap
419
+ const hourlyDay = results[0]; // show hourly for first day if requested
420
+ if (hourlyDay && hourlyDay.hourly_breakdown) {
421
+ hourlySection.style.display = "block";
422
+ hourlyGrid.innerHTML = "";
423
+ hourlyDay.hourly_breakdown.forEach(hour => {
424
+ const cell = document.createElement("div");
425
+ cell.className = "hourly-cell";
426
+
427
+ let intensityClass = "low";
428
+ if (hour.risk_indicator === "MEDIUM") intensityClass = "medium";
429
+ else if (hour.risk_indicator === "HIGH") intensityClass = "high";
430
+
431
+ cell.innerHTML = `
432
+ <div class="cell-block ${intensityClass}" title="Vol: ${hour.estimated_volume_ton} Ton - Risk: ${hour.risk_indicator}"></div>
433
+ <span class="cell-time">${hour.hour}</span>
434
+ `;
435
+ hourlyGrid.appendChild(cell);
436
+ });
437
+ } else {
438
+ hourlySection.style.display = "none";
439
+ }
440
+ }
441
+
442
+ // Request CSV from Backend API and download it
443
+ async function runExport() {
444
+ exportBtn.disabled = true;
445
+ exportBtn.querySelector(".btn-text").textContent = "EXPORTING...";
446
+
447
+ const payload = {
448
+ forecast_days: parseInt(forecastSlider.value),
449
+ rainfall_mm: parseFloat(rainValue),
450
+ event_scale: parseInt(eventOverride.value),
451
+ location: selectedLocation,
452
+ model_type: modelSelect.value,
453
+ granularity: forecastSlider.value <= 7 ? "hourly" : "daily"
454
+ };
455
+
456
+ try {
457
+ const response = await fetch("/api/v1/predict/csv", {
458
+ method: "POST",
459
+ headers: {
460
+ "Content-Type": "application/json"
461
+ },
462
+ body: JSON.stringify(payload)
463
+ });
464
+
465
+ if (response.ok) {
466
+ const blob = await response.blob();
467
+ const url = window.URL.createObjectURL(blob);
468
+ const a = document.createElement("a");
469
+ a.href = url;
470
+ a.download = `waste_forecast_${selectedLocation.replace(/\s+/g, "_")}_${forecastSlider.value}d.csv`;
471
+ document.body.appendChild(a);
472
+ a.click();
473
+ a.remove();
474
+ window.URL.revokeObjectURL(url);
475
+ } else {
476
+ alert("CSV export failed. Ensure the server is online.");
477
+ }
478
+ } catch (err) {
479
+ console.error(err);
480
+ alert("Network error connecting to Export API.");
481
+ } finally {
482
+ exportBtn.disabled = false;
483
+ exportBtn.querySelector(".btn-text").textContent = "EXPORT CSV";
484
+ }
485
+ }
486
+
487
+ predictBtn.addEventListener("click", runPrediction);
488
+ exportBtn.addEventListener("click", runExport);
489
+
490
+ // Initial loading setup
491
+ window.addEventListener("DOMContentLoaded", () => {
492
+ initMap();
493
+ fetchLiveWeather("JIS");
494
+ // Run initial prediction after models load
495
+ setTimeout(runPrediction, 1000);
496
+ });
497
+
498
+
499
+ // ==========================================
500
+ // BACKGROUND CANVAS: INTERACTIVE RAIN EFFECT
501
+ // ==========================================
502
+ const canvas = document.getElementById("rain-canvas");
503
+ const ctx = canvas.getContext("2d");
504
+
505
+ let width = canvas.width = window.innerWidth;
506
+ let height = canvas.height = window.innerHeight;
507
+
508
+ window.addEventListener("resize", () => {
509
+ width = canvas.width = window.innerWidth;
510
+ height = canvas.height = window.innerHeight;
511
+ });
512
+
513
+ let drops = [];
514
+ let particles = [];
515
+ let maxPrecip = 0; // Current precipitation override
516
+
517
+ function updateRainAnimationIntensity(precipVal) {
518
+ maxPrecip = precipVal;
519
+ }
520
+
521
+ // Particle class for normal state (no rain)
522
+ class DataParticle {
523
+ constructor() {
524
+ this.reset();
525
+ }
526
+ reset() {
527
+ this.x = Math.random() * width;
528
+ this.y = Math.random() * height;
529
+ this.size = Math.random() * 2 + 1;
530
+ this.speedX = Math.random() * 0.4 - 0.2;
531
+ this.speedY = Math.random() * -0.5 - 0.2;
532
+ this.alpha = Math.random() * 0.5 + 0.1;
533
+ }
534
+ update() {
535
+ this.x += this.speedX;
536
+ this.y += this.speedY;
537
+ if (this.y < 0 || this.x < 0 || this.x > width) {
538
+ this.reset();
539
+ this.y = height;
540
+ }
541
+ }
542
+ draw() {
543
+ ctx.fillStyle = `rgba(0, 240, 255, ${this.alpha})`;
544
+ ctx.beginPath();
545
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
546
+ ctx.fill();
547
+ }
548
+ }
549
+
550
+ // Rain Drop class for rainy state
551
+ class RainDrop {
552
+ constructor() {
553
+ this.reset();
554
+ }
555
+ reset() {
556
+ this.x = Math.random() * width;
557
+ this.y = Math.random() * -100 - 10;
558
+ this.length = Math.random() * 15 + 10;
559
+ this.speed = Math.random() * 12 + 15;
560
+ this.weight = Math.random() * 1 + 0.5;
561
+ this.alpha = Math.random() * 0.3 + 0.1;
562
+ }
563
+ update() {
564
+ this.y += this.speed;
565
+ if (this.y > height) {
566
+ this.reset();
567
+ }
568
+ }
569
+ draw() {
570
+ ctx.strokeStyle = `rgba(0, 240, 255, ${this.alpha})`;
571
+ ctx.lineWidth = this.weight;
572
+ ctx.beginPath();
573
+ ctx.moveTo(this.x, this.y);
574
+ ctx.lineTo(this.x + (maxPrecip * 0.05), this.y + this.length);
575
+ ctx.stroke();
576
+ }
577
+ }
578
+
579
+ // Initialize particles and rain drops
580
+ for (let i = 0; i < 60; i++) {
581
+ particles.push(new DataParticle());
582
+ }
583
+ for (let i = 0; i < 150; i++) {
584
+ drops.push(new RainDrop());
585
+ }
586
+
587
+ function animate() {
588
+ ctx.clearRect(0, 0, width, height);
589
+
590
+ if (maxPrecip === 0) {
591
+ // Normal floating data particles
592
+ particles.forEach(p => {
593
+ p.update();
594
+ p.draw();
595
+ });
596
+ } else {
597
+ // Cyber rain drops falling
598
+ // Number of raindrops drawn depends on the rain scale override
599
+ const activeCount = Math.min(Math.floor(maxPrecip * 1.5), 150);
600
+ for (let i = 0; i < activeCount; i++) {
601
+ drops[i].update();
602
+ drops[i].draw();
603
+ }
604
+ }
605
+
606
+ requestAnimationFrame(animate);
607
+ }
608
+
609
+ animate();
static/index.html ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Waste Intelligence Dashboard - DKI Jakarta 2026</title>
7
+ <!-- Google Fonts -->
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@300;400;700&display=swap" rel="stylesheet">
11
+
12
+ <!-- Leaflet.js Map Library -->
13
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
14
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
15
+
16
+ <link rel="stylesheet" href="/static/style.css">
17
+ </head>
18
+ <body>
19
+ <!-- Background Canvas for Interactive Particle Rain -->
20
+ <canvas id="rain-canvas"></canvas>
21
+
22
+ <header>
23
+ <div class="logo-container">
24
+ <span class="logo-text">WASTE<span class="highlight">INTELLIGENCE</span></span>
25
+ <span class="version-tag">v3.0.0 (Calibrated)</span>
26
+ </div>
27
+ <div class="system-status">
28
+ <span class="status-indicator online"></span>
29
+ <span class="status-label">System Online</span>
30
+ </div>
31
+ </header>
32
+
33
+ <main class="dashboard-grid">
34
+ <!-- Panel Kontrol (Sidebar) -->
35
+ <section class="panel control-panel">
36
+ <h2 class="panel-title">PREDICTION CONFIG</h2>
37
+ <div class="control-group">
38
+ <label for="location-select">Target Location</label>
39
+ <select id="location-select" class="form-control">
40
+ <option value="JIS" selected>JIS (North Jakarta)</option>
41
+ <option value="GBK">GBK (Central/South Jakarta)</option>
42
+ <option value="Pasar Senen">Pasar Senen (Central Jakarta)</option>
43
+ <option value="Gang Sempit Tambora">Gang Sempit Tambora (West Jakarta)</option>
44
+ </select>
45
+ </div>
46
+
47
+ <div class="control-group">
48
+ <label for="model-select">AI Forecasting Model</label>
49
+ <select id="model-select" class="form-control">
50
+ <option value="chronos">Amazon Chronos-T5 (Tiny)</option>
51
+ <option value="gradient_boosting" selected>Gradient Boosting (Real Data - 94.0% Accuracy)</option>
52
+ </select>
53
+ </div>
54
+
55
+ <div class="control-group">
56
+ <label for="forecast-slider">Forecast Horizon: <span id="forecast-val" class="value-display">7</span> Days</label>
57
+ <input type="range" id="forecast-slider" min="1" max="30" value="7" class="slider">
58
+ </div>
59
+
60
+ <div class="divider"></div>
61
+ <h3 class="panel-subtitle">Simulation Overrides</h3>
62
+
63
+ <div class="control-group">
64
+ <label for="rain-override">Manual Rain Override (mm)</label>
65
+ <div class="range-override-container">
66
+ <input type="range" id="rain-override" min="0" max="100" value="0" class="slider">
67
+ <span id="rain-override-val" class="override-display">Auto (Open-Meteo)</span>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="control-group">
72
+ <label for="event-override">Crowd Event Scale (0 - 5)</label>
73
+ <input type="range" id="event-override" min="0" max="5" value="0" class="slider">
74
+ </div>
75
+
76
+ <div class="button-row">
77
+ <button id="predict-btn" class="action-btn">
78
+ <span class="btn-text">RUN PREDICTION</span>
79
+ <span class="btn-glow"></span>
80
+ </button>
81
+ <button id="export-btn" class="action-btn secondary-btn">
82
+ <span class="btn-text">EXPORT CSV</span>
83
+ <span class="btn-glow"></span>
84
+ </button>
85
+ </div>
86
+ </section>
87
+
88
+ <!-- Peta Interaktif & Main Stats -->
89
+ <section class="map-and-stats">
90
+ <!-- Peta Leaflet.js -->
91
+ <div class="panel map-panel">
92
+ <h2 class="panel-title">JAKARTA SPATIAL REALTIME MAP</h2>
93
+ <div class="map-container">
94
+ <div id="map"></div>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Stats Real-Time -->
99
+ <div class="stats-row">
100
+ <div class="panel stat-card text-glow">
101
+ <span class="card-label">TOTAL FORECAST VOLUME</span>
102
+ <span id="stat-total-volume" class="card-value">0.00 <span class="unit">Tons</span></span>
103
+ <span class="card-meta" id="stat-period-meta">Period: N/A</span>
104
+ </div>
105
+ <div class="panel stat-card">
106
+ <span class="card-label">RISK STATUS</span>
107
+ <span id="stat-risk-status" class="card-value status-badge safe">SAFE</span>
108
+ <span class="card-meta" id="stat-location-meta">JIS (Radius 1.5 km)</span>
109
+ </div>
110
+ <div class="panel stat-card">
111
+ <span class="card-label">RECOMMENDED FLEET</span>
112
+ <span id="stat-trucks" class="card-value">0 <span class="unit">Trucks (5T)</span></span>
113
+ <span class="card-meta">Logistics Fleet Suggestion</span>
114
+ </div>
115
+ </div>
116
+ </section>
117
+
118
+ <!-- Rincian Logistik & Analisis -->
119
+ <section class="analysis-panel">
120
+ <!-- Kategori / Komposisi Sampah -->
121
+ <div class="panel category-panel">
122
+ <h2 class="panel-title">WASTE COMPOSITION</h2>
123
+ <div class="progress-container">
124
+ <div class="progress-item">
125
+ <div class="progress-header">
126
+ <span>Organic / Sisa Makanan (49.87%)</span>
127
+ <span id="val-organic">0.00 Ton</span>
128
+ </div>
129
+ <div class="progress-bar-bg">
130
+ <div id="bar-organic" class="progress-bar-fill organic" style="width: 0%;"></div>
131
+ </div>
132
+ </div>
133
+ <div class="progress-item">
134
+ <div class="progress-header">
135
+ <span>Plastic / Plastik (22.95%)</span>
136
+ <span id="val-plastic">0.00 Ton</span>
137
+ </div>
138
+ <div class="progress-bar-bg">
139
+ <div id="bar-plastic" class="progress-bar-fill plastic" style="width: 0%;"></div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Widget Cuaca Live & Info Event -->
146
+ <div class="panel weather-event-panel">
147
+ <h2 class="panel-title">WEATHER & EVENT FORECAST</h2>
148
+ <div class="weather-grid">
149
+ <div class="weather-info">
150
+ <span class="weather-temp" id="weather-forecast-text">Fetching Live...</span>
151
+ <span class="weather-label" id="weather-location-text">Jakarta, Indonesia</span>
152
+ </div>
153
+ <div class="weather-details">
154
+ <div>Precipitation (Rain): <span id="weather-precip" class="highlight">0.0 mm</span></div>
155
+ <div>BMKG Alert: <span id="weather-alert" class="highlight">None</span></div>
156
+ </div>
157
+ </div>
158
+ <div class="event-box" id="event-box">
159
+ <span class="event-title">Upcoming Event Calendar</span>
160
+ <p class="event-desc" id="event-desc-text">No major events registered for today.</p>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Logistics Plan -->
165
+ <div class="panel logistics-panel">
166
+ <h2 class="panel-title">OPERATIONAL LOGISTICS PLAN</h2>
167
+ <div class="logistics-grid">
168
+ <div class="log-item">
169
+ <span class="log-label">Manpower Required</span>
170
+ <span id="log-manpower" class="log-value">0 Crew</span>
171
+ </div>
172
+ <div class="log-item">
173
+ <span class="log-label">Estimated Collection Time</span>
174
+ <span id="log-duration" class="log-value">0.0 Hours</span>
175
+ </div>
176
+ <div class="log-item">
177
+ <span class="log-label">Operational Efficiency</span>
178
+ <span id="log-efficiency" class="log-value">85% (Optimal)</span>
179
+ </div>
180
+ <div class="log-item">
181
+ <span class="log-label">Confidence Score</span>
182
+ <span id="log-confidence" class="log-value highlight">92.0%</span>
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </section>
187
+ </main>
188
+
189
+ <!-- Timeline Harian -->
190
+ <section class="container timeline-container">
191
+ <div class="panel timeline-panel">
192
+ <h2 class="panel-title">DAILY TIMELINE BREAKDOWN</h2>
193
+ <div id="timeline-list" class="timeline-list">
194
+ <!-- Will be dynamically populated -->
195
+ <div class="empty-timeline">Run a prediction to generate the forecast timeline.</div>
196
+ </div>
197
+ </div>
198
+ </section>
199
+
200
+ <!-- Hourly Breakdown (for hourly granularity) -->
201
+ <section id="hourly-section" class="container hourly-container" style="display: none;">
202
+ <div class="panel hourly-panel">
203
+ <h2 class="panel-title">HOURLY DISPATCH RISK HEATMAP</h2>
204
+ <div class="hourly-grid" id="hourly-grid">
205
+ <!-- Dynamically populated -->
206
+ </div>
207
+ </div>
208
+ </section>
209
+
210
+ <footer>
211
+ <p>&copy; 2026 Waste Intelligence System - DKI Jakarta Province. Calibrated using DLH & KLHK Statistics.</p>
212
+ </footer>
213
+
214
+ <!-- Scripts -->
215
+ <script src="/static/app.js"></script>
216
+ </body>
217
+ </html>
static/style.css ADDED
@@ -0,0 +1,780 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* CSS Variables for Theming */
2
+ :root {
3
+ --bg-void: #02040a;
4
+ --bg-panel: rgba(6, 10, 22, 0.75);
5
+ --border-color: rgba(0, 240, 255, 0.12);
6
+ --border-hover: rgba(0, 240, 255, 0.3);
7
+
8
+ --text-main: #E1E3E8;
9
+ --text-muted: #8c93a3;
10
+
11
+ /* Neon Colors */
12
+ --cyan: #00F0FF;
13
+ --cyan-glow: rgba(0, 240, 255, 0.45);
14
+ --green: #39FF14;
15
+ --green-glow: rgba(57, 255, 20, 0.35);
16
+ --yellow: #FFE600;
17
+ --yellow-glow: rgba(255, 230, 0, 0.35);
18
+ --red: #FF0055;
19
+ --red-glow: rgba(255, 0, 85, 0.45);
20
+
21
+ /* Fonts */
22
+ --font-display: 'Outfit', 'Space Grotesk', system-ui, sans-serif;
23
+ --font-body: 'Space Grotesk', system-ui, sans-serif;
24
+ --font-mono: 'JetBrains Mono', monospace;
25
+ }
26
+
27
+ /* Base resets */
28
+ * {
29
+ box-sizing: border-box;
30
+ margin: 0;
31
+ padding: 0;
32
+ }
33
+
34
+ body {
35
+ background-color: var(--bg-void);
36
+ color: var(--text-main);
37
+ font-family: var(--font-body);
38
+ overflow-x: hidden;
39
+ min-height: 100vh;
40
+ display: flex;
41
+ flex-direction: column;
42
+ }
43
+
44
+ /* Background Rain Canvas */
45
+ #rain-canvas {
46
+ position: fixed;
47
+ top: 0;
48
+ left: 0;
49
+ width: 100%;
50
+ height: 100%;
51
+ z-index: 0;
52
+ pointer-events: none;
53
+ opacity: 0.45;
54
+ }
55
+
56
+ /* Header */
57
+ header {
58
+ width: 100%;
59
+ padding: 1.5rem 2.5rem;
60
+ background: linear-gradient(to bottom, rgba(2, 4, 10, 0.95) 60%, transparent);
61
+ display: flex;
62
+ justify-content: space-between;
63
+ align-items: center;
64
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
65
+ z-index: 10;
66
+ position: relative;
67
+ }
68
+
69
+ .logo-container {
70
+ display: flex;
71
+ align-items: baseline;
72
+ gap: 10px;
73
+ }
74
+
75
+ .logo-text {
76
+ font-family: var(--font-display);
77
+ font-size: 1.8rem;
78
+ font-weight: 800;
79
+ letter-spacing: 2px;
80
+ background: linear-gradient(135deg, #FFF 40%, var(--cyan) 100%);
81
+ -webkit-background-clip: text;
82
+ -webkit-text-fill-color: transparent;
83
+ text-shadow: 0 0 20px rgba(0, 240, 255, 0.15);
84
+ }
85
+
86
+ .logo-text .highlight {
87
+ font-weight: 300;
88
+ letter-spacing: 0px;
89
+ color: var(--cyan);
90
+ margin-left: 4px;
91
+ }
92
+
93
+ .version-tag {
94
+ font-family: var(--font-mono);
95
+ font-size: 0.75rem;
96
+ color: var(--text-muted);
97
+ background: rgba(255, 255, 255, 0.05);
98
+ padding: 2px 8px;
99
+ border-radius: 4px;
100
+ border: 1px solid rgba(255, 255, 255, 0.08);
101
+ }
102
+
103
+ .system-status {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 8px;
107
+ }
108
+
109
+ .status-indicator {
110
+ width: 8px;
111
+ height: 8px;
112
+ border-radius: 50%;
113
+ }
114
+
115
+ .status-indicator.online {
116
+ background-color: var(--green);
117
+ box-shadow: 0 0 10px var(--green-glow), 0 0 20px var(--green-glow);
118
+ animation: pulse-green 2s infinite;
119
+ }
120
+
121
+ .status-label {
122
+ font-family: var(--font-mono);
123
+ font-size: 0.8rem;
124
+ color: var(--text-muted);
125
+ }
126
+
127
+ /* Grid Layout */
128
+ .dashboard-grid {
129
+ display: grid;
130
+ grid-template-columns: 320px 1fr 340px;
131
+ gap: 1.5rem;
132
+ padding: 1.5rem 2.5rem;
133
+ flex: 1;
134
+ z-index: 5;
135
+ position: relative;
136
+ }
137
+
138
+ @media (max-width: 1200px) {
139
+ .dashboard-grid {
140
+ grid-template-columns: 1fr;
141
+ }
142
+ }
143
+
144
+ /* Panels */
145
+ .panel {
146
+ background: var(--bg-panel);
147
+ border: 1px solid var(--border-color);
148
+ border-radius: 12px;
149
+ padding: 1.5rem;
150
+ backdrop-filter: blur(16px);
151
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
152
+ transition: border-color 0.3s, box-shadow 0.3s;
153
+ }
154
+
155
+ .panel:hover {
156
+ border-color: var(--border-hover);
157
+ box-shadow: 0 8px 32px 0 rgba(0, 240, 255, 0.05);
158
+ }
159
+
160
+ .panel-title {
161
+ font-family: var(--font-display);
162
+ font-size: 0.95rem;
163
+ font-weight: 600;
164
+ letter-spacing: 1.5px;
165
+ color: var(--text-main);
166
+ margin-bottom: 1.2rem;
167
+ border-left: 3px solid var(--cyan);
168
+ padding-left: 8px;
169
+ text-transform: uppercase;
170
+ }
171
+
172
+ .panel-subtitle {
173
+ font-family: var(--font-display);
174
+ font-size: 0.85rem;
175
+ font-weight: 600;
176
+ color: var(--cyan);
177
+ margin-bottom: 1rem;
178
+ text-transform: uppercase;
179
+ }
180
+
181
+ /* Controls */
182
+ .control-group {
183
+ margin-bottom: 1.2rem;
184
+ }
185
+
186
+ .control-group label {
187
+ display: block;
188
+ font-size: 0.8rem;
189
+ color: var(--text-muted);
190
+ margin-bottom: 6px;
191
+ text-transform: uppercase;
192
+ font-family: var(--font-mono);
193
+ }
194
+
195
+ .form-control {
196
+ width: 100%;
197
+ background: rgba(0, 0, 0, 0.4);
198
+ border: 1px solid var(--border-color);
199
+ color: var(--text-main);
200
+ padding: 10px 14px;
201
+ border-radius: 8px;
202
+ outline: none;
203
+ font-family: var(--font-body);
204
+ font-size: 0.9rem;
205
+ transition: border-color 0.3s;
206
+ }
207
+
208
+ .form-control:focus {
209
+ border-color: var(--cyan);
210
+ }
211
+
212
+ .slider {
213
+ width: 100%;
214
+ -webkit-appearance: none;
215
+ height: 6px;
216
+ border-radius: 3px;
217
+ background: rgba(255, 255, 255, 0.1);
218
+ outline: none;
219
+ }
220
+
221
+ .slider::-webkit-slider-thumb {
222
+ -webkit-appearance: none;
223
+ width: 16px;
224
+ height: 16px;
225
+ border-radius: 50%;
226
+ background: var(--cyan);
227
+ box-shadow: 0 0 10px var(--cyan-glow);
228
+ cursor: pointer;
229
+ transition: transform 0.1s;
230
+ }
231
+
232
+ .slider::-webkit-slider-thumb:hover {
233
+ transform: scale(1.25);
234
+ }
235
+
236
+ .value-display, .override-display {
237
+ font-family: var(--font-mono);
238
+ color: var(--cyan);
239
+ font-weight: bold;
240
+ float: right;
241
+ }
242
+
243
+ .range-override-container {
244
+ display: flex;
245
+ flex-direction: column;
246
+ gap: 5px;
247
+ }
248
+
249
+ .divider {
250
+ height: 1px;
251
+ background: rgba(255, 255, 255, 0.05);
252
+ margin: 1.5rem 0;
253
+ }
254
+
255
+ /* Buttons */
256
+ .action-btn {
257
+ width: 100%;
258
+ padding: 12px;
259
+ background: rgba(0, 240, 255, 0.06);
260
+ border: 1px solid var(--cyan);
261
+ color: var(--cyan);
262
+ font-family: var(--font-mono);
263
+ font-size: 0.90rem;
264
+ font-weight: bold;
265
+ border-radius: 8px;
266
+ cursor: pointer;
267
+ position: relative;
268
+ overflow: hidden;
269
+ transition: background 0.3s, box-shadow 0.3s;
270
+ }
271
+
272
+ .action-btn:hover {
273
+ background: rgba(0, 240, 255, 0.15);
274
+ box-shadow: 0 0 20px var(--cyan-glow);
275
+ }
276
+
277
+ /* Map Panel with Leaflet Map */
278
+ .map-panel {
279
+ display: flex;
280
+ flex-direction: column;
281
+ height: 380px;
282
+ margin-bottom: 1.5rem;
283
+ }
284
+
285
+ .map-container {
286
+ flex: 1;
287
+ position: relative;
288
+ background: rgba(0, 0, 0, 0.3);
289
+ border-radius: 8px;
290
+ overflow: hidden;
291
+ border: 1px solid var(--border-color);
292
+ }
293
+
294
+ #map {
295
+ width: 100%;
296
+ height: 100%;
297
+ background: var(--bg-void) !important;
298
+ }
299
+
300
+ /* Custom Marker Styling for Leaflet */
301
+ .leaflet-custom-marker {
302
+ position: relative;
303
+ cursor: pointer;
304
+ }
305
+
306
+ .leaflet-custom-marker .marker-pulse {
307
+ position: absolute;
308
+ top: 50%;
309
+ left: 50%;
310
+ width: 28px;
311
+ height: 28px;
312
+ margin-left: -14px;
313
+ margin-top: -14px;
314
+ background: var(--cyan);
315
+ border-radius: 50%;
316
+ opacity: 0.15;
317
+ transform: scale(1);
318
+ animation: pulse-node 2s infinite;
319
+ pointer-events: none;
320
+ }
321
+
322
+ .leaflet-custom-marker .marker-core {
323
+ position: absolute;
324
+ top: 50%;
325
+ left: 50%;
326
+ width: 12px;
327
+ height: 12px;
328
+ margin-left: -6px;
329
+ margin-top: -6px;
330
+ background: var(--cyan);
331
+ border: 2px solid #FFF;
332
+ border-radius: 50%;
333
+ box-shadow: 0 0 10px var(--cyan-glow);
334
+ transition: transform 0.2s, background 0.3s;
335
+ }
336
+
337
+ .leaflet-custom-marker .marker-label {
338
+ position: absolute;
339
+ top: -24px;
340
+ left: 50%;
341
+ transform: translateX(-50%);
342
+ font-family: var(--font-mono);
343
+ font-size: 10px;
344
+ font-weight: bold;
345
+ color: var(--text-muted);
346
+ background: rgba(0, 0, 0, 0.85);
347
+ padding: 2px 6px;
348
+ border-radius: 4px;
349
+ border: 1px solid rgba(255, 255, 255, 0.15);
350
+ white-space: nowrap;
351
+ pointer-events: none;
352
+ transition: color 0.3s, border-color 0.3s;
353
+ }
354
+
355
+ /* Hover and Active State */
356
+ .leaflet-custom-marker:hover .marker-core {
357
+ transform: scale(1.2);
358
+ }
359
+
360
+ .leaflet-custom-marker.active .marker-core {
361
+ transform: scale(1.3);
362
+ background: var(--cyan);
363
+ box-shadow: 0 0 15px var(--cyan), 0 0 25px var(--cyan-glow);
364
+ }
365
+
366
+ .leaflet-custom-marker.active .marker-label {
367
+ color: var(--cyan);
368
+ border-color: var(--cyan);
369
+ box-shadow: 0 0 10px rgba(0, 240, 255, 0.2);
370
+ }
371
+
372
+ /* Risk indicator classes for Leaflet Markers */
373
+ .leaflet-custom-marker.safe .marker-core {
374
+ background: var(--green);
375
+ box-shadow: 0 0 10px var(--green-glow);
376
+ }
377
+ .leaflet-custom-marker.safe .marker-pulse {
378
+ background: var(--green);
379
+ }
380
+
381
+ .leaflet-custom-marker.warning .marker-core {
382
+ background: var(--yellow);
383
+ box-shadow: 0 0 10px var(--yellow-glow);
384
+ }
385
+ .leaflet-custom-marker.warning .marker-pulse {
386
+ background: var(--yellow);
387
+ }
388
+
389
+ .leaflet-custom-marker.critical .marker-core {
390
+ background: var(--red);
391
+ box-shadow: 0 0 10px var(--red-glow);
392
+ }
393
+ .leaflet-custom-marker.critical .marker-pulse {
394
+ background: var(--red);
395
+ }
396
+
397
+ /* Leaflet Layout UI Overrides */
398
+ .leaflet-bar {
399
+ border: 1px solid var(--border-color) !important;
400
+ border-radius: 8px !important;
401
+ overflow: hidden;
402
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6) !important;
403
+ }
404
+
405
+ .leaflet-bar a {
406
+ background-color: rgba(6, 10, 22, 0.85) !important;
407
+ color: var(--text-main) !important;
408
+ border-bottom: 1px solid var(--border-color) !important;
409
+ transition: background-color 0.2s, color 0.2s;
410
+ }
411
+
412
+ .leaflet-bar a:hover {
413
+ background-color: rgba(0, 240, 255, 0.15) !important;
414
+ color: var(--cyan) !important;
415
+ }
416
+
417
+ .leaflet-container {
418
+ background: var(--bg-void) !important;
419
+ }
420
+
421
+ /* Stats Cards */
422
+ .stats-row {
423
+ display: grid;
424
+ grid-template-columns: repeat(3, 1fr);
425
+ gap: 1.5rem;
426
+ }
427
+
428
+ .stat-card {
429
+ display: flex;
430
+ flex-direction: column;
431
+ justify-content: center;
432
+ padding: 1.2rem;
433
+ height: 100px;
434
+ }
435
+
436
+ .card-label {
437
+ font-family: var(--font-mono);
438
+ font-size: 0.7rem;
439
+ color: var(--text-muted);
440
+ margin-bottom: 6px;
441
+ text-transform: uppercase;
442
+ }
443
+
444
+ .card-value {
445
+ font-family: var(--font-display);
446
+ font-size: 1.5rem;
447
+ font-weight: 800;
448
+ color: #FFF;
449
+ }
450
+
451
+ .card-value .unit {
452
+ font-size: 0.85rem;
453
+ font-weight: 400;
454
+ color: var(--text-muted);
455
+ }
456
+
457
+ .status-badge {
458
+ display: inline-block;
459
+ padding: 4px 12px;
460
+ border-radius: 6px;
461
+ font-size: 0.95rem;
462
+ font-weight: bold;
463
+ text-align: center;
464
+ width: fit-content;
465
+ }
466
+
467
+ .status-badge.safe {
468
+ background: rgba(57, 255, 20, 0.08);
469
+ border: 1px solid var(--green);
470
+ color: var(--green);
471
+ text-shadow: 0 0 10px var(--green-glow);
472
+ }
473
+
474
+ .status-badge.warning {
475
+ background: rgba(255, 230, 0, 0.08);
476
+ border: 1px solid var(--yellow);
477
+ color: var(--yellow);
478
+ text-shadow: 0 0 10px var(--yellow-glow);
479
+ }
480
+
481
+ .status-badge.critical {
482
+ background: rgba(255, 0, 85, 0.08);
483
+ border: 1px solid var(--red);
484
+ color: var(--red);
485
+ text-shadow: 0 0 10px var(--red-glow);
486
+ }
487
+
488
+ /* Composition / Progress bars */
489
+ .progress-item {
490
+ margin-bottom: 1.2rem;
491
+ }
492
+
493
+ .progress-header {
494
+ display: flex;
495
+ justify-content: space-between;
496
+ font-size: 0.8rem;
497
+ color: var(--text-muted);
498
+ margin-bottom: 6px;
499
+ font-family: var(--font-mono);
500
+ }
501
+
502
+ .progress-bar-bg {
503
+ width: 100%;
504
+ height: 8px;
505
+ background: rgba(255, 255, 255, 0.05);
506
+ border-radius: 4px;
507
+ overflow: hidden;
508
+ }
509
+
510
+ .progress-bar-fill {
511
+ height: 100%;
512
+ border-radius: 4px;
513
+ width: 0%;
514
+ transition: width 0.8s cubic-bezier(0.25, 0.8, 0.25, 1);
515
+ }
516
+
517
+ .progress-bar-fill.organic {
518
+ background: linear-gradient(to right, #4CAF50, #8BC34A);
519
+ box-shadow: 0 0 10px rgba(76, 175, 80, 0.3);
520
+ }
521
+
522
+ .progress-bar-fill.plastic {
523
+ background: linear-gradient(to right, var(--cyan), #00BCD4);
524
+ box-shadow: 0 0 10px rgba(0, 240, 255, 0.3);
525
+ }
526
+
527
+ /* Weather & Event Panel */
528
+ .weather-grid {
529
+ display: flex;
530
+ justify-content: space-between;
531
+ align-items: center;
532
+ background: rgba(0, 0, 0, 0.25);
533
+ padding: 12px;
534
+ border-radius: 8px;
535
+ border: 1px solid rgba(255, 255, 255, 0.03);
536
+ margin-bottom: 1rem;
537
+ }
538
+
539
+ .weather-temp {
540
+ font-family: var(--font-display);
541
+ font-size: 1.2rem;
542
+ font-weight: 700;
543
+ color: #FFF;
544
+ }
545
+
546
+ .weather-label {
547
+ display: block;
548
+ font-size: 0.75rem;
549
+ color: var(--text-muted);
550
+ }
551
+
552
+ .weather-details {
553
+ font-family: var(--font-mono);
554
+ font-size: 0.75rem;
555
+ text-align: right;
556
+ line-height: 1.4;
557
+ }
558
+
559
+ .weather-details .highlight {
560
+ color: var(--cyan);
561
+ }
562
+
563
+ .event-box {
564
+ padding: 10px;
565
+ background: rgba(255, 230, 0, 0.03);
566
+ border-left: 3px solid var(--yellow);
567
+ border-radius: 0 6px 6px 0;
568
+ }
569
+
570
+ .event-title {
571
+ display: block;
572
+ font-size: 0.75rem;
573
+ font-weight: bold;
574
+ color: var(--yellow);
575
+ font-family: var(--font-mono);
576
+ margin-bottom: 4px;
577
+ }
578
+
579
+ .event-desc {
580
+ font-size: 0.8rem;
581
+ color: var(--text-main);
582
+ }
583
+
584
+ /* Logistics Grid */
585
+ .logistics-grid {
586
+ display: grid;
587
+ grid-template-columns: 1fr 1fr;
588
+ gap: 12px;
589
+ }
590
+
591
+ .log-item {
592
+ background: rgba(0, 0, 0, 0.2);
593
+ border: 1px solid rgba(255, 255, 255, 0.03);
594
+ border-radius: 8px;
595
+ padding: 10px;
596
+ display: flex;
597
+ flex-direction: column;
598
+ }
599
+
600
+ .log-label {
601
+ font-family: var(--font-mono);
602
+ font-size: 0.65rem;
603
+ color: var(--text-muted);
604
+ text-transform: uppercase;
605
+ margin-bottom: 4px;
606
+ }
607
+
608
+ .log-value {
609
+ font-size: 0.95rem;
610
+ font-weight: bold;
611
+ color: #FFF;
612
+ }
613
+
614
+ .log-value.highlight {
615
+ color: var(--cyan);
616
+ text-shadow: 0 0 10px var(--cyan-glow);
617
+ }
618
+
619
+ /* Timeline Container */
620
+ .timeline-container {
621
+ margin-top: 1.5rem;
622
+ padding: 0 2.5rem;
623
+ z-index: 5;
624
+ position: relative;
625
+ }
626
+
627
+ .timeline-list {
628
+ display: flex;
629
+ gap: 12px;
630
+ overflow-x: auto;
631
+ padding-bottom: 10px;
632
+ }
633
+
634
+ .timeline-card {
635
+ min-width: 140px;
636
+ background: rgba(0, 0, 0, 0.4);
637
+ border: 1px solid var(--border-color);
638
+ border-radius: 8px;
639
+ padding: 12px;
640
+ display: flex;
641
+ flex-direction: column;
642
+ align-items: center;
643
+ text-align: center;
644
+ transition: transform 0.2s, border-color 0.2s;
645
+ }
646
+
647
+ .timeline-card:hover {
648
+ transform: translateY(-4px);
649
+ border-color: var(--cyan);
650
+ }
651
+
652
+ .timeline-date {
653
+ font-family: var(--font-mono);
654
+ font-size: 0.7rem;
655
+ color: var(--text-muted);
656
+ margin-bottom: 6px;
657
+ }
658
+
659
+ .timeline-vol {
660
+ font-family: var(--font-display);
661
+ font-size: 1.1rem;
662
+ font-weight: bold;
663
+ color: #FFF;
664
+ margin-bottom: 6px;
665
+ }
666
+
667
+ .timeline-status {
668
+ font-size: 0.7rem;
669
+ padding: 2px 8px;
670
+ border-radius: 4px;
671
+ font-weight: bold;
672
+ }
673
+
674
+ .timeline-status.safe { background: rgba(57, 255, 20, 0.1); color: var(--green); }
675
+ .timeline-status.warning { background: rgba(255, 230, 0, 0.1); color: var(--yellow); }
676
+ .timeline-status.critical { background: rgba(255, 0, 85, 0.1); color: var(--red); }
677
+
678
+ .empty-timeline {
679
+ width: 100%;
680
+ text-align: center;
681
+ padding: 2rem;
682
+ color: var(--text-muted);
683
+ font-style: italic;
684
+ font-size: 0.9rem;
685
+ }
686
+
687
+ /* Hourly Breakdown Container */
688
+ .hourly-container {
689
+ margin-top: 1.5rem;
690
+ padding: 0 2.5rem;
691
+ z-index: 5;
692
+ position: relative;
693
+ margin-bottom: 2rem;
694
+ }
695
+
696
+ .hourly-grid {
697
+ display: grid;
698
+ grid-template-columns: repeat(24, 1fr);
699
+ gap: 4px;
700
+ background: rgba(0, 0, 0, 0.3);
701
+ padding: 10px;
702
+ border-radius: 8px;
703
+ overflow-x: auto;
704
+ }
705
+
706
+ .hourly-cell {
707
+ display: flex;
708
+ flex-direction: column;
709
+ align-items: center;
710
+ gap: 4px;
711
+ }
712
+
713
+ .cell-block {
714
+ width: 100%;
715
+ height: 40px;
716
+ border-radius: 4px;
717
+ transition: opacity 0.3s;
718
+ }
719
+
720
+ .cell-block.low { background-color: rgba(57, 255, 20, 0.35); border: 1px solid var(--green); }
721
+ .cell-block.medium { background-color: rgba(255, 230, 0, 0.35); border: 1px solid var(--yellow); }
722
+ .cell-block.high { background-color: rgba(255, 0, 85, 0.35); border: 1px solid var(--red); }
723
+
724
+ .cell-time {
725
+ font-family: var(--font-mono);
726
+ font-size: 8px;
727
+ color: var(--text-muted);
728
+ }
729
+
730
+ /* Footer */
731
+ footer {
732
+ width: 100%;
733
+ padding: 1.5rem;
734
+ text-align: center;
735
+ border-top: 1px solid rgba(255, 255, 255, 0.03);
736
+ color: var(--text-muted);
737
+ font-size: 0.75rem;
738
+ font-family: var(--font-mono);
739
+ margin-top: auto;
740
+ }
741
+
742
+ /* Animations */
743
+ @keyframes pulse-green {
744
+ 0% { box-shadow: 0 0 0 0 rgba(57, 255, 20, 0.4); }
745
+ 70% { box-shadow: 0 0 0 8px rgba(57, 255, 20, 0); }
746
+ 100% { box-shadow: 0 0 0 0 rgba(57, 255, 20, 0); }
747
+ }
748
+
749
+ @keyframes pulse-node {
750
+ 0% { transform: scale(0.9); opacity: 0.35; }
751
+ 70% { transform: scale(1.6); opacity: 0; }
752
+ 100% { transform: scale(0.9); opacity: 0; }
753
+ }
754
+
755
+ .card-meta {
756
+ font-family: var(--font-mono);
757
+ font-size: 0.65rem;
758
+ color: var(--text-muted);
759
+ margin-top: 6px;
760
+ display: block;
761
+ text-transform: uppercase;
762
+ }
763
+
764
+ .button-row {
765
+ display: grid;
766
+ grid-template-columns: 1fr 1fr;
767
+ gap: 10px;
768
+ }
769
+
770
+ .secondary-btn {
771
+ background: rgba(57, 255, 20, 0.04) !important;
772
+ border: 1px solid var(--green) !important;
773
+ color: var(--green) !important;
774
+ }
775
+
776
+ .secondary-btn:hover {
777
+ background: rgba(57, 255, 20, 0.12) !important;
778
+ box-shadow: 0 0 20px var(--green-glow) !important;
779
+ }
780
+
train.py CHANGED
@@ -3,119 +3,90 @@ import numpy as np
3
  from sklearn.ensemble import GradientBoostingRegressor
4
  from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
5
  import joblib
6
- import io
7
  import warnings
8
  warnings.filterwarnings('ignore')
9
 
10
- print("🚀 MEMULAI PROSES TRAINING AI LEVEL ADVANCED (ECO-TWIN PRO)...\n")
11
 
12
  # ==========================================
13
- # 1. DATA INGESTION & AUGMENTATION (2 TAHUN)
14
  # ==========================================
15
- print("📥 1. Menarik & Memproses Data Historis (2023 - 2024)...")
16
-
17
- # Baseline Sampah (Diambil dari SIPSN DKI 2025)
18
- base_sampah = 1050.50
19
- mrt_harian_avg = 85000
20
- hujan_mean = 10.5
21
-
22
- # Data Event
23
- data_event_csv = """Tanggal,Nama_Event,Ada_Event
24
- 2023-01-01,Tahun Baru 2023,1
25
- 2023-03-11,Konser BLACKPINK,1
26
- 2023-03-12,Konser BLACKPINK,1
27
- 2023-05-26,Java Jazz,1
28
- 2023-06-19,Timnas Argentina,1
29
- 2023-11-15,Coldplay,1
30
- 2023-12-31,Tahun Baru 2024,1
31
- 2024-01-01,Tahun Baru 2024,1
32
- 2024-03-02,Ed Sheeran,1
33
- 2024-05-24,Java Jazz 2024,1
34
- 2024-12-31,Malam Tahun Baru 2025,1"""
35
- df_event = pd.read_csv(io.StringIO(data_event_csv))
36
- df_event['Tanggal'] = pd.to_datetime(df_event['Tanggal'])
37
-
38
- # Bikin Master Kalender 2 Tahun (Lebih banyak data, AI makin pintar)
39
- df = pd.DataFrame({'Tanggal': pd.date_range(start="2023-01-01", end="2024-12-31")})
40
- df = pd.merge(df, df_event[['Tanggal', 'Ada_Event']], on='Tanggal', how='left').fillna({'Ada_Event': 0})
41
-
42
- # Simulasi Pola Realistis
43
- df['Penumpang_MRT'] = np.random.normal(loc=mrt_harian_avg, scale=mrt_harian_avg*0.15, size=len(df)).astype(int)
44
- df['Curah_Hujan_mm'] = np.random.exponential(scale=hujan_mean, size=len(df))
45
- df.loc[df['Curah_Hujan_mm'] < 2, 'Curah_Hujan_mm'] = 0
46
 
47
  # ==========================================
48
- # 2. ADVANCED FEATURE ENGINEERING (MIND-BLOWING)
49
  # ==========================================
50
- print("🧠 2. Melakukan Feature Engineering (Ekstraksi Pola Waktu)...")
51
-
52
- # Ekstraksi Siklus Waktu
53
- df['Hari_Dalam_Minggu'] = df['Tanggal'].dt.dayofweek # 0=Senin, 6=Minggu
54
- df['Bulan'] = df['Tanggal'].dt.month
55
- df['Is_Weekend'] = df['Hari_Dalam_Minggu'].apply(lambda x: 1 if x >= 5 else 0)
56
-
57
- # Lag Features (Mengingat masa lalu)
58
- # "Hujan kemarin bikin sampah hari ini lebih berat (menyerap air)"
59
- df['Hujan_Kemarin'] = df['Curah_Hujan_mm'].shift(1).fillna(0)
60
 
61
- # Target Variable Generation (Rumus Super Kompleks)
62
- df['Volume_Sampah_Ton'] = base_sampah + \
63
- (df['Ada_Event'] * base_sampah * np.random.uniform(0.15, 0.30, size=len(df))) + \
64
- (df['Is_Weekend'] * base_sampah * 0.08) + \
65
- (df['Curah_Hujan_mm'] / 50 * base_sampah * 0.03) + \
66
- (df['Hujan_Kemarin'] / 50 * base_sampah * 0.05) + \
67
- ((df['Penumpang_MRT'] - mrt_harian_avg) / mrt_harian_avg * base_sampah * 0.02)
68
 
69
- # Noise (Fluktuasi harian)
70
- df['Volume_Sampah_Ton'] += np.random.normal(0, base_sampah*0.02, size=len(df))
71
- df['Volume_Sampah_Ton'] = df['Volume_Sampah_Ton'].round(2)
 
 
 
72
 
73
- # Simpan dataset
74
- df.to_csv('dataset_advanced_eco_twin.csv', index=False)
75
 
76
  # ==========================================
77
  # 3. CHRONOLOGICAL SPLIT & TRAINING
78
  # ==========================================
79
- print("⚙️ 3. Melatih Model AI dengan Algoritma Gradient Boosting...")
80
 
81
- # Fitur yang dipakai AI buat mikir
82
- fitur = ['Penumpang_MRT', 'Ada_Event', 'Curah_Hujan_mm', 'Hujan_Kemarin', 'Hari_Dalam_Minggu', 'Bulan', 'Is_Weekend']
83
- X = df[fitur]
84
- y = df['Volume_Sampah_Ton']
 
85
 
86
- # Memisahkan masa lalu (2023) buat belajar, masa depan (2024) buat ujian
87
- train_size = int(len(df) * 0.75) # 75% data awal
88
  X_train, X_test = X.iloc[:train_size], X.iloc[train_size:]
89
  y_train, y_test = y.iloc[:train_size], y.iloc[train_size:]
90
 
91
- # Menggunakan Gradient Boosting (State-of-the-Art)
92
  model = GradientBoostingRegressor(
93
- n_estimators=200,
94
- learning_rate=0.1,
95
- max_depth=4,
96
  random_state=42
97
  )
98
  model.fit(X_train, y_train)
99
 
100
  # ==========================================
101
- # 4. EVALUASI AKURASI (BUAT DIPAMERIN KE JURI)
102
  # ==========================================
 
103
  prediksi = model.predict(X_test)
104
  rmse = mean_squared_error(y_test, prediksi) ** 0.5
105
  mae = mean_absolute_error(y_test, prediksi)
106
  r2 = r2_score(y_test, prediksi)
107
 
108
- print("\n📊 HASIL EVALUASI MODEL (METRICS):")
109
- print(f" ✅ Root Mean Squared Error (RMSE) : {rmse:.2f} Ton")
110
- print(f" ✅ Mean Absolute Error (MAE) : {mae:.2f} Ton")
111
- print(f" ✅ R-Squared (R2 Score) : {r2 * 100:.2f}% (Tingkat Kepercayaan AI)")
 
 
 
 
 
 
112
 
113
  # Cek Fitur Paling Berpengaruh
114
  importances = model.feature_importances_
115
- print("\n🌟 FITUR PALING BERPENGARUH PADA TIMBULAN SAMPAH:")
116
  for name, importance in zip(fitur, importances):
117
  print(f" - {name}: {importance*100:.1f}%")
118
 
119
  # Simpan Model
120
  joblib.dump(model, 'model_sampah_advanced.pkl')
121
- print("\n💾 SUCCESS! 'model_sampah_advanced.pkl' berhasil di-generate!")
 
3
  from sklearn.ensemble import GradientBoostingRegressor
4
  from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
5
  import joblib
 
6
  import warnings
7
  warnings.filterwarnings('ignore')
8
 
9
+ print("MEMULAI PROSES TRAINING AI LEVEL PRODUCTION (LOCALIZED, LAG WEATHER & HOLIDAYS)...\n")
10
 
11
  # ==========================================
12
+ # 1. LOAD LOCALIZED DATA
13
  # ==========================================
14
+ print("1. Menarik & Memproses Data Historis Lokal...")
15
+ df = pd.read_csv('dataset_local_2026.csv')
16
+ df['Tanggal'] = pd.to_datetime(df['Tanggal'])
17
+
18
+ # Sort chronologically to maintain time order
19
+ df = df.sort_values(['Tanggal', 'Location']).reset_index(drop=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  # ==========================================
22
+ # 2. FEATURE ENGINEERING (LOCAL BINDING)
23
  # ==========================================
24
+ print("2. Melakukan One-Hot Encoding Lokasi & Verifikasi Fitur...")
 
 
 
 
 
 
 
 
 
25
 
26
+ # Defensive manual one-hot encoding to guarantee column names and order
27
+ locations = ['JIS', 'GBK', 'Pasar Senen', 'Gang Sempit Tambora']
28
+ for loc in locations:
29
+ df[f'Loc_{loc}'] = (df['Location'] == loc).astype(int)
 
 
 
30
 
31
+ # Fitur yang dipakai AI buat berpikir
32
+ fitur = [
33
+ 'Loc_JIS', 'Loc_GBK', 'Loc_Pasar Senen', 'Loc_Gang Sempit Tambora',
34
+ 'RR', 'Rain_Lag_1', 'Rain_Lag_2', 'Is_Holiday', 'Ada_Event', 'Crowd_Scale',
35
+ 'Hari_Ke', 'Is_Weekend', 'Hari_Dalam_Minggu', 'Bulan'
36
+ ]
37
 
38
+ X = df[fitur]
39
+ y = df['Volume_Ton']
40
 
41
  # ==========================================
42
  # 3. CHRONOLOGICAL SPLIT & TRAINING
43
  # ==========================================
44
+ print("3. Membagi Data secara Kronologis (75/25) & Melatih Model...")
45
 
46
+ # 75% days for training, 25% for test.
47
+ # Since we have 4 locations per day, we split at index: (len(df) // 4 * 0.75) * 4
48
+ num_days = len(df) // 4
49
+ train_days = int(num_days * 0.75)
50
+ train_size = train_days * 4
51
 
 
 
52
  X_train, X_test = X.iloc[:train_size], X.iloc[train_size:]
53
  y_train, y_test = y.iloc[:train_size], y.iloc[train_size:]
54
 
55
+ # Menggunakan Gradient Boosting Regressor (Optimasi Parameter untuk Akurasi >90%)
56
  model = GradientBoostingRegressor(
57
+ n_estimators=300,
58
+ learning_rate=0.05,
59
+ max_depth=5,
60
  random_state=42
61
  )
62
  model.fit(X_train, y_train)
63
 
64
  # ==========================================
65
+ # 4. EVALUASI AKURASI
66
  # ==========================================
67
+ print("4. Mengevaluasi Performa Model pada Data Pengujian...")
68
  prediksi = model.predict(X_test)
69
  rmse = mean_squared_error(y_test, prediksi) ** 0.5
70
  mae = mean_absolute_error(y_test, prediksi)
71
  r2 = r2_score(y_test, prediksi)
72
 
73
+ # Hitung Mean Absolute Percentage Error (MAPE)
74
+ mape = np.mean(np.abs((y_test - prediksi) / y_test)) * 100
75
+ akurasi = 100 - mape
76
+
77
+ print("\nHASIL EVALUASI MODEL (METRICS):")
78
+ print(f" Root Mean Squared Error (RMSE) : {rmse:.2f} Ton")
79
+ print(f" Mean Absolute Error (MAE) : {mae:.2f} Ton")
80
+ print(f" R-Squared (R2 Score) : {r2 * 100:.2f}% (Tingkat Kepercayaan AI)")
81
+ print(f" Mean Absolute Percentage Error (MAPE) : {mape:.2f}%")
82
+ print(f" Akurasi Prediksi Sampah : {akurasi:.2f}%")
83
 
84
  # Cek Fitur Paling Berpengaruh
85
  importances = model.feature_importances_
86
+ print("\nFITUR PALING BERPENGARUH PADA TIMBULAN SAMPAH:")
87
  for name, importance in zip(fitur, importances):
88
  print(f" - {name}: {importance*100:.1f}%")
89
 
90
  # Simpan Model
91
  joblib.dump(model, 'model_sampah_advanced.pkl')
92
+ print("\nSUCCESS! 'model_sampah_advanced.pkl' berhasil di-generate!")