import re _PREFIX = re.compile(r"^[0-9]{4,12}$") def _whole(value: object) -> bool: return isinstance(value, int) and not isinstance(value, bool) def build_sscc(extension_digit: int, company_prefix: str, serial_reference: int) -> str: """Build an 18-digit SSCC: extension digit, GS1 company prefix, zero-padded serial reference and GS1 mod-10 check digit.""" if not _whole(extension_digit) or extension_digit < 0 or extension_digit > 9: raise ValueError("extensionDigit must be 0 to 9, received %r" % (extension_digit,)) # fullmatch, not match: "$" in re also matches before a trailing newline. if not isinstance(company_prefix, str) or not _PREFIX.fullmatch(company_prefix): raise ValueError('companyPrefix must be 4 to 12 digits, received "%s"' % (company_prefix,)) if not _whole(serial_reference): raise TypeError("serialReference must be an integer, received %r" % (serial_reference,)) if serial_reference < 0: raise ValueError("serialReference must not be negative, received %d" % serial_reference) # Prefix and serial share 16 digits, so a longer prefix leaves less room. # Too large is an error: truncating would reuse another unit's number. room = 16 - len(company_prefix) serial = str(serial_reference) if len(serial) > room: raise ValueError( "serialReference must fit in %d digits after a %d-digit company prefix, received %d" % (room, len(company_prefix), serial_reference) ) data = str(extension_digit) + company_prefix + serial.rjust(room, "0") # GS1 mod-10: weights 3, 1, 3, 1 ... from the rightmost data digit leftwards. # validation.gtin does the same sum but only exports validate_gtin, which # takes 8 to 14 digits, not an 18-digit SSCC. total = 0 weight = 3 for ch in reversed(data): total += (ord(ch) - 48) * weight weight = 1 if weight == 3 else 3 return data + str((10 - total % 10) % 10)