Skip to content
Draft
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
* Support forecasting from a given time in the past, by allowing to specify a ``prior`` belief time in the forecasting API endpoint (as already possible with CLI command) [see `PR #1978 <https://www.github.com/FlexMeasures/flexmeasures/pull/1978>`_]
* Show sensor attributes on sensor page, if not empty [see `PR #2015 <https://www.github.com/FlexMeasures/flexmeasures/pull/2015>`_]
* Separate the ``StorageScheduler``'s tie-breaking preference for a full :abbr:`SoC (state of charge)` from its reported energy costs [see `PR #2023 <https://www.github.com/FlexMeasures/flexmeasures/pull/2023>`_]
* The schedule API endpoint now returns a ``scheduling_result`` field alongside ``scheduler_info``. For the first unmet ``soc-minima`` or ``soc-maxima`` constraint it reports the violation datetime and the signed delta (scheduled SoC minus target value). Note that ``soc-targets`` are hard constraints and are never reported here [see `PR #2072 <https://www.github.com/FlexMeasures/flexmeasures/pull/2072>`_]
* Improve asset audit log messages for JSON field edits (especially ``sensors_to_show`` and nested flex-config values) [see `PR #2055 <https://www.github.com/FlexMeasures/flexmeasures/pull/2055>`_]

Infrastructure / Support
Expand Down
40 changes: 40 additions & 0 deletions documentation/features/scheduling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,46 @@ You can add new shiftable-process schedules with the CLI command ``flexmeasures
.. note:: Currently, the ``ProcessScheduler`` uses only the ``consumption-price`` field of the flex-context, so it ignores any site capacities and inflexible devices.


The schedule
------------

A schedule produced by FlexMeasures is a series of power values for each flexible device (represented by its power sensor), covering the scheduling window at the scheduling resolution.

Besides the power values themselves, FlexMeasures also returns additional scheduling metadata in a ``scheduling_result`` field. This field is populated when the device has a ``state-of-charge`` sensor configured (via the ``state-of-charge`` field in the flex model).

**Unresolved targets** (``unresolved_targets``)

The ``unresolved_targets`` field lists soft SoC constraints (``soc-minima`` and/or ``soc-maxima``) that could *not* be satisfied, keyed by state-of-charge sensor ID. For each violated constraint type, it reports:

- ``"datetime"``: the ISO 8601 UTC timestamp of the first violation.
- ``"unmet"``: the magnitude of the violation in kWh (always positive).
For ``soc-minima`` this is the shortage (SoC fell short by this amount);
for ``soc-maxima`` this is the excess (SoC exceeded the target by this amount).

An empty ``{}`` means all constraints of that type were satisfied (or none were defined).

*Example use case*: for EV charging, if the battery could not be fully charged for a planned trip, the ``unresolved_targets`` field will report how much charge is missing. The fleet operator can then plan to use public charge points to make up the difference.

**Resolved targets** (``resolved_targets``)

The ``resolved_targets`` field lists soft SoC constraints that *were* satisfied, keyed by state-of-charge sensor ID. For each met constraint type, it reports the tightest (smallest-margin) slot:

- ``"datetime"``: the ISO 8601 UTC timestamp of the tightest constraint slot.
- ``"margin"``: the headroom available at that slot in kWh (always positive).
For ``soc-minima`` this is how far above the minimum the SoC was;
for ``soc-maxima`` this is how far below the maximum the SoC was.

An empty ``{}`` means no constraints of that type were defined.

.. note:: Setting a ``state-of-charge`` sensor on the device is required to populate
``unresolved_targets`` and ``resolved_targets``. Configure it via the
``state-of-charge`` field in the device's flex model, e.g.
``"state-of-charge": {"sensor": <sensor_id>}``. See the flex-model
documentation for the StorageScheduler for details.

For full technical details of the response schema, refer to the API endpoint documentation for ``GET /sensors/<id>/schedules/<uuid>``.


Work on other schedulers
--------------------------

Expand Down
57 changes: 56 additions & 1 deletion flexmeasures/api/v3_0/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
create_scheduling_job,
get_data_source_for_job,
)
from flexmeasures.data.models.planning.storage import SCHEDULING_RESULT_KEY
from flexmeasures.utils.time_utils import duration_isoformat
from flexmeasures.utils.flexmeasures_inflection import join_words_into_a_list
from flexmeasures.utils.unit_utils import convert_units
Expand Down Expand Up @@ -934,6 +935,47 @@ def get_schedule( # noqa: C901
description: Information about the scheduler that executed the job.
additionalProperties: true

scheduling_result:
type: object
description: |
Additional results produced by the scheduler.
This field is left out for jobs created before this field was introduced.

Requires a ``state-of-charge`` sensor to be set on the device.

The ``unresolved_targets`` field lists soft SoC constraints that could
not be satisfied, keyed by state-of-charge sensor ID string.
An empty ``{}`` means all targets were met (or no constraints were
defined).

Each per-sensor entry may have ``"soc-minima"`` and/or ``"soc-maxima"``
sub-keys (only present when a violation exists), each with:

- ``"datetime"``: ISO 8601 UTC timestamp of the first violation.
- ``"unmet"``: Always-positive shortage/excess in kWh, e.g.
``"260.0 kWh"``. For ``soc-minima`` this is the shortage (SoC fell
short by this amount); for ``soc-maxima`` this is the excess (SoC
exceeded the target by this amount).

The ``resolved_targets`` field lists soft SoC constraints that WERE
satisfied, keyed by state-of-charge sensor ID string.
An empty ``{}`` means no constraints of that type were defined.

Each per-sensor entry may have ``"soc-minima"`` and/or ``"soc-maxima"``
sub-keys (only present when the constraint type was defined and met),
each with:

- ``"datetime"``: ISO 8601 UTC timestamp of the tightest constraint
slot (smallest positive margin).
- ``"margin"``: Always-positive headroom in kWh, e.g. ``"40.0 kWh"``.
For ``soc-minima`` this is how far above the minimum the SoC was;
for ``soc-maxima`` this is how far below the maximum the SoC was.

Note: ``soc-targets`` are modelled as hard constraints, so the
scheduler will never allow a deviation from them by definition.
They are therefore not reported here.
additionalProperties: true

values:
type: array
items:
Expand Down Expand Up @@ -1089,8 +1131,21 @@ def get_schedule( # noqa: C901
unit=unit,
)

# Returns None if the job predates the scheduling_result feature (no meta key),
# or the dict with unresolved_targets if computed.
scheduling_result = job.meta.get(SCHEDULING_RESULT_KEY)
d, s = request_processed(scheduler_info_msg)
return dict(scheduler_info=scheduler_info, **response, **d), s
response_body = dict(
scheduler_info=scheduler_info,
**response,
**d,
)
if scheduling_result is not None:
response_body["scheduling_result"] = scheduling_result
return (
response_body,
s,
)

@route("/<id>", methods=["GET"])
@use_kwargs({"sensor": SensorIdField(data_key="id")}, location="path")
Expand Down
Loading
Loading