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