Functional Weave
Code in Rust

fleet.service-due@1.0.0

impl/python.py

3,160 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 Optional

from .dates_add_days import add_days  ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_add_months import add_months  ← from dates.add-months ^1.0.0 · built alongside by fune
from .dates_days_between import days_between  ← from dates.days-between ^1.0.0 · built alongside by fune
from .fleet_service_due_types import ServiceDue, ServiceTrigger
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune


def _whole_number(name: str, value: int) -> None:
    if isinstance(value, bool) or not isinstance(value, int) or value < 0:
        raise ValueError("%s must be a whole number, 0 or more, received %r" % (name, value))


def service_due(
    last_service_date: str,
    last_service_mileage: int,
    interval_months: int,
    interval_miles: int,
    on_date: str,
    current_mileage: int,
) -> ServiceDue:
    """The next service, by time or mileage, whichever comes first.

    "Whichever comes first" needs a date for the mileage limit too, so it is
    projected from the average daily mileage since the last service, rounded up
    to a whole day: a service booked for the projected day is never late on
    that estimate.
    """
    _whole_number("lastServiceMileage", last_service_mileage)
    _whole_number("intervalMonths", interval_months)
    _whole_number("intervalMiles", interval_miles)
    _whole_number("currentMileage", current_mileage)
    if interval_months == 0 and interval_miles == 0:
        raise ValueError("at least one of intervalMonths and intervalMiles must be above zero")
    days_since = days_between(last_service_date, on_date)
    if days_since < 0:
        raise ValueError("onDate %s is before the last service on %s" % (on_date, last_service_date))
    miles_since = current_mileage - last_service_mileage
    if miles_since < 0:
        raise ValueError(
            "currentMileage %d is below the mileage at the last service, %d" % (current_mileage, last_service_mileage)
        )

    due_date = add_months(last_service_date, interval_months) if interval_months > 0 else None
    days_remaining = None if due_date is None else days_between(on_date, due_date)
    due_mileage = last_service_mileage + interval_miles if interval_miles > 0 else None
    miles_remaining = None if due_mileage is None else due_mileage - current_mileage

    projected: Optional[str] = None
    mileage_date: Optional[str] = None
    if miles_remaining is not None:
        if miles_remaining <= 0:
            mileage_date = on_date
        elif days_since > 0 and miles_since > 0:
            projected = add_days(on_date, round_div(miles_remaining * days_since, miles_since, "up"))
            mileage_date = projected

    next_due: Optional[str] = due_date
    due_by: Optional[ServiceTrigger] = None if due_date is None else "date"
    if mileage_date is not None and (next_due is None or mileage_date < next_due):
        next_due = mileage_date
        due_by = "mileage"

    return ServiceDue(
        due_date=due_date,
        due_mileage=due_mileage,
        days_remaining=days_remaining,
        miles_remaining=miles_remaining,
        projected_mileage_date=projected,
        next_due_date=next_due,
        due_by=due_by,
        overdue=(days_remaining is not None and days_remaining < 0)
        or (miles_remaining is not None and miles_remaining < 0),
    )