Mapping Work Order Types to Utility Network Asset Types
The integration between a maintenance system and a network model usually exchanges identifiers and status, and leaves the semantics to convention. A work order says “replace” and somebody, later, remembers to retire the old asset and commission the new one — or does not, and the model keeps a main that was cut out of the ground last spring. The fix is a mapping written as data: for each work-order type, which asset types it may target, which lifecycle transition it implies, and what model change closing it should produce. That mapping turns an exchange of identifiers into a contract both systems can validate against, before dispatch and again at closure. It extends the identity and ownership discipline in CMMS and GIS integration for work orders.
Environment Prerequisites
- A published work-order type list from the maintenance system, stable enough to map against. A free-text type field cannot be mapped and should be constrained first.
- The asset type taxonomy from the network model, and the crosswalk between the two systems’ identifiers.
- The lifecycle transition map, so an implied transition can be checked for legality from the asset’s current state.
- Python 3.11 with
requests>=2.31for the exchange; no spatial libraries are needed because this layer reasons about types and states. - A rejection route back into the maintenance system, since a work order that fails validation has to reach the planner who raised it.
- A versioned mapping file, because a change to it changes what the two systems consider legal.
Schema-Aware Validation Protocol — Run Before Accepting Orders
- Confirm every active work-order type appears in the mapping. An unmapped type is accepted by default in most integrations, which is how a replacement order comes to have no model effect.
- Check the asset-type lists against the deployed taxonomy. A mapping written against an older asset package silently rejects valid orders after a schema upgrade.
- Verify the implied transitions are legal in the state machine. A mapping that implies a transition the machine forbids will fail at closure, after the work is done.
- Test the rejection path. A rejection that does not reach a planner is a work order that quietly stops existing.
- Reconcile the crosswalk coverage. Orders referencing assets with no crosswalk entry cannot be validated at all, and their count is the size of the identity gap.
Minimal Reproducible Implementation
from __future__ import annotations
import logging
from dataclasses import dataclass, field
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
LOG = logging.getLogger("work-order-map")
# work-order type -> what the network model permits and expects
MAPPING = {
"INSPECTION": {"asset_types": {"*"}, "transition": None, "edit": "NONE"},
"REPAIR": {"asset_types": {"*"}, "transition": None, "edit": "ATTRIBUTES"},
"REPLACEMENT": {"asset_types": {"*"}, "transition": ("ACTIVE", "RETIRED"),
"edit": "GEOMETRY_AND_ASSOCIATIONS"},
"RETIREMENT": {"asset_types": {"*"}, "transition": ("ACTIVE", "ABANDONED"),
"edit": "ASSOCIATIONS"},
"NEW_CONNECTION": {"asset_types": {"ServicePoint", "ServiceLateral"},
"transition": ("PROPOSED", "ACTIVE"),
"edit": "GEOMETRY_AND_ASSOCIATIONS"},
}
LEGAL_TRANSITIONS = {
"PROPOSED": {"ACTIVE", "CANCELLED"},
"ACTIVE": {"ABANDONED", "RETIRED"},
"ABANDONED": {"RETIRED", "ACTIVE"},
"RETIRED": set(),
}
@dataclass
class Verdict:
order_id: str
accepted: bool
reason: str = ""
required_edit: str = "NONE"
@dataclass
class IntakeReport:
verdicts: list[Verdict] = field(default_factory=list)
@property
def rejected(self) -> list[Verdict]:
return [v for v in self.verdicts if not v.accepted]
def validate_order(order_id: str, order_type: str, asset_type: str,
current_state: str) -> Verdict:
"""Check an incoming work order against the mapping and the state machine.
Two checks, both cheap and both before dispatch: may this order type target this
asset type, and is the transition it implies legal from where the asset is now.
"""
spec = MAPPING.get(order_type)
if spec is None:
return Verdict(order_id, False,
f"work-order type {order_type} is not mapped")
allowed = spec["asset_types"]
if "*" not in allowed and asset_type not in allowed:
return Verdict(order_id, False,
f"{order_type} may not target asset type {asset_type}")
transition = spec["transition"]
if transition is not None:
expected_from, expected_to = transition
if current_state != expected_from:
return Verdict(order_id, False,
f"{order_type} expects an asset in {expected_from}, "
f"found {current_state}")
if expected_to not in LEGAL_TRANSITIONS.get(current_state, set()):
return Verdict(order_id, False,
f"{current_state} to {expected_to} is not a legal transition")
return Verdict(order_id, True, required_edit=spec["edit"])
def on_close(order_id: str, order_type: str) -> str:
"""The model change a closed order of this type should produce."""
spec = MAPPING.get(order_type, {})
effect = spec.get("edit", "NONE")
LOG.info("order %s closed: model effect %s", order_id, effect)
return effect
The wildcard in asset_types is deliberate and worth being careful with. Most order types
legitimately apply to anything in service, and enumerating every asset type produces a mapping
that breaks on the next schema addition. The types that genuinely need a restricted list — new
connections, for instance — are the exception, and they are the ones where an incorrectly targeted
order does real damage.
Closing the Loop
Validation before dispatch prevents bad orders. The other half is that closing a good one should change the model, and that is where most integrations stop.
An inspection closes with a condition grade and a date, which flow into the risk model. A repair closes with attribute updates. A replacement closes with two lifecycle transitions and an association transfer — retire the old asset, commission the new one, move the containment and attachment rows across. A retirement closes by driving the same gated workflow a manual retirement uses, including the confirming trace.
The important property is that none of this depends on somebody remembering. The order type says what happens, the mapping says how, and a failure at the gate produces a review item rather than a silent skip. That last point matters: an estate where model changes are attempted and sometimes refused is in far better shape than one where they are sometimes simply not attempted, because the first has a queue and the second has a slow divergence nobody is measuring.
Production Deployment Pattern
- Validate at intake, before scheduling. A rejection that arrives after a crew has been dispatched has already cost more than the check.
- Reject to the planner, with the reason. “Replacement orders expect an asset in ACTIVE, found ABANDONED” is actionable and also tells the planner something true about the network.
- Version the mapping alongside the schema. An asset package upgrade that adds types is a mapping change, and reviewing them together is what keeps the pair consistent.
- Apply the closure effect through the normal gates. The model change from a closed order is not privileged; it goes through the same version, validation and confirming trace.
- Report unmapped order types weekly. New types appear as the maintenance organisation evolves, and each unmapped one is a class of work the model never learns about.
- Track attempted-and-refused closures. A rising count means the two systems are diverging on asset state, which is a reconciliation problem rather than an integration bug.
Conclusion
Writing the work-order-to-asset mapping down as data converts a convention into a contract. The intake check stops mis-typed orders before dispatch, the lifecycle check surfaces disagreements about asset state while they are cheap, and the closure effect makes the model change a consequence of the work rather than something somebody remembers. What remains is a queue of refusals, which is exactly the artefact an estate needs in order to see where its two systems disagree.
Related
- Up to the parent topic: CMMS & GIS Integration for Work Orders
- Up to the section: Asset Lifecycle & Maintenance Automation
- Syncing Work Orders Between ArcGIS and Maximo
- Enforcing Lifecycle Transitions with Attribute Rules
- Modeling Asset Retirement Workflows in ArcGIS Pro
For authoritative reference, consult the Python dataclasses library and the requests documentation.