Aadithya commited on
Commit
6da8909
·
1 Parent(s): 08f8258

feat: implement chronos-on-chronos ensemble for multi-resolution drift calibration

Browse files
Files changed (1) hide show
  1. worker/tasks.py +24 -23
worker/tasks.py CHANGED
@@ -48,18 +48,36 @@ def run_gridops_pipeline(self, dataset_path: str, severity_threshold: float = 0.
48
  client = get_chronos_client()
49
 
50
  if forecast_horizon <= 14:
51
- logger.info("Routing to High-Fidelity Hourly Inference Pipeline (horizon <= 14)")
52
- chronos_result = client.forecast(
 
 
 
 
 
 
 
 
 
 
 
53
  pipeline.train_hourly.values, # pyrefly: ignore[bad-argument-type]
54
  prediction_length=forecast_horizon * 24,
55
  num_samples=20,
56
  )
57
- import numpy as np
58
 
59
  # Bridge: reshape (horizon*24) -> (horizon, 24) and apply daily median
60
- chronos_p10 = np.median(np.asarray(chronos_result['p10']).reshape(forecast_horizon, 24), axis=1)
61
- chronos_p50 = np.median(np.asarray(chronos_result['p50']).reshape(forecast_horizon, 24), axis=1)
62
- chronos_p90 = np.median(np.asarray(chronos_result['p90']).reshape(forecast_horizon, 24), axis=1)
 
 
 
 
 
 
 
 
63
  else:
64
  logger.info("Routing to Structural Daily Inference Pipeline (horizon > 14)")
65
  chronos_result = client.forecast(
@@ -71,23 +89,6 @@ def run_gridops_pipeline(self, dataset_path: str, severity_threshold: float = 0.
71
  chronos_p50 = chronos_result['p50']
72
  chronos_p90 = chronos_result['p90']
73
 
74
- import numpy as np
75
-
76
- # --- Mean-Matching Ensemble Calibration ---
77
- # SARIMA (16-yr context) provides the structurally accurate baseline mean
78
- sarima_mean = np.mean(sarima_fc)
79
-
80
- # Chronos provides the high-fidelity shape, but has a recency-biased mean
81
- chronos_mean = np.mean(chronos_p50)
82
-
83
- # Calculate the bias shift required to anchor Chronos to SARIMA
84
- bias_shift = sarima_mean - chronos_mean
85
-
86
- # Apply the shift and mathematically clip to 0 to prevent negative physical loads
87
- chronos_p10 = np.maximum(chronos_p10 + bias_shift, 0.0)
88
- chronos_p50 = np.maximum(chronos_p50 + bias_shift, 0.0)
89
- chronos_p90 = np.maximum(chronos_p90 + bias_shift, 0.0)
90
-
91
  assert not isinstance(chronos_p10, list)
92
  assert not isinstance(chronos_p50, list)
93
  assert not isinstance(chronos_p90, list)
 
48
  client = get_chronos_client()
49
 
50
  if forecast_horizon <= 14:
51
+ logger.info("Routing to Multi-Resolution Chronos Ensemble (horizon <= 14)")
52
+ import numpy as np
53
+
54
+ # 1. Fetch Daily Baseline (Low drift, high structural accuracy)
55
+ daily_chronos_result = client.forecast(
56
+ pipeline.train.values, # pyrefly: ignore[bad-argument-type]
57
+ prediction_length=forecast_horizon,
58
+ num_samples=20,
59
+ )
60
+ daily_chronos_mean = np.mean(daily_chronos_result['p50'])
61
+
62
+ # 2. Fetch Hourly Shape (High-fidelity zig-zags, high drift)
63
+ hourly_chronos_result = client.forecast(
64
  pipeline.train_hourly.values, # pyrefly: ignore[bad-argument-type]
65
  prediction_length=forecast_horizon * 24,
66
  num_samples=20,
67
  )
 
68
 
69
  # Bridge: reshape (horizon*24) -> (horizon, 24) and apply daily median
70
+ hourly_p10 = np.median(np.asarray(hourly_chronos_result['p10']).reshape(forecast_horizon, 24), axis=1)
71
+ hourly_p50 = np.median(np.asarray(hourly_chronos_result['p50']).reshape(forecast_horizon, 24), axis=1)
72
+ hourly_p90 = np.median(np.asarray(hourly_chronos_result['p90']).reshape(forecast_horizon, 24), axis=1)
73
+
74
+ # 3. Ensemble Calibration
75
+ hourly_chronos_mean = np.mean(hourly_p50)
76
+ bias_shift = daily_chronos_mean - hourly_chronos_mean
77
+
78
+ chronos_p10 = np.maximum(hourly_p10 + bias_shift, 0.0)
79
+ chronos_p50 = np.maximum(hourly_p50 + bias_shift, 0.0)
80
+ chronos_p90 = np.maximum(hourly_p90 + bias_shift, 0.0)
81
  else:
82
  logger.info("Routing to Structural Daily Inference Pipeline (horizon > 14)")
83
  chronos_result = client.forecast(
 
89
  chronos_p50 = chronos_result['p50']
90
  chronos_p90 = chronos_result['p90']
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  assert not isinstance(chronos_p10, list)
93
  assert not isinstance(chronos_p50, list)
94
  assert not isinstance(chronos_p90, list)