from .math_round_div import round_div from .money_amount import Money, money from .property_rental_yield_types import RentalYield # Above this, amount * 10000 is no longer exact as a JavaScript number; the # same limit applies here so every language refuses the same inputs. _MAX_MINOR = 90_000_000_000 def rental_yield(annual_rent: Money, annual_costs: Money, property_value: Money) -> RentalYield: """Gross and net rental yield in basis points. Each is rounded once, half-up, from the exact ratio, never from a rounded percentage.""" currency = property_value.currency for other in (annual_rent, annual_costs): if other.currency != currency: raise ValueError("currency mismatch: %s and %s" % (other.currency, currency)) if property_value.minor <= 0: raise ValueError("propertyValue must be greater than zero, received %r" % (property_value.minor,)) if annual_rent.minor < 0: raise ValueError("annualRent must not be negative, received %r" % (annual_rent.minor,)) if annual_costs.minor < 0: raise ValueError("annualCosts must not be negative, received %r" % (annual_costs.minor,)) for m in (annual_rent.minor, annual_costs.minor, property_value.minor): if m > _MAX_MINOR: raise ValueError("amounts must be at most %d minor units" % (_MAX_MINOR,)) net = annual_rent.minor - annual_costs.minor return RentalYield( gross_basis_points=round_div(annual_rent.minor * 10000, property_value.minor, "half-up"), net_basis_points=round_div(net * 10000, property_value.minor, "half-up"), net_income=money(net, currency), )