manufacturing.oee
Overall equipment effectiveness (availability x performance x quality) in basis points, from times and counts.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 18 tests, run in TypeScript, Python and Rust.
What it does
Overall equipment effectiveness, as defined by Nakajima for TPM and used by most OEE software:
run time = planned production time - downtime availability = run time / planned production time performance = (ideal cycle time x total count) / run time quality = good count / total count OEE = availability x performance x quality = (good count x ideal cycle time) / planned production time
For example
overallEquipmentEffectiveness(25,200, 2,820, 1,000, 19,271, 18,848, half-up)→ availability 8,881, performance 8,611, quality 9,780, oee 7,479, run seconds 22,380 the oee.com worked example: 420 min planned, 47 down, 1.0 s cycle, 19271 made, 18848 goodoverallEquipmentEffectiveness(28,800, 3,600, 1,500, 15,000, 14,700, half-up)→ availability 8,750, performance 8,929, quality 9,800, oee 7,656, run seconds 25,200 OEE is 76.5625% exactly, so 7656; multiplying the rounded factors 8750 x 8929 x 9800 would give 7657overallEquipmentEffectiveness(28,800, 3,600, 1,500, 15,000, 14,700, down)→ availability 8,750, performance 8,928, quality 9,800, oee 7,656, run seconds 25,200 the same shift rounded down
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 overallEquipmentEffectiveness(plannedSeconds: number, downtimeSeconds: number, idealCycleMillis: number, totalCount: number, goodCount: number, mode: RoundingMode): Oee
| plannedSeconds | int | planned production time: the shift less breaks and planned stops |
| downtimeSeconds | int | unplanned stops and changeovers inside the planned time |
| idealCycleMillis | int | the fastest possible time for one unit, in milliseconds |
| totalCount | int | every unit made, good or not |
| goodCount | int | units right first time, without rework |
| mode | RoundingMode | how each exact ratio becomes whole basis points |
| returns | Oee |
The type it declares, generated into your project
/** The three factors and their product, each in basis points (10000 = 100%). */
export interface Oee {
/** run time / planned time */
readonly availability: number;
/** ideal cycle time x total count / run time; above 10000 means the ideal cycle time is too slow */
readonly performance: number;
/** good count / total count; 0 when nothing was made */
readonly quality: number;
/** good count x ideal cycle time / planned time, rounded once */
readonly oee: number;
/** planned time less downtime */
readonly runSeconds: number;
}
Your code names it in one line, in the file that uses it
import { overallEquipmentEffectiveness } from "#fune/manufacturing.oee@^1";
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { type RoundingMode, roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type Oee } from "./manufacturing_oee_types.ts";
const MAX_PLANNED = 100_000_000_000;
function whole(name: string, value: number): void {
if (!Number.isSafeInteger(value)) {
throw new RangeError(`${name} must be a whole number, received ${value}`);
}
}
/**
* OEE and its three factors in basis points. OEE is rounded once from the
* exact product (good count x ideal cycle / planned time), never from the
* three already-rounded factors, which is off by one surprisingly often.
*/
export function overallEquipmentEffectiveness(
plannedSeconds: number,
downtimeSeconds: number,
idealCycleMillis: number,
totalCount: number,
goodCount: number,
mode: RoundingMode,
): Oee {
whole("plannedSeconds", plannedSeconds);
whole("downtimeSeconds", downtimeSeconds);
whole("idealCycleMillis", idealCycleMillis);
whole("totalCount", totalCount);
whole("goodCount", goodCount);
if (plannedSeconds <= 0 || plannedSeconds > MAX_PLANNED) {
throw new RangeError(`plannedSeconds must be from 1 to 10^11, received ${plannedSeconds}`);
}
if (downtimeSeconds < 0 || downtimeSeconds > plannedSeconds) {
throw new RangeError(`downtimeSeconds must be from 0 to plannedSeconds, received ${downtimeSeconds}`);
}
if (idealCycleMillis <= 0) {
throw new RangeError(`idealCycleMillis must be greater than zero, received ${idealCycleMillis}`);
}
if (totalCount < 0) {
throw new RangeError(`totalCount must not be negative, received ${totalCount}`);
}
if (goodCount < 0 || goodCount > totalCount) {
throw new RangeError(`goodCount must be from 0 to totalCount, received ${goodCount}`);
}
if (BigInt(totalCount) * BigInt(idealCycleMillis) * 10000n > BigInt(Number.MAX_SAFE_INTEGER)) {
throw new RangeError("totalCount and idealCycleMillis are too large: total x ideal cycle x 10000 must stay within 2^53 - 1");
}
const runSeconds = plannedSeconds - downtimeSeconds;
if (runSeconds === 0 && totalCount > 0) {
throw new RangeError(`no units can be made with no run time, received totalCount ${totalCount}`);
}
const availability = roundDiv(runSeconds * 10000, plannedSeconds, mode);
const performance = runSeconds === 0 ? 0 : roundDiv(idealCycleMillis * totalCount * 10000, runSeconds * 1000, mode);
const quality = totalCount === 0 ? 0 : roundDiv(goodCount * 10000, totalCount, mode);
const oee = roundDiv(goodCount * idealCycleMillis * 10000, plannedSeconds * 1000, mode);
return { availability, performance, quality, oee, runSeconds };
}Install
fune build
With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 1 dependency, 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 manufacturing.oee
The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./manufacturing.oee-1.0.0-typescript.fune, or fetch it from a terminal with fune pull manufacturing.oee@1.0.0:typescript.
The whole function, every language, is one file too: manufacturing.oee-1.0.0.fune, 17,981 bytes, sha256 cbd3d1eb8231b27c04caa961c4e0f95ed7892e64ecdee06bc38a314984b764b7. 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 manufacturing.oee
after — your function gets the result and the arguments, and returns the final result.
// fune: after manufacturing.oee
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 manufacturing.oee
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 manufacturing.oee --steps.
// fune: step manufacturing.oee 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 | |
|---|---|---|---|
| the oee.com worked example: 420 min planned, 47 down, 1.0 s cycle, 19271 made, 18848 good | 25,200, 2,820, 1,000, 19,271, 18,848, half-up | → | availability 8,881, performance 8,611, quality 9,780, oee 7,479, run seconds 22,380 |
| OEE is 76.5625% exactly, so 7656; multiplying the rounded factors 8750 x 8929 x 9800 would give 7657 | 28,800, 3,600, 1,500, 15,000, 14,700, half-up | → | availability 8,750, performance 8,929, quality 9,800, oee 7,656, run seconds 25,200 |
| the same shift rounded down | 28,800, 3,600, 1,500, 15,000, 14,700, down | → | availability 8,750, performance 8,928, quality 9,800, oee 7,656, run seconds 25,200 |
| the same shift rounded up | 28,800, 3,600, 1,500, 15,000, 14,700, up | → | availability 8,750, performance 8,929, quality 9,800, oee 7,657, run seconds 25,200 |
| an exact half basis point, 6500.5, goes to the even 6500 under half-even | 20,000, 0, 1,000, 13,001, 13,001, half-even | → | availability 10,000, performance 6,500, quality 10,000, oee 6,500, run seconds 20,000 |
| the same half goes up to 6501 under half-up | 20,000, 0, 1,000, 13,001, 13,001, half-up | → | availability 10,000, performance 6,501, quality 10,000, oee 6,501, run seconds 20,000 |
| a sub-second ideal cycle set too slow: performance over 100% is reported, not capped | 28,800, 0, 500, 60,000, 60,000, half-up | → | availability 10,000, performance 10,417, quality 10,000, oee 10,417, run seconds 28,800 |
| an hour with 10 minutes down, a 2 s cycle, 1600 made and 1500 good | 3,600, 600, 2,000, 1,600, 1,500, half-up | → | availability 8,333, performance 10,667, quality 9,375, oee 8,333, run seconds 3,000 |
| down for the whole planned time: every factor is zero | 3,600, 3,600, 1,000, 0, 0, half-up | → | availability 0, performance 0, quality 0, oee 0, run seconds 0 |
| running but nothing made: quality is zero, not a division by zero | 3,600, 0, 1,000, 0, 0, half-up | → | availability 10,000, performance 0, quality 0, oee 0, run seconds 3,600 |
Show the other 8 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| more good units than made is an error | 3,600, 0, 1,000, 100, 101, half-up | → | error: goodCount must be from 0 to totalCount |
| more downtime than planned time is an error | 3,600, 3,601, 1,000, 0, 0, half-up | → | error: downtimeSeconds must be from 0 to plannedSeconds |
| zero planned time is an error | 0, 0, 1,000, 0, 0, half-up | → | error: plannedSeconds must be from 1 to 10^11 |
| a zero ideal cycle time is an error | 3,600, 0, 0, 10, 10, half-up | → | error: idealCycleMillis must be greater than zero |
| units counted with no run time is an error | 3,600, 3,600, 1,000, 5, 5, half-up | → | error: no units can be made with no run time |
| a negative total count is an error | 3,600, 0, 1,000, -1, 0, half-up | → | error: totalCount must not be negative |
| a fractional count is an error | 3,600, 0, 1,000, 10.5, 10, half-up | → | error: totalCount must be a whole number |
| counts too large to compute exactly are an error | 100,000,000,000, 0, 1,000,000, 1,000,000,000, 0, half-up | → | error: totalCount and idealCycleMillis are too large |
More from the author
Every figure is in basis points (10000 = 100%). The worked example from oee.com: 420 minutes planned, 47 minutes down, a 1.0 second ideal cycle, 19,271 made and 18,848 good gives availability 8881, performance 8611, quality 9780 and OEE 7479 (74.79%).
**OEE is rounded once, from the exact product.** Multiplying three factors that were each already rounded to basis points is off by one surprisingly often: 8 hours planned, 1 hour down, a 1.5 second cycle, 15,000 made and 14,700 good is exactly 76.5625%, so 7656; the rounded factors (8750 x 8929 x 9800) give 7657. Each factor and OEE itself are rounded from their exact ratio by `mode`.
**Units.** Planned time and downtime are whole seconds; the ideal cycle time is whole milliseconds, because cycle times under a second (or 1.5 s) are common.
**Performance may exceed 100%.** That means the ideal cycle time is set slower than the machine really runs. It is reported as it is, not capped, because capping hides the bad standard and makes OEE disagree with its factors.
**Edge cases.** With no run time (downtime equals planned time) performance is 0, and with nothing made quality is 0; OEE is 0 in both cases. Counting units with no run time is an error. Good count may not exceed total count, downtime may not exceed planned time, and planned time and ideal cycle time must be positive. Planned time is limited to 10^11 seconds, and total count x ideal cycle x 10000 to 2^53 - 1, so every ratio is computed exactly.
Sources: Seiichi Nakajima, *Introduction to TPM: Total Productive Maintenance*, Productivity Press, 1988; "OEE Calculation", oee.com (Vorne Industries), https://www.oee.com/calculating-oee/ (the worked example above).
Files
| Path | Bytes |
|---|---|
| README.md | 2,169 |
| impl/python.py | 2,658 |
| impl/rust.rs | 3,247 |
| impl/typescript.ts | 2,585 |
| vectors.json | 3,642 |