Rewrote CUSIP goodie to take advantage of Business::CUSIP
parent
7833f3c1f3
commit
bfc2c1dde9
|
@ -2,12 +2,13 @@ package DDG::Goodie::Cusip;
|
|||
# ABSTRACT: Validate a CUSIP ID's check digit.
|
||||
|
||||
use DDG::Goodie;
|
||||
use Business::CUSIP;
|
||||
|
||||
# metadata
|
||||
name "CUSIP check";
|
||||
description "Validates the check digit for a unique stock identifier based on the Committee on Uniform Securities Identification Procedures";
|
||||
primary_example_queries "cusip 037833100";
|
||||
secondary_example_queries "cusip check 38259P706", "844741108 cusip";
|
||||
secondary_example_queries "cusip check 38259P706", "844741108 cusip check";
|
||||
category "finance";
|
||||
topics "economy_and_finance";
|
||||
code_url "https://github.com/tommytommytommy/zeroclickinfo-goodies/lib/DDG/Goodie/Cusip.pm";
|
||||
|
@ -21,7 +22,7 @@ zci answer_type => "cusip";
|
|||
my $CUSIPLENGTH = 9;
|
||||
|
||||
handle remainder => sub {
|
||||
|
||||
|
||||
# strip beginning and end whitespace from remainder
|
||||
s/^\s+|\s+$//g;
|
||||
|
||||
|
@ -31,63 +32,19 @@ handle remainder => sub {
|
|||
# check that the remainder is the correct length and
|
||||
# only contains alphanumeric chars and *, @, and #
|
||||
return if not m/^[A-Z0-9\*\@\#]{$CUSIPLENGTH}$/;
|
||||
|
||||
# split the CUSIP ID (without check digit) into an array of characters
|
||||
my @cusipIdChars = split(//, $_);
|
||||
my $inputCheckDigit = pop @cusipIdChars;
|
||||
|
||||
# aggregate checksum value
|
||||
my $checksum = 0;
|
||||
for (0 .. $#cusipIdChars) {
|
||||
|
||||
# this variable stores the integer equivalent of the CUSIP character
|
||||
my $currentCusipCharValue = 0;
|
||||
|
||||
# map the current CUSIP character into its integer value
|
||||
# based on the pseudo algorithm provided by
|
||||
# https://en.wikipedia.org/wiki/CUSIP#Check_digit_pseudocode
|
||||
if ($cusipIdChars[$_] =~ m/[0-9]/) {
|
||||
$currentCusipCharValue = ord($cusipIdChars[$_]) - ord('0');
|
||||
} elsif ($cusipIdChars[$_] =~ m/[A-Z]/) {
|
||||
$currentCusipCharValue = ord($cusipIdChars[$_]) - ord('A') + 10;
|
||||
} elsif ($cusipIdChars[$_] eq '*') {
|
||||
$currentCusipCharValue = 36;
|
||||
} elsif ($cusipIdChars[$_] eq '@') {
|
||||
$currentCusipCharValue = 37;
|
||||
} elsif ($cusipIdChars[$_] eq '#') {
|
||||
$currentCusipCharValue = 38;
|
||||
}
|
||||
|
||||
# double the CUSIP value for every other character starting with the second
|
||||
if (($_ + 1) % 2 == 0) {
|
||||
$currentCusipCharValue *= 2;
|
||||
}
|
||||
|
||||
# the pseudocode in Wikipedia does not explicitly state that truncating
|
||||
# the division result is necessary, but empirical testing
|
||||
# with 037833100 for AAPL and 38259P706 and 38259P508 for GOOG show
|
||||
# that the truncation is necessary
|
||||
$checksum += int($currentCusipCharValue / 10) + $currentCusipCharValue % 10;
|
||||
}
|
||||
|
||||
# convert the checksum into a single check digit
|
||||
my $calculatedCheckDigit = chr((10 - ($checksum % 10)) % 10 + ord('0'));
|
||||
|
||||
# store answer-specific strings
|
||||
my ($article, $result);
|
||||
my $cusip = Business::CUSIP->new($_);
|
||||
my ($output, $htmlOutput);
|
||||
|
||||
# return the validity of the CUSIP
|
||||
if ($calculatedCheckDigit eq $inputCheckDigit) {
|
||||
$article = "a";
|
||||
$result = "valid";
|
||||
if ($cusip->is_valid) {
|
||||
$output = html_enc($_)." is a properly formatted CUSIP number.";
|
||||
$htmlOutput = "<div class='zci--cusip text--primary'>".html_enc($_)." is a properly formatted <span class='text--secondary'>CUSIP number.</span></div>";
|
||||
} else {
|
||||
$article = "an";
|
||||
$result = "invalid";
|
||||
$output = html_enc($_)." is not a properly formatted CUSIP number.";
|
||||
$htmlOutput = "<div class='zci--cusip text--primary'>".html_enc($_)." is not a properly formatted <span class='text--secondary'>CUSIP number.</span></div>";
|
||||
}
|
||||
|
||||
# create and output results
|
||||
my $output = html_enc($_)." has $article $result CUSIP check digit.";
|
||||
my $htmlOutput = "<div class='zci--cusip text--primary'>".html_enc($_)."<span class='text--secondary'> has $article </span>$result<span class='text--secondary'> CUSIP check digit.</span></div>";
|
||||
# output results
|
||||
return $output, html => $htmlOutput;
|
||||
};
|
||||
|
||||
|
|
58
t/Cusip.t
58
t/Cusip.t
|
@ -43,55 +43,57 @@ ddg_goodie_test(
|
|||
# triggers that SHOULD load the IA
|
||||
|
||||
# typical well-formed queries for AAPL and Southwest
|
||||
'cusip 037833100' => test_zci("037833100 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 844741108' => test_zci("844741108 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'037833100 cusip' => test_zci("037833100 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833100' => test_zci("037833100 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip check 037833100' => test_zci("037833100 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 844741108' => test_zci("844741108 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'037833100 cusip' => test_zci("037833100 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'037833100 cusip check' => test_zci("037833100 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# starting white space should be stripped
|
||||
'cusip 037833100' => test_zci("037833100 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833100' => test_zci("037833100 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# ending white space should be stripped
|
||||
'cusip 037833100 ' => test_zci("037833100 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833100 ' => test_zci("037833100 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# starting and ending white space should be stripped
|
||||
'cusip 037833100 ' => test_zci("037833100 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833100 ' => test_zci("037833100 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# same AAPL queries with an incorrect check digit
|
||||
'cusip 03783310A' => test_zci("03783310A has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 03783310A' => test_zci("03783310A has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 03783310A ' => test_zci("03783310A has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 03783310A ' => test_zci("03783310A has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 03783310A' => test_zci("03783310A is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 03783310A' => test_zci("03783310A is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 03783310A ' => test_zci("03783310A is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 03783310A ' => test_zci("03783310A is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# check CUSIP IDs with capital letters (these are for GOOG and Blackberry)
|
||||
'cusip 38259P706' => test_zci("38259P706 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 38259P508' => test_zci("38259P508 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 09228F103' => test_zci("09228F103 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 38259P706' => test_zci("38259P706 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 38259P508' => test_zci("38259P508 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 09228F103' => test_zci("09228F103 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# check the same CUSIP IDs with lower case letters
|
||||
'cusip 38259p706' => test_zci("38259P706 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 38259p508' => test_zci("38259P508 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 09228f103' => test_zci("09228F103 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 38259p706' => test_zci("38259P706 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 38259p508' => test_zci("38259P508 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 09228f103' => test_zci("09228F103 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# check CUSIP IDs with '*', '#', and '@'
|
||||
# these CUSIP ID check digits were calculated by hand
|
||||
# if possible, these tests should be replaced with verified CUSIP IDs
|
||||
'cusip 037833*00' => test_zci("037833*00 has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833*02' => test_zci("037833*02 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 0378331#0' => test_zci("0378331#0 has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 0378331#7' => test_zci("0378331#7 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833@00' => test_zci("037833\@00 has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833@01' => test_zci("037833\@01 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 037833*00' => test_zci("037833*00 is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 037833*02' => test_zci("037833*02 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 0378331#0' => test_zci("0378331#0 is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 0378331#7' => test_zci("0378331#7 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 037833@00' => test_zci("037833\@00 is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 037833@01' => test_zci("037833\@01 is a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# CUSIP IDs ending in '*', '#', and '@' should not break the IA
|
||||
# even though they are always invalid IDs
|
||||
'cusip 03783310*' => test_zci("03783310* has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 03783310#' => test_zci("03783310# has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 03783310@' => test_zci("03783310\@ has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip 03783310*' => test_zci("03783310* is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 03783310#' => test_zci("03783310# is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip 03783310@' => test_zci("03783310\@ is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
|
||||
# Odd CUSIP IDs should not break the IA
|
||||
'cusip ********8' => test_zci("********8 has a valid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip ########9' => test_zci("########9 has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip @#*@#*@#*' => test_zci("\@#*\@#*\@#* has an invalid CUSIP check digit.", html => qr/.*/),
|
||||
'cusip ********8' => test_zci("********8 is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip ########9' => test_zci("########9 is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
'cusip @#*@#*@#*' => test_zci("\@#*\@#*\@#* is not a properly formatted CUSIP number.", html => qr/.*/),
|
||||
);
|
||||
|
||||
done_testing;
|
||||
|
|
Loading…
Reference in New Issue