PMP-0006 — Pump input-power degradation
| Status | verified — engine e2ff2f8, cxf:fnv1a128:8d0553cbafa876eb8c3007a3178aa9a3, 2026-08-20 |
| Severity | 3 |
| Method | statistical |
| Phase | 2 |
| Category | EFFICIENCY_LOSS |
| Confidence | MEDIUM |
| Estimation | BASELINE_COMPARISON |
| G36 | — |
| Clusters | — |
| Suppresses | — |
| Suppressed by | — |
| Related | PMP-0001, PMP-0002, VFD-0001, VFD-0005 |
| Playbooks | vfd-pump-faults |
| Source | DOE/Hydraulic Institute, Improving Pumping System Performance: A Sourcebook for Industry, 2nd ed., PDF pp.106 and 112–113 — assessment uses baseline energy plus flow, head, speed, fluid, and electrical data to identify efficiency degradation; Library-authored host-fitted expected-power residual; no universal pump curve or portable residual threshold is claimed; Library precedents AHU-0038 (host-published baseline and validity gate) and CHW-0001 (positive-baseline cross-multiplied degradation comparison) |
| Operating states | normal automatic pump operation after startup and before coast-down, with stable staging and a valid expected-power model |
Preconditions (host-enforced): The host owns pump_kw_expected. Train it on known-good operation for this pump, document the fit period and model inputs, freeze or version the fit for evaluation, and publish it only when ready, fresh, and in-domain. Typical inputs may include speed, individual-branch flow, differential pressure, staging, and fluid properties; all used inputs must be valid. pump_kw and pump_kw_expected must cover the same motor/drive electrical boundary and use kW. Exclude startup, coast-down, exercise, manual/bypass operation, safety/current/torque/demand limiting, and changes in parallel-pump configuration. A same-drive VFD-0001 or VFD-0005 is related evidence; suppress only if that deployment proves the active drive state or speed input invalidates this baseline and can scope the association to this pump. yBaselineOk checks numerical positivity only—false means NO_EVAL, while true does not prove freshness/domain validity.
Points: pump_status, pump_kw, pump_kw_expected
Outputs:
yFault— True while a running pump’s measured input power has remained above the valid expected baseline by more than the allowed fraction for sustained_durationyBaselineOk— Evaluability flag — true only when expected power is above minimum_expected_kw; false means NO_EVALyPowerHigh— Diagnostic direction flag — true when a numerically valid baseline has a positive residual above allowance; status is not part of this flag
Parameters:
| Name | Default | Unit | CXF path | Description |
|---|---|---|---|---|
minimum_expected_kw | 0.5 | kW | baselineOk.t | Expected-power floor for numerical evaluability. NO_PORTABLE_DEFAULT: configure above the model’s low-load/noise region; equality is not evaluable. |
max_positive_residual_fraction | 0.15 | 1 | allowance.k | Allowed positive fraction of expected input power. ADOPTED_TUNABLE; 0.15 is an executable starting band, not a published pump-wide limit. |
sustained_duration | 1800.0 | s | persist.delayTime | Continuous excess required before alarm. ADOPTED_TUNABLE 30-minute window; commission against model residuals and plant time constants. |
Description
This rule detects a pump motor/drive assembly drawing materially more active power than a known-good model expects at the same operating condition. The signature can accompany mechanical drag, impeller/strainer degradation, incorrect speed feedback, bypassed control, or reduced motor/drive efficiency; it can also be manufactured by a stale, out-of-domain, or badly scoped baseline. The model remains host-side and is published as an ordinary derived point.
Detection Logic
baseline_ok = pump_kw_expected > minimum_expected_kw
residual = pump_kw - pump_kw_expected
allowance = pump_kw_expected × max_positive_residual_fraction
power_high = baseline_ok AND residual > allowance
yBaselineOk = baseline_ok
yPowerHigh = power_high
yFault = pump_status AND power_high, sustained for sustained_duration
The graph deliberately cross-multiplies instead of dividing. For a positive
valid baseline, actual−expected > fraction×expected is exactly the requested
relative-residual test and cannot evaluate a zero denominator. Both comparisons
are strict. yPowerHigh exposes residual direction independently of run status;
only yFault is status-gated and persisted with delayOnInit=true.
Possible Diagnoses
- Fouled or damaged impeller, blocked strainer, or unexpected hydraulic load
- Bearing, seal, coupling, or alignment drag
- Motor or drive efficiency degradation
- Incorrect speed feedback or control in bypass/manual mode
- Different parallel-pump staging than the baseline condition
- Actual/expected points covering different electrical boundaries
- Stale, out-of-domain, or degradation-trained expected-power model
- Active power sensor scaling or wiring error
Energy Impact
EFFICIENCY_LOSS with BASELINE_COMPARISON. During evaluable alarm intervals the positive residual is directly measured kW above the host model, so the host may integrate it to kWh. That estimate inherits model error and must exclude invalid domains, startup, and mode changes. A low residual is intentionally not accused by this rule because it can mean successful turndown or a separate delivery failure.
Emissions Impact
Scope 2. Multiply evaluable excess kWh by the applicable marginal or accounting grid factor. Report the baseline/model uncertainty with the estimate.
Deviations
- No Divide block is used. Downstream Boolean gating does not short-circuit an elementary Divide; cross-multiplication is equivalent in the positive baseline domain and guarantees no zero-denominator evaluation.
- Expected power is a host-derived point, not a universal curve. The point contract requires model inputs, known-good fit period, electrical boundary, and in-domain readiness. The graph cannot certify those obligations.
- All three defaults need commissioning. The 0.5 kW floor has NO_PORTABLE_DEFAULT; the 15% residual and 1800 s duration are adopted tunables, not source-transcribed thresholds.
- Only positive residual is in scope. Low power may indicate successful reset, broken coupling, bad metering, or another delivery fault; conflating the directions would erase diagnosis.
- VFD relationships stay informational in the library metadata. Whether a same-drive VFD-0001/0005 invalidates this model depends on its inputs and operating state; unconditional ID-level suppression could silence every pump or a baseline that does not use the disputed signal.
- No simulation validation is claimed. The plant harness now preserves per-pump power, flow, and status proxies, but it has no frozen expected-power model trained on a disjoint known-good period. Synthetic vectors validate graph behavior only.
Test Vectors
23 scenarios, clock step 60 s over 3600 s.
| Scenario | Description |
|---|---|
baseline_zero_not_evaluable | A zero expected-power baseline is not evaluable; no divide exists and both diagnostic/fault outputs stay false. |
baseline_negative_not_evaluable | A negative expected value remains numerically safe and not evaluable; cross-multiplication creates no Inf or NaN path. |
baseline_just_below_floor | The denominator guard is strict below its configured floor. |
baseline_exact_floor | Exactly minimum_expected_kw remains not evaluable under the strict comparator. |
baseline_just_above_floor | A small but valid baseline makes evaluability true and can support the full delayed verdict. |
actual_equals_expected | Measured power on the baseline has zero positive residual. |
exact_positive_residual_threshold | Exactly 15 percent excess is clear because the comparison is strict. |
just_above_positive_residual | One test increment above 15 percent asserts yPowerHigh immediately and yFault after 30 minutes. |
negative_residual_not_this_rule | Actual power below expected is deliberately silent; delivery/proof faults own that direction. |
stopped_pump_exposes_diagnostic_only | Power residual direction is diagnostic detail independent of status, while yFault remains status-gated. |
transient_excess_power | A 20-minute power excursion clears before the 30-minute persistence window. |
violation_ends_at_delay_boundary | The residual clears exactly at 1800 seconds before timer evaluation and never alarms. |
sustained_excess_and_recovery | A mature excess-power alarm clears immediately when actual power returns to baseline. |
baseline_change_resets_timer | A valid baseline change that brings the residual inside allowance resets persistence; a later excess serves a new full window. |
invalid_baseline_resets_timer | Expected power falling to zero clears evaluability and diagnostic state; a later valid baseline must serve a new full persistence interval. |
baseline_changes_but_candidate_stays_true | A model update within the still-faulting region does not reset the timer because the Boolean candidate never falls. |
pump_start_begins_timer | A standing residual does not accumulate while stopped; persistence starts only after proven run status rises. |
pump_status_drop_resets_timer | A stopped interval resets persistence even while the baseline residual remains high; restart requires a new full duration. |
two_short_excess_episodes | Two sub-duration excess-power episodes separated by a compliant interval do not accumulate. |
residual_threshold_chatter | Measured power alternates just below and above the 15 percent boundary, so yPowerHigh chatters but persistence never matures. |
degraded_fit_learns_fault_as_normal | Actual and expected are both high because the fit learned degraded operation; the residual is zero and the graph stays silent, pinning the known-good training precondition. |
invalid_stale_baseline_raw_behavior | A stale but positive expected-power value looks valid to the graph and can alarm; model freshness/in-domain status is a mandatory host NO_EVAL gate. |
vfd_relationship_is_host_side | The raw pump baseline alarm remains active with sustained excess; a deployment may suppress only when a same-drive VFD finding actually invalidates this model. |
vectors.json
{
"schema": "cxf-library/vectors/v1",
"clock": {
"step_s": 60,
"horizon_s": 3600
},
"scenarios": [
{
"name": "baseline_zero_not_evaluable",
"description": "A zero expected-power baseline is not evaluable; no divide exists and both diagnostic/fault outputs stay false.",
"inputs": {
"pump_status": true,
"pump_kw": 10.0,
"pump_kw_expected": 0.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "baseline_negative_not_evaluable",
"description": "A negative expected value remains numerically safe and not evaluable; cross-multiplication creates no Inf or NaN path.",
"inputs": {
"pump_status": true,
"pump_kw": 10.0,
"pump_kw_expected": -1.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "baseline_just_below_floor",
"description": "The denominator guard is strict below its configured floor.",
"inputs": {
"pump_status": true,
"pump_kw": 2.0,
"pump_kw_expected": 0.49
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "baseline_exact_floor",
"description": "Exactly minimum_expected_kw remains not evaluable under the strict comparator.",
"inputs": {
"pump_status": true,
"pump_kw": 2.0,
"pump_kw_expected": 0.5
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "baseline_just_above_floor",
"description": "A small but valid baseline makes evaluability true and can support the full delayed verdict.",
"inputs": {
"pump_status": true,
"pump_kw": 0.7,
"pump_kw_expected": 0.51
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1740,
"equals": false
},
{
"output": "yFault",
"from_s": 1860,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "actual_equals_expected",
"description": "Measured power on the baseline has zero positive residual.",
"inputs": {
"pump_status": true,
"pump_kw": 10.0,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "exact_positive_residual_threshold",
"description": "Exactly 15 percent excess is clear because the comparison is strict.",
"inputs": {
"pump_status": true,
"pump_kw": 11.5,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "just_above_positive_residual",
"description": "One test increment above 15 percent asserts yPowerHigh immediately and yFault after 30 minutes.",
"inputs": {
"pump_status": true,
"pump_kw": 11.51,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1740,
"equals": false
},
{
"output": "yFault",
"from_s": 1860,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "negative_residual_not_this_rule",
"description": "Actual power below expected is deliberately silent; delivery/proof faults own that direction.",
"inputs": {
"pump_status": true,
"pump_kw": 8.0,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "stopped_pump_exposes_diagnostic_only",
"description": "Power residual direction is diagnostic detail independent of status, while yFault remains status-gated.",
"inputs": {
"pump_status": false,
"pump_kw": 12.0,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "transient_excess_power",
"description": "A 20-minute power excursion clears before the 30-minute persistence window.",
"inputs": {
"pump_status": true,
"pump_kw": [
{
"t": 0,
"value": 12.0
},
{
"t": 1200,
"value": 10.0
}
],
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 1140,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 1260,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "violation_ends_at_delay_boundary",
"description": "The residual clears exactly at 1800 seconds before timer evaluation and never alarms.",
"inputs": {
"pump_status": true,
"pump_kw": [
{
"t": 0,
"value": 12.0
},
{
"t": 1800,
"value": 10.0
}
],
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "sustained_excess_and_recovery",
"description": "A mature excess-power alarm clears immediately when actual power returns to baseline.",
"inputs": {
"pump_status": true,
"pump_kw": [
{
"t": 0,
"value": 12.0
},
{
"t": 2700,
"value": 10.0
}
],
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 1740,
"equals": false
},
{
"output": "yFault",
"from_s": 1860,
"to_s": 2640,
"equals": true
},
{
"output": "yFault",
"from_s": 2760,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "baseline_change_resets_timer",
"description": "A valid baseline change that brings the residual inside allowance resets persistence; a later excess serves a new full window.",
"inputs": {
"pump_status": true,
"pump_kw": 12.0,
"pump_kw_expected": [
{
"t": 0,
"value": 10.0
},
{
"t": 1200,
"value": 11.0
},
{
"t": 1500,
"value": 10.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 3240,
"equals": false
},
{
"output": "yFault",
"from_s": 3360,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "invalid_baseline_resets_timer",
"description": "Expected power falling to zero clears evaluability and diagnostic state; a later valid baseline must serve a new full persistence interval.",
"inputs": {
"pump_status": true,
"pump_kw": 12.0,
"pump_kw_expected": [
{
"t": 0,
"value": 10.0
},
{
"t": 1200,
"value": 0.0
},
{
"t": 1500,
"value": 10.0
}
]
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 1260,
"to_s": 1440,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3240,
"equals": false
},
{
"output": "yFault",
"from_s": 3360,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "baseline_changes_but_candidate_stays_true",
"description": "A model update within the still-faulting region does not reset the timer because the Boolean candidate never falls.",
"inputs": {
"pump_status": true,
"pump_kw": 13.0,
"pump_kw_expected": [
{
"t": 0,
"value": 10.0
},
{
"t": 900,
"value": 10.5
}
]
},
"expect": [
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 1740,
"equals": false
},
{
"output": "yFault",
"from_s": 1860,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "pump_start_begins_timer",
"description": "A standing residual does not accumulate while stopped; persistence starts only after proven run status rises.",
"inputs": {
"pump_status": [
{
"t": 0,
"value": false
},
{
"t": 600,
"value": true
}
],
"pump_kw": 12.0,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 2340,
"equals": false
},
{
"output": "yFault",
"from_s": 2460,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "pump_status_drop_resets_timer",
"description": "A stopped interval resets persistence even while the baseline residual remains high; restart requires a new full duration.",
"inputs": {
"pump_status": [
{
"t": 0,
"value": true
},
{
"t": 1200,
"value": false
},
{
"t": 1500,
"value": true
}
],
"pump_kw": 12.0,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3240,
"equals": false
},
{
"output": "yFault",
"from_s": 3360,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "two_short_excess_episodes",
"description": "Two sub-duration excess-power episodes separated by a compliant interval do not accumulate.",
"inputs": {
"pump_status": true,
"pump_kw": [
{
"t": 0,
"value": 12.0
},
{
"t": 900,
"value": 10.0
},
{
"t": 1200,
"value": 12.0
},
{
"t": 2400,
"value": 10.0
}
],
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "residual_threshold_chatter",
"description": "Measured power alternates just below and above the 15 percent boundary, so yPowerHigh chatters but persistence never matures.",
"inputs": {
"pump_status": true,
"pump_kw": [
{
"t": 0,
"value": 11.49
},
{
"t": 60,
"value": 11.51
},
{
"t": 120,
"value": 11.49
},
{
"t": 180,
"value": 11.51
},
{
"t": 240,
"value": 11.49
},
{
"t": 300,
"value": 11.51
},
{
"t": 360,
"value": 11.49
},
{
"t": 420,
"value": 11.51
},
{
"t": 480,
"value": 11.49
}
],
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "degraded_fit_learns_fault_as_normal",
"description": "Actual and expected are both high because the fit learned degraded operation; the residual is zero and the graph stays silent, pinning the known-good training precondition.",
"inputs": {
"pump_status": true,
"pump_kw": 12.0,
"pump_kw_expected": 12.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 3600,
"equals": false
}
]
},
{
"name": "invalid_stale_baseline_raw_behavior",
"description": "A stale but positive expected-power value looks valid to the graph and can alarm; model freshness/in-domain status is a mandatory host NO_EVAL gate.",
"inputs": {
"pump_status": true,
"pump_kw": 12.0,
"pump_kw_expected": 8.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 1860,
"to_s": 3600,
"equals": true
}
]
},
{
"name": "vfd_relationship_is_host_side",
"description": "The raw pump baseline alarm remains active with sustained excess; a deployment may suppress only when a same-drive VFD finding actually invalidates this model.",
"inputs": {
"pump_status": true,
"pump_kw": 12.0,
"pump_kw_expected": 10.0
},
"expect": [
{
"output": "yBaselineOk",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yPowerHigh",
"from_s": 0,
"to_s": 3600,
"equals": true
},
{
"output": "yFault",
"from_s": 1860,
"to_s": 3600,
"equals": true
}
]
}
]
}