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

HP-0005 — Refrigerant overcharge — high liquid subcooling

Statusverified — engine e2ff2f8, cxf:fnv1a128:29e19fa80140e255eff5fc094242ff34, 2026-08-18
Severity3
Methodrule
Phase2
CategoryEFFICIENCY_LOSS
ConfidenceMEDIUM
EstimationPROXY_ESTIMATION
G36
Clusters
Suppresses
Suppressed by
RelatedHP-0001, HP-0004, RTU-0009, HP-0007
Playbooksheat-pump-faults
SourceNIST SP 1087, Kim, Yoon, Payne & Domanski, Cooling Mode Fault Detection and Diagnosis Method for a Residential Heat Pump (October 2008), Table 5.2 — for refrigerant overcharge on a TXV-equipped unit, subcooling, condensing temperature and discharge temperature all rise while superheat does not move; NIST SP 1087 §5.4.2 and Fig. 5.14 — the 0.5 °C TXV-upstream subcooling test that selects which fault chart applies; NIST SP 1087 Figs. 5.16-5.17 and Table 5.17 — overcharge is the least EER-sensitive fault tested, and was diagnosed correctly at fault levels its own EER-degradation detector never flagged; Library-authored: the HVAC FDD Reference v1.0 ch.11 specifies no refrigerant-side heat pump rule (HP-0001..052 only); name, severity 3 and method: rule are argued on this card; Sibling precedent: RTU-0002 (a fixed baseline shipped as a named simplification of a fitted model), HP-0001 (placeholder parameter with a silent-failure warning), HP-0002 and HP-0003 (defrost and mode gating kept host-side)
Operating statescompressor running in steady cooling — the source’s tested mode; heating is an authored extension needing its own instance, band and liquid-line sensor (see Deviations)

Preconditions (host-enforced): The host gates this rule end to end and there is no in-rule evaluability output: silence outside the gates is NO_EVAL by frontmatter, not a healthy charge. comp_status must have been true continuously for at least 10 min — subcooling on the source’s rig took roughly 5-8 min to settle after a start, and a pulling-down circuit reads whatever its transient says. defrost_status must be false, and the host must hold evaluation off for a recovery period after a cycle ends: a reversal drives both temperatures through excursions that have nothing to do with charge. cond_sat_temp is host-derived through a refrigerant P-T lookup, so the lookup must match the refrigerant actually in the machine — the wrong one offsets every subcooling reading by a near-constant the rule cannot distinguish from charge. liquid_temp must be sensed between the condenser outlet and the metering device for the mode being evaluated, with proper pipe contact and insulation. subcooling_high_band must be commissioned against this unit’s own target subcooling before any verdict means anything (see Deviations).

Points: cond_sat_temp, liquid_temp

Outputs:

  • yFault — True while liquid subcooling (cond_sat_temp − liquid_temp) has stayed above subcooling_high_band continuously for at least alarm_delay

Parameters:

NameDefaultUnitCXF pathDescription
subcooling_high_band12.0°CscHigh.tLiquid subcooling above which the circuit is holding more refrigerant than it should. PER-UNIT COMMISSIONING VALUE — the shipped 12.0 is roughly double a typical commissioned target of 5-6 °C and catches gross overcharge only; set it to the unit’s own charging-chart target plus about 3 °C to approach the source’s sensitivity
alarm_delay1800.0spersist.delayTimeContinuous excess subcooling required before the alarm asserts (30 min). Long enough to ride out a load step or a post-defrost recovery the host did not gate out; overcharge itself does not come and go

Description

Overcharge is refrigerant the circuit has no room for. The surplus backs up into the outlet end of the condenser, turning surface that should be condensing into extra subcooling area, and the liquid line leaves colder relative to its own condensing saturation temperature — subcooling rises, which is what this rule reads. NIST SP 1087 imposed 10%, 20% and 30% overcharge on a TXV-equipped heat pump in cooling and measured subcooling, condensing temperature and discharge temperature all rising together while superheat did not move at all, because the expansion valve went on holding it. The fault is cheap in energy and expensive in compressor life: 20% too much refrigerant cost that unit 1.1-3.4% of EER, so an efficiency rule such as HP-0001 will usually never see it.

