Functional Weave
Code in Rust

manufacturing.capacity

Capacity against routed load per work centre over a period, with efficiency, utilisation and overload flags.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 14 tests, run in TypeScript, Python and Rust.

What it does

Capacity requirements for a period, work centre by work centre:

capacity = available time x efficiency x utilisation load = the standard minutes of every routed operation in the period load % = load / capacity

For example

  • work_centre_load(work centres ×5, loads ×5) → ×5 a week across five centres: CNC 1938 min capacity against 2000 routed (two loads) is 103.20%; the lathe's 479.52 min is overloaded by 480; a painted-out centre has no percentage; packing at 110% efficiency has 950 spare
  • work_centre_load(work centres ×1, loads ×1) → ×1 a load exactly equal to capacity is full, not overloaded
  • work_centre_load(work centres ×1, loads ×1) → ×1 one minute over is an overload

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 work_centre_load(work_centres: &[WorkCentre], loads: &[RoutedLoad]) -> Vec<CapacityLoad>
work_centresWorkCentre[]each work centre once, with its available time in the period
loadsRoutedLoad[]routed minutes from orders in the same period; several per centre are added
returnsCapacityLoad[]one entry per work centre, in the order given

The types it declares, generated into your project

/// A work centre and the time it can offer in the period.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkCentre {
    pub id: String,
    /// scheduled time in the period: shifts x hours x machines
    pub available_minutes: i64,
    /// standard minutes produced per minute worked: 9500 = 95%; may exceed 10000
    pub efficiency_basis_points: i64,
    /// share of scheduled time actually worked: 8500 = 85%; at most 10000
    pub utilisation_basis_points: i64,
}

/// Standard minutes of work routed to a work centre.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoutedLoad {
    pub work_centre: String,
    pub minutes: i64,
}

/// One work centre's capacity, load and verdict.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CapacityLoad {
    pub work_centre: String,
    /// available x efficiency x utilisation, rounded down
    pub capacity_minutes: i64,
    /// the routed load, summed
    pub load_minutes: i64,
    /// capacityMinutes - loadMinutes; negative when overloaded
    pub spare_minutes: i64,
    /// load as a share of capacity, rounded half-up; null when capacity is zero
    pub load_basis_points: Option<i64>,
    /// load is more than the exact capacity
    pub overloaded: bool,
}

Your code names it in one line, in the file that uses it

fune!(manufacturing.capacity@^1);  // then call work_centre_load(…)
impl/rust.rs · 132 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_rational::{compare_rational, divide_rational, multiply_rational, rational, rational_to_integer};  ← from math.rational ^1.0.0 · built alongside by fune

const MAX_SAFE: i64 = (1i64 << 53) - 1;

fn minutes(what: &str, value: i64) {
    if !(0..=MAX_SAFE).contains(&value) {
        panic!("{} must be a whole number of minutes, not negative, received {}", what, value);
    }
}

