Modeling Asset-Retirement Workflows in ArcGIS Pro
Retiring a utility feature is the most dangerous edit in the asset lifecycle, because it is the one that most easily leaves the network internally inconsistent. Deleting the feature outright destroys the history a rate case or a failure investigation depends on; flipping a status field by hand leaves the feature still wired into its subnetwork, so a trace keeps walking through an asset that no longer carries flow. Doing it ad hoc — one editor, one right-click, no validation — is how a retired transformer ends up energized in the model, or a removed main keeps absorbing customers into a phantom pressure zone. At the scale of a replacement program that retires thousands of features a season, an unrepeatable manual procedure guarantees drift between the ground and the geodatabase, and that drift is exactly what a locate request or a compliance audit surfaces at the worst moment. A retirement must therefore be a governed transition of the lifecycle state machine for utility assets, executed as one atomic, auditable operation, and it belongs to the broader discipline of asset lifecycle and maintenance automation.
This page provides a complete, copy-paste arcpy workflow that transitions a feature out of the active network safely: it validates the preconditions, disables the feature in its subnetwork, sets its lifecycle status to retired inside a single edit operation, preserves an immutable history event, and revalidates topology and tracing before committing. It assumes the connectivity and state-machine foundations described across the parent guide and the topology and tracing workflows that every trace over the network inherits.
Environment Prerequisites
A retirement run against a misconfigured environment produces the most dangerous outcome — a commit that reports success while leaving the feature half-retired. Lock the following before running anything:
- ArcGIS Pro 3.2+ with an active Standard or Advanced license, required for utility network editing and
arcpy.unsubnetwork operations. - The ArcGIS Pro conda environment cloned and activated, with
arcpyimportable from the target Python 3.11 interpreter; never mutate the basearcgispro-py3environment. - A versioned enterprise geodatabase connection (
.sde) pointing at an isolated named version, neverDEFAULT, so the retirement reconciles and posts as a reviewable change set. - A validated utility network topology with no outstanding dirty areas in the work area; validate first, because a retirement over a dirty backlog cannot be trusted to revalidate cleanly.
- The lifecycle-status coded-value domain loaded with exactly
PROPOSED,ACTIVE,ABANDONED, andRETIRED, applied to the target feature class. - An append-only audit table with insert grants for the automation account and no update or delete grants for ordinary editors, so history cannot be rewritten after the fact.
- A defined subnetwork controller and tier for the domain network you are editing, so disabling the feature’s subnetwork participation resolves against a real subnetwork definition.
Schema-Aware Precondition Protocol — Run Before the Retirement
Most retirement failures originate not in the status update itself but in a precondition that was never checked. Work this ordered checklist first; the earliest item is the most frequent culprit.
- Confirm the topology is validated. Query for dirty areas intersecting the feature’s extent. A retirement computed over unvalidated topology can leave a dangling edge that the confirming trace will not catch because the trace itself is running over stale connectivity.
- Confirm the transition is legal. Read the feature’s current lifecycle status and verify that moving it to retired is permitted by the state machine — an active or abandoned feature may retire, but a proposed feature should be deleted from the design version, not retired, and a feature already retired must not be retired again.
- Confirm no active contents remain. If the feature is a container (a vault, a cabinet, a structure), verify that nothing active is contained by or structurally attached to it. Retiring a pole while a live transformer hangs on it is the containment violation that most often slips through.
- Confirm the feature is not a subnetwork controller. A feature that controls a subnetwork cannot simply be retired; its controller role must be reassigned first, or the subnetwork loses its source and every downstream trace fails.
- Confirm you hold an edit lock on an isolated version. Verify the edit session targets the named version and not
DEFAULT, so a bad run is discarded at reconcile rather than immediately polluting the production network everyone else is editing.
Minimal Reproducible Implementation
The following workflow uses arcpy for the edit operation and the network operations, and a plain append to an audit table for history. It validates preconditions, disables the feature in its subnetwork, sets lifecycle status to retired inside one edit operation so a failure rolls back atomically, preserves an audit event, and revalidates topology before returning a structured result. Every step is wrapped so that any failure aborts the transition rather than committing a partial retirement.
import arcpy
import logging
from datetime import datetime, timezone
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
LEGAL_FROM = {"ACTIVE", "ABANDONED"} # states from which retirement is permitted
def retire_feature(
workspace: str,
un_path: str,
feature_class: str,
object_id: int,
actor: str,
work_order: str,
audit_table: str,
) -> dict:
"""Retire one utility-network feature safely and auditable.
Validates preconditions, disables subnetwork participation, sets lifecycle
status to RETIRED inside a single edit operation, appends an immutable audit
event, and revalidates topology. Returns a structured result dict.
Any precondition failure or edit error aborts the transition without commit.
"""
where = f"OBJECTID = {object_id}"
result: dict = {"object_id": object_id, "retired": False, "reason": None}
# 1. Precondition: read current lifecycle status and confirm the transition is legal.
try:
with arcpy.da.SearchCursor(feature_class, ["ASSETID", "LIFECYCLESTATUS"], where) as cur:
asset_id, status = next(iter(cur))
except StopIteration:
result["reason"] = "feature not found"
logging.error("OID %s not found in %s", object_id, feature_class)
return result
if status not in LEGAL_FROM:
result["reason"] = f"illegal transition {status} -> RETIRED"
logging.error("OID %s: %s", object_id, result["reason"])
return result
# 2. Precondition: topology must be clean over the work area before we mutate it.
dirty = arcpy.management.GetCount(
arcpy.management.MakeFeatureLayer(f"{un_path}/Dirty_Areas", "dirty_lyr")
)
if int(dirty[0]) > 0:
result["reason"] = "dirty areas present; validate topology first"
logging.error("OID %s: %s", object_id, result["reason"])
return result
edit = arcpy.da.Editor(workspace)
try:
edit.startEditing(with_undo=True, multiuser_mode=True)
edit.startOperation()
# 3. Disable the feature in its subnetwork: delete the connectivity and
# containment associations that wire it into the network, so it stops
# carrying flow. ModifyAssociations removes the association records for
# this feature; the association type depends on the domain network.
arcpy.un.ModifyAssociations(
in_utility_network=un_path,
operation="DELETE",
input_table=f"{feature_class}|{object_id}",
)
# 4. Set lifecycle status to RETIRED inside this same edit operation.
with arcpy.da.UpdateCursor(feature_class, ["LIFECYCLESTATUS"], where) as ucur:
for row in ucur:
row[0] = "RETIRED"
ucur.updateRow(row)
edit.stopOperation()
edit.stopEditing(save_changes=True)
except Exception as exc: # any failure rolls the whole operation back
if edit.isEditing:
edit.stopOperation()
edit.stopEditing(save_changes=False)
result["reason"] = f"edit failed and rolled back: {exc}"
logging.exception("OID %s: retirement aborted", object_id)
return result
# 5. Preserve history: append an immutable audit event (never overwrite).
event = {
"asset_id": asset_id,
"prior_status": status,
"new_status": "RETIRED",
"actor": actor,
"work_order": work_order,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
with arcpy.da.InsertCursor(
audit_table,
["ASSETID", "PRIOR_STATUS", "NEW_STATUS", "ACTOR", "WORK_ORDER", "EVENT_TS"],
) as icur:
icur.insertRow(
(asset_id, status, "RETIRED", actor, work_order, event["timestamp"])
)
# 6. Revalidate topology so the retirement leaves no dirty area behind.
arcpy.un.ValidateNetworkTopology(un_path)
result.update(retired=True, reason="ok", audit_event=event)
logging.info("OID %s (%s) retired by %s under %s", object_id, asset_id, actor, work_order)
return result
# Example execution
# outcome = retire_feature(
# workspace="C:/data/uneditor.sde",
# un_path="C:/data/uneditor.sde/Water/WaterNetwork",
# feature_class="C:/data/uneditor.sde/Water/WaterLine",
# object_id=48213,
# actor="j.rivera",
# work_order="WO-2026-11884",
# audit_table="C:/data/uneditor.sde/LifecycleAudit",
# )
# print(outcome)
The retirement lives inside one startOperation/stopOperation pair so the subnetwork disable and the status update either both commit or both roll back — there is no window in which the feature is de-energized but still marked active, or retired but still carrying connectivity. The audit insert happens only after the edit commits, so the history never records a retirement that did not actually take. The LEGAL_FROM guard is the same legal-transition contract enforced in the parent state machine, applied here at the one transition that matters most.
The sequence below shows where the atomic boundary sits and why revalidation closes the loop.
Two arcpy.un operations carry the network semantics. Disabling the feature’s participation clears it from the subnetwork so downstream traces stop walking through it, and ValidateNetworkTopology at the end recomputes the dirty areas the disable introduced, confirming the network is internally consistent before the version is posted. Consult the utility network trace and topology tools in the ArcGIS Pro documentation for the exact tool signatures and licensing requirements in your release, and the feature editing with arcpy.da guidance for the edit-operation semantics the atomic boundary relies on.
Production Deployment Pattern
A one-off retirement in an interactive session is a demonstration; an enforced, repeatable workflow is an engineering control. Promote the retirement into the asset lifecycle as follows:
- Run against a named, isolated version. Execute every retirement in a dedicated version created for the work order, then reconcile and post it as a reviewable change set. This keeps a mis-scoped batch out of
DEFAULTuntil a reviewer approves it, and it mirrors the version-isolation discipline every data ingestion pipeline for utility assets applies. - Drive retirements from the work-management system. A retirement should be triggered by a closed CMMS work order, not an editor’s judgment. Consume completed removal work orders over the enterprise REST endpoint, map each to its feature by asset ID, and call
retire_featurewith the work-order number as the audit key so every retirement traces to a physical action in the field. - Wire the confirming trace as a build gate. After the batch reconciles, run a downstream trace from the affected subnetwork controller and assert that no retired feature appears in the result. Let a failure block the post, so a retirement that left the feature wired into the network is caught before it reaches production.
- Apply backoff on transient enterprise-geodatabase errors. SDE reads and edit-session starts fail intermittently under multi-user load. Wrap the edit-session acquisition in a bounded retry — three attempts with exponential backoff — so a momentary lock produces a retry rather than a spuriously aborted retirement and a noisy failure.
- Persist the run manifest. Append each batch’s retired object IDs, prior statuses, work-order numbers, the validation result, and the
arcpyversion to a timestamped, version-controlled log. This chain of custody is what an asset-management review or a reliability audit reads to confirm that what left the network on the map actually left the network in the field.
Conclusion
Retiring a utility feature safely is not a status edit; it is an atomic transition that disables the feature in its subnetwork, sets its lifecycle status inside a single rollback-safe edit operation, records an immutable history event, and revalidates topology before it commits. Enforcing that sequence keeps the model consistent with the ground, prevents retired assets from lingering as phantom connectivity in traces, and produces the version-stamped audit trail a compliance or rate-case review requires. Because the workflow is triggered by closed work orders and gated by a confirming trace, retirement becomes a repeatable engineering control rather than an editor’s one-off. The natural next step is to generalize the same atomic pattern to the other lifecycle transitions — as-built promotion and reactivation — so every state change on the network is governed identically.
Related
- Up to the parent topic: Lifecycle State Machines for Utility Assets
- Up to the section: Asset Lifecycle & Maintenance Automation
- Condition-Based Maintenance Scheduling
- CMMS & GIS Integration for Work Orders
- Topology & Tracing Workflows