Skip to content

Commit bd8cebb

Browse files
chestm007kgreav
andauthored
Tracing api V2 - Pass 2 (#29)
Signed-off-by: Kurt Greaves <kurt.greaves@zepben.com> Signed-off-by: Max Chesterfield <max.chesterfield@zepben.com> Co-authored-by: Kurt Greaves <kurt.greaves@zepben.com>
1 parent 9f8c3d1 commit bd8cebb

File tree

8 files changed

+122
-48
lines changed

8 files changed

+122
-48
lines changed

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
"zepben.evolve==0.48.0",
2525
"numba==0.60.0",
2626
"geojson==2.5.0",
27-
"gql[requests]==3.4.1"
27+
"gql[requests]==3.4.1",
28+
"geopandas",
29+
"pandas",
30+
"shapely"
2831
],
2932
extras_require={
3033
"test": test_deps,

src/zepben/examples/connecting_to_grpc_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ async def connect_using_token():
114114
from zepben.evolve import connect_with_token, NetworkConsumerClient
115115

116116
with open("config.json") as f:
117-
c = json.loads(f.read())
117+
c = json.load(f)
118118

119119
print("Connecting to EWB..")
120120
channel = connect_with_token(

src/zepben/examples/current_state_manipulations.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
66

77
import asyncio
8+
import json
89
import sys
910
from typing import List, Set
1011

1112
from zepben.evolve import (
1213
Feeder, PowerTransformer, Switch, Tracing, NetworkConsumerClient, connect_with_password, Terminal,
13-
BusbarSection, ConductingEquipment, Breaker, EquipmentContainer, StepContext, NetworkTraceStep
14+
BusbarSection, ConductingEquipment, Breaker, EquipmentContainer, StepContext, NetworkTraceStep, connect_with_token
1415
)
1516

1617
from zepben.protobuf.nc.nc_requests_pb2 import INCLUDE_ENERGIZED_FEEDERS, INCLUDE_ENERGIZING_FEEDERS
@@ -48,8 +49,8 @@ async def fetch_zone_feeders(client: NetworkConsumerClient):
4849
await client.get_equipment_container(
4950
feeder.mrid,
5051
Feeder,
51-
include_energizing_containers=INCLUDE_ENERGIZED_FEEDERS,
52-
include_energized_containers=INCLUDE_ENERGIZING_FEEDERS
52+
include_energizing_containers=INCLUDE_ENERGIZING_FEEDERS,
53+
include_energized_containers=INCLUDE_ENERGIZED_FEEDERS
5354
)
5455
print("CPM feeders fetched.")
5556

@@ -244,11 +245,11 @@ def log_txs(desc: str, feeders: Set[Feeder]):
244245

245246

246247
async def main():
247-
if len(sys.argv) != 6:
248-
raise TypeError("you must provided the CLIENT_ID, username, password, host and port to connect")
249248

250249
# noinspection PyTypeChecker
251-
async with connect_with_password(*sys.argv[1:]) as secure_channel:
250+
with open('config.json') as f:
251+
config = json.load(f)
252+
async with connect_with_token(**config) as secure_channel:
252253
await run_simple(NetworkConsumerClient(secure_channel))
253254
await run_swap_feeder(NetworkConsumerClient(secure_channel))
254255

src/zepben/examples/examining_connectivity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def build_network() -> NetworkService:
2626

2727
# We create the objects, and their Terminals
2828
_es = EnergySource(mrid="es", terminals=[
29-
Terminal(mrid="es-t")
29+
Terminal(mrid="es_t")
3030
])
3131

3232
_hv_line = AcLineSegment(mrid="hv_line", terminals=[

src/zepben/examples/export_open_dss_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from datetime import datetime
88

99
from zepben.eas.client.opendss import OpenDssConfig
10-
from zepben.eas.client.work_package import GeneratorConfig, ModelConfig, LoadPlacement, FeederScenarioAllocationStrategy, SolveConfig, RawResultsConfig, \
10+
from zepben.eas.client.work_package import GeneratorConfig, ModelConfig, FeederScenarioAllocationStrategy, SolveConfig, RawResultsConfig, \
1111
MeterPlacementConfig, SwitchMeterPlacementConfig, SwitchClass
1212
from zepben.eas import EasClient, TimePeriod
1313
from time import sleep

src/zepben/examples/fetching_network_hierarchy.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ async def main():
2323

2424
print("Network hierarchy:")
2525
for gr in network_hierarchy.result.geographical_regions.values():
26-
print(f"- {gr.name}")
26+
print(f"- GeographicalRegion mRID: {gr.mrid} name: {gr.name}")
2727
for sgr in gr.sub_geographical_regions:
28-
print(f" - {sgr.name}")
28+
print(f" - SubgeographicalRegion mRID: {sgr.mrid} name: {sgr.name}")
2929
for sub in sgr.substations:
30-
print(f" - {sub.name}")
30+
print(f" - Substation mRID: {sub.mrid} name: {sub.name}")
3131
for fdr in sub.feeders:
32-
print(f" - {fdr.name}")
32+
print(f" - Feeder mRID: {fdr.mrid} name: {fdr.name}")
3333

3434

3535
if __name__ == "__main__":

src/zepben/examples/tracing_conductor_type_by_lv_circuit.py

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
import csv
99
import json
1010
import os
11-
from typing import Any, List, Union
11+
from typing import List, Union, Tuple, Optional, Dict
1212

13-
from zepben.evolve import NetworkConsumerClient, PhaseStep, PhaseCode, AcLineSegment, \
14-
Switch, normal_downstream_trace, FeederDirection, connect_with_token
15-
from zepben.evolve.services.network.tracing.phases.phase_step import start_at
16-
from zepben.protobuf.nc.nc_requests_pb2 import IncludedEnergizedContainers
13+
from zepben.evolve import NetworkConsumerClient, PhaseCode, AcLineSegment, \
14+
FeederDirection, connect_with_token, Tracing, downstream, NetworkTraceStep, ConductingEquipment, PowerTransformer
15+
from zepben.protobuf.nc.nc_requests_pb2 import INCLUDE_ENERGIZED_LV_FEEDERS
16+
17+
LineInfo = Tuple[str, str, Optional[Union[int, float]]]
1718

1819

1920
async def main():
@@ -27,45 +28,38 @@ async def main():
2728
result = (await client.get_network_hierarchy()).throw_on_error().result
2829
print("Connection Established")
2930

30-
switch_to_line_type: dict[str, tuple[list[Any], bool]] = {}
31+
tx_to_line_type: Dict[str, Tuple[List[LineInfo], bool]] = {}
3132

3233
os.makedirs("csvs", exist_ok=True)
3334
for feeder in result.feeders.values():
3435
print(f"Fetching {feeder.mrid}")
3536
if not (network := await get_feeder_network(channel, feeder.mrid)): # Skip feeders that fail to pull down
3637
print(f"Failed to retrieve feeder {feeder.mrid}")
3738
continue
38-
for io in network.objects(Switch):
39+
for io in network.objects(PowerTransformer):
40+
print(io)
3941
_loop = False
4042

4143
for t in io.terminals:
4244
t_dir = t.normal_feeder_direction
4345
if t_dir == FeederDirection.BOTH:
4446
_loop = True
4547

46-
sw_name = io.name
47-
sw_id = io.mrid
4848

49-
# Currently using switch with the following name as a marker for LV circuit heads
50-
if "Circuit Head Switch" in sw_name:
51-
switch_to_line_type[sw_id] = (
52-
await get_downstream_trace(start_at(io, PhaseCode.ABCN)),
53-
loop
54-
)
55-
await save_to_csv(switch_to_line_type, feeder.mrid)
49+
tx_to_line_type[io.mrid] = (await get_downstream_trace(io, PhaseCode.ABCN), _loop)
50+
await save_to_csv(tx_to_line_type, feeder.mrid)
5651

5752

58-
async def save_to_csv(data: dict[str, tuple[list[Any], bool]], feeder_mrid):
53+
async def save_to_csv(data: Dict[str, Tuple[List[LineInfo], bool]], feeder_mrid):
5954
filename = f"csvs/conductor_types_{feeder_mrid}.csv"
6055
with open(filename, mode='w', newline='') as file:
6156
writer = csv.writer(file)
62-
writer.writerow(["Feeder", "Switch", "Line", "Line Type", "Length", "Loop"])
57+
writer.writerow(["Feeder", "Transformer", "Line", "Line Type", "Length", "Loop"])
6358

64-
for switch, (values, loop) in data.items():
65-
for i in range(0, len(values), 3):
66-
line_type = values[i + 1] if i + 1 < len(values) else ""
67-
length = values[i + 2] if i + 2 < len(values) else ""
68-
switch_data = [feeder_mrid, switch, values[i], line_type, length, loop]
59+
for transformer, (values, loop) in data.items():
60+
for value in values:
61+
line, line_type, length = value
62+
switch_data = [feeder_mrid, transformer, line, line_type, length, loop]
6963
writer.writerow(switch_data)
7064

7165
print(f"Data saved to {filename}")
@@ -85,22 +79,18 @@ async def get_feeder_network(channel, feeder_mrid):
8579
return client.service
8680

8781

88-
async def get_downstream_trace(ce: ConductingEquipment, phase_code: PhaseCode) -> list[Union[str, float]]:
89-
l_type: List[Union[str, float]] = []
90-
91-
def collect_eq_in():
92-
async def add_eq(ps: NetworkTraceStep, _):
93-
equip = ps.path.to_equipment
94-
if isinstance(equip, AcLineSegment):
95-
nonlocal l_type
96-
l_type.extend((equip.mrid, equip.asset_info.name, equip.length or 0))
82+
async def get_downstream_trace(ce: ConductingEquipment, phase_code: PhaseCode) -> List[LineInfo]:
83+
l_type: List[LineInfo] = []
9784

98-
return add_eq
85+
def collect_eq_in(step: NetworkTraceStep, _):
86+
if isinstance(equip := step.path.to_equipment, AcLineSegment):
87+
nonlocal l_type
88+
l_type.append((equip.mrid, equip.asset_info.name, equip.length or 0))
9989

10090
await (
10191
Tracing.network_trace()
10292
.add_condition(downstream())
103-
.add_step_action(collect_eq_in())
93+
.add_step_action(collect_eq_in)
10494
).run(start=ce, phases=phase_code)
10595

10696
return l_type
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Copyright 2025 Zeppelin Bend Pty Ltd
2+
#
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
#
7+
# This Source Code Form is subject to the terms of the Mozilla Public
8+
# License, v. 2.0. If a copy of the MPL was not distributed with this
9+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
10+
import asyncio
11+
import json
12+
import os.path
13+
from dataclasses import dataclass
14+
import pandas as pd
15+
16+
from zepben.evolve import NetworkConsumerClient, connect_with_token, PowerTransformer
17+
18+
OUTPUT_FILE = "transformer_id_mapping.csv"
19+
HEADER = True
20+
21+
with open("./config.json") as f:
22+
c = json.loads(f.read())
23+
24+
25+
async def connect():
26+
channel = connect_with_token(host=c["host"], rpc_port=c["rpc_port"], access_token=c["access_token"], ca_filename=c["ca_path"])
27+
network_client = NetworkConsumerClient(channel=channel)
28+
29+
if os.path.exists(OUTPUT_FILE):
30+
print(f"Output file {OUTPUT_FILE} already exists, please delete it if you would like to regenerate.")
31+
return
32+
33+
network_hierarchy = (await network_client.get_network_hierarchy()).throw_on_error().value
34+
35+
print("Network hierarchy:")
36+
for gr in network_hierarchy.geographical_regions.values():
37+
print(f"- Geographical region: {gr.name}")
38+
for sgr in gr.sub_geographical_regions:
39+
print(f" - Subgeographical region: {sgr.name}")
40+
for sub in sgr.substations:
41+
print(f" - Zone Substation: {sub.name}")
42+
await process_nodes(sub.mrid, channel)
43+
for fdr in sub.feeders:
44+
print(f" - Processing Feeder: {fdr.name}")
45+
await process_nodes(fdr.mrid, channel)
46+
return # Only process the first zone...
47+
48+
49+
@dataclass
50+
class NetworkObject(object):
51+
dist_tx_id: str
52+
dist_tx_name: str
53+
container: str
54+
container_mrid: str
55+
56+
57+
async def process_nodes(container_mrid: str, channel):
58+
global HEADER
59+
print("Fetching from server ...")
60+
network_client = NetworkConsumerClient(channel=channel)
61+
network_service = network_client.service
62+
(await network_client.get_equipment_container(container_mrid)).throw_on_error()
63+
container = network_service.get(container_mrid)
64+
container_name = container.name
65+
66+
print("Processing equipment ...")
67+
network_objects = []
68+
for equip in network_service.objects(PowerTransformer):
69+
no = NetworkObject(equip.mrid, equip.name, container_name, container_mrid)
70+
network_objects.append(no)
71+
72+
network_objects = pd.DataFrame(network_objects)
73+
network_objects.to_csv(OUTPUT_FILE, index=False, mode='a', header=HEADER)
74+
print(f"Finished processing {container_mrid}")
75+
if HEADER:
76+
HEADER = False
77+
78+
79+
if __name__ == "__main__":
80+
asyncio.run(connect())

0 commit comments

Comments
 (0)