ALAMDIENG commited on
Commit
5b4e89f
·
1 Parent(s): a6d0ae7

fix: implement verified AI/ML audit fixes (15T truck divisor, Other Waste CSV column, daily variance seed, dynamic risk status, fallback event descriptions)

Browse files
Files changed (3) hide show
  1. app.py +58 -9
  2. frontend/app.js +1 -1
  3. frontend/index.html +1 -1
app.py CHANGED
@@ -224,9 +224,10 @@ def parse_flexible_date(date_input: str, default_year: int = 2026) -> pd.Timesta
224
 
225
  def get_risk_status(volume: float, location: str) -> str:
226
  config = KECAMATAN_DATABASE.get(location, KECAMATAN_DATABASE["Menteng"])
227
- if volume > config["critical_threshold"]:
 
228
  return "CRITICAL"
229
- elif volume > config["warning_threshold"]:
230
  return "WARNING"
231
  return "SAFE"
232
 
@@ -742,10 +743,25 @@ async def predict_waste_volume(req: PredictionRequest):
742
  event_pop = float(evt.get("jumlah_jiwa", evt.get("crowd_scale", 0.0)))
743
  info = f"{evt['event_name']} ({int(event_pop):,} Jiwa) @ {evt['location']}"
744
 
 
 
 
 
 
 
 
 
745
  total_day_jiwa = target_pop + event_pop
746
  pop_scaling_factor = total_day_jiwa / baseline_pop
747
 
748
  raw_prediction = base * rain_m * pop_scaling_factor
 
 
 
 
 
 
 
749
  calibrated_volume = round(float(raw_prediction * calibration_factor), 2)
750
 
751
  total_vol += calibrated_volume
@@ -761,7 +777,7 @@ async def predict_waste_volume(req: PredictionRequest):
761
  paper_waste_ton=round(calibrated_volume*paper_r, 2), metal_waste_ton=round(calibrated_volume*metal_r, 2),
762
  glass_waste_ton=round(calibrated_volume*glass_r, 2), textile_waste_ton=round(calibrated_volume*textile_r, 2),
763
  other_waste_ton=round(calibrated_volume*other_r, 2),
764
- recommended_trucks=max(1, int(np.ceil(calibrated_volume/8))),
765
  risk_status=risk, event_info=info, hourly_breakdown=hourly
766
  ))
767
 
@@ -790,6 +806,14 @@ async def predict_waste_volume(req: PredictionRequest):
790
  event_pop = float(evt.get("jumlah_jiwa", evt.get("crowd_scale", 0.0)))
791
  info = f"{evt['event_name']} ({int(event_pop):,} Jiwa) @ {evt['location']}"
792
 
 
 
 
 
 
 
 
 
793
  total_day_jiwa = target_pop + event_pop
794
  has_event = 1 if (event_pop > 0) else 0
795
 
@@ -819,6 +843,12 @@ async def predict_waste_volume(req: PredictionRequest):
819
  pop_extrapolate_factor = target_pop / baseline_pop
820
  raw_pred *= pop_extrapolate_factor
821
 
 
 
 
 
 
 
822
  calibrated_volume = round(max(0.1, raw_pred), 2)
823
 
824
  total_vol += calibrated_volume
@@ -834,7 +864,7 @@ async def predict_waste_volume(req: PredictionRequest):
834
  paper_waste_ton=round(calibrated_volume*paper_r, 2), metal_waste_ton=round(calibrated_volume*metal_r, 2),
835
  glass_waste_ton=round(calibrated_volume*glass_r, 2), textile_waste_ton=round(calibrated_volume*textile_r, 2),
836
  other_waste_ton=round(calibrated_volume*other_r, 2),
837
- recommended_trucks=max(1, int(np.ceil(calibrated_volume/8))),
838
  risk_status=risk, event_info=info, hourly_breakdown=hourly
839
  ))
840
 
@@ -855,7 +885,7 @@ async def predict_waste_volume(req: PredictionRequest):
855
  logistics_plan=LogisticsPlan(
856
  trucks_needed=trucks,
857
  manpower=trucks*3,
858
- estimated_duration_hours=round(total_vol/8, 1),
859
  efficiency_rate="85% (Optimal)"
860
  )
861
  )
@@ -878,6 +908,7 @@ async def predict_waste_volume_csv(req: PredictionRequest):
878
  "Organic Waste (Tons)", "Plastic Waste (Tons)",
879
  "Paper Waste (Tons)", "Metal Waste (Tons)",
880
  "Glass Waste (Tons)", "Textile Waste (Tons)",
 
881
  "Risk Status", "Event Info", "Recommended Trucks (15T)"
882
  ])
883
 
@@ -887,6 +918,7 @@ async def predict_waste_volume_csv(req: PredictionRequest):
887
  r.organic_waste_ton, r.plastic_waste_ton,
888
  r.paper_waste_ton, r.metal_waste_ton,
889
  r.glass_waste_ton, r.textile_waste_ton,
 
890
  r.risk_status, r.event_info or "", r.recommended_trucks
891
  ])
892
 
@@ -986,16 +1018,24 @@ async def get_autopilot_data():
986
  'Event_Crowd_Headcount': float(event_pop)
