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
overall_equipment_effectiveness(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 goodoverall_equipment_effectiveness(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 7657overall_equipment_effectiveness(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.
pub fn overall_equipment_effectiveness(planned_seconds: i64, downtime_seconds: i64, ideal_cycle_millis: i64, total_count: i64, good_count: i64, mode: &str) -> Oee
| planned_seconds | int | planned production time: the shift less breaks and planned stops |
| downtime_seconds | int | unplanned stops and changeovers inside the planned time |
| ideal_cycle_millis | int | the fastest possible time for one unit, in milliseconds |
| total_count | int | every unit made, good or not |
| good_count | 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%).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Oee {
/// run time / planned time
pub availability: i64,
/// ideal cycle time x total count / run time; above 10000 means the ideal cycle time is too slow
pub performance: i64,
/// good count / total count; 0 when nothing was made
pub quality: i64,
/// good count x ideal cycle time / planned time, rounded once
pub oee: i64,
/// planned time less downtime
pub run_seconds: i64,
}
Your code names it in one line, in the file that uses it
fune!(manufacturing.oee@^1); // then call overall_equipment_effectiveness(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
const MAX_SAFE: i128 = (1i128 << 53) - 1;
const MAX_PLANNED: i64 = 100_000_000_000;
/// 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.
///
/// # Panics
/// Panics on times or counts out of range (see the README), or an unknown mode.
pub fn overall_equipment_effectiveness(
planned_seconds: i64,
downtime_seconds: i64,
ideal_cycle_millis: i64,
total_count: i64,
good_count: i64,
mode: &str,
) -> Oee {
if planned_seconds <= 0 || planned_seconds > MAX_PLANNED {
panic!("plannedSeconds must be from 1 to 10^11, received {}", planned_seconds);
}
if downtime_seconds < 0 || downtime_seconds > planned_seconds {
panic!("downtimeSeconds must be from 0 to plannedSeconds, received {}", downtime_seconds);
}
if ideal_cycle_millis <= 0 {
panic!("idealCycleMillis must be greater than zero, received {}", ideal_cycle_millis);
}
if total_count < 0 {
panic!("totalCount must not be negative, received {}", total_count);
}
if good_count < 0 || good_count > total_count {
panic!("goodCount must be from 0 to totalCount, received {}", good_count);
}
if (total_count as i128) * (ideal_cycle_millis as i128) * 10000 > MAX_SAFE {
panic!("totalCount and idealCycleMillis are too large: total x ideal cycle x 10000 must stay within 2^53 - 1");
}
let run_seconds = planned_seconds - downtime_seconds;
if run_seconds == 0 && total_count > 0 {
panic!("no units can be made with no run time, received totalCount {}", total_count);
}
let availability = round_div(run_seconds * 10000, planned_seconds, mode);
let performance = if run_seconds == 0 {
0
} else {
round_div(ideal_cycle_millis * total_count * 10000, run_seconds * 1000, mode)
};
let quality = if total_count == 0 {
0
} else {
round_div(good_count * 10000, total_count, mode)
};
let oee = round_div(good_count * ideal_cycle_millis * 10000, planned_seconds * 1000, mode);
Oee {
availability,
performance,
quality,
oee,
run_seconds,
}
}
pub fn oee_to_value(result: &Oee) -> Value {
Value::obj(vec![
("availability", Value::Int(result.availability)),
("performance", Value::Int(result.performance)),
("quality", Value::Int(result.quality)),
("oee", Value::Int(result.oee)),
("runSeconds", Value::Int(result.run_seconds)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let names = ["plannedSeconds", "downtimeSeconds", "idealCycleMillis", "totalCount", "goodCount"];
for (i, name) in names.iter().enumerate() {
if let Value::Float(f) = &args[i] {
panic!("{} must be a whole number, received {}", name, f);
}
}
oee_to_value(&overall_equipment_effectiveness(
args[0].as_i64(),
args[1].as_i64(),
args[2].as_i64(),
args[3].as_i64(),
args[4].as_i64(),
args[5].as_str(),
))
}Install
fune build
With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:
fune add manufacturing.oee
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./manufacturing.oee-1.0.0-rust.fune, or fetch it from a terminal with fune pull manufacturing.oee@1.0.0:rust.
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 |