HW-0008 — HWS temperature reset not functioning
| Status | verified — engine e2ff2f8, cxf:fnv1a128:d14378530edda50c195f5757ef979370, 2026-08-17 |
| Severity | 3 |
| Method | statistical |
| Phase | 2 |
| Category | EXCESS_CONSUMPTION |
| Confidence | HIGH |
| Estimation | PROXY_ESTIMATION |
| G36 | — |
| Clusters | — |
| Suppresses | — |
| Suppressed by | — |
| Related | HW-0006, HW-0010, CHW-0002, AHU-0023 |
| Playbooks | missing-reset, hot-water-plant-faults |
| Source | PNNL-27338 §4.5.2 (no HW supply-temperature reset; daily MAX−MIN of the HWS setpoint against a 10 °F range), pp. 4.16-4.17; PNNL-27338 §4.4.2 (high HW supply temperature), pp. 4.12-4.13 — the fault this reset exists to prevent; PNNL-27338 (Katipamula et al. 2018) — adapted via an internal paraphrased deep-read digest, not distributed (rule candidate 7); Sibling-rule precedent: AHU-0023 (window, OAT-range evaluability, half-range tolerance), CHW-0002 (plant-side binding of the same detector); Library extension: HVAC FDD Reference v1.0 ch.14 specifies HW-0001..052 only — index framing in faults/hw/README.md |
| Operating states | Heating season — boiler plant enabled and serving the loop for the bulk of the evaluation window |
Preconditions (host-enforced): hws_temp_sp must be the active setpoint the boilers track — the plant controller’s live value, not a design figure in a schedule table or a boiler’s local panel setting that the BAS overrides. On a multi-boiler plant bind the plant-level setpoint; an individual boiler’s leaving-water setpoint is set by staging logic and reads flat while the plant resets. oat must be a trustworthy outdoor reading: the whole evaluability test rests on it, and the same sensor that HW-0003’s diagnosis 5 warns about (in the sun, above a warm roof, or dead at its last value) feeds this rule too. A plant that must hold a fixed supply temperature for a domestic hot water load has a legitimate reason to keep the setpoint flat — Legionella control sets a floor no reset may cross — and combined heating/DHW plants must either be excluded, be bound to a heating-only boiler where the plant has one, or be gated host-side on the DHW load. The plant must be running for most of the window; a boiler off for the season holds the setpoint still for reasons this rule cannot see. Evaluability is signalled in-rule by yOatVaried: when it is false the verdict is NO_EVAL, not healthy.
Points: hws_temp_sp, oat
Outputs:
yFault— True while the hot water supply temperature setpoint has stayed flat over the evaluation window despite sufficient outdoor-air variation, for at least alarm_delayyOatVaried— Evaluability signal — true when OAT has varied enough within the evaluation window for a flat setpoint to mean anything; false means NO_EVAL and the host must ignore yFault
Parameters:
| Name | Default | Unit | CXF path | Description |
|---|---|---|---|---|
evaluation_window | 604800.0 | s | spRef.samplePeriod, oatRef.samplePeriod, spFlatHeld.delayTime, oatFlatHeld.delayTime | Window over which setpoint flatness and OAT variation are assessed (7 days); drives both baseline sample periods and both dwell timers |
sp_flat_tolerance | 2.75 | °C | spFlat.t | Max deviation of hws_temp_sp from its sampled baseline to count as flat — half of PNNL-27338 §4.5.2’s 10 °F (5.6 °C) minimum expected setpoint range |
oat_variation_tolerance | 4.0 | °C | oatFlat.t | Max deviation of oat from its sampled baseline to still count as flat (half AHU-0023’s 8 °C minimum OAT range; PNNL-27338 states no weather gate for this check — see Deviations) |
alarm_delay | 86400.0 | s | persist.delayTime | Fault persistence before alarm (24 h) |
Description
The hot water supply setpoint sits at its design value from October to April while the weather moves through thirty degrees underneath it. A working reset schedules supply temperature against outdoor air, and the savings arrive by two routes: every metre of distribution pipe sheds less heat, and a condensing boiler that sees return water below roughly 55 °C picks up something like ten points of combustion efficiency it cannot reach at design temperature.
This rule is a library extension — the reference’s ch.14 specifies three hot water rules and no reset checks. The detection is grounded in PNNL-27338 §4.5.2, which flags a plant whose supply setpoint moves less than 10 °F across a day; the window, the weather gate and every block in the graph are this library’s. The weather gate is what makes a flat setpoint evidence: a week where the weather did not move is a week where a correct reset had nothing to do, and a frozen OAT sensor — one of the things that causes a flat setpoint — flattens the driver and takes the rule out of service rather than letting it report a fault it cannot substantiate.
Detection Logic
baseline(x) = x sampled and held every evaluation_window (7 days)
sp_flat = |hws_temp_sp − baseline(hws_temp_sp)| < sp_flat_tolerance,
continuously for evaluation_window
oat_flat = |oat − baseline(oat)| < oat_variation_tolerance,
continuously for evaluation_window
yOatVaried = NOT oat_flat (false ⇒ host reports NO_EVAL)
yFault = sp_flat AND yOatVaried, sustained for alarm_delay
Block graph (rule.cxf.jsonld):
This is AHU-0023’s detector with the boiler plant’s points bound to it. Two
symmetric chains compare each signal against a weekly Discrete.Sampler
baseline, which emits the live input on its first tick, so there is no startup
artifact. spFlatHeld asserts only after the setpoint has stayed within
tolerance continuously for a full window, and any reset activity of 2.75 °C or
more restarts it. oatFlatHeld does the same for outdoor air, and its negation
is yOatVaried — “not varied” means a full window of continuous flatness, so
the signal is optimistically true during the first window after startup, which
is harmless because yFault needs that same window.
Both dwell timers fire on the same tick when plant and weather are flat
together, so the fault conjunction is false by construction on that tick and
there is no boundary race. persist (24 h) filters the remainder, and every
TrueDelay carries delayOnInit = true. Worst-case time to alarm from cold
start is evaluation_window + alarm_delay — 8 days. Comparisons are strict, so
a signal sitting exactly on a tolerance falls on the not-flat side.
Possible Diagnoses
The reference algorithm publishes a detection test and no diagnosis list; these are this library’s, in the order a technician should work them.
- HWS reset never programmed — the common case, and a desk fix
- Reset programmed but disabled, or the setpoint overridden to a fixed value after a cold-weather complaint and never released
- Reset schedule configured with endpoints so close together that the plant never leaves its design temperature — programmed, reviewed, and useless
- Boilers running to their own local panel setpoints and ignoring the BAS value, in which case the trended setpoint may even reset while the water does not
- Domestic hot water load holding the plant at a fixed temperature. Not a fault; a precondition this rule cannot check (see Deviations)
Energy Impact
EXCESS_CONSUMPTION, HIGH confidence, PROXY_ESTIMATION. The savings figure is the hot water playbook’s: HW supply-temperature reset and DP reset together are worth 1–3% of site energy, with no published split. The share belonging to this rule depends on the boiler — on a non-condensing plant the recovery is distribution and standby loss, worth a few percent of plant fuel, while on a condensing plant a reset that drops return water under about 55 °C for the mild half of the season is worth several times that. Heating-dominant, and the strongest case for the fix is in mild climates and shoulder seasons.
Emissions Impact
Scope 1, PROXY_EMISSIONS, HIGH confidence; typical 500–5,000 kg CO₂e/yr for a plant carrying an uncommissioned setpoint through a heating season. The scope is what separates this rule from its DP twin: combustion at the site on a static fuel factor, so a site that has decarbonised its electricity still owns every kilogram. Plants heated electrically or by heat pump move the accounting to Scope 2 and the marginal operating emissions rate; the fault is the same and the host owns the classification.
Deviations
- This rule extends the reference rather than transcribing it. Ch.14 covers
HW-0001, 051 and 052 only, so there is no reference card behind this one —
no published description, operating states, diagnosis list, tunables line or
test vectors. The detection is paraphrased from PNNL-27338 §4.5.2 (Katipamula
et al. 2018);
name,severityandmethodcome fromfaults/hw/README.md. - Daily MAX−MIN → rolling sampled baseline plus dwell. PNNL’s reset checks run once a day at midnight over the prior day’s array; this engine has no windowed min/max block and no batch clock, a question the library settled at AHU-0023 and has reused since. Tolerances are half the reference range — the half-range convention, because a signal swinging ±t about a baseline spans 2t. Detection is equivalent for a setpoint that moves and returns, and slightly conservative for monotonic drift inside one window, where a range test would still call it flat.
- 10 °F becomes a 2.75 °C half-range. The point dictionary carries
hws_temp_spin °C, so the conversion happens here once: a 10 °F interval is 5.56 °C, half-range 2.78, shipped as 2.75 (a 5.5 °C range). The rounding is 1% tight — a hair less willing to call the setpoint flat, so it errs toward silence — and 2.75 is exactly representable in binary, which lets the boundary be pinned to the bit rather than to within a rounding error. - The 7-day window is adopted from AHU-0023, not from PNNL. PNNL’s window
is one calendar day, which works there because the daily batch discards its
evidence at every midnight; a rolling dwell does not, and a one-day dwell would
alarm on the first stable weekend of the season. Seven days is also what makes
the weather gate meaningful, since a front takes days to move through.
evaluation_windowdrives all four timing parameters together and hosts must set them as a group. - The OAT gate is a library addition; §4.5.2 has none. PNNL’s test is a bare range test on the setpoint trend with no driver condition, so a plant holding its setpoint through a genuinely stable week is a fault by that algorithm and NO_EVAL by this one. The conjunct can only make the rule quieter — it never creates a finding — and it buys the degradation property above: a dead OAT sensor suppresses the rule rather than having the flat setpoint it caused reported as a controls fault. AHU-0023 made the same trade.
- The 8 °C minimum OAT range is adopted from AHU-0023 (shipped as
oat_variation_tolerance = 4 °C, half-range), because PNNL has no gate and so supplies no figure. It is a low bar any heating season clears — 8 °C of movement across a week is unremarkable weather anywhere a boiler runs — and it is the value a host tuning both rules expects to find the same on each. Lowering it makes the rule evaluable more often and therefore noisier. - The DHW case is a precondition, not a detection. A plant holding 60 °C for
domestic hot water has a legitimate flat setpoint, and nothing in two points
distinguishes it from a plant nobody programmed. Same limitation HW-0003
records, same answer: exclude the rule, bind a heating-only boiler where the
plant has one, or gate host-side on the DHW load. Widening
sp_flat_tolerancewould convert a false positive into a blind spot. - NO_EVAL is surfaced as
yOatVaried. Boolean logic has no tri-state, so evaluability is a second boundary output the host consults before interpretingyFault— false means NO_EVAL, never healthy. Same name and inverted-flat semantics as AHU-0023, deliberately, so a host binding both reads one convention, and it earns its place as an output rather than an echo by being a stateful window test over the signal’s own sampled baseline. Reals.MovingAveragerejected, and the tick band that follows. The engine implements it with a fixed 64-checkpoint ring, so a seven-day window would needdt ≥ 9,600 sbefore it stops silently dropping its oldest samples. No BAS ticks that slowly; AHU-0023 found this and every reset rule since has inherited it. The sampler-and-dwell chain has no lower bound on tick period, and its upper bound is the one to watch — a reset excursion shorter than one tick is invisible to the flatness test, so trend at 5–15 min.- Strict comparisons on both tolerances.
Reals.LessThresholdisu < t, so a setpoint deviating exactly 2.75 °C clears the flatness dwell and an outdoor temperature deviating exactly 4 °C counts as varied. Equality is measure-zero in continuous data and perfectly reachable in a BAS that quantises setpoints to whole degrees, so both boundaries are pinned from both sides. alarm_delay= 24 h implemented asTrueDelayon the fault conjunction; the evaluation window itself is enforced by the two flatness dwells.delayOnInit = trueon everyTrueDelay(startup conservatism per AHU-0016), so a rule loaded onto an already-faulted plant still waits the full window plus delay. PNNL has no analog to the delay, its evaluation being one shot per midnight.- Two playbooks, matching HW-0006.
missing-resetowns the reset family’s verification step — plot the setpoint against its driver over the window — andhot-water-plant-faultscarries the remedy in its step 1 (program an OAT-based reset schedule). Neither covers the fault alone and both indexes belong to other writers, so this card lists both rather than stretching one. - Blind spots. The rule reads the setpoint, never the water: a plant whose
setpoint schedules beautifully while the boilers make design-temperature water
anyway is diagnosis 4 and invisible here — pair with HW-0002 and HW-0007
to see it. Diagnoses 1–3 are one signature. A reset driven by something other
than weather (a valve-request trim-and-respond sequence, which some hot water
plants run) is legitimately flat in stable weather and legitimately active when
the weather is still, so on those plants the rule stays correct but reports
NO_EVAL more often than it needs to. And a plant simply off for the window
holds both signals flat;
yOatVariedwill usually catch it, but the host’s operating-state gate is what should.
Notes
Fix path is the missing-reset playbook
for the verification step and
hot-water-plant-faults for the
remedy, whose step 1 gives the schedule shape with a worked example range. Plot
hws_temp_sp against oat over the window first: a flat line against moving
weather is this fault, and a line that moves but never leaves a 2 °C band is
diagnosis 3, which looks programmed in a screenshot and behaves almost as badly.
clusters is deliberately empty. CLU-02 (“Missing Reset Strategy”) is an
AHU-scoped cluster triggered by AHU-0023, and membership is
clusters/clusters.json’s to declare — the same call the CHW pair made. A plant
failing both HW-0006 and HW-0008 has one root cause, which is that nobody
commissioned the hot water resets, and it should be dispatched as one visit.
Test Vectors
9 scenarios, clock step 300 s over 777600 s.
| Scenario | Description |
|---|---|
reset_working | HWS setpoint swings 10 degC with the weather; the flatness dwell restarts every 12 h and nothing alarms |
fixed_setpoint | HWS setpoint pinned at 70 degC while OAT swings 2-14 degC; fault asserts at exactly evaluation_window (7 d) + alarm_delay (24 h) = 691200 s, pinned from both sides |
flat_oat_no_eval | NO_EVAL: setpoint flat but the weather never moves; yOatVaried drops at exactly evaluation_window and yFault never fires |
sp_at_flat_tolerance | Setpoint deviates from its baseline by exactly sp_flat_tolerance (2.75 degC): strict < puts that on the not-flat side, so no fault |
sp_within_flat_tolerance | Setpoint jitter of 2.5 degC stays inside sp_flat_tolerance, so it still counts as flat; fault at 691200 s |
oat_at_variation_tolerance | OAT deviates from its baseline by exactly oat_variation_tolerance (4 degC): strict < makes that varied, so the rule stays evaluable and the flat setpoint alarms |
oat_within_variation_tolerance | OAT moves only 3.9 degC about its baseline, inside oat_variation_tolerance: NO_EVAL at exactly evaluation_window, and the simultaneous setpoint-flat edge cannot race it into a fault |
sp_goes_flat_mid_run | Reset stops working 12 h into the run: the dwell starts at 43200 s, so the alarm lands at exactly 43200 + 604800 + 86400 = 734400 s |
reset_resumes | Setpoint flat for 4 days, then reset activity resumes before the window completes; the dwell restarts and nothing alarms |
vectors.json
{
"schema": "cxf-library/vectors/v1",
"clock": {
"step_s": 300,
"horizon_s": 777600
},
"scenarios": [
{
"name": "reset_working",
"description": "HWS setpoint swings 10 degC with the weather; the flatness dwell restarts every 12 h and nothing alarms",
"inputs": {
"hws_temp_sp": [
{
"t": 0,
"value": 70.0
},
{
"t": 43200,
"value": 60.0
},
{
"t": 86400,
"value": 70.0
},
{
"t": 129600,
"value": 60.0
},
{
"t": 172800,
"value": 70.0
},
{
"t": 216000,
"value": 60.0
},
{
"t": 259200,
"value": 70.0
},
{
"t": 302400,
"value": 60.0
},
{
"t": 345600,
"value": 70.0
},
{
"t": 388800,
"value": 60.0
},
{
"t": 432000,
"value": 70.0
},
{
"t": 475200,
"value": 60.0
},
{
"t": 518400,
"value": 70.0
},
{
"t": 561600,
"value": 60.0
},
{
"t": 604800,
"value": 70.0
},
{
"t": 648000,
"value": 60.0
},
{
"t": 691200,
"value": 70.0
},
{
"t": 734400,
"value": 60.0
},
{
"t": 777600,
"value": 70.0
}
],
"oat": [
{
"t": 0,
"value": 2.0
},
{
"t": 43200,
"value": 14.0
},
{
"t": 86400,
"value": 2.0
},
{
"t": 129600,
"value": 14.0
},
{
"t": 172800,
"value": 2.0
},
{
"t": 216000,
"value": 14.0
},
{
"t": 259200,
"value": 2.0
},
{
"t": 302400,
"value": 14.0
},
{
"t": 345600,
"value": 2.0
},
{
"t": 388800,
"value": 14.0
},
{
"t": 432000,
"value": 2.0
},
{
"t": 475200,
"value": 14.0
},
{
"t": 518400,
"value": 2.0
},
{
"t": 561600,
"value": 14.0
},
{
"t": 604800,
"value": 2.0
},
{
"t": 648000,
"value": 14.0
},
{
"t": 691200,
"value": 2.0
},
{
"t": 734400,
"value": 14.0
},
{
"t": 777600,
"value": 2.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 777600,
"equals": false
},
{
"output": "yOatVaried",
"from_s": 0,
"to_s": 777600,
"equals": true
}
]
},
{
"name": "fixed_setpoint",
"description": "HWS setpoint pinned at 70 degC while OAT swings 2-14 degC; fault asserts at exactly evaluation_window (7 d) + alarm_delay (24 h) = 691200 s, pinned from both sides",
"inputs": {
"hws_temp_sp": 70.0,
"oat": [
{
"t": 0,
"value": 2.0
},
{
"t": 43200,
"value": 14.0
},
{
"t": 86400,
"value": 2.0
},
{
"t": 129600,
"value": 14.0
},
{
"t": 172800,
"value": 2.0
},
{
"t": 216000,
"value": 14.0
},
{
"t": 259200,
"value": 2.0
},
{
"t": 302400,
"value": 14.0
},
{
"t": 345600,
"value": 2.0
},
{
"t": 388800,
"value": 14.0
},
{
"t": 432000,
"value": 2.0
},
{
"t": 475200,
"value": 14.0
},
{
"t": 518400,
"value": 2.0
},
{
"t": 561600,
"value": 14.0
},
{
"t": 604800,
"value": 2.0
},
{
"t": 648000,
"value": 14.0
},
{
"t": 691200,
"value": 2.0
},
{
"t": 734400,
"value": 14.0
},
{
"t": 777600,
"value": 2.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 690900,
"equals": false
},
{
"output": "yFault",
"from_s": 691200,
"to_s": 777600,
"equals": true
},
{
"output": "yOatVaried",
"from_s": 0,
"to_s": 777600,
"equals": true
}
]
},
{
"name": "flat_oat_no_eval",
"description": "NO_EVAL: setpoint flat but the weather never moves; yOatVaried drops at exactly evaluation_window and yFault never fires",
"inputs": {
"hws_temp_sp": 70.0,
"oat": 5.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 777600,
"equals": false
},
{
"output": "yOatVaried",
"from_s": 0,
"to_s": 604500,
"equals": true
},
{
"output": "yOatVaried",
"from_s": 604800,
"to_s": 777600,
"equals": false
}
]
},
{
"name": "sp_at_flat_tolerance",
"description": "Setpoint deviates from its baseline by exactly sp_flat_tolerance (2.75 degC): strict < puts that on the not-flat side, so no fault",
"inputs": {
"hws_temp_sp": [
{
"t": 0,
"value": 70.0
},
{
"t": 43200,
"value": 72.75
},
{
"t": 86400,
"value": 70.0
},
{
"t": 129600,
"value": 72.75
},
{
"t": 172800,
"value": 70.0
},
{
"t": 216000,
"value": 72.75
},
{
"t": 259200,
"value": 70.0
},
{
"t": 302400,
"value": 72.75
},
{
"t": 345600,
"value": 70.0
},
{
"t": 388800,
"value": 72.75
},
{
"t": 432000,
"value": 70.0
},
{
"t": 475200,
"value": 72.75
},
{
"t": 518400,
"value": 70.0
},
{
"t": 561600,
"value": 72.75
},
{
"t": 604800,
"value": 70.0
},
{
"t": 648000,
"value": 72.75
},
{
"t": 691200,
"value": 70.0
},
{
"t": 734400,
"value": 72.75
},
{
"t": 777600,
"value": 70.0
}
],
"oat": [
{
"t": 0,
"value": 2.0
},
{
"t": 43200,
"value": 14.0
},
{
"t": 86400,
"value": 2.0
},
{
"t": 129600,
"value": 14.0
},
{
"t": 172800,
"value": 2.0
},
{
"t": 216000,
"value": 14.0
},
{
"t": 259200,
"value": 2.0
},
{
"t": 302400,
"value": 14.0
},
{
"t": 345600,
"value": 2.0
},
{
"t": 388800,
"value": 14.0
},
{
"t": 432000,
"value": 2.0
},
{
"t": 475200,
"value": 14.0
},
{
"t": 518400,
"value": 2.0
},
{
"t": 561600,
"value": 14.0
},
{
"t": 604800,
"value": 2.0
},
{
"t": 648000,
"value": 14.0
},
{
"t": 691200,
"value": 2.0
},
{
"t": 734400,
"value": 14.0
},
{
"t": 777600,
"value": 2.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 777600,
"equals": false
},
{
"output": "yOatVaried",
"from_s": 0,
"to_s": 777600,
"equals": true
}
]
},
{
"name": "sp_within_flat_tolerance",
"description": "Setpoint jitter of 2.5 degC stays inside sp_flat_tolerance, so it still counts as flat; fault at 691200 s",
"inputs": {
"hws_temp_sp": [
{
"t": 0,
"value": 70.0
},
{
"t": 43200,
"value": 72.5
},
{
"t": 86400,
"value": 70.0
},
{
"t": 129600,
"value": 72.5
},
{
"t": 172800,
"value": 70.0
},
{
"t": 216000,
"value": 72.5
},
{
"t": 259200,
"value": 70.0
},
{
"t": 302400,
"value": 72.5
},
{
"t": 345600,
"value": 70.0
},
{
"t": 388800,
"value": 72.5
},
{
"t": 432000,
"value": 70.0
},
{
"t": 475200,
"value": 72.5
},
{
"t": 518400,
"value": 70.0
},
{
"t": 561600,
"value": 72.5
},
{
"t": 604800,
"value": 70.0
},
{
"t": 648000,
"value": 72.5
},
{
"t": 691200,
"value": 70.0
},
{
"t": 734400,
"value": 72.5
},
{
"t": 777600,
"value": 70.0
}
],
"oat": [
{
"t": 0,
"value": 2.0
},
{
"t": 43200,
"value": 14.0
},
{
"t": 86400,
"value": 2.0
},
{
"t": 129600,
"value": 14.0
},
{
"t": 172800,
"value": 2.0
},
{
"t": 216000,
"value": 14.0
},
{
"t": 259200,
"value": 2.0
},
{
"t": 302400,
"value": 14.0
},
{
"t": 345600,
"value": 2.0
},
{
"t": 388800,
"value": 14.0
},
{
"t": 432000,
"value": 2.0
},
{
"t": 475200,
"value": 14.0
},
{
"t": 518400,
"value": 2.0
},
{
"t": 561600,
"value": 14.0
},
{
"t": 604800,
"value": 2.0
},
{
"t": 648000,
"value": 14.0
},
{
"t": 691200,
"value": 2.0
},
{
"t": 734400,
"value": 14.0
},
{
"t": 777600,
"value": 2.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 690900,
"equals": false
},
{
"output": "yFault",
"from_s": 691200,
"to_s": 777600,
"equals": true
}
]
},
{
"name": "oat_at_variation_tolerance",
"description": "OAT deviates from its baseline by exactly oat_variation_tolerance (4 degC): strict < makes that varied, so the rule stays evaluable and the flat setpoint alarms",
"inputs": {
"hws_temp_sp": 70.0,
"oat": [
{
"t": 0,
"value": 5.0
},
{
"t": 43200,
"value": 9.0
},
{
"t": 86400,
"value": 5.0
},
{
"t": 129600,
"value": 9.0
},
{
"t": 172800,
"value": 5.0
},
{
"t": 216000,
"value": 9.0
},
{
"t": 259200,
"value": 5.0
},
{
"t": 302400,
"value": 9.0
},
{
"t": 345600,
"value": 5.0
},
{
"t": 388800,
"value": 9.0
},
{
"t": 432000,
"value": 5.0
},
{
"t": 475200,
"value": 9.0
},
{
"t": 518400,
"value": 5.0
},
{
"t": 561600,
"value": 9.0
},
{
"t": 604800,
"value": 5.0
},
{
"t": 648000,
"value": 9.0
},
{
"t": 691200,
"value": 5.0
},
{
"t": 734400,
"value": 9.0
},
{
"t": 777600,
"value": 5.0
}
]
},
"expect": [
{
"output": "yOatVaried",
"from_s": 0,
"to_s": 777600,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 690900,
"equals": false
},
{
"output": "yFault",
"from_s": 691200,
"to_s": 777600,
"equals": true
}
]
},
{
"name": "oat_within_variation_tolerance",
"description": "OAT moves only 3.9 degC about its baseline, inside oat_variation_tolerance: NO_EVAL at exactly evaluation_window, and the simultaneous setpoint-flat edge cannot race it into a fault",
"inputs": {
"hws_temp_sp": 70.0,
"oat": [
{
"t": 0,
"value": 5.0
},
{
"t": 43200,
"value": 8.9
},
{
"t": 86400,
"value": 5.0
},
{
"t": 129600,
"value": 8.9
},
{
"t": 172800,
"value": 5.0
},
{
"t": 216000,
"value": 8.9
},
{
"t": 259200,
"value": 5.0
},
{
"t": 302400,
"value": 8.9
},
{
"t": 345600,
"value": 5.0
},
{
"t": 388800,
"value": 8.9
},
{
"t": 432000,
"value": 5.0
},
{
"t": 475200,
"value": 8.9
},
{
"t": 518400,
"value": 5.0
},
{
"t": 561600,
"value": 8.9
},
{
"t": 604800,
"value": 5.0
},
{
"t": 648000,
"value": 8.9
},
{
"t": 691200,
"value": 5.0
},
{
"t": 734400,
"value": 8.9
},
{
"t": 777600,
"value": 5.0
}
]
},
"expect": [
{
"output": "yOatVaried",
"from_s": 0,
"to_s": 604500,
"equals": true
},
{
"output": "yOatVaried",
"from_s": 604800,
"to_s": 777600,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 777600,
"equals": false
}
]
},
{
"name": "sp_goes_flat_mid_run",
"description": "Reset stops working 12 h into the run: the dwell starts at 43200 s, so the alarm lands at exactly 43200 + 604800 + 86400 = 734400 s",
"inputs": {
"hws_temp_sp": [
{
"t": 0,
"value": 70.0
},
{
"t": 21600,
"value": 60.0
},
{
"t": 43200,
"value": 70.0
}
],
"oat": [
{
"t": 0,
"value": 2.0
},
{
"t": 43200,
"value": 14.0
},
{
"t": 86400,
"value": 2.0
},
{
"t": 129600,
"value": 14.0
},
{
"t": 172800,
"value": 2.0
},
{
"t": 216000,
"value": 14.0
},
{
"t": 259200,
"value": 2.0
},
{
"t": 302400,
"value": 14.0
},
{
"t": 345600,
"value": 2.0
},
{
"t": 388800,
"value": 14.0
},
{
"t": 432000,
"value": 2.0
},
{
"t": 475200,
"value": 14.0
},
{
"t": 518400,
"value": 2.0
},
{
"t": 561600,
"value": 14.0
},
{
"t": 604800,
"value": 2.0
},
{
"t": 648000,
"value": 14.0
},
{
"t": 691200,
"value": 2.0
},
{
"t": 734400,
"value": 14.0
},
{
"t": 777600,
"value": 2.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 734100,
"equals": false
},
{
"output": "yFault",
"from_s": 734400,
"to_s": 777600,
"equals": true
},
{
"output": "yOatVaried",
"from_s": 0,
"to_s": 777600,
"equals": true
}
]
},
{
"name": "reset_resumes",
"description": "Setpoint flat for 4 days, then reset activity resumes before the window completes; the dwell restarts and nothing alarms",
"inputs": {
"hws_temp_sp": [
{
"t": 0,
"value": 70.0
},
{
"t": 345600,
"value": 60.0
},
{
"t": 388800,
"value": 70.0
},
{
"t": 432000,
"value": 60.0
},
{
"t": 475200,
"value": 70.0
},
{
"t": 518400,
"value": 60.0
},
{
"t": 561600,
"value": 70.0
},
{
"t": 604800,
"value": 60.0
},
{
"t": 648000,
"value": 70.0
},
{
"t": 691200,
"value": 60.0
},
{
"t": 734400,
"value": 70.0
},
{
"t": 777600,
"value": 60.0
}
],
"oat": [
{
"t": 0,
"value": 2.0
},
{
"t": 43200,
"value": 14.0
},
{
"t": 86400,
"value": 2.0
},
{
"t": 129600,
"value": 14.0
},
{
"t": 172800,
"value": 2.0
},
{
"t": 216000,
"value": 14.0
},
{
"t": 259200,
"value": 2.0
},
{
"t": 302400,
"value": 14.0
},
{
"t": 345600,
"value": 2.0
},
{
"t": 388800,
"value": 14.0
},
{
"t": 432000,
"value": 2.0
},
{
"t": 475200,
"value": 14.0
},
{
"t": 518400,
"value": 2.0
},
{
"t": 561600,
"value": 14.0
},
{
"t": 604800,
"value": 2.0
},
{
"t": 648000,
"value": 14.0
},
{
"t": 691200,
"value": 2.0
},
{
"t": 734400,
"value": 14.0
},
{
"t": 777600,
"value": 2.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 777600,
"equals": false
}
]
}
]
}