jtlevine Claude Opus 4.7 (1M context) commited on
Commit
93f565b
·
1 Parent(s): acf51b6

Cut XGBoost/LSTM heat predictor from pipeline and demo

Browse files

Parallels 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>

Files changed (2) hide show
  1. src/api.py +5 -7
  2. src/pipeline.py +6 -16
src/api.py CHANGED
@@ -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,
src/pipeline.py CHANGED
@@ -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
- "trigger_probability_7d": heat.get("trigger_probability", 0),
683
- "prediction_confidence": heat.get("prediction_confidence"),
 
 
684
  "model_tier": forecast_source,
685
- "xgb_probability": heat.get("trigger_probability"),
686
- "lstm_probability": heat.get("neural_trigger_prob"),
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"),