Detection Logic

liquid_subcooling = cond_sat_temp − liquid_temp

yFault = liquid_subcooling > subcooling_high_band,
         sustained continuously for alarm_delay

Block graph (rule.cxf.jsonld):

HP-0005 block graph

One subtraction, one comparison, one timer. liquid_subcooling is computed in-graph rather than bound as a point: the point dictionary defines subcooling as exactly this difference, and both operands are already boundary inputs.

There is deliberately no superheat term. On a unit with a thermostatic expansion valve the valve holds superheat against the fault, and the source’s chart records superheat as unchanged for overcharge at every level it tested — a rule that required a superheat move would be unfirable on the equipment it targets. Nor is there an evaluability output: compressor state, mode and defrost are host preconditions, and the source’s own 0.5 °C TXV test is implied by any subcooling large enough to fire this rule (see Deviations).

The comparison is strict, so a unit sitting exactly on the band reads healthy. persist requires 30 continuous minutes and delayOnInit = true holds that window across a controller restart.

Possible Diagnoses

  1. Refrigerant overcharge from service — topped up by pressure rather than by weight, or charge added to a unit whose actual complaint was airflow
  2. Liquid-line restriction: a plugged filter-drier, a kinked line, or a clogged TXV inlet screen backs refrigerant up ahead of the restriction and raises subcooling identically. The source separates the two by condensing temperature falling and superheat rising, neither of which this rule reads
  3. Non-condensable gas from a short or skipped evacuation — raises head pressure and subcooling together; confirm off-cycle by comparing standstill pressure against ambient saturation, which is why the source excluded it from its online method
  4. A wrong reading rather than a wrong charge: cond_sat_temp derived with another refrigerant’s P-T relation, or a liquid-line sensor with poor pipe contact or missing insulation
  5. Flooded-condenser head-pressure control operating as designed at low ambient, which holds liquid in the condenser on purpose — check whether it is active before touching the charge

Energy Impact

EFFICIENCY_LOSS, MEDIUM confidence, PROXY_ESTIMATION. Overcharge was the fault EER tolerated best of the six the source imposed: 20% too much refrigerant cost 1.1-3.4% of EER across four operating conditions, and reaching a 5% hit took 32-42% overcharge — several times the charge error undercharge needs for the same damage. waste_kw ≈ eer_penalty × elec_power. PROXY and MEDIUM because the rule measures a temperature difference and borrows the penalty from published lab results. The case for fixing it is only partly the meter: excess charge raises head pressure and discharge temperature and pushes liquid toward the compressor, a reliability cost this rule cannot size.

Emissions Impact

Scope 2, PROXY_EMISSIONS, MEDIUM confidence; typically 50-400 kg CO₂e/yr for a commercial packaged heat pump — HP-0001’s range scaled down by this fault’s much smaller efficiency penalty. The waste is compressor electricity, so the avoided-emissions basis is the marginal operating emissions rate (MOER). The larger climate term is off that meter entirely: the excess charge must be recovered rather than vented, and the R-410A still in most of this equipment has a GWP above 2,000.

