DEV Community

Cover image for I Built a Carbon Calculator Where the Emission Factor Doesn't Exist
Jeremiah Say
Jeremiah Say

Posted on

I Built a Carbon Calculator Where the Emission Factor Doesn't Exist

Most emissions calculators are a lookup table with a UI bolted on. You pick "diesel," the code fetches 2.51 kgCO₂e/litre from a factor table, multiplies by your input, done. I've built about a thousand of these. The refrigerant one broke the pattern, and the reason it broke is a small idea that's worth sharing if you ever model physical systems in code.

For refrigerant leakage, there is no emission factor to look up. The factor is the global warming potential of the gas itself.

Let me explain what that means and why it changed how I structured the data layer.

The 30-second domain primer

When a chiller or supermarket fridge leaks, the refrigerant gas escapes to atmosphere. Different gases trap different amounts of heat. We normalise that to GWP — Global Warming Potential — the warming caused by 1 kg of the gas relative to 1 kg of CO₂ over 100 years.

CO₂ has a GWP of 1. R-410A, a common air-conditioning refrigerant, has a GWP of 2,256. So one leaked kilogram of R-410A is climate-equivalent to 2,256 kg of CO₂ [source: IPCC AR6, WG1 Ch7]. That multiplier is the entire story.

The calculation is almost insultingly simple:

function refrigerantEmissions({ chargeKg, leakRatePct, gwp100 }) {
  const leakedMass = chargeKg * (leakRatePct / 100); // kg of gas
  return leakedMass * gwp100;                         // kg CO₂e
}
Enter fullscreen mode Exit fullscreen mode

No factor table. No unit conversion gymnastics. Three numbers. So why did this one take longer to build than the combustion calculators that do need factor tables?

The actual hard part: the factor is a moving target with two valid values at once

Here's the trap. The IPCC publishes GWP values, and it revises them. AR5 (2014) said R-143a had a GWP of 4,470. AR6 (2021) says 5,810. That's a +30% shift in the answer for the exact same physical leak [source: IPCC AR5 / AR6 WG1].

And — this is the part that breaks naive data modelling — both values are still legally correct right now, depending on who's asking:

  • EU CSRD reporting → AR6, mandatory.
  • A 2019 baseline you set under AR5 → still AR5 until you rebaseline.

So the factor isn't a constant. It's a function of (gas, assessment_report). My first instinct — refrigerants["R-410A"].gwp — was wrong, because there is no single .gwp. There are two, and the correct one is a property of the user's reporting context, not the gas.

The data shape that actually works:

const refrigerants = {
  "R-410A": {
    label: "R-410A — Building / split AC (HFC blend)",
    gwp100: { ar6: 2256, ar5: 2088 },  // both live, caller picks
    fgasThreshold: true                 // GWP ≥ 2,500? (regulatory flag)
  },
  // ...50 more
};
Enter fullscreen mode Exit fullscreen mode

The lesson generalises well beyond carbon: when your source of truth revises itself but the old version stays valid, the version is part of the key, not metadata you can flatten away. I've since refactored other calculators the same way.

The drift is not noise — and that's a UX decision, not a data one

For most emission categories, AR5 vs AR6 is sub-1% — genuinely ignorable. For refrigerants it isn't:

Refrigerant AR5 GWP-100 AR6 GWP-100 Drift
R-32 675 771 +14.2%
R-134a 1,430 1,526 +6.7%
R-143a 4,470 5,810 +30.0%
R-410A 2,088 2,256 +8.0%
R-404A 3,922 4,170 +6.3%

Source: IPCC AR5 (WG1 Ch8) and AR6 (WG1 Ch7) GWP-100 values.

A naive build just exposes a toggle and lets the number silently jump 30%. I think that's a quiet way to mislead a user who doesn't know the chemistry changed under them. So the engine computes the drift and surfaces it inline whenever |drift| ≥ 3% — the user sees "switching to AR6 adds X% on your gas mix" before they export anything.

That's a small rule, but it encodes a position I'll defend: a calculator that can produce two different right answers has a duty to tell you why they differ. Hiding it behind a toggle is technically correct and practically dishonest.

The leak rate is a default, and defaults are a trust contract

The other input — annual leak rate — comes from IPCC Tier 1 defaults by equipment class (Table 7.9 of the 2019 Refinement). Transport refrigeration's published range is 15% to 50%. That's enormous. I store the mid-range as the preset but treat it as explicitly provisional:

const equipmentDefaults = {
  "transport-refrigeration": { preset: 25, range: [15, 50] },
  "supermarket":             { preset: 20, range: [10, 35] },
  "chillers":                { preset: 8,  range: [5, 15]  },
  // ...
};
Enter fullscreen mode Exit fullscreen mode

The engine never lets a default masquerade as a measurement. A measured, documented rate always overrides the preset, and the audit export records which basis was used. If you're building anything where users will be audited on your output, the single most valuable feature is provenance on every number — what it was, where it came from, and whether it was assumed or measured. That's worth more than any chart.

Where this lives

The full thing — all 51 refrigerants, the AR5/AR6 engine, the F-Gas regulatory flags, and an audit-mode export that hashes every result back to its source — is live on GreenCalculus. If you want to see the moving-target factor model in the wild, the Scope 1 refrigerant leakage calculator on GreenCalculus is the cleanest example of the pattern.

If you model physical or regulatory systems in code, the takeaway I'd leave you with: the hard part is rarely the arithmetic. It's that your authoritative source is itself versioned, sometimes contradictory, and always more nuanced than a single number. Build for that from day one and the refactor never comes.

Top comments (1)

Collapse
 
huaian666 profile image
HUAICHUAN

Building a carbon calculator where the emission factor literally does not exist yet is a fascinating engineering challenge. Most calculators rely on established databases, but what happens when you are trying to calculate emissions for something so new or niche that no one has measured it yet? Your approach to handling missing data — the interpolation strategies, the transparency about uncertainty ranges, and the honest UI that shows users when an estimate is an estimate — demonstrates excellent scientific and product judgment. The decision to not just fake a number but to be upfront about the gap in knowledge builds more trust than a false sense of precision. This is the kind of tool that could actually influence how people think about their environmental impact, precisely because it is honest about what we know and what we do not. Really thoughtful project.

By the way, if you have time, check out the app I recently developed! Like Code is an iOS app that runs HTML directly on your phone. You can paste any HTML, run it full screen, save offline, and edit on device. It is on the App Store, feel free to check it out! Thank you very much!