Functional Weave
Code in TypeScript

validation.lei@1.0.0

impl/python.py

1,880 bytes · the Python implementation · view raw

from .validation_lei_types import LeiCheck


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


def _is_digit(ch: str) -> bool:
    return "0" <= ch <= "9"


def _mod97(lei: str) -> int:
    """ISO 7064 MOD 97-10 over the whole LEI, letters read as A = 10 ... Z = 35.

    Python could hold the 40-digit expansion as one integer, but the remainder
    is carried one character at a time so this is the same algorithm as the
    Rust sibling, where it cannot. A letter contributes two digits and so
    multiplies the running remainder by 100.
    """
    remainder = 0
    for ch in lei:
        if _is_digit(ch):
            remainder = (remainder * 10 + (ord(ch) - 48)) % 97
        else:
            remainder = (remainder * 100 + (ord(ch) - 55)) % 97
    return remainder


def validate_lei(value: str) -> LeiCheck:
    """Check a Legal Entity Identifier's format and ISO 17442 check digits.

    Well formed is not issued: only GLEIF says whether an LEI is current.
    Validators answer rather than raise.
    """
    if not isinstance(value, str):
        return _invalid("empty")

    # Folded by hand: str.upper() is Unicode-aware and Rust's ASCII fold is not.
    lei = "".join(chr(ord(ch) - 32) if "a" <= ch <= "z" else ch for ch in value if ch != " ")
    if not lei:
        return _invalid("empty")
    if any(not (_is_digit(ch) or "A" <= ch <= "Z") for ch in lei):
        return _invalid("bad-character")
    if len(lei) != 20:
        return _invalid("bad-length")
    if not _is_digit(lei[18]) or not _is_digit(lei[19]):
        return _invalid("bad-format")
    # The remainder must be 1, not 0: the check digits are chosen so that it is.
    if _mod97(lei) != 1:
        return _invalid("bad-check-digit")

    return LeiCheck(valid=True, normalised=lei, lou_prefix=lei[:4], reason=None)