-
-
Save canhlinh/9b01e9f7e27a4b8ac470a78a8722648d to your computer and use it in GitHub Desktop.
Validate and generate NRIC/FIN in python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import random | |
import re | |
class NRIC: | |
def __init__(self, value=None): | |
if isinstance(value, NRIC): | |
self.nric = value.nric | |
elif isinstance(value, str): | |
self.nric = value.strip().upper() | |
def __repr__(self): | |
return f"NRIC('{self.nric}')" | |
def __str__(self): | |
return self.nric | |
@property | |
def value(self): | |
return self.nric | |
@property | |
def length(self): | |
return len(self.nric) | |
@property | |
def first_char(self): | |
return self.nric[:1] if self.is_correct_format else None | |
@property | |
def digits(self): | |
return self.nric[1:-1] if self.is_correct_format else None | |
@property | |
def identifier(self): | |
return self.nric[-4:] if self.is_correct_format else None | |
@property | |
def checksum(self): | |
return self.nric[-1:] if self.is_correct_format else None | |
@property | |
def is_correct_format(self): | |
return bool(re.match(r'^[STFGM]\d{7}[A-Z]$', self.nric)) | |
@property | |
def is_valid(self): | |
try: | |
return self.validate_checksum() | |
except (Exception,): | |
return False | |
def validate_checksum(self): | |
is_correct_format, first_char, checksum = self.is_correct_format, self.first_char, self.checksum | |
digits = self.digits | |
# Perform basic format check first | |
if not is_correct_format: | |
return False | |
# Valid if the checksum matches the calculated checksum | |
return checksum == self.calculate_checksum(first_char, digits) | |
@staticmethod | |
def generate(first_char=None): | |
if not first_char or not re.match(r'^[STFGM]$', first_char, re.IGNORECASE): | |
first_char = random.choice('STFGM') | |
digits = ''.join(str(random.randint(0, 9)) for _ in range(7)) | |
checksum = NRIC.calculate_checksum(first_char, digits) | |
return NRIC(first_char + digits + checksum) | |
@staticmethod | |
def generate_many(amount=1): | |
amount = max(1, int(amount)) | |
return [NRIC.generate() for _ in range(amount)] | |
@staticmethod | |
def validate(value): | |
if isinstance(value, list): | |
return all(item.is_valid if isinstance(item, NRIC) else NRIC(item).is_valid for item in value) | |
else: | |
return value.is_valid if isinstance(value, NRIC) else NRIC(value).is_valid | |
@classmethod | |
def calculate_checksum(cls, first_char, digits): | |
# Multiply each of the digits by the respective weights | |
digits = list(map(int, digits)) | |
digits[0] *= 2 | |
digits[1] *= 7 | |
digits[2] *= 6 | |
digits[3] *= 5 | |
digits[4] *= 4 | |
digits[5] *= 3 | |
digits[6] *= 2 | |
# Calculate total, offset based on first character, and modulus 11 | |
weight = sum(digits) | |
if first_char == 'T' or first_char == 'G': | |
offset = 4 | |
elif first_char == 'M': | |
offset = 3 | |
else: | |
offset = 0 | |
index = (offset + weight) % 11 | |
# If first_char is M, rotate the index | |
if first_char == 'M': | |
index = 10 - index | |
# Get the value of the index in the checksum array based on first_char | |
table = cls.get_checksum_table(first_char) | |
# Return the value of the index in the checksum table | |
return table[index] | |
@classmethod | |
def get_checksum_table(cls, first_char): | |
checksums = { | |
'ST': ['J', 'Z', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A'], | |
'FG': ['X', 'W', 'U', 'T', 'R', 'Q', 'P', 'N', 'M', 'L', 'K'], | |
'M': ['K', 'L', 'J', 'N', 'P', 'Q', 'R', 'T', 'U', 'W', 'X'] | |
} | |
key = [k for k in checksums.keys() if first_char in k] | |
if not key: | |
raise ValueError(f'Unable to find checksum table for "{first_char}"') | |
return checksums[key[0]] | |
if __name__ == "__main__": | |
for i in range(300): | |
nric = NRIC.generate() | |
print(f'{nric} is valid NRIC {NRIC(nric).is_valid}') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment