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 sparework_centre_load(work centres ×1, loads ×1)→ ×1 a load exactly equal to capacity is full, not overloadedwork_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.
def work_centre_load(work_centres: Sequence[WorkCentre], loads: Sequence[RoutedLoad]) -> List[CapacityLoad]
| work_centres | WorkCentre[] | each work centre once, with its available time in the period |
| loads | RoutedLoad[] | routed minutes from orders in the same period; several per centre are added |
| returns | CapacityLoad[] | one entry per work centre, in the order given |
The types it declares, generated into your project
@dataclass(frozen=True)
class WorkCentre:
"""A work centre and the time it can offer in the period."""
id: str
#: scheduled time in the period: shifts x hours x machines
available_minutes: int
#: standard minutes produced per minute worked: 9500 = 95%; may exceed 10000
efficiency_basis_points: int
#: share of scheduled time actually worked: 8500 = 85%; at most 10000
utilisation_basis_points: int
@dataclass(frozen=True)
class RoutedLoad:
"""Standard minutes of work routed to a work centre."""
work_centre: str
minutes: int
@dataclass(frozen=True)
class CapacityLoad:
"""One work centre's capacity, load and verdict."""
work_centre: str
#: available x efficiency x utilisation, rounded down
capacity_minutes: int
#: the routed load, summed
load_minutes: int
#: capacityMinutes - loadMinutes; negative when overloaded
spare_minutes: int
#: load as a share of capacity, rounded half-up; null when capacity is zero
load_basis_points: Optional[int]
#: load is more than the exact capacity
overloaded: bool
Your code names it in one line, in the file that uses it
from fune.manufacturing.capacity import work_centre_load # manufacturing.capacity@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import Dict, List, Sequence
from .manufacturing_capacity_types import CapacityLoad, RoutedLoad, WorkCentre
from .math_rational import compare_rational, divide_rational, multiply_rational, rational, rational_to_integer ← from math.rational ^1.0.0 · built alongside by fune
MAX_SAFE = 2**53 - 1
def _is_whole(value: int) -> bool:
return not isinstance(value, bool) and isinstance(value, int) and -MAX_SAFE <= value <= MAX_SAFE
def _minutes(what: str, value: int) -> None:
if not _is_whole(value) or value < 0:
raise ValueError("%s must be a whole number of minutes, not negative, received %r" % (what, value))
def work_centre_load(work_centres: Sequence[WorkCentre], loads: Sequence[RoutedLoad]) -> List[CapacityLoad]:
"""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.
"""
totals: Dict[str, int] = {}
for wc in work_centres:
if wc.id in totals:
raise ValueError('duplicate work centre "%s"' % (wc.id,))
_minutes('availableMinutes of "%s"' % (wc.id,), wc.available_minutes)
if not _is_whole(wc.efficiency_basis_points) or wc.efficiency_basis_points < 0:
raise ValueError(
'efficiencyBasisPoints of "%s" must be a whole number, not negative, received %r'
% (wc.id, wc.efficiency_basis_points)
)
if not _is_whole(wc.utilisation_basis_points) or not 0 <= wc.utilisation_basis_points <= 10000:
raise ValueError(
'utilisationBasisPoints of "%s" must be a whole number from 0 to 10000, received %r'
% (wc.id, wc.utilisation_basis_points)
)
totals[wc.id] = 0
for load in loads:
if load.work_centre not in totals:
raise ValueError('a load names work centre "%s", which is not in the list' % (load.work_centre,))
_minutes('a load on "%s"' % (load.work_centre,), load.minutes)
total = totals[load.work_centre] + load.minutes
if total > MAX_SAFE:
raise ValueError('the load on "%s" exceeds 2^53 - 1 minutes' % (load.work_centre,))
totals[load.work_centre] = total
result: List[CapacityLoad] = []
for wc in work_centres:
capacity = multiply_rational(
rational(wc.available_minutes, 1),
multiply_rational(rational(wc.efficiency_basis_points, 10000), rational(wc.utilisation_basis_points, 10000)),
)
load_minutes = totals[wc.id]
load = rational(load_minutes, 1)
capacity_minutes = rational_to_integer(capacity, "down")
result.append(
CapacityLoad(
work_centre=wc.id,
capacity_minutes=capacity_minutes,
load_minutes=load_minutes,
spare_minutes=capacity_minutes - load_minutes,
load_basis_points=None
if capacity.numerator == 0
else rational_to_integer(multiply_rational(divide_rational(load, capacity), rational(10000, 1)), "half-up"),
overloaded=compare_rational(load, capacity) > 0,
)
)
return resultInstall
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Python 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.capacity
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./manufacturing.capacity-1.0.0-python.fune, or fetch it from a terminal with fune pull manufacturing.capacity@1.0.0:python.
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.
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Case | Arguments | Expected | |
|---|---|---|---|
| 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
| Path | Bytes |
|---|---|
| README.md | 2,037 |
| impl/python.py | 3,167 |
| impl/rust.rs | 5,520 |
| impl/typescript.ts | 2,786 |
| vectors.json | 6,024 |