1# cusip.py - functions for handling CUSIP numbers 2# 3# Copyright (C) 2015-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"""CUSIP number (financial security identification number). 21 22CUSIP (Committee on Uniform Securities Identification Procedures) numbers are 23used to identify financial securities. CUSIP numbers are a nine-character 24alphanumeric code where the first six characters identify the issuer, 25followed by two digits that identify and a check digit. 26 27More information: 28 29* https://en.wikipedia.org/wiki/CUSIP 30* https://www.cusip.com/ 31 32>>> validate('DUS0421C5') 33'DUS0421C5' 34>>> validate('DUS0421CN') 35Traceback (most recent call last): 36 ... 37InvalidChecksum: ... 38>>> to_isin('91324PAE2') 39'US91324PAE25' 40""" 41 42from stdnum.exceptions import * 43from stdnum.util import clean 44 45 46def compact(number): 47 """Convert the number to the minimal representation. This strips the 48 number of any valid separators and removes surrounding whitespace.""" 49 return clean(number, ' ').strip().upper() 50 51 52# O and I are not valid but are accounted for in the check digit calculation 53_alphabet = '0123456789ABCDEFGH JKLMN PQRSTUVWXYZ*@#' 54 55 56def calc_check_digit(number): 57 """Calculate the check digits for the number.""" 58 # convert to numeric first, then sum individual digits 59 number = ''.join( 60 str((1, 2)[i % 2] * _alphabet.index(n)) for i, n in enumerate(number)) 61 return str((10 - sum(int(n) for n in number)) % 10) 62 63 64def validate(number): 65 """Check if the number provided is valid. This checks the length and 66 check digit.""" 67 number = compact(number) 68 if not all(x in _alphabet for x in number): 69 raise InvalidFormat() 70 if len(number) != 9: 71 raise InvalidLength() 72 if calc_check_digit(number[:-1]) != number[-1]: 73 raise InvalidChecksum() 74 return number 75 76 77def is_valid(number): 78 """Check if the number provided is valid. This checks the length and 79 check digit.""" 80 try: 81 return bool(validate(number)) 82 except ValidationError: 83 return False 84 85 86def to_isin(number): 87 """Convert the number to an ISIN.""" 88 from stdnum import isin 89 return isin.from_natid('US', number) 90