Functional Weave
Code in Rust

fleet.vehicle-depreciation@1.0.0

impl/python.py

2,610 bytes · the Python implementation · view raw

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 .fleet_vehicle_depreciation_types import VehicleValue
from .math_round_div import RoundingMode  ← from math.round-div ^1.0.0 · built alongside by fune
from .math_round_div_big import round_div_big  ← from math.round-div-big ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune


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")),
    )