Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,12 +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.
* Added `FixedTimeLoadOverride` and `TimePeriodLoadOverride` class

### Enhancements
* Added work package config documentation.
Expand Down
102 changes: 82 additions & 20 deletions src/zepben/eas/client/eas_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down Expand Up @@ -211,15 +211,46 @@ 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,
"feederConfigs": {
"configs": [
{
"feeder": config.feeder,
"years": config.years,
"scenarios": config.scenarios,
"timePeriod": {
"startTime": config.load_time.start_time.isoformat(),
"endTime": config.load_time.end_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, TimePeriod) else None,
"fixedTime": config.load_time 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,
} 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(),
"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 {
"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
} 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 {
Expand Down Expand Up @@ -273,7 +304,7 @@ 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,
},
"solve": work_package.generator_config.solve and {
"normVMinPu": work_package.generator_config.solve.norm_vmin_pu,
Expand Down Expand Up @@ -399,15 +430,46 @@ 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,
"feederConfigs": {
"configs": [
{
"feeder": config.feeder,
"years": config.years,
"scenarios": config.scenarios,
"timePeriod": {
"startTime": config.load_time.start_time.isoformat(),
"endTime": config.load_time.end_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, TimePeriod) else None,
"fixedTime": config.load_time 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,
} 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(),
"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 {
"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
} 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 {
Expand Down
134 changes: 128 additions & 6 deletions src/zepben/eas/client/work_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -38,6 +38,11 @@
"WriterOutputConfig",
"WriterConfig",
"YearRange",
"FixedTimeLoadOverride",
"TimePeriodLoadOverride",
"ForecastConfig",
"FeederConfig",
"FeederConfigs",
]


Expand All @@ -62,14 +67,89 @@ class SwitchMeterPlacementConfig:
"""


@dataclass
class FixedTimeLoadOverride:

load_watts: Optional[float]
"""
The reading to be used to override load watts
"""

gen_watts: Optional[float]
"""
The reading to be used to override gen watts
"""

load_var: Optional[float]
"""
The reading to be used to override load var
"""

gen_var: Optional[float]
"""
The reading to be used to override gen var
"""

# def __str__(self):


@dataclass
class TimePeriodLoadOverride:

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, 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: 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, 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: 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, 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: 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, 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
"""


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):
def __init__(self, time: datetime, load_overrides: Optional[Dict[str, FixedTimeLoadOverride]] = None):
self.time = time.replace(tzinfo=None)
self.load_overrides = load_overrides


class TimePeriod:
Expand All @@ -82,11 +162,13 @@ class TimePeriod:
def __init__(
self,
start_time: datetime,
end_time: datetime
end_time: datetime,
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)
self.end_time = end_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
self.load_overrides = load_overrides

@staticmethod
def _validate(start_time: datetime, end_time: datetime):
Expand Down Expand Up @@ -688,9 +770,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"""

Expand All @@ -712,6 +792,48 @@ 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 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, 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
if configuration varies per feeder.
"""

quality_assurance_processing: Optional[bool] = None
"""Whether to enable QA processing"""

Expand Down
Loading