energy.meter-advance
Units consumed between two meter reads, counting the meter rolling over past all nines once.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 13 tests, run in TypeScript, Python and Rust.
What it does
The number of units a meter advanced between two reads: `closing - opening`, or, when the closing read is lower, `closing + 10^digits - opening`, because the register rolled over past all nines. A five-digit meter that read 99,950 and now reads 00,020 has advanced 70 units, not minus 99,930.
It is the step every meter-based bill, estimate and water charge starts with, so `energy.estimate-read`, `energy.bill-calculate` and `energy.water-bill` all use it rather than each re-deriving the rollover.
For example
meterAdvance(12,345, 12,400, 5)→ 55 an ordinary advancemeterAdvance(4,321, 4,321, 5)→ 0 no consumption between readsmeterAdvance(99,950, 20, 5)→ 70 a five-digit meter rolls over past 99999
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 meterAdvance(openingRead: number, closingRead: number, digits: number): number
| openingRead | int | the earlier read, 0 to 10^digits - 1 |
| closingRead | int | the later read; lower than openingRead means the meter rolled over |
| digits | int | how many whole-unit digits the register shows, 1 to 15 |
| returns | int | units consumed, 0 or more |
Your code names it in one line, in the file that uses it
import { meterAdvance } from "#fune/energy.meter-advance@^1";
/**
* Units a meter advanced between two reads. A closing read lower than the
* opening read means the register rolled over past all nines, once.
*/
export function meterAdvance(openingRead: number, closingRead: number, digits: number): number {
if (!Number.isInteger(digits) || digits < 1 || digits > 15) {
throw new RangeError(`digits must be a whole number from 1 to 15, received ${digits}`);
}
const span = 10 ** digits;
for (const read of [openingRead, closingRead]) {
if (!Number.isInteger(read) || read < 0 || read >= span) {
throw new RangeError(`reads must be whole numbers from 0 to ${span - 1} for a ${digits}-digit meter, received ${read}`);
}
}
return closingRead >= openingRead ? closingRead - openingRead : closingRead + span - openingRead;
}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.meter-advance
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./energy.meter-advance-1.0.0-typescript.fune, or fetch it from a terminal with fune pull energy.meter-advance@1.0.0:typescript.
The whole function, every language, is one file too: energy.meter-advance-1.0.0.fune, 7,901 bytes, sha256 3a77a81e3c84db1734e25aa9c38f5b8a26ed3e8ec6c361d57efdb195c3d6dc0d. 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.meter-advance
after — your function gets the result and the arguments, and returns the final result.
// fune: after energy.meter-advance
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.meter-advance --steps.
// fune: step energy.meter-advance 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 | |
|---|---|---|---|
| an ordinary advance | 12,345, 12,400, 5 | → | 55 |
| no consumption between reads | 4,321, 4,321, 5 | → | 0 |
| a five-digit meter rolls over past 99999 | 99,950, 20, 5 | → | 70 |
| a four-digit meter rolls over past 9999 by one unit | 9,999, 0, 4 | → | 1 |
| rolling over from zero back to just below is nearly a full turn | 1, 0, 4 | → | 9,999 |
| a one-digit register | 9, 2, 1 | → | 3 |
| a fifteen-digit register at its top | 999,999,999,999,990, 5, 15 | → | 15 |
| from zero to the top of a six-digit register | 0, 999,999, 6 | → | 999,999 |
| a read too large for the register is refused | 12,345, 123,456, 5 | → | error: reads must be whole numbers from 0 to 99999 for a 5-digit meter |
| a negative read is refused | -1, 10, 5 | → | error: reads must be whole numbers |
Show the other 3 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| a fractional read is refused | 100.5, 120, 5 | → | error: reads must be whole numbers |
| zero digits is refused | 0, 0, 0 | → | error: digits must be a whole number from 1 to 15 |
| sixteen digits is refused | 0, 0, 16 | → | error: digits must be a whole number from 1 to 15 |
More from the author
## Decisions
- **At most one rollover.** Between two reads a meter can only be known to have gone round once; a read history that implies more has a missing read. - **A lower closing read is always a rollover.** A meter exchange, a corrected over-estimate or a meter running backwards all look the same, and all of them read as nearly a full turn of the register. Handle an exchange as two bills (old meter to its final read, new meter from its first), and check large advances before billing them. - **Whole units only.** Reads are the black digits of the register. The red digits (tenths and hundredths on a gas meter) are ignored when reading, as the gov.uk guidance says, so they are not part of the input. - **Reads must fit the register.** 123456 on a five-digit meter is refused, since it cannot be a read of that meter. Up to 15 digits, so every count stays an exact integer in all three languages.
## Source
Office for Product Safety and Standards, "Gas meter readings and bill calculation" (https://www.gov.uk/guidance/gas-meter-readings-and-bill-calculation): reads are the black digits, and units used are the current read less the previous one.
Files
| Path | Bytes |
|---|---|
| README.md | 1,705 |
| impl/python.py | 907 |
| impl/rust.rs | 1,244 |
| impl/typescript.ts | 791 |
| vectors.json | 1,400 |