Skip to content

Small forecasting fixes: forecast-only future regressors and dict-typed CLI options - #2373

Open
BelhsanHmida wants to merge 4 commits into
mainfrom
fix/small-forecasting-fixes
Open

Small forecasting fixes: forecast-only future regressors and dict-typed CLI options#2373
BelhsanHmida wants to merge 4 commits into
mainfrom
fix/small-forecasting-fixes

Conversation

@BelhsanHmida

Copy link
Copy Markdown
Contributor

Description

This PR fixes two independent bugs found while testing the forecasting pipeline with ENTSO-E day-ahead fundamentals (residual load, load forecast, generation outages) as future regressors. Both are small and come with regression tests.

  • Forecast-only future regressors reach the training window: sensors that only ever record forecasts (belief time never after the event start, e.g. day-ahead market fundamentals) were filtered out of the training window entirely, so the model trained on a near-constant, interpolation-filled column and silently ignored the regressor
  • Realized values still win: where a realized belief exists for an event, it takes precedence over the forecast, per event and per regressor
  • Strict semantics preserved: past regressors keep their realized-only contract, and the predict-window slice stays forecast-only, so no ex-post data leaks into a simulated backtest
  • Dict-typed CLI options parse again: plain fields.Dict options such as --model-params (and --snap) were passed to Marshmallow as raw strings and rejected with "Not a valid mapping type"; they now reuse NestedDictParamType, accepting both JSON and Python-literal syntax
  • Test coverage for the Click layer: the dict-option bug shipped unnoticed because existing tests construct the model directly and never exercise the CLI, so the new test invokes the option through CliRunner

Why the regressors went missing

split_data_all_beliefs built the training portion of the future-regressor covariates by keeping only realized beliefs (belief_time > event_start). A day-ahead fundamental is always recorded ~21h before its event, so it has no such beliefs: every training row was dropped, and the missing-value filler papered over the gap by stretching a single value across it.

The effect was total. In my testing, 91% of the covariate series that reached the model was one repeated value, injecting an absurd synthetic signal changed none of the 672 predictions, and forecasting with --future-regressors was bit-identical to a run with no regressors at all.

_latest_known_per_regressor now takes a fall_back_to_forecast flag. When set, it keeps both ex-post and ex-ante beliefs and lets the existing "latest belief per event" selection decide: for any given event a realized belief time necessarily exceeds every forecast belief time, so realized values win wherever they exist and other events fall back to their latest forecast. Only the training-window call site passes the flag.

Example

Both of these now work as documented:

# A future regressor whose sensor only ever records day-ahead forecasts
# now populates the training window instead of collapsing to a constant.
flexmeasures add forecasts --sensor 14 --future-regressors 796 \
    --train-start 2026-03-01T00:00:00+01:00 \
    --start 2026-05-11T00:00:00+02:00 --end 2026-05-18T00:00:00+02:00

# Dict-typed options are parsed at the Click level (JSON or Python-literal).
flexmeasures add forecasts --sensor 14 --model-params '{"max_depth": 6}'

Before this PR the second command failed with:

Error: Invalid forecasting configuration: {'model-params': ['Not a valid mapping type.']}

Testing Instructions

Run the two affected test modules:

pytest flexmeasures/data/tests/test_forecasting_pipeline.py
pytest flexmeasures/cli/tests/test_utils.py
  • test_forecast_only_future_regressor_populates_training_window covers a future regressor with only ex-ante beliefs, plus a second regressor with one realized revision to prove realized values still take precedence per event.
  • test_add_cli_options_from_schema_parses_dict_fields invokes --model-params through Click in both JSON and Python-literal syntax and checks the parsed value passes schema validation.
  • Both tests fail on the parent commit and pass with the fix. Full modules green: 50 passed and 11 passed / 1 xfailed respectively.

Verified against real data too (NL day-ahead prices with ENTSO-E residual load as a future regressor): before the fix the training covariate was 91% a single constant; after it, all 6,812 training values vary as they should.

Sign-off

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on code under GPL or other license that is incompatible with FlexMeasures

BelhsanHmida and others added 3 commits July 30, 2026 14:15
Future regressors whose sensors only ever record ex-ante beliefs (belief
time never after the event start, e.g. day-ahead market fundamentals)
were entirely filtered out of the training window by the strict
realized-only selection in split_data_all_beliefs, leaving the model a
constant, interpolation-filled series. The training-window selection now
falls back to the latest forecast per event when no realized belief
exists, while still preferring realized values where available. The
predict-window slice and past regressors keep their strict semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
add_cli_options_from_schema wired Click-level JSON parsing for
MarshmallowClickMixin and list fields, but plain fields.Dict options
fell through as raw strings, so --model-params '{"max_depth": 6}'
failed schema validation with 'Not a valid mapping type'. Dict fields
now reuse NestedDictParamType, accepting both JSON and Python-literal
syntax. The only workaround used to be passing --config with a file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR number placeholders to be filled in once the PR is opened.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BelhsanHmida BelhsanHmida self-assigned this Jul 31, 2026
@BelhsanHmida BelhsanHmida added bug Something isn't working Forecasting labels Jul 31, 2026
Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working Forecasting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant