From 54ab6ac45f7ecb1d373a0b1adc5222e038ca2695 Mon Sep 17 00:00:00 2001 From: Marcus Koh Date: Wed, 11 Feb 2026 16:01:15 +1100 Subject: [PATCH 1/5] first draft Signed-off-by: Marcus Koh --- changelog.md | 6 ++++-- src/zepben/eas/client/eas_client.py | 3 ++- src/zepben/eas/client/work_package.py | 13 +++++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 2620487..baf48bc 100644 --- a/changelog.md +++ b/changelog.md @@ -1,13 +1,15 @@ # EAS Python client ## [0.28.0] - UNRELEASED ### Breaking Changes -* None. +* For `CandidateGenerationConfig`, replaced `voltage_delta_avg_threshold` with `average_voltage_spread_threshold`, which serves the same purpose but is + in volts instead of voltage per-unit. ### New Features * None. ### Enhancements -* None. +* For `CandidateGenerationConfig`, you may supply a `nominal_low_voltage` in volts to adjust the target "ideal" voltage for LV customers when running + `TAP_OPTIMIZATION` interventions. ### Fixes * None. diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index 5c93fe1..8db8441 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -386,11 +386,12 @@ def work_package_config_to_json(self, work_package: WorkPackageConfig) -> dict: "candidateGeneration": work_package.intervention.candidate_generation and { "type": work_package.intervention.candidate_generation.type.name, "interventionCriteriaName": work_package.intervention.candidate_generation.intervention_criteria_name, - "voltageDeltaAvgThreshold": work_package.intervention.candidate_generation.voltage_delta_avg_threshold, + "averageVoltageSpreadThreshold": work_package.intervention.candidate_generation.average_voltage_spread_threshold, "voltageUnderLimitHoursThreshold": work_package.intervention.candidate_generation.voltage_under_limit_hours_threshold, "voltageOverLimitHoursThreshold": work_package.intervention.candidate_generation.voltage_over_limit_hours_threshold, "tapWeightingFactorLowerThreshold": work_package.intervention.candidate_generation.tap_weighting_factor_lower_threshold, "tapWeightingFactorUpperThreshold": work_package.intervention.candidate_generation.tap_weighting_factor_upper_threshold, + "nominalLowVoltage": work_package.intervention.candidate_generation.nominal_low_voltage, }, "allocationCriteria": work_package.intervention.allocation_criteria, "specificAllocationInstance": work_package.intervention.specific_allocation_instance, diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index caa60e9..d7c8a91 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -767,9 +767,13 @@ class CandidateGenerationConfig: base work package run. Only used when type is CRITERIA. """ - voltage_delta_avg_threshold: Optional[float] = None + average_voltage_spread_threshold: Optional[int] = None """ - The threshold for average deviation in voltage p.u. across the transformer. Only used when type is TAP_OPTIMIZATION. + The threshold for average voltage spread under the transformer over the year, in volts. + Voltage spread at each timestep is calculated by taking the difference between the maximum and minimum voltage over + the nodes under the transformer, for each phase, then taking the maximum of that difference across all phases. + When the average voltage spread exceeds this threshold, it indicates that the transformer is experiencing a + significant voltage swing that may impact system stability. Only used when type is CRITERIA. """ voltage_under_limit_hours_threshold: Optional[int] = None @@ -800,6 +804,11 @@ class CandidateGenerationConfig: positive. Only used when type is TAP_OPTIMIZATION. """ + nominal_low_voltage: Optional[int] = None + """ + The nominal line (phase-to-phase) voltage for LV customers in volts. + """ + @dataclass class PhaseRebalanceProportions: From ea16f85d12020e2d0f4a86eaa318d6e76c01d883 Mon Sep 17 00:00:00 2001 From: Marcus Koh Date: Thu, 12 Feb 2026 14:58:09 +1100 Subject: [PATCH 2/5] remove nominal low voltage from intervention config Signed-off-by: Marcus Koh --- changelog.md | 3 +-- src/zepben/eas/client/eas_client.py | 3 +-- src/zepben/eas/client/work_package.py | 5 ----- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index baf48bc..5c879b8 100644 --- a/changelog.md +++ b/changelog.md @@ -8,8 +8,7 @@ * None. ### Enhancements -* For `CandidateGenerationConfig`, you may supply a `nominal_low_voltage` in volts to adjust the target "ideal" voltage for LV customers when running - `TAP_OPTIMIZATION` interventions. +* None. ### Fixes * None. diff --git a/src/zepben/eas/client/eas_client.py b/src/zepben/eas/client/eas_client.py index 8db8441..0709c8b 100644 --- a/src/zepben/eas/client/eas_client.py +++ b/src/zepben/eas/client/eas_client.py @@ -390,8 +390,7 @@ def work_package_config_to_json(self, work_package: WorkPackageConfig) -> dict: "voltageUnderLimitHoursThreshold": work_package.intervention.candidate_generation.voltage_under_limit_hours_threshold, "voltageOverLimitHoursThreshold": work_package.intervention.candidate_generation.voltage_over_limit_hours_threshold, "tapWeightingFactorLowerThreshold": work_package.intervention.candidate_generation.tap_weighting_factor_lower_threshold, - "tapWeightingFactorUpperThreshold": work_package.intervention.candidate_generation.tap_weighting_factor_upper_threshold, - "nominalLowVoltage": work_package.intervention.candidate_generation.nominal_low_voltage, + "tapWeightingFactorUpperThreshold": work_package.intervention.candidate_generation.tap_weighting_factor_upper_threshold }, "allocationCriteria": work_package.intervention.allocation_criteria, "specificAllocationInstance": work_package.intervention.specific_allocation_instance, diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index d7c8a91..c9bc008 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -804,11 +804,6 @@ class CandidateGenerationConfig: positive. Only used when type is TAP_OPTIMIZATION. """ - nominal_low_voltage: Optional[int] = None - """ - The nominal line (phase-to-phase) voltage for LV customers in volts. - """ - @dataclass class PhaseRebalanceProportions: From 5d1bbeb0c433c727d0fec10a62c0df6f3e8b49cb Mon Sep 17 00:00:00 2001 From: Marcus Koh Date: Fri, 13 Feb 2026 10:34:41 +1100 Subject: [PATCH 3/5] update changelog Signed-off-by: Marcus Koh --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 5c879b8..fc71bad 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ ### Breaking Changes * For `CandidateGenerationConfig`, replaced `voltage_delta_avg_threshold` with `average_voltage_spread_threshold`, which serves the same purpose but is in volts instead of voltage per-unit. + * EAS must support this change in the GraphQL schema (`v2.10.0` and above). ### New Features * None. From c7558758eea60240ae48a4df25881bf015e03d4b Mon Sep 17 00:00:00 2001 From: Marcus Koh Date: Thu, 19 Feb 2026 10:49:03 +1100 Subject: [PATCH 4/5] fix description Signed-off-by: Marcus Koh --- src/zepben/eas/client/work_package.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zepben/eas/client/work_package.py b/src/zepben/eas/client/work_package.py index c9bc008..6343933 100644 --- a/src/zepben/eas/client/work_package.py +++ b/src/zepben/eas/client/work_package.py @@ -769,11 +769,11 @@ class CandidateGenerationConfig: average_voltage_spread_threshold: Optional[int] = None """ - The threshold for average voltage spread under the transformer over the year, in volts. - Voltage spread at each timestep is calculated by taking the difference between the maximum and minimum voltage over + The threshold for average line voltage spread under the transformer over the year, in volts. + Voltage spread at each timestep is calculated by taking the difference between the maximum and minimum phase-to-phase voltage over the nodes under the transformer, for each phase, then taking the maximum of that difference across all phases. When the average voltage spread exceeds this threshold, it indicates that the transformer is experiencing a - significant voltage swing that may impact system stability. Only used when type is CRITERIA. + significant voltage swing that may impact system stability. Only used when type is TAP_OPTIMIZATION. """ voltage_under_limit_hours_threshold: Optional[int] = None From 210e8a99ad7ecaf55584139b5acb1823ceab292d Mon Sep 17 00:00:00 2001 From: Marcus Koh Date: Thu, 19 Feb 2026 10:59:56 +1100 Subject: [PATCH 5/5] test Signed-off-by: Marcus Koh --- test/test_eas_client.py | 52 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/test/test_eas_client.py b/test/test_eas_client.py index e8b68a3..f2504e2 100644 --- a/test/test_eas_client.py +++ b/test/test_eas_client.py @@ -17,7 +17,7 @@ from werkzeug import Response from zepben.ewb.auth import ZepbenTokenFetcher -from zepben.eas import EasClient, Study, SolveConfig, InterventionConfig, YearRange +from zepben.eas import EasClient, Study, SolveConfig, InterventionConfig, YearRange, CandidateGenerationConfig, CandidateGenerationType from zepben.eas import FeederConfig, ForecastConfig, FixedTimeLoadOverride from zepben.eas.client.ingestor import IngestorConfigInput, IngestorRunsSortCriteriaInput, IngestorRunsFilterInput, \ IngestorRunState, IngestorRuntimeKind @@ -1744,3 +1744,53 @@ def test_work_package_config_to_json_includes_server_defaulted_fields_if_specifi "dvms": None, "allocationLimitPerYear": 5 } + +def test_work_package_config_to_json_for_tap_optimization(httpserver: HTTPServer): + eas_client = EasClient( + LOCALHOST, + httpserver.port, + verify_certificate=False + ) + + wp_config = WorkPackageConfig( + name="wp", + syf_config=FeederConfigs([]), + intervention=InterventionConfig( + base_work_package_id="abc", + year_range=YearRange(2020, 2025), + intervention_type=InterventionClass.DISTRIBUTION_TAP_OPTIMIZATION, + allocation_limit_per_year=5, + candidate_generation=CandidateGenerationConfig( + type=CandidateGenerationType.TAP_OPTIMIZATION, + average_voltage_spread_threshold=40, + voltage_under_limit_hours_threshold=1, + voltage_over_limit_hours_threshold=2, + tap_weighting_factor_lower_threshold=-0.3, + tap_weighting_factor_upper_threshold=0.4 + ) + ) + ) + json_config = eas_client.work_package_config_to_json(wp_config) + + assert json_config["intervention"] == { + "baseWorkPackageId": "abc", + "yearRange": { + "maxYear": 2025, + "minYear": 2020 + }, + "interventionType": "DISTRIBUTION_TAP_OPTIMIZATION", + "candidateGeneration": { + "type": "TAP_OPTIMIZATION", + "interventionCriteriaName": None, + "averageVoltageSpreadThreshold": 40, + "voltageUnderLimitHoursThreshold": 1, + "voltageOverLimitHoursThreshold": 2, + "tapWeightingFactorLowerThreshold": -0.3, + "tapWeightingFactorUpperThreshold": 0.4, + }, + "allocationCriteria": None, + "specificAllocationInstance": None, + "phaseRebalanceProportions": None, + "dvms": None, + "allocationLimitPerYear": 5 + }