Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://www.github.com/FlexMeasures/flexmeasures/pull/2075>`_]
* Stop creating new toy assets when restarting the docker-compose stack [see `PR #2018 <https://www.github.com/FlexMeasures/flexmeasures/pull/2018>`_]
* Migrate from ``pip`` to ``uv`` for dependency management, and from ``make`` to ``poe`` [see `PR #1973 <https://github.com/FlexMeasures/flexmeasures/pull/1973>`_]
* Improve contact information to get in touch with the FlexMeasures community [see `PR #2022 <https://www.github.com/FlexMeasures/flexmeasures/pull/2022>`_]
Expand Down
4 changes: 0 additions & 4 deletions flexmeasures/data/models/forecasting/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
class NotEnoughDataException(Exception):
pass


class InvalidHorizonException(Exception):
pass
40 changes: 8 additions & 32 deletions flexmeasures/data/services/forecasting.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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).
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
19 changes: 0 additions & 19 deletions flexmeasures/data/tests/test_forecasting_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
9 changes: 0 additions & 9 deletions flexmeasures/utils/time_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading