Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
58d8bb7
feat: relax storage SoC bounds by default
BelhsanHmida Jun 25, 2026
2944dac
feat: remove storage fallback scheduler
BelhsanHmida Jun 25, 2026
09e9998
feat: remove storage fallback policy helper
BelhsanHmida Jun 25, 2026
11eeb37
test: cover SoC relaxation schema defaults
BelhsanHmida Jun 25, 2026
0fd1487
test: use default SoC breach prices
BelhsanHmida Jun 25, 2026
18aa13b
test: expect storage infeasibility without fallback
BelhsanHmida Jun 25, 2026
783fd8b
test: assert storage schedules do not fall back
BelhsanHmida Jun 25, 2026
48114c1
test: update sequential scheduling fallback case
BelhsanHmida Jun 25, 2026
9d90ff9
docs: describe default SoC relaxation metadata
BelhsanHmida Jun 25, 2026
ed23342
docs: update storage scheduling infeasibility guide
BelhsanHmida Jun 25, 2026
6483a52
docs: clarify fallback redirects for custom schedulers
BelhsanHmida Jun 25, 2026
2191013
docs: scope fallback redirect configuration
BelhsanHmida Jun 25, 2026
c08ba18
docs: refresh SoC relaxation OpenAPI text
BelhsanHmida Jun 25, 2026
7135f33
docs: add fallback scheduler changelog entry
BelhsanHmida Jun 26, 2026
429d639
test: allow small unit conversion drift
BelhsanHmida Jun 26, 2026
aedfa12
test: avoid exact quantity string comparisons
BelhsanHmida Jun 26, 2026
3a68fa7
docs: clarify storage infeasibility behavior
BelhsanHmida Jun 28, 2026
79241ee
test: simplify storage fallback assertion comment
BelhsanHmida Jun 28, 2026
0f805b8
docs: regenerate openapi-specs.json
BelhsanHmida Jun 28, 2026
eaa4a7a
fix: preserve explicit zero breach prices
BelhsanHmida Jul 5, 2026
0468c9b
Merge branch 'main' into feat/retire-fallback-scheduler
BelhsanHmida Jul 5, 2026
492519a
feat: support sensor reference defaults
BelhsanHmida Jul 6, 2026
07f0963
test: cover sensor reference defaults
BelhsanHmida Jul 6, 2026
2711f81
feat: accept dynamic canonical soc bounds
BelhsanHmida Jul 6, 2026
bf315d2
test: cover dynamic canonical soc bounds
BelhsanHmida Jul 6, 2026
0291546
docs: update soc bounds schema metadata
BelhsanHmida Jul 6, 2026
08bf214
docs: update soc bounds api examples
BelhsanHmida Jul 6, 2026
ec2fb40
docs: explain canonical dynamic soc bounds
BelhsanHmida Jul 6, 2026
16f8c53
docs: regenerate openapi specs
BelhsanHmida Jul 6, 2026
c9fe6d6
fix: narrow tuple quantity parsing errors
BelhsanHmida Jul 6, 2026
b8d96a0
fix: clarify variable quantity type errors
BelhsanHmida Jul 6, 2026
e6a24eb
fix: update variable quantity deserialize typing
BelhsanHmida Jul 6, 2026
7e226b4
fix: allow null sensor reference defaults
BelhsanHmida Jul 6, 2026
3c82f2d
refactor: remove ineffective storage schema fields
BelhsanHmida Jul 6, 2026
e20c2cf
fix: handle malformed quantity tuples
BelhsanHmida Jul 6, 2026
7371674
docs: add changelog entry
BelhsanHmida Jul 6, 2026
73465ac
Merge remote-tracking branch 'origin/main' into feat/retire-fallback-…
BelhsanHmida Jul 9, 2026
ed9e107
fix: reject null sensor reference defaults
BelhsanHmida Jul 10, 2026
a0410a3
fix: relax scalar soc bounds by default
BelhsanHmida Jul 10, 2026
fb06d92
docs: clarify sensor defaults and soc relaxation
BelhsanHmida Jul 10, 2026
6874c64
test: preserve hard soc bound expectations
BelhsanHmida Jul 10, 2026
779d61e
feat: support sensor reference fallbacks
BelhsanHmida Jul 10, 2026
3db0f87
docs: clarify canonical soc bounds
BelhsanHmida Jul 10, 2026
49ec4a0
docs: explain canonical soc routing
BelhsanHmida Jul 10, 2026
b7955bb
test: preserve hard soc scheduling scenarios
BelhsanHmida Jul 10, 2026
b0aa05d
Merge branch 'main' into feat/retire-fallback-scheduler
BelhsanHmida Jul 13, 2026
468bdd9
chore: align agent instructions with main
BelhsanHmida Jul 13, 2026
919e28d
docs: fix jobs OpenAPI description indentation
BelhsanHmida Jul 13, 2026
73f828f
Merge remote-tracking branch 'origin/feat/retire-fallback-scheduler' …
BelhsanHmida Jul 13, 2026
f17a7c6
fix: honor explicit soc relaxation disablement
BelhsanHmida Jul 13, 2026
4f8ce42
fix: skip soc relaxation without initial state
BelhsanHmida Jul 13, 2026
2defb6f
test: match infeasible validation error
BelhsanHmida Jul 13, 2026
5918dc2
Merge branch 'main' into feat/retire-fallback-scheduler
BelhsanHmida Jul 17, 2026
f354561
fix(schema): let relax-constraints=false keep SoC constraints hard
BelhsanHmida Jul 17, 2026
1bafc44
docs: restore hard-constraints list and describe the SoC relaxation o…
BelhsanHmida Jul 17, 2026
bf2faf3
test: restore config via monkeypatch and fix truncated docstring
BelhsanHmida Jul 17, 2026
69cadd3
Merge branch 'feat/retire-fallback-scheduler' into feat/dynamic-soc-b…
BelhsanHmida Jul 18, 2026
3e0c6ae
fix: complete the merge reconciliation left out of the previous commit
BelhsanHmida Jul 18, 2026
b932527
docs+test: fix review follow-ups for relax-soc-constraints default
BelhsanHmida Jul 22, 2026
8dd90b6
fix+docs: review follow-ups for canonical soc-min/soc-max
BelhsanHmida Jul 22, 2026
0aefa2b
Merge branch 'main' into feat/retire-fallback-scheduler
BelhsanHmida Jul 22, 2026
90920ec
Merge branch 'feat/retire-fallback-scheduler' (incl. main) into feat/…
BelhsanHmida Jul 22, 2026
d1e3f27
Merge branch 'main' into feat/retire-fallback-scheduler
BelhsanHmida Aug 5, 2026
9cf7add
Merge branch 'feat/retire-fallback-scheduler' into feat/dynamic-soc-b…
BelhsanHmida Aug 5, 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: 2 additions & 0 deletions documentation/api/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ v3.0-32 | July XX, 2026
* Legacy ``schedule`` field (in scheduling endpoints) and ``forecast`` field (in forecasting endpoints) remain in responses, unchanged, for backward compatibility. New clients should prefer ``job``; see :ref:`api_background_jobs` for the full response format. These fields are not (yet) formally deprecated — see the "Planned API v4" discussion linked from :ref:`api_deprecation` for where and when their removal is being tracked.
- ``GET /api/v3_0/jobs/<uuid>`` now returns ``202 Accepted`` while a job is queued or running, ``422 Unprocessable Entity`` for failed jobs, and ``200 OK`` for finished jobs. See :ref:`api_background_jobs` for the response format and polling flow.
- ``GET /api/v3_0/jobs/<uuid>`` now also returns kebab-case metadata fields such as ``func-name`` and ``enqueued-at``, alongside the existing snake_case fields (``func_name``, ``enqueued_at``, etc.), which remain unchanged for backward compatibility. New clients should prefer the kebab-case fields.
- Breaking: ``relax-soc-constraints`` now defaults to ``True`` (set it or ``relax-constraints`` to ``False`` to keep ``soc-minima``/``soc-maxima`` hard), and the built-in storage fallback scheduler has been retired. ``GET /api/v3_0/sensors/<id>/schedules/<uuid>`` now returns ``400`` with the failure reason for an infeasible storage schedule instead of a ``303`` redirect to (or an automatic follow of) a fallback schedule. ``FLEXMEASURES_FALLBACK_REDIRECT`` remains relevant only for custom schedulers that still define a fallback scheduler.
- ``soc-min`` and ``soc-max`` in the storage flex-model are now canonical: besides a fixed quantity, they also accept a sensor reference or time series, making them dynamic storage SoC boundaries. Sensor references (on any field that accepts them) may now include a ``default`` fallback quantity to fill time slots where the referenced sensor has no value. ``soc-minima`` and ``soc-maxima`` remain supported as deprecated legacy aliases, and a fixed ``soc-min``/``soc-max`` now follows the default SoC relaxation behavior instead of always being a hard constraint.
- Added a ``group`` field to the storage flex-model, accepted by the `/assets/(id)/schedules/trigger <../api/v3_0.html#post--api-v3_0-assets-id-schedules-trigger>`_ (POST) endpoint, referencing a power sensor representing a group of devices (e.g. a shared inverter or feeder). The group's ``power-capacity`` is enforced as a hard constraint on the group's aggregate power, while its ``consumption-capacity``/``production-capacity`` are enforced as soft constraints with default breach prices; the group's scheduled aggregate power is saved to the group sensor.
- The ``group`` field also accepts a ``{"asset": <id>}`` reference (besides ``{"sensor": <id>}``), pointing at an asset whose own (DB-stored) flex-model defines the group's constraints. Such a group defines no power sensor of its own; its aggregate schedule is instead saved via its ``consumption``/``production`` output sensor references, following the same conventions as any other asset-only flex-model entry. This lets the entire flex-model for a device tree (including groups) live in the DB, with ``flex-model`` omitted or empty on the trigger request.
- Fixed: `/assets/(id)/schedules/trigger <../api/v3_0.html#post--api-v3_0-assets-id-schedules-trigger>`_ (POST) now also accepts the legacy ``force_new_job_creation`` field name (in addition to ``force-new-job-creation``), matching the sensor-level trigger endpoint. Previously, only the sensor-level endpoint accepted both spellings.
Expand Down
11 changes: 6 additions & 5 deletions documentation/api/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,19 @@ See Other (303)
---------------

