Functional Weave
Code in TypeScript

agri.milk-price@1.0.0

impl/typescript.ts

3,398 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 { money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type MilkContract, type MilkPrice, type MilkSupply } from "./agri_milk_price_types.ts";

const MAX_PRICE = 1000000;
const MAX_LITRES = 1000000000;

function whole(value: number, name: string, min: number, max: number): number {
  if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) {
    throw new RangeError(`${name} must be a whole number from ${min} to ${max}, received ${value}`);
  }
  return value;
}

/**
 * Milk price for one period under a buyer's contract.
 *
 * Prices are thousandths of a penny (or cent) per litre, because contracts
 * quote to 0.01 or 0.001 ppl. Constituent adjustments are pro rata per 0.1
 * percentage point and are each rounded to a thousandth, half away from
 * zero; the payment is litres x price, rounded once to the penny.
 */
export function milkPrice(contract: MilkContract, supply: MilkSupply): MilkPrice {
  money(0, contract.currency);
  const base = whole(contract.basePrice, "basePrice", 0, MAX_PRICE);
  const stdFat = whole(contract.standardButterfatBasisPoints, "standardButterfatBasisPoints", 0, 10000);
  const fatValue = whole(contract.butterfatValue, "butterfatValue", -MAX_PRICE, MAX_PRICE);
  const stdProtein = whole(contract.standardProteinBasisPoints, "standardProteinBasisPoints", 0, 10000);
  const proteinValue = whole(contract.proteinValue, "proteinValue", -MAX_PRICE, MAX_PRICE);
  const litres = whole(supply.litres, "litres", 0, MAX_LITRES);
  const fat = whole(supply.butterfatBasisPoints, "butterfatBasisPoints", 0, 10000);
  const protein = whole(supply.proteinBasisPoints, "proteinBasisPoints", 0, 10000);
  const month = whole(supply.month, "month", 1, 12);

  // 0.1 percentage point is 10 basis points.
  const fatAdjustment = roundDiv((fat - stdFat) * fatValue, 10, "half-up");
  const proteinAdjustment = roundDiv((protein - stdProtein) * proteinValue, 10, "half-up");

  let volumeBonus = 0;
  let previous = -1;
  contract.volumeBonuses.forEach((tier, i) => {
    const minimum = whole(tier.minimumLitres, `volumeBonuses[${i}].minimumLitres`, 0, MAX_LITRES);
    const bonus = whole(tier.bonus, `volumeBonuses[${i}].bonus`, -MAX_PRICE, MAX_PRICE);
    if (minimum <= previous) throw new RangeError("volumeBonuses must be in strictly ascending order of minimumLitres");
    previous = minimum;
    if (litres >= minimum) volumeBonus = bonus;
  });

  let seasonal = 0;
  if (contract.seasonality.length === 12) {
    contract.seasonality.forEach((v, i) => whole(v, `seasonality[${i}]`, -MAX_PRICE, MAX_PRICE));
    seasonal = contract.seasonality[month - 1];
  } else if (contract.seasonality.length !== 0) {
    throw new RangeError(`seasonality must have 12 monthly values or none, received ${contract.seasonality.length}`);
  }

  const price = base + fatAdjustment + proteinAdjustment + volumeBonus + seasonal;
  if (price < 0) throw new RangeError(`price per litre must not be negative, received ${price}`);
  if (price > MAX_PRICE) throw new RangeError(`price per litre must be at most ${MAX_PRICE}, received ${price}`);
  return {
    basePrice: base,
    butterfatAdjustment: fatAdjustment,
    proteinAdjustment,
    volumeBonus,
    seasonalAdjustment: seasonal,
    pricePerLitre: price,
    total: money(roundDiv(price * litres, 1000, "half-up"), contract.currency),
  };
}