Skip to content
Merged
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 @@ -37,6 +37,7 @@ Infrastructure / Support
* Support filtering time series data by data source account [`PR #2065 <https://www.github.com/FlexMeasures/flexmeasures/pull/2065>`_]
* Speed up finding the data sources associated with a sensor and the sensors associated with a data source [`PR #2151 <https://www.github.com/FlexMeasures/flexmeasures/pull/2151>`_]
* Speed up sensor stats, especially potent when viewing sensors stats over a large sensor history [`PR #2173 <https://www.github.com/FlexMeasures/flexmeasures/pull/2173>`_]
* Various smaller fixes in documenting scheduling endpoints and flex-model fields [`PR #2122 <https://www.github.com/FlexMeasures/flexmeasures/pull/2122>`_]

Bugfixes
-----------
Expand Down
2 changes: 1 addition & 1 deletion documentation/tut/forecasting_scheduling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ Run ``flexmeasures add schedule --help`` for more information.
.. _scheduling_resolution:

Scheduling resolution
~~~~~~~~~~~~~~~~~~~~~
---------------------

The ``--resolution`` parameter (available in both the CLI and API) controls how often the scheduler is allowed to change power setpoints in the resulting schedule. This can be useful for several scenarios:

Expand Down
15 changes: 14 additions & 1 deletion flexmeasures/api/common/schemas/scheduling.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
from flexmeasures.api.common.schemas.utils import make_openapi_compatible
from flexmeasures.data.schemas.scheduling.storage import StorageFlexModelSchema
from flexmeasures.data.schemas.scheduling import FlexContextSchema
from flexmeasures.data.schemas.sensors import SensorIdField


# Create FlexContext, FlexModel and AssetTrigger OpenAPI compatible schemas
storage_flex_model_schema_openAPI = make_openapi_compatible(StorageFlexModelSchema)

storage_flex_model_schema_openAPI = make_openapi_compatible(
StorageFlexModelSchema,
include=[
{
"sensor": SensorIdField(
metadata=dict(
description="ID of the device's power sensor.",
)
)
}
],
)
flex_context_schema_openAPI = make_openapi_compatible(FlexContextSchema)
11 changes: 9 additions & 2 deletions flexmeasures/api/common/schemas/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
)


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

- allows to include additional fields (e.g. {"new_field": fields.String()})
- Drops custom __init__ args from the original schema
- Replaces custom fields (like VariableQuantityField) with String
"""
Expand All @@ -29,7 +32,11 @@ def make_openapi_compatible(schema_cls: Type[Schema]) -> Type[Schema]: # noqa:
sensor_only_validators.append(validator[-1])

new_fields = {}
for name, field in schema_cls._declared_fields.items():
tobeadded_fields = schema_cls._declared_fields
if include:
for item in include:
tobeadded_fields.update(item)
for name, field in tobeadded_fields.items():

if schema_cls in (ForecastingTriggerSchema, TrainPredictPipelineConfigSchema):
if "cli" in field.metadata and field.metadata["cli"].get(
Expand Down
16 changes: 10 additions & 6 deletions flexmeasures/api/v3_0/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,14 @@ def __init__(self, *args, **kwargs):
description="The flex-context is validated according to the scheduler's `FlexContextSchema`.",
),
)
flex_model = fields.Nested(
storage_flex_model_schema_openAPI(exclude=["asset"]),
required=True,
data_key="flex-model",
metadata=dict(
description="The flex-model validation is handled by the scheduler. What follows is the schema used by the `StorageScheduler`.",
flex_model = fields.List(
fields.Nested(
storage_flex_model_schema_openAPI(exclude=["asset"]),
required=True,
data_key="flex-model",
metadata=dict(
description="Flex-model per device (identified by `sensor`). The flex-model validation is handled by the scheduler. What follows is the schema used by the `StorageScheduler`.",
),
),
)

Expand Down Expand Up @@ -1418,6 +1420,8 @@ def trigger_schedule(
power-capacity: 25 kW
consumption-capacity: {sensor: 42}
production-capacity: 30 kW
soc-minima:
- {start: "2015-06-02T12:00:00+00:00", end: "2015-06-02T13:00:00+00:00", value: 10 kWh}
- sensor: 932
consumption-capacity: 0 kW
production-capacity: {sensor: 760}
Expand Down
14 changes: 11 additions & 3 deletions flexmeasures/api/v3_0/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
job_status_description,
process_sensor_data_ingestion,
)
from flexmeasures.api.common.utils.deprecation_utils import (
_add_headers as add_deprecation_header,
)
from flexmeasures.auth.policy import check_access
from flexmeasures.auth.decorators import permission_required_for_context
from flexmeasures.auth.loaders import flex_context_loader, flex_model_loader
Expand Down Expand Up @@ -815,12 +818,14 @@ def trigger_schedule(
**kwargs,
):
"""
.. :quickref: Schedules; Trigger scheduling job for one device
.. :quickref: Schedules; Trigger scheduling job for one device (deprecated)
---
post:
summary: Trigger scheduling job for one device
summary: Trigger scheduling job for one device. Deprecated.

