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

HW-0012 — Excess boiler stages at low plant load

Statusverified — engine e2ff2f8, cxf:fnv1a128:5f5748540da32f7ac07fe2d05e6c46cb, 2026-08-20
Severity3
Methodrule
Phase2
CategoryEXCESS_CONSUMPTION
ConfidenceMEDIUM
EstimationQUALITATIVE_ONLY
G36
Clusters
Suppresses
Suppressed by
RelatedHW-0001, HW-0002, HW-0011
Playbookshot-water-plant-faults
SourceLBNL Simulated Boiler Plant dataset inventory, PDF pp.4-8 — two identical parallel boilers, staged heat-load control, status channels, and useful secondary-loop power; it does not publish the shipped generic thresholds or an over-staging fault; PNNL Heating Plant Equipment Modeling Inputs — boiler type, sizing, efficiency, and stage-up part-load ratios are plant-specific inputs, supporting an adoption-blocking staging map rather than a portable threshold; Library precedent RTU-0001/TOWER-0003 — verified native integer stage/count comparison and initialization-safe continuous persistence
Operating statesnormal automatic boiler staging after fleet, availability, load basis, minimum-time, and stage-transition state have settled

Preconditions (host-enforced): boiler_stage_count must count proven firing staging units in one configured eligible fleet, not enabled or available units. A unit may be a whole boiler or a comparable modular burner section, but unlike units must not be mixed in one scalar count. hw_plant_load_fraction must be useful plant heating load divided by one commissioned eligible-fleet capacity basis, remain in 0..1, and keep that denominator stable through the evaluation window. If fleet membership or capacity basis changes, restart evaluation or report NO_EVAL. The threshold and allowed count require site commissioning against sizes, turndown, minimum flow, venting, emissions, redundancy, and minimum on/off policy; the shipped numbers are adoption-blocking placeholders. Exclude warm-up, freeze protection, emergency redundancy, exercise, maintenance, rotation, recent starts/stops, stage overlap, and intentional safety/application limits. OAT alone is not a valid load proxy. Freshness, alignment, finite values, and derivation provenance remain host obligations.

Points: boiler_stage_count, hw_plant_load_fraction

Outputs:

  • yFault — True after a valid low-load plant operates above the commissioned allowed firing-stage count continuously for sustained_duration
  • yLoadOk — Evaluability output; true only for an inclusive 0..1 load fraction. False means NO_EVAL and every other output is uninterpretable
  • yLowLoad — Raw diagnostic; true when load fraction is strictly below low_load_fraction, including for invalid negative values, so consult yLoadOk first
  • yExcessStages — Raw diagnostic; true when firing stage count is strictly above max_stages_at_low_load

Parameters:

NameDefaultUnitCXF pathDescription
low_load_fraction0.351lowLoad.tNO_PORTABLE_DEFAULT: executable adoption-blocking placeholder. Replace from the commissioned staging map, boiler sizes, turndown, minimum-flow, and emissions constraints before deployment.
max_stages_at_low_load11excessStages.tNO_PORTABLE_DEFAULT: executable adoption-blocking placeholder. Replace with the allowed comparable firing-unit count for the configured fleet and load region.
sustained_duration900.0spersist.delayTimeADOPTED_TUNABLE continuous over-staging proof. Set beyond ordinary stage overlap, rotation, and minimum-on/off transitions.

Description

This rule identifies a configured boiler fleet keeping more firing units on than its commissioned staging map permits at low useful load. The point is not that two boilers are universally wrong: unequal machines, modular burners, minimum-flow requirements, redundancy, emissions, and minimum run time can make two units correct. Those facts define the adoption contract and are why both shipped decision thresholds deliberately block portable deployment.

Detection Logic

negative_load = hw_plant_load_fraction < 0
above_one     = hw_plant_load_fraction > 1
yLoadOk       = NOT negative_load AND NOT above_one

