Functional Weave
Code in TypeScript

inventory.safety-stock@1.0.0

impl/typescript.ts

2,617 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 { Z_SCORES } from "./inventory_safety_stock_data.ts";  ← this capability’s own data, compiled from data/z-scores.json into the same file by fune build
import { type SafetyStockInput } from "./inventory_safety_stock_types.ts";

function amount(name: string, value: number | null, method: string): number {
  if (value === null || value === undefined) {
    throw new RangeError(`${method} needs ${name}`);
  }
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
    throw new RangeError(`${name} must be a finite number, not negative, received ${value}`);
  }
  return value;
}

// Six decimal places, half away from zero, then up to whole units: float noise
// such as 55.00000000000001 must not cost a whole extra unit.
function wholeUnitsUp(x: number): number {
  const y = x * 1e6;
  let micro = Math.floor(y);
  if (y - micro >= 0.5) micro += 1;
  return Math.floor((micro + 999999) / 1e6);
}

/** Safety stock in whole units, by the service-level or max-minus-average method. */
export function safetyStock(input: SafetyStockInput): number {
  const leadTime = amount("leadTime", input.leadTime, input.method);
  if (input.method === "service-level") {
    const level = input.serviceLevelBasisPoints;
    if (level === null || level === undefined) throw new RangeError("service-level needs serviceLevelBasisPoints");
    const row = Z_SCORES.find((r) => r.serviceLevelBasisPoints === level);
    if (!row) {
      throw new RangeError(`no z-score for a service level of ${level} basis points: use one of ${Z_SCORES.map((r) => r.serviceLevelBasisPoints).join(", ")}`);
    }
    const sd = amount("demandStdDev", input.demandStdDev, input.method);
    let variance = leadTime * sd * sd;
    const sl = input.leadTimeStdDev ?? 0;
    amount("leadTimeStdDev", sl, input.method);
    if (sl > 0) {
      const d = amount("averageDemand", input.averageDemand, "service-level with a varying lead time");
      variance = variance + d * d * sl * sl;
    }
    return wholeUnitsUp((row.zTenThousandths / 10000) * Math.sqrt(variance));
  }
  if (input.method === "max-minus-average") {
    const maxDemand = amount("maxDemand", input.maxDemand, input.method);
    const maxLeadTime = amount("maxLeadTime", input.maxLeadTime, input.method);
    const averageDemand = amount("averageDemand", input.averageDemand, input.method);
    const worst = maxDemand * maxLeadTime;
    const usual = averageDemand * leadTime;
    if (worst < usual) {
      throw new RangeError(`maxDemand x maxLeadTime (${worst}) must not be less than averageDemand x leadTime (${usual})`);
    }
    return wholeUnitsUp(worst - usual);
  }
  throw new RangeError(`unknown safety stock method "${input.method}"`);
}