Skip to content

Commit

Permalink
Sunset old flex config fields (#580)
Browse files Browse the repository at this point in the history
* Sunset old flex config fields

Signed-off-by: F.N. Claessen <felix@seita.nl>

* API changelog entry

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Changelog warning

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Changelog entry

Signed-off-by: F.N. Claessen <felix@seita.nl>

* typo

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Fix header level

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Remove obsolete code

Signed-off-by: F.N. Claessen <felix@seita.nl>

* Remove redundant test util argument

Signed-off-by: F.N. Claessen <felix@seita.nl>

Signed-off-by: F.N. Claessen <felix@seita.nl>
  • Loading branch information
Flix6x committed Jan 17, 2023
1 parent 156e91b commit 82e4c03
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 149 deletions.
7 changes: 6 additions & 1 deletion documentation/api/change_log.rst
Expand Up @@ -5,6 +5,11 @@ API change log

.. note:: The FlexMeasures API follows its own versioning scheme. This is also reflected in the URL, allowing developers to upgrade at their own pace.

v3.0-6 | 2023-02-01
"""""""""""""""""""

- Sunset all fields that were moved to ``flex-model`` and ``flex-context`` fields to `/sensors/<id>/schedules/trigger` (POST). See v3.0-5.

v3.0-5 | 2023-01-04
"""""""""""""""""""

Expand All @@ -21,7 +26,7 @@ v3.0-5 | 2023-01-04
- ``production-price-sensor`` -> send in ``flex-context`` instead
- ``inflexible-device-sensors`` -> send in ``flex-context`` instead

- Introduced the ``duration``field to `/sensors/<id>/schedules/trigger` (POST) for setting a planning horizon explicitly.
- Introduced the ``duration`` field to `/sensors/<id>/schedules/trigger` (POST) for setting a planning horizon explicitly.
- Allow posting ``soc-targets`` to `/sensors/<id>/schedules/trigger` (POST) that exceed the default planning horizon, and ignore posted targets that exceed the max planning horizon.
- Added a subsection on deprecating and sunsetting to the Introduction section.
- Added a subsection on describing flexibility to the Notation section.
Expand Down
2 changes: 1 addition & 1 deletion documentation/api/notation.rst
Expand Up @@ -367,7 +367,7 @@ The resolution of the underlying data will remain zero (and the returned message
.. _sources:

Sources
-------
^^^^^^^

Requests for data may filter by source. FlexMeasures keeps track of the data source (the data's author, for example, a user, forecaster or scheduler belonging to a given organisation) of time series data.
For example, to obtain data originating from data source 42, include the following:
Expand Down
14 changes: 13 additions & 1 deletion documentation/changelog.rst
Expand Up @@ -6,6 +6,19 @@ FlexMeasures Changelog
v0.13.0 | February XX, 2023
============================

.. warning:: The API endpoint (`[POST] /sensors/(id)/schedules/trigger <api/v3_0.html#post--api-v3_0-sensors-(id)-schedules-trigger>`_) to make new schedules sunsets the deprecated (since v0.12) storage flexibility parameters (they move to the ``flex-model`` parameter group), as well as the parameters describing other sensors (they move to ``flex-context``).

New features
-------------

Bugfixes
-----------


Infrastructure / Support
----------------------
* Sunset several API fields for `/sensors/<id>/schedules/trigger` (POST) that have moved into the ``flex-model`` or ``flex-context`` fields [see `PR #580 <https://www.github.com/FlexMeasures/flexmeasures/pull/580>`_]


v0.12.1 | January 12, 2023
============================
Expand All @@ -15,7 +28,6 @@ Bugfixes
* Fix validation of (deprecated) API parameter ``roundtrip-efficiency`` [see `PR #582 <https://www.github.com/FlexMeasures/flexmeasures/pull/582>`_]



v0.12.0 | January 4, 2023
============================

Expand Down
121 changes: 2 additions & 119 deletions flexmeasures/api/v3_0/sensors.py
@@ -1,13 +1,12 @@
from datetime import datetime, timedelta
from typing import List, Dict, Optional
from typing import Optional

from flask import current_app
from flask_classful import FlaskView, route
from flask_json import as_json
from flask_security import auth_required
import isodate
from marshmallow import fields, ValidationError
from marshmallow.validate import OneOf
from rq.job import Job, NoSuchJobError
from timely_beliefs import BeliefsDataFrame
from webargs.flaskparser import use_args, use_kwargs
Expand All @@ -18,7 +17,6 @@
unknown_schedule,
invalid_flex_config,
)
from flexmeasures.api.common.utils.deprecation_utils import deprecate_fields
from flexmeasures.api.common.utils.validators import (
optional_duration_accepted,
)
Expand All @@ -42,27 +40,13 @@
get_data_source_for_job,
)
from flexmeasures.utils.time_utils import duration_isoformat
from flexmeasures.utils.unit_utils import ur


# Instantiate schemas outside of endpoint logic to minimize response time
get_sensor_schema = GetSensorDataSchema()
post_sensor_schema = PostSensorDataSchema()
sensors_schema = SensorSchema(many=True)

DEPRECATED_FLEX_CONFIGURATION_FIELDS = [
"soc-at-start",
"soc-min",
"soc-max",
"soc-unit",
"roundtrip-efficiency",
"prefer-charging-sooner",
"soc-targets",
"consumption-price-sensor",
"production-price-sensor",
"inflexible-device-sensors",
]


class SensorAPI(FlaskView):

Expand Down Expand Up @@ -211,7 +195,6 @@ def get_data(self, response: dict):
{"sensor": SensorIdField(data_key="id")},
location="path",
)
# TODO: Everything other than start_of_schedule, prior, flex_model and flex_context is to be deprecated in 0.13. We let the scheduler decide (flex model) or nest (portfolio)
@use_kwargs(
{
"start_of_schedule": AwareDateTimeField(
Expand All @@ -222,36 +205,9 @@ def get_data(self, response: dict):
load_default=PlanningDurationField.load_default
),
"flex_model": fields.Dict(data_key="flex-model"),
"soc_sensor_id": fields.Str(data_key="soc-sensor", required=False),
"roundtrip_efficiency": fields.Raw(data_key="roundtrip-efficiency"),
"start_value": fields.Float(data_key="soc-at-start"),
"soc_min": fields.Float(data_key="soc-min"),
"soc_max": fields.Float(data_key="soc-max"),
"unit": fields.Str(
data_key="soc-unit",
validate=OneOf(
[
"kWh",
"MWh",
]
),
), # todo: allow unit to be set per field, using QuantityField("%", validate=validate.Range(min=0, max=1))
"targets": fields.List(fields.Dict, data_key="soc-targets"),
"prefer_charging_sooner": fields.Bool(
data_key="prefer-charging-sooner", required=False
),
"flex_context": fields.Nested(
FlexContextSchema, required=False, data_key="flex-context"
),
"consumption_price_sensor": SensorIdField(
data_key="consumption-price-sensor", required=False
),
"production_price_sensor": SensorIdField(
data_key="production-price-sensor", required=False
),
"inflexible_device_sensors": fields.List(
SensorIdField, data_key="inflexible-device-sensors", required=False
),
},
location="json",
)
Expand All @@ -261,16 +217,6 @@ def trigger_schedule( # noqa: C901
start_of_schedule: datetime,
duration: timedelta,
belief_time: Optional[datetime] = None,
start_value: Optional[float] = None,
soc_min: Optional[float] = None,
soc_max: Optional[float] = None,
unit: Optional[str] = None,
roundtrip_efficiency: Optional[ur.Quantity] = None,
prefer_charging_sooner: Optional[bool] = True,
consumption_price_sensor: Optional[Sensor] = None,
production_price_sensor: Optional[Sensor] = None,
inflexible_device_sensors: Optional[List[Sensor]] = None,
soc_sensor_id: Optional[int] = None,
flex_model: Optional[dict] = None,
flex_context: Optional[dict] = None,
**kwargs,
Expand Down Expand Up @@ -383,69 +329,6 @@ def trigger_schedule( # noqa: C901
:status 405: INVALID_METHOD
:status 422: UNPROCESSABLE_ENTITY
"""
# -- begin deprecation logic, can be removed after 0.13
deprecate_fields(
DEPRECATED_FLEX_CONFIGURATION_FIELDS,
deprecation_date="2022-12-14",
deprecation_link="https://flexmeasures.readthedocs.io/en/latest/api/change_log.html#v3-0-5-2022-12-30",
sunset_date="2023-02-01",
sunset_link="https://flexmeasures.readthedocs.io/en/latest/api/change_log.html#v3-0-5-2022-12-30",
)
found_fields: Dict[str, List[str]] = dict(model=[], context=[])
deprecation_message = ""
# flex-model
for param, param_name in [
(start_value, "soc-at-start"),
(soc_min, "soc-min"),
(soc_max, "soc-max"),
(unit, "soc-unit"),
(kwargs.get("targets"), "soc-targets"),
(roundtrip_efficiency, "roundtrip-efficiency"),
(
prefer_charging_sooner,
"prefer-charging-sooner",
),
]:
if flex_model is None:
flex_model = {}
if param is not None:
if param_name not in flex_model:
flex_model[param_name] = param
found_fields["model"].append(param_name)
# flex-context
for param, param_name in [
(
consumption_price_sensor,
"consumption-price-sensor",
),
(
production_price_sensor,
"production-price-sensor",
),
(
inflexible_device_sensors,
"inflexible-device-sensors",
),
]:
if flex_context is None:
flex_context = {}
if param is not None:
if param_name not in flex_context:
flex_context[param_name] = param
found_fields["context"].append(param_name)
if found_fields["model"] or found_fields["context"]:
deprecation_message = "The following fields you sent are deprecated and will be sunset in the next version:"
if found_fields["model"]:
deprecation_message += f" {', '.join(found_fields['model'])} (please pass as part of flex_model)."
if found_fields["context"]:
deprecation_message += f" {', '.join(found_fields['context'])} (please pass as part of flex_context)."

if soc_sensor_id is not None:
deprecation_message += (
"The field soc-sensor-id is be deprecated and will be sunset in v0.13."
)
# -- end deprecation logic

end_of_schedule = start_of_schedule + duration
scheduler_kwargs = dict(
sensor=sensor,
Expand All @@ -470,7 +353,7 @@ def trigger_schedule( # noqa: C901
db.session.commit()

response = dict(schedule=job.id)
d, s = request_processed(deprecation_message)
d, s = request_processed()
return dict(**response, **d), s

@route("/<id>/schedules/<uuid>", methods=["GET"])
Expand Down
15 changes: 0 additions & 15 deletions flexmeasures/api/v3_0/tests/test_sensor_schedules.py
Expand Up @@ -76,14 +76,7 @@ def test_trigger_schedule_with_invalid_flexmodel(
@pytest.mark.parametrize(
"message, asset_name",
[
(message_for_trigger_schedule(deprecated_format_pre012=True), "Test battery"),
(message_for_trigger_schedule(), "Test battery"),
(
message_for_trigger_schedule(
with_targets=True, deprecated_format_pre012=True
),
"Test charging station",
),
(message_for_trigger_schedule(with_targets=True), "Test charging station"),
],
)
Expand All @@ -101,9 +94,6 @@ def test_trigger_and_get_schedule(
assert len(app.queues["scheduling"]) == 0

sensor = Sensor.query.filter(Sensor.name == asset_name).one_or_none()
# This makes sure we have fresh data. A hack we can remove after the deprecation cases are removed.
TimedBelief.query.filter(TimedBelief.sensor_id == sensor.id).delete()

with app.test_client() as client:
auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
trigger_schedule_response = client.post(
Expand All @@ -112,11 +102,6 @@ def test_trigger_and_get_schedule(
headers={"Authorization": auth_token},
)
print("Server responded with:\n%s" % trigger_schedule_response.json)
if "flex-model" not in message:
check_deprecation(trigger_schedule_response)
assert (
"soc-min" in trigger_schedule_response.json["message"]
) # deprecation warning
assert trigger_schedule_response.status_code == 200
job_id = trigger_schedule_response.json["schedule"]

Expand Down
16 changes: 4 additions & 12 deletions flexmeasures/api/v3_0/tests/utils.py
Expand Up @@ -45,7 +45,6 @@ def message_for_trigger_schedule(
with_targets: bool = False,
realistic_targets: bool = True,
too_far_into_the_future_targets: bool = False,
deprecated_format_pre012: bool = False,
) -> dict:
message = {
"start": "2015-01-01T00:00:00+01:00",
Expand All @@ -56,18 +55,13 @@ def message_for_trigger_schedule(
"start"
] = "2040-01-01T00:00:00+01:00" # We have no beliefs in our test database about 2040 prices

flex_model = {
message["flex-model"] = {
"soc-at-start": 12.1, # in kWh, according to soc-unit
"soc-min": 0, # in kWh, according to soc-unit
"soc-max": 40, # in kWh, according to soc-unit
"soc-unit": "kWh",
"roundtrip-efficiency": "98%",
}
if deprecated_format_pre012:
# unpack flex model directly as message fields
message = dict(**message, **flex_model)
else:
message["flex-model"] = flex_model
if with_targets:
if realistic_targets:
# this target (in kWh, according to soc-unit) is well below the soc_max_in_mwh on the battery's sensor attributes
Expand All @@ -80,9 +74,7 @@ def message_for_trigger_schedule(
target_datetime = "2015-02-02T23:00:00+01:00"
else:
target_datetime = "2015-01-02T23:00:00+01:00"
targets = [{"value": target_value, "datetime": target_datetime}]
if deprecated_format_pre012:
message["soc-targets"] = targets
else:
message["flex-model"]["soc-targets"] = targets
message["flex-model"]["soc-targets"] = [
{"value": target_value, "datetime": target_datetime}
]
return message

0 comments on commit 82e4c03

Please sign in to comment.