How to run a Hosting Capacity Work Package
This feature is only available to certain customers. If you are interested in using this feature, please contact Zepben for more information.
This guide shows a basic Python example for starting an LV Hosting Capacity work package through the Energy Workbench Python client.
An intrinsic work package estimates additional import or export capacity by adding synthetic load or generation to selected network locations until configured voltage or thermal limits are reached. For a conceptual overview of how this works and what the results mean, see What is Intrinsic Hosting Capacity Mode?.
Prerequisites
Before running an intrinsic work package, you need:
- Access to an Energy Workbench server.
- Credentials with permission to create hosting capacity work packages (
HC_WORK_PACKAGE:CREATE). - Python and the
zepben.easpackage installed. - The feeder mRIDs, scenario names, and years you want to run.
pip install zepben.eas
Minimal Example
Create an EasClient, build an IntrinsicWorkPackageInput, then submit it using Mutation.run_intrinsic_work_package.
from datetime import datetime
import asyncio
from zepben.eas.client.eas_client import EasClient
from zepben.eas import (
Mutation,
IntrinsicInitialLoadStateConfigInput,
IntrinsicInitialStateSelectorMode,
IntrinsicSyfConfigInput,
IntrinsicWorkPackageInput,
)
client = EasClient(
host=eas_server_host,
port=eas_server_port,
protocol=eas_server_protocol,
access_token=eas_server_access_token,
verify_certificate=eas_server_verify_certificate,
ca_filename=eas_server_ca_filename,
)
work_package = IntrinsicWorkPackageInput(
syf=IntrinsicSyfConfigInput(
feeders=["feeder-mrid-1"],
scenario="base",
year=2025,
),
initialStateSelector=IntrinsicInitialLoadStateConfigInput(
selectorMode=IntrinsicInitialStateSelectorMode.FIXED_TIME,
startTime=datetime.fromisoformat("2024-01-11T12:00:00"),
includeLoads=True,
includeExistingDer=True,
loadScalingFactor=1.0,
derScalingFactor=1.0,
),
)
async def main():
result = await client.mutation(
Mutation.run_intrinsic_work_package(
work_package,
work_package_name="Example intrinsic work package",
)
)
print(result)
asyncio.run(main())
The mutation returns the new work package ID if the request is accepted.
Required Configuration
SYF Config
syf defines the work package scope.
| Field | Required | Notes |
|---|---|---|
| feeders | Yes | List of feeder mRIDs. Must contain at least one feeder. Each feeder is solved independently. |
| year | Yes | Forecast year (integer). |
| scenario | No | Scenario name used when selecting the initial state. Defaults to "base" on the server if omitted. |
Initial State Selector Config
initialStateSelector defines the starting load and generation state for the run.
| Field | Required | Notes |
|---|---|---|
| selectorMode | Yes | One of ZERO_LOAD, FIXED_LOAD, PEAK_FEEDER_IMPORT, PEAK_FEEDER_EXPORT, or FIXED_TIME. |
| startTime | Yes | Required for all modes. For FIXED_TIME, this is the exact load time to use. For PEAK_FEEDER_IMPORT and PEAK_FEEDER_EXPORT, this is the start of the period to search for the peak. For ZERO_LOAD and FIXED_LOAD, this determines the network topology snapshot (switching state) used for the run. |
| endTime | Conditional | Required for PEAK_FEEDER_IMPORT and PEAK_FEEDER_EXPORT. Must be after startTime. |
| perCustomerLoadWatts | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
| perCustomerGenWatts | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
| perCustomerLoadVar | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
| perCustomerGenVar | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
| includeLoads | No | Whether existing loads should be included in initial load states. Defaults to true. |
| includeExistingDer | No | Whether existing DER should be included in initial load states. Defaults to true. |
| loadScalingFactor | No | Scaling factor applied to existing loads in initial load states. Defaults to 1.0. |
| derScalingFactor | No | Scaling factor applied to existing DER in initial load states. Defaults to 1.0. |
includeLoads, includeExistingDer, loadScalingFactor and derScalingFactor are only supported for FIXED_TIME, PEAK_FEEDER_IMPORT, and PEAK_FEEDER_EXPORT. See Adjusting the Existing Load and DER for what they mean.
FIXED_TIME - snapshot of actual load and generation at a specific moment:
initialStateSelector=IntrinsicInitialLoadStateConfigInput(
selectorMode=IntrinsicInitialStateSelectorMode.FIXED_TIME,
startTime=datetime.fromisoformat("2024-01-11T12:00:00"),
includeLoads=True,
includeExistingDer=True,
)
PEAK_FEEDER_EXPORT - worst-case export moment in a time window (most conservative for solar hosting capacity):
initialStateSelector=IntrinsicInitialLoadStateConfigInput(
selectorMode=IntrinsicInitialStateSelectorMode.PEAK_FEEDER_EXPORT,
startTime=datetime.fromisoformat("2024-01-01T00:00:00"),
endTime=datetime.fromisoformat("2024-12-31T23:59:59"),
includeLoads=True,
includeExistingDer=True,
)
PEAK_FEEDER_IMPORT - worst-case import moment (most conservative for load growth or EV assessments):
initialStateSelector=IntrinsicInitialLoadStateConfigInput(
selectorMode=IntrinsicInitialStateSelectorMode.PEAK_FEEDER_IMPORT,
startTime=datetime.fromisoformat("2024-01-01T00:00:00"),
endTime=datetime.fromisoformat("2024-12-31T23:59:59"),
includeLoads=True,
includeExistingDer=True,
)
ZERO_LOAD - empty network, no existing load or DER. Produces a theoretical upper bound on network capacity:
initialStateSelector=IntrinsicInitialLoadStateConfigInput(
selectorMode=IntrinsicInitialStateSelectorMode.ZERO_LOAD,
startTime=datetime.fromisoformat("2024-01-01T00:00:00"),
)
FIXED_LOAD - uniform per-customer baseline, useful for standardised comparisons across feeders:
initialStateSelector=IntrinsicInitialLoadStateConfigInput(
selectorMode=IntrinsicInitialStateSelectorMode.FIXED_LOAD,
startTime=datetime.fromisoformat("2024-01-01T00:00:00"),
perCustomerLoadWatts=500.0,
perCustomerGenWatts=0.0,
perCustomerLoadVar=0.0,
perCustomerGenVar=0.0,
)
Common Optional Configuration
Search Config
Use search to control the intrinsic capacity search.
work_package.search = IntrinsicSearchConfigInput(
stepKwPerCustomer=5.0,
maxSteps=1000,
stopOnHvViolation=True,
lockOutCapacityZoneOnViolation=True,
)
| Field | Default | Restriction |
|---|---|---|
| stepKwPerCustomer | 1.0 | |
| maxSteps | 1000 | Must be 10000 or less. |
| stopOnHvViolation | true | Stops the search when HV violations are observed. |
| lockOutCapacityZoneOnViolation | true | Stops incrementing additional import/export in a capacity zone after it violates constraints. |
The search can add at most stepKwPerCustomer x maxSteps per customer, so set the two together. For choosing a step size, see How the Search Works.
Injection Resource Config
Use injectionResource to control whether the intrinsic resource is modelled as export generation or import load.
work_package.injectionResource = IntrinsicInjectionResourceConfigInput(
method=IntrinsicInjectionResourceMethod.EXPORT_GENERATION,
loadModelType=IntrinsicLoadModelType.NEGATIVE_LOAD,
powerFactor=0.95,
phaseMatching=IntrinsicPhaseMatching.MATCH_CUSTOMER_PHASES,
)
| Field | Default | Notes |
|---|---|---|
| method | EXPORT_GENERATION | Use EXPORT_GENERATION for export hosting capacity or IMPORT_LOAD for import capacity. |
| loadModelType | NEGATIVE_LOAD | How added generation is represented in OpenDSS. Use PV_SYSTEM only when a PV profile is available. For IMPORT_LOAD, leave as NEGATIVE_LOAD - the addition is modelled as an ordinary load, and PV_SYSTEM cannot add load. This field does not change the direction - method does. |
| pvProfileId | None | Required when loadModelType is PV_SYSTEM (Must exist in the hosting capacity input database). |
| powerFactor | 1.0 | |
| phaseMatching | MATCH_CUSTOMER_PHASES | Currently the only exposed option. |
Allocation Config
Use allocation to control how intrinsic capacity is distributed.
from zepben.eas import (
IntrinsicAllocationConfigInput,
IntrinsicAllocationMethod,
IntrinsicAllocationScope,
IntrinsicExistingCapacityBasis,
)
work_package.allocation = IntrinsicAllocationConfigInput(
method=IntrinsicAllocationMethod.WEIGHTED_BY_EXISTING_CUSTOMER,
scope=IntrinsicAllocationScope.WITHIN_MEASUREMENT_ZONE,
existingCapacityBasis=IntrinsicExistingCapacityBasis.EXISTING_EXPORT_KW,
)
| Field | Default | Notes |
|---|---|---|
| method | UNIFORM_PER_INCLUDED_CUSTOMER | Use WEIGHTED_BY_EXISTING_CUSTOMER to weight by existing import or export. |
| scope | None | Required when method is WEIGHTED_BY_EXISTING_CUSTOMER. Values: FEEDER_WIDE, WITHIN_MEASUREMENT_ZONE. |
| existingCapacityBasis | None | Required when method is WEIGHTED_BY_EXISTING_CUSTOMER. Values: EXISTING_EXPORT_KW, EXISTING_IMPORT_KW. |
Constraints Config
Use constraints to enable voltage and thermal limits. Supplying an hv or lv block enables that constraint.
from zepben.eas import (
IntrinsicConstraintsConfigInput,
IntrinsicHvVoltageConstraintInput,
IntrinsicLvVoltageConstraintInput,
IntrinsicRatingBasis,
IntrinsicThermalConstraintInput,
IntrinsicThermalConstraintsInput,
IntrinsicVoltageConstraintsInput,
)
work_package.constraints = IntrinsicConstraintsConfigInput(
voltage=IntrinsicVoltageConstraintsInput(
hv=IntrinsicHvVoltageConstraintInput(minPu=0.95, maxPu=1.05),
lv=IntrinsicLvVoltageConstraintInput(min=216.0, max=253.0),
),
thermal=IntrinsicThermalConstraintsInput(
hv=IntrinsicThermalConstraintInput(
percentOfRating=100.0,
ratingBasis=IntrinsicRatingBasis.NORMAL,
),
lv=IntrinsicThermalConstraintInput(
percentOfRating=100.0,
ratingBasis=IntrinsicRatingBasis.NORMAL,
),
),
)
| Block | Field | Default | Notes |
|---|---|---|---|
voltage.hv | minPu | 0.90 | Minimum HV voltage in per unit. |
voltage.hv | maxPu | 1.054 | Maximum HV voltage in per unit. |
voltage.lv | min | 216.0 | Minimum LV voltage in volts. |
voltage.lv | max | 253.0 | Maximum LV voltage in volts. |
voltage.lv | measurementType | MAGNITUDE | MAGNITUDE or PERCENTILE. See below. |
thermal.hv / thermal.lv | percentOfRating | 100.0 | Thermal limit as a percentage of the asset rating. |
thermal.hv / thermal.lv | ratingBasis | NORMAL | NORMAL or EMERGENCY. |
Defaults only apply to a block you supply. A block you leave out is not checked.
measurementType (LV voltage only) controls how the limits are checked within each capacity group:
MAGNITUDE- a single phase voltage outsidemin/maxis a breach. Recorded asMagnitudeLvConstraint-Vmin/MagnitudeLvConstraint-Vmax.PERCENTILE- a breach only occurs when the group's 1st percentile voltage (v1) drops belowminor its 99th percentile (v99) rises abovemax, allowing a few outlying voltages. The percentiles are fixed. Recorded asPercentileLvConstraint-V1Limit/PercentileLvConstraint-V99Limit. In small groups v1 and v99 are effectively the lowest and highest voltages, so results matchMAGNITUDE.
Model Configuration
The model field uses the same model configuration input (HcModelConfigInput) as a normal hosting capacity work package, so use the existing work package model configuration options when you need to override model generation behaviour.
Troubleshooting
OpenDSS error 485: Max Control Iterations Exceeded
The power flow did not settle within the allowed control iterations. Changing the injection powerFactor can trigger this.
- Increase
maxControlIterin the Solve Config (default 20), e.g. to 100. If it still fails at 100, the iteration limit is not the problem. - A common cause is a voltage regulator hunting between taps. Try widening
closedLoopVBandand/or adjustingclosedLoopVRegSetPoint- suitable values vary by feeder. See Closed Loop Voltage Control.
Monitoring The Run
After the mutation returns a work package ID, use the standard work package manager or progress APIs described in How to run a work package and How to manage work packages.
Understanding the Results
Once the work package completes, results are written to the Intrinsic Hosting Capacity output tables. For a guide on interpreting those results - including how to read headroom values, what it means when a result hits maxSteps, and how to trace binding constraints - see Understanding Intrinsic Results.