Deviations

  • A fixed nominal band replaces the source’s regressed reference model — the central simplification. The source predicts each feature from a third-order polynomial in outdoor drybulb, indoor drybulb and indoor dew point, then tests the residual against a noise band it measured at 0.574 °C for subcooling. No CDL block expresses that regression, so this card tests an absolute subcooling the way manufacturer charging charts publish one. RTU-0002 names the same class of substitution for its fixed split baselines.
  • subcooling_high_band ships as a commissioning placeholder and the default is deliberately insensitive. The source’s no-fault rig ran about 5 °C of subcooling and 30% overcharge raised it only 2-3 °C, so at the shipped 12.0 °C this rule catches gross overcharge and nothing subtle. That is the price of a runnable default on an unknown unit; the retune is target subcooling plus ~3 °C, and it fails silently in both directions (set low, every hot afternoon alarms; set high, nothing ever does).
  • No superheat conjunct, by evidence rather than convenience. The source’s fault chart marks superheat unchanged for overcharge in every tested case, because the TXV was never driven out of its control range by this fault. The companion undercharge rule (HP-0004) is where superheat carries information — it is the feature that separates the two charge faults, not one that confirms either alone.
  • No yTxvOk evaluability output, unlike the undercharge branch. The source uses subcooling above 0.5 °C at the valve inlet to decide which fault chart applies. Any subcooling large enough to trip this rule satisfies that test by construction, so the flag would be true whenever yFault is, and false only when the liquid line is effectively two-phase — a state in which “not overcharged” is a sound verdict, not NO_EVAL. Publishing it would invite hosts to discard a correct answer.
  • Discharge and condensing temperature are dropped from the pattern. The source’s overcharge signature is three simultaneous positive residuals (subcooling, condensing temperature, discharge temperature), but the other two are meaningful only against a conditions-regressed baseline: both track ambient and load directly, and no commissioning practice publishes a fixed nominal for either. Subcooling is the one feature of the three with a published target.
  • The rule stands alone; the source runs its chart only after an efficiency detector trips. In the source’s algorithm the rule chart is consulted only once EER falls 3% below the reference model. Not reproduced deliberately: its own results table shows 10-20% overcharge diagnosed correctly with EER degradation under 3% and the warning never firing, which is exactly the coverage this card adds over HP-0001.
  • A known confusion the rule cannot resolve. A severe liquid-line restriction raises subcooling too — it appears with subcooling up in the source’s saturated-valve chart — and so does non-condensable gas, steeply. This card names them in Possible Diagnoses rather than pretending to a differential diagnosis it has no points for; the playbook narrows it on site.
  • Heating-mode use is an authored extension. The source tested cooling only, and on a reversible unit the reversing valve swaps which coil is the condenser, so one liquid-line sensor is generally valid in one mode. Run one instance per mode, each with its own sensor binding and its own band. The physics of charge inventory carries over; this lab data does not.
  • Compressor, mode and defrost gating stay host preconditions, per the library’s stance that the graph computes the fault given valid data. Defrost matters more here than elsewhere: reversing the circuit scrambles both refrigerant temperatures for minutes, and every reading during and just after is noise to this rule (HP-0002 and HP-0003 precedent).
  • Strict > at the band. CDL Reals has no GreaterEqual, so a unit exactly on the band reads healthy where a technician would call it high — measure-zero on a real-valued signal, and both sides are pinned by vectors.
  • persist.delayOnInit = true (CDL default is false), the library’s standing choice: a unit already overcharged at controller start waits out the full 30 minutes rather than alarming on the first tick.
  • Single-fault provenance. The source’s chart, like every chart of this family, is fit from single-fault tests and misreads combinations. One boolean on one feature inherits no ranking error, but read the diagnosis list as a family rather than a verdict whenever a second fault is plausible.
  • Severity 3, category: EFFICIENCY_LOSS and clusters: [] are authored, not inherited — there is no reference card for this fault. method: rule describes this graph, not the source, whose own diagnosis is a probabilistic classifier over regressed residuals. Every scenario in vectors.json is library-authored and replayed against the pinned engine rev.

Notes

Read alongside HP-0001, which will usually be silent here by design, and HP-0004, the undercharge branch — the two firing together is a contradiction and means a sensor or a P-T lookup is wrong, not that the unit is both over- and undercharged. Confirm at the service ports before recovering anything; the heat-pump-faults playbook orders the charge work, and resolution is subcooling back inside the manufacturer’s charging-chart target with the recovered refrigerant weighed rather than guessed. Rule out the liquid-line restriction first: pulling charge out of a unit with a plugged drier makes the machine worse.

Test Vectors

10 scenarios, clock step 300 s over 9000 s.

