<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: HVAC Fitter</title>
    <description>The latest articles on DEV Community by HVAC Fitter (@hvacfitter).</description>
    <link>https://dev.to/hvacfitter</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4191228%2F2e138f53-2a0d-41fd-bb59-1dcbc67f329a.png</url>
      <title>DEV Community: HVAC Fitter</title>
      <link>https://dev.to/hvacfitter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hvacfitter"/>
    <language>en</language>
    <item>
      <title>Pilot Light Goes Out When You Let Go? A Small Script to Sort Thermocouple From Thermopile Problems</title>
      <dc:creator>HVAC Fitter</dc:creator>
      <pubDate>Tue, 10 Nov 2026 20:13:52 +0000</pubDate>
      <link>https://dev.to/hvacfitter/pilot-light-goes-out-when-you-let-go-a-small-script-to-sort-thermocouple-from-thermopile-problems-594m</link>
      <guid>https://dev.to/hvacfitter/pilot-light-goes-out-when-you-let-go-a-small-script-to-sort-thermocouple-from-thermopile-problems-594m</guid>
      <description>&lt;p&gt;Every fall, the same question shows up: "My pilot lights, but it goes out the second I let go of the button." This post walks through why that happens, then turns the checks into a tiny Python helper you can adapt. It's written by the HVAC Fitter team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gas safety first.&lt;/strong&gt; Turn the gas control knob and the manual supply valve off before you touch the pilot assembly, and let it cool. If you smell gas, don't flip switches or use a phone indoors. Leave and call your gas company from outside. Never bypass a safety device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the flame dies when you release the button
&lt;/h2&gt;

&lt;p&gt;Holding the pilot button forces the gas port open by hand. While you hold it, the pilot flame heats a sensor. That heat makes a small voltage, and the voltage powers a magnet in the gas valve. When you let go, the magnet is supposed to take over and keep the valve open.&lt;/p&gt;

&lt;p&gt;If the flame lights while you hold the button and dies when you let go, the valve isn't getting enough voltage. The usual cause is a weak, dirty or badly placed sensor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thermocouple or thermopile?
&lt;/h2&gt;

&lt;p&gt;These are not interchangeable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;thermocouple&lt;/strong&gt; is a single heat sensor that makes a tiny voltage, often around 30 mV. Its only job is holding the pilot safety magnet open. It usually screws into the valve with a threaded nut.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;thermopile&lt;/strong&gt; (or "pilot generator") is several thermocouples joined together. Many put out roughly 600 to 750 mV, enough to run a millivolt control circuit, like a wall thermostat on a unit with no house power. It often connects with spade terminals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the first question is always which one you have. The part number on the old sensor, or the model number on the appliance's rating plate, settles it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple things to check before buying anything
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Flame contact.&lt;/strong&gt; The pilot flame should wrap around the top part of the probe tip. A lazy or misaimed flame won't heat it enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soot or corrosion.&lt;/strong&gt; Heavy buildup on the probe can block heat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The connection at the valve.&lt;/strong&gt; A loose nut or terminal can drop the small voltage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tube length and routing.&lt;/strong&gt; A replacement must reach the valve without stretching or kinking.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Turning it into a helper script
&lt;/h2&gt;

&lt;p&gt;Here's a small, dependency-free script that turns those observations into next steps. It doesn't diagnose anything on its own. It only puts the checks above in order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="s2"&gt;"""Pilot-out triage helper: orders the checks for a pilot that won't stay lit.
Not a substitute for a licensed technician. Gas off before any inspection."""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pilot_triage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lights_while_held&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;dies_on_release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;sensor_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# "thermocouple", "thermopile" or "unknown"&lt;/span&gt;
                 &lt;span class="n"&gt;flame_hits_tip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;probe_dirty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;valve_connection_tight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;lights_while_held&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Pilot won't light at all: this isn't a sensor problem. "&lt;/span&gt;
                     &lt;span class="s2"&gt;"Check gas supply and the pilot orifice, or call a pro."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;dies_on_release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Pilot stays lit: the sensor is likely doing its job. Look elsewhere."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sensor_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Identify the sensor first (part number on the old one, "&lt;/span&gt;
                     &lt;span class="s2"&gt;"or the appliance model number). Thermocouples and "&lt;/span&gt;
                     &lt;span class="s2"&gt;"thermopiles are not interchangeable."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;flame_hits_tip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Re-aim or clean the pilot so the flame wraps the probe tip."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;probe_dirty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Clean soot or corrosion off the probe (gas off, cool)."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;valve_connection_tight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Snug the connection at the gas valve: a loose fit drops voltage."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fixable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Identify"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;fixable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"If everything looks right, the sensor is probably worn. "&lt;/span&gt;
                     &lt;span class="s2"&gt;"Replace it with the same type, connection and equal or longer length."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;pilot_triage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"thermocouple"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it with "lights, dies on release, thermocouple, flame on tip, clean, tight" prints one line: the sensor is probably worn, so replace it like for like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Matching a replacement
