Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
53e65b2
feat: make start_date optional
Flix6x Jan 14, 2026
5dffa31
docs: stop putting a focus on `start-date`
Flix6x Jan 14, 2026
0d98e4b
feat: start testing timing parameters against ForecasterParametersSchema
Flix6x Jan 15, 2026
74f558e
dev: comment out failing test case
Flix6x Jan 15, 2026
b33c4ba
dev: fix commenting out failing test case
Flix6x Jan 15, 2026
efa4d5b
fix: monkeypatch now
Flix6x Jan 15, 2026
1a88b4c
refactor: freeze server_now in every FlexMeasures module
Flix6x Jan 15, 2026
5465fb6
fix: failing tests (reproduced with `pytest -k "test_user_crud or tes…
Flix6x Jan 15, 2026
f9bbaae
feat: make end_date optional param
BelhsanHmida Jan 19, 2026
d74d65e
feat: add optional end_date handling in ForecasterParametersSchema
BelhsanHmida Jan 19, 2026
5866da8
dev: uncomment empty test case. with todo to include every timing par…
BelhsanHmida Jan 19, 2026
61811c8
style: run pre-commit
BelhsanHmida Jan 19, 2026
d5c77f1
docs: add changelog entry.
BelhsanHmida Jan 20, 2026
98a30e2
Revert "docs: add changelog entry."
BelhsanHmida Jan 20, 2026
3bbbefb
chore: remove dev print statement
BelhsanHmida Jan 20, 2026
e7d46c7
feat: add not timing params giving test case
BelhsanHmida Jan 20, 2026
5f53b9a
test: only end_date given test_case
BelhsanHmida Jan 20, 2026
ce33d4a
test: add comments
BelhsanHmida Jan 20, 2026
7077a42
test: add case for both start and end dates in ForecasterParametersSc…
BelhsanHmida Jan 20, 2026
75e026d
test: end_date and train_period test case
BelhsanHmida Jan 20, 2026
7c05dcc
test: add case for only start date with training period in Forecaster…
BelhsanHmida Jan 20, 2026
c286574
test: add case for only start date with retrain frequency (predict_pe…
BelhsanHmida Jan 20, 2026
379470d
test: add case for start date with training period and retrain freque…
BelhsanHmida Jan 20, 2026
bd606f7
fix: correctly ensure start date is before end date in ForecasterPara…
BelhsanHmida Jan 20, 2026
ed9d87a
style: run pre-commit
BelhsanHmida Jan 20, 2026
ab18c13
chore: standardize timestamp floor method to lowercase '1h' because o…
BelhsanHmida Jan 20, 2026
6326ee2
docs(fix): reference training_period instead of max_training_period i…
BelhsanHmida Jan 20, 2026
5372f37
docs: add changelog entry.
BelhsanHmida Jan 20, 2026
23af90c
feat: make end_date option optional in add forecasts.
BelhsanHmida Jan 20, 2026
ad68ada
fix: add end_date not none check
BelhsanHmida Jan 20, 2026
6c967ff
fix: allow end_date to be nullable in ForecasterParametersSchema
BelhsanHmida Jan 20, 2026
dfdc858
feat: add pre_load method to drop None values in ForecasterParameters…
BelhsanHmida Jan 20, 2026
cb74806
refactor: rename train_predict_pipeline to add_forecast for clarity i…
BelhsanHmida Jan 20, 2026
d876465
style: run pre-commit
BelhsanHmida Jan 20, 2026
fa098f9
chore: change formatting of start-date and end-date in changelog
BelhsanHmida Jan 21, 2026
c1df163
properly link ForecasterParametersSchema in OpenAPI specs
nhoening Jan 21, 2026
1897b29
fix: validate max_training_period to disallow years and months in For…
BelhsanHmida Jan 21, 2026
89bfdff
Merge branch 'main' into feat/optional-training-start
BelhsanHmida Feb 1, 2026
786fdbd
feat(test): add save_belief_time to expectations
BelhsanHmida Feb 1, 2026
157ca17
docs: add start_date test case comment
BelhsanHmida Feb 1, 2026
9f2ee64
docs: add predict start test case comment
BelhsanHmida Feb 1, 2026
672f67a
fix: remove forecast_frequency default because the default is handled…
BelhsanHmida Feb 1, 2026
b32377d
fix: remove max-forecast-horizon default value we set default in reso…
BelhsanHmida Feb 6, 2026
ffbe4f0
fix: add max_forecast_horizon check for retrain_frequency calculation
BelhsanHmida Feb 6, 2026
c46f4d8
feat: set default retrain_frequency to 48 hours when no end date, max…
BelhsanHmida Feb 6, 2026
28e6c04
feat: calculate n_cycles based on end_date and retrain_frequency_in_h…
BelhsanHmida Feb 6, 2026
89c19af
feat: add 'n_cycles' to fields removed in Forecaster class
BelhsanHmida Feb 6, 2026
2dd8b10
feat(test): update max-forecast-horizon and forecast-frequency in tes…
BelhsanHmida Feb 6, 2026
6314f37
feat(test): update max_forecast_horizon and forecast_frequency in tes…
BelhsanHmida Feb 6, 2026
e3bdf9a
feat(test): add test case when end date and retrain_frequency are given
BelhsanHmida Feb 6, 2026
74da913
feat: update description of max_forecast_horizon to reflect dependenc…
BelhsanHmida Feb 6, 2026
8e4619b
fix: update predict_period_in_hours to reflect retrain_freq
BelhsanHmida Feb 6, 2026
16c2c70
feat(test): add n_cycles parameter to test expectations
BelhsanHmida Feb 6, 2026
ff8f37f
style: run pre-commit
BelhsanHmida Feb 6, 2026
00648af
chore: regenerate openapi-specs.json
BelhsanHmida Feb 6, 2026
f8f7045
feat(test): increase test end_date to have two cycles
BelhsanHmida Feb 6, 2026
eb72bf4
Merge branch 'main' into feat/optional-training-start
BelhsanHmida Feb 6, 2026
f53173b
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/opt…
Flix6x Feb 7, 2026
c802394
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/opt…
Flix6x Feb 8, 2026
14842f2
docs(test): add better documentation of expectations for predict and …
BelhsanHmida Feb 10, 2026
ae26709
Update flexmeasures/data/schemas/forecasting/pipeline.py
BelhsanHmida Feb 10, 2026
6763342
Merge branch 'main' into feat/optional-training-start
BelhsanHmida Feb 12, 2026
286f7a4
fix: fix forecast frequency field description to reflect true default
BelhsanHmida Feb 13, 2026
a1a6052
Revert "fix: fix forecast frequency field description to reflect true…
BelhsanHmida Feb 13, 2026
81eee12
fix: fix forecast frequency field description to reflect true default
BelhsanHmida Feb 13, 2026
1d3dda0
feat: validate retrain frequency as a multiple of forecast frequency
BelhsanHmida Feb 13, 2026
a2b048d
fix: suppress complexity warning in validate_parameters method
BelhsanHmida Feb 13, 2026
e659d15
dev: specify schema tests cases
Flix6x Feb 13, 2026
6116133
feat: set default retrain frequency based on planning horizon configu…
BelhsanHmida Feb 13, 2026
965dee6
feat(test): add test cases
BelhsanHmida Feb 13, 2026
a4ec365
style: run pre-commit
BelhsanHmida Feb 13, 2026
c809a52
Refactor/forecasting parameter datakeys (#1953)
BelhsanHmida Feb 14, 2026
93d9a0d
docs: remove "internal" PR from changelog (became part of PR #1917)
Flix6x Feb 14, 2026
8343627
docs: make PR #1917 part of a single changelog entry, which introduce…
Flix6x Feb 14, 2026
427c175
fix: update test input
Flix6x Feb 14, 2026
7e710d6
refactor: move as-job out of the parameters schema
Flix6x Feb 15, 2026
bc2cc20
fix: update test
Flix6x Feb 15, 2026
cfaa3ee
fix: ensure backwards compatibility with DataGenerator subclasses who…
Flix6x Feb 15, 2026
aa38852
fix: pass original instantiated fields instead of re-instantiating them
Flix6x Feb 16, 2026
05aba39
fix: prevent TrainPredictPipeline from catching exceptions and hiding…
Flix6x Feb 16, 2026
04bfee8
fix: prevent empty forecast from being returned silently
Flix6x Feb 16, 2026
5c2ddb8
fix: set load_default for ensure-positive
Flix6x Feb 16, 2026
6c6a561
fix: parameters are already deserialized, so use **kwargs
Flix6x Feb 16, 2026
44f0fe3
refactor: move regressors to config
Flix6x Feb 16, 2026
64ff101
fix: load_default of regressor lists
Flix6x Feb 16, 2026
5f10f79
style: black
Flix6x Feb 16, 2026
4ff6c07
chore: update openapi-specs.json
Flix6x Feb 16, 2026
f7e5888
fix: forecasting trigger schema
Flix6x Feb 16, 2026
1ac2f80
fix: update test comments
Flix6x Feb 16, 2026
5075d6f
refactor: modify data rather than return new dict (this makes it easi…
Flix6x Feb 16, 2026
7a29da0
feat: move missing-threshold to config
Flix6x Feb 16, 2026
089e2c2
feat: move ensure-positive to config
Flix6x Feb 16, 2026
b4a9f39
delete: obsolete fields from parameters post_load
Flix6x Feb 16, 2026
cda755f
feat: check DataGenerator parameters explicitly
Flix6x Feb 16, 2026
8b72aa4
style: flake8; remove obsolete imports
Flix6x Feb 16, 2026
70da821
fix: prevent PredictPipeline from catching exceptions and hiding erro…
Flix6x Feb 16, 2026
ee5a5a0
fix: prevent TrainPipeline from catching exceptions and hiding error …
Flix6x Feb 16, 2026
14dece5
fix: expect the original ValueError instead of the CustomException
Flix6x Feb 16, 2026
f6bcaeb
fix: prevent BasePipeline from catching exceptions and hiding error m…
Flix6x Feb 16, 2026
cf3c76f
feat: use an existing custom error that is more to the point
Flix6x Feb 16, 2026
adad8d2
remove: obsolete exception class and util
Flix6x Feb 16, 2026
f68826f
fix: move over all config fields from CLI options to Forecaster config
Flix6x Feb 16, 2026
54726c4
Merge branch 'main' into feat/optional-training-start
BelhsanHmida Feb 16, 2026
bf7bf94
fix: suppress complexity warning for add_forecast function
BelhsanHmida Feb 16, 2026
4ca1319
Update timing defaults in forecasting schema (#1974)
BelhsanHmida Feb 23, 2026
0409a5e
docs: CLI changelog entry
Flix6x Feb 23, 2026
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
2 changes: 1 addition & 1 deletion documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ v0.31.0 | February XX, 2026
New features
-------------
* Improve CSV upload validation by inferring the intended base resolution even when data contains valid gaps, instead of requiring perfectly regular timestamps [see `PR #1918 <https://www.github.com/FlexMeasures/flexmeasures/pull/1918>`_]
* New forecasting API endpoints `[POST] /sensors/(id)/forecasts/trigger <api/v3_0.html#post--api-v3_0-sensors-id-forecasts-trigger>`_ and `[GET] /sensors/(id)/forecasts/(uuid) <api/v3_0.html#get--api-v3_0-sensors-id-forecasts-uuid>`_ to forecast sensor data [see `PR #1813 <https://www.github.com/FlexMeasures/flexmeasures/pull/1813>`_ and `PR #1823 <https://www.github.com/FlexMeasures/flexmeasures/pull/1823>`_]
* New forecasting API endpoints, and all timing parameters in forecasting CLI got sensible defaults for ease of use `[POST] /sensors/(id)/forecasts/trigger <api/v3_0.html#post--api-v3_0-sensors-id-forecasts-trigger>`_ and `[GET] /sensors/(id)/forecasts/(uuid) <api/v3_0.html#get--api-v3_0-sensors-id-forecasts-uuid>`_ to forecast sensor data [see `PR #1813 <https://www.github.com/FlexMeasures/flexmeasures/pull/1813>`_, `PR #1823 <https://www.github.com/FlexMeasures/flexmeasures/pull/1823>`_ and `PR #1917 <https://www.github.com/FlexMeasures/flexmeasures/pull/1917>`_]
* Support setting a resolution when triggering a schedule via the API or CLI [see `PR #1857 <https://www.github.com/FlexMeasures/flexmeasures/pull/1857>`_]
* Support variable peak pricing and changes in commitment baselines [see `PR #1835 <https://www.github.com/FlexMeasures/flexmeasures/pull/1835>`_]
* Support storing the aggregate power schedule [see `PR #1736 <https://www.github.com/FlexMeasures/flexmeasures/pull/1736>`_]
Expand Down
1 change: 1 addition & 0 deletions documentation/cli/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ since v0.31.0 | February XX, 2026
* Fix ``delete-beliefs`` CLI command ignoring the ``--source`` filter during deletion, preventing unintended removal of beliefs from other sources.
* Let ``flexmeasures add schedule`` create schedules with only information known prior to some time using the ``prior`` option.
* New ``-dry-run`` flag for ``flexmeasures add schedule`` to avoid saving anything (printing out the results instead).
* Streamlines option names for ``flexmeasures add forecasts`` with API usage (preserving backwards compatibility).
* Return validation errors instead of database errors for fields that map to database objects.
* Mutate job state when running ``flexmeasures jobs run-job <job ID>``, including updating metadata and moving between registries
* Add ``flexmeasures jobs stats``, which shows queueing statistics to help evaluate the health of the queueing system.
Expand Down
4 changes: 2 additions & 2 deletions documentation/features/forecasting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ The main CLI parameters that control this process are:
- ``to-date``: The global cutoff point. Training and prediction cycles continue until the ``predict-end`` reaches this date.
- ``max-forecast-horizon``: The maximum length of a forecast into the future.
- ``forecast-frequency``: Determines the number of prediction cycles within the forecast period (e.g. daily, hourly).
- ``start-date``: Define the start of historical data used for training.
- ``train-period``: Define a window of historical data to use for training.

Note that:

``forecast-frequency`` together with ``max-forecast-horizon`` determine how the forecasting cycles advance through time.
``start-date`` / ``from-date`` and ``to-date`` allow precise control over the training and prediction windows in each cycle.
``train-period``, ``from-date`` and ``to-date`` allow precise control over the training and prediction windows in each cycle.

Forecasting via the API
-----------------------
Expand Down
5 changes: 2 additions & 3 deletions documentation/tut/forecasting_scheduling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,8 @@ There are two ways to queue a forecasting job:
.. code-block:: json

{
"start_date": "2025-01-01T00:00:00+00:00",
"start_predict_date": "2025-01-04T00:00:00+00:00",
"end_date": "2025-01-04T04:00:00+00:00"
"start": "2025-01-04T00:00:00+00:00",
"duration": "PT4H"
}

Example response:
Expand Down
5 changes: 5 additions & 0 deletions flexmeasures/api/common/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ def unknown_schedule(message: str) -> ResponseTuple:
return dict(result="Rejected", status="UNKNOWN_SCHEDULE", message=message), 400


@BaseMessage("No known forecast for this time period.")
def unknown_forecast(message: str) -> ResponseTuple:
return dict(result="Rejected", status="UNKNOWN_FORECAST", message=message), 400


def fallback_schedule_redirect(message: str, location: str) -> ResponseTuple:
return (
dict(result="Rejected", status="UNKNOWN_SCHEDULE", message=message),
Expand Down
25 changes: 24 additions & 1 deletion flexmeasures/api/common/schemas/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
from marshmallow import Schema, fields

from flexmeasures.utils.doc_utils import rst_to_openapi
from flexmeasures.data.schemas.forecasting.pipeline import (
ForecastingTriggerSchema,
TrainPredictPipelineConfigSchema,
)
from flexmeasures.data.schemas.sensors import (
SensorReferenceSchema,
VariableQuantityField,
VariableQuantityOpenAPISchema,
)


def make_openapi_compatible(schema_cls: Type[Schema]) -> Type[Schema]:
def make_openapi_compatible(schema_cls: Type[Schema]) -> Type[Schema]: # noqa: C901
"""
Create an OpenAPI-compatible version of a Marshmallow schema.

Expand All @@ -27,6 +31,25 @@ def make_openapi_compatible(schema_cls: Type[Schema]) -> Type[Schema]:
new_fields = {}
for name, field in schema_cls._declared_fields.items():

if schema_cls in (ForecastingTriggerSchema, TrainPredictPipelineConfigSchema):
if "cli" in field.metadata and field.metadata["cli"].get(
"cli-exclusive", False
):
continue
if isinstance(field, fields.Nested):
nested_schema_cls = type(field.schema)
if nested_schema_cls is TrainPredictPipelineConfigSchema:
field_copy = fields.Nested(
make_openapi_compatible(nested_schema_cls),
metadata=field.metadata,
data_key=field.data_key,
many=field.many,
required=field.required,
allow_none=field.allow_none,
)
new_fields[name] = field_copy
continue

# Copy metadata, but sanitize description for OpenAPI
metadata = dict(getattr(field, "metadata", {}))
if "description" in metadata:
Expand Down
6 changes: 5 additions & 1 deletion flexmeasures/api/v3_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
from marshmallow import Schema

from flexmeasures import __version__ as fm_version
from flexmeasures.api.v3_0.sensors import SensorAPI
from flexmeasures.api.v3_0.sensors import (
SensorAPI,
forecasting_trigger_schema_openAPI,
)
from flexmeasures.api.v3_0.accounts import AccountAPI
from flexmeasures.api.v3_0.users import UserAPI
from flexmeasures.api.v3_0.assets import AssetAPI, AssetTypesAPI
Expand Down Expand Up @@ -137,6 +140,7 @@ def create_openapi_specs(app: Flask):
# Explicitly register OpenAPI-compatible schemas
schemas = [
("FlexContextOpenAPISchema", flex_context_schema_openAPI),
("forecasting_trigger_schema_openAPI", forecasting_trigger_schema_openAPI),
("UserAPIQuerySchema", UserAPIQuerySchema),
("AssetAPIQuerySchema", AssetAPIQuerySchema),
("AssetSchema", AssetSchema),
Expand Down
69 changes: 38 additions & 31 deletions flexmeasures/api/v3_0/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
from flexmeasures.api.common.responses import (
request_processed,
unrecognized_event,
unknown_forecast,
unknown_schedule,
unprocessable_entity,
fallback_schedule_redirect,
)
from flexmeasures.api.common.schemas.utils import make_openapi_compatible
from flexmeasures.api.common.utils.validators import (
optional_duration_accepted,
)
Expand Down Expand Up @@ -71,14 +73,27 @@
from flexmeasures.data.models.forecasting import Forecaster
from flexmeasures.data.services.data_sources import get_data_generator
from flexmeasures.data.schemas.forecasting.pipeline import (
ForecasterParametersSchema,
ForecastingTriggerSchema,
)

# Instantiate schemes outside of endpoint logic to minimize response time
sensors_schema = SensorSchema(many=True)
sensor_schema = SensorSchema()
partial_sensor_schema = SensorSchema(partial=True, exclude=["generic_asset_id"])

# Create ForecasterParametersSchema OpenAPI compatible schema
EXCLUDED_FORECASTING_FIELDS = [
# todo: hide these in the config schema instead
# "train_period",
# "max_training_period",
"sensor_to_save",
]
forecasting_trigger_schema_openAPI = make_openapi_compatible(ForecastingTriggerSchema)(
# partial=True,
exclude=EXCLUDED_FORECASTING_FIELDS
+ ["sensor"],
)


class SensorKwargsSchema(Schema):
account = AccountIdField(data_key="account_id", required=False)
Expand Down Expand Up @@ -1519,7 +1534,10 @@ def get_status(self, id, sensor):

@route("/<id>/forecasts/trigger", methods=["POST"])
@use_args(
ForecasterParametersSchema(),
ForecastingTriggerSchema(
# partial=True,
exclude=EXCLUDED_FORECASTING_FIELDS,
),
location="combined_sensor_data_description",
as_kwargs=True,
)
Expand All @@ -1533,9 +1551,8 @@ def trigger_forecast(self, id: int, **params):
description: |
Trigger a forecasting job for a sensor.

This endpoint starts a forecasting job asynchronously and returns a
job UUID. The job will run in the background and generate forecast values
for the specified period.
This endpoint starts a forecasting job asynchronously and returns a job UUID.
The job will run in the background and generate forecasts for the specified period.

Once triggered, the job status and results can be retrieved using the
``GET /api/v3_0/sensors/<id>/forecasts/<uuid>`` endpoint.
Expand All @@ -1554,25 +1571,10 @@ def trigger_forecast(self, id: int, **params):
required: true
content:
application/json:
schema:
type: object
properties:
start_date:
type: string
format: date-time
description: Start date of the historical data used for training.
start_predict_date:
type: string
format: date-time
description: Start date of the forecast period.
end_date:
type: string
format: date-time
description: End date of the forecast period.
schema: forecasting_trigger_schema_openAPI
example:
start_date: "2026-01-01T00:00:00+01:00"
start_predict_date: "2026-01-15T00:00:00+01:00"
end_date: "2026-01-17T00:00:00+01:00"
start: "2026-01-15T00:00:00+01:00"
duration: "P2D"
responses:
200:
description: PROCESSED
Expand Down Expand Up @@ -1611,9 +1613,6 @@ def trigger_forecast(self, id: int, **params):
# Put the sensor to save in the parameters
parameters["sensor"] = params["sensor_to_save"].id

# Ensure the forecast is run as a job on a forecasting queue
parameters["as_job"] = True

# Set forecaster model
model = parameters.pop("model", "TrainPredictPipeline")

Expand All @@ -1622,7 +1621,7 @@ def trigger_forecast(self, id: int, **params):
forecaster = get_data_generator(
source=None,
model=model,
config={},
config=parameters.pop("config", {}),
save_config=True,
data_generator_type=Forecaster,
)
Expand All @@ -1631,7 +1630,7 @@ def trigger_forecast(self, id: int, **params):

# Queue forecasting job
try:
job_id = forecaster.compute(parameters=parameters)
job_id = forecaster.compute(parameters=parameters, as_job=True)
except Exception as e:
current_app.logger.exception("Forecast job failed to enqueue.")
return unprocessable_entity(str(e))
Expand Down Expand Up @@ -1731,6 +1730,8 @@ def get_forecast(self, id: int, uuid: str, sensor: Sensor, job_id: str):
summary: Started forecasting job
value:
status: "STARTED"
400:
description: UNKNOWN_FORECAST
401:
description: UNAUTHORIZED
403:
Expand Down Expand Up @@ -1770,7 +1771,9 @@ def get_forecast(self, id: int, uuid: str, sensor: Sensor, job_id: str):
return dict(**response), s

# Check job status
if not job.is_finished:
if job.is_finished:
message = "A forecasting job has been processed with your job ID"
else:
job_status = job.get_status()
job_status_name = (
job_status.upper() if isinstance(job_status, str) else job_status.name
Expand All @@ -1789,8 +1792,8 @@ def get_forecast(self, id: int, uuid: str, sensor: Sensor, job_id: str):
data_source = get_data_source_for_job(job, type="forecasting")

forecasts = sensor.search_beliefs(
event_starts_after=job.meta.get("start_predict_date"),
event_ends_before=job.meta.get("end_date"),
event_starts_after=job.meta.get("start"),
event_ends_before=job.meta.get("end"),
source=data_source,
most_recent_beliefs_only=True,
use_latest_version_per_event=True,
Expand All @@ -1800,6 +1803,10 @@ def get_forecast(self, id: int, uuid: str, sensor: Sensor, job_id: str):
current_app.logger.exception("Failed to get forecast job status.")
return unprocessable_entity(str(e))

if forecasts.empty:
return unknown_forecast(
f"{message}, but the forecast was not found in the database."
)
start = forecasts["event_start"].min()
last_event_start = forecasts["event_start"].max()

Expand Down
32 changes: 15 additions & 17 deletions flexmeasures/api/v3_0/tests/test_forecasting_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
import isodate
import pytest
from flask import url_for
from flexmeasures.data.services.scheduling import (
get_data_source_for_job,
)

from rq.job import Job
from flexmeasures.utils.job_utils import work_on_rq
from flexmeasures.api.tests.utils import get_auth_token
Expand Down Expand Up @@ -35,18 +33,21 @@ def test_trigger_and_fetch_forecasts(

# Trigger job
payload = {
"start_date": "2025-01-01T00:00:00+00:00",
"start_predict_date": "2025-01-05T00:00:00+00:00",
"end_date": "2025-01-05T02:00:00+00:00",
"max_forecast_horizon": "PT1H",
"retrain_frequency": "PT1H",
"start": "2025-01-05T00:00:00+00:00",
"end": "2025-01-05T02:00:00+00:00",
"max-forecast-horizon": "PT1H",
"forecast-frequency": "PT1H",
"config": {
"train-start": "2025-01-01T00:00:00+00:00",
"retrain-frequency": "PT1H",
},
}

trigger_url = url_for("SensorAPI:trigger_forecast", id=sensor_0.id)
trigger_res = client.post(
trigger_url, json=payload, headers={"Authorization": token}
)
assert trigger_res.status_code == 200
assert trigger_res.status_code == 200, trigger_res.json

trigger_json = trigger_res.get_json()
wrap_up_job = app.queues["forecasting"].fetch_job(trigger_json["forecast"])
Expand Down Expand Up @@ -77,15 +78,15 @@ def test_trigger_and_fetch_forecasts(
payload["sensor"] = sensor_1.id

# Run pipeline manually to compute expected forecasts
pipeline = TrainPredictPipeline()
pipeline = TrainPredictPipeline(config=payload.pop("config", {}))
pipeline.compute(parameters=payload)

# Fetch forecasts for each job
for job_id in job_ids:

fetch_url = url_for("SensorAPI:get_forecast", id=sensor_0.id, uuid=job_id)
res = client.get(fetch_url, headers={"Authorization": token})
assert res.status_code == 200
assert res.status_code == 200, res.json

data = res.get_json()

Expand All @@ -103,14 +104,11 @@ def test_trigger_and_fetch_forecasts(
assert isinstance(api_forecasts, list)
assert len(api_forecasts) > 0

# Identify which data source wrote these beliefs
data_source = get_data_source_for_job(job, type="forecasting")

# Load only the latest belief per event_start
forecasts_df = sensor_1.search_beliefs(
event_starts_after=job.meta.get("start_predict_date"),
event_ends_before=job.meta.get("end_date") + sensor_1.event_resolution,
source=data_source,
event_starts_after=job.meta.get("start"),
event_ends_before=job.meta.get("end"),
source_types=["forecaster"],
most_recent_beliefs_only=True,
use_latest_version_per_event=True,
).reset_index()
Expand Down
Loading