Spaces:
Sleeping
Sleeping
feat: upgrade crowd event modeling to BPS Jumlah Jiwa headcount across 44 Kecamatans
Browse files- app.py +81 -60
- event_jakarta_2026.txt +32 -32
- frontend/app.js +19 -1
- frontend/index.html +3 -3
app.py
CHANGED
|
@@ -65,60 +65,60 @@ app.mount("/static", StaticFiles(directory="frontend"), name="static")
|
|
| 65 |
# ==========================================
|
| 66 |
KECAMATAN_DATABASE = {
|
| 67 |
# 1. JAKARTA PUSAT (8 Kecamatan) - Total: 1150 Ton
|
| 68 |
-
"Menteng": {"latitude": -6.1950, "longitude": 106.8322, "normal_avg": 120.0, "warning_threshold": 160.0, "critical_threshold": 180.0, "city": "Jakarta Pusat"},
|
| 69 |
-
"Senen": {"latitude": -6.1822, "longitude": 106.8452, "normal_avg": 180.0, "warning_threshold": 220.0, "critical_threshold": 240.0, "city": "Jakarta Pusat"},
|
| 70 |
-
"Cempaka Putih": {"latitude": -6.1802, "longitude": 106.8686, "normal_avg": 90.0, "warning_threshold": 120.0, "critical_threshold": 140.0, "city": "Jakarta Pusat"},
|
| 71 |
-
"Johar Baru": {"latitude": -6.1866, "longitude": 106.8572, "normal_avg": 70.0, "warning_threshold": 95.0, "critical_threshold": 110.0, "city": "Jakarta Pusat"},
|
| 72 |
-
"Kemayoran": {"latitude": -6.1628, "longitude": 106.8438, "normal_avg": 180.0, "warning_threshold": 220.0, "critical_threshold": 240.0, "city": "Jakarta Pusat"},
|
| 73 |
-
"Sawah Besar": {"latitude": -6.1554, "longitude": 106.8322, "normal_avg": 110.0, "warning_threshold": 145.0, "critical_threshold": 165.0, "city": "Jakarta Pusat"},
|
| 74 |
-
"Tanah Abang": {"latitude": -6.2104, "longitude": 106.8122, "normal_avg": 250.0, "warning_threshold": 320.0, "critical_threshold": 350.0, "city": "Jakarta Pusat"},
|
| 75 |
-
"Gambir": {"latitude": -6.1764, "longitude": 106.8190, "normal_avg": 150.0, "warning_threshold": 195.0, "critical_threshold": 215.0, "city": "Jakarta Pusat"},
|
| 76 |
|
| 77 |
# 2. JAKARTA UTARA (6 Kecamatan) - Total: 1350 Ton
|
| 78 |
-
"Penjaringan": {"latitude": -6.1264, "longitude": 106.7822, "normal_avg": 280.0, "warning_threshold": 350.0, "critical_threshold": 380.0, "city": "Jakarta Utara"},
|
| 79 |
-
"Tanjung Priok": {"latitude": -6.1322, "longitude": 106.8722, "normal_avg": 260.0, "warning_threshold": 320.0, "critical_threshold": 350.0, "city": "Jakarta Utara"},
|
| 80 |
-
"Koja": {"latitude": -6.1214, "longitude": 106.9133, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Utara"},
|
| 81 |
-
"Cilincing": {"latitude": -6.1288, "longitude": 106.9452, "normal_avg": 290.0, "warning_threshold": 370.0, "critical_threshold": 400.0, "city": "Jakarta Utara"},
|
| 82 |
-
"Pademangan": {"latitude": -6.1328, "longitude": 106.8422, "normal_avg": 140.0, "warning_threshold": 180.0, "critical_threshold": 200.0, "city": "Jakarta Utara"},
|
| 83 |
-
"Kelapa Gading": {"latitude": -6.1552, "longitude": 106.9022, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Utara"},
|
| 84 |
|
| 85 |
# 3. JAKARTA BARAT (8 Kecamatan) - Total: 1550 Ton
|
| 86 |
-
"Cengkareng": {"latitude": -6.1528, "longitude": 106.7322, "normal_avg": 340.0, "warning_threshold": 420.0, "critical_threshold": 460.0, "city": "Jakarta Barat"},
|
| 87 |
-
"Grogol Petamburan": {"latitude": -6.1622, "longitude": 106.7882, "normal_avg": 220.0, "warning_threshold": 280.0, "critical_threshold": 310.0, "city": "Jakarta Barat"},
|
| 88 |
-
"Kalideres": {"latitude": -6.1428, "longitude": 106.7022, "normal_avg": 260.0, "warning_threshold": 330.0, "critical_threshold": 360.0, "city": "Jakarta Barat"},
|
| 89 |
-
"Kebon Jeruk": {"latitude": -6.1922, "longitude": 106.7722, "normal_avg": 210.0, "warning_threshold": 260.0, "critical_threshold": 290.0, "city": "Jakarta Barat"},
|
| 90 |
-
"Kembangan": {"latitude": -6.1828, "longitude": 106.7382, "normal_avg": 180.0, "warning_threshold": 230.0, "critical_threshold": 250.0, "city": "Jakarta Barat"},
|
| 91 |
-
"Palmerah": {"latitude": -6.2028, "longitude": 106.7882, "normal_avg": 160.0, "warning_threshold": 200.0, "critical_threshold": 220.0, "city": "Jakarta Barat"},
|
| 92 |
-
"Taman Sari": {"latitude": -6.1454, "longitude": 106.8182, "normal_avg": 100.0, "warning_threshold": 130.0, "critical_threshold": 150.0, "city": "Jakarta Barat"},
|
| 93 |
-
"Tambora": {"latitude": -6.1500, "longitude": 106.8000, "normal_avg": 80.0, "warning_threshold": 110.0, "critical_threshold": 125.0, "city": "Jakarta Barat"},
|
| 94 |
|
| 95 |
# 4. JAKARTA SELATAN (10 Kecamatan) - Total: 1850 Ton
|
| 96 |
-
"Cilandak": {"latitude": -6.2928, "longitude": 106.7922, "normal_avg": 180.0, "warning_threshold": 230.0, "critical_threshold": 250.0, "city": "Jakarta Selatan"},
|
| 97 |
-
"Jagakarsa": {"latitude": -6.3328, "longitude": 106.8222, "normal_avg": 220.0, "warning_threshold": 280.0, "critical_threshold": 310.0, "city": "Jakarta Selatan"},
|
| 98 |
-
"Kebayoran Baru": {"latitude": -6.2422, "longitude": 106.7982, "normal_avg": 210.0, "warning_threshold": 260.0, "critical_threshold": 290.0, "city": "Jakarta Selatan"},
|
| 99 |
-
"Kebayoran Lama": {"latitude": -6.2488, "longitude": 106.7722, "normal_avg": 230.0, "warning_threshold": 290.0, "critical_threshold": 320.0, "city": "Jakarta Selatan"},
|
| 100 |
-
"Mampang Prapatan": {"latitude": -6.2522, "longitude": 106.8182, "normal_avg": 120.0, "warning_threshold": 150.0, "critical_threshold": 170.0, "city": "Jakarta Selatan"},
|
| 101 |
-
"Pancoran": {"latitude": -6.2622, "longitude": 106.8382, "normal_avg": 130.0, "warning_threshold": 160.0, "critical_threshold": 180.0, "city": "Jakarta Selatan"},
|
| 102 |
-
"Pasar Minggu": {"latitude": -6.2828, "longitude": 106.8438, "normal_avg": 240.0, "warning_threshold": 300.0, "critical_threshold": 330.0, "city": "Jakarta Selatan"},
|
| 103 |
-
"Pesanggrahan": {"latitude": -6.2588, "longitude": 106.7588, "normal_avg": 160.0, "warning_threshold": 200.0, "critical_threshold": 220.0, "city": "Jakarta Selatan"},
|
| 104 |
-
"Setiabudi": {"latitude": -6.2228, "longitude": 106.8282, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Selatan"},
|
| 105 |
-
"Tebet": {"latitude": -6.2288, "longitude": 106.8482, "normal_avg": 170.0, "warning_threshold": 210.0, "critical_threshold": 230.0, "city": "Jakarta Selatan"},
|
| 106 |
|
| 107 |
# 5. JAKARTA TIMUR (10 Kecamatan) - Total: 2100 Ton
|
| 108 |
-
"Cakung": {"latitude": -6.1828, "longitude": 106.9482, "normal_avg": 350.0, "warning_threshold": 430.0, "critical_threshold": 470.0, "city": "Jakarta Timur"},
|
| 109 |
-
"Cipayung": {"latitude": -6.3128, "longitude": 106.9022, "normal_avg": 140.0, "warning_threshold": 180.0, "critical_threshold": 200.0, "city": "Jakarta Timur"},
|
| 110 |
-
"Ciracas": {"latitude": -6.3228, "longitude": 106.8782, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Timur"},
|
| 111 |
-
"Duren Sawit": {"latitude": -6.2228, "longitude": 106.9282, "normal_avg": 300.0, "warning_threshold": 370.0, "critical_threshold": 410.0, "city": "Jakarta Timur"},
|
| 112 |
-
"Jatinegara": {"latitude": -6.2222, "longitude": 106.8682, "normal_avg": 240.0, "warning_threshold": 300.0, "critical_threshold": 330.0, "city": "Jakarta Timur"},
|
| 113 |
-
"Kramat Jati": {"latitude": -6.2722, "longitude": 106.8682, "normal_avg": 220.0, "warning_threshold": 270.0, "critical_threshold": 300.0, "city": "Jakarta Timur"},
|
| 114 |
-
"Makasar": {"latitude": -6.2622, "longitude": 106.8782, "normal_avg": 160.0, "warning_threshold": 200.0, "critical_threshold": 220.0, "city": "Jakarta Timur"},
|
| 115 |
-
"Matraman": {"latitude": -6.2022, "longitude": 106.8582, "normal_avg": 130.0, "warning_threshold": 160.0, "critical_threshold": 180.0, "city": "Jakarta Timur"},
|
| 116 |
-
"Pasar Rebo": {"latitude": -6.3122, "longitude": 106.8522, "normal_avg": 150.0, "warning_threshold": 190.0, "critical_threshold": 210.0, "city": "Jakarta Timur"},
|
| 117 |
-
"Pulo Gadung": {"latitude": -6.1922, "longitude": 106.8922, "normal_avg": 220.0, "warning_threshold": 270.0, "critical_threshold": 300.0, "city": "Jakarta Timur"},
|
| 118 |
|
| 119 |
# 6. KEPULAUAN SERIBU (2 Kecamatan) - Total: 20 Ton
|
| 120 |
-
"Kepulauan Seribu Utara": {"latitude": -5.5722, "longitude": 106.5522, "normal_avg": 11.0, "warning_threshold": 15.0, "critical_threshold": 18.0, "city": "Kepulauan Seribu"},
|
| 121 |
-
"Kepulauan Seribu Selatan": {"latitude": -5.7722, "longitude": 106.6522, "normal_avg": 9.0, "warning_threshold": 12.0, "critical_threshold": 15.0, "city": "Kepulauan Seribu"}
|
| 122 |
}
|
| 123 |
|
| 124 |
ALLOWED_LOCATIONS = list(KECAMATAN_DATABASE.keys())
|
|
@@ -129,7 +129,8 @@ ALLOWED_LOCATIONS = list(KECAMATAN_DATABASE.keys())
|
|
| 129 |
class PredictionRequest(BaseModel):
|
| 130 |
forecast_days: int = Field(7, ge=1, le=30, description="Forecast horizon in days (1-30)")
|
| 131 |
rainfall_mm: float = Field(0.0, ge=0, description="Precipitation override. 0.0 means Auto (Open-Meteo)")
|
| 132 |
-
event_scale: int = Field(0, ge=0,
|
|
|
|
| 133 |
location: str = Field(..., description="Target sub-district (Kecamatan)")
|
| 134 |
start_date: Optional[str] = Field(None, description="Start date: YYYY-MM-DD")
|
| 135 |
granularity: str = Field("daily", pattern="^(daily|hourly)$", description="Granularity")
|
|
@@ -305,10 +306,13 @@ async def load_assets():
|
|
| 305 |
if str(r.get("ada_event", "1")) == "1":
|
| 306 |
dk = str(r.get("tanggal", "")).strip()
|
| 307 |
if dk:
|
|
|
|
|
|
|
| 308 |
events_data[dk] = {
|
| 309 |
"event_name": str(r.get("nama_event", "")),
|
| 310 |
"location": str(r.get("lokasi", "")),
|
| 311 |
-
"crowd_scale":
|
|
|
|
| 312 |
}
|
| 313 |
logger.info(f"β
Event calendar loaded: {len(events_data)} entries")
|
| 314 |
except Exception as e:
|
|
@@ -514,7 +518,17 @@ async def predict_waste_volume(req: PredictionRequest):
|
|
| 514 |
# Fetch live weather forecast from Open-Meteo API
|
| 515 |
weather_forecast = await fetch_rainfall_forecast(config["latitude"], config["longitude"], req.forecast_days)
|
| 516 |
|
| 517 |
-
# Calibrations Setup
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
dataset_mean = df_history["Volume_Total_Ton"].mean()
|
| 519 |
real_baseline = config["normal_avg"]
|
| 520 |
calibration_factor = real_baseline / dataset_mean
|
|
@@ -548,17 +562,18 @@ async def predict_waste_volume(req: PredictionRequest):
|
|
| 548 |
if rain_val > 5.0:
|
| 549 |
rain_m = 1.0 + min(rain_val * 0.002, 0.20)
|
| 550 |
|
| 551 |
-
# Events multiplier
|
| 552 |
evt = events_data.get(d_str)
|
| 553 |
-
|
| 554 |
info = None
|
| 555 |
-
if evt and
|
| 556 |
-
|
| 557 |
-
info = f"{evt['event_name']} @ {evt['location']}"
|
| 558 |
-
elif req.event_scale > 0:
|
| 559 |
-
evt_m = 1.0 + req.event_scale * 0.10
|
| 560 |
|
| 561 |
-
|
|
|
|
|
|
|
|
|
|
| 562 |
calibrated_volume = round(float(raw_prediction * calibration_factor), 2)
|
| 563 |
|
| 564 |
total_vol += calibrated_volume
|
|
@@ -591,15 +606,21 @@ async def predict_waste_volume(req: PredictionRequest):
|
|
| 591 |
rain_lag1 = req.rainfall_mm if (req.rainfall_mm > 0.0 and i == 1) else weather_forecast.get((curr_date - timedelta(days=1)).strftime("%Y-%m-%d"), 0.0)
|
| 592 |
|
| 593 |
evt = events_data.get(d_str)
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
|
| 598 |
# Fitur dataframe construction matching train.py
|
| 599 |
mrt_riders = 85000 if curr_date.weekday() < 5 else 45000
|
| 600 |
features = pd.DataFrame([{
|
| 601 |
'Penumpang_MRT': mrt_riders,
|
| 602 |
-
'Ada_Event': has_event
|
| 603 |
'Curah_Hujan_mm': rain_val,
|
| 604 |
'Hujan_Kemarin': rain_lag1,
|
| 605 |
'Hari_Dalam_Minggu': curr_date.weekday(),
|
|
@@ -608,7 +629,7 @@ async def predict_waste_volume(req: PredictionRequest):
|
|
| 608 |
}])
|
| 609 |
|
| 610 |
raw_pred = float(model_gbr.predict(features)[0])
|
| 611 |
-
calibrated_volume = round(float(raw_pred * calibration_factor), 2)
|
| 612 |
|
| 613 |
total_vol += calibrated_volume
|
| 614 |
risk = get_risk_status(calibrated_volume, req.location)
|
|
|
|
| 65 |
# ==========================================
|
| 66 |
KECAMATAN_DATABASE = {
|
| 67 |
# 1. JAKARTA PUSAT (8 Kecamatan) - Total: 1150 Ton
|
| 68 |
+
"Menteng": {"latitude": -6.1950, "longitude": 106.8322, "population_jiwa": 88000, "normal_avg": 120.0, "warning_threshold": 160.0, "critical_threshold": 180.0, "city": "Jakarta Pusat"},
|
| 69 |
+
"Senen": {"latitude": -6.1822, "longitude": 106.8452, "population_jiwa": 128000, "normal_avg": 180.0, "warning_threshold": 220.0, "critical_threshold": 240.0, "city": "Jakarta Pusat"},
|
| 70 |
+
"Cempaka Putih": {"latitude": -6.1802, "longitude": 106.8686, "population_jiwa": 96000, "normal_avg": 90.0, "warning_threshold": 120.0, "critical_threshold": 140.0, "city": "Jakarta Pusat"},
|
| 71 |
+
"Johar Baru": {"latitude": -6.1866, "longitude": 106.8572, "population_jiwa": 130000, "normal_avg": 70.0, "warning_threshold": 95.0, "critical_threshold": 110.0, "city": "Jakarta Pusat"},
|
| 72 |
+
"Kemayoran": {"latitude": -6.1628, "longitude": 106.8438, "population_jiwa": 255000, "normal_avg": 180.0, "warning_threshold": 220.0, "critical_threshold": 240.0, "city": "Jakarta Pusat"},
|
| 73 |
+
"Sawah Besar": {"latitude": -6.1554, "longitude": 106.8322, "population_jiwa": 126000, "normal_avg": 110.0, "warning_threshold": 145.0, "critical_threshold": 165.0, "city": "Jakarta Pusat"},
|
| 74 |
+
"Tanah Abang": {"latitude": -6.2104, "longitude": 106.8122, "population_jiwa": 175000, "normal_avg": 250.0, "warning_threshold": 320.0, "critical_threshold": 350.0, "city": "Jakarta Pusat"},
|
| 75 |
+
"Gambir": {"latitude": -6.1764, "longitude": 106.8190, "population_jiwa": 97000, "normal_avg": 150.0, "warning_threshold": 195.0, "critical_threshold": 215.0, "city": "Jakarta Pusat"},
|
| 76 |
|
| 77 |
# 2. JAKARTA UTARA (6 Kecamatan) - Total: 1350 Ton
|
| 78 |
+
"Penjaringan": {"latitude": -6.1264, "longitude": 106.7822, "population_jiwa": 312000, "normal_avg": 280.0, "warning_threshold": 350.0, "critical_threshold": 380.0, "city": "Jakarta Utara"},
|
| 79 |
+
"Tanjung Priok": {"latitude": -6.1322, "longitude": 106.8722, "population_jiwa": 415000, "normal_avg": 260.0, "warning_threshold": 320.0, "critical_threshold": 350.0, "city": "Jakarta Utara"},
|
| 80 |
+
"Koja": {"latitude": -6.1214, "longitude": 106.9133, "population_jiwa": 330000, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Utara"},
|
| 81 |
+
"Cilincing": {"latitude": -6.1288, "longitude": 106.9452, "population_jiwa": 430000, "normal_avg": 290.0, "warning_threshold": 370.0, "critical_threshold": 400.0, "city": "Jakarta Utara"},
|
| 82 |
+
"Pademangan": {"latitude": -6.1328, "longitude": 106.8422, "population_jiwa": 168000, "normal_avg": 140.0, "warning_threshold": 180.0, "critical_threshold": 200.0, "city": "Jakarta Utara"},
|
| 83 |
+
"Kelapa Gading": {"latitude": -6.1552, "longitude": 106.9022, "population_jiwa": 143000, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Utara"},
|
| 84 |
|
| 85 |
# 3. JAKARTA BARAT (8 Kecamatan) - Total: 1550 Ton
|
| 86 |
+
"Cengkareng": {"latitude": -6.1528, "longitude": 106.7322, "population_jiwa": 592000, "normal_avg": 340.0, "warning_threshold": 420.0, "critical_threshold": 460.0, "city": "Jakarta Barat"},
|
| 87 |
+
"Grogol Petamburan": {"latitude": -6.1622, "longitude": 106.7882, "population_jiwa": 240000, "normal_avg": 220.0, "warning_threshold": 280.0, "critical_threshold": 310.0, "city": "Jakarta Barat"},
|
| 88 |
+
"Kalideres": {"latitude": -6.1428, "longitude": 106.7022, "population_jiwa": 460000, "normal_avg": 260.0, "warning_threshold": 330.0, "critical_threshold": 360.0, "city": "Jakarta Barat"},
|
| 89 |
+
"Kebon Jeruk": {"latitude": -6.1922, "longitude": 106.7722, "population_jiwa": 380000, "normal_avg": 210.0, "warning_threshold": 260.0, "critical_threshold": 290.0, "city": "Jakarta Barat"},
|
| 90 |
+
"Kembangan": {"latitude": -6.1828, "longitude": 106.7382, "population_jiwa": 310000, "normal_avg": 180.0, "warning_threshold": 230.0, "critical_threshold": 250.0, "city": "Jakarta Barat"},
|
| 91 |
+
"Palmerah": {"latitude": -6.2028, "longitude": 106.7882, "population_jiwa": 205000, "normal_avg": 160.0, "warning_threshold": 200.0, "critical_threshold": 220.0, "city": "Jakarta Barat"},
|
| 92 |
+
"Taman Sari": {"latitude": -6.1454, "longitude": 106.8182, "population_jiwa": 125000, "normal_avg": 100.0, "warning_threshold": 130.0, "critical_threshold": 150.0, "city": "Jakarta Barat"},
|
| 93 |
+
"Tambora": {"latitude": -6.1500, "longitude": 106.8000, "population_jiwa": 270000, "normal_avg": 80.0, "warning_threshold": 110.0, "critical_threshold": 125.0, "city": "Jakarta Barat"},
|
| 94 |
|
| 95 |
# 4. JAKARTA SELATAN (10 Kecamatan) - Total: 1850 Ton
|
| 96 |
+
"Cilandak": {"latitude": -6.2928, "longitude": 106.7922, "population_jiwa": 215000, "normal_avg": 180.0, "warning_threshold": 230.0, "critical_threshold": 250.0, "city": "Jakarta Selatan"},
|
| 97 |
+
"Jagakarsa": {"latitude": -6.3328, "longitude": 106.8222, "population_jiwa": 390000, "normal_avg": 220.0, "warning_threshold": 280.0, "critical_threshold": 310.0, "city": "Jakarta Selatan"},
|
| 98 |
+
"Kebayoran Baru": {"latitude": -6.2422, "longitude": 106.7982, "population_jiwa": 145000, "normal_avg": 210.0, "warning_threshold": 260.0, "critical_threshold": 290.0, "city": "Jakarta Selatan"},
|
| 99 |
+
"Kebayoran Lama": {"latitude": -6.2488, "longitude": 106.7722, "population_jiwa": 310000, "normal_avg": 230.0, "warning_threshold": 290.0, "critical_threshold": 320.0, "city": "Jakarta Selatan"},
|
| 100 |
+
"Mampang Prapatan": {"latitude": -6.2522, "longitude": 106.8182, "population_jiwa": 150000, "normal_avg": 120.0, "warning_threshold": 150.0, "critical_threshold": 170.0, "city": "Jakarta Selatan"},
|
| 101 |
+
"Pancoran": {"latitude": -6.2622, "longitude": 106.8382, "population_jiwa": 170000, "normal_avg": 130.0, "warning_threshold": 160.0, "critical_threshold": 180.0, "city": "Jakarta Selatan"},
|
| 102 |
+
"Pasar Minggu": {"latitude": -6.2828, "longitude": 106.8438, "population_jiwa": 315000, "normal_avg": 240.0, "warning_threshold": 300.0, "critical_threshold": 330.0, "city": "Jakarta Selatan"},
|
| 103 |
+
"Pesanggrahan": {"latitude": -6.2588, "longitude": 106.7588, "population_jiwa": 250000, "normal_avg": 160.0, "warning_threshold": 200.0, "critical_threshold": 220.0, "city": "Jakarta Selatan"},
|
| 104 |
+
"Setiabudi": {"latitude": -6.2228, "longitude": 106.8282, "population_jiwa": 110000, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Selatan"},
|
| 105 |
+
"Tebet": {"latitude": -6.2288, "longitude": 106.8482, "population_jiwa": 220000, "normal_avg": 170.0, "warning_threshold": 210.0, "critical_threshold": 230.0, "city": "Jakarta Selatan"},
|
| 106 |
|
| 107 |
# 5. JAKARTA TIMUR (10 Kecamatan) - Total: 2100 Ton
|
| 108 |
+
"Cakung": {"latitude": -6.1828, "longitude": 106.9482, "population_jiwa": 559000, "normal_avg": 350.0, "warning_threshold": 430.0, "critical_threshold": 470.0, "city": "Jakarta Timur"},
|
| 109 |
+
"Cipayung": {"latitude": -6.3128, "longitude": 106.9022, "population_jiwa": 290000, "normal_avg": 140.0, "warning_threshold": 180.0, "critical_threshold": 200.0, "city": "Jakarta Timur"},
|
| 110 |
+
"Ciracas": {"latitude": -6.3228, "longitude": 106.8782, "population_jiwa": 310000, "normal_avg": 190.0, "warning_threshold": 240.0, "critical_threshold": 270.0, "city": "Jakarta Timur"},
|
| 111 |
+
"Duren Sawit": {"latitude": -6.2228, "longitude": 106.9282, "population_jiwa": 420000, "normal_avg": 300.0, "warning_threshold": 370.0, "critical_threshold": 410.0, "city": "Jakarta Timur"},
|
| 112 |
+
"Jatinegara": {"latitude": -6.2222, "longitude": 106.8682, "population_jiwa": 315000, "normal_avg": 240.0, "warning_threshold": 300.0, "critical_threshold": 330.0, "city": "Jakarta Timur"},
|
| 113 |
+
"Kramat Jati": {"latitude": -6.2722, "longitude": 106.8682, "population_jiwa": 300000, "normal_avg": 220.0, "warning_threshold": 270.0, "critical_threshold": 300.0, "city": "Jakarta Timur"},
|
| 114 |
+
"Makasar": {"latitude": -6.2622, "longitude": 106.8782, "population_jiwa": 210000, "normal_avg": 160.0, "warning_threshold": 200.0, "critical_threshold": 220.0, "city": "Jakarta Timur"},
|
| 115 |
+
"Matraman": {"latitude": -6.2022, "longitude": 106.8582, "population_jiwa": 175000, "normal_avg": 130.0, "warning_threshold": 160.0, "critical_threshold": 180.0, "city": "Jakarta Timur"},
|
| 116 |
+
"Pasar Rebo": {"latitude": -6.3122, "longitude": 106.8522, "population_jiwa": 220000, "normal_avg": 150.0, "warning_threshold": 190.0, "critical_threshold": 210.0, "city": "Jakarta Timur"},
|
| 117 |
+
"Pulo Gadung": {"latitude": -6.1922, "longitude": 106.8922, "population_jiwa": 300000, "normal_avg": 220.0, "warning_threshold": 270.0, "critical_threshold": 300.0, "city": "Jakarta Timur"},
|
| 118 |
|
| 119 |
# 6. KEPULAUAN SERIBU (2 Kecamatan) - Total: 20 Ton
|
| 120 |
+
"Kepulauan Seribu Utara": {"latitude": -5.5722, "longitude": 106.5522, "population_jiwa": 16000, "normal_avg": 11.0, "warning_threshold": 15.0, "critical_threshold": 18.0, "city": "Kepulauan Seribu"},
|
| 121 |
+
"Kepulauan Seribu Selatan": {"latitude": -5.7722, "longitude": 106.6522, "population_jiwa": 13000, "normal_avg": 9.0, "warning_threshold": 12.0, "critical_threshold": 15.0, "city": "Kepulauan Seribu"}
|
| 122 |
}
|
| 123 |
|
| 124 |
ALLOWED_LOCATIONS = list(KECAMATAN_DATABASE.keys())
|
|
|
|
| 129 |
class PredictionRequest(BaseModel):
|
| 130 |
forecast_days: int = Field(7, ge=1, le=30, description="Forecast horizon in days (1-30)")
|
| 131 |
rainfall_mm: float = Field(0.0, ge=0, description="Precipitation override. 0.0 means Auto (Open-Meteo)")
|
| 132 |
+
event_scale: Optional[int] = Field(0, ge=0, description="Legacy crowd scale (optional fallback)")
|
| 133 |
+
jumlah_jiwa: Optional[int] = Field(None, ge=0, description="Target headcount / population override (Jumlah Jiwa)")
|
| 134 |
location: str = Field(..., description="Target sub-district (Kecamatan)")
|
| 135 |
start_date: Optional[str] = Field(None, description="Start date: YYYY-MM-DD")
|
| 136 |
granularity: str = Field("daily", pattern="^(daily|hourly)$", description="Granularity")
|
|
|
|
| 306 |
if str(r.get("ada_event", "1")) == "1":
|
| 307 |
dk = str(r.get("tanggal", "")).strip()
|
| 308 |
if dk:
|
| 309 |
+
raw_jiwa = float(r.get("jumlah_jiwa", r.get("skala_keramaian", 0)))
|
| 310 |
+
crowd_jiwa = raw_jiwa * 20000.0 if (0 < raw_jiwa <= 5) else raw_jiwa
|
| 311 |
events_data[dk] = {
|
| 312 |
"event_name": str(r.get("nama_event", "")),
|
| 313 |
"location": str(r.get("lokasi", "")),
|
| 314 |
+
"crowd_scale": crowd_jiwa,
|
| 315 |
+
"jumlah_jiwa": crowd_jiwa
|
| 316 |
}
|
| 317 |
logger.info(f"β
Event calendar loaded: {len(events_data)} entries")
|
| 318 |
except Exception as e:
|
|
|
|
| 518 |
# Fetch live weather forecast from Open-Meteo API
|
| 519 |
weather_forecast = await fetch_rainfall_forecast(config["latitude"], config["longitude"], req.forecast_days)
|
| 520 |
|
| 521 |
+
# Calibrations & Headcount Setup
|
| 522 |
+
baseline_pop = float(config.get("population_jiwa", 100000))
|
| 523 |
+
|
| 524 |
+
# User input target headcount / population override (Jumlah Jiwa)
|
| 525 |
+
if req.jumlah_jiwa is not None and req.jumlah_jiwa > 0:
|
| 526 |
+
target_pop = float(req.jumlah_jiwa)
|
| 527 |
+
elif req.event_scale and req.event_scale > 0:
|
| 528 |
+
target_pop = baseline_pop + (req.event_scale * 20000.0 if req.event_scale <= 5 else float(req.event_scale))
|
| 529 |
+
else:
|
| 530 |
+
target_pop = float(baseline_pop)
|
| 531 |
+
|
| 532 |
dataset_mean = df_history["Volume_Total_Ton"].mean()
|
| 533 |
real_baseline = config["normal_avg"]
|
| 534 |
calibration_factor = real_baseline / dataset_mean
|
|
|
|
| 562 |
if rain_val > 5.0:
|
| 563 |
rain_m = 1.0 + min(rain_val * 0.002, 0.20)
|
| 564 |
|
| 565 |
+
# Events multiplier from headcount (Jumlah Jiwa)
|
| 566 |
evt = events_data.get(d_str)
|
| 567 |
+
event_pop = 0.0
|
| 568 |
info = None
|
| 569 |
+
if evt and (req.location.lower() in evt["location"].lower() or evt["location"].lower() == "jakarta"):
|
| 570 |
+
event_pop = float(evt.get("jumlah_jiwa", evt.get("crowd_scale", 0.0)))
|
| 571 |
+
info = f"{evt['event_name']} ({int(event_pop):,} Jiwa) @ {evt['location']}"
|
|
|
|
|
|
|
| 572 |
|
| 573 |
+
total_day_jiwa = target_pop + event_pop
|
| 574 |
+
pop_scaling_factor = total_day_jiwa / baseline_pop
|
| 575 |
+
|
| 576 |
+
raw_prediction = base * rain_m * pop_scaling_factor
|
| 577 |
calibrated_volume = round(float(raw_prediction * calibration_factor), 2)
|
| 578 |
|
| 579 |
total_vol += calibrated_volume
|
|
|
|
| 606 |
rain_lag1 = req.rainfall_mm if (req.rainfall_mm > 0.0 and i == 1) else weather_forecast.get((curr_date - timedelta(days=1)).strftime("%Y-%m-%d"), 0.0)
|
| 607 |
|
| 608 |
evt = events_data.get(d_str)
|
| 609 |
+
event_pop = 0.0
|
| 610 |
+
info = None
|
| 611 |
+
if evt and (req.location.lower() in evt["location"].lower() or evt["location"].lower() == "jakarta"):
|
| 612 |
+
event_pop = float(evt.get("jumlah_jiwa", evt.get("crowd_scale", 0.0)))
|
| 613 |
+
info = f"{evt['event_name']} ({int(event_pop):,} Jiwa) @ {evt['location']}"
|
| 614 |
+
|
| 615 |
+
total_day_jiwa = target_pop + event_pop
|
| 616 |
+
pop_scaling_factor = total_day_jiwa / baseline_pop
|
| 617 |
+
has_event = 1 if (event_pop > 0 or target_pop > baseline_pop) else 0
|
| 618 |
|
| 619 |
# Fitur dataframe construction matching train.py
|
| 620 |
mrt_riders = 85000 if curr_date.weekday() < 5 else 45000
|
| 621 |
features = pd.DataFrame([{
|
| 622 |
'Penumpang_MRT': mrt_riders,
|
| 623 |
+
'Ada_Event': has_event,
|
| 624 |
'Curah_Hujan_mm': rain_val,
|
| 625 |
'Hujan_Kemarin': rain_lag1,
|
| 626 |
'Hari_Dalam_Minggu': curr_date.weekday(),
|
|
|
|
| 629 |
}])
|
| 630 |
|
| 631 |
raw_pred = float(model_gbr.predict(features)[0])
|
| 632 |
+
calibrated_volume = round(float(raw_pred * calibration_factor * pop_scaling_factor), 2)
|
| 633 |
|
| 634 |
total_vol += calibrated_volume
|
| 635 |
risk = get_risk_status(calibrated_volume, req.location)
|
event_jakarta_2026.txt
CHANGED
|
@@ -1,32 +1,32 @@
|
|
| 1 |
-
tanggal,nama_event,lokasi,
|
| 2 |
-
2026-01-01,Perayaan Tahun Baru,Monas,
|
| 3 |
-
2026-02-17,Imlek Festival,Glodok,
|
| 4 |
-
2026-03-18,H-3 Lebaran,Jakarta,
|
| 5 |
-
2026-03-22,Idul Fitri,Jakarta,
|
| 6 |
-
2026-04-10,Jakarta Art Festival,JIExpo,
|
| 7 |
-
2026-05-01,May Day Rally,Monas,
|
| 8 |
-
2026-06-11,PRJ Opening,JIExpo,
|
| 9 |
-
2026-06-13,BTN Marathon 2026,Jalan Protokol,
|
| 10 |
-
2026-06-14,PRJ Weekend,JIExpo,
|
| 11 |
-
2026-06-21,PRJ Peak Weekend,JIExpo,
|
| 12 |
-
2026-06-28,PRJ Mid-Event Weekend,JIExpo,
|
| 13 |
-
2026-07-01,DWP 2026 Jakarta,Kemayoran,
|
| 14 |
-
2026-07-02,INAGRITECH 2026,Kemayoran,
|
| 15 |
-
2026-07-02,"INAMARINE, INAWELDING & RAILWAYTECH INDONESIA 2026",Kemayoran,
|
| 16 |
-
2026-07-08,BritCham Indonesia's Golf Tournament,Kebayoran Lama,
|
| 17 |
-
2026-07-08,Canada Cup 2026,Kebayoran Lama,
|
| 18 |
-
2026-07-11,Uji Coba Sistem AI (Demo Event),Gambir,
|
| 19 |
-
2026-07-12,Konser Musik Spektakuler,Menteng,
|
| 20 |
-
2026-07-13,Japan Edu Expo 2026,Setiabudi,
|
| 21 |
-
2026-07-13,Anime Festival Asia Indonesia 2026,Tanah Abang,
|
| 22 |
-
2026-07-13,πππππππππ ππππ ππππ ππππ,Tanah Abang,
|
| 23 |
-
2026-07-14,Bangor Run Jakarta 2026,Tanah Abang,
|
| 24 |
-
2026-07-14,Run For Animals 2026,Cipayung,
|
| 25 |
-
2026-07-14,Solar Run 2026,Gambir,
|
| 26 |
-
2026-07-14,Tangyβs Story Adventures | WCIJ Trial Class Experience,Kebayoran Lama,
|
| 27 |
-
2026-07-20,PRJ Final Weekend,JIExpo,
|
| 28 |
-
2026-08-17,HUT RI ke-81,Monas,
|
| 29 |
-
2026-09-15,Food & Culture Expo,Ancol,
|
| 30 |
-
2026-11-25,Ancol Music Fest,Ancol,
|
| 31 |
-
2026-12-20,Christmas Market,Bundaran HI,
|
| 32 |
-
2026-12-31,Countdown Jakarta 2027,Monas,
|
|
|
|
| 1 |
+
tanggal,nama_event,lokasi,jumlah_jiwa
|
| 2 |
+
2026-01-01,Perayaan Tahun Baru,Monas,100000
|
| 3 |
+
2026-02-17,Imlek Festival,Glodok,25000
|
| 4 |
+
2026-03-18,H-3 Lebaran,Jakarta,80000
|
| 5 |
+
2026-03-22,Idul Fitri,Jakarta,150000
|
| 6 |
+
2026-04-10,Jakarta Art Festival,JIExpo,20000
|
| 7 |
+
2026-05-01,May Day Rally,Monas,30000
|
| 8 |
+
2026-06-11,PRJ Opening,JIExpo,120000
|
| 9 |
+
2026-06-13,BTN Marathon 2026,Jalan Protokol,40000
|
| 10 |
+
2026-06-14,PRJ Weekend,JIExpo,80000
|
| 11 |
+
2026-06-21,PRJ Peak Weekend,JIExpo,150000
|
| 12 |
+
2026-06-28,PRJ Mid-Event Weekend,JIExpo,75000
|
| 13 |
+
2026-07-01,DWP 2026 Jakarta,Kemayoran,100000
|
| 14 |
+
2026-07-02,INAGRITECH 2026,Kemayoran,50000
|
| 15 |
+
2026-07-02,"INAMARINE, INAWELDING & RAILWAYTECH INDONESIA 2026",Kemayoran,45000
|
| 16 |
+
2026-07-08,BritCham Indonesia's Golf Tournament,Kebayoran Lama,15000
|
| 17 |
+
2026-07-08,Canada Cup 2026,Kebayoran Lama,15000
|
| 18 |
+
2026-07-11,Uji Coba Sistem AI (Demo Event),Gambir,60000
|
| 19 |
+
2026-07-12,Konser Musik Spektakuler,Menteng,35000
|
| 20 |
+
2026-07-13,Japan Edu Expo 2026,Setiabudi,50000
|
| 21 |
+
2026-07-13,Anime Festival Asia Indonesia 2026,Tanah Abang,120000
|
| 22 |
+
2026-07-13,πππππππππ ππππ ππππ ππππ,Tanah Abang,110000
|
| 23 |
+
2026-07-14,Bangor Run Jakarta 2026,Tanah Abang,30000
|
| 24 |
+
2026-07-14,Run For Animals 2026,Cipayung,25000
|
| 25 |
+
2026-07-14,Solar Run 2026,Gambir,30000
|
| 26 |
+
2026-07-14,Tangyβs Story Adventures | WCIJ Trial Class Experience,Kebayoran Lama,10000
|
| 27 |
+
2026-07-20,PRJ Final Weekend,JIExpo,140000
|
| 28 |
+
2026-08-17,HUT RI ke-81,Monas,90000
|
| 29 |
+
2026-09-15,Food & Culture Expo,Ancol,25000
|
| 30 |
+
2026-11-25,Ancol Music Fest,Ancol,40000
|
| 31 |
+
2026-12-20,Christmas Market,Bundaran HI,35000
|
| 32 |
+
2026-12-31,Countdown Jakarta 2027,Monas,110000
|
frontend/app.js
CHANGED
|
@@ -72,6 +72,7 @@ const forecastVal = document.getElementById("forecast-val");
|
|
| 72 |
const rainOverride = document.getElementById("rain-override");
|
| 73 |
const rainOverrideVal = document.getElementById("rain-override-val");
|
| 74 |
const eventOverride = document.getElementById("event-override");
|
|
|
|
| 75 |
const predictBtn = document.getElementById("predict-btn");
|
| 76 |
const exportBtn = document.getElementById("export-btn");
|
| 77 |
|
|
@@ -202,9 +203,25 @@ if (rainOverride) {
|
|
| 202 |
});
|
| 203 |
}
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
if (locationSelect) {
|
| 206 |
locationSelect.addEventListener("change", (e) => {
|
| 207 |
selectedLocation = e.target.value;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
updateActiveMapMarker(selectedLocation);
|
| 209 |
panToLocation(selectedLocation);
|
| 210 |
fetchLiveWeather(selectedLocation);
|
|
@@ -418,7 +435,8 @@ async function runPrediction() {
|
|
| 418 |
const payload = {
|
| 419 |
forecast_days: parseInt(forecastSlider.value),
|
| 420 |
rainfall_mm: parseFloat(rainValue),
|
| 421 |
-
|
|
|
|
| 422 |
location: selectedLocation,
|
| 423 |
model_type: modelSelect.value,
|
| 424 |
granularity: forecastSlider.value <= 7 ? "hourly" : "daily"
|
|
|
|
| 72 |
const rainOverride = document.getElementById("rain-override");
|
| 73 |
const rainOverrideVal = document.getElementById("rain-override-val");
|
| 74 |
const eventOverride = document.getElementById("event-override");
|
| 75 |
+
const eventOverrideVal = document.getElementById("event-override-val");
|
| 76 |
const predictBtn = document.getElementById("predict-btn");
|
| 77 |
const exportBtn = document.getElementById("export-btn");
|
| 78 |
|
|
|
|
| 203 |
});
|
| 204 |
}
|
| 205 |
|
| 206 |
+
if (eventOverride) {
|
| 207 |
+
eventOverride.addEventListener("input", (e) => {
|
| 208 |
+
const val = parseInt(e.target.value);
|
| 209 |
+
if (eventOverrideVal) {
|
| 210 |
+
eventOverrideVal.textContent = `${val.toLocaleString()} Jiwa`;
|
| 211 |
+
}
|
| 212 |
+
});
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
if (locationSelect) {
|
| 216 |
locationSelect.addEventListener("change", (e) => {
|
| 217 |
selectedLocation = e.target.value;
|
| 218 |
+
const pop = KECAMATAN_DATABASE[selectedLocation]?.population_jiwa || 100000;
|
| 219 |
+
if (eventOverride) {
|
| 220 |
+
eventOverride.value = pop;
|
| 221 |
+
}
|
| 222 |
+
if (eventOverrideVal) {
|
| 223 |
+
eventOverrideVal.textContent = `${pop.toLocaleString()} Jiwa (BPS)`;
|
| 224 |
+
}
|
| 225 |
updateActiveMapMarker(selectedLocation);
|
| 226 |
panToLocation(selectedLocation);
|
| 227 |
fetchLiveWeather(selectedLocation);
|
|
|
|
| 435 |
const payload = {
|
| 436 |
forecast_days: parseInt(forecastSlider.value),
|
| 437 |
rainfall_mm: parseFloat(rainValue),
|
| 438 |
+
jumlah_jiwa: parseInt(eventOverride.value),
|
| 439 |
+
event_scale: 0,
|
| 440 |
location: selectedLocation,
|
| 441 |
model_type: modelSelect.value,
|
| 442 |
granularity: forecastSlider.value <= 7 ? "hourly" : "daily"
|
frontend/index.html
CHANGED
|
@@ -214,8 +214,8 @@
|
|
| 214 |
</div>
|
| 215 |
|
| 216 |
<div class="control-group">
|
| 217 |
-
<label for="event-override">
|
| 218 |
-
<input type="range" id="event-override" min="
|
| 219 |
</div>
|
| 220 |
|
| 221 |
<div class="button-row">
|
|
@@ -423,7 +423,7 @@
|
|
| 423 |
|
| 424 |
<footer>
|
| 425 |
<p>© 2026 Aeterna AI - DKI Jakarta Waste Management Intelligence. Calibrated with DLH & Open-Meteo.</p>
|
| 426 |
-
<p style="margin-top: 0.6rem; font-size: 0.72rem; opacity: 0.75; letter-spacing: 0.5px; font-family: var(--font-mono); line-height: 1.5;">Developed by: <span style="color:var(--cyan); font-weight:bold;">Faril Putra Pratama</span> (
|
| 427 |
</footer>
|
| 428 |
|
| 429 |
<!-- Custom Cyber HUD Cursor -->
|
|
|
|
| 214 |
</div>
|
| 215 |
|
| 216 |
<div class="control-group">
|
| 217 |
+
<label for="event-override">Simulasi Populasi (<span id="event-override-val" class="override-display">Auto (BPS Baseline)</span>)</label>
|
| 218 |
+
<input type="range" id="event-override" min="10000" max="700000" step="5000" value="88000" class="slider">
|
| 219 |
</div>
|
| 220 |
|
| 221 |
<div class="button-row">
|
|
|
|
| 423 |
|
| 424 |
<footer>
|
| 425 |
<p>© 2026 Aeterna AI - DKI Jakarta Waste Management Intelligence. Calibrated with DLH & Open-Meteo.</p>
|
| 426 |
+
<p style="margin-top: 0.6rem; font-size: 0.72rem; opacity: 0.75; letter-spacing: 0.5px; font-family: var(--font-mono); line-height: 1.5;">Developed with β‘ by: <span style="color:var(--cyan); font-weight:bold;">Faril Putra Pratama</span> (Lead Full-Stack AI Engineer)</p>
|
| 427 |
</footer>
|
| 428 |
|
| 429 |
<!-- Custom Cyber HUD Cursor -->
|