from typing import Sequence from .fleet_vehicle_depreciation_types import VehicleValue from .math_round_div import RoundingMode from .math_round_div_big import round_div_big from .money_amount import Money, money def _whole(name: str, value: int, maximum: int) -> None: if isinstance(value, bool) or not isinstance(value, int) or value < 0 or value > maximum: raise ValueError("%s must be a whole number from 0 to %d, received %r" % (name, maximum, value)) def vehicle_depreciation( cost: Money, age_months: int, mileage: int, retained_per_year_basis_points: Sequence[int], retained_per10k_miles_basis_points: int, mode: RoundingMode, ) -> VehicleValue: """A vehicle's value from its age and mileage, on the caller's retention curves. Each whole year multiplies by that year's retention and a part year by a straight-line share of it; each whole 10,000 miles multiplies by the mileage retention and the remaining miles by a straight-line share. The product is kept as one exact fraction and rounded once: rounding the value year by year drifts by a penny or more over a long life. """ if cost.minor < 0: raise ValueError("cost must not be negative, received %d" % (cost.minor,)) _whole("ageMonths", age_months, 600) _whole("mileage", mileage, 2000000) if len(retained_per_year_basis_points) == 0: raise ValueError("retainedPerYearBasisPoints must have at least one year") for bp in retained_per_year_basis_points: _whole("a retention rate", bp, 10000) _whole("a retention rate", retained_per10k_miles_basis_points, 10000) last = len(retained_per_year_basis_points) - 1 def rate_for(year: int) -> int: return retained_per_year_basis_points[min(year, last)] num, den = 1, 1 years = age_months // 12 for year in range(years): num *= rate_for(year) den *= 10000 months = age_months % 12 if months > 0: num *= 120000 - (10000 - rate_for(years)) * months den *= 120000 mile_rate = retained_per10k_miles_basis_points for _ in range(mileage // 10000): num *= mile_rate den *= 10000 miles = mileage % 10000 if miles > 0: num *= 100000000 - (10000 - mile_rate) * miles den *= 100000000 value = int(round_div_big(str(cost.minor * num), str(den), mode)) return VehicleValue( value=money(value, cost.currency), depreciation=money(cost.minor - value, cost.currency), retained_basis_points=int(round_div_big(str(num * 10000), str(den), "half-up")), )