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 Optional
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
from .property_rent_review_types import RentReviewResult, RentReviewRule
_MAX_RENT = 1_000_000_000
_MAX_INDEX = 1_000_000
def _is_int(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool)
def _check_basis_points(name: str, value: Optional[int]) -> None:
if value is None:
return
if not _is_int(value) or value <= -10000 or value > 100000:
raise ValueError("%s must be an integer from -9999 to 100000, received %r" % (name, value))
def rent_review(
passing_rent: Money,
base_index: int,
review_index: int,
collar_basis_points: Optional[int],
cap_basis_points: Optional[int],
upward_only: bool,
) -> RentReviewResult:
"""The rent after an index-linked review, held between an optional collar
and cap, and not below the passing rent when upward-only. Every comparison
is on exact fractions and the rent is rounded half-up once."""
if passing_rent.minor < 0 or passing_rent.minor > _MAX_RENT:
raise ValueError(
"passingRent must be between 0 and %d minor units, received %r" % (_MAX_RENT, passing_rent.minor)
)
for name, value in (("baseIndex", base_index), ("reviewIndex", review_index)):
if not _is_int(value) or value <= 0 or value > _MAX_INDEX:
raise ValueError("%s must be an integer from 1 to %d, received %r" % (name, _MAX_INDEX, value))
_check_basis_points("collarBasisPoints", collar_basis_points)
_check_basis_points("capBasisPoints", cap_basis_points)
if collar_basis_points is not None and cap_basis_points is not None and collar_basis_points > cap_basis_points:
raise ValueError("collar %d is above cap %d" % (collar_basis_points, cap_basis_points))
p = passing_rent.minor
# The multiplier as a fraction num/den: the index ratio, or 1 + collar/cap.
num, den = review_index, base_index
rule: RentReviewRule = "index"
if cap_basis_points is not None and review_index * 10000 > base_index * (10000 + cap_basis_points):
num, den, rule = 10000 + cap_basis_points, 10000, "cap"
elif collar_basis_points is not None and review_index * 10000 < base_index * (10000 + collar_basis_points):
num, den, rule = 10000 + collar_basis_points, 10000, "collar"
if upward_only and num < den:
num, den, rule = 1, 1, "upward-only"
return RentReviewResult(
new_rent=money(round_div(p * num, den, "half-up"), passing_rent.currency),
index_rent=money(round_div(p * review_index, base_index, "half-up"), passing_rent.currency),
index_change_basis_points=round_div((review_index - base_index) * 10000, base_index, "half-up"),
applied_by=rule,
)