Functional Weave
Code in TypeScript

charity.gift-aid@1.0.2

impl/python.py

2,268 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 .charity_gift_aid_data import BASIC_RATE, BASIC_RATE_HISTORY  ← this capability’s own data, compiled from data/basic-rate.json into the same file by fune build
from .charity_gift_aid_types import GiftAid
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

ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")


def _basic_rate(on_date: str) -> int:
    for row in BASIC_RATE:
        if row.valid_from <= on_date and (row.valid_to is None or on_date <= row.valid_to):
            return row.basis_points
    # A history=current build carries only the rate in force; answering an old
    # gift with today's rate would be a quiet wrong claim, so say why instead.
    if BASIC_RATE_HISTORY != "full" and len(BASIC_RATE) > 0:
        earliest = min(row.valid_from for row in BASIC_RATE)
        if on_date < earliest:
            raise ValueError(
                "no Gift Aid basic rate on %s: this build was installed with history=%s, so it only carries rules from %s. "
                "Reinstall with history=full for earlier tax years." % (on_date, BASIC_RATE_HISTORY, earliest)
            )
    raise ValueError("no Gift Aid basic rate on %s" % (on_date,))


def gift_aid(donation: Money, on_date: str) -> GiftAid:
    """The tax a charity reclaims on a Gift Aid donation. The donor is treated
    as having given the gift after deducting basic rate tax (ITA 2007 s414), so
    the charity reclaims donation x rate / (100% - rate): 25p per £1 at 20%.
    It is always the UK basic rate, for Scottish and Welsh taxpayers too.
    """
    if donation.currency != "GBP":
        raise ValueError("Gift Aid is claimed in GBP, received %s" % (donation.currency,))
    if donation.minor < 0:
        raise ValueError("donation must not be negative, received %d" % (donation.minor,))
    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,))
    rate = _basic_rate(on_date)
    # Rounded down: a claim may never exceed the tax treated as deducted.
    tax = round_div(donation.minor * rate, 10000 - rate, "down")
    return GiftAid(
        donation=money(donation.minor, "GBP"),
        tax_reclaimable=money(tax, "GBP"),
        gross=money(donation.minor + tax, "GBP"),
        basic_rate_basis_points=rate,
    )