Functional Weave
Code in Rust

property.rental-yield@1.0.0

impl/typescript.ts

1,723 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 { type RentalYield } from "./property_rental_yield_types.ts";

// Above this, amount * 10000 is no longer exact as a JavaScript number.
const MAX_MINOR = 90_000_000_000;

/**
 * Gross and net rental yield in basis points. Each is rounded once, half-up,
 * from the exact ratio, never from a rounded percentage.
 */
export function rentalYield(annualRent: Money, annualCosts: Money, propertyValue: Money): RentalYield {
  const currency = propertyValue.currency;
  for (const other of [annualRent, annualCosts]) {
    if (other.currency !== currency) {
      throw new RangeError(`currency mismatch: ${other.currency} and ${currency}`);
    }
  }
  if (!Number.isInteger(propertyValue.minor) || propertyValue.minor <= 0) {
    throw new RangeError(`propertyValue must be greater than zero, received ${propertyValue.minor}`);
  }
  if (!Number.isInteger(annualRent.minor) || annualRent.minor < 0) {
    throw new RangeError(`annualRent must not be negative, received ${annualRent.minor}`);
  }
  if (!Number.isInteger(annualCosts.minor) || annualCosts.minor < 0) {
    throw new RangeError(`annualCosts must not be negative, received ${annualCosts.minor}`);
  }
  for (const m of [annualRent.minor, annualCosts.minor, propertyValue.minor]) {
    if (m > MAX_MINOR) throw new RangeError(`amounts must be at most ${MAX_MINOR} minor units`);
  }
  const net = annualRent.minor - annualCosts.minor;
  return {
    grossBasisPoints: roundDiv(annualRent.minor * 10000, propertyValue.minor, "half-up"),
    netBasisPoints: roundDiv(net * 10000, propertyValue.minor, "half-up"),
    netIncome: money(net, currency),
  };
}