2,242 bytes · the Python implementation · view raw
from .validation_uk_ni_number_types import UkNiNumber
#: HMRC NIM39110: D, F, I, Q, U and V are never either letter of a prefix,#: and O is never the second.
_BARRED_FIRST = "DFIQUV"
_BARRED_SECOND = "DFIOQUV"#: Prefixes made of allowed letters that HMRC still never issues.
_BARRED_PREFIXES = ("BG", "GB", "KN", "NK", "NT", "TN", "ZZ")
#: The suffix records the quarter a contribution card was once returned in,#: and is always one of these four.
_SUFFIXES = "ABCD"def _invalid(reason: str) -> UkNiNumber:
return UkNiNumber(valid=False, normalised=None, reason=reason)
def _is_letter(ch: str) -> bool:
return"A" <= ch <= "Z"def _is_digit(ch: str) -> bool:
return"0" <= ch <= "9"def uk_ni_number(value: str) -> UkNiNumber:
"""Check a National Insurance number's shape and prefix rules. There is no check digit in a NINO, so a pass means "could have been issued", never "belongs to this person". The checks run in a fixed order (characters, length, shape, prefix, suffix) so that every language reports the same first reason. """ifnot isinstance(value, str):
return _invalid("empty")
compact = ""for ch in value:
# Only the ASCII space is ignored: NINOs are printed in pairs.if ch == " ":
continue# ASCII-only upper-casing, so no language's Unicode rules can turn a# stray character into a letter that passes.if"a" <= ch <= "z":
ch = chr(ord(ch) - 32)
ifnot _is_letter(ch) andnot _is_digit(ch):
return _invalid("bad-character")
compact += ch
if compact == "":
return _invalid("empty")
if len(compact) != 9:
return _invalid("bad-length")
ifnot (_is_letter(compact[0]) and _is_letter(compact[1]) and _is_letter(compact[8])):
return _invalid("bad-format")
ifnot all(_is_digit(ch) for ch in compact[2:8]):
return _invalid("bad-format")
if compact[0] in _BARRED_FIRST or compact[1] in _BARRED_SECOND or compact[0:2] in _BARRED_PREFIXES:
return _invalid("bad-prefix")
if compact[8] notin _SUFFIXES:
return _invalid("bad-suffix")
return UkNiNumber(valid=True, normalised=compact, reason=None)