diff --git a/src/gsy_e/constants.py b/src/gsy_e/constants.py
index 6e6bc627a..82e77aa38 100644
--- a/src/gsy_e/constants.py
+++ b/src/gsy_e/constants.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# Need to import required settings from gsy-framework in order to be available in d3a,
# thus avoiding accessing the gsy-framework constants.
# pylint: disable=unused-import
@@ -66,14 +67,19 @@
CONNECT_TO_PROFILES_DB = False
SEND_EVENTS_RESPONSES_TO_SDK_VIA_RQ = False
+RUN_IN_NON_P2P_MODE = False
+
DEFAULT_SCM_COMMUNITY_NAME = "Community"
DEFAULT_SCM_GRID_NAME = "Grid"
FORWARD_MARKET_MAX_DURATION_YEARS = 6
+MIN_OFFER_BID_AGE_P2P_DISABLED = 360
+
class SettlementTemplateStrategiesConstants:
"""Constants related to the configuration of settlement template strategies"""
+
INITIAL_BUYING_RATE = 0
FINAL_BUYING_RATE = 50
INITIAL_SELLING_RATE = 50
diff --git a/src/gsy_e/gsy_e_core/non_p2p_handler.py b/src/gsy_e/gsy_e_core/non_p2p_handler.py
new file mode 100644
index 000000000..41404db09
--- /dev/null
+++ b/src/gsy_e/gsy_e_core/non_p2p_handler.py
@@ -0,0 +1,54 @@
+from gsy_framework.constants_limits import GlobalConfig
+from gsy_framework.exceptions import GSyException
+
+
+class NonP2PHandler:
+ """Handles non-p2p case"""
+
+ def __init__(self, scenario: dict):
+ self.non_p2p_scenario = scenario
+ self._energy_sell_rate = 0.0
+ self._energy_buy_rate = 0.0
+ self._get_energy_rates_from_infinite_bus(scenario)
+ self._handle_non_p2p_scenario(scenario)
+
+ def _get_energy_rates_from_infinite_bus(self, scenario: dict):
+ for child in scenario["children"]:
+ if child.get("type") == "InfiniteBus":
+ self._energy_buy_rate = child.get("energy_buy_rate", GlobalConfig.FEED_IN_TARIFF)
+ self._energy_sell_rate = child.get(
+ "energy_sell_rate", GlobalConfig.MARKET_MAKER_RATE
+ )
+ return
+
+ raise GSyException(
+ "For non-p2p simulation, an InfiniteBus has to be present in the first "
+ "level of the configuration tree."
+ )
+
+ @staticmethod
+ def _is_home_area(area: dict):
+ return area.get("children") and all(
+ child.get("type", None) for child in area.get("children")
+ )
+
+ def _add_market_maker_to_home(self, area: dict):
+ if "children" not in area or not area["children"]:
+ return
+ if not self._is_home_area(area):
+ return
+ area["children"].append(
+ {
+ "name": "MarketMaker",
+ "type": "InfiniteBus",
+ "energy_buy_rate": self._energy_buy_rate,
+ "energy_sell_rate": self._energy_sell_rate,
+ }
+ )
+
+ def _handle_non_p2p_scenario(self, area: dict):
+ if "children" not in area or not area["children"]:
+ return
+ self._add_market_maker_to_home(area)
+ for child in area["children"]:
+ self._handle_non_p2p_scenario(child)
diff --git a/src/gsy_e/gsy_e_core/rq_job_handler.py b/src/gsy_e/gsy_e_core/rq_job_handler.py
index 14bf1480a..bf8224f28 100644
--- a/src/gsy_e/gsy_e_core/rq_job_handler.py
+++ b/src/gsy_e/gsy_e_core/rq_job_handler.py
@@ -14,6 +14,7 @@
from gsy_e.gsy_e_core.simulation import run_simulation
from gsy_e.gsy_e_core.util import update_advanced_settings
from gsy_e.models.config import SimulationConfig
+from gsy_e.gsy_e_core.non_p2p_handler import NonP2PHandler
logging.getLogger().setLevel(logging.ERROR)
logger = logging.getLogger(__name__)
@@ -21,24 +22,31 @@
# pylint: disable=too-many-branches, too-many-statements
-def launch_simulation_from_rq_job(scenario: Dict,
- settings: Optional[Dict],
- events: Optional[str],
- aggregator_device_mapping: Dict,
- saved_state: Dict,
- scm_properties: Dict,
- job_id: str,
- connect_to_profiles_db: bool = True):
+def launch_simulation_from_rq_job(
+ scenario: Dict,
+ settings: Optional[Dict],
+ events: Optional[str],
+ aggregator_device_mapping: Dict,
+ saved_state: Dict,
+ scm_properties: Dict,
+ job_id: str,
+ connect_to_profiles_db: bool = True,
+):
# pylint: disable=too-many-arguments, too-many-locals
"""Launch simulation from rq job."""
gsy_e.constants.CONFIGURATION_ID = scenario.pop("configuration_uuid", None)
try:
if not gsy_e.constants.CONFIGURATION_ID:
- raise Exception("configuration_uuid was not provided")
+ raise Exception(
+ "configuration_uuid was not provided"
+ ) # pylint disable=broad-exception-raised
- logger.error("Starting simulation with job_id: %s and configuration id: %s",
- job_id, gsy_e.constants.CONFIGURATION_ID)
+ logger.error(
+ "Starting simulation with job_id: %s and configuration id: %s",
+ job_id,
+ gsy_e.constants.CONFIGURATION_ID,
+ )
settings = _adapt_settings(settings)
@@ -47,21 +55,33 @@ def launch_simulation_from_rq_job(scenario: Dict,
_configure_constants_constsettings(scenario, settings, connect_to_profiles_db)
+ if gsy_e.constants.RUN_IN_NON_P2P_MODE:
+ scenario = NonP2PHandler(scenario).non_p2p_scenario
+
slot_length_realtime = (
duration(seconds=settings["slot_length_realtime"].seconds)
- if "slot_length_realtime" in settings else None)
+ if "slot_length_realtime" in settings
+ else None
+ )
scenario_name = "json_arg"
- kwargs = {"no_export": True,
- "seed": settings.get("random_seed", 0)}
+ kwargs = {"no_export": True, "seed": settings.get("random_seed", 0)}
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.COEFFICIENTS.value:
kwargs.update({"scm_properties": scm_properties})
past_slots_sim_state = _handle_scm_past_slots_simulation_run(
- scenario, settings, events, aggregator_device_mapping, saved_state, job_id,
- scenario_name, slot_length_realtime, kwargs)
+ scenario,
+ settings,
+ events,
+ aggregator_device_mapping,
+ saved_state,
+ job_id,
+ scenario_name,
+ slot_length_realtime,
+ kwargs,
+ )
if past_slots_sim_state is not None:
saved_state = past_slots_sim_state
@@ -69,39 +89,48 @@ def launch_simulation_from_rq_job(scenario: Dict,
# facilitate the state resume.
saved_state["general"]["sim_status"] = "running"
- config = _create_config_settings_object(
- scenario, settings, aggregator_device_mapping)
+ config = _create_config_settings_object(scenario, settings, aggregator_device_mapping)
if settings.get("type") == ConfigurationType.CANARY_NETWORK.value:
- config.start_date = (
- instance(
- datetime.combine(date.today(), datetime.min.time()),
- tz=gsy_e.constants.TIME_ZONE))
+ config.start_date = instance(
+ datetime.combine(date.today(), datetime.min.time()), tz=gsy_e.constants.TIME_ZONE
+ )
if ConstSettings.MASettings.MARKET_TYPE == SpotMarketTypeEnum.COEFFICIENTS.value:
- config.start_date = config.start_date.subtract(hours=settings["scm"]
- ["scm_cn_hours_of_delay"])
-
- run_simulation(setup_module_name=scenario_name,
- simulation_config=config,
- simulation_events=events,
- redis_job_id=job_id,
- saved_sim_state=saved_state,
- slot_length_realtime=slot_length_realtime,
- kwargs=kwargs)
-
- logger.info("Finishing simulation with job_id: %s and configuration id: %s",
- job_id, gsy_e.constants.CONFIGURATION_ID)
+ config.start_date = config.start_date.subtract(
+ hours=settings["scm"]["scm_cn_hours_of_delay"]
+ )
+
+ run_simulation(
+ setup_module_name=scenario_name,
+ simulation_config=config,
+ simulation_events=events,
+ redis_job_id=job_id,
+ saved_sim_state=saved_state,
+ slot_length_realtime=slot_length_realtime,
+ kwargs=kwargs,
+ )
+
+ logger.info(
+ "Finishing simulation with job_id: %s and configuration id: %s",
+ job_id,
+ gsy_e.constants.CONFIGURATION_ID,
+ )
# pylint: disable=broad-except
except Exception:
# pylint: disable=import-outside-toplevel
from gsy_e.gsy_e_core.redis_connections.simulation import publish_job_error_output
- logger.error("Error on jobId, %s, configuration id: %s",
- job_id, gsy_e.constants.CONFIGURATION_ID)
+
+ logger.error(
+ "Error on jobId, %s, configuration id: %s", job_id, gsy_e.constants.CONFIGURATION_ID
+ )
publish_job_error_output(job_id, traceback.format_exc())
- logger.error("Error on jobId, %s, configuration id: %s: error sent to gsy-web",
- job_id, gsy_e.constants.CONFIGURATION_ID)
+ logger.error(
+ "Error on jobId, %s, configuration id: %s: error sent to gsy-web",
+ job_id,
+ gsy_e.constants.CONFIGURATION_ID,
+ )
raise
@@ -119,7 +148,8 @@ def _adapt_settings(settings: Dict) -> Dict:
def _configure_constants_constsettings(
- scenario: Dict, settings: Dict, connect_to_profiles_db: bool):
+ scenario: Dict, settings: Dict, connect_to_profiles_db: bool
+):
assert isinstance(scenario, dict)
GlobalConfig.CONFIG_TYPE = settings.get("type")
@@ -127,11 +157,14 @@ def _configure_constants_constsettings(
if settings.get("type") == ConfigurationType.COLLABORATION.value:
gsy_e.constants.EXTERNAL_CONNECTION_WEB = True
- if settings.get("type") in [ConfigurationType.CANARY_NETWORK.value,
- ConfigurationType.B2B.value]:
+ if settings.get("type") in [
+ ConfigurationType.CANARY_NETWORK.value,
+ ConfigurationType.B2B.value,
+ ]:
gsy_e.constants.EXTERNAL_CONNECTION_WEB = True
gsy_e.constants.RUN_IN_REALTIME = (
- settings.get("type") == ConfigurationType.CANARY_NETWORK.value)
+ settings.get("type") == ConfigurationType.CANARY_NETWORK.value
+ )
if settings.get("type") == ConfigurationType.B2B.value:
ConstSettings.ForwardMarketSettings.ENABLE_FORWARD_MARKETS = True
@@ -149,30 +182,36 @@ def _configure_constants_constsettings(
if bid_offer_match_algo:
ConstSettings.MASettings.BID_OFFER_MATCH_TYPE = bid_offer_match_algo
- ConstSettings.SettlementMarketSettings.RELATIVE_STD_FROM_FORECAST_FLOAT = (
- settings.get(
- "relative_std_from_forecast_percent",
- ConstSettings.SettlementMarketSettings.RELATIVE_STD_FROM_FORECAST_FLOAT
- ))
+ ConstSettings.SettlementMarketSettings.RELATIVE_STD_FROM_FORECAST_FLOAT = settings.get(
+ "relative_std_from_forecast_percent",
+ ConstSettings.SettlementMarketSettings.RELATIVE_STD_FROM_FORECAST_FLOAT,
+ )
ConstSettings.SettlementMarketSettings.ENABLE_SETTLEMENT_MARKETS = settings.get(
"settlement_market_enabled",
- ConstSettings.SettlementMarketSettings.ENABLE_SETTLEMENT_MARKETS
+ ConstSettings.SettlementMarketSettings.ENABLE_SETTLEMENT_MARKETS,
)
gsy_e.constants.CONNECT_TO_PROFILES_DB = connect_to_profiles_db
+ if settings.get("p2p_enabled", True) is False:
+ ConstSettings.MASettings.MIN_BID_AGE = gsy_e.constants.MIN_OFFER_BID_AGE_P2P_DISABLED
+ ConstSettings.MASettings.MIN_OFFER_AGE = gsy_e.constants.MIN_OFFER_BID_AGE_P2P_DISABLED
+ gsy_e.constants.RUN_IN_NON_P2P_MODE = True
+
if settings.get("scm"):
ConstSettings.SCMSettings.MARKET_ALGORITHM = CoefficientAlgorithm(
- settings["scm"]["coefficient_algorithm"]).value
+ settings["scm"]["coefficient_algorithm"]
+ ).value
ConstSettings.SCMSettings.GRID_FEES_REDUCTION = settings["scm"]["grid_fees_reduction"]
- ConstSettings.SCMSettings.INTRACOMMUNITY_BASE_RATE_EUR = (
- settings["scm"]["intracommunity_rate_base_eur"])
+ ConstSettings.SCMSettings.INTRACOMMUNITY_BASE_RATE_EUR = settings["scm"][
+ "intracommunity_rate_base_eur"
+ ]
else:
assert spot_market_type is not SpotMarketTypeEnum.COEFFICIENTS.value
def _create_config_settings_object(
- scenario: Dict, settings: Dict, aggregator_device_mapping: Dict
+ scenario: Dict, settings: Dict, aggregator_device_mapping: Dict
) -> SimulationConfig:
config_settings = {
@@ -202,13 +241,9 @@ def _create_config_settings_object(
"market_maker_rate": settings.get(
"market_maker_rate", ConstSettings.GeneralSettings.DEFAULT_MARKET_MAKER_RATE
),
- "capacity_kW": settings.get(
- "capacity_kW", ConstSettings.PVSettings.DEFAULT_CAPACITY_KW
- ),
+ "capacity_kW": settings.get("capacity_kW", ConstSettings.PVSettings.DEFAULT_CAPACITY_KW),
"grid_fee_type": settings.get("grid_fee_type", GlobalConfig.grid_fee_type),
- "external_connection_enabled": settings.get(
- "external_connection_enabled", False
- ),
+ "external_connection_enabled": settings.get("external_connection_enabled", False),
"aggregator_device_mapping": aggregator_device_mapping,
"hours_of_delay": settings.get("scm", {}).get(
"hours_of_delay", ConstSettings.SCMSettings.HOURS_OF_DELAY
@@ -222,10 +257,15 @@ def _create_config_settings_object(
def _handle_scm_past_slots_simulation_run(
- scenario: Dict, settings: Optional[Dict], events: Optional[str],
- aggregator_device_mapping: Dict, saved_state: Dict, job_id: str,
- scenario_name: str, slot_length_realtime: Optional[duration],
- kwargs: Dict
+ scenario: Dict,
+ settings: Optional[Dict],
+ events: Optional[str],
+ aggregator_device_mapping: Dict,
+ saved_state: Dict,
+ job_id: str,
+ scenario_name: str,
+ slot_length_realtime: Optional[duration],
+ kwargs: Dict,
) -> Optional[Dict]:
# pylint: disable=too-many-arguments
"""
@@ -243,16 +283,15 @@ def _handle_scm_past_slots_simulation_run(
settings_copy = deepcopy(settings)
config = _create_config_settings_object(
- scenario_copy, settings_copy, aggregator_device_mapping)
+ scenario_copy, settings_copy, aggregator_device_mapping
+ )
# We are running SCM Canary Networks with some days of delay compared to realtime in order to
# compensate for delays in transmission of the asset measurements.
# Adding 4 hours of extra time to the SCM past slots simulation duration, in order to
# compensate for the runtime of the SCM past slots simulation and to not have any results gaps
# after this simulation run and the following Canary Network launch.
config.end_date = (
- now(tz=gsy_e.constants.TIME_ZONE)
- .subtract(hours=config.hours_of_delay)
- .add(hours=4)
+ now(tz=gsy_e.constants.TIME_ZONE).subtract(hours=config.hours_of_delay).add(hours=4)
)
config.sim_duration = config.end_date - config.start_date
GlobalConfig.sim_duration = config.sim_duration
@@ -264,6 +303,7 @@ def _handle_scm_past_slots_simulation_run(
redis_job_id=job_id,
saved_sim_state=saved_state,
slot_length_realtime=slot_length_realtime,
- kwargs=kwargs)
+ kwargs=kwargs,
+ )
gsy_e.constants.RUN_IN_REALTIME = True
return simulation_state
diff --git a/src/gsy_e/gsy_e_core/sim_results/endpoint_buffer.py b/src/gsy_e/gsy_e_core/sim_results/endpoint_buffer.py
index e3cac55b5..d63e87d2b 100644
--- a/src/gsy_e/gsy_e_core/sim_results/endpoint_buffer.py
+++ b/src/gsy_e/gsy_e_core/sim_results/endpoint_buffer.py
@@ -32,6 +32,7 @@
from gsy_framework.utils import get_json_dict_memory_allocation_size
from pendulum import DateTime
+import gsy_e.constants
from gsy_e.gsy_e_core.sim_results.offer_bids_trades_hr_stats import OfferBidTradeGraphStats
from gsy_e.gsy_e_core.util import (
get_feed_in_tariff_rate_from_config,
@@ -237,6 +238,8 @@ def _structure_results_from_area_object(target_area: "AreaBase") -> Dict:
area_dict = {}
area_dict["name"] = target_area.name
area_dict["uuid"] = target_area.uuid
+ if target_area.is_home_area and gsy_e.constants.RUN_IN_NON_P2P_MODE:
+ area_dict["non_p2p"] = True
area_dict["parent_uuid"] = (
target_area.parent.uuid if target_area.parent is not None else ""
)
diff --git a/src/gsy_e/models/area/area_base.py b/src/gsy_e/models/area/area_base.py
index 078d22c55..cc99af462 100644
--- a/src/gsy_e/models/area/area_base.py
+++ b/src/gsy_e/models/area/area_base.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from logging import getLogger
from typing import TYPE_CHECKING, List, Union, Optional
from uuid import uuid4
@@ -80,16 +81,19 @@ class AreaBase:
Base class for the Area model. Contains common behavior for both coefficient trading and
market trading.
"""
+
# pylint: disable=too-many-arguments,too-many-instance-attributes
- def __init__(self, name: str = None,
- children: List["Area"] = None,
- uuid: str = None,
- strategy: Optional[Union["BaseStrategy", "TradingStrategyBase"]] = None,
- config: SimulationConfig = None,
- grid_fee_percentage: float = None,
- grid_fee_constant: float = None):
- validate_area(grid_fee_constant=grid_fee_constant,
- grid_fee_percentage=grid_fee_percentage)
+ def __init__(
+ self,
+ name: str = None,
+ children: List["Area"] = None,
+ uuid: str = None,
+ strategy: Optional[Union["BaseStrategy", "TradingStrategyBase"]] = None,
+ config: SimulationConfig = None,
+ grid_fee_percentage: float = None,
+ grid_fee_constant: float = None,
+ ):
+ validate_area(grid_fee_constant=grid_fee_constant, grid_fee_percentage=grid_fee_percentage)
self.active = False
self.log = TaggedLogWrapper(log, name)
self.__name = name
@@ -100,9 +104,8 @@ def __init__(self, name: str = None,
children = []
children = [child for child in children if child is not None]
self.children = (
- AreaChildrenList(self, children)
- if children is not None
- else AreaChildrenList(self))
+ AreaChildrenList(self, children) if children is not None else AreaChildrenList(self)
+ )
for child in self.children:
child.parent = self
@@ -124,9 +127,11 @@ def trades(self) -> List["Trade"]:
return self.strategy.trades
def _set_grid_fees(self, grid_fee_const, grid_fee_percentage):
- grid_fee_type = self.config.grid_fee_type \
- if self.config is not None \
+ grid_fee_type = (
+ self.config.grid_fee_type
+ if self.config is not None
else ConstSettings.MASettings.GRID_FEE_TYPE
+ )
if grid_fee_type == 1:
grid_fee_percentage = None
elif grid_fee_type == 2:
@@ -165,8 +170,10 @@ def get_path_to_root_fees(self) -> float:
def get_grid_fee(self):
"""Return the current grid fee for the area."""
grid_fee_type = (
- self.config.grid_fee_type if self.config is not None
- else ConstSettings.MASettings.GRID_FEE_TYPE)
+ self.config.grid_fee_type
+ if self.config is not None
+ else ConstSettings.MASettings.GRID_FEE_TYPE
+ )
return self.grid_fee_constant if grid_fee_type == 1 else self.grid_fee_percentage
@@ -202,15 +209,18 @@ def area_reconfigure_event(self, **kwargs):
grid_fee_constant = (
kwargs["grid_fee_constant"]
if key_in_dict_and_not_none(kwargs, "grid_fee_constant")
- else self.grid_fee_constant)
+ else self.grid_fee_constant
+ )
grid_fee_percentage = (
kwargs["grid_fee_percentage"]
if key_in_dict_and_not_none(kwargs, "grid_fee_percentage")
- else self.grid_fee_percentage)
+ else self.grid_fee_percentage
+ )
try:
- validate_area(grid_fee_constant=grid_fee_constant,
- grid_fee_percentage=grid_fee_percentage)
+ validate_area(
+ grid_fee_constant=grid_fee_constant, grid_fee_percentage=grid_fee_percentage
+ )
except (GSyAreaException, GSyDeviceException) as ex:
log.error(ex)
@@ -228,10 +238,14 @@ def update_descendants_strategy_prices(self):
child.update_descendants_strategy_prices()
except GSyException:
log.exception("area.update_descendants_strategy_prices failed.")
- return
def get_results_dict(self):
"""Calculate the results dict for the coefficients trading."""
if self.strategy is not None:
return self.strategy.state.get_results_dict(self.current_market_time_slot)
return {}
+
+ @property
+ def is_home_area(self):
+ "Return if the area is a home area."
+ return self.children and all(child.strategy for child in self.children)
diff --git a/src/gsy_e/models/area/coefficient_area.py b/src/gsy_e/models/area/coefficient_area.py
index b08597c9a..e553d69d9 100644
--- a/src/gsy_e/models/area/coefficient_area.py
+++ b/src/gsy_e/models/area/coefficient_area.py
@@ -96,9 +96,6 @@ def area_reconfigure_event(self, **kwargs):
if self.strategy is not None:
self.strategy.area_reconfigure_event(**kwargs)
- def _is_home_area(self):
- return self.children and all(child.strategy for child in self.children)
-
def _calculate_home_after_meter_data(
self, current_time_slot: DateTime, scm_manager: "SCMManager"
) -> None:
@@ -130,14 +127,14 @@ def calculate_home_after_meter_data(
self, current_time_slot: DateTime, scm_manager: "SCMManager"
) -> None:
"""Recursive function that calculates the home after meter data."""
- if self._is_home_area():
+ if self.is_home_area:
self._calculate_home_after_meter_data(current_time_slot, scm_manager)
for child in sorted(self.children, key=lambda _: random()):
child.calculate_home_after_meter_data(current_time_slot, scm_manager)
def trigger_energy_trades(self, scm_manager: "SCMManager") -> None:
"""Recursive function that triggers energy trading on all children of the root area."""
- if self._is_home_area():
+ if self.is_home_area:
scm_manager.calculate_home_energy_bills(self.uuid)
for child in sorted(self.children, key=lambda _: random()):
child.trigger_energy_trades(scm_manager)
@@ -159,7 +156,7 @@ def change_home_coefficient_percentage(self, scm_manager: "SCMManager") -> None:
"""Recursive function that change home coefficient percentage based on energy need.
This method is for dynamic energy allocation algorithm.
"""
- if self._is_home_area():
+ if self.is_home_area:
self._change_home_coefficient_percentage(scm_manager)
for child in self.children:
child.change_home_coefficient_percentage(scm_manager)
diff --git a/tests/test_gsy_core/test_non_p2p_handler.py b/tests/test_gsy_core/test_non_p2p_handler.py
new file mode 100644
index 000000000..094c91529
--- /dev/null
+++ b/tests/test_gsy_core/test_non_p2p_handler.py
@@ -0,0 +1,75 @@
+from gsy_e.gsy_e_core.non_p2p_handler import NonP2PHandler
+
+SCENARIO = {
+ "name": "Grid",
+ "children": [
+ {
+ "name": "InfiniteBus",
+ "type": "InfiniteBus",
+ "energy_sell_rate": 31.0,
+ "energy_buy_rate": 15.0,
+ },
+ {
+ "name": "Community",
+ "children": [
+ {
+ "name": "House 1",
+ "children": [{"name": "Load", "type": "Load"}, {"name": "PV", "type": "PV"}],
+ },
+ {
+ "name": "House 2",
+ "children": [{"name": "Load", "type": "Load"}, {"name": "PV", "type": "PV"}],
+ },
+ ],
+ },
+ ],
+}
+
+
+class TestNonP2PHandler:
+
+ @staticmethod
+ def test_handle_non_p2p_scenario_adds_market_makers_to_homes():
+ handler = NonP2PHandler(SCENARIO)
+ assert handler.non_p2p_scenario == {
+ "name": "Grid",
+ "children": [
+ {
+ "name": "InfiniteBus",
+ "type": "InfiniteBus",
+ "energy_sell_rate": 31.0,
+ "energy_buy_rate": 15.0,
+ },
+ {
+ "name": "Community",
+ "children": [
+ {
+ "name": "House 1",
+ "children": [
+ {"name": "Load", "type": "Load"},
+ {"name": "PV", "type": "PV"},
+ {
+ "name": "MarketMaker",
+ "type": "InfiniteBus",
+ "energy_buy_rate": 15.0,
+ "energy_sell_rate": 31.0,
+ },
+ ],
+ },
+ {
+ "name": "House 2",
+ "children": [
+ {"name": "Load", "type": "Load"},
+ {"name": "PV", "type": "PV"},
+ {
+ "name": "MarketMaker",
+ "type": "InfiniteBus",
+ "energy_buy_rate": 15.0,
+ "energy_sell_rate": 31.0,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ }
diff --git a/tests/test_stats.py b/tests/test_stats.py
index 6837dc813..a9fa1f96a 100644
--- a/tests/test_stats.py
+++ b/tests/test_stats.py
@@ -15,6 +15,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
# pylint: disable=protected-access
from math import isclose
from unittest.mock import MagicMock, Mock
@@ -23,8 +24,10 @@
import pytest
from gsy_framework.data_classes import Trade, TraderDetails
from gsy_framework.sim_results.bills import MarketEnergyBills
-from gsy_framework.unit_test_utils import assert_dicts_identical, \
- assert_lists_contain_same_elements
+from gsy_framework.unit_test_utils import (
+ assert_dicts_identical,
+ assert_lists_contain_same_elements,
+)
from pendulum import today, now
from gsy_e import constants
@@ -40,6 +43,7 @@ def fixture_auto():
class FakeArea:
"""Fake class that mimics the Area class."""
+
# pylint: disable=missing-function-docstring,too-many-instance-attributes
def __init__(self, name, children=None, past_markets=None):
if not children:
@@ -58,6 +62,7 @@ def __init__(self, name, children=None, past_markets=None):
self.stats.imported_energy = Mock()
self.stats.exported_energy = Mock()
self.name_uuid_mapping = {}
+ self.is_home_area = False
@property
def future_markets(self):
@@ -83,6 +88,7 @@ def get_results_dict():
class FakeMarket:
"""Fake class that mimics the Market class."""
+
# pylint: disable=too-many-instance-attributes
def __init__(self, trades, name="Area", fees=0.0):
self.name = name
@@ -90,15 +96,18 @@ def __init__(self, trades, name="Area", fees=0.0):
self.time_slot = today(tz=constants.TIME_ZONE)
self.market_fee = fees
self.const_fee_rate = fees
- self.time_slot_str = self.time_slot.format(constants.DATE_TIME_FORMAT) \
- if self.time_slot is not None \
+ self.time_slot_str = (
+ self.time_slot.format(constants.DATE_TIME_FORMAT)
+ if self.time_slot is not None
else None
+ )
self.offer_history = []
self.bid_history = []
class FakeOffer:
"""Fake class that mimics the Offer class."""
+
def __init__(self, price, energy, seller):
self.price = price
self.energy = energy
@@ -111,19 +120,29 @@ def serializable_dict(self):
return self.__dict__
-def _trade(price, buyer, energy=1, seller=None, fee_price=0.):
- return Trade("id", now(tz=constants.TIME_ZONE),
- TraderDetails(seller, ""), TraderDetails(buyer, ""),
- energy, price, offer=FakeOffer(price, energy, seller),
- fee_price=fee_price)
+def _trade(price, buyer, energy=1, seller=None, fee_price=0.0):
+ return Trade(
+ "id",
+ now(tz=constants.TIME_ZONE),
+ TraderDetails(seller, ""),
+ TraderDetails(buyer, ""),
+ energy,
+ price,
+ offer=FakeOffer(price, energy, seller),
+ fee_price=fee_price,
+ )
@pytest.fixture(name="area")
def fixture_area():
- return FakeArea("parent",
- [FakeArea("child1"),
- FakeArea("child2", [FakeArea("grandchild1", [FakeArea("-")])]),
- FakeArea("child3", [FakeArea("grandchild2")])])
+ return FakeArea(
+ "parent",
+ [
+ FakeArea("child1"),
+ FakeArea("child2", [FakeArea("grandchild1", [FakeArea("-")])]),
+ FakeArea("child3", [FakeArea("grandchild2")]),
+ ],
+ )
@pytest.fixture(name="markets")
@@ -131,18 +150,19 @@ def fixture_markets():
"""Example with all equal energy prices"""
return (
FakeMarket((_trade(5, "Fridge"), _trade(3, "PV"), _trade(10, "MA 1"))),
- FakeMarket((_trade(1, "Storage"), _trade(4, "Fridge"), _trade(6, "Fridge"),
- _trade(2, "Fridge"))),
- FakeMarket((_trade(11, "MA 3"), _trade(20, "MA 9"), _trade(21, "MA 3")))
+ FakeMarket(
+ (_trade(1, "Storage"), _trade(4, "Fridge"), _trade(6, "Fridge"), _trade(2, "Fridge"))
+ ),
+ FakeMarket((_trade(11, "MA 3"), _trade(20, "MA 9"), _trade(21, "MA 3"))),
)
@pytest.fixture(name="markets2")
def fixture_markets2():
"""Example with different energy prices to test weighted averaging"""
- return(
+ return (
FakeMarket((_trade(11, "Fridge", 11), _trade(4, "Storage", 4), _trade(1, "MA 1", 10))),
- FakeMarket((_trade(3, "ECar", 1), _trade(9, "Fridge", 3), _trade(3, "Storage", 1)))
+ FakeMarket((_trade(3, "ECar", 1), _trade(9, "Fridge", 3), _trade(3, "Storage", 1))),
)
@@ -150,15 +170,13 @@ def fixture_markets2():
def fixture_grid():
fridge = FakeArea("fridge")
pv = FakeArea("pv")
- house1 = FakeArea("house1",
- children=[fridge, pv])
+ house1 = FakeArea("house1", children=[fridge, pv])
house1.past_markets = [FakeMarket((_trade(1, "fridge", 2, "pv"),), "house1")]
fridge.parent = house1
pv.parent = house1
e_car = FakeArea("e-car")
- house2 = FakeArea("house2",
- children=[e_car])
+ house2 = FakeArea("house2", children=[e_car])
house2.past_markets = [FakeMarket((_trade(1, "e-car", 1, "ma"),), "house2")]
e_car.parent = house2
@@ -175,7 +193,7 @@ def fixture_grid():
"commercial": commercial.uuid,
"fridge": fridge.uuid,
"pv": pv.uuid,
- "e-car": e_car.uuid
+ "e-car": e_car.uuid,
}
return grid
@@ -185,34 +203,45 @@ def test_energy_bills(grid):
epb.spot_market_time_slot_str = grid.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid)
m_bills = MarketEnergyBills(should_export_plots=True)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
result = m_bills.bills_results
assert result["house2"]["Accumulated Trades"]["bought"] == result["commercial"]["sold"] == 1
- assert result["house2"]["Accumulated Trades"]["spent"] == result["commercial"]["earned"] == \
- 0.01
+ assert (
+ result["house2"]["Accumulated Trades"]["spent"] == result["commercial"]["earned"] == 0.01
+ )
assert result["commercial"]["spent"] == result["commercial"]["bought"] == 0
assert result["fridge"]["bought"] == 2 and isclose(result["fridge"]["spent"], 0.01)
assert result["pv"]["sold"] == 2 and isclose(result["pv"]["earned"], 0.01)
assert "children" not in result
- grid.children[0].past_markets = [FakeMarket((_trade(2, "fridge", 2, "pv"),
- _trade(3, "fridge", 1, "ma")), "house1")]
- grid.children[1].past_markets = [FakeMarket((_trade(1, "e-car", 4, "ma"),
- _trade(1, "e-car", 8, "ma"),
- _trade(3, "ma", 5, "e-car")), "house2")]
+ grid.children[0].past_markets = [
+ FakeMarket((_trade(2, "fridge", 2, "pv"), _trade(3, "fridge", 1, "ma")), "house1")
+ ]
+ grid.children[1].past_markets = [
+ FakeMarket(
+ (
+ _trade(1, "e-car", 4, "ma"),
+ _trade(1, "e-car", 8, "ma"),
+ _trade(3, "ma", 5, "e-car"),
+ ),
+ "house2",
+ )
+ ]
grid.past_markets = [FakeMarket((_trade(2, "house2", 12, "commercial"),), "grid")]
epb.spot_market_time_slot_str = grid.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
result = m_bills.bills_results
assert result["house2"]["Accumulated Trades"]["bought"] == result["commercial"]["sold"] == 13
- assert result["house2"]["Accumulated Trades"]["spent"] == \
- result["commercial"]["earned"] == \
- 0.03
+ assert (
+ result["house2"]["Accumulated Trades"]["spent"] == result["commercial"]["earned"] == 0.03
+ )
assert result["commercial"]["spent"] == result["commercial"]["bought"] == 0
assert result["fridge"]["bought"] == 5 and isclose(result["fridge"]["spent"], 0.06)
assert result["pv"]["sold"] == 4 and isclose(result["pv"]["earned"], 0.03)
@@ -224,13 +253,14 @@ def test_energy_bills_last_past_market(grid):
epb.spot_market_time_slot_str = grid.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid)
m_bills = MarketEnergyBills(should_export_plots=True)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
result = m_bills.bills_results
assert result["house2"]["Accumulated Trades"]["bought"] == result["commercial"]["sold"] == 1
- assert result["house2"]["Accumulated Trades"]["spent"] == \
- result["commercial"]["earned"] == \
- 0.01
+ assert (
+ result["house2"]["Accumulated Trades"]["spent"] == result["commercial"]["earned"] == 0.01
+ )
external_trades = result["house2"]["External Trades"]
assert external_trades["total_energy"] == external_trades["bought"] - external_trades["sold"]
assert external_trades["total_cost"] == external_trades["spent"] - external_trades["earned"]
@@ -245,8 +275,9 @@ def test_energy_bills_redis(grid):
epb.spot_market_time_slot_str = grid.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid)
m_bills = MarketEnergyBills(should_export_plots=True)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
result = m_bills.bills_results
result_redis = m_bills.bills_redis_results
for house in grid.children:
@@ -268,8 +299,9 @@ def test_calculate_raw_energy_bills(grid):
assert "children" not in bills[commercial_uuid]
house1_uuid = grid.name_uuid_mapping["house1"]
assert grid.name_uuid_mapping["pv"] in bills[house1_uuid]["children"]
- pv_bills = [v for k, v in bills[house1_uuid]["children"].items()
- if k == grid.name_uuid_mapping["pv"]][0]
+ pv_bills = [
+ v for k, v in bills[house1_uuid]["children"].items() if k == grid.name_uuid_mapping["pv"]
+ ][0]
assert pv_bills["sold"] == 2.0 and isclose(pv_bills["earned"], 0.01)
assert grid.name_uuid_mapping["fridge"] in bills[house1_uuid]["children"]
house2_uuid = grid.name_uuid_mapping["house2"]
@@ -277,8 +309,16 @@ def test_calculate_raw_energy_bills(grid):
def _compare_bills(bill1, bill2):
- key_list = ["spent", "earned", "bought", "sold", "total_energy", "total_cost",
- "market_fee", "type"]
+ key_list = [
+ "spent",
+ "earned",
+ "bought",
+ "sold",
+ "total_energy",
+ "total_cost",
+ "market_fee",
+ "type",
+ ]
for k in key_list:
assert bill1[k] == bill2[k]
@@ -321,9 +361,7 @@ def fixture_grid2():
grid = FakeArea(
"street",
children=[house1, house2],
- past_markets=[FakeMarket(
- (_trade(2, house1.name, 3, house2.name),), "street"
- )]
+ past_markets=[FakeMarket((_trade(2, house1.name, 3, house2.name),), "street")],
)
house1.parent = grid
house2.parent = grid
@@ -335,8 +373,9 @@ def test_energy_bills_finds_mas(grid2):
epb.spot_market_time_slot_str = grid2.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid2)
m_bills = MarketEnergyBills(should_export_plots=True)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
result = m_bills.bills_results
assert result["house1"]["bought"] == result["house2"]["sold"] == 3
@@ -346,8 +385,9 @@ def test_energy_bills_ensure_device_types_are_populated(grid2):
epb.spot_market_time_slot_str = grid2.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid2)
m_bills = MarketEnergyBills(should_export_plots=True)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
result = m_bills.bills_results
assert result["house1"]["type"] == "Area"
assert result["house2"]["type"] == "Area"
@@ -355,21 +395,31 @@ def test_energy_bills_ensure_device_types_are_populated(grid2):
@pytest.fixture(name="grid_fees")
def fixture_grid_fees():
- house1 = FakeArea("house1",
- children=[FakeArea("testPV")],
- past_markets=[FakeMarket([], name="house1", fees=6.0)])
- house2 = FakeArea("house2",
- children=[FakeArea("testLoad")],
- past_markets=[FakeMarket((_trade(2, "testload", 3, "MA house2",
- fee_price=3.0),), name="house2", fees=3.0)])
+ house1 = FakeArea(
+ "house1",
+ children=[FakeArea("testPV")],
+ past_markets=[FakeMarket([], name="house1", fees=6.0)],
+ )
+ house2 = FakeArea(
+ "house2",
+ children=[FakeArea("testLoad")],
+ past_markets=[
+ FakeMarket(
+ (_trade(2, "testload", 3, "MA house2", fee_price=3.0),), name="house2", fees=3.0
+ )
+ ],
+ )
house1.display_type = "House 1 type"
house2.display_type = "House 2 type"
grid = FakeArea(
"street",
children=[house1, house2],
- past_markets=[FakeMarket((_trade(2, house2.name, 3, house1.name,
- fee_price=1.0),), "street", fees=1.0)
- ])
+ past_markets=[
+ FakeMarket(
+ (_trade(2, house2.name, 3, house1.name, fee_price=1.0),), "street", fees=1.0
+ )
+ ],
+ )
house1.parent = grid
house2.parent = grid
grid.name_uuid_mapping = {
@@ -389,9 +439,13 @@ def test_energy_bills_accumulate_fees(grid_fees):
m_bills._update_market_fees(epb.area_result_dict, epb.flattened_area_core_stats_dict)
grid_fees.children[0].past_markets = [FakeMarket([], name="house1", fees=2.0)]
grid_fees.children[1].past_markets = []
- grid_fees.past_markets = [FakeMarket((_trade(2, grid_fees.children[0].name, 3,
- grid_fees.children[0].name,
- fee_price=4.0),), "street", fees=4.0)]
+ grid_fees.past_markets = [
+ FakeMarket(
+ (_trade(2, grid_fees.children[0].name, 3, grid_fees.children[0].name, fee_price=4.0),),
+ "street",
+ fees=4.0,
+ )
+ ]
epb.spot_market_time_slot_str = grid_fees.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid_fees)
m_bills._update_market_fees(epb.area_result_dict, epb.flattened_area_core_stats_dict)
@@ -419,17 +473,23 @@ def test_energy_bills_report_correctly_market_fees(grid_fees):
epb.spot_market_time_slot_str = grid_fees.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid_fees)
m_bills = MarketEnergyBills(should_export_plots=True)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
grid_fees.children[0].past_markets = [FakeMarket([], name="house1", fees=2.0)]
grid_fees.children[1].past_markets = []
- grid_fees.past_markets = [FakeMarket((_trade(2, grid_fees.children[0].name, 3,
- grid_fees.children[0].name,
- fee_price=4.0),), "street", fees=4.0)]
+ grid_fees.past_markets = [
+ FakeMarket(
+ (_trade(2, grid_fees.children[0].name, 3, grid_fees.children[0].name, fee_price=4.0),),
+ "street",
+ fees=4.0,
+ )
+ ]
epb.spot_market_time_slot_str = grid_fees.spot_market.time_slot_str
epb._populate_core_stats_and_sim_state(grid_fees)
- m_bills.update(epb.area_result_dict, epb.flattened_area_core_stats_dict,
- epb.spot_market_time_slot_str)
+ m_bills.update(
+ epb.area_result_dict, epb.flattened_area_core_stats_dict, epb.spot_market_time_slot_str
+ )
result = m_bills.bills_results
assert result["street"]["house1"]["market_fee"] == 0.04
assert result["street"]["house2"]["market_fee"] == 0.01