|
| 1 | +--- |
| 2 | +status: "draft" |
| 3 | +version: "v1.0" |
| 4 | +task_type: "standard" |
| 5 | +requires_ml_review: false |
| 6 | +requires_data_engineer: false |
| 7 | +target_modules: ["packages/ml_core/src/ml_core/utils.py"] |
| 8 | +--- |
| 9 | +# Implementation Plan: Extend Forecast Horizon in Evaluation |
| 10 | + |
| 11 | +## Objective |
| 12 | +Modify `evaluate_and_save_model` to allow the forecast horizon to extend beyond `test_end` (e.g., by 14 days) for inference, while keeping the evaluation metrics restricted to the `[test_start, test_end]` period. |
| 13 | + |
| 14 | +## Context |
| 15 | +Currently, all input data in `evaluate_and_save_model` is sliced to `test_end`. This limits the generated forecast to the test period (which is 1 day in integration tests). By extending the NWP slice to `test_end + 14 days`, the model can generate a 14-day forecast. The evaluation will naturally remain restricted to `test_end` because the actuals (`power_time_series`) will still be sliced to `test_end`, and the evaluation uses an `inner` join between predictions and actuals. |
| 16 | + |
| 17 | +## Proposed Changes |
| 18 | + |
| 19 | +### 1. Fix `time_col` determination for `power_time_series` |
| 20 | +In both `evaluate_and_save_model` and `train_and_log_model`, the `time_col` logic currently checks for `"power_flows" in key`. This fails to match the new `"power_time_series"` key, causing actuals to not be sliced at all. |
| 21 | +- **Change:** Update the condition to check for `"power" in key` (or explicitly `key in ["power_flows", "power_time_series"]`). |
| 22 | +- **Why:** Ensures `power_time_series` is correctly identified and sliced using `period_end_time`. |
| 23 | + |
| 24 | +### 2. Extend `slice_end` for NWPs in `evaluate_and_save_model` |
| 25 | +Modify the temporal slicing loop in `evaluate_and_save_model` to extend the end date for NWP data. |
| 26 | +- **Change:** Introduce a `slice_end` variable that defaults to `test_end`. |
| 27 | +- **Change:** If `"nwps" in key`, set `slice_end = test_end + timedelta(days=getattr(config.model, "forecast_horizon_days", 14))`. |
| 28 | +- **Change:** Pass `slice_end` to `_slice_temporal_data` instead of `test_end`. |
| 29 | +- **Why:** This provides the model with future weather data, allowing it to generate predictions up to 14 days beyond `test_end`. |
| 30 | + |
| 31 | +### 3. Verify Evaluation Restriction |
| 32 | +No changes are needed to the evaluation logic itself. |
| 33 | +- **Why:** Because `power_time_series` (actuals) will now be correctly sliced to `test_end`, the `inner` join between `results_lf` (which extends to `test_end + 14 days`) and `actuals_lf` will automatically drop the future predictions from the evaluation dataset (`eval_lf`). The returned dataframe will still contain the full 14-day forecast. |
| 34 | + |
| 35 | +## Code Snippet (for `evaluate_and_save_model`) |
| 36 | +```python |
| 37 | + # 1. Universal Temporal Slicing for Test Set |
| 38 | + test_start = config.data_split.test_start |
| 39 | + test_end = config.data_split.test_end |
| 40 | + |
| 41 | + sliced_data = {} |
| 42 | + for key, val in kwargs.items(): |
| 43 | + if key == "time_series_metadata": |
| 44 | + sliced_data[key] = val |
| 45 | + continue |
| 46 | + |
| 47 | + # Fix time_col to handle power_time_series |
| 48 | + time_col = "period_end_time" if "power" in key else "valid_time" |
| 49 | + |
| 50 | + # Add a configurable lookback for autoregressive features |
| 51 | + slice_start = test_start |
| 52 | + if "power" in key or "nwps" in key: |
| 53 | + lookback = getattr(config.model, "required_lookback_days", 14) |
| 54 | + slice_start = test_start - timedelta(days=lookback) |
| 55 | + |
| 56 | + # Extend slice_end for NWPs to allow forecasting beyond test_end |
| 57 | + slice_end = test_end |
| 58 | + if "nwps" in key: |
| 59 | + forecast_horizon = getattr(config.model, "forecast_horizon_days", 14) |
| 60 | + slice_end = test_end + timedelta(days=forecast_horizon) |
| 61 | + |
| 62 | + sliced_data[key] = _slice_temporal_data(val, slice_start, slice_end, time_col) |
| 63 | +``` |
| 64 | + |
| 65 | +## Code Snippet (for `train_and_log_model`) |
| 66 | +```python |
| 67 | + # Fix time_col to handle power_time_series |
| 68 | + time_col = "period_end_time" if "power" in key else "valid_time" |
| 69 | + |
| 70 | + # Add a configurable lookback for autoregressive features |
| 71 | + slice_start = train_start |
| 72 | + if "power" in key or "nwps" in key: |
| 73 | + lookback = getattr(config.model, "required_lookback_days", 14) |
| 74 | + slice_start = train_start - timedelta(days=lookback) |
| 75 | +``` |
| 76 | + |
| 77 | +## Coding Standards & Mandates |
| 78 | +- **Comments:** You must add explicit code comments explaining *why* `slice_end` is extended for NWPs (to allow forecasting into the future) and *why* the evaluation remains restricted (because the inner join with actuals naturally drops future predictions). Do not just describe *what* the code is doing. |
| 79 | +- **No FLAW IDs:** You are strictly forbidden from referencing any FLAW-XXX IDs in code comments. |
0 commit comments