description: |
Trigger FlexMeasures to create a schedule for this sensor.
Trigger FlexMeasures to create a schedule for this sensor. Deprecated - please use the [Assets scheduling endpoint](#/Assets/post_api_v3_0_assets__id__schedules_trigger) instead.

The assumption is that this sensor is the power sensor on a flexible asset.

In this request, you can describe:
Expand Down Expand Up @@ -1025,6 +1030,9 @@ def trigger_schedule(
d, s = request_processed()
return dict(**response, **d), s

# mark endpoint as deprecated
trigger_schedule.after_request = lambda response: add_deprecation_header(response)

@route("/<id>/schedules/<uuid>", methods=["GET"])
@use_kwargs(GetScheduleSchema(), location="args_and_json")
@permission_required_for_context("read", ctx_arg_name="sensor")
Expand Down
25 changes: 17 additions & 8 deletions flexmeasures/data/schemas/scheduling/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def to_dict(self):


STATE_OF_CHARGE = MetaData(
description="Sensor used to record the scheduled state of charge. If ``soc-at-start`` is omitted, FlexMeasures will also use this field to infer the starting state of charge. For this use case, the field may also contain a time series specification instead. When a sensor is used, its unit may be an energy unit (e.g. MWh or kWh) or a percentage (%). For sensors with a % unit, the ``soc-max`` flex-model field must be set to a non-zero value to allow converting between the energy-based schedule and a percentage.",
description="Sensor used to record the scheduled state of charge. If ``soc-at-start`` is omitted, FlexMeasures will also use this field to infer the starting state of charge. For this use case, the field may also contain a time series specification instead. When a sensor is used, its unit may be an energy unit (e.g. MWh or kWh) or a percentage (%). For sensors with a % unit, the ``soc-max`` flex-model field must be set to a non-zero value to allow converting between the energy-based schedule and a percentage. Also, the state-of-charge sensor's resolution should be instantaneous (i.e. `PT0M`).",
example={"sensor": 12},
)
SOC_AT_START = MetaData(
Expand Down Expand Up @@ -219,18 +219,27 @@ def to_dict(self):
SOC_MINIMA = MetaData(
description="""Set points that form lower boundaries, e.g. to target a full car battery in the morning.
If a ``soc-minima-breach-price`` is defined, the ``soc-minima`` become soft constraints in the optimization problem.
Otherwise, they become hard constraints. [#maximum_overlap]_""",
example=[{"datetime": "2024-02-05T08:00:00+01:00", "value": "8.2 kWh"}],
Otherwise, they become hard constraints. [#maximum_overlap]_. Both single points in time and ranges are possible, see example.""",
example=[
{"datetime": "2024-02-05T08:00:00+01:00", "value": "8.2 kWh"},
{
"value": "51 kWh",
"start": "2024-02-05T12:00:00+01:00",
"end": "2024-02-05T13:30:00+01:00",
},
],
)
SOC_MAXIMA = MetaData(
description="""Set points that form upper boundaries at certain times, e.g. to target an empty heat buffer before a maintenance window.
If a ``soc-maxima-breach-price`` is defined, the ``soc-maxima`` become soft constraints in the optimization problem.
Otherwise, they become hard constraints. [#minimum_overlap]_""",
example={
"value": "51 kWh",
"start": "2024-02-05T12:00:00+01:00",
"end": "2024-02-05T13:30:00+01:00",
},
example=[
{
"value": "51 kWh",
"start": "2024-02-05T12:00:00+01:00",
"end": "2024-02-05T13:30:00+01:00",
}
],
)
SOC_TARGETS = MetaData(
description="""
Expand Down
48 changes: 34 additions & 14 deletions flexmeasures/ui/static/openapi-specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1379,8 +1379,8 @@
},
"/api/v3_0/sensors/{id}/schedules/trigger": {
"post": {
"summary": "Trigger scheduling job for one device",
"description": "Trigger FlexMeasures to create a schedule for this sensor.\nThe assumption is that this sensor is the power sensor on a flexible asset.\n\nIn this request, you can describe:\n\n- the schedule's main features (when does it start, what unit should it report, prior to what time can we assume knowledge)\n- the flexibility model for the sensor (state and constraint variables, e.g. current state of charge of a battery, or connection capacity)\n- the flexibility context which the sensor operates in (other sensors under the same EMS which are relevant, e.g. prices)\n\nFor details on flexibility model and context, see the [documentation on describing flexibility](https://flexmeasures.readthedocs.io/latest/features/scheduling.html#describing-flexibility).\nThe schemas we use in this endpoint documentation do not describe the full flexibility model and context (as the docs do), as these are very flexible (e.g. fixed values or sensors).\nThe examples below illustrate how to describe a flexibility model and context.\n\n> <strong>Note:</strong> To schedule an EMS with multiple flexible sensors at once,\n> use the [Assets scheduling endpoint](#/Assets/post_api_v3_0_assets__id__schedules_trigger) instead.\n\nAbout the duration of the schedule and targets within the schedule:\n\n- The length of the schedule can be set explicitly through the 'duration' field.\n- Otherwise, it is set by the config setting `FLEXMEASURES_PLANNING_HORIZON`, which defaults to 48 hours.\n- If the flex-model contains targets that lie beyond the planning horizon, the length of the schedule is extended to accommodate them.\n- Finally, the schedule length is limited by the config setting `FLEXMEASURES_MAX_PLANNING_HORIZON`, which defaults to 2520 steps of the sensor's resolution. Targets that exceed the max planning horizon are not accepted.\n\nThe 'resolution' field governs how often setpoints are allowed to change.\nNote that the resulting schedule is still saved in the sensor resolution.\n\nAbout the scheduling algorithm being used:\n\n- The appropriate algorithm is chosen by FlexMeasures (based on asset type).\n- It's also possible to use custom schedulers and custom flexibility models.\n- If you have ideas for algorithms that should be part of FlexMeasures, let us know: [https://flexmeasures.io/get-in-touch/](https://flexmeasures.io/get-in-touch/)\n",
"summary": "Trigger scheduling job for one device. Deprecated.",
"description": "Trigger FlexMeasures to create a schedule for this sensor. Deprecated - please use the [Assets scheduling endpoint](#/Assets/post_api_v3_0_assets__id__schedules_trigger) instead.\n\nThe assumption is that this sensor is the power sensor on a flexible asset.\n\nIn this request, you can describe:\n\n- the schedule's main features (when does it start, what unit should it report, prior to what time can we assume knowledge)\n- the flexibility model for the sensor (state and constraint variables, e.g. current state of charge of a battery, or connection capacity)\n- the flexibility context which the sensor operates in (other sensors under the same EMS which are relevant, e.g. prices)\n\nFor details on flexibility model and context, see the [documentation on describing flexibility](https://flexmeasures.readthedocs.io/latest/features/scheduling.html#describing-flexibility).\nThe schemas we use in this endpoint documentation do not describe the full flexibility model and context (as the docs do), as these are very flexible (e.g. fixed values or sensors).\nThe examples below illustrate how to describe a flexibility model and context.\n\n> <strong>Note:</strong> To schedule an EMS with multiple flexible sensors at once,\n> use the [Assets scheduling endpoint](#/Assets/post_api_v3_0_assets__id__schedules_trigger) instead.\n\nAbout the duration of the schedule and targets within the schedule:\n\n- The length of the schedule can be set explicitly through the 'duration' field.\n- Otherwise, it is set by the config setting `FLEXMEASURES_PLANNING_HORIZON`, which defaults to 48 hours.\n- If the flex-model contains targets that lie beyond the planning horizon, the length of the schedule is extended to accommodate them.\n- Finally, the schedule length is limited by the config setting `FLEXMEASURES_MAX_PLANNING_HORIZON`, which defaults to 2520 steps of the sensor's resolution. Targets that exceed the max planning horizon are not accepted.\n\nThe 'resolution' field governs how often setpoints are allowed to change.\nNote that the resulting schedule is still saved in the sensor resolution.\n\nAbout the scheduling algorithm being used:\n\n- The appropriate algorithm is chosen by FlexMeasures (based on asset type).\n- It's also possible to use custom schedulers and custom flexibility models.\n- If you have ideas for algorithms that should be part of FlexMeasures, let us know: [https://flexmeasures.io/get-in-touch/](https://flexmeasures.io/get-in-touch/)\n",
"security": [
{
"ApiKeyAuth": []
Expand Down Expand Up @@ -3873,7 +3873,14 @@
"consumption-capacity": {
"sensor": 42
},
"production-capacity": "30 kW"
"production-capacity": "30 kW",
"soc-minima": [
{
"start": "2015-06-02T12:00:00+00:00",
"end": "2015-06-02T13:00:00+00:00",
"value": "10 kWh"
}
]
},
{
"sensor": 932,
Expand Down Expand Up @@ -6039,19 +6046,26 @@
},
"soc-maxima": {
"description": "Set points that form upper boundaries at certain times, e.g. to target an empty heat buffer before a maintenance window.\nIf a <code>soc-maxima-breach-price</code> is defined, the <code>soc-maxima</code> become soft constraints in the optimization problem.\nOtherwise, they become hard constraints.",
"example": {
"value": "51 kWh",
"start": "2024-02-05T12:00:00+01:00",
"end": "2024-02-05T13:30:00+01:00"
},
"example": [
{
"value": "51 kWh",
"start": "2024-02-05T12:00:00+01:00",
"end": "2024-02-05T13:30:00+01:00"
}
],
"$ref": "#/components/schemas/VariableQuantityOpenAPI"
},
"soc-minima": {
"description": "Set points that form lower boundaries, e.g. to target a full car battery in the morning.\nIf a <code>soc-minima-breach-price</code> is defined, the <code>soc-minima</code> become soft constraints in the optimization problem.\nOtherwise, they become hard constraints.",
"description": "Set points that form lower boundaries, e.g. to target a full car battery in the morning.\nIf a <code>soc-minima-breach-price</code> is defined, the <code>soc-minima</code> become soft constraints in the optimization problem.\nOtherwise, they become hard constraints.. Both single points in time and ranges are possible, see example.",
"example": [
{
"datetime": "2024-02-05T08:00:00+01:00",
"value": "8.2 kWh"
},
{
"value": "51 kWh",
"start": "2024-02-05T12:00:00+01:00",
"end": "2024-02-05T13:30:00+01:00"
}
],
"$ref": "#/components/schemas/VariableQuantityOpenAPI"
Expand All @@ -6076,7 +6090,7 @@
"example": "kWh"
},
"state-of-charge": {
"description": "Sensor used to record the scheduled state of charge. If <code>soc-at-start</code> is omitted, FlexMeasures will also use this field to infer the starting state of charge. For this use case, the field may also contain a time series specification instead. When a sensor is used, its unit may be an energy unit (e.g. MWh or kWh) or a percentage (%). For sensors with a % unit, the <code>soc-max</code> flex-model field must be set to a non-zero value to allow converting between the energy-based schedule and a percentage.",
"description": "Sensor used to record the scheduled state of charge. If <code>soc-at-start</code> is omitted, FlexMeasures will also use this field to infer the starting state of charge. For this use case, the field may also contain a time series specification instead. When a sensor is used, its unit may be an energy unit (e.g. MWh or kWh) or a percentage (%). For sensors with a % unit, the <code>soc-max</code> flex-model field must be set to a non-zero value to allow converting between the energy-based schedule and a percentage. Also, the state-of-charge sensor's resolution should be instantaneous (i.e. `PT0M`).",
"example": {
"sensor": 12
},
Expand Down Expand Up @@ -6127,6 +6141,10 @@
}
],
"items": {}
},
"sensor": {
"type": "integer",
"description": "ID of the device's power sensor."
}
},
"additionalProperties": false
Expand Down Expand Up @@ -6158,9 +6176,12 @@
"example": "PT2H",
"format": "duration"
},
"flex-model": {
"description": "The flex-model validation is handled by the scheduler. What follows is the schema used by the `StorageScheduler`.",
"$ref": "#/components/schemas/StorageFlexModelSchemaOpenAPI"
"flex_model": {
"type": "array",
"items": {
"description": "Flex-model per device (identified by `sensor`). The flex-model validation is handled by the scheduler. What follows is the schema used by the `StorageScheduler`.",
"$ref": "#/components/schemas/StorageFlexModelSchemaOpenAPI"
}
},
"flex-context": {
"description": "The flex-context is validated according to the scheduler's `FlexContextSchema`.",
Expand All @@ -6178,7 +6199,6 @@
},
"required": [
"flex-context",
"flex-model",
"start"
],
"additionalProperties": false
Expand Down
Loading