Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import Sequence
from .lending_ltv_types import LtvBand, LtvResult
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money ← from money.amount ^1.0.0 · built alongside by fune
#: Above this, loan * 10000 is no longer exact as a JavaScript number.
_MAX_LOAN = 900719925474
def loan_to_value(loan: Money, property_value: Money, bands: Sequence[LtvBand]) -> LtvResult:
"""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.
"""
if loan.currency != property_value.currency:
raise ValueError("currency mismatch: %s and %s" % (loan.currency, property_value.currency))
if property_value.minor <= 0:
raise ValueError("propertyValue must be greater than zero, received %r" % (property_value.minor,))
if loan.minor < 0:
raise ValueError("loan must not be negative, received %r" % (loan.minor,))
if loan.minor > _MAX_LOAN:
raise ValueError("loan must be at most %d minor units" % (_MAX_LOAN,))
if len(bands) == 0:
raise ValueError("bands must not be empty")
for i, band in enumerate(bands):
ceiling = band.max_basis_points
if isinstance(ceiling, bool) or not isinstance(ceiling, int) or ceiling < 1:
raise ValueError("band ceilings must be whole basis points of 1 or more, received %r" % (ceiling,))
if i > 0 and ceiling <= bands[i - 1].max_basis_points:
raise ValueError("band ceilings must be in strictly ascending order")
basis_points = round_div(loan.minor * 10000, property_value.minor, "up")
for band in bands:
if basis_points <= band.max_basis_points:
return LtvResult(basis_points=basis_points, band=band.label)
return LtvResult(basis_points=basis_points, band=None)