Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* None.

### Fixes
* None.
* Reordered the feeder equipment and direction assignment on database read to prevent parallel feeders from tracing back into the zone substation.

### Notes
* None.
Expand Down
25 changes: 15 additions & 10 deletions src/zepben/ewb/database/sqlite/network/network_database_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ def __init__(
async def _post_load(self) -> bool:
status = await super()._post_load()

#
# NOTE: We need to have the feeder head equipment assigned before we can set the feeder directions to prevent
# tracing back into the zone substation in parallel feeders. Rather than splitting the feeder assignment
# into two passes, we can just assign the equipment to feeders before we set the directions.
#
self._logger.info("Assigning equipment to feeders...")
await self.assign_to_feeders.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.assign_to_feeders.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
self._logger.info("Equipment assigned to feeders.")

self._logger.info("Assigning equipment to LV feeders...")
await self.assign_to_lv_feeders.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.assign_to_lv_feeders.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
self._logger.info("Equipment assigned to LV feeders.")

self._logger.info("Applying feeder direction to network...")
await self.set_feeder_direction.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.set_feeder_direction.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
Expand All @@ -91,16 +106,6 @@ async def _post_load(self) -> bool:

self._logger.info("Phasing applied to network.")

self._logger.info("Assigning equipment to feeders...")
await self.assign_to_feeders.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.assign_to_feeders.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
self._logger.info("Equipment assigned to feeders.")

self._logger.info("Assigning equipment to LV feeders...")
await self.assign_to_lv_feeders.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.assign_to_lv_feeders.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
self._logger.info("Equipment assigned to LV feeders.")

self._logger.info("Validating that each equipment is assigned to a container...")
self._validate_equipment_containers()
self._logger.info("Equipment containers validated.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ class SetDirection:
"""
Convenience class that provides methods for setting feeder direction on a [NetworkService]
This class is backed by a [BranchRecursiveTraversal].

NOTE: The feeder head equipment must be assigned to its [Feeder] before this is run. If you don't,
the feeder direction will be assigned back through feeder heads when they are run in parallel.

:param debug_logger: An optional `Logger` that can be used to log debug messages on what the underlying trace is doing. This
should only ever be used in a debug cycle, and should always be `None` in production code.
"""

def __init__(self, debug_logger: Logger = None):
Expand Down
4 changes: 2 additions & 2 deletions test/database/sqlite/network/test_network_database_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ async def test_calls_expected_processors_including_post_processes(self):
call.service.unresolved_references(),
call.service.unresolved_references().__iter__,

call.assign_to_feeders.run(self.service),
call.assign_to_lv_feeders.run(self.service),
call.set_direction.run(self.service),
call.set_phases.run(self.service),
call.phase_inferrer.run(self.service),
call.assign_to_feeders.run(self.service),
call.assign_to_lv_feeders.run(self.service),

# calls for _validate_equipment_containers()
call.service.objects(Equipment),
Expand Down
22 changes: 20 additions & 2 deletions test/database/sqlite/network/test_network_database_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
PotentialTransformer, SwitchInfo, RelayInfo, CurrentRelay, EvChargingUnit, TapChangerControl, DistanceRelay, VoltageRelay, ProtectionRelayScheme, \
ProtectionRelaySystem, Ground, GroundDisconnector, SeriesCompensator, NetworkService, GroundingImpedance, \
PetersenCoil, ReactiveCapabilityCurve, SynchronousMachine, PanDemandResponseFunction, BatteryControl, StaticVarCompensator, Tracing, NetworkStateOperators, \
NetworkTraceStep
NetworkTraceStep, TestNetworkBuilder
from zepben.ewb.model.cim.iec61968.assetinfo.cable_info import CableInfo
from zepben.ewb.model.cim.iec61968.assetinfo.overhead_wire_info import OverheadWireInfo
from zepben.ewb.model.cim.iec61968.assets.asset_owner import AssetOwner
Expand All @@ -66,13 +66,13 @@
from zepben.ewb.model.cim.iec61970.base.wires.ratio_tap_changer import RatioTapChanger
from zepben.ewb.services.common import resolver


hypothesis_settings = dict(
deadline=2000,
suppress_health_check=[HealthCheck.function_scoped_fixture, HealthCheck.too_slow],
max_examples=4
)


# FIXME: see Line [305]
class PatchedNetworkTraceStepPath(NetworkTraceStep.Path):
@property
Expand Down Expand Up @@ -702,6 +702,24 @@ def add_deferred_reference(service: NetworkService):

await self._validate_unresolved_failure(str(pec), "RegulatingControl tcc", add_deferred_reference)

async def test_assigns_feeders_in_parallel_correctly(self):
# This test is to ensure parallel feeders don't assign directions back through the feeder heads. This was seen in the wild when
# the feeder directions were set before the equipment was assigned, meaning no feeder heads were detected in the tracing.
ns = await (
TestNetworkBuilder()
.from_source() # s0
.to_breaker() # b1
.to_acls() # c2
.to_breaker() # b3
.to_source() # s4
.add_feeder("b1", 2)
.add_feeder("b3", 1)
.build(apply_directions_from_sources=False)
)

# If the read from the database matches the test network we built, then the equipment is correctly assigned.
await self._validate_schema(ns)

async def test_only_loads_street_address_fields_if_required(self):
# This test is here to make sure the database reading correctly removes the parts of loaded street addresses that are not filled out.
write_service = NetworkService()
Expand Down