/// Capacity, load and overload per work centre. Capacity is kept as an exact
/// fraction, so a load a fraction of a minute over it is still an overload.
///
/// # Panics
/// Panics on a duplicate or unknown work centre, negative times, negative
/// efficiency, or utilisation outside 0-10000.
pub fn work_centre_load(work_centres: &[WorkCentre], loads: &[RoutedLoad]) -> Vec<CapacityLoad> {
    let mut totals: Vec<(&str, i64)> = Vec::new();
    for wc in work_centres {
        if totals.iter().any(|(id, _)| *id == wc.id) {
            panic!("duplicate work centre \"{}\"", wc.id);
        }
        minutes(&format!("availableMinutes of \"{}\"", wc.id), wc.available_minutes);
        if !(0..=MAX_SAFE).contains(&wc.efficiency_basis_points) {
            panic!(
                "efficiencyBasisPoints of \"{}\" must be a whole number, not negative, received {}",
                wc.id, wc.efficiency_basis_points
            );
        }
        if !(0..=10000).contains(&wc.utilisation_basis_points) {
            panic!(
                "utilisationBasisPoints of \"{}\" must be a whole number from 0 to 10000, received {}",
                wc.id, wc.utilisation_basis_points
            );
        }
        totals.push((&wc.id, 0));
    }
    for load in loads {
        let slot = match totals.iter_mut().find(|(id, _)| *id == load.work_centre) {
            Some(slot) => slot,
            None => panic!("a load names work centre \"{}\", which is not in the list", load.work_centre),
        };
        minutes(&format!("a load on \"{}\"", load.work_centre), load.minutes);
        let sum = slot.1 + load.minutes;
        if sum > MAX_SAFE {
            panic!("the load on \"{}\" exceeds 2^53 - 1 minutes", load.work_centre);
        }
        slot.1 = sum;
    }
    work_centres
        .iter()
        .zip(totals.iter())
        .map(|(wc, (_, load_minutes))| {
            let capacity = multiply_rational(
                &rational(wc.available_minutes, 1),
                &multiply_rational(
                    &rational(wc.efficiency_basis_points, 10000),
                    &rational(wc.utilisation_basis_points, 10000),
                ),
            );
            let load = rational(*load_minutes, 1);
            let capacity_minutes = rational_to_integer(&capacity, "down");
            CapacityLoad {
                work_centre: wc.id.clone(),
                capacity_minutes,
                load_minutes: *load_minutes,
                spare_minutes: capacity_minutes - load_minutes,
                load_basis_points: if capacity.numerator == 0 {
                    None
                } else {
                    Some(rational_to_integer(
                        &multiply_rational(&divide_rational(&load, &capacity), &rational(10000, 1)),
                        "half-up",
                    ))
                },
                overloaded: compare_rational(&load, &capacity) > 0,
            }
        })
        .collect()
}

