Functional Weave
Code in Python

validation.uk-utr@1.0.0

impl/python.py

1,927 bytes · the Python implementation · view raw

from typing import List

from .validation_uk_utr_types import UkUtr

#: Weights for the second to tenth digits; the first digit is the check digit.
#: From HMRC's own reference checker (hmrc/domain, UtrReferenceChecker).
_WEIGHTS = (6, 7, 8, 9, 10, 5, 4, 3, 2)

#: The check digit for each remainder of the weighted sum modulo 11. It is
#: 11 - remainder except that remainders 0 and 1 give 2 and 1, so a check digit
#: is always a single digit and no reference is impossible.
_CHECK_DIGITS = (2, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1)


def _invalid(reason: str) -> UkUtr:
    return UkUtr(valid=False, normalised=None, reason=reason)


def uk_utr(value: str) -> UkUtr:
    """Check a Unique Taxpayer Reference (Self Assessment or Corporation Tax).

    A pass means the reference is well formed, not that HMRC issued it to
    this taxpayer. Checks run in a fixed order (characters, length, check
    digit) so every language gives the same first reason.
    """
    if not isinstance(value, str):
        return _invalid("empty")
    compact = "".join(ch for ch in value if ch != " ")
    if compact == "":
        return _invalid("empty")
    # Self Assessment shows the UTR with a K after it and some letters put
    # it in front; one K at either end is stripped, as HMRC's checker does.
    if compact[0] in ("K", "k"):
        compact = compact[1:]
    elif compact[-1] in ("K", "k"):
        compact = compact[:-1]

    digits: List[int] = []
    for ch in compact:
        if ch < "0" or ch > "9":
            return _invalid("bad-character")
        digits.append(ord(ch) - 48)
    if len(digits) == 13:
        return _invalid("thirteen-digits")
    if len(digits) != 10:
        return _invalid("bad-length")

    total = sum(d * w for d, w in zip(digits[1:], _WEIGHTS))
    if _CHECK_DIGITS[total % 11] != digits[0]:
        return _invalid("bad-check-digit")
    return UkUtr(valid=True, normalised=compact, reason=None)