987
  }])
988
 
989
- # Predict directly using Spatial GBR model
990
  if model_gbr is not None:
991
  raw_pred = float(model_gbr.predict(features)[0])
 
 
 
 
 
 
 
992
  calibrated_volume = round(max(0.1, raw_pred), 2)
993
  else:
994
  calibrated_volume = round(float(config["normal_avg"]), 2)
995
 
996
- trucks = max(1, int(np.ceil(calibrated_volume / 8)))
997
 
998
- status = "CRITICAL" if calibrated_volume > config["critical_threshold"] else "WARNING" if calibrated_volume > config["warning_threshold"] else "SAFE"
 
999
 
1000
  total_vol += calibrated_volume
1001
  total_trucks += trucks
@@ -1014,6 +1054,15 @@ async def get_autopilot_data():
1014
  kecamatan_results.sort(key=lambda x: x["volume_ton"], reverse=True)
1015
  top_5 = kecamatan_results[:5]
1016
 
 
 
 
 
 
 
 
 
 
1017
  return {
1018
  "status": "success",
1019
  "date": d_str,
@@ -1021,5 +1070,5 @@ async def get_autopilot_data():
1021
  "total_trucks": total_trucks,
1022
  "top_kecamatan": top_5,
1023
  "rainy_regions": rainy_count,
1024
- "event_today": evt["event_name"] if evt else None
1025
  }
 
224
 
225
  def get_risk_status(volume: float, location: str) -> str:
226
  config = KECAMATAN_DATABASE.get(location, KECAMATAN_DATABASE["Menteng"])
227
+ norm = config["normal_avg"]
228
+ if volume > norm * 1.30:
229
  return "CRITICAL"
230
+ elif volume > norm * 1.12:
231
  return "WARNING"
232
  return "SAFE"
233
 
 
743
  event_pop = float(evt.get("jumlah_jiwa", evt.get("crowd_scale", 0.0)))
744
  info = f"{evt['event_name']} ({int(event_pop):,} Jiwa) @ {evt['location']}"
745
 
746
+ if info is None:
747
+ if rain_val > 15.0:
748
+ info = f"Rain Impact ({rain_val} mm)"
749
+ elif curr_date.weekday() >= 5:
750
+ info = "Weekend Activity"
751
+ else:
752
+ info = "Routine Operations"
753
+
754
  total_day_jiwa = target_pop + event_pop
755
  pop_scaling_factor = total_day_jiwa / baseline_pop
756
 
757
  raw_prediction = base * rain_m * pop_scaling_factor
758
+
759
+ # Apply stable pseudo-random daily variance (±2.5%) to simulate real human activity fluctuations
760
+ import hashlib
761
+ seed_val = int(hashlib.md5(f"{d_str}_{req.location}".encode()).hexdigest(), 16)
762
+ daily_variance = 1.0 + ((seed_val % 100) - 50) / 2000.0
763
+ raw_prediction *= daily_variance
764
+
765
  calibrated_volume = round(float(raw_prediction * calibration_factor), 2)
766
 
767
  total_vol += calibrated_volume
 
777
  paper_waste_ton=round(calibrated_volume*paper_r, 2), metal_waste_ton=round(calibrated_volume*metal_r, 2),
778
  glass_waste_ton=round(calibrated_volume*glass_r, 2), textile_waste_ton=round(calibrated_volume*textile_r, 2),
779
  other_waste_ton=round(calibrated_volume*other_r, 2),
780
+ recommended_trucks=max(1, int(np.ceil(calibrated_volume/15))),
781
  risk_status=risk, event_info=info, hourly_breakdown=hourly
782
  ))
783
 
 
806
  event_pop = float(evt.get("jumlah_jiwa", evt.get("crowd_scale", 0.0)))
807
  info = f"{evt['event_name']} ({int(event_pop):,} Jiwa) @ {evt['location']}"
808
 
809
+ if info is None:
810
+ if rain_val > 15.0:
811
+ info = f"Rain Impact ({rain_val} mm)"
812
+ elif curr_date.weekday() >= 5:
813
+ info = "Weekend Activity"
814
+ else:
815
+ info = "Routine Operations"
816
+
817
  total_day_jiwa = target_pop + event_pop
818
  has_event = 1 if (event_pop > 0) else 0
819
 
 
843
  pop_extrapolate_factor = target_pop / baseline_pop
844
  raw_pred *= pop_extrapolate_factor
845
 
846
+ # Apply stable pseudo-random daily variance (±2.5%) to simulate real human activity fluctuations
847
+ import hashlib
848
+ seed_val = int(hashlib.md5(f"{d_str}_{req.location}".encode()).hexdigest(), 16)
849
+ daily_variance = 1.0 + ((seed_val % 100) - 50) / 2000.0
850
+ raw_pred *= daily_variance
851
+
852
  calibrated_volume = round(max(0.1, raw_pred), 2)
853
 
854
  total_vol += calibrated_volume
 
864
  paper_waste_ton=round(calibrated_volume*paper_r, 2), metal_waste_ton=round(calibrated_volume*metal_r, 2),
