Skip to content

Commit 41e6102

Browse files
CopilotFlix6x
andauthored
Support new forecasting endpoints (/sensors/{id}/forecasts/trigger and /sensors/{id}/forecasts/{uuid}) (#180)
* Initial plan * Add trigger_forecast, get_forecast, and trigger_and_get_forecast methods Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com> * Add tests for trigger_forecast, get_forecast, and trigger_and_get_forecast Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com> * Fix black linting issue and add copilot agent instructions Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com> * Add forecasting examples to README and docs/forecasting.rst; simplify copilot agent instructions Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com> * fix: update agent path to client tests Signed-off-by: F.N. Claessen <claessen@seita.nl> * refactor: move test module Signed-off-by: F.N. Claessen <claessen@seita.nl> * fix: missing __init__.py Signed-off-by: F.N. Claessen <claessen@seita.nl> --------- Signed-off-by: F.N. Claessen <claessen@seita.nl> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Flix6x <30658763+Flix6x@users.noreply.github.com> Co-authored-by: F.N. Claessen <claessen@seita.nl>
1 parent 27e1640 commit 41e6102

7 files changed

Lines changed: 564 additions & 2 deletions

File tree

.github/agents/copilot.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ The main client class is `FlexMeasuresClient` in `src/flexmeasures_client/client
2020

2121
```bash
2222
pip install -e ".[testing]"
23-
python3 -m pytest tests/test_client.py -q
23+
python3 -m pytest tests/client -q
2424
```
2525

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

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

6060
Do not accept the sub-agent's output at face value — run both checks yourself and iterate if needed.

README.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,23 @@ This can be used to retrieve the schedule, using:
242242
The client will re-try until the schedule is available or the ``MAX_POLLING_STEPS`` of ``10`` is reached.
243243

244244

245+
Forecasting
246+
===========
247+
248+
Trigger a forecast for a sensor and wait for the result:
249+
250+
.. code-block:: python
251+
252+
forecast = await client.trigger_and_get_forecast(
253+
sensor_id=<sensor_id>, # int
254+
duration="PT24H", # ISO duration – how far ahead to forecast
255+
)
256+
# Returns e.g. {"values": [1.2, 1.5, ...], "start": "...", "duration": "PT24H", "unit": "kW"}
257+
258+
The client polls until the forecasting job is complete. For more advanced options
259+
(training window, regressors, forecast frequency, etc.) see :doc:`forecasting`.
260+
261+
245262
Development
246263
==============
247264

docs/forecasting.rst

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
.. _forecasting:
2+
3+
Forecasting
4+
===========
5+
6+
The FlexMeasures Client supports the forecasting API endpoints introduced in
7+
FlexMeasures v0.31.0:
8+
9+
- ``POST /sensors/<id>/forecasts/trigger`` — queue a forecasting job
10+
- ``GET /sensors/<id>/forecasts/<uuid>`` — poll for results
11+
12+
These are exposed through three client methods:
13+
14+
- :meth:`trigger_forecast` — trigger and return the job UUID
15+
- :meth:`get_forecast` — poll until results are ready
16+
- :meth:`trigger_and_get_forecast` — convenience wrapper for both
17+
18+
.. note::
19+
20+
These endpoints require a FlexMeasures server of version **0.31.0** or above.
21+
22+
23+
Basic example
24+
-------------
25+
26+
Forecast the next 24 hours for a sensor, using server-side defaults for the
27+
training window:
28+
29+
.. code-block:: python
30+
31+
import asyncio
32+
from flexmeasures_client import FlexMeasuresClient
33+
34+
async def main():
35+
client = FlexMeasuresClient(
36+
host="localhost:5000",
37+
ssl=False,
38+
email="user@example.com",
39+
password="password",
40+
)
41+
42+
forecast = await client.trigger_and_get_forecast(
43+
sensor_id=1,
44+
duration="PT24H",
45+
)
46+
print(forecast)
47+
# e.g. {"values": [1.2, 1.5, 1.8, ...], "start": "...", "duration": "PT24H", "unit": "kW"}
48+
49+
await client.close()
50+
51+
asyncio.run(main())
52+
53+
54+
Specifying a forecast window
55+
-----------------------------
56+
57+
Use ``start`` and ``end`` (or ``start`` and ``duration``) to define the exact
58+
period to forecast:
59+
60+
.. code-block:: python
61+
62+
forecast = await client.trigger_and_get_forecast(
63+
sensor_id=1,
64+
start="2025-01-15T00:00:00+01:00",
65+
end="2025-01-17T00:00:00+01:00",
66+
)
67+
68+
69+
Controlling the training window
70+
---------------------------------
71+
72+
Pass training parameters inside a nested structure via the ``train_start``,
73+
``train_period``, and ``retrain_frequency`` keyword arguments:
74+
75+
.. code-block:: python
76+
77+
forecast = await client.trigger_and_get_forecast(
78+
sensor_id=1,
79+
start="2025-01-15T00:00:00+01:00",
80+
duration="PT48H",
81+
# Training configuration
82+
train_start="2025-01-01T00:00:00+01:00", # historical data start
83+
train_period="P14D", # use 14 days of history
84+
retrain_frequency="PT24H", # retrain every 24 h
85+
)
86+
87+
88+
Using regressors
89+
----------------
90+
91+
You can improve forecast accuracy by supplying regressor sensor IDs:
92+
93+
.. code-block:: python
94+
95+
forecast = await client.trigger_and_get_forecast(
96+
sensor_id=1,
97+
duration="PT24H",
98+
# Sensors whose *forecasts* matter (e.g. weather forecasts)
99+
future_regressors=[10, 11],
100+
# Sensors whose *measurements* matter (e.g. price history)
101+
past_regressors=[20],
102+
)
103+
104+
105+
Step-by-step usage
106+
-------------------
107+
108+
Trigger and retrieve separately to handle the job UUID yourself:
109+
110+
.. code-block:: python
111+
112+
# Step 1 – enqueue the forecasting job
113+
forecast_id = await client.trigger_forecast(
114+
sensor_id=1,
115+
start="2025-01-15T00:00:00+01:00",
116+
end="2025-01-17T00:00:00+01:00",
117+
)
118+
print(f"Job queued: {forecast_id}")
119+
120+
# Step 2 – poll until the job finishes
121+
forecast = await client.get_forecast(
122+
sensor_id=1,
123+
forecast_id=forecast_id,
124+
)
125+
print(forecast)
126+
127+
128+
Polling behaviour
129+
-----------------
130+
131+
``get_forecast`` polls the server with a ``GET`` request and returns when the
132+
server responds with HTTP 200. The polling respects the same client-level
133+
settings as scheduling:
134+
135+
- ``polling_interval`` (default 10 s) — time between retries
136+
- ``polling_timeout`` (default 200 s) — maximum total wait time
137+
- ``max_polling_steps`` (default 10) — maximum number of poll attempts
138+
139+
Override them at client construction time:
140+
141+
.. code-block:: python
142+
143+
client = FlexMeasuresClient(
144+
...,
145+
polling_interval=5.0, # check every 5 seconds
146+
polling_timeout=300.0, # wait up to 5 minutes
147+
)

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Contents
3030
:maxdepth: 2
3131

3232
Overview <readme>
33+
Forecasting <forecasting>
3334
Contributions & Help <contributing>
3435
License <license>
3536
Authors <authors>

0 commit comments

Comments
 (0)