Skip to main content
Version: Next

How to run a Hosting Capacity Work Package

Paid Feature - Only available for some customers

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.eas package 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.

FieldRequiredNotes
feedersYesList of feeder mRIDs. Must contain at least one feeder. Each feeder is solved independently.
yearYesForecast year (integer).
scenarioNoScenario 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.

FieldRequiredNotes
selectorModeYesOne of ZERO_LOAD, FIXED_LOAD, PEAK_FEEDER_IMPORT, PEAK_FEEDER_EXPORT, or FIXED_TIME.
startTimeYesRequired 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.
endTimeConditionalRequired for PEAK_FEEDER_IMPORT and PEAK_FEEDER_EXPORT. Must be after startTime.
perCustomerLoadWattsConditionalRequired for FIXED_LOAD; rejected for other selector modes.
perCustomerGenWattsConditionalRequired for FIXED_LOAD; rejected for other selector modes.
perCustomerLoadVarConditionalRequired for FIXED_LOAD; rejected for other selector modes.
perCustomerGenVarConditionalRequired for FIXED_LOAD; rejected for other selector modes.
includeLoadsNoWhether existing loads should be included in initial load states. Defaults to true.
includeExistingDerNoWhether existing DER should be included in initial load states. Defaults to true.
loadScalingFactorNoScaling factor applied to existing loads in initial load states. Defaults to 1.0.
derScalingFactorNoScaling 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,
)
FieldDefaultRestriction
stepKwPerCustomer1.0
maxSteps1000Must be 10000 or less.
stopOnHvViolationtrueStops the search when HV violations are observed.
lockOutCapacityZoneOnViolationtrueStops 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,
)
FieldDefaultNotes
methodEXPORT_GENERATIONUse EXPORT_GENERATION for export hosting capacity or IMPORT_LOAD for import capacity.
loadModelTypeNEGATIVE_LOADHow 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.
pvProfileIdNoneRequired when loadModelType is PV_SYSTEM (Must exist in the hosting capacity input database).
powerFactor1.0
phaseMatchingMATCH_CUSTOMER_PHASESCurrently 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,
)
FieldDefaultNotes
methodUNIFORM_PER_INCLUDED_CUSTOMERUse WEIGHTED_BY_EXISTING_CUSTOMER to weight by existing import or export.
scopeNoneRequired when method is WEIGHTED_BY_EXISTING_CUSTOMER. Values: FEEDER_WIDE, WITHIN_MEASUREMENT_ZONE.
existingCapacityBasisNoneRequired 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,
),
),
)
BlockFieldDefaultNotes
voltage.hvminPu0.90Minimum HV voltage in per unit.
voltage.hvmaxPu1.054Maximum HV voltage in per unit.
voltage.lvmin216.0Minimum LV voltage in volts.
voltage.lvmax253.0Maximum LV voltage in volts.
voltage.lvmeasurementTypeMAGNITUDEMAGNITUDE or PERCENTILE. See below.
thermal.hv / thermal.lvpercentOfRating100.0Thermal limit as a percentage of the asset rating.
thermal.hv / thermal.lvratingBasisNORMALNORMAL 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 outside min/max is a breach. Recorded as MagnitudeLvConstraint-Vmin / MagnitudeLvConstraint-Vmax.
  • PERCENTILE - a breach only occurs when the group's 1st percentile voltage (v1) drops below min or its 99th percentile (v99) rises above max, allowing a few outlying voltages. The percentiles are fixed. Recorded as PercentileLvConstraint-V1Limit / PercentileLvConstraint-V99Limit. In small groups v1 and v99 are effectively the lowest and highest voltages, so results match MAGNITUDE.

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.

  1. Increase maxControlIter in the Solve Config (default 20), e.g. to 100. If it still fails at 100, the iteration limit is not the problem.
  2. A common cause is a voltage regulator hunting between taps. Try widening closedLoopVBand and/or adjusting closedLoopVRegSetPoint - 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.