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.1 (not the latest) · 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
priceCap(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 daypriceCap(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 daypriceCap(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.
export function priceCap(fuel: EnergyFuel, paymentMethod: PaymentMethod, onDate: string): PriceCap
| fuel | EnergyFuel | electricity (single-rate) or gas |
| paymentMethod | PaymentMethod | direct-debit is the only method this version carries figures for |
| onDate | date | the day of supply |
| returns | PriceCap |
The types it declares, generated into your project
export type EnergyFuel = "electricity" | "gas";
export type PaymentMethod = "direct-debit" | "standard-credit" | "prepayment";
/** The capped rates for one cap period, as Ofgem publishes them. */
export interface PriceCap {
readonly fuel: string;
readonly paymentMethod: string;
/** GB: the England, Scotland and Wales average */
readonly region: string;
/** thousandths of a penny per kWh: 24.67p is 24670 */
readonly unitRate: number;
/** thousandths of a penny per day: 57.21p is 57210 */
readonly standingCharge: number;
/** VAT included in both figures: 500, or 0 where none applied */
readonly vatBasisPoints: number;
/** first day of the cap period */
readonly validFrom: string;
/** last day of the cap period */
readonly validTo: string;
}
Your code names it in one line, in the file that uses it
import { priceCap } from "#fune/energy.price-cap@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { PRICE_CAP, PRICE_CAP_HISTORY } from "./energy_price_cap_data.ts"; ← this capability’s own data, compiled from data/price-cap.json into the same file by fune build
import { type EnergyFuel, type PaymentMethod, type PriceCap } from "./energy_price_cap_types.ts";
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
const FUELS = ["electricity", "gas"];
const METHODS = ["direct-debit", "standard-credit", "prepayment"];
/** The Ofgem price cap unit rate and standing charge in force on `onDate`. */
export function priceCap(fuel: EnergyFuel, paymentMethod: PaymentMethod, onDate: string): PriceCap {
if (!ISO_DATE.test(onDate)) {
throw new RangeError(`onDate must be an ISO date (YYYY-MM-DD), received "${onDate}"`);
}
if (!FUELS.includes(fuel)) {
throw new RangeError(`unknown fuel "${fuel}": expected electricity or gas`);
}
if (!METHODS.includes(paymentMethod)) {
throw new RangeError(`unknown payment method "${paymentMethod}": expected direct-debit, standard-credit or prepayment`);
}
const rows = PRICE_CAP.filter((r) => r.fuel === fuel && r.paymentMethod === paymentMethod);
if (rows.length === 0) {
throw new RangeError(`no price cap figures for ${fuel} paid by ${paymentMethod}: this version carries GB-average Direct Debit figures only`);
}
const row = rows.find((r) => r.validFrom <= onDate && onDate <= r.validTo);
if (row === undefined) {
// A pruned build must not answer an old date with a newer cap.
if (PRICE_CAP_HISTORY !== "full") {
const earliest = rows.reduce((e, r) => (r.validFrom < e ? r.validFrom : e), rows[0].validFrom);
if (onDate < earliest) {
throw new RangeError(
`no price cap for ${fuel}/${paymentMethod} on ${onDate}: this build was installed with history=${PRICE_CAP_HISTORY}, ` +
`so it only carries periods from ${earliest}. Reinstall with history=full for older dates.`
);
}
}
throw new RangeError(`no price cap for ${fuel}/${paymentMethod} on ${onDate}`);
}
return {
fuel,
paymentMethod,
region: row.region,
unitRate: row.unitRate,
standingCharge: row.standingCharge,
vatBasisPoints: row.vatBasisPoints,
validFrom: row.validFrom,
validTo: row.validTo,
};
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the TypeScript 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.price-cap
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./energy.price-cap-1.0.1-typescript.fune, or fetch it from a terminal with fune pull energy.price-cap@1.0.1:typescript.
The whole function, every language, is one file too: energy.price-cap-1.0.1.fune, 27,349 bytes, sha256 01528a344e3a739f896776b9ee4b867c47104d87ba50feba25906ff17ef70f52. 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.
Files
| Path | Bytes |
|---|---|
| README.md | 3,736 |
| 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 |