Some API responses return ``HTTP status 303 (See Other)`` to redirect the client to a different resource.
This happens, for example, when a scheduling job fails and a fallback schedule has been computed instead.
This can happen when a custom scheduler defines a fallback scheduler, the original scheduling job fails, and the fallback schedule has been computed instead.
In that case, the response includes a ``Location`` header pointing to the fallback schedule endpoint, so clients can automatically retrieve the fallback result.

The response body will contain a JSON message with a ``status`` field set to ``"UNKNOWN_SCHEDULE"`` and a ``message`` field explaining the reason for the redirect.

.. note::

The fallback schedule mechanism activates when the main scheduler encounters an infeasible problem (i.e. when constraints cannot be satisfied).
This is less likely to happen when ``"relax-constraints": true`` is set in the ``flex-context``, as constraint relaxation softens most infeasibility-causing constraints.
The hard constraints that remain even after constraint relaxation are ``soc-min``, ``soc-max``, ``soc-targets`` and ``power-capacity`` in the ``flex-model``, and ``site-power-capacity`` in the ``flex-context``.
FlexMeasures' built-in storage scheduler no longer computes a fallback schedule for infeasible problems.
Instead, ``soc-min`` and ``soc-max`` boundaries, including the legacy aliases ``soc-minima`` and ``soc-maxima``, are relaxed by default through ``"relax-soc-constraints": true`` (setting either it or ``"relax-constraints"`` to ``false`` keeps them hard).
The hard constraints that remain even after constraint relaxation are ``soc-targets`` and ``power-capacity`` in the ``flex-model``, and ``site-power-capacity`` in the ``flex-context``.
If hard constraints cannot be satisfied, the scheduling job fails and clients receive the failure reason when requesting the schedule.
Comment thread
BelhsanHmida marked this conversation as resolved.

