energy.carbon-emissions
kgCO2e from kWh or litres of a fuel, using the UK government GHG conversion factors for the year.
1.0.0 (not the latest) · 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 kgcarbon_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 factorcarbon_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.
pub fn carbon_emissions(fuel: &str, quantity: i64, year: i64) -> CarbonEmissions
| fuel | string | electricity, electricity-td, natural-gas, lpg, burning-oil, gas-oil, fuel-oil, diesel or petrol |
| quantity | int | in thousandths of the fuel's unit: watt-hours for kWh fuels, millilitres for litre fuels |
| year | int | the conversion factor set to use, 2023 to 2026: normally the year being reported |
| returns | CarbonEmissions |
The type it declares, generated into your project
/// The emissions and the factor they came from, for an audit trail.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CarbonEmissions {
pub fuel: String,
/// kWh or litres
pub unit: String,
pub year: i64,
/// the GHG Protocol scope the factor belongs to: 1, 2 or 3
pub scope: i64,
/// kgCO2e per unit, in hundred-thousandths: 0.20705 kg/kWh is 20705
pub factor: i64,
/// emissions in grams of CO2e (kgCO2e to three decimals), rounded half-up
pub grams: i64,
}
Your code names it in one line, in the file that uses it
fune!(energy.carbon-emissions@^1); // then call carbon_emissions(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::energy_carbon_emissions_data::CONVERSION_FACTORS; ← this capability’s own data, compiled from data/conversion-factors.json into the same file by fune build
use super::math_round_div::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.
const DIVISOR: i64 = 100_000;
const MAX_SAFE: i128 = 9_007_199_254_740_991;
/// kgCO2e, as whole grams, for an amount of a fuel, from the UK government
/// conversion factor set for `year`.
///
/// # Panics
/// Panics on a negative quantity, an unknown fuel, a year with no factor set,
/// or a result too large to be exact in every language.
pub fn carbon_emissions(fuel: &str, quantity: i64, year: i64) -> CarbonEmissions {
if quantity < 0 {
panic!(
"quantity must be a whole number of thousandths of a unit, 0 or more, received {}",
quantity
);
}
if !CONVERSION_FACTORS.iter().any(|f| f.fuel == fuel) {
panic!("unknown fuel \"{}\"", fuel);
}
let row = match CONVERSION_FACTORS.iter().find(|f| f.fuel == fuel && f.year == year) {
Some(row) => row,
None => panic!("no conversion factor for {} in {}", fuel, year),
};
let whole = quantity / DIVISOR;
let rest = quantity % DIVISOR;
let grams = whole as i128 * row.factor as i128 + round_div(rest * row.factor, DIVISOR, "half-up") as i128;
if grams > MAX_SAFE {
panic!("emissions too large to calculate exactly");
}
CarbonEmissions {
fuel: fuel.to_string(),
unit: row.unit.to_string(),
year,
scope: row.scope,
factor: row.factor,
grams: grams as i64,
}
}
pub fn carbon_emissions_to_value(e: &CarbonEmissions) -> Value {
Value::obj(vec![
("fuel", Value::str(&e.fuel)),
("unit", Value::str(&e.unit)),
("year", Value::Int(e.year)),
("scope", Value::Int(e.scope)),
("factor", Value::Int(e.factor)),
("grams", Value::Int(e.grams)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
if let Value::Float(f) = &args[1] {
panic!("quantity must be a whole number of thousandths of a unit, 0 or more, received {}", f);
}
carbon_emissions_to_value(&carbon_emissions(args[0].as_str(), args[1].as_i64(), args[2].as_i64()))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add energy.carbon-emissions
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./energy.carbon-emissions-1.0.0-rust.fune, or fetch it from a terminal with fune pull energy.carbon-emissions@1.0.0:rust.
The whole function, every language, is one file too: energy.carbon-emissions-1.0.0.fune, 22,106 bytes, sha256 bae07b09df8f0ef3bd4ff5ecfd0029294f6be101f8709fde8d1a549ce45c75aa. 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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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)
Files
| Path | Bytes |
|---|---|
| README.md | 3,242 |
| data/conversion-factors.json | 5,759 |
| impl/python.py | 1,373 |
| impl/rust.rs | 2,236 |
| impl/typescript.ts | 1,461 |
| vectors.json | 3,347 |