diff --git a/documentation/changelog.rst b/documentation/changelog.rst index fc0f417056..98725f9e18 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -17,6 +17,7 @@ New features Infrastructure / Support ---------------------- +* Remove old rolling viewpoint forecasting infrastructure: ``make_fixed_viewpoint_forecasts`` (which only raised ``NotImplementedError``), ``make_rolling_viewpoint_forecasts`` (renamed to ``make_forecasts``), and the ``supported_horizons`` utility. The invalid-horizon validation check is also removed [see `PR #2075 `_] * Stop creating new toy assets when restarting the docker-compose stack [see `PR #2018 `_] * Migrate from ``pip`` to ``uv`` for dependency management, and from ``make`` to ``poe`` [see `PR #1973 `_] * Improve contact information to get in touch with the FlexMeasures community [see `PR #2022 `_] diff --git a/flexmeasures/data/models/forecasting/exceptions.py b/flexmeasures/data/models/forecasting/exceptions.py index 1d963d79b4..611990664f 100644 --- a/flexmeasures/data/models/forecasting/exceptions.py +++ b/flexmeasures/data/models/forecasting/exceptions.py @@ -1,6 +1,2 @@ class NotEnoughDataException(Exception): pass - - -class InvalidHorizonException(Exception): - pass diff --git a/flexmeasures/data/services/forecasting.py b/flexmeasures/data/services/forecasting.py index 9b8089f288..8ca8acd7fb 100644 --- a/flexmeasures/data/services/forecasting.py +++ b/flexmeasures/data/services/forecasting.py @@ -16,7 +16,6 @@ from flexmeasures.data import db from flexmeasures.data.models.forecasting import lookup_model_specs_configurator -from flexmeasures.data.models.forecasting.exceptions import InvalidHorizonException from flexmeasures.data.models.time_series import Sensor, TimedBelief from flexmeasures.data.models.forecasting.utils import ( get_query_window, @@ -27,13 +26,12 @@ as_server_time, server_now, forecast_horizons_for, - supported_horizons, ) """ The life cycle of a forecasting job: 1. A forecasting job is born in create_forecasting_jobs. -2. It is run in make_rolling_viewpoint_forecasts or make_fixed_viewpoint_forecasts, which write results to the db. +2. It is run in make_forecasts, which writes results to the db. This is also where model specs are configured and a possible fallback model is stored for step 3. 3. If an error occurs (and the worker is configured accordingly), handle_forecasting_exception comes in. This might re-enqueue the job or try a different model (which creates a new job). @@ -96,7 +94,7 @@ def create_forecasting_jobs( jobs: list[Job] = [] for horizon in horizons: job = Job.create( - make_rolling_viewpoint_forecasts, + make_forecasts, kwargs=dict( sensor_id=sensor_id, horizon=horizon, @@ -130,35 +128,18 @@ def create_forecasting_jobs( return jobs -def make_fixed_viewpoint_forecasts( +def make_forecasts( sensor_id: int, horizon: timedelta, start: datetime, end: datetime, custom_model_params: dict = None, ) -> int: - """Build forecasting model specs, make fixed-viewpoint forecasts, and save the forecasts made. + """Build forecasting model specs, make forecasts, and save the forecasts made. Each individual forecast is a belief about a time interval. - Fixed-viewpoint forecasts share the same belief time. - See the timely-beliefs lib for relevant terminology. - """ - # todo: implement fixed-viewpoint forecasts - raise NotImplementedError - - -def make_rolling_viewpoint_forecasts( - sensor_id: int, - horizon: timedelta, - start: datetime, - end: datetime, - custom_model_params: dict = None, -) -> int: - """Build forecasting model specs, make rolling-viewpoint forecasts, and save the forecasts made. - - Each individual forecast is a belief about a time interval. - Rolling-viewpoint forecasts share the same belief horizon (the duration between belief time and knowledge time). - Model specs are also retrained in a rolling fashion, but with its own frequency set in custom_model_params. + Forecasts share the same belief horizon (the duration between belief time and knowledge time). + Model specs are retrained periodically, with frequency set in custom_model_params. See the timely-beliefs lib for relevant terminology. Parameters @@ -214,12 +195,7 @@ def make_rolling_viewpoint_forecasts( rq_job.meta["fallback_model_search_term"] = fallback_model_search_term rq_job.save() - # before we run the model, check if horizon is okay and enough data is available - if horizon not in supported_horizons(): - raise InvalidHorizonException( - "Invalid horizon on job %s: %s" % (rq_job.id, horizon) - ) - + # before we run the model, check if enough data is available query_window = get_query_window( model_specs.start_of_training, end, @@ -290,7 +266,7 @@ def handle_forecasting_exception(job, exc_type, exc_value, traceback): if "fallback_model_search_term" in job.meta: if job.meta["fallback_model_search_term"] is not None: new_job = Job.create( - make_rolling_viewpoint_forecasts, + make_forecasts, args=job.args, kwargs=job.kwargs, connection=current_app.queues["forecasting"].connection, diff --git a/flexmeasures/data/tests/test_forecasting_jobs.py b/flexmeasures/data/tests/test_forecasting_jobs.py index 3de6f85b79..d132df8338 100644 --- a/flexmeasures/data/tests/test_forecasting_jobs.py +++ b/flexmeasures/data/tests/test_forecasting_jobs.py @@ -199,25 +199,6 @@ def test_failed_forecasting_insufficient_data( check_failures(app.queues["forecasting"], 2 * ["NotEnoughDataException"]) -def test_failed_forecasting_invalid_horizon( - app, run_as_cli, clean_redis, setup_test_data -): - """This one (as well as the fallback) should fail as the horizon is invalid.""" - - # asset has only 1 power sensor - solar_device_1: Sensor = setup_test_data["solar-asset-1"].sensors[0] - - create_forecasting_jobs( - start_of_roll=as_server_time(datetime(2015, 1, 1, 21)), - end_of_roll=as_server_time(datetime(2015, 1, 1, 23)), - horizons=[timedelta(hours=18)], - sensor_id=solar_device_1.id, - custom_model_params=custom_model_params(), - ) - work_on_rq(app.queues["forecasting"], exc_handler=handle_forecasting_exception) - check_failures(app.queues["forecasting"], 2 * ["InvalidHorizonException"]) - - def test_failed_unknown_model(app, clean_redis, setup_test_data): """This one should fail because we use a model search term which yields no model configurator.""" diff --git a/flexmeasures/utils/time_utils.py b/flexmeasures/utils/time_utils.py index 0c9ba62cf0..4f3c9801fa 100644 --- a/flexmeasures/utils/time_utils.py +++ b/flexmeasures/utils/time_utils.py @@ -279,15 +279,6 @@ def forecast_horizons_for(resolution: str | timedelta) -> list[str] | list[timed return horizons -def supported_horizons() -> list[timedelta]: - return [ - timedelta(hours=1), - timedelta(hours=6), - timedelta(hours=24), - timedelta(hours=48), - ] - - def timedelta_to_pandas_freq_str(resolution: timedelta) -> str: return to_offset(resolution).freqstr