yLowLoad      = hw_plant_load_fraction < low_load_fraction
yExcessStages = boiler_stage_count > max_stages_at_low_load

yFault = TrueDelay(yLoadOk AND yLowLoad AND yExcessStages,
                   sustained_duration)

Block graph (rule.cxf.jsonld):

HW-0012 block graph

The load-validity interval includes exactly 0 and 1. Exact load 0.35 and exact stage count 1 are clear because both decision comparisons are strict. The graph uses CDL.Integers.GreaterThreshold directly. delayOnInit=true requires the full interval on evaluator startup and any false conjunct resets the timer.

Read yLoadOk first. A negative load makes raw yLowLoad=true, but it gates yFault off and means NO_EVAL; non-finite/freshness checks remain host-side.

Possible Diagnoses

  1. Stage-down threshold, timer, or minimum-run logic set too conservatively.
  2. Lead/lag sequence leaving a second boiler latched after load falls.
  3. Enabled/available units mistakenly counted as proven firing units.
  4. Load numerator, commissioned capacity, or fleet membership derived wrong.
  5. Boiler sizes/turndown make the adopted scalar stage rule invalid.
  6. Redundancy, exercise, freeze, emissions, or minimum-flow mode not excluded.

Energy Impact

At the same useful load, excess firing machines can add jacket and standby loss, operate each burner below its efficient modulation region, and add purge or light-off cycles. Magnitude depends on equipment and sequence. Without measured fuel and a commissioned alternative staging model, the result remains qualitative and no generic savings percentage is claimed.

Emissions Impact

Any scope-1 impact follows the site-specific fuel penalty of the actual staging sequence. This rule has neither a fuel measurement nor a counterfactual staging model, so it does not assign an emissions quantity.

Deviations

  • The brief classed 0.35 and one stage as adopted tunables. They are reclassified NO_PORTABLE_DEFAULT: source material does not establish them, and boiler sizing, turndown, topology, and policy change the correct values materially.
  • The load denominator is one stable commissioned eligible-fleet capacity, not ambiguously “current” capacity. Availability changes require a new explicit configuration and evaluation restart.
  • Range validity is implemented in-graph as yLoadOk; finite values, source quality, and derivation provenance cannot be proven by inverted comparisons.
  • The LBNL plant has useful healthy staging channels but no injected over-stage fault. No local dataset was available and EnergyPlus’s target loop has only one real boiler, so this card records no simulation validation claim.
  • No Boiler Control Instability cluster is created. Short cycling, hunting, and over-staging can co-occur, but no one trigger reliably occurs first or shares one repair that clears all members.

Notes

Investigate HW-0001 for resulting starts, HW-0011 for unstable modulation and temperature, and HW-0002 for measured efficiency degradation. Never reduce stages until manufacturer turndown, minimum flow, venting, emissions, safety, redundancy, and minimum-time requirements have been checked.

Test Vectors

22 scenarios, clock step 60 s over 3600 s.

