1# damm.py - functions for performing the Damm checksum algorithm 2# 3# Copyright (C) 2016-2017 Arthur de Jong 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18# 02110-1301 USA 19 20"""The Damm algorithm. 21 22The Damm algorithm is a check digit algorithm that should detect all 23single-digit errors and all adjacent transposition errors. Based on 24anti-symmetric quasigroup of order 10 it uses a substitution table. 25 26This implementation uses the table from Wikipedia by default but a custom 27table can be provided. 28 29More information: 30 31* https://en.wikipedia.org/wiki/Damm_algorithm 32 33>>> validate('572') 34Traceback (most recent call last): 35 ... 36InvalidChecksum: ... 37>>> calc_check_digit('572') 38'4' 39>>> validate('5724') 40'5724' 41>>> table = ( 42... (0, 2, 3, 4, 5, 6, 7, 8, 9, 1), 43... (2, 0, 4, 1, 7, 9, 5, 3, 8, 6), 44... (3, 7, 0, 5, 2, 8, 1, 6, 4, 9), 45... (4, 1, 8, 0, 6, 3, 9, 2, 7, 5), 46... (5, 6, 2, 9, 0, 7, 4, 1, 3, 8), 47... (6, 9, 7, 3, 1, 0, 8, 5, 2, 4), 48... (7, 5, 1, 8, 4, 2, 0, 9, 6, 3), 49... (8, 4, 6, 2, 9, 5, 3, 0, 1, 7), 50... (9, 8, 5, 7, 3, 1, 6, 4, 0, 2), 51... (1, 3, 9, 6, 8, 4, 2, 7, 5, 0)) 52>>> checksum('816', table=table) 539 54""" 55 56from stdnum.exceptions import * 57 58 59_operation_table = ( 60 (0, 3, 1, 7, 5, 9, 8, 6, 4, 2), 61 (7, 0, 9, 2, 1, 5, 4, 8, 6, 3), 62 (4, 2, 0, 6, 8, 7, 1, 3, 5, 9), 63 (1, 7, 5, 0, 9, 8, 3, 4, 2, 6), 64 (6, 1, 2, 3, 0, 4, 5, 9, 7, 8), 65 (3, 6, 7, 4, 2, 0, 9, 5, 8, 1), 66 (5, 8, 6, 9, 7, 2, 0, 1, 3, 4), 67 (8, 9, 4, 5, 3, 6, 2, 0, 1, 7), 68 (9, 4, 3, 8, 6, 1, 7, 2, 0, 5), 69 (2, 5, 8, 1, 4, 3, 6, 7, 9, 0)) 70 71 72def checksum(number, table=None): 73 """Calculate the Damm checksum over the provided number. The checksum is 74 returned as an integer value and should be 0 when valid.""" 75 table = table or _operation_table 76 i = 0 77 for n in str(number): 78 i = table[i][int(n)] 79 return i 80 81 82def validate(number, table=None): 83 """Check if the number provided passes the Damm algorithm.""" 84 if not bool(number): 85 raise InvalidFormat() 86 try: 87 valid = checksum(number, table=table) == 0 88 except Exception: 89 raise InvalidFormat() 90 if not valid: 91 raise InvalidChecksum() 92 return number 93 94 95def is_valid(number, table=None): 96 """Check if the number provided passes the Damm algorithm.""" 97 try: 98 return bool(validate(number, table=table)) 99 except ValidationError: 100 return False 101 102 103def calc_check_digit(number, table=None): 104 """Calculate the extra digit that should be appended to the number to 105 make it a valid number.""" 106 return str(checksum(number, table=table)) 107