Functional Weave
Code in Rust

manufacturing.capacity@1.0.0

impl/typescript.ts

2,786 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { compareRational, divideRational, multiplyRational, rational, rationalToInteger } from "./math_rational.ts";  ← from math.rational ^1.0.0 · built alongside by fune
import { type CapacityLoad, type RoutedLoad, type WorkCentre } from "./manufacturing_capacity_types.ts";

function minutes(what: string, value: number): void {
  if (!Number.isSafeInteger(value) || value < 0) {
    throw new RangeError(`${what} must be a whole number of minutes, not negative, received ${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.
 */
export function workCentreLoad(workCentres: readonly WorkCentre[], loads: readonly RoutedLoad[]): readonly CapacityLoad[] {
  const totals = new Map<string, number>();
  for (const wc of workCentres) {
    if (totals.has(wc.id)) {
      throw new RangeError(`duplicate work centre "${wc.id}"`);
    }
    minutes(`availableMinutes of "${wc.id}"`, wc.availableMinutes);
    if (!Number.isSafeInteger(wc.efficiencyBasisPoints) || wc.efficiencyBasisPoints < 0) {
      throw new RangeError(`efficiencyBasisPoints of "${wc.id}" must be a whole number, not negative, received ${wc.efficiencyBasisPoints}`);
    }
    if (!Number.isInteger(wc.utilisationBasisPoints) || wc.utilisationBasisPoints < 0 || wc.utilisationBasisPoints > 10000) {
      throw new RangeError(`utilisationBasisPoints of "${wc.id}" must be a whole number from 0 to 10000, received ${wc.utilisationBasisPoints}`);
    }
    totals.set(wc.id, 0);
  }
  for (const load of loads) {
    const total = totals.get(load.workCentre);
    if (total === undefined) {
      throw new RangeError(`a load names work centre "${load.workCentre}", which is not in the list`);
    }
    minutes(`a load on "${load.workCentre}"`, load.minutes);
    const sum = total + load.minutes;
    if (!Number.isSafeInteger(sum)) {
      throw new RangeError(`the load on "${load.workCentre}" exceeds 2^53 - 1 minutes`);
    }
    totals.set(load.workCentre, sum);
  }
  return workCentres.map((wc) => {
    const capacity = multiplyRational(
      rational(wc.availableMinutes, 1),
      multiplyRational(rational(wc.efficiencyBasisPoints, 10000), rational(wc.utilisationBasisPoints, 10000)),
    );
    const loadMinutes = totals.get(wc.id) as number;
    const load = rational(loadMinutes, 1);
    const capacityMinutes = rationalToInteger(capacity, "down");
    return {
      workCentre: wc.id,
      capacityMinutes,
      loadMinutes,
      spareMinutes: capacityMinutes - loadMinutes,
      loadBasisPoints:
        capacity.numerator === 0
          ? null
          : rationalToInteger(multiplyRational(divideRational(load, capacity), rational(10000, 1)), "half-up"),
      overloaded: compareRational(load, capacity) > 0,
    };
  });
}