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)