865
  glass_waste_ton=round(calibrated_volume*glass_r, 2), textile_waste_ton=round(calibrated_volume*textile_r, 2),
866
  other_waste_ton=round(calibrated_volume*other_r, 2),
867
+ recommended_trucks=max(1, int(np.ceil(calibrated_volume/15))),
868
  risk_status=risk, event_info=info, hourly_breakdown=hourly
869
  ))
870
 
 
885
  logistics_plan=LogisticsPlan(
886
  trucks_needed=trucks,
887
  manpower=trucks*3,
888
+ estimated_duration_hours=round(total_vol/15, 1),
889
  efficiency_rate="85% (Optimal)"
890
  )
891
  )
 
908
  "Organic Waste (Tons)", "Plastic Waste (Tons)",
909
  "Paper Waste (Tons)", "Metal Waste (Tons)",
910
  "Glass Waste (Tons)", "Textile Waste (Tons)",
911
+ "Other Waste (Tons)",
912
  "Risk Status", "Event Info", "Recommended Trucks (15T)"
913
  ])
914
 
 
918
  r.organic_waste_ton, r.plastic_waste_ton,
919
  r.paper_waste_ton, r.metal_waste_ton,
920
  r.glass_waste_ton, r.textile_waste_ton,
921
+ r.other_waste_ton,
922
  r.risk_status, r.event_info or "", r.recommended_trucks
923
  ])
924
 
 
1018
  'Event_Crowd_Headcount': float(event_pop)
1019
  }])
1020
 
1021
+ # Predict directly using Spatial GBR model with daily variance seed
1022
  if model_gbr is not None:
1023
  raw_pred = float(model_gbr.predict(features)[0])
1024
+
1025
+ # Apply stable pseudo-random daily variance (±2.5%)
1026
+ import hashlib
1027
+ seed_val = int(hashlib.md5(f"{d_str}_{loc}".encode()).hexdigest(), 16)
1028
+ daily_variance = 1.0 + ((seed_val % 100) - 50) / 2000.0
1029
+ raw_pred *= daily_variance
1030
+
1031
  calibrated_volume = round(max(0.1, raw_pred), 2)
1032
  else:
1033
  calibrated_volume = round(float(config["normal_avg"]), 2)
1034
 
1035
+ trucks = max(1, int(np.ceil(calibrated_volume / 15)))
1036
 
1037
+ norm = config["normal_avg"]
1038
+ status = "CRITICAL" if calibrated_volume > norm * 1.30 else "WARNING" if calibrated_volume > norm * 1.12 else "SAFE"
1039
 
1040
  total_vol += calibrated_volume
1041
  total_trucks += trucks
 
1054
  kecamatan_results.sort(key=lambda x: x["volume_ton"], reverse=True)
1055
  top_5 = kecamatan_results[:5]
1056
 
1057
+ # Custom event description fallback for Autopilot
1058
+ event_label = "Routine Operations"
1059
+ if evt:
1060
+ event_label = evt["event_name"]
1061
+ elif rainy_count > 10:
1062
+ event_label = "Heavy Rainy Weather"
1063
+ elif today.weekday() >= 5:
1064
+ event_label = "Weekend Activity"
1065
+
1066
  return {
1067
  "status": "success",
1068
  "date": d_str,
 
1070
  "total_trucks": total_trucks,
1071
  "top_kecamatan": top_5,
1072
  "rainy_regions": rainy_count,
1073
+ "event_today": event_label
1074
  }
frontend/app.js CHANGED
@@ -486,7 +486,7 @@ function updateDashboardData(data, confScore, message) {
486
  updateMarkerRisk(selectedLocation, maxRisk);
487
  drawTransitRoute(selectedLocation);
488
 
489
- if (statTrucks) statTrucks.innerHTML = `${data.logistics_plan.trucks_needed} <span class="unit">Trucks (8T)</span>`;
490
 
491
  const startDateStr = results[0].date;
492
  const endDateStr = results[results.length - 1].date;
 
486
  updateMarkerRisk(selectedLocation, maxRisk);
487
  drawTransitRoute(selectedLocation);
488
 
489
+ if (statTrucks) statTrucks.innerHTML = `${data.logistics_plan.trucks_needed} <span class="unit">Trucks (15T)</span>`;
490
 
491
  const startDateStr = results[0].date;
492
  const endDateStr = results[results.length - 1].date;
frontend/index.html CHANGED
@@ -405,7 +405,7 @@
405
  </div>
406
  <div class="panel stat-card">
407
  <span class="card-label">RECOMMENDED FLEET</span>
408
- <span id="stat-trucks" class="card-value">0 <span class="unit">Trucks (8T)</span></span>
409
  <span class="card-meta">Logistics Fleet Suggestion</span>
410
  </div>
411
  </div>
 
405
  </div>
406
  <div class="panel stat-card">
407
  <span class="card-label">RECOMMENDED FLEET</span>
408
+ <span id="stat-trucks" class="card-value">0 <span class="unit">Trucks (15T)</span></span>
409
  <span class="card-meta">Logistics Fleet Suggestion</span>
410
  </div>
411
  </div>