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 } from "./money_amount.ts"; ← from money.amount ^1.0.0 · built alongside by fune
import { type LtvBand, type LtvResult } from "./lending_ltv_types.ts";
/** Above this, loan × 10000 is no longer exact as a JavaScript number. */
const MAX_LOAN = 900719925474;
/**
* Loan-to-value, and the band from the lender's table. The band is chosen on
* the exact ratio, loan × 10000 <= ceiling × value, never on a rounded
* percentage, so 75.004% is not "75%". The reported basis points are rounded
* up, which agrees: a whole ceiling is met exactly when the rounded-up ratio
* is within it.
*/
export function loanToValue(loan: Money, propertyValue: Money, bands: readonly LtvBand[]): LtvResult {
if (loan.currency !== propertyValue.currency) {
throw new RangeError(`currency mismatch: ${loan.currency} and ${propertyValue.currency}`);
}
if (!Number.isInteger(propertyValue.minor) || propertyValue.minor <= 0) {
throw new RangeError(`propertyValue must be greater than zero, received ${propertyValue.minor}`);
}
if (!Number.isInteger(loan.minor) || loan.minor < 0) {
throw new RangeError(`loan must not be negative, received ${loan.minor}`);
}
if (loan.minor > MAX_LOAN) {
throw new RangeError(`loan must be at most ${MAX_LOAN} minor units`);
}
if (bands.length === 0) {
throw new RangeError("bands must not be empty");
}
for (let i = 0; i < bands.length; i += 1) {
const max = bands[i].maxBasisPoints;
if (!Number.isInteger(max) || max < 1) {
throw new RangeError(`band ceilings must be whole basis points of 1 or more, received ${max}`);
}
if (i > 0 && max <= bands[i - 1].maxBasisPoints) {
throw new RangeError("band ceilings must be in strictly ascending order");
}
}
const basisPoints = roundDiv(loan.minor * 10000, propertyValue.minor, "up");
const found = bands.find((band) => basisPoints <= band.maxBasisPoints);
return { basisPoints, band: found === undefined ? null : found.label };
}