Cut XGBoost/LSTM heat predictor from pipeline and demo
Browse filesParallels the MI MOS cut. HeatWavePredictor (XGBoost classifier +
ensembled LSTM) was trained on 2 years of pure-synthetic AR(1) noise
using CITY_CLIMATE seasonal curves (heat_forecast.py:331, lstm auto-
retrain at :530). It wrote trigger_probability_7d / prediction_confidence
/ xgb_probability / lstm_probability into the predictions table, but
nothing downstream used those numbers for a decision -- GraphCast drives
trigger firing, burn analysis drives pricing. Classic orphan layer.
Changes:
- Drop HeatWavePredictor import + self._predictor in pipeline.py
- Drop the predictor.predict() call inside _step_predict; the step now
only does GraphCast/observed trigger logic and burn-analysis pricing
- predictions row: NULL for trigger_probability_7d, prediction_confidence,
xgb_probability, lstm_probability. model_tier and ensemble_method
retained (they describe the active forecast source, not the dropped
ensemble). annual_cost_per_worker / payout_factor / learned_frequency
keep coming from burn analysis
- api.py _generate_demo_data: drop HeatWavePredictor, replace with a
composite/100 heuristic for the demo trigger_probability_7d shape
Kept intact:
- UHI XGBoost (uhi_model.py) -- thin wrapper over published UHI
literature formula, values match Dar UHI measurements, low cost
- heat_forecast.py / lstm_model.py files on disk -- not wired from
runtime anymore but left for the WIP Chronos-Bolt retrain work the
user has in models/heat_lstm.pt + scripts/retrain_trigger_heads.py
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/api.py +5 -7
- src/pipeline.py +6 -16
|
@@ -27,7 +27,6 @@ from fastapi.responses import HTMLResponse
|
|
| 27 |
from config import ZONES, ZONE_MAP, CITIES, HEAT_THRESHOLDS, PAYOUT_PER_EVENT_USD
|
| 28 |
from src.indexing.heat_index import calculate_wbgt, calculate_heat_index, count_consecutive_days, count_trigger_days
|
| 29 |
from src.downscaling.uhi_model import UHICorrector
|
| 30 |
-
from src.prediction.heat_forecast import HeatWavePredictor
|
| 31 |
from src.pricing.burn_analysis import BurnAnalysisPricer
|
| 32 |
from src.pricing.budget_optimizer import BudgetOptimizer
|
| 33 |
from src.database.crud import init_db, upsert_zone
|
|
@@ -123,7 +122,6 @@ def _generate_demo_data():
|
|
| 123 |
|
| 124 |
# Initialize ML models
|
| 125 |
uhi_corrector = UHICorrector()
|
| 126 |
-
predictor = HeatWavePredictor()
|
| 127 |
|
| 128 |
# City base temperatures (ERA5-Land grid-level — before UHI correction)
|
| 129 |
city_climate = {
|
|
@@ -173,11 +171,6 @@ def _generate_demo_data():
|
|
| 173 |
daily_hi.append(hi)
|
| 174 |
daily_uhi_deltas.append(round(uhi_delta, 1))
|
| 175 |
|
| 176 |
-
# ML heat wave prediction (pass last 30 days for proper anomaly features)
|
| 177 |
-
pred_prob, pred_conf, pred_tier = predictor.predict(
|
| 178 |
-
z, daily_temps[-30:], daily_humidity[-30:], daily_wbgt[-30:],
|
| 179 |
-
)
|
| 180 |
-
|
| 181 |
max_temp = max(daily_temps)
|
| 182 |
max_wbgt = max(daily_wbgt)
|
| 183 |
recent_temps = daily_temps[-7:]
|
|
@@ -208,6 +201,11 @@ def _generate_demo_data():
|
|
| 208 |
|
| 209 |
enrolled = int(z.worker_population_est * rng.uniform(0.15, 0.45))
|
| 210 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
zone_data = {
|
| 212 |
"zone_id": z.zone_id,
|
| 213 |
"name": z.name,
|
|
|
|
| 27 |
from config import ZONES, ZONE_MAP, CITIES, HEAT_THRESHOLDS, PAYOUT_PER_EVENT_USD
|
| 28 |
from src.indexing.heat_index import calculate_wbgt, calculate_heat_index, count_consecutive_days, count_trigger_days
|
| 29 |
from src.downscaling.uhi_model import UHICorrector
|
|
|
|
| 30 |
from src.pricing.burn_analysis import BurnAnalysisPricer
|
| 31 |
from src.pricing.budget_optimizer import BudgetOptimizer
|
| 32 |
from src.database.crud import init_db, upsert_zone
|
|
|
|
| 122 |
|
| 123 |
# Initialize ML models
|
| 124 |
uhi_corrector = UHICorrector()
|
|
|
|
| 125 |
|
| 126 |
# City base temperatures (ERA5-Land grid-level — before UHI correction)
|
| 127 |
city_climate = {
|
|
|
|
| 171 |
daily_hi.append(hi)
|
| 172 |
daily_uhi_deltas.append(round(uhi_delta, 1))
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
max_temp = max(daily_temps)
|
| 175 |
max_wbgt = max(daily_wbgt)
|
| 176 |
recent_temps = daily_temps[-7:]
|
|
|
|
| 201 |
|
| 202 |
enrolled = int(z.worker_population_est * rng.uniform(0.15, 0.45))
|
| 203 |
|
| 204 |
+
# Composite-driven trigger probability for demo shape. Not a model output.
|
| 205 |
+
pred_prob = round(min(1.0, composite / 100), 2)
|
| 206 |
+
pred_conf = 0.5
|
| 207 |
+
pred_tier = "composite_heuristic"
|
| 208 |
+
|
| 209 |
zone_data = {
|
| 210 |
"zone_id": z.zone_id,
|
| 211 |
"name": z.name,
|
|
@@ -27,7 +27,6 @@ from src.healing.healer import HealingAgent, RuleBasedFallback, HealedData
|
|
| 27 |
from src.indexing.heat_risk import compute_heat_risk, HeatTriggerEvent
|
| 28 |
from src.indexing.heat_index import calculate_wbgt, calculate_heat_index
|
| 29 |
from src.downscaling.uhi_model import UHICorrector
|
| 30 |
-
from src.prediction.heat_forecast import HeatWavePredictor
|
| 31 |
from src.calibration.basis_risk import assess_all_zones
|
| 32 |
from src.explanation.explainer import TriggerExplainer, TemplateExplainer
|
| 33 |
|
|
@@ -102,7 +101,6 @@ class HeatRiskPipeline:
|
|
| 102 |
|
| 103 |
# ML models
|
| 104 |
self._uhi_corrector = UHICorrector()
|
| 105 |
-
self._predictor = HeatWavePredictor()
|
| 106 |
self._burn_pricer = None # initialized lazily in _step_predict
|
| 107 |
|
| 108 |
# Load ERA5-Land historical data for padding short climate histories
|
|
@@ -587,17 +585,7 @@ class HeatRiskPipeline:
|
|
| 587 |
heat = self._heat_data.get(zone_id, {})
|
| 588 |
corrected = heat.get("corrected_temps", temps)
|
| 589 |
|
| 590 |
-
if len(corrected) >= 7:
|
| 591 |
-
prob, conf, tier = self._predictor.predict(
|
| 592 |
-
zone, corrected[-30:], humidities[-30:], wbgts[-30:]
|
| 593 |
-
)
|
| 594 |
-
else:
|
| 595 |
-
prob, conf, tier = 0.1, 0.3, "climatology"
|
| 596 |
-
|
| 597 |
self._heat_data.setdefault(zone_id, {}).update({
|
| 598 |
-
"trigger_probability": prob,
|
| 599 |
-
"prediction_confidence": conf,
|
| 600 |
-
"model_tier": tier,
|
| 601 |
"temps": corrected,
|
| 602 |
"humidities": humidities,
|
| 603 |
"wbgts": wbgts,
|
|
@@ -679,11 +667,13 @@ class HeatRiskPipeline:
|
|
| 679 |
self._db_write(insert_prediction, self.db, {
|
| 680 |
"zone_id": zone_id,
|
| 681 |
"date": today,
|
| 682 |
-
|
| 683 |
-
|
|
|
|
|
|
|
| 684 |
"model_tier": forecast_source,
|
| 685 |
-
"xgb_probability":
|
| 686 |
-
"lstm_probability":
|
| 687 |
"ensemble_method": "graphcast_forecast" if gc_wbgt else "observed_fallback",
|
| 688 |
"annual_cost_per_worker": heat.get("annual_cost_per_worker"),
|
| 689 |
"payout_factor": heat.get("payout_factor"),
|
|
|
|
| 27 |
from src.indexing.heat_risk import compute_heat_risk, HeatTriggerEvent
|
| 28 |
from src.indexing.heat_index import calculate_wbgt, calculate_heat_index
|
| 29 |
from src.downscaling.uhi_model import UHICorrector
|
|
|
|
| 30 |
from src.calibration.basis_risk import assess_all_zones
|
| 31 |
from src.explanation.explainer import TriggerExplainer, TemplateExplainer
|
| 32 |
|
|
|
|
| 101 |
|
| 102 |
# ML models
|
| 103 |
self._uhi_corrector = UHICorrector()
|
|
|
|
| 104 |
self._burn_pricer = None # initialized lazily in _step_predict
|
| 105 |
|
| 106 |
# Load ERA5-Land historical data for padding short climate histories
|
|
|
|
| 585 |
heat = self._heat_data.get(zone_id, {})
|
| 586 |
corrected = heat.get("corrected_temps", temps)
|
| 587 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
self._heat_data.setdefault(zone_id, {}).update({
|
|
|
|
|
|
|
|
|
|
| 589 |
"temps": corrected,
|
| 590 |
"humidities": humidities,
|
| 591 |
"wbgts": wbgts,
|
|
|
|
| 667 |
self._db_write(insert_prediction, self.db, {
|
| 668 |
"zone_id": zone_id,
|
| 669 |
"date": today,
|
| 670 |
+
# trigger probability columns stay NULL: the synthetic-trained
|
| 671 |
+
# XGBoost/LSTM predictor was cut. GraphCast drives triggers.
|
| 672 |
+
"trigger_probability_7d": None,
|
| 673 |
+
"prediction_confidence": None,
|
| 674 |
"model_tier": forecast_source,
|
| 675 |
+
"xgb_probability": None,
|
| 676 |
+
"lstm_probability": None,
|
| 677 |
"ensemble_method": "graphcast_forecast" if gc_wbgt else "observed_fallback",
|
| 678 |
"annual_cost_per_worker": heat.get("annual_cost_per_worker"),
|
| 679 |
"payout_factor": heat.get("payout_factor"),
|