from .validation_uk_company_number_data import COMPANY_PREFIXES from .validation_uk_company_number_types import UkCompanyNumber #: Every company number is eight characters: eight digits, or a two-character #: prefix and six digits. _LENGTH = 8 def _invalid(reason: str) -> UkCompanyNumber: return UkCompanyNumber(valid=False, normalised=None, prefix=None, jurisdiction=None, company_type=None, reason=reason) def _is_letter(ch: str) -> bool: return "A" <= ch <= "Z" def _is_digit(ch: str) -> bool: return "0" <= ch <= "9" def validate_uk_company_number(value: str) -> UkCompanyNumber: """Check a Companies House company number's shape and prefix. There is no check digit, so a pass means the number is shaped like one Companies House issues under a prefix it uses, not that the company exists: that is a lookup on the register. Checks run in a fixed order (characters, shape, length, prefix) so every language gives the same first reason. """ if not isinstance(value, str): return _invalid("empty") compact = "" for ch in value: if ch == " ": continue # ASCII-only upper-casing, so every language folds identically. if "a" <= ch <= "z": ch = chr(ord(ch) - 32) if not _is_letter(ch) and not _is_digit(ch): return _invalid("bad-character") compact += ch if compact == "": return _invalid("empty") # R0 (pre-partition Northern Ireland) is the one prefix containing a digit. if compact.startswith("R0") or (len(compact) >= 2 and _is_letter(compact[0]) and _is_letter(compact[1])): prefix = compact[0:2] elif _is_letter(compact[0]): return _invalid("bad-format") else: prefix = "" digits = compact[len(prefix):] if not all(_is_digit(ch) for ch in digits): return _invalid("bad-format") width = _LENGTH - len(prefix) if len(digits) == 0 or len(digits) > width: return _invalid("bad-length") # Leading zeros are part of the number and are often dropped when typed, # so they are put back; a number of nothing but zeros is no company. if all(ch == "0" for ch in digits): return _invalid("bad-format") for row in COMPANY_PREFIXES: if row.prefix == prefix: return UkCompanyNumber( valid=True, normalised=prefix + digits.rjust(width, "0"), prefix=prefix, jurisdiction=row.jurisdiction, company_type=row.company_type, reason=None, ) return _invalid("unknown-prefix")