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,
};
});
}