ScenarioDescription
nominal_subcooling_healthyA correctly charged unit: 45 °C condensing saturation against a 39 °C liquid line is 6 K of subcooling, near the ~5 K the source measured on its no-fault rig and half the shipped band.
hot_day_same_subcooling_healthyBoth temperatures 10 K higher — a hot afternoon, or a condenser starved of air — and the same 6 K of subcooling. The rule reads the difference, not the level, so nothing here is a charge verdict. Pairs with overcharged_liquid_backs_up: wire the operands backwards and both flip.
overcharged_liquid_backs_upExcess refrigerant floods the lower condenser circuits: the liquid line leaves at 35 °C against a 50 °C condensing saturation, 15 K of subcooling. Both operands hold from t=0, so delayOnInit puts the alarm one alarm_delay in.
subcooling_exactly_at_the_bandThreshold edge: 47.0 − 35.0 is exactly subcooling_high_band (12.0 K), and both operands are binary-exact, so this is a true boundary pin. The comparison is strict, so a unit sitting precisely on the band reads healthy.
subcooling_just_above_the_bandThreshold edge from the other side: 47.2 − 35.0 is 12.2 K, two tenths past the band, and the alarm matures after alarm_delay.
two_phase_liquid_line_reads_healthyThe state the source’s 0.5 K TXV control-limit test names: 0.2 K of subcooling, a liquid line that is effectively two-phase. This card exposes no evaluability flag for it because the verdict is sound — a unit with no liquid seal is many things, none of them overcharged.
negative_subcooling_reads_healthySensor pathology: the liquid line reads 1.5 K hotter than the condensing saturation, which is physically impossible on a healthy circuit and usually means a P-T lookup running the wrong refrigerant or a swapped pair of sensors. The subtraction goes negative and the rule stays silent rather than saturating in either direction.
transient_high_subcooling_never_alarmsSubcooling jumps to 15 K for 1200 s — a load step, or a head-pressure control stroke that briefly holds liquid in the condenser — and recovers 600 s short of alarm_delay. The timer resets on the recovery and nothing is reported.
delay_runs_from_the_crossing_not_from_loadThe unit starts healthy at 6 K and crosses the band at t = 1200 s, holding 13 K thereafter. The alarm lands at 1200 + alarm_delay, not at alarm_delay — the persistence window is measured from the crossing.
alarm_clears_after_charge_recoveredRecovery: 15 K of subcooling alarms at 1800 s, the technician recovers the excess charge at t = 5400 s (playbook step 2), and the liquid line returns to 6 K of subcooling. yFault drops on that tick — TrueDelay only delays the rising edge.
vectors.json
{
  "schema": "cxf-library/vectors/v1",
  "clock": {
    "step_s": 300,
    "horizon_s": 9000
  },
  "scenarios": [
    {
      "name": "nominal_subcooling_healthy",
      "description": "A correctly charged unit: 45 \u00b0C condensing saturation against a 39 \u00b0C liquid line is 6 K of subcooling, near the ~5 K the source measured on its no-fault rig and half the shipped band.",
      "inputs": {
        "cond_sat_temp": 45.0,
        "liquid_temp": 39.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 9000,
          "equals": false
        }
      ]
    },
    {
      "name": "hot_day_same_subcooling_healthy",
      "description": "Both temperatures 10 K higher \u2014 a hot afternoon, or a condenser starved of air \u2014 and the same 6 K of subcooling. The rule reads the difference, not the level, so nothing here is a charge verdict. Pairs with overcharged_liquid_backs_up: wire the operands backwards and both flip.",
      "inputs": {
        "cond_sat_temp": 55.0,
        "liquid_temp": 49.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 9000,
          "equals": false
        }
      ]
    },
    {
      "name": "overcharged_liquid_backs_up",
      "description": "Excess refrigerant floods the lower condenser circuits: the liquid line leaves at 35 \u00b0C against a 50 \u00b0C condensing saturation, 15 K of subcooling. Both operands hold from t=0, so delayOnInit puts the alarm one alarm_delay in.",
      "inputs": {
        "cond_sat_temp": 50.0,
        "liquid_temp": 35.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 2100,
          "to_s": 9000,
          "equals": true
        }
      ]
    },
    {
      "name": "subcooling_exactly_at_the_band",
      "description": "Threshold edge: 47.0 \u2212 35.0 is exactly subcooling_high_band (12.0 K), and both operands are binary-exact, so this is a true boundary pin. The comparison is strict, so a unit sitting precisely on the band reads healthy.",
      "inputs": {
        "cond_sat_temp": 47.0,
        "liquid_temp": 35.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 9000,
          "equals": false
        }
      ]
    },
    {
      "name": "subcooling_just_above_the_band",
      "description": "Threshold edge from the other side: 47.2 \u2212 35.0 is 12.2 K, two tenths past the band, and the alarm matures after alarm_delay.",
      "inputs": {
        "cond_sat_temp": 47.2,
        "liquid_temp": 35.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 2100,
          "to_s": 9000,
          "equals": true
        }
      ]
    },
    {
      "name": "two_phase_liquid_line_reads_healthy",
      "description": "The state the source's 0.5 K TXV control-limit test names: 0.2 K of subcooling, a liquid line that is effectively two-phase. This card exposes no evaluability flag for it because the verdict is sound \u2014 a unit with no liquid seal is many things, none of them overcharged.",
      "inputs": {
        "cond_sat_temp": 45.0,
        "liquid_temp": 44.8
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 9000,
          "equals": false
        }
      ]
    },
    {
      "name": "negative_subcooling_reads_healthy",
      "description": "Sensor pathology: the liquid line reads 1.5 K hotter than the condensing saturation, which is physically impossible on a healthy circuit and usually means a P-T lookup running the wrong refrigerant or a swapped pair of sensors. The subtraction goes negative and the rule stays silent rather than saturating in either direction.",
      "inputs": {
        "cond_sat_temp": 45.0,
        "liquid_temp": 46.5
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 9000,
          "equals": false
        }
      ]
    },
    {
      "name": "transient_high_subcooling_never_alarms",
      "description": "Subcooling jumps to 15 K for 1200 s \u2014 a load step, or a head-pressure control stroke that briefly holds liquid in the condenser \u2014 and recovers 600 s short of alarm_delay. The timer resets on the recovery and nothing is reported.",
      "inputs": {
        "cond_sat_temp": 45.0,
        "liquid_temp": [
          {
            "t": 0,
            "value": 39.0
          },
          {
            "t": 600,
            "value": 30.0
          },
          {
            "t": 1800,
            "value": 39.0
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 9000,
          "equals": false
        }
      ]
    },
    {
      "name": "delay_runs_from_the_crossing_not_from_load",
      "description": "The unit starts healthy at 6 K and crosses the band at t = 1200 s, holding 13 K thereafter. The alarm lands at 1200 + alarm_delay, not at alarm_delay \u2014 the persistence window is measured from the crossing.",
      "inputs": {
        "cond_sat_temp": 45.0,
        "liquid_temp": [
          {
            "t": 0,
            "value": 39.0
          },
          {
            "t": 1200,
            "value": 32.0
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2700,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 3300,
          "to_s": 9000,
          "equals": true
        }
      ]
    },
    {
      "name": "alarm_clears_after_charge_recovered",
      "description": "Recovery: 15 K of subcooling alarms at 1800 s, the technician recovers the excess charge at t = 5400 s (playbook step 2), and the liquid line returns to 6 K of subcooling. yFault drops on that tick \u2014 TrueDelay only delays the rising edge.",
      "inputs": {
        "cond_sat_temp": 50.0,
        "liquid_temp": [
          {
            "t": 0,
            "value": 35.0
          },
          {
            "t": 5400,
            "value": 44.0
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 2100,
          "to_s": 5100,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 5700,
          "to_s": 9000,
          "equals": false
        }
      ]
    }
  ]
}