Functional Weave
Code in Python

fleet.company-car-bik@1.0.2

impl/python.py

7,316 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.

import re
from typing import NoReturn, Optional

from .dates_days_between import days_between  ← from dates.days-between ^1.0.0 · built alongside by fune
from .dates_uk_tax_year import uk_tax_year  ← from dates.uk-tax-year ^1.0.0 · built alongside by fune
from .fleet_company_car_bik_data import BIK_BANDS, BIK_BANDS_HISTORY, BIK_LIMITS, BIK_LIMITS_HISTORY, FUEL_MULTIPLIERS, FUEL_MULTIPLIERS_HISTORY  ← this capability’s own data, compiled from data/bik-bands.json into the same file by fune build
from .fleet_company_car_bik_types import CompanyCarBik
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_add import add_money  ← from money.add ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune

ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
MAX_SAFE = 9007199254740991
# Cars first registered before this date keep their NEDC figures, and in
# 2021-22 their own (one point higher) column of the table.
WLTP_FROM = "2020-04-06"


def _in_force(row, on_date: str) -> bool:
    return row.valid_from <= on_date and (row.valid_to is None or on_date <= row.valid_to)


def _missing(what: str, label: str, rows, history: str, on_date: str) -> NoReturn:
    # A history=current build has dropped old rows; say so rather than
    # answering an old tax year with today's figures.
    if history != "full":
        earliest: Optional[str] = None
        for row in rows:
            if earliest is None or row.valid_from < earliest:
                earliest = row.valid_from
        if earliest is not None and on_date < earliest:
            raise ValueError(
                "no %s for tax year %s: this build was installed with history=%s, "
                "so it only carries figures from %s. Reinstall with history=full for earlier years."
                % (what, label, history, earliest)
            )
    raise ValueError("no %s for tax year %s" % (what, label))


def _check_gbp(name: str, amount: Money) -> None:
    if amount.currency != "GBP":
        raise ValueError("company car benefit is worked in GBP; %s is in %s" % (name, amount.currency))
    if amount.minor < 0:
        raise ValueError("%s must not be negative, received %d" % (name, amount.minor))


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


def _check_date(name: str, value) -> None:
    if not isinstance(value, str) or not ISO_DATE.fullmatch(value):
        raise ValueError('%s must be an ISO date (YYYY-MM-DD), received "%s"' % (name, value))


def _proportion(minor: int, basis_points: int, days_available: int, days_in_year: int) -> int:
    # price x basis points x days available / (10000 x days in year), rounded
    # down to the penny: one rounding, and never more benefit than the law charges.
    product = minor * basis_points * days_available
    # Python would carry on, but TypeScript cannot, and all three must agree.
    if product > MAX_SAFE:
        raise ValueError("the benefit calculation exceeds 2^53 - 1; the list price is too large")
    return round_div(product, 10000 * days_in_year, "down")


def company_car_bik(
    list_price: Money,
    capital_contributions: Money,
    co2: int,
    electric_range_miles: int,
    diesel_supplement: bool,
    first_registered: str,
    on_date: str,
    unavailable_days: int,
    private_use_contributions: Money,
    free_fuel: bool,
) -> CompanyCarBik:
    """The taxable benefit of a company car for one UK tax year (ITEPA 2003 s121).

    Price less capital contributions (at most 5,000.00), times the appropriate
    percentage for the car's CO2 figure and electric range in that year, cut
    pro rata for days the car was unavailable, less what the employee paid for
    private use. Free fuel adds the year's fuel benefit multiplier times the
    same percentage, cut for the same days.
    """
    _check_gbp("listPrice", list_price)
    _check_gbp("capitalContributions", capital_contributions)
    _check_gbp("privateUseContributions", private_use_contributions)
    _check_whole("co2", co2, "g/km")
    _check_whole("electricRangeMiles", electric_range_miles, "miles")
    _check_date("firstRegistered", first_registered)
    _check_date("onDate", on_date)
    if diesel_supplement and co2 == 0:
        raise ValueError("a zero-emission car cannot take the diesel supplement")

    year = uk_tax_year(on_date)
    if first_registered > year.end:
        raise ValueError("firstRegistered (%s) is after the end of tax year %s" % (first_registered, year.label))
    days_in_year = days_between(year.start, year.end) + 1
    if (
        isinstance(unavailable_days, bool)
        or not isinstance(unavailable_days, int)
        or unavailable_days < 0
        or unavailable_days > days_in_year
    ):
        raise ValueError(
            "unavailableDays must be a whole number from 0 to %d, received %s" % (days_in_year, unavailable_days)
        )
    days_available = days_in_year - unavailable_days
    registration = "before-2020-04-06" if first_registered < WLTP_FROM else "from-2020-04-06"

    def applies(row) -> bool:
        return row.registration == "any" or row.registration == registration

    limit = next((row for row in BIK_LIMITS if applies(row) and _in_force(row, on_date)), None)
    if limit is None:
        _missing("company car benefit rates", year.label, BIK_LIMITS, BIK_LIMITS_HISTORY, on_date)

    if co2 >= 75:
        # s139(3): one point per whole 5 g/km above 75, up to the year's maximum.
        basis_points = min(limit.from75_basis_points + 100 * ((co2 - 75) // 5), limit.maximum_basis_points)
    else:
        band = next(
            (
                row
                for row in BIK_BANDS
                if applies(row)
                and _in_force(row, on_date)
                and row.co2_from <= co2 <= row.co2_to
                and row.range_from <= electric_range_miles
                and (row.range_to is None or electric_range_miles <= row.range_to)
            ),
            None,
        )
        if band is None:
            _missing("company car benefit band", year.label, BIK_BANDS, BIK_BANDS_HISTORY, on_date)
        basis_points = band.basis_points
    if diesel_supplement:
        basis_points = min(basis_points + limit.diesel_supplement_basis_points, limit.maximum_basis_points)

    counted = min(capital_contributions.minor, limit.capital_contribution_cap_minor)
    price = money(max(list_price.minor - counted, 0), "GBP")
    provisional = _proportion(price.minor, basis_points, days_available, days_in_year)
    car_benefit = money(max(provisional - private_use_contributions.minor, 0), "GBP")

    fuel_benefit = money(0, "GBP")
    # s149(4): electricity, and energy for a car that cannot emit CO2, is not fuel.
    if free_fuel and co2 > 0:
        multiplier = next((row for row in FUEL_MULTIPLIERS if _in_force(row, on_date)), None)
        if multiplier is None:
            _missing("car fuel benefit multiplier", year.label, FUEL_MULTIPLIERS, FUEL_MULTIPLIERS_HISTORY, on_date)
        fuel_benefit = money(_proportion(multiplier.pounds * 100, basis_points, days_available, days_in_year), "GBP")

    return CompanyCarBik(
        tax_year=year.label,
        days_in_year=days_in_year,
        days_available=days_available,
        price=price,
        appropriate_basis_points=basis_points,
        car_benefit=car_benefit,
        fuel_benefit=fuel_benefit,
        total=add_money(car_benefit, fuel_benefit),
    )