Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

VFD-0003 — At maximum speed with load unsatisfied

Statusverified — engine e2ff2f8, cxf:fnv1a128:172305490314de625490108d7740b3b6, 2026-08-20
Severity3
Methodrule
Phase2
CategoryCOMFORT_ENERGY
ConfidenceMEDIUM
EstimationQUALITATIVE_ONLY
G36
Clusters
Suppresses
Suppressed byVFD-0001, VFD-0005
RelatedVFD-0002, VFD-0004
Playbooksvfd-pump-faults
SourcePNNL Variable Speed Drives O&M Best Practice — output frequency/speed and the controlled process variable are core drive operating evidence; manufacturer and application limits remain site-specific; NIST, Automatically Detecting Faulty Regulation in HVAC Controls (2013) — generic failure-to-regulate and actuator-limit fault classes; Library-authored high-limit complement to VFD-0002, reusing its assembled speed boundary, symmetric process-error test, and 900 s persistence convention
Operating statesdrive enabled, under remote automatic process control, and not intentionally capacity-limited

Preconditions (host-enforced): The final drive enable and active-loop states are host gates; a stopped drive is NO_EVAL, not healthy. The drive must be accepting remote automatic control, with approved hand/bypass, commissioning, smoke/purge, lead/lag transition, and functional-test intervals excluded. VFD-0005 suppresses this rule while automatic control is absent. vfd_speed must be trustworthy normalized feedback; VFD-0001 suppresses this rule during command/feedback tracking failure. The host must exclude intentional torque, current, safety, demand, or equipment-capacity limits. vfd_process_value and vfd_process_sp must be the same loop in the same units, the setpoint must be settled for that loop’s time constant, and pv_error_threshold must be configured in those units. When any gate is unmet the verdict is NO_EVAL, not healthy.

Points: vfd_speed, vfd_process_value, vfd_process_sp

Outputs:

  • yFault — True while speed is strictly above max_speed minus speed_tolerance and either directional process error has persisted for sustained_duration
  • yProcessHigh — Immediate raw diagnostic; true when process value is more than pv_error_threshold above setpoint, whether or not speed is at maximum
  • yProcessLow — Immediate raw diagnostic; true when process value is more than pv_error_threshold below setpoint, whether or not speed is at maximum

Parameters:

NameDefaultUnitCXF pathDescription
max_speed100.0%maxSpd.kADOPTED_TUNABLE: normalized maximum-speed reference. Configure to the drive/application limit when it is not 100%.
speed_tolerance3.0%tol.kLIBRARY_PRECEDENT: margin below maximum that still counts as pinned at the high limit; inherited from VFD-0002’s assembled limit shape.
pv_error_threshold10.01processHigh.t, processLow.tNO_PORTABLE_DEFAULT: absolute directional error in the bound loop’s own units. The executable 10.0 is a placeholder; deployment is blocked until it is set for that loop.
sustained_duration900.0spersist.delayTimeLIBRARY_PRECEDENT: continuous high-limit and process-error conjunction required before alarm (15 min), reused from VFD-0002.

Description

A process loop has spent its last increment of actuator authority: the drive is near its configured maximum, yet the quantity it regulates remains materially away from setpoint. That signature does not identify one component. It can mean an obstructed air or water path, undersized equipment, a drive held below its reported limit by current or torque protection, a bad process sensor, an incorrect control direction, or a maximum that was commissioned too low. What it does identify is the useful decision boundary: more ordinary loop output is no longer available, so waiting for the controller to recover is not a fix.

Detection Logic

speed_ceiling = max_speed - speed_tolerance
at_max        = vfd_speed > speed_ceiling
process_high  = vfd_process_value - vfd_process_sp > pv_error_threshold
process_low   = vfd_process_sp - vfd_process_value > pv_error_threshold

yProcessHigh = process_high
yProcessLow  = process_low
yFault       = TrueDelay(at_max AND (process_high OR process_low),
                         sustained_duration)

