Functional Weave
Code in Python

energy.carbon-emissions

kgCO2e from kWh or litres of a fuel, using the UK government GHG conversion factors for the year.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 17 tests, run in TypeScript, Python and Rust.

What it does

Emissions in CO2 equivalent (CO2e) from an amount of fuel or electricity:

kgCO2e = quantity x conversion factor

For example

  • carbon_emissions(electricity, 1,000,000, 2,024) → fuel electricity, unit kWh, year 2,024, scope 2, factor 20,705, grams 207,050 1,000 kWh of grid electricity in 2024 is 207.05 kg
  • carbon_emissions(electricity, 1,000,000, 2,026) → fuel electricity, unit kWh, year 2,026, scope 2, factor 13,096, grams 130,960 the same electricity in 2026 uses the 2026 factor
  • carbon_emissions(electricity, 1,000,000, 2,023) → fuel electricity, unit kWh, year 2,023, scope 2, factor 20,707, grams 207,070 2023 electricity at the five published decimals

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def carbon_emissions(fuel: str, quantity: int, year: int) -> CarbonEmissions
fuelstringelectricity, electricity-td, natural-gas, lpg, burning-oil, gas-oil, fuel-oil, diesel or petrol
quantityintin thousandths of the fuel's unit: watt-hours for kWh fuels, millilitres for litre fuels
yearintthe conversion factor set to use, 2023 to 2026: normally the year being reported
returnsCarbonEmissions

The type it declares, generated into your project

@dataclass(frozen=True)
class CarbonEmissions:
    """The emissions and the factor they came from, for an audit trail."""

    fuel: str
    #: kWh or litres
    unit: str
    year: int
    #: the GHG Protocol scope the factor belongs to: 1, 2 or 3
    scope: int
    #: kgCO2e per unit, in hundred-thousandths: 0.20705 kg/kWh is 20705
    factor: int
    #: emissions in grams of CO2e (kgCO2e to three decimals), rounded half-up
    grams: int

Your code names it in one line, in the file that uses it

from fune.energy.carbon_emissions import carbon_emissions  # energy.carbon-emissions@^1
impl/python.py · 28 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from .energy_carbon_emissions_data import CONVERSION_FACTORS  ← this capability’s own data, compiled from data/conversion-factors.json into the same file by fune build
from .energy_carbon_emissions_types import CarbonEmissions
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune

# quantity (thousandths of a unit) x factor (hundred-thousandths of a kg)
# / 100000 is grams.
DIVISOR = 100_000
MAX_SAFE = 9007199254740991


def carbon_emissions(fuel: str, quantity: int, year: int) -> CarbonEmissions:
    """kgCO2e, as whole grams, for an amount of a fuel, from the UK government
    conversion factor set for ``year``.
    """
    if not isinstance(quantity, int) or isinstance(quantity, bool) or quantity < 0:
        raise ValueError(
            "quantity must be a whole number of thousandths of a unit, 0 or more, received %s" % (quantity,)
        )
    if not any(f.fuel == fuel for f in CONVERSION_FACTORS):
        raise ValueError('unknown fuel "%s"' % (fuel,))
    row = next((f for f in CONVERSION_FACTORS if f.fuel == fuel and f.year == year), None)
    if row is None:
        raise ValueError("no conversion factor for %s in %s" % (fuel, year))
    whole, rest = divmod(quantity, DIVISOR)
    grams = whole * row.factor + round_div(rest * row.factor, DIVISOR, "half-up")
    if grams > MAX_SAFE:
        raise ValueError("emissions too large to calculate exactly")
    return CarbonEmissions(fuel=fuel, unit=row.unit, year=year, scope=row.scope, factor=row.factor, grams=grams)

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add energy.carbon-emissions
Download for Python energy.carbon-emissions-1.0.1-python.fune · 18,718 bytes sha256 442eec95cfbee45169f51babdbb57455056e3e11ffd22636b7d529e8a479a407

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./energy.carbon-emissions-1.0.1-python.fune, or fetch it from a terminal with fune pull energy.carbon-emissions@1.0.1:python.

The whole function, every language, is one file too: energy.carbon-emissions-1.0.1.fune, 22,579 bytes, sha256 2c79961b918cd1eb475f3c88476437760af9a41cf4c3a20b71b338f11d0e9451. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before energy.carbon-emissions

after — your function gets the result and the arguments, and returns the final result.

# fune: after energy.carbon-emissions

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace math.round-div in energy.carbon-emissions

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show energy.carbon-emissions --steps.

