Functional Weave
Code in TypeScript

property.council-tax-band@1.0.2

impl/python.py

2,548 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 .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .property_council_tax_band_data import BAND_RATIOS, BAND_RATIOS_HISTORY  ← this capability’s own data, compiled from data/band-ratios.json into the same file by fune build
from .property_council_tax_band_types import CouncilTaxBandCharge

_ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
_NATIONS = ("england", "wales", "scotland")
# Keeps Band D x the largest proportion exact as a JavaScript number; the same limit here.
_MAX_MINOR = 1_000_000_000_000


def council_tax_band(band_d_charge: Money, band: str, nation: str, on_date: str) -> CouncilTaxBandCharge:
    """The council tax for a band: Band D x the band's statutory proportion /
    Band D's, rounded half-up to the penny once. The proportions are dated
    rows, so a Scottish band E before April 2017 uses 440/360 and after it
    473/360."""
    if not isinstance(on_date, str) or not _ISO_DATE.fullmatch(on_date):
        raise ValueError('onDate must be an ISO date (YYYY-MM-DD), received "%s"' % (on_date,))
    if nation not in _NATIONS:
        raise ValueError('unknown nation "%s": use england, wales or scotland' % (nation,))
    if band_d_charge.currency != "GBP":
        raise ValueError("bandDCharge must be in GBP, received %s" % (band_d_charge.currency,))
    if band_d_charge.minor < 0 or band_d_charge.minor > _MAX_MINOR:
        raise ValueError(
            "bandDCharge must be between 0 and %d minor units, received %r" % (_MAX_MINOR, band_d_charge.minor)
        )
    found = None
    earliest = None
    for row in BAND_RATIOS:
        if row.nation != nation:
            continue
        if earliest is None or row.valid_from < earliest:
            earliest = row.valid_from
        if row.band != band or on_date < row.valid_from:
            continue
        if row.valid_to is not None and on_date > row.valid_to:
            continue
        found = row
    if found is None:
        if BAND_RATIOS_HISTORY != "full" and earliest is not None and on_date < earliest:
            raise ValueError(
                "no council tax band %s for %s on %s: this build was installed with history=%s, so it only carries rules from %s"
                % (band, nation, on_date, BAND_RATIOS_HISTORY, earliest)
            )
        raise ValueError("no council tax band %s for %s on %s" % (band, nation, on_date))
    return CouncilTaxBandCharge(
        band=band,
        ratio_numerator=found.numerator,
        ratio_denominator=found.denominator,
        charge=money(round_div(band_d_charge.minor * found.numerator, found.denominator, "half-up"), "GBP"),
    )