Functional Weave
Code in TypeScript

charity.fundraising-ratio@1.0.0

impl/typescript.ts

2,011 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 { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { currencyDigits } from "./money_currency_digits.ts";  ← from money.currency-digits ^1.0.0 · built alongside by fune
import { type FundraisingRatios } from "./charity_fundraising_ratio_types.ts";

function check(name: string, currency: string, amount: Money): void {
  if (amount.currency !== currency) throw new RangeError(`currency mismatch: ${amount.currency} and ${currency}`);
  if (amount.minor < 0) throw new RangeError(`${name} must not be negative, received ${amount.minor}`);
}

/**
 * Cost to raise £1 and the share of spending that went on the charity's
 * purposes. Each ratio is divided once from the integer amounts and rounded
 * half up, so the figures do not depend on the order of any float arithmetic.
 */
export function fundraisingRatios(
  fundraisingCosts: Money,
  fundsRaised: Money,
  charitableSpend: Money,
  totalExpenditure: Money
): FundraisingRatios {
  const currency = fundraisingCosts.currency;
  check("fundraisingCosts", currency, fundraisingCosts);
  check("fundsRaised", currency, fundsRaised);
  check("charitableSpend", currency, charitableSpend);
  check("totalExpenditure", currency, totalExpenditure);
  if (fundsRaised.minor === 0) throw new RangeError("fundsRaised must be positive to work out a cost to raise");
  if (totalExpenditure.minor === 0) throw new RangeError("totalExpenditure must be positive to work out a charitable spend ratio");
  if (charitableSpend.minor > totalExpenditure.minor) {
    throw new RangeError(`charitableSpend of ${charitableSpend.minor} is more than totalExpenditure of ${totalExpenditure.minor}`);
  }
  const unit = 10 ** currencyDigits(currency);
  return {
    costToRaiseOne: money(roundDiv(fundraisingCosts.minor * unit, fundsRaised.minor, "half-up"), currency),
    costRatioBasisPoints: roundDiv(fundraisingCosts.minor * 10000, fundsRaised.minor, "half-up"),
    charitableSpendBasisPoints: roundDiv(charitableSpend.minor * 10000, totalExpenditure.minor, "half-up"),
  };
}