Functional Weave
Code in Rust

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 good
  • overall_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 7657
  • overall_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_secondsintplanned production time: the shift less breaks and planned stops
downtime_secondsintunplanned stops and changeovers inside the planned time
ideal_cycle_millisintthe fastest possible time for one unit, in milliseconds
total_countintevery unit made, good or not
good_countintunits 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%).
#[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(…)
impl/rust.rs · 89 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.

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
Download for Rust manufacturing.oee-1.0.0-rust.fune · 12,548 bytes sha256 bb8f7a89edb31fe0d97a3ed930c4707d7721931c5d18c4c86bd0c3148b281237

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.

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