Skip to content

Commit b9c6033

Browse files
authored
[DEV-4192] Support advanced PV in workpackage request (#48)
Signed-off-by: vince <vince.white@zepben.com>
1 parent d5866d4 commit b9c6033

4 files changed

Lines changed: 65 additions & 14 deletions

File tree

changelog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313
* `rating_threshold`
1414
* `simplify_plsi_threshold`
1515
* `emerg_amp_scaling`
16+
* Added optional field `inverterControlConfig` to `ModelConfig`. This `PVVoltVARVoltWattConfig` allows the configuration of advanced inverter control profiles.
1617

1718
### Enhancements
1819
* None.
1920

2021
### Fixes
21-
* None.
22+
* `TimePeriod` no longer truncates the `start_time` and `end_time` to midnight(`00:00:00`). `TimePeriod` will now preserve arbitrary start and end times to minute precision.
2223

2324
### Notes
2425
* None.

src/zepben/eas/client/eas_client.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,12 @@ def generator_config_to_json(self, generator_config: Optional[GeneratorConfig])
244244
"useSpanLevelThreshold": generator_config.model.use_span_level_threshold,
245245
"ratingThreshold": generator_config.model.rating_threshold,
246246
"simplifyPLSIThreshold": generator_config.model.simplify_plsi_threshold,
247-
"emergAmpScaling": generator_config.model.emerg_amp_scaling
247+
"emergAmpScaling": generator_config.model.emerg_amp_scaling,
248+
"inverterControlConfig": generator_config.model.inverter_control_config and {
249+
"cutOffDate": generator_config.model.inverter_control_config.cut_off_date and generator_config.model.inverter_control_config.cut_off_date.isoformat(),
250+
"beforeCutOffProfile": generator_config.model.inverter_control_config.beforeCutOffProfile,
251+
"afterCutOffProfile": generator_config.model.inverter_control_config.afterCutOffProfile
252+
}
248253
},
249254
"solve": generator_config.solve and {
250255
"normVMinPu": generator_config.solve.norm_vmin_pu,
@@ -1330,6 +1335,11 @@ async def async_get_paged_opendss_models(
13301335
ratingThreshold
13311336
simplifyPLSIThreshold
13321337
emergAmpScaling
1338+
inverterControlConfig {
1339+
cutOffDate
1340+
beforeCutOffProfile
1341+
afterCutOffProfile
1342+
}
13331343
}
13341344
solve {
13351345
normVMinPu

src/zepben/eas/client/work_package.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"LoadPlacement",
2222
"FeederScenarioAllocationStrategy",
2323
"MeterPlacementConfig",
24+
"PVVoltVARVoltWattConfig",
2425
"ModelConfig",
2526
"SolveMode",
2627
"SolveConfig",
@@ -147,7 +148,7 @@ class FixedTime:
147148
"""
148149

149150
def __init__(self, load_time: datetime, load_overrides: Optional[Dict[str, FixedTimeLoadOverride]] = None):
150-
self.load_time = load_time.replace(tzinfo=None)
151+
self.load_time = load_time.replace(second=0, microsecond=0, tzinfo=None)
151152
self.load_overrides = load_overrides
152153

153154

@@ -165,8 +166,8 @@ def __init__(
165166
load_overrides: Optional[Dict[str, TimePeriodLoadOverride]] = None
166167
):
167168
self._validate(start_time, end_time)
168-
self.start_time = start_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
169-
self.end_time = end_time.replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=None)
169+
self.start_time = start_time.replace(second=0, microsecond=0, tzinfo=None)
170+
self.end_time = end_time.replace(second=0, microsecond=0, tzinfo=None)
170171
self.load_overrides = load_overrides
171172

172173
@staticmethod
@@ -208,6 +209,22 @@ class MeterPlacementConfig:
208209
"""The ID of the meter group to use for populating EnergyMeters at EnergyConsumers."""
209210

210211

212+
@dataclass
213+
class PVVoltVARVoltWattConfig:
214+
cut_off_date: Optional[datetime] = None
215+
"""Optional cut-off date to determine which profile to apply to equipment during translation to the OpenDss model.
216+
If supplied, the "commissionedDate" of the equipment is compared against this date, equipment that do not have a
217+
"commissionedDate" will receive the [beforeCutOffProfile]. If null, the [afterCutOffProfile] profile is applied to all equipment."""
218+
219+
beforeCutOffProfile: Optional[str] = None
220+
"""Optional name of the profile to apply to equipment with a "commissionDate" before [cutOffDate].
221+
If null the equipment will be translated into a regular Generator the rather a PVSystem."""
222+
223+
afterCutOffProfile: Optional[str] = None
224+
"""Optional name of the profile to apply to equipment with a "commissionDate" after [cutOffDate].
225+
If null the equipment will be translated into a regular Generator the rather a PVSystem."""
226+
227+
211228
@dataclass
212229
class ModelConfig:
213230
vm_pu: Optional[float] = None
@@ -492,6 +509,11 @@ class ModelConfig:
492509
Set as a factor value, i.e put as 1.5 if scaling is 150%
493510
"""
494511

512+
inverter_control_config: Optional[PVVoltVARVoltWattConfig] = None
513+
"""
514+
Optional configuration object to enable modelling generation equipment as PVSystems controlled by InvControls rather than Generators.
515+
"""
516+
495517

496518
class SolveMode(Enum):
497519
YEARLY = "YEARLY"

test/test_eas_client.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
Order
2727
from zepben.eas.client.study import Result
2828
from zepben.eas.client.work_package import FeederConfigs, TimePeriodLoadOverride, \
29-
FixedTime, NodeLevelResultsConfig
29+
FixedTime, NodeLevelResultsConfig, PVVoltVARVoltWattConfig
3030
from zepben.eas.client.work_package import WorkPackageConfig, TimePeriod, GeneratorConfig, ModelConfig, \
3131
FeederScenarioAllocationStrategy, LoadPlacement, MeterPlacementConfig, SwitchMeterPlacementConfig, SwitchClass, \
3232
SolveMode, RawResultsConfig
@@ -195,8 +195,8 @@ def test_get_work_package_cost_estimation_no_verify_success(httpserver: HTTPServ
195195
[1],
196196
["scenario"],
197197
TimePeriod(
198-
datetime(2022, 1, 1),
199-
datetime(2022, 1, 2),
198+
datetime(2022, 1, 1, 10),
199+
datetime(2022, 1, 2, 12),
200200
None
201201
)
202202
)
@@ -753,6 +753,7 @@ def hosting_capacity_run_calibration_with_calibration_time_request_handler(reque
753753
'fixUndersizedServiceLines': None,
754754
'genVMaxPu': None,
755755
'genVMinPu': None,
756+
'inverterControlConfig': None,
756757
'loadIntervalLengthHours': None,
757758
'loadModel': None,
758759
'loadPlacement': None,
@@ -860,6 +861,7 @@ def hosting_capacity_run_calibration_with_generator_config_request_handler(reque
860861
'fixUndersizedServiceLines': None,
861862
'genVMaxPu': None,
862863
'genVMinPu': None,
864+
'inverterControlConfig': None,
863865
'loadIntervalLengthHours': None,
864866
'loadModel': None,
865867
'loadPlacement': None,
@@ -961,6 +963,7 @@ def hosting_capacity_run_calibration_with_partial_model_config_request_handler(r
961963
'fixUndersizedServiceLines': None,
962964
'genVMaxPu': None,
963965
'genVMinPu': None,
966+
'inverterControlConfig': None,
964967
'loadIntervalLengthHours': None,
965968
'loadModel': None,
966969
'loadPlacement': None,
@@ -1089,8 +1092,8 @@ def run_opendss_export_request_handler(request):
10891092
}]
10901093
}} if isinstance(OPENDSS_CONFIG.load_time, FixedTime) else
10911094
{"timePeriod": {
1092-
"startTime": "2022-04-01T00:00:00",
1093-
"endTime": "2023-04-01T00:00:00",
1095+
"startTime": "2022-04-01T10:13:00",
1096+
"endTime": "2023-04-01T12:14:00",
10941097
"overrides": [{
10951098
'loadId': 'meter1',
10961099
'loadWattsOverride': [1.0],
@@ -1169,7 +1172,12 @@ def run_opendss_export_request_handler(request):
11691172
"useSpanLevelThreshold": True,
11701173
"ratingThreshold": 20.0,
11711174
"simplifyPLSIThreshold": 20.0,
1172-
"emergAmpScaling": 1.8
1175+
"emergAmpScaling": 1.8,
1176+
'inverterControlConfig': {
1177+
'afterCutOffProfile': 'afterProfile',
1178+
'beforeCutOffProfile': 'beforeProfile',
1179+
'cutOffDate': '2024-04-12T11:42:00'
1180+
},
11731181
},
11741182
"solve": {
11751183
"normVMinPu": 0.9,
@@ -1214,8 +1222,8 @@ def run_opendss_export_request_handler(request):
12141222
year=2024,
12151223
feeder="feeder1",
12161224
load_time=TimePeriod(
1217-
datetime(2022, 4, 1),
1218-
datetime(2023, 4, 1),
1225+
datetime(2022, 4, 1, 10, 13),
1226+
datetime(2023, 4, 1, 12, 14),
12191227
{"meter1": TimePeriodLoadOverride([1.0], [2.0], [3.0], [4.0])}
12201228
),
12211229
model_name="TEST OPENDSS MODEL 1",
@@ -1282,7 +1290,12 @@ def run_opendss_export_request_handler(request):
12821290
use_span_level_threshold=True,
12831291
rating_threshold=20.0,
12841292
simplify_plsi_threshold=20.0,
1285-
emerg_amp_scaling= 1.8
1293+
emerg_amp_scaling=1.8,
1294+
inverter_control_config=PVVoltVARVoltWattConfig(
1295+
cut_off_date=datetime(2024, 4, 12, 11, 42),
1296+
beforeCutOffProfile="beforeProfile",
1297+
afterCutOffProfile="afterProfile"
1298+
)
12861299
),
12871300
SolveConfig(
12881301
norm_vmin_pu=0.9,
@@ -1464,6 +1477,11 @@ def test_run_opendss_export_valid_certificate_success(ca: trustme.CA, httpserver
14641477
ratingThreshold
14651478
simplifyPLSIThreshold
14661479
emergAmpScaling
1480+
inverterControlConfig {
1481+
cutOffDate
1482+
beforeCutOffProfile
1483+
afterCutOffProfile
1484+
}
14671485
}
14681486
solve {
14691487
normVMinPu

0 commit comments

Comments
 (0)