&lt;/h2&gt;

&lt;p&gt;When you do replace it, match three things: the type (thermocouple vs thermopile), the connection style (threaded nut vs spade terminals), and a tube that's the same length or a bit longer. Our &lt;a href="https://hvacfitter.com/guides/pilot-light-wont-stay-lit" rel="noopener noreferrer"&gt;pilot light troubleshooting guide&lt;/a&gt; goes deeper on each, with example part specs. If your furnace uses a hot surface igniter instead of a standing pilot, the &lt;a href="https://hvacfitter.com/guides/furnace-wont-ignite" rel="noopener noreferrer"&gt;furnace won't ignite guide&lt;/a&gt; is the better starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to call a pro
&lt;/h2&gt;

&lt;p&gt;Call a licensed technician if you smell gas at any point, if the pilot won't light at all, if a new sensor doesn't fix it, or if you're not comfortable working near gas. A gas valve that won't hold with a good sensor needs professional service.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;HVAC Fitter team. Not professional advice; follow your appliance manual and local codes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hvac</category>
      <category>python</category>
      <category>diy</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Contactor Chatter on a Heat Pump: What to Check Before You Order Parts</title>
      <dc:creator>HVAC Fitter</dc:creator>
      <pubDate>Fri, 06 Nov 2026 23:22:47 +0000</pubDate>
      <link>https://dev.to/hvacfitter/contactor-chatter-on-a-heat-pump-what-to-check-before-you-order-parts-31ml</link>
      <guid>https://dev.to/hvacfitter/contactor-chatter-on-a-heat-pump-what-to-check-before-you-order-parts-31ml</guid>
      <description>&lt;p&gt;&lt;em&gt;By the HVAC Fitter team. General information, not a substitute for your system's manual or a licensed tech when you cannot safely work on live power.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A heat pump that chatters is hard to ignore. The outdoor unit buzzes, the contactor armature chatters in and out, and the compressor may never settle into a clean start. The instinct is to order a new contactor. Sometimes that is right. Often it is not — and a new contactor that chatters for the same reason will fail just as fast.&lt;/p&gt;

&lt;p&gt;Before buying parts: confirm supply under load, check the lugs, inspect the coil circuit, then look at the contacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "chatter" usually means
&lt;/h2&gt;

&lt;p&gt;A contactor is a relay. The thermostat (or control board) applies low-voltage coil power — usually 24 V on residential gear — and the coil pulls the armature closed so line voltage reaches the compressor and outdoor fan. Chatter is the armature bouncing because the magnetic pull cannot hold the contacts closed under load. That bounce arcs and pits the faces; pitted faces raise resistance and heat, which worsens the next cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Supply voltage under load
&lt;/h2&gt;

