From fc0269561742b284653242296325be8c223e5c7b Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 20 Jan 2025 14:03:51 +0100 Subject: [PATCH 1/8] feat: add stubs for tests and initial code structure Signed-off-by: F.N. Claessen --- flexmeasures_s2/api/tests/test_api.py | 58 +++++++++++++++---- flexmeasures_s2/conftest.py | 50 +++++++++++++++- flexmeasures_s2/models/__init__.py | 0 flexmeasures_s2/models/const.py | 2 + flexmeasures_s2/scheduler/schedulers.py | 15 +++++ flexmeasures_s2/scheduler/schemas.py | 14 ++++- flexmeasures_s2/scheduler/tests/__init__.py | 0 flexmeasures_s2/scheduler/tests/test_frbc.py | 18 ++++++ .../scheduler/tests/test_schemas.py | 20 +++++++ 9 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 flexmeasures_s2/models/__init__.py create mode 100644 flexmeasures_s2/models/const.py create mode 100644 flexmeasures_s2/scheduler/tests/__init__.py create mode 100644 flexmeasures_s2/scheduler/tests/test_frbc.py create mode 100644 flexmeasures_s2/scheduler/tests/test_schemas.py diff --git a/flexmeasures_s2/api/tests/test_api.py b/flexmeasures_s2/api/tests/test_api.py index d2048ad..3f4eb6f 100644 --- a/flexmeasures_s2/api/tests/test_api.py +++ b/flexmeasures_s2/api/tests/test_api.py @@ -1,16 +1,52 @@ from flask import url_for +from flask_security import decorators as fs_decorators +from rq.job import Job +from flexmeasures.api.tests.test_auth_token import patched_check_token +from flexmeasures.api.tests.utils import UserContext +from flexmeasures.data.services.scheduling import ( + handle_scheduling_exception, + get_data_source_for_job, +) +from flexmeasures.data.tests.utils import work_on_rq -def test_get_somedata_needs_authtoken(client): - response = client.get( - url_for("flexmeasures-s2 API.somedata"), - headers={"content-type": "application/json"}, - follow_redirects=True, - ) - assert response.status_code == 401 # HTTP error code 401 Unauthorized. - assert "application/json" in response.content_type - assert "not be properly authenticated" in response.json["message"] +def test_s2_frbc_api(monkeypatch, app, setup_frbc_asset): + sensor = setup_frbc_asset.sensors[0] -# TODO: The somedata endpoint requires authentication to be testes successfully. -# We'll need to add a user in conftest, which also requires us to add a db to testing + with UserContext("test_admin_user@seita.nl") as admin: + auth_token = admin.get_auth_token() + + monkeypatch.setattr(fs_decorators, "_check_token", patched_check_token) + with app.test_client() as client: + trigger_schedule_response = client.post( + url_for("SensorAPI:trigger_schedule", id=sensor.id), + json={ + "flex-context": { + "target-profile": {}, # add target profile + } + }, + headers={"Authorization": auth_token}, + ) + print("Server responded with:\n%s" % trigger_schedule_response.json) + assert trigger_schedule_response.status_code == 200 + job_id = trigger_schedule_response.json["schedule"] + + # Now that our scheduling job was accepted, we process the scheduling queue + work_on_rq(app.queues["scheduling"], exc_handler=handle_scheduling_exception) + job = Job.fetch(job_id, connection=app.queues["scheduling"].connection).is_finished + assert job.is_finished is True + + # First, make sure the expected scheduler data source is now there + job.refresh() # catch meta info that was added on this very instance + scheduler_source = get_data_source_for_job(job) + assert scheduler_source.model == "S2Scheduler" + + # try to retrieve the schedule through the /sensors//schedules/ [GET] api endpoint + # todo: to be discussed: the response from the S2Scheduler might get a different format than the FM default + # get_schedule_response = client.get( + # url_for("SensorAPI:get_schedule", id=sensor.id, uuid=job_id), + # query_string={"duration": "PT48H"}, + # ) + # print("Server responded with:\n%s" % get_schedule_response.json) + # assert get_schedule_response.status_code == 200 diff --git a/flexmeasures_s2/conftest.py b/flexmeasures_s2/conftest.py index 186ee1a..0fe56ba 100644 --- a/flexmeasures_s2/conftest.py +++ b/flexmeasures_s2/conftest.py @@ -1,12 +1,18 @@ import pytest +from flask_sqlalchemy import SQLAlchemy + +from flexmeasures import Asset, AssetType, Sensor, Account from flexmeasures.app import create as create_flexmeasures_app +from flexmeasures.auth.policy import ADMIN_ROLE from flexmeasures.conftest import ( # noqa F401 db, fresh_db, -) # Use these fixtures to rely on the FlexMeasures database. +) # Use these fixtures to rely on the FlexMeasures database. There might be others in flexmeasures/conftest you want to also re-use +from flexmeasures.data.services.users import create_user -# There might be others in flexmeasures/conftest you want to also re-use +from flexmeasures_s2 import S2_SCHEDULER_SPECS +from flexmeasures_s2.models.const import FRBC_TYPE @pytest.fixture(scope="session") @@ -25,3 +31,43 @@ def app(): ctx.pop() print("DONE WITH APP FIXTURE") + + +@pytest.fixture(scope="module") +def setup_admin(db: SQLAlchemy): # noqa: F811 + account = Account(name="Some FlexMeasures host") + db.session.add(account) + create_user( + username="Test Admin User", + email="test_admin_user@seita.nl", + account_name=account.name, + password="testtest", + user_roles=dict(name=ADMIN_ROLE, description="A user who can do everything."), + ) + yield account + + +@pytest.fixture(scope="module") +def setup_frbc_asset(db: SQLAlchemy, setup_admin): # noqa: F811 + asset_type = AssetType(name=FRBC_TYPE) + asset = Asset( + name="Test FRBC asset", + generic_asset_type=asset_type, + owner=setup_admin, + ) + asset.attributes = { + "custom-scheduler": S2_SCHEDULER_SPECS, + "flex-model": { + "S2-FRBC-device-state": {}, # todo: add serialized state + }, + } + db.session.add(asset) + sensor = Sensor( + name="power", + unit="kW", + event_resolution="PT5M", + generic_asset=asset, + ) + db.session.add(sensor) + db.session.flush() # assign (asset and sensor) IDs + yield asset diff --git a/flexmeasures_s2/models/__init__.py b/flexmeasures_s2/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flexmeasures_s2/models/const.py b/flexmeasures_s2/models/const.py new file mode 100644 index 0000000..6ff6a35 --- /dev/null +++ b/flexmeasures_s2/models/const.py @@ -0,0 +1,2 @@ +NAMESPACE = "fm-s2" +FRBC_TYPE = f"{NAMESPACE}.FRBC" diff --git a/flexmeasures_s2/scheduler/schedulers.py b/flexmeasures_s2/scheduler/schedulers.py index 7ab3080..7a683e6 100644 --- a/flexmeasures_s2/scheduler/schedulers.py +++ b/flexmeasures_s2/scheduler/schedulers.py @@ -1,6 +1,8 @@ import pandas as pd from flexmeasures import Scheduler +from flexmeasures_s2.scheduler.schemas import S2FlexModelSchema, TNOFlexContextSchema + class S2Scheduler(Scheduler): @@ -12,6 +14,7 @@ def compute(self, *args, **kwargs): Just a dummy scheduler that always plans to consume at maximum capacity. (Schedulers return positive values for consumption, and negative values for production) """ + raise NotImplementedError("todo: implement scheduling logic") return pd.Series( self.sensor.get_attribute("capacity_in_mw"), index=pd.date_range( @@ -21,4 +24,16 @@ def compute(self, *args, **kwargs): def deserialize_config(self): """Do not care about any flex config sent in.""" + # Find flex-model in asset attributes + self.flex_model = self.asset.attributes.get("flex-model", {}) + + self.deserialize_flex_config() self.config_deserialized = True + + def deserialize_flex_config(self): + """Deserialize flex-model and flex-context""" + # Deserialize flex-model + self.flex_model = S2FlexModelSchema().load(self.flex_model) + + # Deserialize self.flex_context + self.flex_context = TNOFlexContextSchema().load(self.flex_context) diff --git a/flexmeasures_s2/scheduler/schemas.py b/flexmeasures_s2/scheduler/schemas.py index 9e42dfa..bed6480 100644 --- a/flexmeasures_s2/scheduler/schemas.py +++ b/flexmeasures_s2/scheduler/schemas.py @@ -1,5 +1,17 @@ -from marshmallow import Schema +from flexmeasures.data.schemas import AwareDateTimeField, DurationField + +from marshmallow import Schema, fields class S2FlexModelSchema(Schema): ... + + +class TNOTargetProfile(Schema): + start = AwareDateTimeField() + duration = DurationField() + values = fields.List(fields.Float) + + +class TNOFlexContextSchema(Schema): + target_profile = fields.Nested(TNOTargetProfile()) diff --git a/flexmeasures_s2/scheduler/tests/__init__.py b/flexmeasures_s2/scheduler/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flexmeasures_s2/scheduler/tests/test_frbc.py b/flexmeasures_s2/scheduler/tests/test_frbc.py new file mode 100644 index 0000000..e0ef854 --- /dev/null +++ b/flexmeasures_s2/scheduler/tests/test_frbc.py @@ -0,0 +1,18 @@ +import pandas as pd + +from flexmeasures_s2.scheduler.schedulers import S2Scheduler + + +def test_s2_frbc_scheduler(setup_frbc_asset): + scheduler = S2Scheduler( + setup_frbc_asset, + start=pd.Timestamp("2025-01-20T13:00+01"), + end=pd.Timestamp("2025-01-20T19:00+01"), + resolution=pd.Timedelta("PT1H"), + flex_model={}, # S2Scheduler fetches this from asset attributes + flex_context={ + "target-profile": {}, # todo: port target profile from Java test + }, + ) + results = scheduler.compute() + assert results == "todo: check for expected results" diff --git a/flexmeasures_s2/scheduler/tests/test_schemas.py b/flexmeasures_s2/scheduler/tests/test_schemas.py new file mode 100644 index 0000000..0f61a4d --- /dev/null +++ b/flexmeasures_s2/scheduler/tests/test_schemas.py @@ -0,0 +1,20 @@ +import pytest + +from flexmeasures_s2.scheduler.schemas import TNOTargetProfile + + +@pytest.mark.parametrize( + "target_profile", + [ + # todo: port test cases from Java test + {}, + { + "start": "2025-01-20T13:00+01", + "duration": "PT3H", + "values": [0, 1, 2], + }, + ], +) +def test_tno_profile_schema(target_profile: dict): + """Check whether the profile schema""" + TNOTargetProfile().load(target_profile) From 854804796e12aaa0a674fa0f6cc3ae40b486caa9 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Mon, 20 Jan 2025 23:21:10 +0100 Subject: [PATCH 2/8] Joule profile for target profile and original data Signed-off-by: Vlad Iftime --- .../scheduler/tests/JouleProfileOriginal.py | 290 ++++++++++++++++++ flexmeasures_s2/scheduler/tests/test_frbc.py | 3 +- 2 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py diff --git a/flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py b/flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py new file mode 100644 index 0000000..3dcc247 --- /dev/null +++ b/flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py @@ -0,0 +1,290 @@ +JouleProfileTarget = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +] diff --git a/flexmeasures_s2/scheduler/tests/test_frbc.py b/flexmeasures_s2/scheduler/tests/test_frbc.py index e0ef854..1ba199f 100644 --- a/flexmeasures_s2/scheduler/tests/test_frbc.py +++ b/flexmeasures_s2/scheduler/tests/test_frbc.py @@ -1,6 +1,7 @@ import pandas as pd from flexmeasures_s2.scheduler.schedulers import S2Scheduler +from JouleProfileOriginal import JouleProfileTarget def test_s2_frbc_scheduler(setup_frbc_asset): @@ -11,7 +12,7 @@ def test_s2_frbc_scheduler(setup_frbc_asset): resolution=pd.Timedelta("PT1H"), flex_model={}, # S2Scheduler fetches this from asset attributes flex_context={ - "target-profile": {}, # todo: port target profile from Java test + "target-profile": JouleProfileTarget, # todo: port target profile from Java test }, ) results = scheduler.compute() From c9468dcca3a20a2a7b180ff83b8a55e475177d18 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Tue, 21 Jan 2025 17:05:32 +0100 Subject: [PATCH 3/8] Added the device state as ported from Java Signed-off-by: Vlad Iftime --- flexmeasures_s2/conftest.py | 4 +- .../scheduler/s2_frbc_device_state.py | 10 + flexmeasures_s2/scheduler/test_frbc_device.py | 127 ++++++++ flexmeasures_s2/scheduler/tests/test.java | 287 ++++++++++++++++++ 4 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 flexmeasures_s2/scheduler/s2_frbc_device_state.py create mode 100644 flexmeasures_s2/scheduler/test_frbc_device.py create mode 100644 flexmeasures_s2/scheduler/tests/test.java diff --git a/flexmeasures_s2/conftest.py b/flexmeasures_s2/conftest.py index 0fe56ba..3646db3 100644 --- a/flexmeasures_s2/conftest.py +++ b/flexmeasures_s2/conftest.py @@ -14,6 +14,8 @@ from flexmeasures_s2 import S2_SCHEDULER_SPECS from flexmeasures_s2.models.const import FRBC_TYPE +from flexmeasures_s2.scheduler.test_frbc_device import test_device_state # noqa: F401 + @pytest.fixture(scope="session") def app(): @@ -58,7 +60,7 @@ def setup_frbc_asset(db: SQLAlchemy, setup_admin): # noqa: F811 asset.attributes = { "custom-scheduler": S2_SCHEDULER_SPECS, "flex-model": { - "S2-FRBC-device-state": {}, # todo: add serialized state + "S2-FRBC-device-state": test_device_state, # ?todo: add serialized state }, } db.session.add(asset) diff --git a/flexmeasures_s2/scheduler/s2_frbc_device_state.py b/flexmeasures_s2/scheduler/s2_frbc_device_state.py new file mode 100644 index 0000000..4553f0a --- /dev/null +++ b/flexmeasures_s2/scheduler/s2_frbc_device_state.py @@ -0,0 +1,10 @@ +from s2python.frbc import ( + FRBCSystemDescription, +) + +from typing import List +from pydantic import BaseModel + + +class S2FrbcDeviceState(BaseModel): + system_descriptions: List[FRBCSystemDescription] diff --git a/flexmeasures_s2/scheduler/test_frbc_device.py b/flexmeasures_s2/scheduler/test_frbc_device.py new file mode 100644 index 0000000..6195075 --- /dev/null +++ b/flexmeasures_s2/scheduler/test_frbc_device.py @@ -0,0 +1,127 @@ +import datetime +import uuid + +from s2_frbc_device_state import S2FrbcDeviceState + +from s2python.frbc import ( + FRBCSystemDescription, + FRBCActuatorDescription, + FRBCOperationMode, + FRBCOperationModeElement, + FRBCStorageDescription, +) + +from s2python.common import ( + Commodity, + Transition, + Timer, + NumberRange, + PowerRange, + CommodityQuantity, +) + + +# Define the test object +test_device_state = S2FrbcDeviceState( + system_descriptions=[ + FRBCSystemDescription( + valid_from=datetime.datetime.now(tz=datetime.timezone.utc), + actuators=[ + FRBCActuatorDescription( + id=uuid.uuid4(), + diagnostic_label="charge", + supported_commodities=[Commodity.ELECTRICITY], + operation_modes=[ + FRBCOperationMode( + id="charge.on", + diagnostic_label="charge.on", + elements=[ + FRBCOperationModeElement( + fill_level_range=NumberRange( + start_of_range=0.0, + end_of_range=100.0, + ), + fill_rate=NumberRange( + start_of_range=0.0054012349, + end_of_range=0.0054012349, + ), + power_ranges=[ + PowerRange( + start_of_range=28000.0, + end_of_range=28000.0, + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + ) + ], + ) + ], + abnormal_condition_only=False, + ), + FRBCOperationMode( + id="charge.off", + diagnostic_label="charge.off", + elements=[ + FRBCOperationModeElement( + fill_level_range=NumberRange( + start_of_range=0, + end_of_range=100, + ), + fill_rate=NumberRange( + start_of_range=0, + end_of_range=0, + ), + power_ranges=[ + PowerRange( + start_of_range=0, + end_of_range=0, + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + ) + ], + running_costs=None, + ) + ], + abnormal_condition_only=False, + ), + ], + transitions=[ + Transition( + id="off.to.on", + from_="charge.off", + to="charge.on", + start_timers=["on.to.off.timer"], + blocking_timers=["off.to.on.timer"], + abnormal_condition_only=False, + ), + Transition( + id="on.to.off", + from_="charge.on", + to="charge.off", + start_timers=["off.to.on.timer"], + blocking_timers=["on.to.off.timer"], + abnormal_condition_only=False, + ), + ], + timers=[ + Timer( + id="on.to.off.timer", + diagnostic_label="on.to.off.timer", + duration=30, + ), + Timer( + id="off.to.on.timer", + diagnostic_label="off.to.on.timer", + duration=30, + ), + ], + ) + ], + storage=FRBCStorageDescription( + diagnostic_label="battery", + fill_level_label="SoC %", + provides_leakage_behaviour=False, + provides_fill_level_target_profile=True, + provides_usage_forecast=False, + fill_level_range=NumberRange(start_of_range=0, end_of_range=100), + ), + ) + ], +) diff --git a/flexmeasures_s2/scheduler/tests/test.java b/flexmeasures_s2/scheduler/tests/test.java new file mode 100644 index 0000000..8529e05 --- /dev/null +++ b/flexmeasures_s2/scheduler/tests/test.java @@ -0,0 +1,287 @@ +S2FrbcDeviceState(systemDescriptions=[class FRBCSystemDescription { + validFrom: 1970-01-01T02:00+01:00 + actuators: [class FRBCActuatorDescription { + id: 74d81672-4698-4d92-bc26-12d15369a428 + diagnosticLabel: charge + supportedCommodities: [ELECTRICITY] + status: class FRBCActuatorStatus { + activeOperationModeId: charge.on + operationModeFactor: 0 + previousOperationModeId: null + transitionTimestamp: null + } + operationModes: [class FRBCOperationMode { + id: charge.on + diagnosticLabel: charge.on + elements: [class FRBCOperationModeElement { + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + fillRate: class CommonNumberRange { + startOfRange: 0.0054012349 + endOfRange: 0.0054012349 + } + powerRanges: [class CommonPowerRange { + startOfRange: 28000.0 + endOfRange: 28000.0 + commodityQuantity: ELECTRIC.POWER.L1 + }] + runningCosts: null + }] + abnormalConditionOnly: false + }, class FRBCOperationMode { + id: charge.off + diagnosticLabel: charge.off + elements: [class FRBCOperationModeElement { + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + fillRate: class CommonNumberRange { + startOfRange: 0 + endOfRange: 0 + } + powerRanges: [class CommonPowerRange { + startOfRange: 0 + endOfRange: 0 + commodityQuantity: ELECTRIC.POWER.L1 + }] + runningCosts: null + }] + abnormalConditionOnly: false + }] + transitions: [class CommonTransition { + id: off.to.on + from: charge.off + to: charge.on + startTimers: [on.to.off.timer] + blockingTimers: [off.to.on.timer] + transitionCosts: null + transitionDuration: null + abnormalConditionOnly: false + }, class CommonTransition { + id: on.to.off + from: charge.on + to: charge.off + startTimers: [off.to.on.timer] + blockingTimers: [on.to.off.timer] + transitionCosts: null + transitionDuration: null + abnormalConditionOnly: false + }] + timers: [class CommonTimer { + id: on.to.off.timer + diagnosticLabel: on.to.off.timer + duration: 30 + finishedAt: -999999999-01-01T00:00+18:00 + }, class CommonTimer { + id: off.to.on.timer + diagnosticLabel: off.to.on.timer + duration: 30 + finishedAt: -999999999-01-01T00:00+18:00 + }] + }] + storage: class FRBCStorageDescription { + diagnosticLabel: battery + fillLevelLabel: SoC % + providesLeakageBehaviour: false + providesFillLevelTargetProfile: true + providesUsageForecast: false + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + status: class FRBCStorageStatus { + presentFillLevel: 0.0 + } + leakageBehaviour: null + } +}, class FRBCSystemDescription { + validFrom: 1970-01-01T09:13+01:00 + actuators: [class FRBCActuatorDescription { + id: e0ddc962-e865-4d85-bff5-17a2c14bcec6 + diagnosticLabel: off + supportedCommodities: [ELECTRICITY] + status: class FRBCActuatorStatus { + activeOperationModeId: off + operationModeFactor: 0 + previousOperationModeId: null + transitionTimestamp: null + } + operationModes: [class FRBCOperationMode { + id: off + diagnosticLabel: off + elements: [class FRBCOperationModeElement { + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + fillRate: class CommonNumberRange { + startOfRange: 0 + endOfRange: 0 + } + powerRanges: [class CommonPowerRange { + startOfRange: 0 + endOfRange: 0 + commodityQuantity: ELECTRIC.POWER.L1 + }] + runningCosts: null + }] + abnormalConditionOnly: false + }] + transitions: [] + timers: [] + }] + storage: class FRBCStorageDescription { + diagnosticLabel: battery + fillLevelLabel: SoC % + providesLeakageBehaviour: false + providesFillLevelTargetProfile: false + providesUsageForecast: true + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + status: class FRBCStorageStatus { + presentFillLevel: 100.0 + } + leakageBehaviour: null + } +}, class FRBCSystemDescription { + validFrom: 1970-01-01T13:49+01:00 + actuators: [class FRBCActuatorDescription { + id: 751551e9-acd0-4ec8-9da8-377f9faa5e36 + diagnosticLabel: charge + supportedCommodities: [ELECTRICITY] + status: class FRBCActuatorStatus { + activeOperationModeId: charge.on + operationModeFactor: 0 + previousOperationModeId: null + transitionTimestamp: null + } + operationModes: [class FRBCOperationMode { + id: charge.on + diagnosticLabel: charge.on + elements: [class FRBCOperationModeElement { + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + fillRate: class CommonNumberRange { + startOfRange: 0.01099537114 + endOfRange: 0.01099537114 + } + powerRanges: [class CommonPowerRange { + startOfRange: 57000.0 + endOfRange: 57000.0 + commodityQuantity: ELECTRIC.POWER.L1 + }] + runningCosts: null + }] + abnormalConditionOnly: false + }, class FRBCOperationMode { + id: charge.off + diagnosticLabel: charge.off + elements: [class FRBCOperationModeElement { + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + fillRate: class CommonNumberRange { + startOfRange: 0 + endOfRange: 0 + } + powerRanges: [class CommonPowerRange { + startOfRange: 0 + endOfRange: 0 + commodityQuantity: ELECTRIC.POWER.L1 + }] + runningCosts: null + }] + abnormalConditionOnly: false + }] + transitions: [class CommonTransition { + id: off.to.on + from: charge.off + to: charge.on + startTimers: [on.to.off.timer] + blockingTimers: [off.to.on.timer] + transitionCosts: null + transitionDuration: null + abnormalConditionOnly: false + }, class CommonTransition { + id: on.to.off + from: charge.on + to: charge.off + startTimers: [off.to.on.timer] + blockingTimers: [on.to.off.timer] + transitionCosts: null + transitionDuration: null + abnormalConditionOnly: false + }] + timers: [class CommonTimer { + id: on.to.off.timer + diagnosticLabel: on.to.off.timer + duration: 30 + finishedAt: -999999999-01-01T00:00+18:00 + }, class CommonTimer { + id: off.to.on.timer + diagnosticLabel: off.to.on.timer + duration: 30 + finishedAt: -999999999-01-01T00:00+18:00 + }] + }] + storage: class FRBCStorageDescription { + diagnosticLabel: battery + fillLevelLabel: SoC % + providesLeakageBehaviour: false + providesFillLevelTargetProfile: true + providesUsageForecast: false + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + status: class FRBCStorageStatus { + presentFillLevel: 37.7463951 + } + leakageBehaviour: null + } +}], leakageBehaviours=[class FRBCLeakageBehaviour { + validFrom: 1970-01-01T02:00+01:00 + elements: [class FRBCLeakageBehaviourElement { + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + leakageRate: 0 + }] +}, class FRBCLeakageBehaviour { + validFrom: 1970-01-01T13:49+01:00 + elements: [class FRBCLeakageBehaviourElement { + fillLevelRange: class CommonNumberRange { + startOfRange: 0 + endOfRange: 100 + } + leakageRate: 0 + }] +}], usageForecasts=[class FRBCUsageForecast { + startTime: 1970-01-01T02:00+01:00 + elements: [class FRBCUsageForecastElement { + duration: 25980 + usageRateUpperLimit: 0 + usageRateUpper95PPR: 0 + usageRateUpper68PPR: 0 + usageRateExpected: 0 + usageRateLower68PPR: 0 + usageRateLower95PPR: 0 + usageRateLowerLimit: 0 + }] +}, class FRBCUsageForecast { + startTime: 1970-01-01T09:13+01:00 + elements: [class FRBCUsageForecastElement { + duration: 16560 + usageRateUpperLimit: null + usageRateUpper95PPR: null + usageRateUpper68PPR: null + usageRateExpected: -0.00... \ No newline at end of file From e0694ca0345143b4d470bb2a31ab2722c35df402 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Wed, 22 Jan 2025 09:17:30 +0100 Subject: [PATCH 4/8] Added serialised JSON for FRBC device state Signed-off-by: Vlad Iftime --- .../scheduler/test_frbc_device.json | 123 ++++++++ flexmeasures_s2/scheduler/test_frbc_device.py | 13 +- .../scheduler/tests/JouleProfileOriginal.py | 290 ------------------ flexmeasures_s2/scheduler/tests/test_frbc.py | 4 +- 4 files changed, 134 insertions(+), 296 deletions(-) create mode 100644 flexmeasures_s2/scheduler/test_frbc_device.json delete mode 100644 flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py diff --git a/flexmeasures_s2/scheduler/test_frbc_device.json b/flexmeasures_s2/scheduler/test_frbc_device.json new file mode 100644 index 0000000..ef38b22 --- /dev/null +++ b/flexmeasures_s2/scheduler/test_frbc_device.json @@ -0,0 +1,123 @@ +{ + "system_descriptions": [ + { + "message_type": "FRBC.SystemDescription", + "message_id": "f6769b77-3d85-4d61-8a28-3bd70fc5cef4", + "valid_from": "2025-01-21 17:52:08.121054+00:00", + "actuators": [ + { + "id": "1ebd596b-3031-403f-95a1-9520550b161b", + "diagnostic_label": "charge", + "supported_commodities": [ + "Commodity.ELECTRICITY" + ], + "operation_modes": [ + { + "id": "charge.on", + "diagnostic_label": "charge.on", + "elements": [ + { + "fill_level_range": { + "start_of_range": 0.0, + "end_of_range": 100.0 + }, + "fill_rate": { + "start_of_range": 0.0054012349, + "end_of_range": 0.0054012349 + }, + "power_ranges": [ + { + "start_of_range": 28000.0, + "end_of_range": 28000.0, + "commodity_quantity": "CommodityQuantity.ELECTRIC_POWER_L1" + } + ], + "running_costs": null + } + ], + "abnormal_condition_only": false + }, + { + "id": "charge.off", + "diagnostic_label": "charge.off", + "elements": [ + { + "fill_level_range": { + "start_of_range": 0.0, + "end_of_range": 100.0 + }, + "fill_rate": { + "start_of_range": 0.0, + "end_of_range": 0.0 + }, + "power_ranges": [ + { + "start_of_range": 0.0, + "end_of_range": 0.0, + "commodity_quantity": "CommodityQuantity.ELECTRIC_POWER_L1" + } + ], + "running_costs": null + } + ], + "abnormal_condition_only": false + } + ], + "transitions": [ + { + "id": "off.to.on", + "from_": "charge.off", + "to": "charge.on", + "start_timers": [ + "on.to.off.timer" + ], + "blocking_timers": [ + "off.to.on.timer" + ], + "transition_costs": null, + "transition_duration": null, + "abnormal_condition_only": false + }, + { + "id": "on.to.off", + "from_": "charge.on", + "to": "charge.off", + "start_timers": [ + "off.to.on.timer" + ], + "blocking_timers": [ + "on.to.off.timer" + ], + "transition_costs": null, + "transition_duration": null, + "abnormal_condition_only": false + } + ], + "timers": [ + { + "id": "on.to.off.timer", + "diagnostic_label": "on.to.off.timer", + "duration": 30 + }, + { + "id": "off.to.on.timer", + "diagnostic_label": "off.to.on.timer", + "duration": 30 + } + ] + } + ], + "storage": { + "diagnostic_label": "battery", + "fill_level_label": "SoC %", + "provides_leakage_behaviour": false, + "provides_fill_level_target_profile": true, + "provides_usage_forecast": false, + "fill_level_range": { + "start_of_range": 0.0, + "end_of_range": 100.0 + } + } + } + ] +} \ No newline at end of file diff --git a/flexmeasures_s2/scheduler/test_frbc_device.py b/flexmeasures_s2/scheduler/test_frbc_device.py index 6195075..5508712 100644 --- a/flexmeasures_s2/scheduler/test_frbc_device.py +++ b/flexmeasures_s2/scheduler/test_frbc_device.py @@ -1,7 +1,7 @@ import datetime import uuid -from s2_frbc_device_state import S2FrbcDeviceState +from flexmeasures_s2.scheduler.s2_frbc_device_state import S2FrbcDeviceState from s2python.frbc import ( FRBCSystemDescription, @@ -25,10 +25,11 @@ test_device_state = S2FrbcDeviceState( system_descriptions=[ FRBCSystemDescription( + message_id=str(uuid.uuid4()), valid_from=datetime.datetime.now(tz=datetime.timezone.utc), actuators=[ FRBCActuatorDescription( - id=uuid.uuid4(), + id=str(uuid.uuid4()), # Ensure id is a string diagnostic_label="charge", supported_commodities=[Commodity.ELECTRICITY], operation_modes=[ @@ -85,7 +86,9 @@ transitions=[ Transition( id="off.to.on", - from_="charge.off", + **{ + "from": "charge.off" + }, # Use a workaround to set 'from' since it's a keyword in Python, to="charge.on", start_timers=["on.to.off.timer"], blocking_timers=["off.to.on.timer"], @@ -93,7 +96,9 @@ ), Transition( id="on.to.off", - from_="charge.on", + **{ + "from": "charge.on" + }, # Use a workaround to set 'from' since it's a keyword in Python, to="charge.off", start_timers=["off.to.on.timer"], blocking_timers=["on.to.off.timer"], diff --git a/flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py b/flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py deleted file mode 100644 index 3dcc247..0000000 --- a/flexmeasures_s2/scheduler/tests/JouleProfileOriginal.py +++ /dev/null @@ -1,290 +0,0 @@ -JouleProfileTarget = [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 8400000, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 17100000, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, -] diff --git a/flexmeasures_s2/scheduler/tests/test_frbc.py b/flexmeasures_s2/scheduler/tests/test_frbc.py index 1ba199f..84757e3 100644 --- a/flexmeasures_s2/scheduler/tests/test_frbc.py +++ b/flexmeasures_s2/scheduler/tests/test_frbc.py @@ -1,7 +1,7 @@ import pandas as pd from flexmeasures_s2.scheduler.schedulers import S2Scheduler -from JouleProfileOriginal import JouleProfileTarget +from flexmeasures_s2.scheduler.tests.joule_profile import get_JouleProfileTarget def test_s2_frbc_scheduler(setup_frbc_asset): @@ -12,7 +12,7 @@ def test_s2_frbc_scheduler(setup_frbc_asset): resolution=pd.Timedelta("PT1H"), flex_model={}, # S2Scheduler fetches this from asset attributes flex_context={ - "target-profile": JouleProfileTarget, # todo: port target profile from Java test + "target-profile": get_JouleProfileTarget(), # todo: port target profile from Java test }, ) results = scheduler.compute() From 20ef8c801a04f7bbe5549d5fa3f9a580bc0acee6 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Fri, 24 Jan 2025 15:02:59 +0100 Subject: [PATCH 5/8] Added the target joule profile Signed-off-by: Vlad Iftime --- .../scheduler/tests/joule_profile.py | 297 ++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 flexmeasures_s2/scheduler/tests/joule_profile.py diff --git a/flexmeasures_s2/scheduler/tests/joule_profile.py b/flexmeasures_s2/scheduler/tests/joule_profile.py new file mode 100644 index 0000000..2080332 --- /dev/null +++ b/flexmeasures_s2/scheduler/tests/joule_profile.py @@ -0,0 +1,297 @@ +from typing import List + + +JouleProfileTarget: List = [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 8400000, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 17100000, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +] + + +def get_JouleProfileTarget() -> List: + return JouleProfileTarget From c022c93ea070646914a88e94ebe6da6517e56967 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Fri, 24 Jan 2025 14:28:38 +0100 Subject: [PATCH 6/8] Added deserialisation test in test_frbc Signed-off-by: Vlad Iftime --- flexmeasures_s2/scheduler/test_frbc_device.py | 47 +++++++++++++------ flexmeasures_s2/scheduler/tests/test_frbc.py | 15 ++++++ .../scheduler/tests/test_schemas.py | 3 ++ 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/flexmeasures_s2/scheduler/test_frbc_device.py b/flexmeasures_s2/scheduler/test_frbc_device.py index 5508712..ef0614c 100644 --- a/flexmeasures_s2/scheduler/test_frbc_device.py +++ b/flexmeasures_s2/scheduler/test_frbc_device.py @@ -20,9 +20,26 @@ CommodityQuantity, ) +example_serialized_device_state = None + +# Load a JSON serialized FRBC device state from a file +with open("test_frbc_device.json", "r") as file: + example_serialized_device_state = file.read() + + +# Define specific ids +charge_on_id = str(uuid.uuid4()) +charge_off_id = str(uuid.uuid4()) + +on_to_off = str(uuid.uuid4()) +off_to_on = str(uuid.uuid4()) + +on_to_off_timer_id = str(uuid.uuid4()) +off_to_on_timer_id = str(uuid.uuid4()) + # Define the test object -test_device_state = S2FrbcDeviceState( +example_deserialized_device_state = S2FrbcDeviceState( system_descriptions=[ FRBCSystemDescription( message_id=str(uuid.uuid4()), @@ -34,7 +51,7 @@ supported_commodities=[Commodity.ELECTRICITY], operation_modes=[ FRBCOperationMode( - id="charge.on", + id=charge_on_id, diagnostic_label="charge.on", elements=[ FRBCOperationModeElement( @@ -58,7 +75,7 @@ abnormal_condition_only=False, ), FRBCOperationMode( - id="charge.off", + id=charge_off_id, diagnostic_label="charge.off", elements=[ FRBCOperationModeElement( @@ -85,34 +102,34 @@ ], transitions=[ Transition( - id="off.to.on", + id=off_to_on, **{ - "from": "charge.off" + "from": charge_off_id }, # Use a workaround to set 'from' since it's a keyword in Python, - to="charge.on", - start_timers=["on.to.off.timer"], - blocking_timers=["off.to.on.timer"], + to=charge_on_id, + start_timers=[on_to_off_timer_id], + blocking_timers=[off_to_on_timer_id], abnormal_condition_only=False, ), Transition( - id="on.to.off", + id=on_to_off, **{ - "from": "charge.on" + "from": charge_on_id }, # Use a workaround to set 'from' since it's a keyword in Python, - to="charge.off", - start_timers=["off.to.on.timer"], - blocking_timers=["on.to.off.timer"], + to=charge_off_id, + start_timers=[off_to_on_timer_id], + blocking_timers=[on_to_off_timer_id], abnormal_condition_only=False, ), ], timers=[ Timer( - id="on.to.off.timer", + id=on_to_off_timer_id, diagnostic_label="on.to.off.timer", duration=30, ), Timer( - id="off.to.on.timer", + id=off_to_on_timer_id, diagnostic_label="off.to.on.timer", duration=30, ), diff --git a/flexmeasures_s2/scheduler/tests/test_frbc.py b/flexmeasures_s2/scheduler/tests/test_frbc.py index 84757e3..5059723 100644 --- a/flexmeasures_s2/scheduler/tests/test_frbc.py +++ b/flexmeasures_s2/scheduler/tests/test_frbc.py @@ -2,6 +2,9 @@ from flexmeasures_s2.scheduler.schedulers import S2Scheduler from flexmeasures_s2.scheduler.tests.joule_profile import get_JouleProfileTarget +from flexmeasures_s2.scheduler.test_frbc_device import ( + example_deserialized_device_state, +) def test_s2_frbc_scheduler(setup_frbc_asset): @@ -17,3 +20,15 @@ def test_s2_frbc_scheduler(setup_frbc_asset): ) results = scheduler.compute() assert results == "todo: check for expected results" + + +def test_s2_frbc_deserialise(setup_frbc_asset): + scheduler = S2Scheduler( + setup_frbc_asset, + start=pd.Timestamp("2025-01-20T13:00+01"), + end=pd.Timestamp("2025-01-20T19:00+01"), + resolution=pd.Timedelta("PT1H"), + flex_model={}, # S2Scheduler fetches this from asset attributes + flex_context={}, + ) + assert scheduler.deserialize_config() == example_deserialized_device_state diff --git a/flexmeasures_s2/scheduler/tests/test_schemas.py b/flexmeasures_s2/scheduler/tests/test_schemas.py index 0f61a4d..9b52e35 100644 --- a/flexmeasures_s2/scheduler/tests/test_schemas.py +++ b/flexmeasures_s2/scheduler/tests/test_schemas.py @@ -1,6 +1,9 @@ import pytest +import pandas as pd from flexmeasures_s2.scheduler.schemas import TNOTargetProfile +from flexmeasures_s2.scheduler.schedulers import S2Scheduler +from flexmeasures_s2.scheduler.tests.joule_profile import get_JouleProfileTarget @pytest.mark.parametrize( From 43942a09faeb83a1942149479b8193876a1d563f Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Fri, 24 Jan 2025 14:30:33 +0100 Subject: [PATCH 7/8] Added deserialisation test in test_frbc Signed-off-by: Vlad Iftime --- flexmeasures_s2/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flexmeasures_s2/conftest.py b/flexmeasures_s2/conftest.py index 3646db3..1d69c0c 100644 --- a/flexmeasures_s2/conftest.py +++ b/flexmeasures_s2/conftest.py @@ -14,7 +14,9 @@ from flexmeasures_s2 import S2_SCHEDULER_SPECS from flexmeasures_s2.models.const import FRBC_TYPE -from flexmeasures_s2.scheduler.test_frbc_device import test_device_state # noqa: F401 +from flexmeasures_s2.scheduler.test_frbc_device import ( + example_serialized_device_state, +) # noqa: F401 @pytest.fixture(scope="session") @@ -60,7 +62,7 @@ def setup_frbc_asset(db: SQLAlchemy, setup_admin): # noqa: F811 asset.attributes = { "custom-scheduler": S2_SCHEDULER_SPECS, "flex-model": { - "S2-FRBC-device-state": test_device_state, # ?todo: add serialized state + "S2-FRBC-device-state": example_serialized_device_state, # ?todo: add serialized state }, } db.session.add(asset) From da138ab0c3ee72a382afddf164be1df013df7501 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Fri, 24 Jan 2025 14:31:50 +0100 Subject: [PATCH 8/8] Fix: flake and black formatting Signed-off-by: Vlad Iftime --- flexmeasures_s2/scheduler/schemas.py | 3 +-- flexmeasures_s2/scheduler/tests/test_schemas.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/flexmeasures_s2/scheduler/schemas.py b/flexmeasures_s2/scheduler/schemas.py index bed6480..03f98f2 100644 --- a/flexmeasures_s2/scheduler/schemas.py +++ b/flexmeasures_s2/scheduler/schemas.py @@ -3,8 +3,7 @@ from marshmallow import Schema, fields -class S2FlexModelSchema(Schema): - ... +class S2FlexModelSchema(Schema): ... class TNOTargetProfile(Schema): diff --git a/flexmeasures_s2/scheduler/tests/test_schemas.py b/flexmeasures_s2/scheduler/tests/test_schemas.py index 9b52e35..0f61a4d 100644 --- a/flexmeasures_s2/scheduler/tests/test_schemas.py +++ b/flexmeasures_s2/scheduler/tests/test_schemas.py @@ -1,9 +1,6 @@ import pytest -import pandas as pd from flexmeasures_s2.scheduler.schemas import TNOTargetProfile -from flexmeasures_s2.scheduler.schedulers import S2Scheduler -from flexmeasures_s2.scheduler.tests.joule_profile import get_JouleProfileTarget @pytest.mark.parametrize(