energy.price-cap
Ofgem energy price cap unit rate and daily standing charge for a fuel and payment method on a date (GB average).
1.0.2 · published 2026-10-03 by charlie · Anterra
Pinned by 19 tests, run in TypeScript, Python and Rust.
What it does
The Ofgem default tariff cap ("energy price cap") in force on a date: the unit rate per kWh and the daily standing charge a supplier may charge a household on a standard variable tariff, for electricity or gas.
priceCap("electricity", "direct-debit", "2026-05-01")
-> unitRate 24670 (24.67p/kWh), standingCharge 57210 (57.21p/day), vatBasisPoints 500
For example
price_cap(electricity, direct-debit, 2026-05-01)→ fuel electricity, payment method direct-debit, region GB, unit rate 24,670, standing charge 57,210, vat basis points 5%, valid from 2026-04-01, valid to 2026-06-30 electricity in May 2026: 24.67p/kWh and 57.21p a dayprice_cap(gas, direct-debit, 2026-05-01)→ fuel gas, payment method direct-debit, region GB, unit rate 5,740, standing charge 29,090, vat basis points 5%, valid from 2026-04-01, valid to 2026-06-30 gas in May 2026: 5.74p/kWh and 29.09p a dayprice_cap(electricity, direct-debit, 2025-04-01)→ fuel electricity, payment method direct-debit, region GB, unit rate 27,030, standing charge 53,800, vat basis points 5%, valid from 2025-04-01, valid to 2025-06-30 the first day of a cap period takes the new cap
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 price_cap(fuel: &str, payment_method: &str, on_date: &str) -> PriceCap
| fuel | EnergyFuel | electricity (single-rate) or gas |
| payment_method | PaymentMethod | direct-debit is the only method this version carries figures for |
| on_date | date | the day of supply |
| returns | PriceCap |
The types it declares, generated into your project
// EnergyFuel is a string in Rust, one of: "electricity", "gas".
// Parameters take it as &str and results hold it as String.
// PaymentMethod is a string in Rust, one of: "direct-debit", "standard-credit", "prepayment".
// Parameters take it as &str and results hold it as String.
/// The capped rates for one cap period, as Ofgem publishes them.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PriceCap {
pub fuel: String,
pub payment_method: String,
/// GB: the England, Scotland and Wales average
pub region: String,
/// thousandths of a penny per kWh: 24.67p is 24670
pub unit_rate: i64,
/// thousandths of a penny per day: 57.21p is 57210
pub standing_charge: i64,
/// VAT included in both figures: 500, or 0 where none applied
pub vat_basis_points: i64,
/// first day of the cap period
pub valid_from: String,
/// last day of the cap period
pub valid_to: String,
}
Your code names it in one line, in the file that uses it
fune!(energy.price-cap@^1); // then call price_cap(…)
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_price_cap_data::{PRICE_CAP, PRICE_CAP_HISTORY}; ← this capability’s own data, compiled from data/price-cap.json into the same file by fune build
fn is_iso_date(value: &str) -> bool {
let bytes = value.as_bytes();
bytes.len() == 10
&& bytes[4] == b'-'
&& bytes[7] == b'-'
&& bytes.iter().enumerate().all(|(i, b)| i == 4 || i == 7 || b.is_ascii_digit())
}
/// The Ofgem price cap unit rate and standing charge in force on `on_date`.
///
/// # Panics
/// Panics on a malformed date, an unknown fuel or payment method, a payment
/// method with no figures, or a date outside every period carried.
pub fn price_cap(fuel: &str, payment_method: &str, on_date: &str) -> PriceCap {
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
if fuel != "electricity" && fuel != "gas" {
panic!("unknown fuel \"{}\": expected electricity or gas", fuel);
}
if !["direct-debit", "standard-credit", "prepayment"].contains(&payment_method) {
panic!(
"unknown payment method \"{}\": expected direct-debit, standard-credit or prepayment",
payment_method
);
}
let rows: Vec<_> = PRICE_CAP
.iter()
.filter(|r| r.fuel == fuel && r.payment_method == payment_method)
.collect();
if rows.is_empty() {
panic!(
"no price cap figures for {} paid by {}: this version carries GB-average Direct Debit figures only",
fuel, payment_method
);
}
let row = match rows.iter().find(|r| r.valid_from <= on_date && on_date <= r.valid_to) {
Some(row) => row,
None => {
// A pruned build must not answer an old date with a newer cap.
if PRICE_CAP_HISTORY != "full" {
let earliest = rows.iter().map(|r| r.valid_from).min().unwrap();
if on_date < earliest {
panic!(
"no price cap for {}/{} on {}: this build was installed with history={}, so it only carries periods from {}. Reinstall with history=full for older dates.",
fuel, payment_method, on_date, PRICE_CAP_HISTORY, earliest
);
}
}
panic!("no price cap for {}/{} on {}", fuel, payment_method, on_date)
}
};
PriceCap {
fuel: fuel.to_string(),
payment_method: payment_method.to_string(),
region: row.region.to_string(),
unit_rate: row.unit_rate,
standing_charge: row.standing_charge,
vat_basis_points: row.vat_basis_points,
valid_from: row.valid_from.to_string(),
valid_to: row.valid_to.to_string(),
}
}
pub fn price_cap_to_value(p: &PriceCap) -> Value {
Value::obj(vec![
("fuel", Value::str(&p.fuel)),
("paymentMethod", Value::str(&p.payment_method)),
("region", Value::str(&p.region)),
("unitRate", Value::Int(p.unit_rate)),
("standingCharge", Value::Int(p.standing_charge)),
("vatBasisPoints", Value::Int(p.vat_basis_points)),
("validFrom", Value::str(&p.valid_from)),
("validTo", Value::str(&p.valid_to)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
price_cap_to_value(&price_cap(args[0].as_str(), args[1].as_str(), args[2].as_str()))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and nothing else, 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.price-cap
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./energy.price-cap-1.0.2-rust.fune, or fetch it from a terminal with fune pull energy.price-cap@1.0.2:rust.
The whole function, every language, is one file too: energy.price-cap-1.0.2.fune, 27,981 bytes, sha256 b5ceea9f411ec53104d7e2ca1a14be3bffe7b245a623d40c0148b2e68f993042. 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.price-cap
after — your function gets the result and the arguments, and returns the final result.
// fune: after energy.price-cap
replace — it requires no other capability, so there is no dependency to replace.
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.price-cap --steps.
// fune: step energy.price-cap 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 | |
|---|---|---|---|
| electricity in May 2026: 24.67p/kWh and 57.21p a day | electricity, direct-debit, 2026-05-01 | → | fuel electricity, payment method direct-debit, region GB, unit rate 24,670, standing charge 57,210, vat basis points 5%, valid from 2026-04-01, valid to 2026-06-30 |
| gas in May 2026: 5.74p/kWh and 29.09p a day | gas, direct-debit, 2026-05-01 | → | fuel gas, payment method direct-debit, region GB, unit rate 5,740, standing charge 29,090, vat basis points 5%, valid from 2026-04-01, valid to 2026-06-30 |
| the first day of a cap period takes the new cap | electricity, direct-debit, 2025-04-01 | → | fuel electricity, payment method direct-debit, region GB, unit rate 27,030, standing charge 53,800, vat basis points 5%, valid from 2025-04-01, valid to 2025-06-30 |
| the last day of a cap period keeps the old one | electricity, direct-debit, 2025-03-31 | → | fuel electricity, payment method direct-debit, region GB, unit rate 24,860, standing charge 60,970, vat basis points 5%, valid from 2025-01-01, valid to 2025-03-31 |
| gas, July to September 2025 | gas, direct-debit, 2025-08-15 | → | fuel gas, payment method direct-debit, region GB, unit rate 6,330, standing charge 29,820, vat basis points 5%, valid from 2025-07-01, valid to 2025-09-30 |
| gas standing charge jumps in October 2025 | gas, direct-debit, 2025-10-01 | → | fuel gas, payment method direct-debit, region GB, unit rate 6,290, standing charge 34,030, vat basis points 5%, valid from 2025-10-01, valid to 2025-12-31 |
| electricity, January to March 2026 | electricity, direct-debit, 2026-02-28 | → | fuel electricity, payment method direct-debit, region GB, unit rate 27,690, standing charge 54,750, vat basis points 5%, valid from 2026-01-01, valid to 2026-03-31 |
| electricity from 1 October 2026 carries no VAT | electricity, direct-debit, 2026-10-01 | → | fuel electricity, payment method direct-debit, region GB, unit rate 26,320, standing charge 54,830, vat basis points 0%, valid from 2026-10-01, valid to 2026-12-31 |
| gas from 1 October 2026 still includes 5% VAT | gas, direct-debit, 2026-12-31 | → | fuel gas, payment method direct-debit, region GB, unit rate 7,970, standing charge 29,680, vat basis points 5%, valid from 2026-10-01, valid to 2026-12-31 |
| the earliest period carried, October 2024 | gas, direct-debit, 2024-10-01 | → | fuel gas, payment method direct-debit, region GB, unit rate 6,240, standing charge 31,660, vat basis points 5%, valid from 2024-10-01, valid to 2024-12-31 |
Show the other 9 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| electricity, July to September 2026 | electricity, direct-debit, 2026-09-30 | → | fuel electricity, payment method direct-debit, region GB, unit rate 26,110, standing charge 57,190, vat basis points 5%, valid from 2026-07-01, valid to 2026-09-30 |
| a period not yet announced is an error, not the latest cap | electricity, direct-debit, 2027-01-01 | → | error: no price cap for electricity/direct-debit on 2027-01-01 |
| before the earliest period carried | gas, direct-debit, 2024-09-30 | → | error: no price cap for gas/direct-debit on 2024-09-30 |
| prepayment figures are not carried | electricity, prepayment, 2026-05-01 | → | error: no price cap figures for electricity paid by prepayment |
| an unknown fuel | oil, direct-debit, 2026-05-01 | → | error: unknown fuel "oil" |
| an unknown payment method | gas, cash, 2026-05-01 | → | error: unknown payment method "cash" |
| a malformed date | gas, direct-debit, 2026-5-1 | → | error: onDate must be an ISO date |
| a onDate with a trailing newline is refused | gas, direct-debit, 2026-09-16 | → | error: onDate must be an ISO date (YYYY-MM-DD), received "2026-09-16 " |
| a onDate in Arabic-Indic digits is refused | gas, direct-debit, ٢٠٢٦-٠٩-١٦ | → | error: onDate must be an ISO date (YYYY-MM-DD), received "٢٠٢٦-٠٩-١٦" |
More from the author
## Scope of the data
- **GB average, Direct Debit, single-rate electricity.** These are the figures Ofgem publishes as the headline cap for each period: the average across England, Scotland and Wales for a customer paying by Direct Debit. Nine periods, **1 October 2024 to 31 December 2026**. - **Not regional.** The cap differs by the 14 electricity regions. Ofgem publishes the regional rates only in its interactive tables and spreadsheets, which this version does not carry; a regional bill needs the supplier's own rates (which may be below the cap anyway). - **Not standard credit or prepayment.** The types list them because the cap is set for each, but Ofgem does not publish GB-average unit rates for them in a form that could be checked here, so asking for one is an error rather than a guess. They will be new rows, not new code. - **Not Economy 7 / multi-rate.** Ofgem caps a multi-register meter as a whole rather than publishing a GB-average day and night rate.
## Units and VAT
Rates are integers in **thousandths of a penny**, matching the rest of the `energy.*` capabilities (`energy.tariff-time-of-use`, `energy.bill-calculate`); Ofgem's figures are rounded to two decimal places, so the last digit is always 0.
The figures **include VAT**, and `vatBasisPoints` says how much: 5% on everything, except electricity from 1 October 2026, which the government zero-rated for domestic customers in Great Britain from 1 October 2026 to 31 March 2027 (Ofgem: "It does not include VAT from 1 October 2026 to 31 March 2027"). To get the pre-VAT rate for a bill, divide by `1 + vatBasisPoints / 10000`.
## Dates
A period runs `validFrom` to `validTo` inclusive (calendar quarters). A date outside every period this version carries is an error: the cap for a period not yet announced is not today's cap. A build installed with `history=current` keeps only periods still running and refuses older dates with an explanation.
## Sources
Ofgem news releases, "Changes to energy price cap between ..." (each gives the Direct Debit GB-average unit rate and standing charge for electricity and gas, and states the VAT they include):
- 1 January to 31 March 2026: https://www.ofgem.gov.uk/news/changes-energy-price-cap-between-1-january-and-31-march-2026 - 1 April to 30 June 2026: https://www.ofgem.gov.uk/news/changes-energy-price-cap-between-1-april-and-30-june-2026 - 1 July to 30 September 2026: https://www.ofgem.gov.uk/news/changes-energy-price-cap-between-1-july-and-30-september-2026 - 1 October to 31 December 2026: https://www.ofgem.gov.uk/news/changes-energy-price-cap-between-1-october-and-31-december-2026
Ofgem, "Energy price cap" consumer page, as archived by the Internet Archive (https://web.archive.org/web/2025*/https://www.ofgem.gov.uk/information-consumers/energy-advice-households/energy-price-cap) in February, May, August and November 2025, each showing the current and next period: October to December 2024, and each quarter of 2025.
Ofgem, "Energy price cap (default tariff) levels", https://www.ofgem.gov.uk/energy-regulation/domestic-and-non-domestic/energy-pricing-rules/energy-price-cap/energy-price-cap-default-tariff-levels (the regional and per-payment-method models this version does not carry).
1.0.1 fixes Python accepting a trailing newline or non-ASCII digits in onDate; adds tests.
## Notices
Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
Source: Ofgem (Crown copyright), reused under the Open Government Licence v3.0.
1.0.2 adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 260 |
| README.md | 4,075 |
| data/price-cap.json | 5,846 |
| impl/python.py | 2,260 |
| impl/rust.rs | 3,311 |
| impl/typescript.ts | 2,144 |
| vectors.json | 4,696 |