Block graph (rule.cxf.jsonld):

VFD-0003 block graph

The high and low branches are separate rather than an absolute-value block so the graph can expose the error direction without pretending to know the loop’s control sign. They are mutually exclusive at a positive threshold and remain raw: either may be true while the drive is at mid-speed. Only their OR is gated by atMax and delayed. Any release of the speed or process branch clears the fault and resets the complete 900-second timer.

Possible Diagnoses

  1. Air- or water-side obstruction: dirty filter/coil, closed damper or valve, blocked strainer, or restricted duct/pipe.
  2. Equipment or distribution system undersized for the present load.
  3. Drive current, torque, safety, demand, or application limit below the normalized maximum assumed by the BAS.
  4. Process sensor bias, setpoint/unit mismatch, or reversed control action.
  5. Mechanical degradation in the driven equipment, belt, coupling, or impeller.
  6. Maximum-speed parameter or reset sequence commissioned incorrectly.

Energy Impact

COMFORT_ENERGY, MEDIUM confidence, QUALITATIVE_ONLY. A saturated drive can spend long periods near its largest electrical draw without meeting demand, but the three inputs cannot distinguish useful peak-load operation from wasted work behind an obstruction. Use the playbook to establish the cause before applying fan/pump laws or claiming savings.

Emissions Impact

Scope 2, qualitative. VFD-driven equipment is electric; any avoidable high-speed runtime becomes purchased-electricity emissions. No avoided-emissions number is defensible without measured power and a diagnosed correction.

Deviations

  • This is a library-authored high-limit complement, not a transcribed PNNL or NIST equation. Those sources support monitoring drive output and regulated process behavior; the exact 100%, 3-point, 10-unit, and 900-second choices are classified above rather than presented as published universal limits.
  • pv_error_threshold has no portable default. The shipped 10.0 exists so the CXF graph is executable. A deployment that has not replaced it in the same units as the bound PV/setpoint is invalid.
  • Both comparisons are strict. Exactly 97% is clear at the defaults and exactly +/-10 loop units leaves both direction flags false. The vectors pin every boundary from both sides.
  • Direction does not imply underdelivery. yProcessHigh can be the bad side of one loop and the desired response of another. It is a diagnostic sign, not a cause label.
  • Automatic mode is suppressed explicitly. VFD-0005 suppresses this rule because a local/hand or bypassed drive invalidates the automatic-loop saturation inference; VFD-0001 does the same when feedback cannot establish where the drive actually is. Suppression must be instance-scoped to the same physical drive.
  • The graph has no enable input. A stopped drive produces a raw process direction but cannot satisfy at_max; the host still reports NO_EVAL because the operating premise is absent.
  • persist.delayOnInit = true, so a violation present at engine start waits the full 15 minutes.
  • No simulation validation is claimed. The current harness has no actual drive feedback paired with that same loop’s PV and setpoint; joining the pump-flow proxy to boiler temperature would be cross-loop fabrication.

Notes

Read the sign before visiting the field, then read the loop definition. A high PV at maximum speed may be correct action for a reverse-acting loop, evidence of overdelivery, or proof that the drive limit is unrelated to the bound process. The generic rule deliberately stops at that boundary. Resolve VFD-0005 and VFD-0001 first, verify loop units and direction, then check configured drive limits and the mechanical path.

Test Vectors

20 scenarios, clock step 60 s over 3600 s.

