Functional Weave
Code in Python

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.

def work_centre_load(work_centres: Sequence[WorkCentre], loads: Sequence[RoutedLoad]) -> List[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

@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
impl/python.py · 68 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.

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 result

Install

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
Download for Python manufacturing.capacity-1.0.0-python.fune · 15,684 bytes sha256 cfa3f24250329d7acca763ddff9669e86b31a9079ef26540f2c736d437314c7d

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.

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