Auditing Valve Operability and Turn-Count Records
An isolation trace returns valves, and a crew closes them. Everything between those two sentences assumes the valves can actually be turned — an assumption the network model rarely records and almost never checks. A valve that has not been exercised in a decade, one that seized after a main replacement re-graded the road above it, and one that closes eleven of its fourteen turns before meeting debris all read as ordinary open valves in the isolation layer. The last is the worst: it reports as closed after the crew turns it, and the main stays live. This guide builds the operability audit that keeps those valves out of isolation sets, using exercise dates, turn counts and a criticality-driven interval. It extends the barrier discipline in valve and isolator mapping strategies.
Environment Prerequisites
- An isolation feature class carrying operability attribution: last exercise date, recorded
turns, manufacturer turn specification, and an operable flag distinct from
OPERATIONAL_STATUS. - An exercise interval per criticality class rather than one interval for the estate; a transmission isolation valve and a hydrant lateral valve do not need the same cadence.
- Python 3.11 with
pandas>=2.0;arcpyonly where the exclusion is written back to the geodatabase rather than applied in the trace layer. - A work-management feed carrying completed exercise records, so the audit reads what the crews actually did rather than what was scheduled.
- A defined shortfall tolerance — how far short of the specification a valve may stop before it is downgraded — agreed with operations rather than chosen in code.
- A route into the isolation trace so an inoperable valve is excluded from results immediately, not at the next data load.
Schema-Aware Validation Protocol — Run Before the Audit
- Confirm operability is a separate attribute from status. An estate that encodes “seized” as a status value has made every trace that filters on status wrong in one direction or the other.
- Check the manufacturer turn specification is populated per valve class. Without it, a turn count is a number with nothing to compare against.
- Reconcile exercise records against work orders. An exercise date with no completed work order behind it is a date somebody typed, and it will keep a bad valve in service.
- Look for valves with no exercise record at all. These are not compliant-by-default; they are unknown, and unknown is closer to inoperable than to operable for planning purposes.
- Verify the criticality assignment. The interval is driven by it, so a valve whose criticality was never rolled up from the network is on the wrong cadence.
Minimal Reproducible Implementation
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from datetime import date, timedelta
import pandas as pd
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
LOG = logging.getLogger("valve-audit")
INTERVAL_DAYS = {"CRITICAL": 365, "HIGH": 730, "STANDARD": 1825}
SHORTFALL_TOLERANCE = 0.10 # 10% short of specification is still acceptable
@dataclass
class ValveFinding:
valve_id: str
state: str # OPERABLE, DUE, RESTRICTED, INOPERABLE, UNKNOWN
reason: str
exclude_from_isolation: bool
@dataclass
class AuditResult:
findings: list[ValveFinding] = field(default_factory=list)
@property
def excluded(self) -> list[str]:
return [f.valve_id for f in self.findings if f.exclude_from_isolation]
def audit_valves(valves: pd.DataFrame, as_of: date) -> AuditResult:
"""Classify every valve by operability and decide which to exclude from isolation.
``valves`` carries valve_id, criticality, last_exercised, recorded_turns,
spec_turns and operable_flag. The audit is deliberately conservative: a valve with
no record is UNKNOWN and excluded, because planning a shutoff around a valve nobody
has touched is the assumption this audit exists to remove.
"""
result = AuditResult()
for _, row in valves.iterrows():
valve_id = str(row["valve_id"])
crit = str(row.get("criticality", "STANDARD"))
interval = timedelta(days=INTERVAL_DAYS.get(crit, 1825))
last = row.get("last_exercised")
spec = row.get("spec_turns")
turns = row.get("recorded_turns")
if pd.isna(last) or pd.isna(spec):
result.findings.append(ValveFinding(
valve_id, "UNKNOWN", "no exercise record or no turn specification", True))
continue
if row.get("operable_flag") is False:
result.findings.append(ValveFinding(
valve_id, "INOPERABLE", "flagged inoperable by the last exercise", True))
continue
if not pd.isna(turns) and float(turns) < float(spec) * (1 - SHORTFALL_TOLERANCE):
shortfall = float(spec) - float(turns)
state = "INOPERABLE" if float(turns) < float(spec) * 0.5 else "RESTRICTED"
result.findings.append(ValveFinding(
valve_id, state,
f"stopped {shortfall:.0f} turn(s) short of {float(spec):.0f}", True))
continue
overdue = (as_of - pd.to_datetime(last).date()) > interval
if overdue:
result.findings.append(ValveFinding(
valve_id, "DUE", f"last exercised beyond the {crit} interval", False))
else:
result.findings.append(ValveFinding(valve_id, "OPERABLE", "", False))
LOG.info("%d valve(s): %d excluded from isolation", len(result.findings),
len(result.excluded))
return result
DUE is deliberately not excluded. A valve past its interval is a maintenance finding, not
evidence of a defect, and excluding every overdue valve from isolation planning would empty the
isolation layer in most estates. RESTRICTED and UNKNOWN are excluded, because in both cases
there is positive reason to doubt the valve.
Production Deployment Pattern
- Feed the exclusion list into the isolation trace, not into the data. The trace should consult the exclusion at query time, so a repaired valve returns to service as soon as the exercise record arrives.
- Drive exercise scheduling from the audit. The
DUElist is the work programme, ordered by criticality, and it belongs in the maintenance system rather than in a spreadsheet. - Record turns on every exercise, not just success or failure. The turn count is what distinguishes a valve that is fine from one that is on its way to seizing.
- Treat a partial closure as an incident. A valve that reads closed and passes water has already invalidated at least one isolation plan; the finding should reach whoever planned it.
- Publish operability alongside status in every consumer. A dispatch console showing status without operability is showing half the picture during the event where the difference matters.
- Persist the audit. Findings, intervals, tolerance and the as-of date, so a later review can see what was known about a valve on the day a shutoff was planned around it.
- Report coverage, not just findings. The share of the isolation layer with a current exercise record is the number that describes the estate; a finding count says nothing about how much of the network was assessable in the first place.
Conclusion
Operability is a second dimension the isolation layer needs and usually lacks. Auditing exercise dates against criticality-driven intervals finds the valves nobody has touched; comparing recorded turns against the manufacturer specification finds the ones that no longer travel; and excluding restricted and unknown valves from isolation results keeps a shutoff plan honest. The exercise programme that follows is ordinary maintenance work — but it is prioritised by what the network actually depends on rather than by what is easy to reach.
Related
- Up to the parent topic: Valve & Isolator Mapping Strategies
- Up to the section: Topology & Tracing Workflows
- Emergency Isolation Scripting for Water Main Breaks
- Syncing SCADA Barriers to Valve State in Real Time
- Condition-Based Maintenance Scheduling
For authoritative reference, consult the AWWA standards catalogue and the pandas documentation.