Functional Weave
Code in TypeScript

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 good
  • overallEquipmentEffectiveness(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 7657
  • overallEquipmentEffectiveness(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
plannedSecondsintplanned production time: the shift less breaks and planned stops
downtimeSecondsintunplanned stops and changeovers inside the planned time
idealCycleMillisintthe fastest possible time for one unit, in milliseconds
totalCountintevery unit made, good or not
goodCountintunits right first time, without rework
modeRoundingModehow each exact ratio becomes whole basis points
returnsOee

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";
impl/typescript.ts · 57 lines · open · raw

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
Download for TypeScript manufacturing.oee-1.0.0-typescript.fune · 11,846 bytes sha256 67801278fac0cb0fae742e60c87089601efd54784dd62153b7fb1d8cfcad78eb

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.

CaseArgumentsExpected
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
CaseArgumentsExpected
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

PathBytes
README.md2,169
impl/python.py2,658
impl/rust.rs3,247
impl/typescript.ts2,585
vectors.json3,642