DEV Community

HVAC Fitter
HVAC Fitter

Posted on

Frost or a Frozen Heat Pump? Writing the Triage as a Small Python Function

The first cold mornings of November bring the same question every year: the outdoor unit of a heat pump is white with frost. Is that normal, or is it broken?

The answer is a short decision list, and a decision list is easy to write as code. This post turns the checks from HVAC Fitter's heat pump iced up guide into a function you can read top to bottom. It's a toy for learning, not a repair tool.

The facts the code encodes

From the guide (updated Oct 22, 2026):

  • Light white frost is normal when it's near freezing outside. Moisture sticks to the cold coil, and the defrost cycle clears it.
  • Thick ice or a solid block is not normal. Airflow stops and heating drops fast.
  • During defrost, the unit briefly runs in cooling mode and the outdoor fan usually stops. So a still fan during defrost is expected. A fan that won't spin while heating is not.
  • A fan that hums but won't spin often points to a bad capacitor rather than a dead motor.
  • Ice that stays for days points to the defrost control board.
  • A clogged indoor filter can disrupt the cycle too. Most filters need changing every 1 to 3 months.
  • Refrigerant work needs an EPA Section 608 certified tech. That's always the last branch.

The function

from dataclasses import dataclass

@dataclass
class Observation:
    ice: str                   # "none", "light frost", "thick ice" or "solid block"
    hours_iced: float          # how long the ice has stayed
    outdoor_fan_spinning: bool # while the unit is heating (not during defrost)
    fan_hums: bool             # you hear a hum but the blades don't turn
    filter_age_months: float   # months since the indoor filter was changed

def triage(o: Observation) -> list[str]:
    steps = []
    if o.ice in ("none", "light frost"):
        return ["Normal. Light frost is standard near freezing; the defrost cycle clears it."]
    if o.filter_age_months >= 3:
        steps.append("Replace the indoor air filter (most need it every 1 to 3 months).")
    if not o.outdoor_fan_spinning:
        if o.fan_hums:
            steps.append("Fan hums but won't spin: suspect the run capacitor before the motor.")
        else:
            steps.append("Fan dead: check the motor; a replacement must match HP, volts and rotation.")
    if o.hours_iced >= 48:
        steps.append("Ice that stays for days: suspect the defrost control board.")
    steps.append("Still iced after that, or any refrigerant question: call a licensed tech.")
    return steps

if __name__ == "__main__":
    cases = {
        "frosty morning": Observation("light frost", 1, True, False, 1),
        "old filter":     Observation("thick ice", 6, True, False, 5),
        "humming fan":    Observation("thick ice", 30, False, True, 1),
        "solid block":    Observation("solid block", 48, True, False, 2),
    }
    for name, obs in cases.items():
        print(f"{name}:")
        for s in triage(obs):
            print("  -", s)
Enter fullscreen mode Exit fullscreen mode

Output

frosty morning:
  - Normal. Light frost is standard near freezing; the defrost cycle clears it.
old filter:
  - Replace the indoor air filter (most need it every 1 to 3 months).
  - Still iced after that, or any refrigerant question: call a licensed tech.
humming fan:
  - Fan hums but won't spin: suspect the run capacitor before the motor.
  - Still iced after that, or any refrigerant question: call a licensed tech.
solid block:
  - Ice that stays for days: suspect the defrost control board.
  - Still iced after that, or any refrigerant question: call a licensed tech.
Enter fullscreen mode Exit fullscreen mode

Why the order matters

The checks run cheapest-first. A filter costs a few dollars and takes a minute, so it comes before anything electrical. The "call a licensed tech" line is never skipped. Every frozen case ends there if the easy fixes don't work.

Two things the code doesn't try to do:

  1. It doesn't tell you to open the unit. The guide's safety rules come first: power off at the breaker and the outdoor disconnect, test for power, and remember that a capacitor can hold a charge with the power off. If that isn't you, stop and call a pro.
  2. It doesn't name a part. If the fan hums, the next step is reading the capacitor label and matching it. See which capacitor do I need. A replacement fan motor must match horsepower, voltage and rotation.

Try extending it

  • Add a defrost_running flag so a still fan during defrost returns "normal".
  • Return objects with a cost and needs_pro field, then sort by them.
  • Write tests for the four cases above with pytest.

If you'd rather check with a person than a function, the full guide has the thaw steps and the part lists: hvacfitter.com/guides/heat-pump-iced-up.

Top comments (0)