diff --git a/documentation/changelog.rst b/documentation/changelog.rst index 4a760c37ff..5b77fa24c1 100644 --- a/documentation/changelog.rst +++ b/documentation/changelog.rst @@ -37,6 +37,7 @@ Infrastructure / Support * Support filtering time series data by data source account [`PR #2065 `_] * Speed up finding the data sources associated with a sensor and the sensors associated with a data source [`PR #2151 `_] * Speed up sensor stats, especially potent when viewing sensors stats over a large sensor history [`PR #2173 `_] +* Various smaller fixes in documenting scheduling endpoints and flex-model fields [`PR #2122 `_] Bugfixes ----------- diff --git a/documentation/tut/forecasting_scheduling.rst b/documentation/tut/forecasting_scheduling.rst index 952faf87f7..d01fd67cf5 100644 --- a/documentation/tut/forecasting_scheduling.rst +++ b/documentation/tut/forecasting_scheduling.rst @@ -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: diff --git a/flexmeasures/api/common/schemas/scheduling.py b/flexmeasures/api/common/schemas/scheduling.py index 1a26566f18..f33c5ee555 100644 --- a/flexmeasures/api/common/schemas/scheduling.py +++ b/flexmeasures/api/common/schemas/scheduling.py @@ -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) diff --git a/flexmeasures/api/common/schemas/utils.py b/flexmeasures/api/common/schemas/utils.py index 536e062600..f457d2bed2 100644 --- a/flexmeasures/api/common/schemas/utils.py +++ b/flexmeasures/api/common/schemas/utils.py @@ -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 """ @@ -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( diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py index d9aac707e9..27b79c49cd 100644 --- a/flexmeasures/api/v3_0/assets.py +++ b/flexmeasures/api/v3_0/assets.py @@ -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`.", + ), ), ) @@ -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} diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py index 88a2eb6c2c..615b68d9b3 100644 --- a/flexmeasures/api/v3_0/sensors.py +++ b/flexmeasures/api/v3_0/sensors.py @@ -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 @@ -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: @@ -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("//schedules/", methods=["GET"]) @use_kwargs(GetScheduleSchema(), location="args_and_json") @permission_required_for_context("read", ctx_arg_name="sensor") diff --git a/flexmeasures/data/schemas/scheduling/metadata.py b/flexmeasures/data/schemas/scheduling/metadata.py index 5852d6d286..cf03767d2b 100644 --- a/flexmeasures/data/schemas/scheduling/metadata.py +++ b/flexmeasures/data/schemas/scheduling/metadata.py @@ -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( @@ -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=""" diff --git a/flexmeasures/ui/static/openapi-specs.json b/flexmeasures/ui/static/openapi-specs.json index 74153ec68b..9d4363b9cc 100644 --- a/flexmeasures/ui/static/openapi-specs.json +++ b/flexmeasures/ui/static/openapi-specs.json @@ -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> Note: 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> Note: 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": [] @@ -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, @@ -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 soc-maxima-breach-price is defined, the soc-maxima 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 soc-minima-breach-price is defined, the soc-minima 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 soc-minima-breach-price is defined, the soc-minima 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" @@ -6076,7 +6090,7 @@ "example": "kWh" }, "state-of-charge": { - "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 }, @@ -6127,6 +6141,10 @@ } ], "items": {} + }, + "sensor": { + "type": "integer", + "description": "ID of the device's power sensor." } }, "additionalProperties": false @@ -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`.", @@ -6178,7 +6199,6 @@ }, "required": [ "flex-context", - "flex-model", "start" ], "additionalProperties": false