How to run an LV Intrinsic Hosting Capacity Work Package
This guide shows a basic Python example for starting an LV intrinsic 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 zepben.eas.client.eas_client import EasClient
from zepben.eas.lib.custom_mutations import Mutation
from zepben.eas.lib import (
IntrinsicInitialLoadStateConfigInput,
IntrinsicInitialStateSelectorMode,
IntrinsicSyfConfigInput,
IntrinsicWorkPackageInput,
)
client = EasClient(
host=eas_server_host,
port=eas_server_port,
protocol=eas_server_protocol,
client_id=eas_server_client_id,
username=eas_server_username,
password=eas_server_password,
client_secret=eas_server_client_secret,
verify_certificate=eas_server_verify_certificate,
ca_filename=eas_server_ca_filename,
)
work_package = IntrinsicWorkPackageInput(
syf=IntrinsicSyfConfigInput(
feeders=["feeder-mrid-1"],
year=2025,
scenario="base",
),
initial_state_selector=IntrinsicInitialLoadStateConfigInput(
selector_mode=IntrinsicInitialStateSelectorMode.FIXED_TIME,
start_time="2024-01-11T12:00:00",
include_loads=True,
include_existing_der=True,
load_scaling_factor=1.0,
der_scaling_factor=1.0,
),
)
result = client.mutation(
Mutation.run_intrinsic_work_package(
input=work_package,
work_package_name="Example intrinsic work package",
)
)
print(result)
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 | A forecast year. |
scenario | No | Scenario name used when selecting the initial state. Defaults to "base" on the server if omitted. |
Initial State Selector Config
initial_state_selector defines the starting load and generation state for the run.
| Field | Required | Notes |
|---|---|---|
selector_mode | Yes | One of ZERO_LOAD, FIXED_LOAD, PEAK_FEEDER_IMPORT, PEAK_FEEDER_EXPORT, or FIXED_TIME. |
start_time | Yes | For FIXED_TIME, this is the exact load time. For peak feeder modes, this is the start of the search period. |
end_time | Conditional | Required for PEAK_FEEDER_IMPORT and PEAK_FEEDER_EXPORT. Must be after start_time. |
per_customer_load_watts | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
per_customer_gen_watts | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
per_customer_load_var | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
per_customer_gen_var | Conditional | Required for FIXED_LOAD; rejected for other selector modes. |
include_loads | No | Whether existing loads should be included in initial load states. Defaults to true. Only supported for FIXED_TIME, PEAK_FEEDER_IMPORT, and PEAK_FEEDER_EXPORT. |
include_existing_der | No | Whether existing DER should be included in initial load states. Defaults to true. Only supported for FIXED_TIME, PEAK_FEEDER_IMPORT, and PEAK_FEEDER_EXPORT. |
load_scaling_factor | No | Scaling factor applied to existing loads in initial load states. Defaults to 1.0. Only supported for FIXED_TIME, PEAK_FEEDER_IMPORT, and PEAK_FEEDER_EXPORT. |
der_scaling_factor | No | Scaling factor applied to existing DER in initial load states. Defaults to 1.0. Only supported for FIXED_TIME, PEAK_FEEDER_IMPORT, and PEAK_FEEDER_EXPORT. |
initial_state_selector=IntrinsicInitialLoadStateConfigInput(
selector_mode=IntrinsicInitialStateSelectorMode.FIXED_TIME,
start_time="2024-01-11T12:00:00",
include_loads=True,
include_existing_der=True,
load_scaling_factor=1.0,
der_scaling_factor=1.0,
)
Common Optional Configuration
Search Config
Use searchConfig to control the intrinsic capacity search.
from zepben.eas.lib import IntrinsicSearchConfigInput
work_package.search = IntrinsicSearchConfigInput(
step_kw_per_customer=5.0,
max_steps=1000,
stop_on_hv_violation=True,
lock_out_capacity_zone_on_violation=True,
)
| Field | Default | Restriction |
|---|---|---|
step_kw_per_customer | 1.0 | |
max_steps | 1000 | Must be 10000 or less. |
stop_on_hv_violation | true | Stops the search when HV violations are observed. |
lock_out_capacity_zone_on_violation | true | Stops incrementing additional import/export in a capacity zone after it violates constraints. |
Injection Resource Config
Use injection_resource to control whether the intrinsic resource is modelled as export generation or import load.
from zepben.eas.lib import (
IntrinsicInjectionResourceConfigInput,
IntrinsicInjectionResourceMethod,
IntrinsicLoadModelType,
IntrinsicPhaseMatching,
)
work_package.injection_resource = IntrinsicInjectionResourceConfigInput(
method=IntrinsicInjectionResourceMethod.EXPORT_GENERATION,
load_model_type=IntrinsicLoadModelType.NEGATIVE_LOAD,
power_factor=0.95,
phase_matching=IntrinsicPhaseMatching.MATCH_CUSTOMER_PHASES,
)
| Field | Default | Notes |
|---|---|---|
method | EXPORT_GENERATION | Use EXPORT_GENERATION for export hosting capacity or IMPORT_LOAD for import capacity. |
load_model_type | NEGATIVE_LOAD | Use PV_SYSTEM only when a PV profile is available. |
pv_profile_id | None | Required when load_model_type is PV_SYSTEM (Must exist in the hosting capacity input database). |
power_factor | 1.0 | |
phase_matching | MATCH_CUSTOMER_PHASES | Currently the only exposed option. |
Allocation Config
Use allocationConfig to control how intrinsic capacity is distributed.
from zepben.eas.lib import (
IntrinsicAllocationConfigInput,
IntrinsicAllocationMethod,
IntrinsicAllocationScope,
IntrinsicExistingCapacityBasis,
)
work_package.allocation = IntrinsicAllocationConfigInput(
method=IntrinsicAllocationMethod.WEIGHTED_BY_EXISTING_CUSTOMER,
scope=IntrinsicAllocationScope.WITHIN_MEASUREMENT_ZONE,
existing_capacity_basis=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. |
existing_capacity_basis | 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.lib import (
IntrinsicConstraintsConfigInput,
IntrinsicHvVoltageConstraintInput,
IntrinsicLvVoltageConstraintInput,
IntrinsicRatingBasis,
IntrinsicThermalConstraintInput,
IntrinsicThermalConstraintsInput,
IntrinsicVoltageConstraintsInput,
)
work_package.constraints = IntrinsicConstraintsConfigInput(
voltage=IntrinsicVoltageConstraintsInput(
hv=IntrinsicHvVoltageConstraintInput(min_pu=0.95, max_pu=1.05),
lv=IntrinsicLvVoltageConstraintInput(min=216.0, max=253.0),
),
thermal=IntrinsicThermalConstraintsInput(
hv=IntrinsicThermalConstraintInput(
percent_of_rating=100.0,
rating_basis=IntrinsicRatingBasis.NORMAL,
),
lv=IntrinsicThermalConstraintInput(
percent_of_rating=100.0,
rating_basis=IntrinsicRatingBasis.NORMAL,
),
),
)
Full Example
from zepben.eas.client.eas_client import EasClient
from zepben.eas.lib.custom_mutations import Mutation
from zepben.eas.lib import (
IntrinsicAllocationConfigInput,
IntrinsicAllocationMethod,
IntrinsicAllocationScope,
IntrinsicConstraintsConfigInput,
IntrinsicExistingCapacityBasis,
IntrinsicHvVoltageConstraintInput,
IntrinsicInitialLoadStateConfigInput,
IntrinsicInitialStateSelectorMode,
IntrinsicInjectionResourceConfigInput,
IntrinsicInjectionResourceMethod,
IntrinsicLoadModelType,
IntrinsicLvVoltageConstraintInput,
IntrinsicRatingBasis,
IntrinsicSearchConfigInput,
IntrinsicSyfConfigInput,
IntrinsicThermalConstraintInput,
IntrinsicThermalConstraintsInput,
IntrinsicVoltageConstraintsInput,
IntrinsicWorkPackageInput,
)
client = EasClient(...)
work_package = IntrinsicWorkPackageInput(
syf=IntrinsicSyfConfigInput(
feeders=["feeder-mrid-1"],
year=2025,
scenario="base",
),
initial_state_selector=IntrinsicInitialLoadStateConfigInput(
selector_mode=IntrinsicInitialStateSelectorMode.FIXED_TIME,
start_time="2024-01-11T12:00:00",
include_loads=True,
include_existing_der=True,
load_scaling_factor=1.0,
der_scaling_factor=1.0,
),
search=IntrinsicSearchConfigInput(
step_kw_per_customer=5.0,
max_steps=1000,
stop_on_hv_violation=True,
lock_out_capacity_zone_on_violation=True,
),
allocation=IntrinsicAllocationConfigInput(
method=IntrinsicAllocationMethod.WEIGHTED_BY_EXISTING_CUSTOMER,
scope=IntrinsicAllocationScope.WITHIN_MEASUREMENT_ZONE,
existing_capacity_basis=IntrinsicExistingCapacityBasis.EXISTING_EXPORT_KW,
),
injection_resource=IntrinsicInjectionResourceConfigInput(
method=IntrinsicInjectionResourceMethod.EXPORT_GENERATION,
load_model_type=IntrinsicLoadModelType.NEGATIVE_LOAD,
power_factor=0.95,
),
constraints=IntrinsicConstraintsConfigInput(
voltage=IntrinsicVoltageConstraintsInput(
hv=IntrinsicHvVoltageConstraintInput(min_pu=0.95, max_pu=1.05),
lv=IntrinsicLvVoltageConstraintInput(min=216.0, max=253.0),
),
thermal=IntrinsicThermalConstraintsInput(
hv=IntrinsicThermalConstraintInput(
percent_of_rating=100.0,
rating_basis=IntrinsicRatingBasis.NORMAL,
),
lv=IntrinsicThermalConstraintInput(
percent_of_rating=100.0,
rating_basis=IntrinsicRatingBasis.NORMAL,
),
),
),
)
result = client.mutation(
Mutation.run_intrinsic_work_package(
input=work_package,
work_package_name="2025 export intrinsic capacity",
)
)
Model Configuration
The Python field model maps to the GraphQL model field. It uses the same model configuration input as a normal hosting capacity work package, so use the existing work package model configuration options when you need to override model generation behaviour.
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, the results are written to the Intrinsic Hosting Capacity output tables. The tables below are the ones produced by an intrinsic work package.
Where to start: lv_group_hosting_capacity
The lv_group_hosting_capacity table is your primary result. Each row is one distribution transformer and reports how much additional export or import capacity is available under it.
The two most important columns:
headroom_kw_per_customer- the maximum additional kW each customer under this transformer could receive simultaneously before a network limit was hit. For example, a value of 3.5 kW means the network can support everyone under that transformer adding up to 3.5 kW of solar at once.total_kw_added- the aggregate kW added across all customers in the group at the binding point. This is the total hosting capacity of the transformer group.
Check headroom_direction (export or import) to confirm which type of capacity you are looking at. If you ran with EXPORT_GENERATION in your injection_resource config, results will be export.
When addition_kw_scaling_applied is true, the per-customer figure reflects the headroom for the highest-weighted customer, not a uniform value.
Understanding what constrained the result: constraint_violations
For any transformer where headroom_kw_per_customer is low, join through binding_constraints to constraint_violations to see which specific asset or voltage limit stopped the search.
The metric column tells you what type of constraint it was (voltage or thermal). The violating_value and constraint_limit columns show how far over the threshold the network was at the binding iteration. The conducting_equipment_mrid identifies the specific asset - join this to your network data to locate it on the map.
Per-customer detail
For a deeper dive into per-customer allocations and connection-point voltages at the binding state, see customer_allocations in the output tables reference.