From 8ca46e5996763c6e078505d56ab186aefab9a278 Mon Sep 17 00:00:00 2001 From: Jimmy Tung Date: Tue, 13 May 2025 15:48:26 +1000 Subject: [PATCH 1/7] Add load override for hosting capacity work packages Signed-off-by: Jimmy Tung --- changelog.md | 3 + src/zepben/eas/client/eas_client.py | 40 +++++++++++- src/zepben/eas/client/work_package.py | 88 +++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 0d3214b..0b1b456 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,9 @@ * Update `ModelConfig` to contain an optional `transformer_tap_settings` field to specify a set of distribution transformer tap settings to be applied by the model-processor. * Added basic client method to run a hosting capacity calibration and method to query its status. * Added basic client method to run a hosting capacity work package cost estimation. +* Update `ModelConfig` to contain 2 optional list for load override + * `fixed_time_load_override` - for fixed time models + * `time_period_load_override` - for models with start and end time points. ### Enhancements * Added work package config documentation. diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index d900a04..a242102 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -273,7 +273,25 @@ async def async_get_work_package_cost_estimation(self, work_package: WorkPackage "defaultGenWatts": work_package.generator_config.model.default_gen_watts, "defaultLoadVar": work_package.generator_config.model.default_load_var, "defaultGenVar": work_package.generator_config.model.default_gen_var, - "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings + "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings, + "fixedTimeLoadOverride": work_package.generator_config.model.fixed_time_load_override and [ + { + "loadId": spc.load_id, + "loadWattsOverride": spc.load_watts_override, + "genWattsOverride": spc.gen_watts_override, + "loadVarOverride": spc.load_var_override, + "genVarOverride": spc.gen_var_override, + } for spc in work_package.generator_config.model.fixed_time_load_override + ], + "TimePeriodLoadOverride": work_package.generator_config.model.time_period_load_override and [ + { + "loadId": spc.load_id, + "loadWattsOverride": spc.load_watts_override, + "genWattsOverride": spc.gen_watts_override, + "loadVarOverride": spc.load_var_override, + "genVarOverride": spc.gen_var_override, + } for spc in work_package.generator_config.model.time_period_load_override + ], }, "solve": work_package.generator_config.solve and { "normVMinPu": work_package.generator_config.solve.norm_vmin_pu, @@ -461,7 +479,25 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "defaultGenWatts": work_package.generator_config.model.default_gen_watts, "defaultLoadVar": work_package.generator_config.model.default_load_var, "defaultGenVar": work_package.generator_config.model.default_gen_var, - "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings + "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings, + "fixedTimeLoadOverride": work_package.generator_config.model.fixed_time_load_override and [ + { + "loadId": lo.load_id, + "loadWattsOverride": lo.load_watts_override, + "genWattsOverride": lo.gen_watts_override, + "loadVarOverride": lo.load_var_override, + "genVarOverride": lo.gen_var_override, + } for lo in work_package.generator_config.model.fixed_time_load_override + ], + "TimePeriodLoadOverride": work_package.generator_config.model.time_period_load_override and [ + { + "loadId": lo.load_id, + "loadWattsOverride": lo.load_watts_override, + "genWattsOverride": lo.gen_watts_override, + "loadVarOverride": lo.load_var_override, + "genVarOverride": lo.gen_var_override, + } for lo in work_package.generator_config.model.time_period_load_override + ], }, "solve": work_package.generator_config.solve and { "normVMinPu": work_package.generator_config.solve.norm_vmin_pu, diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index a2d869d..8241e32 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -38,6 +38,8 @@ "WriterOutputConfig", "WriterConfig", "YearRange", + "FixedTimeLoadOverride", + "TimePeriodLoadOverride", ] @@ -127,6 +129,82 @@ class MeterPlacementConfig: """The ID of the meter group to use for populating EnergyMeters at EnergyConsumers.""" +@dataclass +class FixedTimeLoadOverride: + load_id: str + """Id of the meter to replace the load data for.""" + + load_watts_override: Optional[float] + """ + The reading to be used to override load watts + """ + + gen_watts_override: Optional[float] + """ + The reading to be used to override gen watts + """ + + load_var_override: Optional[float] + """ + The reading to be used to override load var + """ + + gen_var_override: Optional[float] + """ + The reading to be used to override gen var + """ + + +@dataclass +class TimePeriodLoadOverride: + load_id: str + """Id of the meter to replace the load data for.""" + + load_watts_override: Optional[List[float]] + """ + A list of readings to be used to override load watts. + Can be either a yearly or daily profile. + The number of entries must match the number of entries in load_var_override, and the expected number for the configured load_interval_length_hours. + For load_interval_length_hours: + 0.25: 96 entries for daily and 35040 for yearly + 0.5: 48 entries for daily and 17520 for yearly + 1.0: 24 entries for daily and 8760 for yearly + """ + + gen_watts_override: Optional[List[float]] + """ + A list of readings to be used to override gen watts. + Can be either a yearly or daily profile. + The number of entries must match the number of entries in gen_var_override, and the expected number for the configured load_interval_length_hours. + For load_interval_length_hours: + 0.25: 96 entries for daily and 35040 for yearly + 0.5: 48 entries for daily and 17520 for yearly + 1.0: 24 entries for daily and 8760 for yearly + """ + + load_var_override: Optional[List[float]] + """ + A list of readings to be used to override load var. + Can be either a yearly or daily profile. + The number of entries must match the number of entries in load_watts_override, and the expected number for the configured load_interval_length_hours. + For load_interval_length_hours: + 0.25: 96 entries for daily and 35040 for yearly + 0.5: 48 entries for daily and 17520 for yearly + 1.0: 24 entries for daily and 8760 for yearly + """ + + gen_var_override: Optional[List[float]] + """ + A list of readings to be used to override gen var. + Can be either a yearly or daily profile. + The number of entries must match the number of entries in gen_watts_override, and the expected number for the configured load_interval_length_hours. + For load_interval_length_hours: + 0.25: 96 entries for daily and 35040 for yearly + 0.5: 48 entries for daily and 17520 for yearly + 1.0: 24 entries for daily and 8760 for yearly + """ + + @dataclass class ModelConfig: vm_pu: Optional[float] = None @@ -358,6 +436,16 @@ class ModelConfig: The name of the set of distribution transformer tap settings to be applied to the model from an external source. """ + fixed_time_load_override: Optional[List[FixedTimeLoadOverride]] = None + """ + The list of meters and load profiles replacement to be applied to the work package model (Fixed time point). + """ + + time_period_load_override: Optional[List[TimePeriodLoadOverride]] = None + """ + The list of meters and load profiles replacement to be applied to the work package model (Time period). + """ + class SolveMode(Enum): YEARLY = "YEARLY" From 2a395a83112d353da8c3ce8d21dce1278bd791a4 Mon Sep 17 00:00:00 2001 From: Jimmy Tung Date: Wed, 14 May 2025 17:48:44 +1000 Subject: [PATCH 2/7] Use dictionary for load overrides Signed-off-by: Jimmy Tung --- src/zepben/eas/client/eas_client.py | 30 +++++++++++++-------------- src/zepben/eas/client/work_package.py | 10 +++------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index a242102..5537f3a 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -480,24 +480,22 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "defaultLoadVar": work_package.generator_config.model.default_load_var, "defaultGenVar": work_package.generator_config.model.default_gen_var, "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings, - "fixedTimeLoadOverride": work_package.generator_config.model.fixed_time_load_override and [ - { - "loadId": lo.load_id, - "loadWattsOverride": lo.load_watts_override, - "genWattsOverride": lo.gen_watts_override, - "loadVarOverride": lo.load_var_override, - "genVarOverride": lo.gen_var_override, + "fixedTimeLoadOverride": work_package.generator_config.model.fixed_time_load_override and { + lo:{ + "loadWattsOverride": work_package.generator_config.model.fixed_time_load_override[lo].load_watts_override, + "genWattsOverride": work_package.generator_config.model.fixed_time_load_override[lo].gen_watts_override, + "loadVarOverride": work_package.generator_config.model.fixed_time_load_override[lo].load_var_override, + "genVarOverride": work_package.generator_config.model.fixed_time_load_override[lo].gen_var_override, } for lo in work_package.generator_config.model.fixed_time_load_override - ], - "TimePeriodLoadOverride": work_package.generator_config.model.time_period_load_override and [ - { - "loadId": lo.load_id, - "loadWattsOverride": lo.load_watts_override, - "genWattsOverride": lo.gen_watts_override, - "loadVarOverride": lo.load_var_override, - "genVarOverride": lo.gen_var_override, + }, + "TimePeriodLoadOverride": work_package.generator_config.model.time_period_load_override and { + lo:{ + "loadWatts": work_package.generator_config.model.time_period_load_override[lo].load_watts_override, + "genWatts": work_package.generator_config.model.time_period_load_override[lo].gen_watts_override, + "loadVar": work_package.generator_config.model.time_period_load_override[lo].load_var_override, + "genVar": work_package.generator_config.model.time_period_load_override[lo].gen_var_override, } for lo in work_package.generator_config.model.time_period_load_override - ], + }, }, "solve": work_package.generator_config.solve and { "normVMinPu": work_package.generator_config.solve.norm_vmin_pu, diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index 8241e32..5f23e47 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from datetime import datetime from enum import Enum -from typing import List, Optional, Union +from typing import List, Optional, Union, Dict __all__ = [ "SwitchClass", @@ -131,8 +131,6 @@ class MeterPlacementConfig: @dataclass class FixedTimeLoadOverride: - load_id: str - """Id of the meter to replace the load data for.""" load_watts_override: Optional[float] """ @@ -157,8 +155,6 @@ class FixedTimeLoadOverride: @dataclass class TimePeriodLoadOverride: - load_id: str - """Id of the meter to replace the load data for.""" load_watts_override: Optional[List[float]] """ @@ -436,12 +432,12 @@ class ModelConfig: The name of the set of distribution transformer tap settings to be applied to the model from an external source. """ - fixed_time_load_override: Optional[List[FixedTimeLoadOverride]] = None + fixed_time_load_override: Optional[Dict[str, FixedTimeLoadOverride]] = None """ The list of meters and load profiles replacement to be applied to the work package model (Fixed time point). """ - time_period_load_override: Optional[List[TimePeriodLoadOverride]] = None + time_period_load_override: Optional[Dict[str, TimePeriodLoadOverride]] = None """ The list of meters and load profiles replacement to be applied to the work package model (Time period). """ From 02cadd51f5a3b5d2f9e18a267b5176be52daa9c0 Mon Sep 17 00:00:00 2001 From: Jimmy Tung Date: Fri, 23 May 2025 11:15:25 +1000 Subject: [PATCH 3/7] Fixed up missed config changes. Signed-off-by: Jimmy Tung --- changelog.md | 10 +- src/zepben/eas/client/work_package.py | 184 +++++++++++++++----------- 2 files changed, 112 insertions(+), 82 deletions(-) diff --git a/changelog.md b/changelog.md index 0b1b456..83a735a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,15 +1,17 @@ # EAS Python client ## [0.18.0] - UNRELEASED ### Breaking Changes -* None. +* Added `load_overrides` to both `FixedTime` and `TimePeriod` which consist of a list of `FixedTimeLoadOverride` and `TimePeriodLoadOverride` +* `WorkPackageConfig` has some of its variables moved into the new classes `ForecastConfig` and `FeederConfig`. + * Moved `feeders`, `years`, `scenarios` and `load_time`. + * `WorkPackageConfig` now has a new variable `syf_config` consist of a Union of `ForecastConfig`, and list of `FeederConfig`. + * This is to support feeder specific load override events ### New Features * Update `ModelConfig` to contain an optional `transformer_tap_settings` field to specify a set of distribution transformer tap settings to be applied by the model-processor. * Added basic client method to run a hosting capacity calibration and method to query its status. * Added basic client method to run a hosting capacity work package cost estimation. -* Update `ModelConfig` to contain 2 optional list for load override - * `fixed_time_load_override` - for fixed time models - * `time_period_load_override` - for models with start and end time points. +* Added `FixedTimeLoadOverride` and `TimePeriodLoadOverride` class ### Enhancements * Added work package config documentation. diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index 5f23e47..ebebc68 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -40,6 +40,8 @@ "YearRange", "FixedTimeLoadOverride", "TimePeriodLoadOverride", + "ForecastConfig", + "FeederConfig", ] @@ -64,71 +66,6 @@ class SwitchMeterPlacementConfig: """ -class FixedTime: - """ - A single point in time to model. Should be precise to the minute, and load data must be - present for the provided time in the load database for accurate results. - """ - - def __init__(self, time: datetime): - self.time = time.replace(tzinfo=None) - - -class TimePeriod: - """ - A time period to model, from a start time to an end time. Maximum of 1 year. - - Load data must be available in the load database between the provided start and end time for accurate results. - """ - - def __init__( - self, - start_time: datetime, - end_time: datetime - ): - self._validate(start_time, end_time) - self.start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) - self.end_time = end_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) - - @staticmethod - def _validate(start_time: datetime, end_time: datetime): - ddelta = (end_time - start_time).days - - if ddelta > 365: - raise ValueError("The difference between 'start_time' and 'end_time' cannot be greater than a year.") - - if ddelta < 1: - raise ValueError("The difference between 'start_time' and 'end_time' cannot be less than a day.") - - if ddelta < 0: - raise ValueError("The 'start_time' must be before 'end_time'.") - - -class LoadPlacement(Enum): - PER_ENERGY_CONSUMER = "PER_ENERGY_CONSUMER" - PER_USAGE_POINT = "PER_USAGE_POINT" - - -class FeederScenarioAllocationStrategy(Enum): - RANDOM = "RANDOM" - ADDITIVE = "ADDITIVE" - - -@dataclass -class MeterPlacementConfig: - feeder_head: Optional[bool] = None - """Whether to place a meter at the voltage source at the feeder head.""" - - dist_transformers: Optional[bool] = None - """Whether to place a meter at the secondary winding of each distribution transformer.""" - - switch_meter_placement_configs: Optional[List[SwitchMeterPlacementConfig]] = None - """Specifies which switch classes to place meters at, and the regex pattern to match for in the switch names.""" - - energy_consumer_meter_group: Optional[str] = None - """The ID of the meter group to use for populating EnergyMeters at EnergyConsumers.""" - - @dataclass class FixedTimeLoadOverride: @@ -201,6 +138,74 @@ class TimePeriodLoadOverride: """ +class FixedTime: + """ + A single point in time to model. Should be precise to the minute, and load data must be + present for the provided time in the load database for accurate results. + """ + + def __init__(self, time: datetime, load_overrides: List[FixedTimeLoadOverride]): + self.time = time.replace(tzinfo=None) + self.load_overrides = load_overrides + + +class TimePeriod: + """ + A time period to model, from a start time to an end time. Maximum of 1 year. + + Load data must be available in the load database between the provided start and end time for accurate results. + """ + + def __init__( + self, + start_time: datetime, + end_time: datetime, + load_overrides: List[TimePeriodLoadOverride] + ): + self._validate(start_time, end_time) + self.start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) + self.end_time = end_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) + self.load_override = load_overrides + + @staticmethod + def _validate(start_time: datetime, end_time: datetime): + ddelta = (end_time - start_time).days + + if ddelta > 365: + raise ValueError("The difference between 'start_time' and 'end_time' cannot be greater than a year.") + + if ddelta < 1: + raise ValueError("The difference between 'start_time' and 'end_time' cannot be less than a day.") + + if ddelta < 0: + raise ValueError("The 'start_time' must be before 'end_time'.") + + +class LoadPlacement(Enum): + PER_ENERGY_CONSUMER = "PER_ENERGY_CONSUMER" + PER_USAGE_POINT = "PER_USAGE_POINT" + + +class FeederScenarioAllocationStrategy(Enum): + RANDOM = "RANDOM" + ADDITIVE = "ADDITIVE" + + +@dataclass +class MeterPlacementConfig: + feeder_head: Optional[bool] = None + """Whether to place a meter at the voltage source at the feeder head.""" + + dist_transformers: Optional[bool] = None + """Whether to place a meter at the secondary winding of each distribution transformer.""" + + switch_meter_placement_configs: Optional[List[SwitchMeterPlacementConfig]] = None + """Specifies which switch classes to place meters at, and the regex pattern to match for in the switch names.""" + + energy_consumer_meter_group: Optional[str] = None + """The ID of the meter group to use for populating EnergyMeters at EnergyConsumers.""" + + @dataclass class ModelConfig: vm_pu: Optional[float] = None @@ -432,16 +437,6 @@ class ModelConfig: The name of the set of distribution transformer tap settings to be applied to the model from an external source. """ - fixed_time_load_override: Optional[Dict[str, FixedTimeLoadOverride]] = None - """ - The list of meters and load profiles replacement to be applied to the work package model (Fixed time point). - """ - - time_period_load_override: Optional[Dict[str, TimePeriodLoadOverride]] = None - """ - The list of meters and load profiles replacement to be applied to the work package model (Time period). - """ - class SolveMode(Enum): YEARLY = "YEARLY" @@ -772,9 +767,7 @@ class InterventionConfig: @dataclass -class WorkPackageConfig: - """ A data class representing the configuration for a hosting capacity work package """ - name: str +class ForecastConfig(object): feeders: List[str] """The feeders to process in this work package""" @@ -796,6 +789,41 @@ class WorkPackageConfig: result in inaccurate results. """ +@dataclass +class FeederConfig(object): + feeder: str + """The feeder to process in this work package""" + + years: List[int] + """ + The years to process for the specified feeders in this work package. + The years should be configured in the input database forecasts for all supplied scenarios. + """ + + scenarios: List[str] + """ + The scenarios to model. These should be configured in the input.scenario_configuration table. + """ + + load_time: Union[TimePeriod, FixedTime] + """ + The time to use for the base load data. The provided time[s] must be available in the + load database for accurate results. Specifying an invalid time (i.e one with no load data) will + result in inaccurate results. + """ + + +@dataclass +class WorkPackageConfig: + """ A data class representing the configuration for a hosting capacity work package """ + name: str + syf_config: Union[ForecastConfig, List[FeederConfig]] + """ + The configuration of the scenario, years, and feeders to run. Use ForecastConfig + for the same scenarios and years applied across all feeders, and the more in depth FeederConfig + if configuration varies per feeder. + """ + quality_assurance_processing: Optional[bool] = None """Whether to enable QA processing""" From 11f472339427baeea4a133a906b9b89cecf14113 Mon Sep 17 00:00:00 2001 From: Jimmy Tung Date: Fri, 23 May 2025 16:17:31 +1000 Subject: [PATCH 4/7] Remove override suffix Signed-off-by: Jimmy Tung --- src/zepben/eas/client/work_package.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index ebebc68..3943ac4 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -38,8 +38,8 @@ "WriterOutputConfig", "WriterConfig", "YearRange", - "FixedTimeLoadOverride", - "TimePeriodLoadOverride", + "FixedTimeLoad", + "TimePeriodLoad", "ForecastConfig", "FeederConfig", ] @@ -67,7 +67,7 @@ class SwitchMeterPlacementConfig: @dataclass -class FixedTimeLoadOverride: +class FixedTimeLoad: load_watts_override: Optional[float] """ @@ -91,7 +91,7 @@ class FixedTimeLoadOverride: @dataclass -class TimePeriodLoadOverride: +class TimePeriodLoad: load_watts_override: Optional[List[float]] """ @@ -144,7 +144,7 @@ class FixedTime: present for the provided time in the load database for accurate results. """ - def __init__(self, time: datetime, load_overrides: List[FixedTimeLoadOverride]): + def __init__(self, time: datetime, load_overrides: List[FixedTimeLoad]): self.time = time.replace(tzinfo=None) self.load_overrides = load_overrides @@ -160,7 +160,7 @@ def __init__( self, start_time: datetime, end_time: datetime, - load_overrides: List[TimePeriodLoadOverride] + load_overrides: List[TimePeriodLoad] ): self._validate(start_time, end_time) self.start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) From a1d8b5b247bf3729432ebabde622efe0e5b1b7bb Mon Sep 17 00:00:00 2001 From: Jimmy Tung Date: Wed, 4 Jun 2025 11:21:31 +1000 Subject: [PATCH 5/7] Update for syfConfig changes. Signed-off-by: Jimmy Tung --- src/zepben/eas/client/eas_client.py | 134 +++++++++++++++----------- src/zepben/eas/client/work_package.py | 52 ++++++---- test/test_eas_client.py | 107 ++++++++++++-------- 3 files changed, 178 insertions(+), 115 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index 5537f3a..d44f8fd 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -17,7 +17,7 @@ from zepben.eas.client.study import Study from zepben.eas.client.util import construct_url -from zepben.eas.client.work_package import WorkPackageConfig, FixedTime, TimePeriod +from zepben.eas.client.work_package import WorkPackageConfig, FixedTime, TimePeriod, ForecastConfig, FeederConfigs __all__ = ["EasClient"] @@ -211,15 +211,45 @@ async def async_get_work_package_cost_estimation(self, work_package: WorkPackage "variables": { "workPackageName": work_package.name, "input": { - "feeders": work_package.feeders, - "years": work_package.years, - "scenarios": work_package.scenarios, - "fixedTime": work_package.load_time.time.isoformat() - if isinstance(work_package.load_time, FixedTime) else None, - "timePeriod": { - "startTime": work_package.load_time.start_time.isoformat(), - "endTime": work_package.load_time.end_time.isoformat(), - } if isinstance(work_package.load_time, TimePeriod) else None, + "syfConfig": work_package.syf_config and { + "type": "forecastConfig", + "feeders": work_package.syf_config.feeders, + "years" : work_package.syf_config.years, + "scenarios": work_package.syf_config.scenarios, + "loadTime": work_package.syf_config.load_time and { + "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, FixedTime) else { + "startTime": work_package.syf_config.load_time.start_time.isoformat(), + "endTime": work_package.syf_config.load_time.end_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, TimePeriod) else None + } if isinstance(work_package.syf_config, ForecastConfig) else { + "type": "feederConfigs", + "configs": [ + { + "feeder": config.feeder, + "years": config.years, + "scenarios": config.scenarios, + "loadTime": config.load_time and { + "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), + "loadOverrides": config.load_time.load_overrides and { + key: value.__dict__ + for key, value in config.load_time.load_overrides.items()} + } if isinstance(config.load_time, FixedTime) else { + "startTime": config.load_time.start_time.isoformat(), + "endTime": config.load_time.end_time.isoformat(), + "loadOverrides": config.load_time.load_overrides and { + key: value.__dict__ + for key, value in config.load_time.load_overrides.items()} + } if isinstance(config.load_time, TimePeriod) else None + } for config in work_package.syf_config.configs + ] + } if isinstance(work_package.syf_config, FeederConfigs) else None, "qualityAssuranceProcessing": work_package.quality_assurance_processing, "generatorConfig": work_package.generator_config and { "model": work_package.generator_config.model and { @@ -274,24 +304,6 @@ async def async_get_work_package_cost_estimation(self, work_package: WorkPackage "defaultLoadVar": work_package.generator_config.model.default_load_var, "defaultGenVar": work_package.generator_config.model.default_gen_var, "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings, - "fixedTimeLoadOverride": work_package.generator_config.model.fixed_time_load_override and [ - { - "loadId": spc.load_id, - "loadWattsOverride": spc.load_watts_override, - "genWattsOverride": spc.gen_watts_override, - "loadVarOverride": spc.load_var_override, - "genVarOverride": spc.gen_var_override, - } for spc in work_package.generator_config.model.fixed_time_load_override - ], - "TimePeriodLoadOverride": work_package.generator_config.model.time_period_load_override and [ - { - "loadId": spc.load_id, - "loadWattsOverride": spc.load_watts_override, - "genWattsOverride": spc.gen_watts_override, - "loadVarOverride": spc.load_var_override, - "genVarOverride": spc.gen_var_override, - } for spc in work_package.generator_config.model.time_period_load_override - ], }, "solve": work_package.generator_config.solve and { "normVMinPu": work_package.generator_config.solve.norm_vmin_pu, @@ -417,15 +429,45 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "variables": { "workPackageName": work_package.name, "input": { - "feeders": work_package.feeders, - "years": work_package.years, - "scenarios": work_package.scenarios, - "fixedTime": work_package.load_time.time.isoformat() - if isinstance(work_package.load_time, FixedTime) else None, - "timePeriod": { - "startTime": work_package.load_time.start_time.isoformat(), - "endTime": work_package.load_time.end_time.isoformat(), - } if isinstance(work_package.load_time, TimePeriod) else None, + "syfConfig": work_package.syf_config and { + "type": "forecastConfig", + "feeders": work_package.syf_config.feeders, + "years" : work_package.syf_config.years, + "scenarios": work_package.syf_config.scenarios, + "loadTime": work_package.syf_config.load_time and { + "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, FixedTime) else { + "startTime": work_package.syf_config.load_time.start_time.isoformat(), + "endTime": work_package.syf_config.load_time.end_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, TimePeriod) else None + } if isinstance(work_package.syf_config, ForecastConfig) else { + "type": "feederConfigs", + "configs": [ + { + "feeder": config.feeder, + "years": config.years, + "scenarios": config.scenarios, + "loadTime": config.load_time and { + "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), + "loadOverrides": config.load_time.load_overrides and { + key: value.__dict__ + for key, value in config.load_time.load_overrides.items()} + } if isinstance(config.load_time, FixedTime) else { + "startTime": config.load_time.start_time.isoformat(), + "endTime": config.load_time.end_time.isoformat(), + "loadOverrides": config.load_time.load_overrides and { + key: value.__dict__ + for key, value in config.load_time.load_overrides.items()} + } if isinstance(config.load_time, TimePeriod) else None + } for config in work_package.syf_config.configs + ] + } if isinstance(work_package.syf_config, FeederConfigs) else None, "qualityAssuranceProcessing": work_package.quality_assurance_processing, "generatorConfig": work_package.generator_config and { "model": work_package.generator_config.model and { @@ -479,23 +521,7 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "defaultGenWatts": work_package.generator_config.model.default_gen_watts, "defaultLoadVar": work_package.generator_config.model.default_load_var, "defaultGenVar": work_package.generator_config.model.default_gen_var, - "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings, - "fixedTimeLoadOverride": work_package.generator_config.model.fixed_time_load_override and { - lo:{ - "loadWattsOverride": work_package.generator_config.model.fixed_time_load_override[lo].load_watts_override, - "genWattsOverride": work_package.generator_config.model.fixed_time_load_override[lo].gen_watts_override, - "loadVarOverride": work_package.generator_config.model.fixed_time_load_override[lo].load_var_override, - "genVarOverride": work_package.generator_config.model.fixed_time_load_override[lo].gen_var_override, - } for lo in work_package.generator_config.model.fixed_time_load_override - }, - "TimePeriodLoadOverride": work_package.generator_config.model.time_period_load_override and { - lo:{ - "loadWatts": work_package.generator_config.model.time_period_load_override[lo].load_watts_override, - "genWatts": work_package.generator_config.model.time_period_load_override[lo].gen_watts_override, - "loadVar": work_package.generator_config.model.time_period_load_override[lo].load_var_override, - "genVar": work_package.generator_config.model.time_period_load_override[lo].gen_var_override, - } for lo in work_package.generator_config.model.time_period_load_override - }, + "transformerTapSettings": work_package.generator_config.model.transformer_tap_settings }, "solve": work_package.generator_config.solve and { "normVMinPu": work_package.generator_config.solve.norm_vmin_pu, diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index 3943ac4..5db337b 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -38,10 +38,11 @@ "WriterOutputConfig", "WriterConfig", "YearRange", - "FixedTimeLoad", - "TimePeriodLoad", + "FixedTimeLoadOverride", + "TimePeriodLoadOverride", "ForecastConfig", "FeederConfig", + "FeederConfigs", ] @@ -67,70 +68,72 @@ class SwitchMeterPlacementConfig: @dataclass -class FixedTimeLoad: +class FixedTimeLoadOverride: - load_watts_override: Optional[float] + load_watts: Optional[float] """ The reading to be used to override load watts """ - gen_watts_override: Optional[float] + gen_watts: Optional[float] """ The reading to be used to override gen watts """ - load_var_override: Optional[float] + load_var: Optional[float] """ The reading to be used to override load var """ - gen_var_override: Optional[float] + gen_var: Optional[float] """ The reading to be used to override gen var """ + # def __str__(self): + @dataclass -class TimePeriodLoad: +class TimePeriodLoadOverride: - load_watts_override: Optional[List[float]] + load_watts: Optional[List[float]] """ A list of readings to be used to override load watts. Can be either a yearly or daily profile. - The number of entries must match the number of entries in load_var_override, and the expected number for the configured load_interval_length_hours. + The number of entries must match the number of entries in load_var, and the expected number for the configured load_interval_length_hours. For load_interval_length_hours: 0.25: 96 entries for daily and 35040 for yearly 0.5: 48 entries for daily and 17520 for yearly 1.0: 24 entries for daily and 8760 for yearly """ - gen_watts_override: Optional[List[float]] + gen_watts: Optional[List[float]] """ A list of readings to be used to override gen watts. Can be either a yearly or daily profile. - The number of entries must match the number of entries in gen_var_override, and the expected number for the configured load_interval_length_hours. + The number of entries must match the number of entries in gen_var, and the expected number for the configured load_interval_length_hours. For load_interval_length_hours: 0.25: 96 entries for daily and 35040 for yearly 0.5: 48 entries for daily and 17520 for yearly 1.0: 24 entries for daily and 8760 for yearly """ - load_var_override: Optional[List[float]] + load_var: Optional[List[float]] """ A list of readings to be used to override load var. Can be either a yearly or daily profile. - The number of entries must match the number of entries in load_watts_override, and the expected number for the configured load_interval_length_hours. + The number of entries must match the number of entries in load_watts, and the expected number for the configured load_interval_length_hours. For load_interval_length_hours: 0.25: 96 entries for daily and 35040 for yearly 0.5: 48 entries for daily and 17520 for yearly 1.0: 24 entries for daily and 8760 for yearly """ - gen_var_override: Optional[List[float]] + gen_var: Optional[List[float]] """ A list of readings to be used to override gen var. Can be either a yearly or daily profile. - The number of entries must match the number of entries in gen_watts_override, and the expected number for the configured load_interval_length_hours. + The number of entries must match the number of entries in gen_watts, and the expected number for the configured load_interval_length_hours. For load_interval_length_hours: 0.25: 96 entries for daily and 35040 for yearly 0.5: 48 entries for daily and 17520 for yearly @@ -144,8 +147,8 @@ class FixedTime: present for the provided time in the load database for accurate results. """ - def __init__(self, time: datetime, load_overrides: List[FixedTimeLoad]): - self.time = time.replace(tzinfo=None) + def __init__(self, time: datetime, load_overrides: Optional[Dict[str, FixedTimeLoadOverride]]): + self.fetch_load_time = time.replace(tzinfo=None) self.load_overrides = load_overrides @@ -160,12 +163,12 @@ def __init__( self, start_time: datetime, end_time: datetime, - load_overrides: List[TimePeriodLoad] + load_overrides: Optional[Dict[str, TimePeriodLoadOverride]] ): self._validate(start_time, end_time) self.start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) self.end_time = end_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) - self.load_override = load_overrides + self.load_overrides = load_overrides @staticmethod def _validate(start_time: datetime, end_time: datetime): @@ -789,6 +792,7 @@ class ForecastConfig(object): result in inaccurate results. """ + @dataclass class FeederConfig(object): feeder: str @@ -813,11 +817,17 @@ class FeederConfig(object): """ +@dataclass +class FeederConfigs(object): + configs: list[FeederConfig] + """The feeder to process in this work package""" + + @dataclass class WorkPackageConfig: """ A data class representing the configuration for a hosting capacity work package """ name: str - syf_config: Union[ForecastConfig, List[FeederConfig]] + syf_config: Union[ForecastConfig, FeederConfigs] """ The configuration of the scenario, years, and feeders to run. Use ForecastConfig for the same scenarios and years applied across all feeders, and the more in depth FeederConfig diff --git a/test/test_eas_client.py b/test/test_eas_client.py index 7796bc7..b534db0 100644 --- a/test/test_eas_client.py +++ b/test/test_eas_client.py @@ -16,9 +16,10 @@ from werkzeug import Response from zepben.auth import ZepbenTokenFetcher -from zepben.eas import EasClient, Study +from zepben.eas import EasClient, Study, FeederConfig, ForecastConfig, FixedTimeLoadOverride from zepben.eas.client.study import Result -from zepben.eas.client.work_package import WorkPackageConfig, TimePeriod +from zepben.eas.client.work_package import WorkPackageConfig, TimePeriod, FeederConfigs, TimePeriodLoadOverride, \ + FixedTime mock_host = ''.join(random.choices(string.ascii_lowercase, k=10)) mock_port = random.randrange(1024) @@ -179,12 +180,16 @@ def test_get_work_package_cost_estimation_no_verify_success(httpserver: HTTPServ res = eas_client.get_work_package_cost_estimation( WorkPackageConfig( "wp_name", - ["feeder"], - [1], - ["scenario"], - TimePeriod( - datetime(2022, 1, 1), - datetime(2022, 1, 2)) + ForecastConfig( + ["feeder"], + [1], + ["scenario"], + TimePeriod( + datetime(2022, 1, 1), + datetime(2022, 1, 2), + None + ) + ) ) ) httpserver.check_assertions() @@ -206,12 +211,16 @@ def test_get_work_package_cost_estimation_invalid_certificate_failure(ca: trustm eas_client.get_work_package_cost_estimation( WorkPackageConfig( "wp_name", - ["feeder"], - [1], - ["scenario"], - TimePeriod( - datetime(2022, 1, 1), - datetime(2022, 1, 2)) + ForecastConfig( + ["feeder"], + [1], + ["scenario"], + TimePeriod( + datetime(2022, 1, 1), + datetime(2022, 1, 2), + None + ) + ) ) ) @@ -230,12 +239,17 @@ def test_get_work_package_cost_estimation_valid_certificate_success(ca: trustme. res = eas_client.get_work_package_cost_estimation( WorkPackageConfig( "wp_name", - ["feeder"], - [1], - ["scenario"], - TimePeriod( - datetime(2022, 1, 1), - datetime(2022, 1, 2)) + FeederConfigs( + [FeederConfig( + "feeder", + [1], + ["scenario"], + FixedTime( + datetime(2022, 1, 1), + {"meter": FixedTimeLoadOverride(1, 2, 3, 4)} + ) + )] + ) ) ) httpserver.check_assertions() @@ -253,12 +267,16 @@ def test_run_hosting_capacity_work_package_no_verify_success(httpserver: HTTPSer res = eas_client.run_hosting_capacity_work_package( WorkPackageConfig( "wp_name", - ["feeder"], - [1], - ["scenario"], - TimePeriod( - datetime(2022, 1, 1), - datetime(2022, 1, 2)) + ForecastConfig( + ["feeder"], + [1], + ["scenario"], + TimePeriod( + datetime(2022, 1, 1), + datetime(2022, 1, 2), + None + ) + ) ) ) httpserver.check_assertions() @@ -280,12 +298,16 @@ def test_run_hosting_capacity_work_package_invalid_certificate_failure(ca: trust eas_client.run_hosting_capacity_work_package( WorkPackageConfig( "wp_name", - ["feeder"], - [1], - ["scenario"], - TimePeriod( - datetime(2022, 1, 1), - datetime(2022, 1, 2)) + ForecastConfig( + ["feeder"], + [1], + ["scenario"], + TimePeriod( + datetime(2022, 1, 1), + datetime(2022, 1, 2), + None + ) + ) ) ) @@ -304,12 +326,16 @@ def test_run_hosting_capacity_work_package_valid_certificate_success(ca: trustme res = eas_client.run_hosting_capacity_work_package( WorkPackageConfig( "wp_name", - ["feeder"], - [1], - ["scenario"], - TimePeriod( - datetime(2022, 1, 1), - datetime(2022, 1, 2)) + ForecastConfig( + ["feeder"], + [1], + ["scenario"], + TimePeriod( + datetime(2022, 1, 1), + datetime(2022, 1, 2), + {"meter1": TimePeriodLoadOverride([1.0], [2.0], [3.0], [4.0])} + ) + ) ) ) httpserver.check_assertions() @@ -684,7 +710,8 @@ def hosting_capacity_run_calibration_with_calibration_time_request_handler(reque query = " ".join(actual_body['query'].split()) assert query == "mutation runCalibration($calibrationName: String!, $calibrationTimeLocal: LocalDateTime) { runCalibration(calibrationName: $calibrationName, calibrationTimeLocal: $calibrationTimeLocal) }" - assert actual_body['variables'] == {"calibrationName": "TEST CALIBRATION", "calibrationTimeLocal": "1992-01-28T00:00:20"} + assert actual_body['variables'] == {"calibrationName": "TEST CALIBRATION", + "calibrationTimeLocal": "1992-01-28T00:00:20"} return Response(json.dumps({"result": "success"}), status=200, content_type="application/json") @@ -725,4 +752,4 @@ def test_get_hosting_capacity_calibration_sets_no_verify_success(httpserver: HTT get_hosting_capacity_calibration_sets_request_handler) res = eas_client.get_hosting_capacity_calibration_sets() httpserver.check_assertions() - assert res == ["one", "two", "three"] \ No newline at end of file + assert res == ["one", "two", "three"] From eefe1545e6af24e025f771ff517493cbd2d46d49 Mon Sep 17 00:00:00 2001 From: Jimmy Tung Date: Tue, 17 Jun 2025 11:17:46 +1000 Subject: [PATCH 6/7] Make query consistent with EAS GQL Signed-off-by: Jimmy Tung --- src/zepben/eas/client/eas_client.py | 100 +++++++++++++------------- src/zepben/eas/client/work_package.py | 4 +- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index d44f8fd..cb1fc34 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -211,45 +211,47 @@ async def async_get_work_package_cost_estimation(self, work_package: WorkPackage "variables": { "workPackageName": work_package.name, "input": { - "syfConfig": work_package.syf_config and { - "type": "forecastConfig", - "feeders": work_package.syf_config.feeders, - "years" : work_package.syf_config.years, - "scenarios": work_package.syf_config.scenarios, - "loadTime": work_package.syf_config.load_time and { - "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { - key: value.__dict__ - for key, value in work_package.syf_config.load_time.load_overrides.items()} - } if isinstance(work_package.syf_config.load_time, FixedTime) else { - "startTime": work_package.syf_config.load_time.start_time.isoformat(), - "endTime": work_package.syf_config.load_time.end_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { - key: value.__dict__ - for key, value in work_package.syf_config.load_time.load_overrides.items()} - } if isinstance(work_package.syf_config.load_time, TimePeriod) else None - } if isinstance(work_package.syf_config, ForecastConfig) else { + "feederConfigs": { "type": "feederConfigs", "configs": [ { "feeder": config.feeder, "years": config.years, "scenarios": config.scenarios, - "loadTime": config.load_time and { - "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), + "timePeriod": { + "startTime": config.load_time.start_time.isoformat(), + "endTime": config.load_time.end_time.isoformat(), "loadOverrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} - } if isinstance(config.load_time, FixedTime) else { - "startTime": config.load_time.start_time.isoformat(), - "endTime": config.load_time.end_time.isoformat(), + } if isinstance(config.load_time, TimePeriod) else None, + "fixedTime": config.load_time and { + "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), "loadOverrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} - } if isinstance(config.load_time, TimePeriod) else None + } if isinstance(config.load_time, FixedTime) else None, } for config in work_package.syf_config.configs ] } if isinstance(work_package.syf_config, FeederConfigs) else None, + "forecastConfig": { + "feeders": work_package.syf_config.feeders, + "years": work_package.syf_config.years, + "scenarios": work_package.syf_config.scenarios, + "timePeriod": { + "startTime": work_package.syf_config.load_time.start_time.isoformat(), + "endTime": work_package.syf_config.load_time.end_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, TimePeriod) else None, + "fixedTime": work_package.syf_config.load_time and { + "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, FixedTime) else None + } if isinstance(work_package.syf_config, ForecastConfig) else None, "qualityAssuranceProcessing": work_package.quality_assurance_processing, "generatorConfig": work_package.generator_config and { "model": work_package.generator_config.model and { @@ -429,45 +431,47 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "variables": { "workPackageName": work_package.name, "input": { - "syfConfig": work_package.syf_config and { - "type": "forecastConfig", - "feeders": work_package.syf_config.feeders, - "years" : work_package.syf_config.years, - "scenarios": work_package.syf_config.scenarios, - "loadTime": work_package.syf_config.load_time and { - "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { - key: value.__dict__ - for key, value in work_package.syf_config.load_time.load_overrides.items()} - } if isinstance(work_package.syf_config.load_time, FixedTime) else { - "startTime": work_package.syf_config.load_time.start_time.isoformat(), - "endTime": work_package.syf_config.load_time.end_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { - key: value.__dict__ - for key, value in work_package.syf_config.load_time.load_overrides.items()} - } if isinstance(work_package.syf_config.load_time, TimePeriod) else None - } if isinstance(work_package.syf_config, ForecastConfig) else { + "feederConfigs": { "type": "feederConfigs", "configs": [ { "feeder": config.feeder, "years": config.years, "scenarios": config.scenarios, - "loadTime": config.load_time and { - "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), + "timePeriod": { + "startTime": config.load_time.start_time.isoformat(), + "endTime": config.load_time.end_time.isoformat(), "loadOverrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} - } if isinstance(config.load_time, FixedTime) else { - "startTime": config.load_time.start_time.isoformat(), - "endTime": config.load_time.end_time.isoformat(), + } if isinstance(config.load_time, TimePeriod) else None, + "fixedTime": config.load_time and { + "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), "loadOverrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} - } if isinstance(config.load_time, TimePeriod) else None + } if isinstance(config.load_time, FixedTime) else None, } for config in work_package.syf_config.configs ] } if isinstance(work_package.syf_config, FeederConfigs) else None, + "forecastConfig": { + "feeders": work_package.syf_config.feeders, + "years": work_package.syf_config.years, + "scenarios": work_package.syf_config.scenarios, + "timePeriod": { + "startTime": work_package.syf_config.load_time.start_time.isoformat(), + "endTime": work_package.syf_config.load_time.end_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, TimePeriod) else None, + "fixedTime": work_package.syf_config.load_time and { + "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), + "loadOverrides": work_package.syf_config.load_time.load_overrides and { + key: value.__dict__ + for key, value in work_package.syf_config.load_time.load_overrides.items()} + } if isinstance(work_package.syf_config.load_time, FixedTime) else None + } if isinstance(work_package.syf_config, ForecastConfig) else None, "qualityAssuranceProcessing": work_package.quality_assurance_processing, "generatorConfig": work_package.generator_config and { "model": work_package.generator_config.model and { diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index 5db337b..07dffca 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -147,7 +147,7 @@ class FixedTime: present for the provided time in the load database for accurate results. """ - def __init__(self, time: datetime, load_overrides: Optional[Dict[str, FixedTimeLoadOverride]]): + def __init__(self, time: datetime, load_overrides: Optional[Dict[str, FixedTimeLoadOverride]] = None): self.fetch_load_time = time.replace(tzinfo=None) self.load_overrides = load_overrides @@ -163,7 +163,7 @@ def __init__( self, start_time: datetime, end_time: datetime, - load_overrides: Optional[Dict[str, TimePeriodLoadOverride]] + load_overrides: Optional[Dict[str, TimePeriodLoadOverride]] = None ): self._validate(start_time, end_time) self.start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) From 8833a5ca5a01c71739cf7a515218515e3fbbee10 Mon Sep 17 00:00:00 2001 From: Jimmy Tung Date: Wed, 18 Jun 2025 15:10:53 +1000 Subject: [PATCH 7/7] Fix further inconsistencies. Signed-off-by: Jimmy Tung --- src/zepben/eas/client/eas_client.py | 26 ++++++++++++-------------- src/zepben/eas/client/work_package.py | 2 +- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index cb1fc34..36da373 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -212,7 +212,6 @@ async def async_get_work_package_cost_estimation(self, work_package: WorkPackage "workPackageName": work_package.name, "input": { "feederConfigs": { - "type": "feederConfigs", "configs": [ { "feeder": config.feeder, @@ -221,13 +220,13 @@ async def async_get_work_package_cost_estimation(self, work_package: WorkPackage "timePeriod": { "startTime": config.load_time.start_time.isoformat(), "endTime": config.load_time.end_time.isoformat(), - "loadOverrides": config.load_time.load_overrides and { + "overrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} } if isinstance(config.load_time, TimePeriod) else None, "fixedTime": config.load_time and { - "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), - "loadOverrides": config.load_time.load_overrides and { + "loadTime": config.load_time.time.isoformat(), + "overrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} } if isinstance(config.load_time, FixedTime) else None, @@ -241,13 +240,13 @@ async def async_get_work_package_cost_estimation(self, work_package: WorkPackage "timePeriod": { "startTime": work_package.syf_config.load_time.start_time.isoformat(), "endTime": work_package.syf_config.load_time.end_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { + "overrides": work_package.syf_config.load_time.load_overrides and { key: value.__dict__ for key, value in work_package.syf_config.load_time.load_overrides.items()} } if isinstance(work_package.syf_config.load_time, TimePeriod) else None, "fixedTime": work_package.syf_config.load_time and { - "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { + "loadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), + "overrides": work_package.syf_config.load_time.load_overrides and { key: value.__dict__ for key, value in work_package.syf_config.load_time.load_overrides.items()} } if isinstance(work_package.syf_config.load_time, FixedTime) else None @@ -432,7 +431,6 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "workPackageName": work_package.name, "input": { "feederConfigs": { - "type": "feederConfigs", "configs": [ { "feeder": config.feeder, @@ -441,13 +439,13 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "timePeriod": { "startTime": config.load_time.start_time.isoformat(), "endTime": config.load_time.end_time.isoformat(), - "loadOverrides": config.load_time.load_overrides and { + "overrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} } if isinstance(config.load_time, TimePeriod) else None, "fixedTime": config.load_time and { - "fetchLoadTime": config.load_time.fetch_load_time.isoformat(), - "loadOverrides": config.load_time.load_overrides and { + "loadTime": config.load_time.time.isoformat(), + "overrides": config.load_time.load_overrides and { key: value.__dict__ for key, value in config.load_time.load_overrides.items()} } if isinstance(config.load_time, FixedTime) else None, @@ -461,13 +459,13 @@ async def async_run_hosting_capacity_work_package(self, work_package: WorkPackag "timePeriod": { "startTime": work_package.syf_config.load_time.start_time.isoformat(), "endTime": work_package.syf_config.load_time.end_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { + "overrides": work_package.syf_config.load_time.load_overrides and { key: value.__dict__ for key, value in work_package.syf_config.load_time.load_overrides.items()} } if isinstance(work_package.syf_config.load_time, TimePeriod) else None, "fixedTime": work_package.syf_config.load_time and { - "fetchLoadTime": work_package.syf_config.load_time.fetch_load_time.isoformat(), - "loadOverrides": work_package.syf_config.load_time.load_overrides and { + "loadTime": work_package.syf_config.load_time.time.isoformat(), + "overrides": work_package.syf_config.load_time.load_overrides and { key: value.__dict__ for key, value in work_package.syf_config.load_time.load_overrides.items()} } if isinstance(work_package.syf_config.load_time, FixedTime) else None diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index 07dffca..9d26048 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -148,7 +148,7 @@ class FixedTime: """ def __init__(self, time: datetime, load_overrides: Optional[Dict[str, FixedTimeLoadOverride]] = None): - self.fetch_load_time = time.replace(tzinfo=None) + self.time = time.replace(tzinfo=None) self.load_overrides = load_overrides