Skip to content
Merged
4 changes: 2 additions & 2 deletions .github/agents/copilot.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The main client class is `FlexMeasuresClient` in `src/flexmeasures_client/client

```bash
pip install -e ".[testing]"
python3 -m pytest tests/test_client.py -q
python3 -m pytest tests/client -q
```

## Linting
Expand Down Expand Up @@ -54,7 +54,7 @@ pip install pre-commit && pre-commit run --all-files
Delegate test writing to the **test-specialist** sub-agent (see `.github/agents/test-specialist.md`).
After the sub-agent completes, **verify yourself** that:

1. All new tests pass: `python3 -m pytest tests/test_client.py -q`
1. All new tests pass: `python3 -m pytest tests/client -q`
2. Linting passes: `black --check src/ tests/ && flake8 src/ tests/`

Do not accept the sub-agent's output at face value — run both checks yourself and iterate if needed.
17 changes: 17 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,23 @@ This can be used to retrieve the schedule, using:
The client will re-try until the schedule is available or the ``MAX_POLLING_STEPS`` of ``10`` is reached.


Forecasting
===========

Trigger a forecast for a sensor and wait for the result:

.. code-block:: python

forecast = await client.trigger_and_get_forecast(
sensor_id=<sensor_id>, # int
duration="PT24H", # ISO duration – how far ahead to forecast
)
# Returns e.g. {"values": [1.2, 1.5, ...], "start": "...", "duration": "PT24H", "unit": "kW"}

The client polls until the forecasting job is complete. For more advanced options
(training window, regressors, forecast frequency, etc.) see :doc:`forecasting`.


Development
==============

Expand Down
147 changes: 147 additions & 0 deletions docs/forecasting.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
.. _forecasting:

Forecasting
===========

The FlexMeasures Client supports the forecasting API endpoints introduced in
FlexMeasures v0.31.0:

- ``POST /sensors/<id>/forecasts/trigger`` — queue a forecasting job
- ``GET /sensors/<id>/forecasts/<uuid>`` — poll for results

These are exposed through three client methods:

- :meth:`trigger_forecast` — trigger and return the job UUID
- :meth:`get_forecast` — poll until results are ready
- :meth:`trigger_and_get_forecast` — convenience wrapper for both

.. note::

These endpoints require a FlexMeasures server of version **0.31.0** or above.


Basic example
-------------

Forecast the next 24 hours for a sensor, using server-side defaults for the
training window:

.. code-block:: python

import asyncio
from flexmeasures_client import FlexMeasuresClient

async def main():
client = FlexMeasuresClient(
host="localhost:5000",
ssl=False,
email="user@example.com",
password="password",
)

forecast = await client.trigger_and_get_forecast(
sensor_id=1,
duration="PT24H",
)
print(forecast)
# e.g. {"values": [1.2, 1.5, 1.8, ...], "start": "...", "duration": "PT24H", "unit": "kW"}

await client.close()

asyncio.run(main())


Specifying a forecast window
-----------------------------

Use ``start`` and ``end`` (or ``start`` and ``duration``) to define the exact
period to forecast:

.. code-block:: python

forecast = await client.trigger_and_get_forecast(
sensor_id=1,
start="2025-01-15T00:00:00+01:00",
end="2025-01-17T00:00:00+01:00",
)


Controlling the training window
---------------------------------

Pass training parameters inside a nested structure via the ``train_start``,
``train_period``, and ``retrain_frequency`` keyword arguments:

.. code-block:: python

forecast = await client.trigger_and_get_forecast(
sensor_id=1,
start="2025-01-15T00:00:00+01:00",
duration="PT48H",
# Training configuration
train_start="2025-01-01T00:00:00+01:00", # historical data start
train_period="P14D", # use 14 days of history
retrain_frequency="PT24H", # retrain every 24 h
)


Using regressors
----------------

You can improve forecast accuracy by supplying regressor sensor IDs:

.. code-block:: python

forecast = await client.trigger_and_get_forecast(
sensor_id=1,
duration="PT24H",
# Sensors whose *forecasts* matter (e.g. weather forecasts)
future_regressors=[10, 11],
# Sensors whose *measurements* matter (e.g. price history)
past_regressors=[20],
)


Step-by-step usage
-------------------

Trigger and retrieve separately to handle the job UUID yourself:

.. code-block:: python

# Step 1 – enqueue the forecasting job
forecast_id = await client.trigger_forecast(
sensor_id=1,
start="2025-01-15T00:00:00+01:00",
end="2025-01-17T00:00:00+01:00",
)
print(f"Job queued: {forecast_id}")

# Step 2 – poll until the job finishes
forecast = await client.get_forecast(
sensor_id=1,
forecast_id=forecast_id,
)
print(forecast)


Polling behaviour
-----------------

``get_forecast`` polls the server with a ``GET`` request and returns when the
server responds with HTTP 200. The polling respects the same client-level
settings as scheduling:

- ``polling_interval`` (default 10 s) — time between retries
- ``polling_timeout`` (default 200 s) — maximum total wait time
- ``max_polling_steps`` (default 10) — maximum number of poll attempts

Override them at client construction time:

.. code-block:: python

client = FlexMeasuresClient(
...,
polling_interval=5.0, # check every 5 seconds
polling_timeout=300.0, # wait up to 5 minutes
)
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Contents
:maxdepth: 2

Overview <readme>
Forecasting <forecasting>
Contributions & Help <contributing>
License <license>
Authors <authors>
Expand Down
Loading