&lt;p&gt;A weak coil pull is often a voltage problem, not a bad coil. With the disconnect pulled and verified dead, measure safely once power is restored:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coil voltage with the unit calling.&lt;/strong&gt; Measure at the coil terminals while the thermostat is calling. Steady ~24 V (or the coil's rated voltage) is expected. A sag into the teens under call is a control-circuit or transformer problem — a new contactor will not fix it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line voltage under load.&lt;/strong&gt; Measure L1–L2 (or L1–N on single-pole setups) while the compressor tries to start. A deep sag under start amp draw points at a loose lug, undersized conductor, weak disconnect, or a utility-side issue upstream.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If either reading collapses only when the unit tries to start, chase the supply first. A new coil on a soft 18 V control circuit will chatter just like the old one.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Loose lug or terminal
&lt;/h2&gt;

&lt;p&gt;Loose power lugs are often misdiagnosed as a "bad contactor." With power locked out and verified, inspect every line and load lug for discoloration, heat marks, or a lug that turns with light torque. Check the outdoor disconnect and mounting screws — vibration loosens hardware over seasons. Confirm the coil control wires are tight and not oxidized. Retorque to the manufacturer's spec; if a lug or body is heat-damaged, replace it — do not reuse a melted lug on a new contactor.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Failing coil
&lt;/h2&gt;

&lt;p&gt;When coil voltage is solid under call and the lugs are tight, the coil itself may be weak. Compare the printed coil voltage rating to what the control circuit delivers. Watch the armature: a coil that pulls in weakly, then drops out as compressor inrush hits, is often end-of-life — after you have ruled out supply sag. A burnt winding or cracked bobbin is a clear replace.&lt;/p&gt;

&lt;p&gt;When you replace, match &lt;strong&gt;poles&lt;/strong&gt;, &lt;strong&gt;coil volts&lt;/strong&gt;, and &lt;strong&gt;amp rating&lt;/strong&gt; (same or higher FLA/inductive rating). Our &lt;a href="https://hvacfitter.com/guides/contactor-replacement-basics" rel="noopener noreferrer"&gt;contactor replacement basics&lt;/a&gt; cover those three numbers and the safe swap sequence — lock out, pull the disconnect, and test for power before you touch anything. The capacitor next to the contactor can hold a charge too.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Pitted or burnt contacts
&lt;/h2&gt;

&lt;p&gt;With power locked out, inspect the contact faces. Light gray oxidation is common. Deep pits, black cratering, or welded tips mean hard arcing — from chatter, a hard-starting compressor, or insects bridging the faces (ants are classic). Contacts stuck closed keep the outdoor unit running after the thermostat opens. Pitted contacts justify a replace — after you have ruled out low coil voltage and a loose line lug. Otherwise you are swapping the victim, not the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you order
&lt;/h2&gt;

&lt;p&gt;Work the list in order: (1) coil voltage under call — steady and on-spec? (2) line voltage under start — no deep sag? (3) lugs and control terminals — tight, no heat damage? (4) coil and contact faces — burnt, weak, or pitted?&lt;/p&gt;

&lt;p&gt;If steps 1–3 fail, fix those first. If they pass and the contacts or coil look bad, replace and match the three numbers on the old label. For the outdoor unit model plate (corner panel where the copper lines enter), see &lt;a href="https://hvacfitter.com/guides/find-your-model-number" rel="noopener noreferrer"&gt;how to find your model number&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Call a licensed HVAC tech if you cannot verify power is dead, if any conductor looks melted, or if a new contactor chatters or burns out quickly. A contactor that keeps failing often signals a larger electrical or compressor problem, not a parts lottery.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by the HVAC Fitter team (hvacfitter.com). Contactors carry line voltage — treat them that way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hvac</category>
      <category>troubleshooting</category>
      <category>hardware</category>
    </item>
    <item>
      <title>Frost or a Frozen Heat Pump? Writing the Triage as a Small Python Function</title>
      <dc:creator>HVAC Fitter</dc:creator>
      <pubDate>Thu, 05 Nov 2026 20:23:38 +0000</pubDate>
      <link>https://dev.to/hvacfitter/frost-or-a-frozen-heat-pump-writing-the-triage-as-a-small-python-function-465o</link>
      <guid>https://dev.to/hvacfitter/frost-or-a-frozen-heat-pump-writing-the-triage-as-a-small-python-function-465o</guid>
      <description>&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://hvacfitter.com/guides/heat-pump-iced-up" rel="noopener noreferrer"&gt;heat pump iced up guide&lt;/a&gt; into a function you can read top to bottom. It's a toy for learning, not a repair tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The facts the code encodes
&lt;/h2&gt;

&lt;p&gt;From the guide (updated Oct 22, 2026):&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The function
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;                   &lt;span class="c1"&gt;# "none", "light frost", "thick ice" or "solid block"
&lt;/span&gt;    &lt;span class="n"&gt;hours_iced&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;          &lt;span class="c1"&gt;# how long the ice has stayed
&lt;/span&gt;    &lt;span class="n"&gt;outdoor_fan_spinning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="c1"&gt;# while the unit is heating (not during defrost)
&lt;/span&gt;    &lt;span class="n"&gt;fan_hums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;             &lt;span class="c1"&gt;# you hear a hum but the blades don't turn
&lt;/span&gt;    &lt;span class="n"&gt;filter_age_months&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;   &lt;span class="c1"&gt;# months since the indoor filter was changed
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ice&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;light frost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Normal. Light frost is standard near freezing; the defrost cycle clears it.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter_age_months&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Replace the indoor air filter (most need it every 1 to 3 months).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outdoor_fan_spinning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fan_hums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fan hums but won&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t spin: suspect the run capacitor before the motor.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fan dead: check the motor; a replacement must match HP, volts and rotation.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hours_iced&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ice that stays for days: suspect the defrost control board.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Still iced after that, or any refrigerant question: call a licensed tech.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;steps&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frosty morning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;light frost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;old filter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thick ice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;humming fan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thick ice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solid block&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nc"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;solid block&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obs&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  -&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why the order matters
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Two things the code doesn't try to do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't tell you to open the unit.&lt;/strong&gt; The guide's safety rules come first: power off at the breaker &lt;em&gt;and&lt;/em&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't name a part.&lt;/strong&gt; If the fan hums, the next step is reading the capacitor label and matching it. See &lt;a href="https://hvacfitter.com/guides/which-capacitor-do-i-need" rel="noopener noreferrer"&gt;which capacitor do I need&lt;/a&gt;. A replacement fan motor must match horsepower, voltage and rotation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try extending it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;defrost_running&lt;/code&gt; flag so a still fan during defrost returns "normal".&lt;/li&gt;
&lt;li&gt;Return objects with a &lt;code&gt;cost&lt;/code&gt; and &lt;code&gt;needs_pro&lt;/code&gt; field, then sort by them.&lt;/li&gt;
&lt;li&gt;Write tests for the four cases above with &lt;code&gt;pytest&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd rather check with a person than a function, the full guide has the thaw steps and the part lists: &lt;a href="https://hvacfitter.com/guides/heat-pump-iced-up" rel="noopener noreferrer"&gt;hvacfitter.com/guides/heat-pump-iced-up&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>iot</category>
    </item>
    <item>
      <title>Why a furnace short cycles: a simple order of checks</title>
      <dc:creator>HVAC Fitter</dc:creator>
      <pubDate>Sat, 31 Oct 2026 16:15:59 +0000</pubDate>
      <link>https://dev.to/hvacfitter/why-a-furnace-short-cycles-a-simple-order-of-checks-522k</link>
      <guid>https://dev.to/hvacfitter/why-a-furnace-short-cycles-a-simple-order-of-checks-522k</guid>
      <description>&lt;p&gt;Short cycling means the furnace lights, runs for a few seconds or minutes, shuts off, and then tries again. The house never quite warms up, and the parts wear faster.&lt;/p&gt;

&lt;p&gt;Most cases come down to three things: airflow, a safety control, or the thermostat. Here is the order I would check them in, cheapest first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safety first.&lt;/strong&gt; Turn off power at the breaker or furnace switch before you open anything, and shut the gas valve before working near the burners. If you smell gas, leave the house and call the gas company from outside. Never bypass a limit switch, rollout switch or flame sensor.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The air filter
&lt;/h2&gt;

&lt;p&gt;A clogged filter starves the furnace of air. The heat exchanger gets too hot, the high limit switch opens, and the burners shut off. A new filter of the right size is the first and cheapest fix. If the limit keeps tripping after that, this guide walks through the airflow checks: &lt;a href="https://hvacfitter.com/guides/furnace-limit-switch-tripping" rel="noopener noreferrer"&gt;High limit switch keeps tripping&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The flame sensor
&lt;/h2&gt;

&lt;p&gt;If the burners light and then go out after a few seconds, the flame sensor may not be "seeing" the flame. A thin coat of residue is enough. Often a gentle cleaning fixes it; sometimes a new sensor is the better choice: &lt;a href="https://hvacfitter.com/guides/flame-sensor-clean-or-replace" rel="noopener noreferrer"&gt;Flame sensor: clean it or replace it?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The thermostat
&lt;/h2&gt;

&lt;p&gt;A thermostat in a drafty spot, near a supply vent, or with weak batteries can call for heat on and off. Check its spot and its settings before you buy parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to stop and call a pro
&lt;/h2&gt;

&lt;p&gt;If the filter is clean, the sensor is clean and the furnace still short cycles, or you see error codes you can't clear, call a licensed technician.&lt;/p&gt;

&lt;p&gt;The full walkthrough, with what each symptom looks like, is here: &lt;a href="https://hvacfitter.com/guides/furnace-short-cycling" rel="noopener noreferrer"&gt;Furnace short cycling: causes and fixes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by the HVAC Fitter team.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hvac</category>
      <category>diy</category>
      <category>home</category>
      <category>maintenance</category>
    </item>
    <item>
      <title>16x25x1 Isn't 16 by 25: Parsing and Validating HVAC Air Filter Sizes in Python</title>
      <dc:creator>HVAC Fitter</dc:creator>
      <pubDate>Mon, 26 Oct 2026 19:02:25 +0000</pubDate>
      <link>https://dev.to/hvacfitter/16x25x1-isnt-16-by-25-parsing-and-validating-hvac-air-filter-sizes-in-python-348j</link>
      <guid>https://dev.to/hvacfitter/16x25x1-isnt-16-by-25-parsing-and-validating-hvac-air-filter-sizes-in-python-348j</guid>
      <description>&lt;p&gt;Measure a common furnace filter with a tape and you'll get about 15.5 by 24.5 inches, three-quarters of an inch thick. Nobody sells that size. The label on the frame says &lt;strong&gt;16x25x1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That little gap between the label and the tape is a nice, small data-modeling problem. If you build anything that matches parts (a store search, an inventory sheet, a home-maintenance app), you'll hit it. Here's a tiny Python model of the rules.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by the HVAC Fitter team (hvacfitter.com). General information, not a replacement for your system's manual.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules, in plain English
&lt;/h2&gt;

&lt;p&gt;These come from our guide on &lt;a href="https://hvacfitter.com/guides/how-to-size-an-air-filter" rel="noopener noreferrer"&gt;how to size an air filter&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The size is printed on the frame of the old filter, like &lt;code&gt;16x25x1&lt;/code&gt;: length, width and depth, in inches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The order of the first two numbers doesn't matter.&lt;/strong&gt; A 16x25 and a 25x16 are the same filter.&lt;/li&gt;
&lt;li&gt;The label is the &lt;strong&gt;nominal&lt;/strong&gt; size, rounded up. A 16x25x1 really measures about 15.5 x 24.5 x 0.75 in, so it can slide into the slot.&lt;/li&gt;
&lt;li&gt;No label? Measure it and &lt;strong&gt;round each number up&lt;/strong&gt; to the next whole inch (or the next half inch for odd sizes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depth must match the slot.&lt;/strong&gt; Filters come 1, 2, 4 and 5 in deep. Never force a thicker one in, and don't use a thin one in a deep slot, because air goes around it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rules 2, 3 and 5 are exactly the kind of thing a naive string match gets wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing a size string
&lt;/h2&gt;

&lt;p&gt;People type sizes every way: &lt;code&gt;16x25x1&lt;/code&gt;, &lt;code&gt;25 X 16 x 1&lt;/code&gt;, &lt;code&gt;16*25*1&lt;/code&gt;. Normalize them into one canonical form, with the two face numbers sorted so order stops mattering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="n"&gt;VALID_DEPTHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;# inches; the common furnace filter depths
&lt;/span&gt;&lt;span class="n"&gt;SIZE_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^\s*(\d+(?:\.\d+)?)\s*[x×X*]\s*(\d+(?:\.\d+)?)\s*[x×X*]\s*(\d+(?:\.\d+)?)\s*$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilterSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;      &lt;span class="c1"&gt;# the smaller face dimension
&lt;/span&gt;    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;      &lt;span class="c1"&gt;# the larger face dimension
&lt;/span&gt;    &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FilterSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Parse &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16x25x1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25 X 16 x 1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16*25*1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; into one canonical size.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SIZE_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not a filter size: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 16x25 and 25x16 are the same filter
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;FilterSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sorting the face dimensions is the whole trick for rule 2. Now &lt;code&gt;parse_size("25 X 16 x 1")&lt;/code&gt; and &lt;code&gt;parse_size("16x25x1")&lt;/code&gt; are equal, because frozen dataclasses compare by value.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a tape measure to a label size
&lt;/h2&gt;

&lt;p&gt;Rule 3 and 4 in code: round each measured number up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;nominal_from_measured&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FilterSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Measured frame (e.g. 15.5 x 24.5 x 0.75) -&amp;gt; the size printed on the label.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;FilterSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt; &lt;span class="nf"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;- 1e-9&lt;/code&gt; keeps a measured &lt;code&gt;16.0&lt;/code&gt; from turning into 17 when float math leaves it at &lt;code&gt;16.0000000001&lt;/code&gt;. This version only rounds to whole inches. If you need the "next half inch for odd sizes" case, round to &lt;code&gt;math.ceil(n * 2) / 2&lt;/code&gt; and check the result against a list of sizes you actually stock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking a swap
&lt;/h2&gt;

&lt;p&gt;Most real bugs show up when someone swaps one filter for another. Compare the face sizes and the depth separately, so the error message says &lt;em&gt;what&lt;/em&gt; is wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return problems with replacing filter `old` by filter `new` (empty = fine).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;face size differs: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; vs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;too thick, it won&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t fit the slot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;
               &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;too thin, air will go around it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;depth &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in vs slot &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;why&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VALID_DEPTHS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unusual depth &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; in; check the slot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;25 X 16 x 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16x25x1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nominal_from_measured&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;15.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;24.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16x25x1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;check_swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16x25x1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;25x16x1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;check_swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16x25x4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16x25x1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;check_swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16x25x1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16x20x1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;['depth 1 in vs slot 4 in: too thin, air will go around it']
['face size differs: 16x25 vs 16x20']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first case is an easy mistake: the slot takes a 4-inch filter, someone grabs a 1-inch one because the face size matches. The face check passes, the depth check catches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just store the string?
&lt;/h2&gt;

&lt;p&gt;Because three different strings mean the same filter, and one string (&lt;code&gt;16x25x1&lt;/code&gt;) means a frame that measures something else. If your search index stores the raw label text, "25x16x1" returns nothing, and a customer with a tape measure never finds a match. Store the canonical form, and search on it.&lt;/p&gt;

&lt;p&gt;The same idea works for part numbers. A 4-inch filter marked CF100A1009 has a set of replacements, which is a lookup table problem, not a parsing problem. See the &lt;a href="https://hvacfitter.com/replacement/cf100a1009" rel="noopener noreferrer"&gt;CF100A1009 replacement page&lt;/a&gt; for one example.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few edge cases worth a test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;16 x 25 x 1"&lt;/code&gt; with an inch mark: strip &lt;code&gt;"&lt;/code&gt; and &lt;code&gt;in&lt;/code&gt; before matching.&lt;/li&gt;
&lt;li&gt;Metric labels: some filters print both. Keep the inch size as the key.&lt;/li&gt;
&lt;li&gt;MERV rating: it isn't part of the size. Many homes use MERV 8 to 11, and a higher MERV can slow airflow on some systems, so keep it as a separate field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole model: one regex, one sort, one round-up and two comparisons. If you want to see it used on real stock, our &lt;a href="https://hvacfitter.com/parts/air-filters" rel="noopener noreferrer"&gt;air filter listings&lt;/a&gt; are filtered by the numbers printed on the old frame.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Turn the system off before you swap a filter, and point the airflow arrow toward the furnace or air handler.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>datavalidation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Modeling "will this part fit?" as code: a replacement-rule validator for HVAC capacitors</title>
      <dc:creator>HVAC Fitter</dc:creator>
      <pubDate>Fri, 23 Oct 2026 17:05:31 +0000</pubDate>
      <link>https://dev.to/hvacfitter/modeling-will-this-part-fit-as-code-a-replacement-rule-validator-for-hvac-capacitors-4a5k</link>
      <guid>https://dev.to/hvacfitter/modeling-will-this-part-fit-as-code-a-replacement-rule-validator-for-hvac-capacitors-4a5k</guid>
      <description>&lt;p&gt;Most "does this part fit?" questions in e-commerce get answered with a lookup table: part A replaces part B because somebody typed it into a spreadsheet. That breaks down for commodity electrical parts, where thousands of part numbers are really just a handful of ratings. For those, it's better to encode the &lt;strong&gt;substitution rules&lt;/strong&gt; and check any candidate against the original part.&lt;/p&gt;

&lt;p&gt;Run capacitors in air conditioners and heat pumps are a clean example. The label on the old part tells you nearly everything you need, and the rules for a safe replacement are short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capacitance (µF, also printed MFD) must match exactly.&lt;/strong&gt; A dual-run label like &lt;code&gt;45 + 5 MFD&lt;/code&gt; means 45 µF for the compressor (HERM) and 5 µF for the fan. The tolerance printed next to it (±5% or ±6%) is the normal manufacturing range. It doesn't let you pick a different size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voltage must be the same or higher.&lt;/strong&gt; A 440 V part can replace a 370 V part. A 370 V part can never replace a 440 V one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type must match.&lt;/strong&gt; Dual run (three terminals: HERM, FAN, C), single run (two terminals) and start capacitors (rated as a range, like 88 to 108 µF) aren't interchangeable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shape doesn't matter electrically.&lt;/strong&gt; Round vs oval is only a fit-in-the-bracket question.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's that as a tiny validator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;            &lt;span class="c1"&gt;# "dual_run", "single_run" or "start"
&lt;/span&gt;    &lt;span class="n"&gt;uf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;            &lt;span class="c1"&gt;# (45, 5) for dual, (10,) for single
&lt;/span&gt;    &lt;span class="n"&gt;volts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;           &lt;span class="c1"&gt;# 370 or 440, typically
&lt;/span&gt;    &lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;round&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;can_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return a list of problems. An empty list means it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s a valid substitute.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type mismatch: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; vs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uf&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;µF must match exactly: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; vs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uf&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;volts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;volts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voltage too low: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;volts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; V &amp;lt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;volts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; V&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;note: different shape, check the mounting bracket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt;

&lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dual_run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;370&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;can_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dual_run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;440&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;   &lt;span class="c1"&gt;# []
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;can_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dual_run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;440&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;   &lt;span class="c1"&gt;# µF mismatch
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;can_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dual_run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;440&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="nc"&gt;Capacitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dual_run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;370&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;        &lt;span class="c1"&gt;# voltage too low
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few design notes from building this kind of check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Return reasons, not a boolean.&lt;/strong&gt; "Doesn't fit" is useless to a customer. "Voltage too low" teaches them the rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate hard failures from warnings.&lt;/strong&gt; Shape is a warning. µF and voltage are hard stops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse the label, not the part number.&lt;/strong&gt; Many brands print the same ratings under different numbers. Normalizing &lt;code&gt;45/5&lt;/code&gt;, &lt;code&gt;45+5&lt;/code&gt; and &lt;code&gt;45 + 5 MFD&lt;/code&gt; into one tuple does most of the work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never let tolerance leak into matching.&lt;/strong&gt; It's tempting to treat ±6% as a fuzzy-match window. Don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want the plain-English version of these rules (with label photos and the safety steps, because a capacitor can hold a charge with the power off), the &lt;a href="https://hvacfitter.com/guides/which-capacitor-do-i-need" rel="noopener noreferrer"&gt;HVAC Fitter capacitor guide&lt;/a&gt; is where I took them from. A worked example of one rating is the &lt;a href="https://hvacfitter.com/specs/45-5-mfd-dual-run-capacitor" rel="noopener noreferrer"&gt;45/5 MFD dual run capacitor spec page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The same pattern works for contactors (coil voltage and amps), transformers (VA and volts) and air filters (nominal size). Encode the rule once and every new part number is just data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Safety note: this is about data modeling. If you're replacing a capacitor yourself, turn off power at the breaker and the outdoor disconnect and discharge the capacitor first, or call a licensed HVAC technician.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>ecommerce</category>
    </item>
  </channel>
</rss>
