Functional Weave
Code in TypeScript

payroll.tax-code-parse@1.0.1

impl/python.py

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

from .money_amount import money  ← from money.amount ^1.0.0 · built alongside by fune
from .payroll_tax_code_parse_types import TaxCode

# The whitespace every language trims alike. str.strip() and \s also take
# \x1c-\x1f and \x85, JavaScript's trim() and \s also take \ufeff, and Rust's
# trim() takes \x85, so each implementation names the same set instead.
SPACE = " \t\n\r\x0b\x0c\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000"

# The basis marker may be written with or without a space: "1257L W1", "1257LM1", "1257L X".
BASIS = re.compile(r"(.*?)[ \t\n\r\x0b\x0c\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]*(W1|M1|X)")
ALLOWANCE = re.compile(r"(0|[1-9][0-9]{0,5})([LMNT])")
K_CODE = re.compile(r"K([1-9][0-9]{0,5})")
D_CODE = re.compile(r"D([0-9])")


def parse_tax_code(code: str) -> TaxCode:
    """Take a UK PAYE tax code apart into its region, kind, allowance and basis.

    The annual allowance is number x 10 + 9 pounds, not number x 10: a code
    covers a 10-pound band of allowances and HMRC's routines give the employee
    the top of it, so 1257L is worth 12,579 pounds a year to the payroll.
    """
    original = code
    text = code.strip(SPACE).upper()
    if text == "":
        raise ValueError('unrecognised tax code "%s"' % (original,))

    cumulative = True
    basis = BASIS.fullmatch(text)
    if basis is not None and basis.group(1) != "":
        text = basis.group(1)
        cumulative = False

    region = "rest-of-uk"
    body = text
    if text.startswith("S"):
        region = "scotland"
        body = text[1:]
    elif text.startswith("C"):
        region = "wales"
        body = text[1:]

    number = 0
    suffix: Optional[str] = None
    allowance_pounds = 0

    allowance = ALLOWANCE.fullmatch(body)
    k = K_CODE.fullmatch(body)
    d = D_CODE.fullmatch(body)
    if body == "NT":
        if region != "rest-of-uk":
            raise ValueError('there is no Scottish or Welsh NT code: "%s"' % (original,))
        kind = "no-tax"
    elif body == "BR":
        kind = "basic-rate"
    elif d is not None:
        kind = "d-rate"
        number = int(d.group(1))
    elif k is not None:
        kind = "negative-allowance"
        number = int(k.group(1))
        allowance_pounds = -(number * 10 + 9)
    elif allowance is not None:
        number = int(allowance.group(1))
        suffix = allowance.group(2)
        if number == 0 and suffix != "T":
            raise ValueError('unrecognised tax code "%s": a zero allowance is only issued as 0T' % (original,))
        kind = "allowance"
        allowance_pounds = 0 if number == 0 else number * 10 + 9
    else:
        raise ValueError('unrecognised tax code "%s"' % (original,))

    prefix = "S" if region == "scotland" else "C" if region == "wales" else ""
    return TaxCode(
        code=prefix + body,
        region=region,
        kind=kind,
        number=number,
        suffix=suffix,
        annual_allowance=money(allowance_pounds * 100, "GBP"),
        cumulative=cumulative,
    )