# fune: step energy.carbon-emissions after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
1,000 kWh of grid electricity in 2024 is 207.05 kg electricity, 1,000,000, 2,024 → fuel electricity, unit kWh, year 2,024, scope 2, factor 20,705, grams 207,050
the same electricity in 2026 uses the 2026 factor electricity, 1,000,000, 2,026 → fuel electricity, unit kWh, year 2,026, scope 2, factor 13,096, grams 130,960
2023 electricity at the five published decimals electricity, 1,000,000, 2,023 → fuel electricity, unit kWh, year 2,023, scope 2, factor 20,707, grams 207,070
transmission and distribution losses for the same 1,000 kWh in 2025 electricity-td, 1,000,000, 2,025 → fuel electricity-td, unit kWh, year 2,025, scope 3, factor 1,853, grams 18,530
a year of household gas, 12,000 kWh in 2025 natural-gas, 12,000,000, 2,025 → fuel natural-gas, unit kWh, year 2,025, scope 1, factor 18,296, grams 2,195,520
a 50 litre diesel fill in 2026 diesel, 50,000, 2,026 → fuel diesel, unit litres, year 2,026, scope 1, factor 258,354, grams 129,177
one litre of petrol in 2023 rounds 2097.47 g down petrol, 1,000, 2,023 → fuel petrol, unit litres, year 2,023, scope 1, factor 209,747, grams 2,097
1,000 litres of heating oil in 2024 burning-oil, 1,000,000, 2,024 → fuel burning-oil, unit litres, year 2,024, scope 1, factor 254,015, grams 2,540,150
LPG by the litre lpg, 2,500, 2,025 → fuel lpg, unit litres, year 2,025, scope 1, factor 155,713, grams 3,893
an exact half gram rounds up: 10 kWh at 0.20705 is 2070.5 g electricity, 10,000, 2,024 → fuel electricity, unit kWh, year 2,024, scope 2, factor 20,705, grams 2,071
Show the other 7 tests
CaseArgumentsExpected
nothing burned is no emissions gas-oil, 0, 2,026 → fuel gas-oil, unit litres, year 2,026, scope 1, factor 275,541, grams 0
a gigawatt-hour stays exact past 2^53 in the intermediate product electricity, 1,000,000,000,000, 2,024 → fuel electricity, unit kWh, year 2,024, scope 2, factor 20,705, grams 207,050,000,000
fuel oil, 3.17492 kg a litre fuel-oil, 1, 2,026 → fuel fuel-oil, unit litres, year 2,026, scope 1, factor 317,492, grams 3
a year with no factor set is an error, not another year's factor electricity, 1,000, 2,022 → error: no conversion factor for electricity in 2022
an unknown fuel is refused coal, 1,000, 2,025 → error: unknown fuel "coal"
a negative quantity is refused diesel, -1,000, 2,025 → error: quantity must be a whole number of thousandths of a unit
a fractional quantity is refused diesel, 1,000.5, 2,025 → error: quantity must be a whole number of thousandths of a unit

More from the author

using the UK government's greenhouse gas conversion factors for company reporting (published each June by DESNZ, previously BEIS), for the 2023, 2024, 2025 and 2026 sets. They are the factors Streamlined Energy and Carbon Reporting (SECR) and most UK carbon footprints use.

## Fuels

| fuel | unit | factor used | scope | |---|---|---|---| | `electricity` | kWh | UK electricity, "Electricity generated" | 2 | | `electricity-td` | kWh | UK electricity transmission and distribution losses | 3 | | `natural-gas` | kWh | Natural gas, kWh (Gross CV) | 1 | | `lpg` | litres | LPG, litres | 1 | | `burning-oil` | litres | Burning oil (kerosene, heating oil), litres | 1 | | `gas-oil` | litres | Gas oil (red diesel), litres | 1 | | `fuel-oil` | litres | Fuel oil, litres | 1 | | `diesel` | litres | Diesel (average biofuel blend), litres | 1 | | `petrol` | litres | Petrol (average biofuel blend), litres | 1 |

Gas bills are in kWh on gross calorific value (see `energy.gas-kwh`), which is why the gas factor is the Gross CV one. A full electricity footprint is `electricity` plus `electricity-td` for the same kWh.

## Units and rounding

- Quantities are integers in **thousandths** of the fuel's unit (watt-hours, millilitres), so fractional litres and kWh are exact. - Factors are integers in **hundred-thousandths of a kg** per unit: the five decimal places DESNZ publishes (0.20705 kg/kWh is `20705`). The 2023 flat file carries more digits than the published tables show (0.2070742...); those were rounded half-up to the five places the condensed set displays. - The result is **grams of CO2e**, rounded once, half-up (kgCO2e to three decimals). The product is split so it stays exact for any quantity whose answer fits an exact integer.

## Why the year is an argument, and there are no effective dates

A factor set is chosen by reporting year, not by the date the fuel was burned: a company reporting its 2025 financial year uses the 2025 set (or states which it used). Older sets stay valid for restating older years, so they are never "no longer in force" and the table deliberately has no `effective` columns: a `history=current` build keeps every year. A year that is not in the table is an error rather than falling back to another year's factors.

## Sources

Department for Energy Security and Net Zero, "Greenhouse gas reporting: conversion factors", flat-format workbooks, rows "Fuels", "UK electricity" and "Transmission and distribution", column "kg CO2e":

- 2023: https://www.gov.uk/government/publications/greenhouse-gas-reporting-conversion-factors-2023 (ghg-conversion-factors-2023-flat-file-update.xlsx) - 2024: https://www.gov.uk/government/publications/greenhouse-gas-reporting-conversion-factors-2024 (ghg-conversion-factors-2024-FlatFormat_v1_1.xlsx) - 2025: https://www.gov.uk/government/publications/greenhouse-gas-reporting-conversion-factors-2025 (ghg-conversion-factors-2025-flat-format.xlsx) - 2026: https://www.gov.uk/government/publications/greenhouse-gas-reporting-conversion-factors-2026 (ghg-conversion-factors-2026-flat-format-revised.xlsx)

## Notices

Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).

1.0.1 adds its attribution notices (NOTICE). The code and the tests are unchanged.

Files

PathBytes
NOTICE186
README.md3,500
data/conversion-factors.json5,759
impl/python.py1,373
impl/rust.rs2,236
impl/typescript.ts1,461
vectors.json3,347