Functional Weave
Code in TypeScript

manufacturing.variance@1.0.0

impl/typescript.ts

3,265 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 { type Rational, multiplyRational, rational, rationalToInteger } from "./math_rational.ts";  ← from math.rational ^1.0.0 · built alongside by fune
import { type RoundingMode } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, assertSameCurrency, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type CostVariances, type LabourActual, type MaterialActual, type Variance } from "./manufacturing_variance_types.ts";

function notNegative(name: string, value: Rational): Rational {
  const r = rational(value.numerator, value.denominator);
  if (r.numerator < 0) {
    throw new RangeError(`${name} must not be negative`);
  }
  return r;
}

function costOf(name: string, value: Money, currency: Money): number {
  assertSameCurrency(currency, value);
  if (value.minor < 0) {
    throw new RangeError(`${name} must not be negative, received ${value.minor}`);
  }
  return value.minor;
}

function variance(signed: number, currency: string): Variance {
  return {
    amount: money(Math.abs(signed), currency),
    effect: signed > 0 ? "adverse" : signed < 0 ? "favourable" : "none",
  };
}

/**
 * Material price and usage, labour rate and efficiency variances, flexed to
 * actual output. The four flexed costs are each rounded once and every
 * variance is a difference of them, so the variances always sum exactly.
 */
export function costVariances(material: MaterialActual, labour: LabourActual, actualOutput: number, mode: RoundingMode): CostVariances {
  if (!Number.isSafeInteger(actualOutput) || actualOutput < 0) {
    throw new RangeError(`actualOutput must be a whole number, not negative, received ${actualOutput}`);
  }
  const unit = material.standardPrice;
  const currency = unit.currency;
  const sp = costOf("material standardPrice", material.standardPrice, unit);
  const matActual = costOf("material actualCost", material.actualCost, unit);
  const sr = costOf("labour standardRate", labour.standardRate, unit);
  const labActual = costOf("labour actualCost", labour.actualCost, unit);
  const sq = notNegative("material standardQuantity", material.standardQuantity);
  const aq = notNegative("material actualQuantity", material.actualQuantity);
  const sh = notNegative("labour standardHours", labour.standardHours);
  const ah = notNegative("labour actualHours", labour.actualHours);
  const output = rational(actualOutput, 1);

  const actualAtStandard = rationalToInteger(multiplyRational(aq, rational(sp, 1)), mode);
  const standardMaterial = rationalToInteger(multiplyRational(multiplyRational(sq, output), rational(sp, 1)), mode);
  const hoursAtStandard = rationalToInteger(multiplyRational(ah, rational(sr, 1)), mode);
  const standardLabour = rationalToInteger(multiplyRational(multiplyRational(sh, output), rational(sr, 1)), mode);

  const standardCost = standardMaterial + standardLabour;
  const actualCost = matActual + labActual;
  return {
    materialPrice: variance(matActual - actualAtStandard, currency),
    materialUsage: variance(actualAtStandard - standardMaterial, currency),
    labourRate: variance(labActual - hoursAtStandard, currency),
    labourEfficiency: variance(hoursAtStandard - standardLabour, currency),
    total: variance(actualCost - standardCost, currency),
    standardCost: money(standardCost, currency),
    actualCost: money(actualCost, currency),
  };
}