energy.water-bill
Metered water and sewerage bill from two reads: volumetric charges, return to sewer, standing and surface water charges.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 14 tests, run in TypeScript, Python and Rust.
What it does
A metered water and sewerage bill for England and Wales, from the reads at the start and end of the period and the water company's charges:
| line | how | |---|---| | water volume | m³ used x water rate per m³ | | sewerage volume | m³ used x return to sewer % x sewerage rate per m³ | | water standing charge | annual charge x days / 365 | | sewerage standing charge | annual charge x days / 365 | | surface water drainage | annual charge x days / 365, unless the property's surface water does not drain to the public sewer |
For example
waterBill(date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000…)→ cubic metres 123, days 182, water volume charge £255.05, sewerage volume charge £292.13, water standing charge £14.96, sewerage standing charge £19.95, surface water charge £27.42… six months, 123 m³: sewerage on 95% is exactly 29212.5p and rounds upwaterBill(date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000…)→ cubic metres 123, days 182, water volume charge £255.05, sewerage volume charge £292.13, water standing charge £14.96, sewerage standing charge £19.95, surface water charge £0.00,… surface water that does not reach the sewer is not chargedwaterBill(date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000…)→ cubic metres 123, days 182, water volume charge £255.05, sewerage volume charge £292.12, water standing charge £14.95, sewerage standing charge £19.94, surface water charge £27.42… rounding down every line
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 waterBill(openingRead: MeterRead, closingRead: MeterRead, meterDigits: number, tariff: WaterTariff, surfaceWaterDrains: boolean, currency: string, mode: RoundingMode): WaterBill
| openingRead | MeterRead | the read at the start of the period, whole cubic metres |
| closingRead | MeterRead | the read at the end; lower means the meter rolled over |
| meterDigits | int | whole-m³ digits on the register, 1 to 15 |
| tariff | WaterTariff | the water company's charges for the period |
| surfaceWaterDrains | bool | false when rainwater from the property does not reach the public sewer: no surface water charge |
| currency | string | the currency of the tariff |
| mode | RoundingMode | how each charge rounds to a whole minor unit |
| returns | WaterBill |
The types it declares, generated into your project
/** One meter read. */
export interface MeterRead {
readonly date: string;
/** the register, whole cubic metres */
readonly value: number;
}
/** A metered water and sewerage tariff, as a water company's charges scheme lists it. */
export interface WaterTariff {
/** water supply per m³, in thousandths of a minor unit: £2.0736 is 207360 */
readonly waterVolumeRate: number;
/** sewerage per m³ returned to the sewer, in thousandths of a minor unit */
readonly sewerageVolumeRate: number;
/** water supply standing charge per year, in minor units */
readonly waterStandingCharge: number;
/** sewerage standing charge per year, in minor units */
readonly sewerageStandingCharge: number;
/** surface water drainage charge per year, in minor units; 0 if not separately charged */
readonly surfaceWaterCharge: number;
/** share of water charged as returned to the sewer, basis points: 95% is 9500 */
readonly returnToSewer: number;
}
/** The bill, one charge per line. */
export interface WaterBill {
/** water used */
readonly cubicMetres: number;
/** days between the reads */
readonly days: number;
readonly waterVolumeCharge: Money;
/** on cubicMetres x returnToSewer */
readonly sewerageVolumeCharge: Money;
/** annual charge x days / 365 */
readonly waterStandingCharge: Money;
/** annual charge x days / 365 */
readonly sewerageStandingCharge: Money;
/** annual charge x days / 365, or zero when surface water does not drain to the sewer */
readonly surfaceWaterCharge: Money;
/** the sum of the lines */
readonly total: Money;
}
Your code names it in one line, in the file that uses it
import { waterBill } from "#fune/energy.water-bill@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { daysBetween } from "./dates_days_between.ts"; ← from dates.days-between ^1.0.0 · built alongside by fune
import { meterAdvance } from "./energy_meter_advance.ts"; ← from energy.meter-advance ^1.0.0 · built alongside by fune
import { type RoundingMode, roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { money } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type MeterRead, type WaterBill, type WaterTariff } from "./energy_water_bill_types.ts";
function checkRate(what: string, value: number): void {
if (!Number.isInteger(value) || value < 0) {
throw new RangeError(`${what} must be a whole number of 0 or more, received ${value}`);
}
}
function exact(product: number): number {
if (product > Number.MAX_SAFE_INTEGER) {
throw new RangeError("water charge too large to calculate exactly");
}
return product;
}
/**
* A metered water and sewerage bill: volumetric water, sewerage on the share
* returned to the sewer, and standing and surface water charges apportioned
* by days / 365, each line rounded once.
*/
export function waterBill(
openingRead: MeterRead,
closingRead: MeterRead,
meterDigits: number,
tariff: WaterTariff,
surfaceWaterDrains: boolean,
currency: string,
mode: RoundingMode
): WaterBill {
const cubicMetres = meterAdvance(openingRead.value, closingRead.value, meterDigits);
const days = daysBetween(openingRead.date, closingRead.date);
if (days < 0) {
throw new RangeError(`the closing read must not be dated before the opening read, received ${openingRead.date} and ${closingRead.date}`);
}
checkRate("waterVolumeRate", tariff.waterVolumeRate);
checkRate("sewerageVolumeRate", tariff.sewerageVolumeRate);
checkRate("waterStandingCharge", tariff.waterStandingCharge);
checkRate("sewerageStandingCharge", tariff.sewerageStandingCharge);
checkRate("surfaceWaterCharge", tariff.surfaceWaterCharge);
if (!Number.isInteger(tariff.returnToSewer) || tariff.returnToSewer < 0 || tariff.returnToSewer > 10000) {
throw new RangeError(`returnToSewer must be basis points from 0 to 10000, received ${tariff.returnToSewer}`);
}
const water = roundDiv(exact(cubicMetres * tariff.waterVolumeRate), 1000, mode);
const sewerage = roundDiv(exact(cubicMetres * tariff.sewerageVolumeRate * tariff.returnToSewer), 10_000_000, mode);
const waterStanding = roundDiv(exact(tariff.waterStandingCharge * days), 365, mode);
const sewerageStanding = roundDiv(exact(tariff.sewerageStandingCharge * days), 365, mode);
const surface = surfaceWaterDrains ? roundDiv(exact(tariff.surfaceWaterCharge * days), 365, mode) : 0;
return {
cubicMetres,
days,
waterVolumeCharge: money(water, currency),
sewerageVolumeCharge: money(sewerage, currency),
waterStandingCharge: money(waterStanding, currency),
sewerageStandingCharge: money(sewerageStanding, currency),
surfaceWaterCharge: money(surface, currency),
total: money(water + sewerage + waterStanding + sewerageStanding + surface, currency),
};
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 4 dependencies, 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.water-bill
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./energy.water-bill-1.0.0-typescript.fune, or fetch it from a terminal with fune pull energy.water-bill@1.0.0:typescript.
The whole function, every language, is one file too: energy.water-bill-1.0.0.fune, 28,608 bytes, sha256 79d9f71e0a111957c20dff84b0ca2a486863c3ca44ebdd8b2b69095d390ad594. 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.water-bill
after — your function gets the result and the arguments, and returns the final result.
// fune: after energy.water-bill
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 dates.days-between in energy.water-bill
// fune: replace energy.meter-advance in energy.water-bill
// fune: replace math.round-div in energy.water-bill
// fune: replace money.amount in energy.water-bill
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.water-bill --steps.
// fune: step energy.water-bill 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 | |
|---|---|---|---|
| six months, 123 m³: sewerage on 95% is exactly 29212.5p and rounds up | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000… | → | cubic metres 123, days 182, water volume charge £255.05, sewerage volume charge £292.13, water standing charge £14.96, sewerage standing charge £19.95, surface water charge £27.42… |
| surface water that does not reach the sewer is not charged | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000… | → | cubic metres 123, days 182, water volume charge £255.05, sewerage volume charge £292.13, water standing charge £14.96, sewerage standing charge £19.95, surface water charge £0.00,… |
| rounding down every line | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000… | → | cubic metres 123, days 182, water volume charge £255.05, sewerage volume charge £292.12, water standing charge £14.95, sewerage standing charge £19.94, surface water charge £27.42… |
| a meter rolling over past 99999 | date 2026-01-01, value 99,990, date 2026-01-31, value 10, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000, … | → | cubic metres 20, days 30, water volume charge £41.47, sewerage volume charge £47.50, water standing charge £2.47, sewerage standing charge £3.29, surface water charge £4.52, total… |
| a leap year of standing charge is 366/365 of the annual charge | date 2024-01-01, value 500, date 2025-01-01, value 500, 5, water volume rate 0, sewerage volume rate 0, water standing charge 3,000, sewerage standing charge 0, surface water char… | → | cubic metres 0, days 366, water volume charge £0.00, sewerage volume charge £0.00, water standing charge £30.08, sewerage standing charge £0.00, surface water charge £0.00, total … |
| an ordinary year of standing charges is exactly the annual charges | date 2025-04-01, value 500, date 2026-04-01, value 500, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000, su… | → | cubic metres 0, days 365, water volume charge £0.00, sewerage volume charge £0.00, water standing charge £30.00, sewerage standing charge £40.00, surface water charge £55.00, tota… |
| a business with no water returned to the sewer pays no sewerage volume | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000… | → | cubic metres 123, days 182, water volume charge £255.05, sewerage volume charge £0.00, water standing charge £14.96, sewerage standing charge £19.95, surface water charge £27.42, … |
| reads on the same day with no use cost nothing | date 2026-04-01, value 42, date 2026-04-01, value 42, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000, surf… | → | cubic metres 0, days 0, water volume charge £0.00, sewerage volume charge £0.00, water standing charge £0.00, sewerage standing charge £0.00, surface water charge £0.00, total £0.… |
| a closing read dated before the opening read is refused | date 2026-09-30, value 1,123, date 2026-04-01, value 1,000, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000… | → | error: the closing read must not be dated before the opening read |
| a return to sewer over 100% is refused | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000… | → | error: returnToSewer must be basis points from 0 to 10000 |
Show the other 4 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a negative rate is refused | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate -1, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000, sur… | → | error: waterVolumeRate must be a whole number of 0 or more |
| a fractional standing charge is refused | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000.5, sewerage standing charge 4,0… | → | error: waterStandingCharge must be a whole number of 0 or more |
| a read that does not fit the register is refused | date 2026-04-01, value 1,000, date 2026-09-30, value 100,000, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,0… | → | error: reads must be whole numbers from 0 to 99999 |
| a lowercase currency is refused | date 2026-04-01, value 1,000, date 2026-09-30, value 1,123, 5, water volume rate 207,360, sewerage volume rate 250,000, water standing charge 3,000, sewerage standing charge 4,000… | → | error: is not an uppercase ISO 4217 currency code |
More from the author
The rates are the caller's: every company publishes its own charges scheme each April, and they differ by company and by customer type.
## Decisions
- **Return to sewer.** Sewerage is charged on the water assumed to go back to the sewer, usually 95% of what is supplied (90% for some, a site-specific figure for many businesses). It is a tariff field in basis points, and the sewerage volume charge is computed exactly on `m³ x rate x share` with one rounding, not on a rounded volume. - **Surface water drainage allowance.** Where no rainwater from the property drains to the public sewer (it goes to a soakaway, say), the company must not charge for surface water drainage. Pass `surfaceWaterDrains: false` and that line is zero. Companies that fold surface water into the sewerage standing charge publish a reduced sewerage charge for such properties instead; pass that tariff and a zero `surfaceWaterCharge`. - **Days.** The period is the days between the two read dates (`dates.days-between`): reads on 1 April and 30 September are 182 days. Annual charges are apportioned by `days / 365` in every year, including a leap year, which is the usual daily-apportionment rule in charges schemes; a company that apportions by 366 in a leap year will differ by a penny or so. - **Rollover** is handled by `energy.meter-advance`: a lower closing read is a meter that went round once. - **Each line is rounded once** by `mode` and the total is the sum of the lines, as the bill prints them. - **No VAT.** Water and sewerage services to households are zero-rated, and most business supplies are too; if yours are not, apply VAT to the total.
## Sources
- Ofwat, "Surface water and highway drainage", https://www.ofwat.gov.uk/households/your-water-bill/surfacewaterdrainage/ ("If no surface water from your property enters a public sewer then you may qualify for a reduction in your sewerage charge"). - Ofwat, "Metered customers and applying for metered charges", https://www.ofwat.gov.uk/households/your-water-bill/metering/ - The rates, standing charges, return-to-sewer percentage and apportionment rule are each company's own, in its annual charges scheme.
Files
| Path | Bytes |
|---|---|
| README.md | 2,758 |
| impl/python.py | 2,948 |
| impl/rust.rs | 5,152 |
| impl/typescript.ts | 2,871 |
| vectors.json | 8,328 |