pub fn capacity_load_to_value(c: &CapacityLoad) -> Value {
    Value::obj(vec![
        ("workCentre", Value::str(&c.work_centre)),
        ("capacityMinutes", Value::Int(c.capacity_minutes)),
        ("loadMinutes", Value::Int(c.load_minutes)),
        ("spareMinutes", Value::Int(c.spare_minutes)),
        ("loadBasisPoints", c.load_basis_points.map(Value::Int).unwrap_or(Value::Null)),
        ("overloaded", Value::Bool(c.overloaded)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let centres: Vec<WorkCentre> = args[0]
        .as_arr()
        .iter()
        .map(|v| {
            let id = v.get("id").as_str();
            if let Value::Float(f) = v.get("availableMinutes") {
                panic!("availableMinutes of \"{}\" must be a whole number of minutes, not negative, received {}", id, f);
            }
            if let Value::Float(f) = v.get("efficiencyBasisPoints") {
                panic!("efficiencyBasisPoints of \"{}\" must be a whole number, not negative, received {}", id, f);
            }
            if let Value::Float(f) = v.get("utilisationBasisPoints") {
                panic!("utilisationBasisPoints of \"{}\" must be a whole number from 0 to 10000, received {}", id, f);
            }
            WorkCentre {
                id: id.to_string(),
                available_minutes: v.get("availableMinutes").as_i64(),
                efficiency_basis_points: v.get("efficiencyBasisPoints").as_i64(),
                utilisation_basis_points: v.get("utilisationBasisPoints").as_i64(),
            }
        })
        .collect();
    let loads: Vec<RoutedLoad> = args[1]
        .as_arr()
        .iter()
        .map(|v| {
            let wc = v.get("workCentre").as_str();
            if let Value::Float(f) = v.get("minutes") {
                panic!("a load on \"{}\" must be a whole number of minutes, not negative, received {}", wc, f);
            }
            RoutedLoad {
                work_centre: wc.to_string(),
                minutes: v.get("minutes").as_i64(),
            }
        })
        .collect();
    Value::Arr(work_centre_load(&centres, &loads).iter().map(capacity_load_to_value).collect())
}

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.capacity
Download for Rust manufacturing.capacity-1.0.0-rust.fune · 18,175 bytes sha256 84196e97063d74e71e7e1cd30dc2b681195ffc64e69c269e20ab99047472f888

The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./manufacturing.capacity-1.0.0-rust.fune, or fetch it from a terminal with fune pull manufacturing.capacity@1.0.0:rust.

The whole function, every language, is one file too: manufacturing.capacity-1.0.0.fune, 24,339 bytes, sha256 800880c66390d77d913aba0819f30b53e18201a1e71deffe04f2b59a02f81183. 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.capacity

after — your function gets the result and the arguments, and returns the final result.

// fune: after manufacturing.capacity

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.rational in manufacturing.capacity

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.capacity --steps.

// fune: step manufacturing.capacity 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
a week across five centres: CNC 1938 min capacity against 2000 routed (two loads) is 103.20%; the lathe's 479.52 min is overloaded by 480; a painted-out centre has no percentage; packing at 110% efficiency has 950 spare work centres ×5, loads ×5 → ×5
a load exactly equal to capacity is full, not overloaded work centres ×1, loads ×1 → ×1
one minute over is an overload work centres ×1, loads ×1 → ×1
capacity 479.52 with 479 minutes of load fits: rounded down capacity and the exact one agree work centres ×1, loads ×1 → ×1
a centre with zero capacity and no load is not overloaded work centres ×1, → ×1
a load percentage half-way between basis points rounds up: 1 minute on 20000 is 0.5 bp work centres ×1, loads ×1 → ×1
no work centres, no result , →
a load on a work centre not in the list is an error work centres ×5, loads ×1 → error: a load names work centre "WELD", which is not in the list
a work centre listed twice is an error work centres ×2, → error: duplicate work centre "CNC"
utilisation over 100% is an error work centres ×1, → error: utilisationBasisPoints of "CNC" must be a whole number from 0 to 10000
Show the other 4 tests
CaseArgumentsExpected
negative efficiency is an error work centres ×1, → error: efficiencyBasisPoints of "CNC" must be a whole number, not negative
negative available time is an error work centres ×1, → error: availableMinutes of "CNC" must be a whole number of minutes, not negative
a negative load is an error work centres ×1, loads ×1 → error: a load on "CNC" must be a whole number of minutes, not negative
a fractional load is an error work centres ×1, loads ×1 → error: a load on "CNC" must be a whole number of minutes, not negative

More from the author

With 2,400 minutes scheduled (a week of 8-hour days), 95% efficiency and 85% utilisation, a work centre can deliver 1,938 standard minutes; 2,000 minutes of routed work is 103.20% of that, overloaded by 62 minutes.

**Utilisation and efficiency.** Utilisation is the share of scheduled time the centre actually works (after breakdowns, waiting and absence), so it cannot exceed 100%. Efficiency is standard minutes earned per minute worked, and can exceed 100% when the standards are loose. Both are basis points.

**Overload is decided on the exact capacity.** Capacity is an exact fraction (`math.rational`) and a centre is overloaded when its load is more than that fraction. `capacityMinutes` reports it rounded down, which agrees with the flag because loads are whole minutes: 480 minutes of load against 479.52 minutes of capacity is overloaded, and a tool that rounded capacity to the nearest minute (480) would call it exactly full. `spareMinutes` is capacity minus load, so it is negative exactly when `overloaded` is true.

**Zero capacity.** A centre with no capacity in the period (down for maintenance, utilisation 0) has no load percentage: `loadBasisPoints` is null, and any load on it is an overload.

**Rules.** Every load must name a work centre in the list, and each centre appears once; loads for the same centre are added. Times are whole minutes, not negative. Results follow the order of `workCentres`, including centres with no load. The period is whatever the caller's available times and loads cover: a day, a week, a month.

Source: the capacity requirements planning calculation as defined in the APICS (ASCM) Dictionary, entries "capacity", "efficiency", "utilization" and "rated capacity" (rated capacity = available time x utilization x efficiency).

Files

PathBytes
README.md2,037
impl/python.py3,167
impl/rust.rs5,520
impl/typescript.ts2,786
vectors.json6,024