Server administrators can configure whether clients receive a 303 redirect (``FLEXMEASURES_FALLBACK_REDIRECT = True``) or whether FlexMeasures follows the fallback automatically and returns the fallback schedule directly (``FLEXMEASURES_FALLBACK_REDIRECT = False``, the default).
For custom schedulers that still define a fallback scheduler, server administrators can configure whether clients receive a 303 redirect (``FLEXMEASURES_FALLBACK_REDIRECT = True``) or whether FlexMeasures follows the fallback automatically and returns the fallback schedule directly (``FLEXMEASURES_FALLBACK_REDIRECT = False``, the default).

Here is a client-side code example in Python for handling 303 redirects (this merely follows the redirect and should be revised to make use of the client's monitoring tools):

Expand Down
2 changes: 2 additions & 0 deletions documentation/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ New features
* Commodity contexts that omit grid-connection fields (prices and site capacities) now get smart defaults instead of failing or silently leaving the grid unconstrained — for instance, a bare ``{"commodity": "gas"}`` is treated as having no grid connection; see :ref:`commodity_context_defaults` for the full rules [see `PR #2272 <https://www.github.com/FlexMeasures/flexmeasures/pull/2272>`_]
* CLI support for adding/editing account attributes [see `PR #2242 <https://www.github.com/FlexMeasures/flexmeasures/pull/2242>`_]
* Improve chart axis domain for event values not around zero, with a per-sub-chart ``y-axis`` option in ``sensors_to_show`` (default ``zero``, which pads the axis out to include zero) that can be set to ``data`` to fit a sub-chart's y-axis to the values shown, to an explicit ``[min, max]`` domain that the axis will cover at least (expanding to fit data beyond it), or to a strict ``{"min": min, "max": max}`` domain that the axis will never exceed (clamping data beyond it, with a warning when that happens), editable from the graph editor [see `PR #2244 <https://www.github.com/FlexMeasures/flexmeasures/pull/2244>`_]
* Breaking behaviour change: storage SoC constraints (``soc-minima``/``soc-maxima``) are now relaxed by default (``relax-soc-constraints`` defaults to ``True``; set it or ``relax-constraints`` to ``False`` to keep them hard), and the built-in storage fallback scheduler has been retired, so infeasible storage problems now fail with their failure reason instead of silently saving a fallback schedule; ``FLEXMEASURES_FALLBACK_REDIRECT`` is now only relevant for custom schedulers that define a fallback scheduler [see `PR #2252 <https://www.github.com/FlexMeasures/flexmeasures/pull/2252>`_]
* Support defining ``default`` fallbacks on sensor references, and support dynamic storage ``soc-min`` and ``soc-max`` boundaries with sensor references or time series; ``soc-minima`` and ``soc-maxima`` remain supported as legacy aliases, and scalar ``soc-min`` / ``soc-max`` follow the default SoC relaxation behavior [see `PR #2267 <https://www.github.com/FlexMeasures/flexmeasures/pull/2267>`_]
* Extended ``GET /api/v3_0/jobs/<uuid>`` with a ``result`` field containing ``unresolved`` and ``resolved`` soft state-of-charge constraint analysis (``soc-minima``/``soc-maxima`` violations or satisfied constraints, keyed by asset ID) for scheduling jobs; both arrays are empty when no SoC constraints were defined [see `PR #2072 <https://www.github.com/FlexMeasures/flexmeasures/pull/2072>`_]
* New storage flex-model field ``operation-modes`` confines a device's power to one of several power bands, following the S2 standard's operation modes — for example, a device that is either off or running at one fixed power [see `PR #2278 <https://www.github.com/FlexMeasures/flexmeasures/pull/2278>`_]
* New ``FLEXMEASURES_LP_SOLVER_OPTIONS`` config setting to pass solver options to the scheduling solver, validated against the installed HiGHS build so that unknown or unsupported options raise instead of being silently ignored [see `PR #2283 <https://www.github.com/FlexMeasures/flexmeasures/pull/2283>`_]
Expand Down
6 changes: 3 additions & 3 deletions documentation/concepts/commitments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Typical translations include:

- tariffs (``consumption-price``, ``production-price``) → an ``"energy"`` FlowCommitment with zero baseline so net consumption/production is priced;
- peak/excess limits (``site-peak-production``, ``site-peak-production-price``, etc.) → dedicated peak FlowCommitment(s);
- storage-related fields (``soc-minima``, ``soc-minima-breach-price``, etc.) → StockCommitment(s).
- storage-related fields (``soc-min``, ``soc-max``, legacy ``soc-minima`` and ``soc-maxima``, and the SoC breach prices) → StockCommitment(s).


Let us look at some concrete examples.
Expand Down Expand Up @@ -179,9 +179,9 @@ commitments the scheduler constructs.

6. **SOC minima / maxima (storage preferences)**

- *Fields used*: ``soc-minima``, ``soc-minima-breach-price``, ``soc-maxima`` and ``soc-maxima-breach-price``.
- *Fields used*: ``soc-min``, ``soc-minima``, ``soc-minima-breach-price``, ``soc-max``, ``soc-maxima`` and ``soc-maxima-breach-price``.
- *Commitment*: StockCommitment(s) that price deviations below minima or
above maxima. Hard storage capacities are set through ``soc-min`` and ``soc-max`` instead and are modelled as Pyomo constraints.
above maxima. Set ``relax-soc-constraints`` to ``False`` to keep these SoC bounds as hard Pyomo constraints instead.

7. **Power bands per device**

Expand Down
4 changes: 3 additions & 1 deletion documentation/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,9 @@ Default: ``None`` (defaults are set internally for each sunset API version, e.g.
FLEXMEASURES_FALLBACK_REDIRECT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Control how the API handles a failed scheduling job when a fallback schedule has been computed.
Control how the API handles a failed scheduling job when a custom scheduler has computed a fallback schedule.

FlexMeasures' built-in storage scheduler no longer computes fallback schedules, but custom schedulers may still define fallback schedulers.

If ``True``, the API returns ``HTTP status 303 (See Other)`` with a ``Location`` header pointing to the fallback schedule endpoint.
Clients must follow this redirect themselves to obtain the fallback schedule (see :ref:`api_see_other`).
Expand Down
27 changes: 13 additions & 14 deletions documentation/features/scheduling.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.. _scheduling:

Scheduling
Scheduling
===========

Scheduling is the main value-drive of FlexMeasures. We have two major types of schedulers built-in, for storage devices (usually batteries or hot water storage) and processes (usually in industry).
Expand Down Expand Up @@ -387,19 +387,20 @@ If you model devices that *buffer* energy (e.g. thermal energy storage systems c
However, here are some tips to model a buffer correctly:

- Describe the thermal energy content in kWh or MWh.
- Set ``soc-minima`` to the accumulative usage forecast.
- Set dynamic ``soc-min`` values to the accumulative usage forecast.
- Set ``charging-efficiency`` to the sensor describing the :abbr:`COP (coefficient of performance)` values.
- Set ``storage-efficiency`` to a value below 100% to model (heat) loss.

For a hands-on example of a heat buffer fed by multiple devices, see :ref:`tut_multi_feed_storage`.

What happens if the flex model describes an infeasible problem for the storage scheduler? Excellent question!
It is highly important for a robust operation that these situations still lead to a somewhat good outcome.
From our practical experience, we derived a ``StorageFallbackScheduler``.
It simplifies an infeasible situation by just starting to charge, discharge, or do neither,
depending on the first target state of charge and the capabilities of the asset.
If the flex model describes an infeasible problem for the storage scheduler, the failure should remain visible.
By default, ``soc-min`` and ``soc-max`` boundaries are relaxed into soft constraints, so the scheduler can still return a useful schedule when these boundaries cannot be fully met.
The legacy ``soc-minima`` and ``soc-maxima`` aliases follow the same behavior.
Setting either ``relax-soc-constraints`` or ``relax-constraints`` to ``false`` in the flex-context keeps them as hard constraints.
Exact ``soc-targets``, ``power-capacity`` (in the flex-model), and ``site-power-capacity`` (in the flex-context) remain hard constraints.
If those hard constraints make the problem infeasible, the scheduling job fails instead of producing a fallback schedule.

Of course, we also log a failure in the scheduling job, so it's important to take note of these failures. Often, mis-configured flex models are the reason.
It is important to take note of these failures. Often, misconfigured flex models are the reason.

For a hands-on tutorial on using some of the storage flex-model fields, head over to :ref:`tut_v2g` use case and `the API documentation for triggering schedules <../api/v3_0.html#post--api-v3_0-assets-id-schedules-trigger>`_.
For further hands-on examples, see :ref:`tut_multi_feed_storage` (multiple devices feeding one shared storage) and :ref:`tut_multi_commodity` (devices on different commodities scheduled together).
Expand All @@ -417,15 +418,15 @@ Some examples from practice (usually industry) could be:

- A centrifuge's daily work of combing through sludge water. Depends on amount of sludge present.
- Production processes with a target amount of output until the end of the current shift. The target usually comes out of production planning.
- Application of coating under hot temperature, with fixed number of times it needs to happen before some deadline.
- Application of coating under hot temperature, with fixed number of times it needs to happen before some deadline.

.. list-table::
:header-rows: 1
:widths: 20 25 90

* - Field
- Example value
- Description
- Description
* - ``power``
- ``"15kW"``
- Nominal power of the load.
Expand All @@ -436,7 +437,7 @@ Some examples from practice (usually industry) could be:
- ``"MAX"``
- Objective of the scheduler, to maximize (``"MAX"``) or minimize (``"MIN"``).
* - ``time_restrictions``
- ``[{"start": "2015-01-02T08:00:00+01:00", "duration": "PT2H"}]``
- ``[{"start": "2015-01-02T08:00:00+01:00", "duration": "PT2H"}]``
- Time periods in which the load cannot be scheduled to run.
* - ``process_type``
- ``"INFLEXIBLE"``, ``"SHIFTABLE"`` or ``"BREAKABLE"``
Expand Down Expand Up @@ -686,5 +687,3 @@ Here are some thoughts on further innovation:
This is ongoing architecture design work, and therefore happens in development settings, until we are happy with the outcomes.
Thoughts welcome :)
- Aggregating flexibility of a group of assets (e.g. a neighborhood) and optimizing its aggregated usage (e.g. for grid congestion support) is also an exciting direction for expansion.


Loading
Loading