ScenarioDescription
healthy_mid_speed_satisfiedAt 60% speed with PV one loop unit above setpoint, neither saturation nor process-error direction is active.
maximum_speed_satisfiedThe drive can sit at 100% without fault when the controlled process is within its configured error band.
maximum_speed_process_highAt 100% speed with PV 15 loop units above setpoint, the high diagnostic is immediate and the combined fault waits 15 minutes.
maximum_speed_process_lowThe symmetric direction: PV 15 loop units below setpoint activates only yProcessLow and reaches the same persisted fault.
speed_exactly_at_ceilingmax_speed - speed_tolerance is 97%; the graph uses strict greater-than, so exactly 97% is not at maximum even though the raw high-error diagnostic is true.
speed_just_beyond_ceilingAt 97.1% the speed term passes; a 10.1-unit PV miss brackets both assembled strict boundaries from the faulting side.
pv_error_exactly_at_threshold_highA positive error of exactly 10.0 loop units is not high under the strict comparator, even at 100% speed.
pv_error_exactly_at_threshold_lowThe negative-error boundary is symmetric: PV exactly 10.0 units below setpoint does not activate yProcessLow.
violation_shorter_than_durationA 14-minute high-process episode ends one sample before the 15-minute persistence can mature and never alarms.
speed_release_resets_timerSix hundred seconds at maximum, one sample below the ceiling, then a fresh violation proves the conjunction timer resets rather than accumulating disjoint episodes.
process_recovery_resets_timerThe same reset through the process branch: a one-sample return inside the allowance band discards elapsed persistence before the miss returns.
initialization_violation_waitsA violation present at engine initialization waits the full delay because persist.delayOnInit is true.
stopped_drive_raw_behavior_host_gatedWith the drive stopped, the raw process-low diagnostic remains true but the high-speed term is false. The required enabled/auto/active-loop exclusion is host-side.
mid_speed_raw_direction_switchAt 50% speed the fault remains false while the raw process diagnostic switches from high to low at t=1800 s, proving the secondary outputs are not saturation-gated.
both_just_beyond_low_boundariesThe low-error mirror brackets both strict thresholds from the active side: speed 97.1% and PV 10.1 units below setpoint.
violation_ends_at_delay_boundaryThe candidate releases at the staged 900-second boundary before that tick is evaluated. No assertion is made on the boundary tick, and no fault appears.
two_short_opposite_direction_episodesA short high-error episode and a separate low-error episode are divided by recovery. Direction changes do not let the shared persistence timer accumulate disjoint intervals.
recovery_after_alarmA matured high-direction fault clears immediately when PV returns to setpoint at t=1800 s.
speed_threshold_chatter_resets_delaySpeed alternates on opposite sides of the 97% ceiling every minute while PV remains high. The candidate repeatedly resets and never survives 900 seconds.
process_threshold_chatter_resets_delayPV alternates just inside and outside the high-error threshold every minute at maximum speed; the strict candidate never persists long enough to fault.
vectors.json
{
  "schema": "cxf-library/vectors/v1",
  "clock": {
    "step_s": 60,
    "horizon_s": 3600
  },
  "scenarios": [
    {
      "name": "healthy_mid_speed_satisfied",
      "description": "At 60% speed with PV one loop unit above setpoint, neither saturation nor process-error direction is active.",
      "inputs": {
        "vfd_speed": 60.0,
        "vfd_process_value": 101.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "maximum_speed_satisfied",
      "description": "The drive can sit at 100% without fault when the controlled process is within its configured error band.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": 105.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "maximum_speed_process_high",
      "description": "At 100% speed with PV 15 loop units above setpoint, the high diagnostic is immediate and the combined fault waits 15 minutes.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": 115.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "maximum_speed_process_low",
      "description": "The symmetric direction: PV 15 loop units below setpoint activates only yProcessLow and reaches the same persisted fault.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": 85.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "speed_exactly_at_ceiling",
      "description": "max_speed - speed_tolerance is 97%; the graph uses strict greater-than, so exactly 97% is not at maximum even though the raw high-error diagnostic is true.",
      "inputs": {
        "vfd_speed": 97.0,
        "vfd_process_value": 115.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "speed_just_beyond_ceiling",
      "description": "At 97.1% the speed term passes; a 10.1-unit PV miss brackets both assembled strict boundaries from the faulting side.",
      "inputs": {
        "vfd_speed": 97.1,
        "vfd_process_value": 110.1,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "pv_error_exactly_at_threshold_high",
      "description": "A positive error of exactly 10.0 loop units is not high under the strict comparator, even at 100% speed.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": 110.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "pv_error_exactly_at_threshold_low",
      "description": "The negative-error boundary is symmetric: PV exactly 10.0 units below setpoint does not activate yProcessLow.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": 90.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "violation_shorter_than_duration",
      "description": "A 14-minute high-process episode ends one sample before the 15-minute persistence can mature and never alarms.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": [
          {
            "t": 0,
            "value": 115.0
          },
          {
            "t": 840,
            "value": 100.0
          }
        ],
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "speed_release_resets_timer",
      "description": "Six hundred seconds at maximum, one sample below the ceiling, then a fresh violation proves the conjunction timer resets rather than accumulating disjoint episodes.",
      "inputs": {
        "vfd_speed": [
          {
            "t": 0,
            "value": 100.0
          },
          {
            "t": 600,
            "value": 90.0
          },
          {
            "t": 660,
            "value": 100.0
          }
        ],
        "vfd_process_value": 115.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 1620,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "process_recovery_resets_timer",
      "description": "The same reset through the process branch: a one-sample return inside the allowance band discards elapsed persistence before the miss returns.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": [
          {
            "t": 0,
            "value": 85.0
          },
          {
            "t": 600,
            "value": 100.0
          },
          {
            "t": 660,
            "value": 85.0
          }
        ],
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 1620,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "initialization_violation_waits",
      "description": "A violation present at engine initialization waits the full delay because persist.delayOnInit is true.",
      "inputs": {
        "vfd_speed": 98.0,
        "vfd_process_value": 80.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "stopped_drive_raw_behavior_host_gated",
      "description": "With the drive stopped, the raw process-low diagnostic remains true but the high-speed term is false. The required enabled/auto/active-loop exclusion is host-side.",
      "inputs": {
        "vfd_speed": 0.0,
        "vfd_process_value": 80.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "mid_speed_raw_direction_switch",
      "description": "At 50% speed the fault remains false while the raw process diagnostic switches from high to low at t=1800 s, proving the secondary outputs are not saturation-gated.",
      "inputs": {
        "vfd_speed": 50.0,
        "vfd_process_value": [
          {
            "t": 0,
            "value": 115.0
          },
          {
            "t": 1800,
            "value": 85.0
          }
        ],
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 1740,
          "equals": true
        },
        {
          "output": "yProcessHigh",
          "from_s": 1860,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 1740,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 1860,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "both_just_beyond_low_boundaries",
      "description": "The low-error mirror brackets both strict thresholds from the active side: speed 97.1% and PV 10.1 units below setpoint.",
      "inputs": {
        "vfd_speed": 97.1,
        "vfd_process_value": 89.9,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yProcessHigh",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yProcessLow",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "violation_ends_at_delay_boundary",
      "description": "The candidate releases at the staged 900-second boundary before that tick is evaluated. No assertion is made on the boundary tick, and no fault appears.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": [
          {
            "t": 0,
            "value": 115.0
          },
          {
            "t": 900,
            "value": 100.0
          }
        ],
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "two_short_opposite_direction_episodes",
      "description": "A short high-error episode and a separate low-error episode are divided by recovery. Direction changes do not let the shared persistence timer accumulate disjoint intervals.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": [
          {
            "t": 0,
            "value": 115.0
          },
          {
            "t": 600,
            "value": 100.0
          },
          {
            "t": 720,
            "value": 85.0
          },
          {
            "t": 1320,
            "value": 100.0
          }
        ],
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "recovery_after_alarm",
      "description": "A matured high-direction fault clears immediately when PV returns to setpoint at t=1800 s.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": [
          {
            "t": 0,
            "value": 115.0
          },
          {
            "t": 1800,
            "value": 100.0
          }
        ],
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1740,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 1860,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "speed_threshold_chatter_resets_delay",
      "description": "Speed alternates on opposite sides of the 97% ceiling every minute while PV remains high. The candidate repeatedly resets and never survives 900 seconds.",
      "inputs": {
        "vfd_speed": [
          {
            "t": 0,
            "value": 97.1
          },
          {
            "t": 60,
            "value": 96.9
          },
          {
            "t": 120,
            "value": 97.1
          },
          {
            "t": 180,
            "value": 96.9
          },
          {
            "t": 240,
            "value": 97.1
          },
          {
            "t": 300,
            "value": 96.9
          },
          {
            "t": 360,
            "value": 97.1
          },
          {
            "t": 420,
            "value": 96.9
          },
          {
            "t": 480,
            "value": 97.1
          },
          {
            "t": 540,
            "value": 96.9
          },
          {
            "t": 600,
            "value": 97.1
          },
          {
            "t": 660,
            "value": 96.9
          },
          {
            "t": 720,
            "value": 97.1
          },
          {
            "t": 780,
            "value": 96.9
          },
          {
            "t": 840,
            "value": 97.1
          },
          {
            "t": 900,
            "value": 96.9
          },
          {
            "t": 960,
            "value": 97.1
          },
          {
            "t": 1020,
            "value": 96.9
          },
          {
            "t": 1080,
            "value": 97.1
          },
          {
            "t": 1140,
            "value": 96.9
          },
          {
            "t": 1200,
            "value": 97.1
          },
          {
            "t": 1260,
            "value": 96.9
          },
          {
            "t": 1320,
            "value": 97.1
          },
          {
            "t": 1380,
            "value": 96.9
          },
          {
            "t": 1440,
            "value": 97.1
          },
          {
            "t": 1500,
            "value": 96.9
          },
          {
            "t": 1560,
            "value": 97.1
          },
          {
            "t": 1620,
            "value": 96.9
          },
          {
            "t": 1680,
            "value": 97.1
          },
          {
            "t": 1740,
            "value": 96.9
          },
          {
            "t": 1800,
            "value": 97.1
          },
          {
            "t": 1860,
            "value": 96.9
          },
          {
            "t": 1920,
            "value": 97.1
          },
          {
            "t": 1980,
            "value": 96.9
          },
          {
            "t": 2040,
            "value": 97.1
          },
          {
            "t": 2100,
            "value": 96.9
          },
          {
            "t": 2160,
            "value": 97.1
          },
          {
            "t": 2220,
            "value": 96.9
          },
          {
            "t": 2280,
            "value": 97.1
          },
          {
            "t": 2340,
            "value": 96.9
          },
          {
            "t": 2400,
            "value": 97.1
          },
          {
            "t": 2460,
            "value": 96.9
          },
          {
            "t": 2520,
            "value": 97.1
          },
          {
            "t": 2580,
            "value": 96.9
          },
          {
            "t": 2640,
            "value": 97.1
          },
          {
            "t": 2700,
            "value": 96.9
          },
          {
            "t": 2760,
            "value": 97.1
          },
          {
            "t": 2820,
            "value": 96.9
          },
          {
            "t": 2880,
            "value": 97.1
          },
          {
            "t": 2940,
            "value": 96.9
          },
          {
            "t": 3000,
            "value": 97.1
          },
          {
            "t": 3060,
            "value": 96.9
          },
          {
            "t": 3120,
            "value": 97.1
          },
          {
            "t": 3180,
            "value": 96.9
          },
          {
            "t": 3240,
            "value": 97.1
          },
          {
            "t": 3300,
            "value": 96.9
          },
          {
            "t": 3360,
            "value": 97.1
          },
          {
            "t": 3420,
            "value": 96.9
          },
          {
            "t": 3480,
            "value": 97.1
          },
          {
            "t": 3540,
            "value": 96.9
          }
        ],
        "vfd_process_value": 115.0,
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "process_threshold_chatter_resets_delay",
      "description": "PV alternates just inside and outside the high-error threshold every minute at maximum speed; the strict candidate never persists long enough to fault.",
      "inputs": {
        "vfd_speed": 100.0,
        "vfd_process_value": [
          {
            "t": 0,
            "value": 110.1
          },
          {
            "t": 60,
            "value": 109.9
          },
          {
            "t": 120,
            "value": 110.1
          },
          {
            "t": 180,
            "value": 109.9
          },
          {
            "t": 240,
            "value": 110.1
          },
          {
            "t": 300,
            "value": 109.9
          },
          {
            "t": 360,
            "value": 110.1
          },
          {
            "t": 420,
            "value": 109.9
          },
          {
            "t": 480,
            "value": 110.1
          },
          {
            "t": 540,
            "value": 109.9
          },
          {
            "t": 600,
            "value": 110.1
          },
          {
            "t": 660,
            "value": 109.9
          },
          {
            "t": 720,
            "value": 110.1
          },
          {
            "t": 780,
            "value": 109.9
          },
          {
            "t": 840,
            "value": 110.1
          },
          {
            "t": 900,
            "value": 109.9
          },
          {
            "t": 960,
            "value": 110.1
          },
          {
            "t": 1020,
            "value": 109.9
          },
          {
            "t": 1080,
            "value": 110.1
          },
          {
            "t": 1140,
            "value": 109.9
          },
          {
            "t": 1200,
            "value": 110.1
          },
          {
            "t": 1260,
            "value": 109.9
          },
          {
            "t": 1320,
            "value": 110.1
          },
          {
            "t": 1380,
            "value": 109.9
          },
          {
            "t": 1440,
            "value": 110.1
          },
          {
            "t": 1500,
            "value": 109.9
          },
          {
            "t": 1560,
            "value": 110.1
          },
          {
            "t": 1620,
            "value": 109.9
          },
          {
            "t": 1680,
            "value": 110.1
          },
          {
            "t": 1740,
            "value": 109.9
          },
          {
            "t": 1800,
            "value": 110.1
          },
          {
            "t": 1860,
            "value": 109.9
          },
          {
            "t": 1920,
            "value": 110.1
          },
          {
            "t": 1980,
            "value": 109.9
          },
          {
            "t": 2040,
            "value": 110.1
          },
          {
            "t": 2100,
            "value": 109.9
          },
          {
            "t": 2160,
            "value": 110.1
          },
          {
            "t": 2220,
            "value": 109.9
          },
          {
            "t": 2280,
            "value": 110.1
          },
          {
            "t": 2340,
            "value": 109.9
          },
          {
            "t": 2400,
            "value": 110.1
          },
          {
            "t": 2460,
            "value": 109.9
          },
          {
            "t": 2520,
            "value": 110.1
          },
          {
            "t": 2580,
            "value": 109.9
          },
          {
            "t": 2640,
            "value": 110.1
          },
          {
            "t": 2700,
            "value": 109.9
          },
          {
            "t": 2760,
            "value": 110.1
          },
          {
            "t": 2820,
            "value": 109.9
          },
          {
            "t": 2880,
            "value": 110.1
          },
          {
            "t": 2940,
            "value": 109.9
          },
          {
            "t": 3000,
            "value": 110.1
          },
          {
            "t": 3060,
            "value": 109.9
          },
          {
            "t": 3120,
            "value": 110.1
          },
          {
            "t": 3180,
            "value": 109.9
          },
          {
            "t": 3240,
            "value": 110.1
          },
          {
            "t": 3300,
            "value": 109.9
          },
          {
            "t": 3360,
            "value": 110.1
          },
          {
            "t": 3420,
            "value": 109.9
          },
          {
            "t": 3480,
            "value": 110.1
          },
          {
            "t": 3540,
            "value": 109.9
          }
        ],
        "vfd_process_sp": 100.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    }
  ]
}