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)