diff --git a/gsy_framework/read_user_profile.py b/gsy_framework/read_user_profile.py index a12d9970..a104bdcc 100644 --- a/gsy_framework/read_user_profile.py +++ b/gsy_framework/read_user_profile.py @@ -47,6 +47,13 @@ class InputProfileTypes(Enum): ENERGY_KWH = 4 +class LiveProfileTypes(Enum): + """Enum for type of live data profiles""" + NO_LIVE_DATA = 0 + FORECAST = 1 + MEASUREMENT = 2 + + def _str_to_datetime(time_string, time_format) -> DateTime: """ Converts time_string into a pendulum (DateTime) object that either takes the global start date @@ -325,6 +332,8 @@ def read_arbitrary_profile(profile_type: InputProfileTypes, :param current_timestamp: :return: a mapping from time to profile values """ + if input_profile in [{}, None]: + return {} profile = _read_from_different_sources_todict(input_profile, current_timestamp=current_timestamp) profile_time_list = list(profile.keys()) diff --git a/gsy_framework/schema/avro_schemas/launch_simulation_scenario.json b/gsy_framework/schema/avro_schemas/launch_simulation_scenario.json index 11f611b0..e1656cd0 100644 --- a/gsy_framework/schema/avro_schemas/launch_simulation_scenario.json +++ b/gsy_framework/schema/avro_schemas/launch_simulation_scenario.json @@ -157,6 +157,7 @@ ]}, {"name": "power_profile", "type": ["null", "string"]}, {"name": "power_profile_uuid", "type": ["null", "string"]}, + {"name": "power_measurement_uuid", "type": ["null", "string"]}, {"name": "capacity_kW", "type": ["null", "float"]}, {"name": "tilt", "type": ["null", "float"]}, {"name": "azimuth", "type": ["null", "float"]}, @@ -195,6 +196,7 @@ {"name": "energy_rate_decrease_per_update", "type": ["null", "float"]}, {"name": "power_profile", "type": ["null", "string"]}, {"name": "power_profile_uuid", "type": ["null", "string"]}, + {"name": "power_measurement_uuid", "type": ["null", "string"]}, {"name": "update_interval", "type": ["null", "int"]}, {"name": "fit_to_limit", "type": ["null","boolean"]}, {"name": "capacity_kW", "type": ["null", "float"]}, @@ -227,6 +229,7 @@ {"name": "target_device_kpi", "type": ["null", "int"]}, {"name": "daily_load_profile", "type": ["null", "string"]}, {"name": "daily_load_profile_uuid", "type": ["null", "string"]}, + {"name": "daily_load_measurement_uuid", "type": ["null", "string"]}, {"name": "use_market_maker_rate", "type": ["null", "boolean"]}, {"name": "forecast_stream_enabled", "type": ["null", "boolean"]}, {"name": "forecast_stream_id", "type": ["null", "string"]}, @@ -315,6 +318,7 @@ ]}, {"name": "prosumption_kWh_profile", "type": ["null", "string"]}, {"name": "prosumption_kWh_profile_uuid", "type": ["null", "string"]}, + {"name": "prosumption_kWh_measurement_uuid", "type": ["null", "string"]}, {"name": "use_market_maker_rate", "type": ["null", "boolean"]}, {"name": "allow_external_connection", "type": ["null", "boolean"]}, {"name": "display_type", "type": "string"}, @@ -338,6 +342,7 @@ ]}, {"name": "consumption_kWh_profile", "type": ["null", "string"]}, {"name": "consumption_kWh_profile_uuid", "type": ["null", "string"]}, + {"name": "consumption_kWh_measurement_uuid", "type": ["null", "string"]}, {"name": "use_market_maker_rate", "type": ["null", "boolean"]}, {"name": "allow_external_connection", "type": ["null", "boolean"]}, {"name": "display_type", "type": "string"}, @@ -363,6 +368,7 @@ ]}, {"name": "smart_meter_profile", "type": ["null", "string"]}, {"name": "smart_meter_profile_uuid", "type": ["null", "string"]}, + {"name": "smart_meter_measurement_uuid", "type": ["null", "string"]}, {"name": "final_buying_rate", "type": ["null", "float"]}, {"name": "initial_buying_rate", "type": ["null", "float"]}, {"name": "final_selling_rate", "type": ["null", "float"]}, @@ -398,9 +404,11 @@ {"name": "initial_temp_C", "type": "float"}, {"name": "external_temp_C_profile", "type": ["null", "string"]}, {"name": "external_temp_C_profile_uuid", "type": ["null", "string"]}, + {"name": "external_temp_C_measurement_uuid", "type": ["null", "string"]}, {"name": "tank_volume_l", "type": "float"}, {"name": "consumption_kWh_profile", "type": ["null", "string"]}, {"name": "consumption_kWh_profile_uuid", "type": ["null", "string"]}, + {"name": "consumption_kWh_measurement_uuid", "type": ["null", "string"]}, {"name": "source_type", "type": "int"}, {"name": "final_buying_rate", "type": ["null", "float"]}, {"name": "initial_buying_rate", "type": ["null", "float"]}, diff --git a/gsy_framework/sim_results/scm/bills.py b/gsy_framework/sim_results/scm/bills.py index a3ab74bb..c4f52743 100644 --- a/gsy_framework/sim_results/scm/bills.py +++ b/gsy_framework/sim_results/scm/bills.py @@ -63,7 +63,7 @@ def update(self, area_result_dict, core_stats, current_market_slot): if not area.get("children"): continue - if "bills" not in core_stats[area["uuid"]]: + if "bills" not in core_stats[area["uuid"]] or not core_stats[area["uuid"]]["bills"]: continue if area["uuid"] not in self.bills_redis_results: diff --git a/gsy_framework/utils.py b/gsy_framework/utils.py index 50633810..2de572bc 100644 --- a/gsy_framework/utils.py +++ b/gsy_framework/utils.py @@ -98,7 +98,7 @@ def generate_market_slot_list(start_timestamp=None): No input arguments, required input is only handled by a preconfigured GlobalConfig @return: List with market slot datetimes """ - time_span = (duration(days=PROFILE_EXPANSION_DAYS) + time_span = (GlobalConfig.slot_length if GlobalConfig.is_canary_network() else min(GlobalConfig.sim_duration, duration(days=PROFILE_EXPANSION_DAYS))) time_span += duration(hours=ConstSettings.FutureMarketSettings.FUTURE_MARKET_DURATION_HOURS) diff --git a/gsy_framework/validators/profile_validator.py b/gsy_framework/validators/profile_validator.py index 4131037c..ff48b37e 100644 --- a/gsy_framework/validators/profile_validator.py +++ b/gsy_framework/validators/profile_validator.py @@ -11,14 +11,18 @@ class ProfileValidator: def __init__( self, profile: Dict[DateTime, float], start_time: Optional[DateTime] = None, end_time: Optional[DateTime] = None, slot_length: Optional[timedelta] = None): - assert len(profile) > 0, "profile is empty" + self.profile = OrderedDict(profile) + if not self.profile: + return self.start_time = start_time if start_time else self._profile_start_time self.end_time = end_time if end_time else self._profile_end_time self.slot_length: timedelta = slot_length def validate(self): """Validate if profile corresponds to the start_time, end_time and slot_length setting.""" + if not self.profile: + return if self.slot_length: self._validate_slot_length() else: diff --git a/tests/test_read_user_profile.py b/tests/test_read_user_profile.py index 6de05f6f..0d79d95b 100644 --- a/tests/test_read_user_profile.py +++ b/tests/test_read_user_profile.py @@ -18,7 +18,7 @@ import pytest from pendulum import datetime, duration, today -from gsy_framework.constants_limits import PROFILE_EXPANSION_DAYS, TIME_ZONE, GlobalConfig +from gsy_framework.constants_limits import TIME_ZONE, GlobalConfig from gsy_framework.enums import ConfigurationType from gsy_framework.read_user_profile import ( InputProfileTypes, _fill_gaps_in_profile, _generate_slot_based_zero_values_dict_from_profile, @@ -122,16 +122,8 @@ def test_read_profile_for_player(): def test_read_arbitrary_profile_returns_correct_profile_in_canary_network( set_is_canary_network): set_is_canary_network(True) - market_maker_rate = 30 - GlobalConfig.sim_duration = duration(hours=3) - expected_last_time_slot = today(tz=TIME_ZONE).add(days=PROFILE_EXPANSION_DAYS - 1, - hours=23, minutes=45) - mmr = read_arbitrary_profile(InputProfileTypes.IDENTITY, market_maker_rate) - assert list(mmr.keys())[-1] == expected_last_time_slot - GlobalConfig.sim_duration = duration(hours=30) - expected_last_time_slot = today(tz=TIME_ZONE).add(days=PROFILE_EXPANSION_DAYS - 1, - hours=23, minutes=45) - mmr = read_arbitrary_profile(InputProfileTypes.IDENTITY, market_maker_rate) + expected_last_time_slot = today(tz=TIME_ZONE) + mmr = read_arbitrary_profile(InputProfileTypes.IDENTITY, 30) assert list(mmr.keys())[-1] == expected_last_time_slot @staticmethod @@ -192,3 +184,16 @@ def test_resample_energy_profile_performs_correctly_for_equal_resolutions(): duration(hours=4), datetime(2021, 1, 25, 0, 0)) assert result_profile == input_profile + + @staticmethod + def test_read_arbitrary_profile_returns_early_for_empty_profiles(): + original_slot_length = GlobalConfig.slot_length + original_sim_duration = GlobalConfig.sim_duration + GlobalConfig.slot_length = duration(hours=1) + GlobalConfig.sim_duration = duration(hours=4) + assert read_arbitrary_profile(InputProfileTypes.POWER_W, {}) == {} + assert read_arbitrary_profile(InputProfileTypes.POWER_W, None) == {} + assert len(read_arbitrary_profile(InputProfileTypes.POWER_W, 0)) == 4 + assert set(read_arbitrary_profile(InputProfileTypes.POWER_W, 0).values()) == {0} + GlobalConfig.slot_length = original_slot_length + GlobalConfig.sim_duration = original_sim_duration diff --git a/tests/test_validators/test_profile_validator.py b/tests/test_validators/test_profile_validator.py index 58cf0fe6..48cf6f82 100644 --- a/tests/test_validators/test_profile_validator.py +++ b/tests/test_validators/test_profile_validator.py @@ -1,4 +1,5 @@ from copy import copy +from unittest.mock import Mock import pytest @@ -8,6 +9,7 @@ class TestProfileValidator: + # pylint: disable=protected-access @staticmethod @pytest.mark.parametrize("input_profile, end_time", [ @@ -87,3 +89,15 @@ def test_profile_validator_fails_if_gap_in_profile_no_slot_length(input_profile) del profile[gap_time] with pytest.raises(AssertionError): ProfileValidator(profile=profile).validate() + + @staticmethod + def test_profile_validator_returns_early_if_empty_profile(): + validator = ProfileValidator(profile={}) + validator._validate_slot_length = Mock() + validator._get_and_validate_time_diffs = Mock() + validator._validate_start_end_date = Mock() + validator.slot_length = Mock() + assert validator.validate() is None + validator._validate_slot_length.assert_not_called() + validator._get_and_validate_time_diffs.assert_not_called() + validator._validate_start_end_date.assert_not_called()