ScenarioDescription
zero_stages_at_low_loadA stopped plant at low load exposes only the low-load diagnostic and is not over-staged.
one_stage_at_low_loadOne firing boiler is the shipped allowance below the low-load threshold.
two_stages_at_low_load_initializationTwo firing boilers at low load assert both diagnostics immediately: yFault is clear one tick before 900 s, asserts exactly at 900 s, and remains true after the full delay.
multiple_stages_at_high_loadMultiple boilers are appropriate above the configured low-load region.
load_exact_threshold_clearExactly 0.35 is not low load under the strict comparator.
load_just_below_thresholdOne thousandth below the load threshold activates the low-load branch.
stage_count_exact_threshold_clearExactly one firing stage is not excess under the integer comparison.
both_just_beyond_boundariesTwo stages and 0.349 load are the nearest representable faulting values on both branches.
load_zero_endpoint_is_validZero is an inclusive valid load endpoint and can support an over-staging finding.
load_one_endpoint_is_validOne is an inclusive valid load endpoint and is outside the low-load region.
stage_overlap_ends_one_tick_before_persistenceA second stage drops out one evaluator step before 900 seconds and never alarms.
mature_alarm_clears_when_stage_dropsA mature over-staging alarm clears immediately when the extra boiler stops firing.
mature_alarm_clears_when_load_risesA real load increase clears the low-load premise even while two boilers continue firing.
two_short_overlaps_do_not_accumulateSeparated transient stage overlaps each receive a fresh timer.
load_rise_resets_timerA temporary load rise resets persistence before the original low-load condition returns.
stage_drop_resets_timerA one-stage interval resets persistence before the second stage returns.
load_threshold_chatter_resets_delayLoad alternating around 0.35 never stays low for the full delay.
stage_count_chatter_resets_delayA second stage repeatedly entering and leaving below the proof duration never matures.
negative_load_is_not_evaluableA negative derived load is numerically low, but the in-graph range gate blocks the alarm and exposes NO_EVAL.
load_above_one_is_not_evaluableA derived load above one is invalid; yLoadOk prevents a false healthy interpretation even though low-load is false.
enabled_count_misbinding_raw_false_positiveCounting enabled or available boilers instead of firing machines can create this raw alarm; the derivation contract must reject that binding.
redundancy_warmup_is_host_excludedTwo firing boilers during an emergency-redundancy or warm-up interval are raw over-staging but explicitly outside normal automatic sequencing.
vectors.json
{
  "schema": "cxf-library/vectors/v1",
  "clock": {
    "step_s": 60,
    "horizon_s": 3600
  },
  "scenarios": [
    {
      "name": "zero_stages_at_low_load",
      "description": "A stopped plant at low load exposes only the low-load diagnostic and is not over-staged.",
      "inputs": {
        "boiler_stage_count": 0,
        "hw_plant_load_fraction": 0.1
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "one_stage_at_low_load",
      "description": "One firing boiler is the shipped allowance below the low-load threshold.",
      "inputs": {
        "boiler_stage_count": 1,
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "two_stages_at_low_load_initialization",
      "description": "Two firing boilers at low load assert both diagnostics immediately: yFault is clear one tick before 900 s, asserts exactly at 900 s, and remains true after the full delay.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 900,
          "to_s": 900,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "multiple_stages_at_high_load",
      "description": "Multiple boilers are appropriate above the configured low-load region.",
      "inputs": {
        "boiler_stage_count": 3,
        "hw_plant_load_fraction": 0.8
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "load_exact_threshold_clear",
      "description": "Exactly 0.35 is not low load under the strict comparator.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 0.35
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "load_just_below_threshold",
      "description": "One thousandth below the load threshold activates the low-load branch.",
      "inputs": {
        "boiler_stage_count": 1,
        "hw_plant_load_fraction": 0.349
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "stage_count_exact_threshold_clear",
      "description": "Exactly one firing stage is not excess under the integer comparison.",
      "inputs": {
        "boiler_stage_count": 1,
        "hw_plant_load_fraction": 0.1
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "both_just_beyond_boundaries",
      "description": "Two stages and 0.349 load are the nearest representable faulting values on both branches.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 0.349
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "load_zero_endpoint_is_valid",
      "description": "Zero is an inclusive valid load endpoint and can support an over-staging finding.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 0.0
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "load_one_endpoint_is_valid",
      "description": "One is an inclusive valid load endpoint and is outside the low-load region.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 1.0
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "stage_overlap_ends_one_tick_before_persistence",
      "description": "A second stage drops out one evaluator step before 900 seconds and never alarms.",
      "inputs": {
        "boiler_stage_count": [
          {
            "t": 0,
            "value": 2
          },
          {
            "t": 840,
            "value": 1
          }
        ],
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "mature_alarm_clears_when_stage_drops",
      "description": "A mature over-staging alarm clears immediately when the extra boiler stops firing.",
      "inputs": {
        "boiler_stage_count": [
          {
            "t": 0,
            "value": 2
          },
          {
            "t": 1800,
            "value": 1
          }
        ],
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1740,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 1860,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yExcessStages",
          "from_s": 1860,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "mature_alarm_clears_when_load_rises",
      "description": "A real load increase clears the low-load premise even while two boilers continue firing.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": [
          {
            "t": 0,
            "value": 0.2
          },
          {
            "t": 1800,
            "value": 0.5
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1740,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 1860,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLowLoad",
          "from_s": 1860,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "two_short_overlaps_do_not_accumulate",
      "description": "Separated transient stage overlaps each receive a fresh timer.",
      "inputs": {
        "boiler_stage_count": [
          {
            "t": 0,
            "value": 2
          },
          {
            "t": 600,
            "value": 1
          },
          {
            "t": 900,
            "value": 2
          },
          {
            "t": 1500,
            "value": 1
          }
        ],
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "load_rise_resets_timer",
      "description": "A temporary load rise resets persistence before the original low-load condition returns.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": [
          {
            "t": 0,
            "value": 0.2
          },
          {
            "t": 600,
            "value": 0.5
          },
          {
            "t": 900,
            "value": 0.2
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1740,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 1860,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "stage_drop_resets_timer",
      "description": "A one-stage interval resets persistence before the second stage returns.",
      "inputs": {
        "boiler_stage_count": [
          {
            "t": 0,
            "value": 2
          },
          {
            "t": 600,
            "value": 1
          },
          {
            "t": 900,
            "value": 2
          }
        ],
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1740,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 1860,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "load_threshold_chatter_resets_delay",
      "description": "Load alternating around 0.35 never stays low for the full delay.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": [
          {
            "t": 0,
            "value": 0.349
          },
          {
            "t": 180,
            "value": 0.351
          },
          {
            "t": 360,
            "value": 0.349
          },
          {
            "t": 540,
            "value": 0.351
          },
          {
            "t": 720,
            "value": 0.349
          },
          {
            "t": 900,
            "value": 0.351
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "stage_count_chatter_resets_delay",
      "description": "A second stage repeatedly entering and leaving below the proof duration never matures.",
      "inputs": {
        "boiler_stage_count": [
          {
            "t": 0,
            "value": 2
          },
          {
            "t": 180,
            "value": 1
          },
          {
            "t": 360,
            "value": 2
          },
          {
            "t": 540,
            "value": 1
          },
          {
            "t": 720,
            "value": 2
          },
          {
            "t": 900,
            "value": 1
          }
        ],
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "negative_load_is_not_evaluable",
      "description": "A negative derived load is numerically low, but the in-graph range gate blocks the alarm and exposes NO_EVAL.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": -0.1
      },
      "expect": [
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "load_above_one_is_not_evaluable",
      "description": "A derived load above one is invalid; yLoadOk prevents a false healthy interpretation even though low-load is false.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 1.1
      },
      "expect": [
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 3600,
          "equals": false
        }
      ]
    },
    {
      "name": "enabled_count_misbinding_raw_false_positive",
      "description": "Counting enabled or available boilers instead of firing machines can create this raw alarm; the derivation contract must reject that binding.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 0.1
      },
      "expect": [
        {
          "output": "yLowLoad",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yExcessStages",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    },
    {
      "name": "redundancy_warmup_is_host_excluded",
      "description": "Two firing boilers during an emergency-redundancy or warm-up interval are raw over-staging but explicitly outside normal automatic sequencing.",
      "inputs": {
        "boiler_stage_count": 2,
        "hw_plant_load_fraction": 0.2
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 3600,
          "equals": true
        },
        {
          "output": "yLoadOk",
          "from_s": 0,
          "to_s": 3600,
          "equals": true
        }
      ]
    }
  ]
}