sec_id 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +18 -0
- data/README.md +27 -3
- data/lib/sec_id.rb +1 -0
- data/lib/sec_id/base.rb +26 -5
- data/lib/sec_id/cusip.rb +71 -0
- data/lib/sec_id/isin.rb +6 -2
- data/lib/sec_id/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f48ba902d7677a13e7d53d251473ba5a8b5c0379c06b09ceacda30df79a31d1
|
4
|
+
data.tar.gz: 1e54b0d518ca576ad853ff0d170026bbf9e0267d419c8a20209e5334a6d15276
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10c3e1c32582736216141a539161a6d8aead43bdde27c4ad25ac95fd02ce4009959585248c24a740b0b8752554d36d31dfe2e3d28b1ec1ca92b9b8be2c69b1e7
|
7
|
+
data.tar.gz: 41a279f6df4045093a9c870c3ddd03845fe508d0d729bc145f59d96351b3580d51285fbae1aa0bef2c176b4c526d4452a5423796b348028ecbe63a9346430f12
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## [1.1.0] - 2019-02-03
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- CUSIP numbers support: `SecId::CUSIP`
|
8
|
+
- CHANGELOG.md file added
|
9
|
+
|
10
|
+
### Updated
|
11
|
+
|
12
|
+
- Char to digit conversion now uses precalculated tables instead of dynamic calculation for speed
|
13
|
+
|
14
|
+
## [1.0.0] - 2017-10-25
|
15
|
+
|
16
|
+
### Added
|
17
|
+
|
18
|
+
- ISIN numbers support: `SecId::ISIN`
|
data/README.md
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# SecId
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/sec_id.svg)](https://badge.fury.io/rb/sec_id)
|
3
3
|
[![Build Status](https://travis-ci.org/svyatov/sec_id.svg?branch=master)](https://travis-ci.org/svyatov/sec_id)
|
4
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/a4759963a5ddc4d55b24/maintainability)](https://codeclimate.com/github/svyatov/sec_id/maintainability)
|
5
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/a4759963a5ddc4d55b24/test_coverage)](https://codeclimate.com/github/svyatov/sec_id/test_coverage)
|
4
6
|
|
5
7
|
Validate security identification numbers with ease!
|
6
8
|
|
7
9
|
Check-digit calculation is also available.
|
8
10
|
|
9
11
|
Currently supported standards:
|
10
|
-
[ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number)
|
12
|
+
[ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number),
|
13
|
+
[CUSIP](https://en.wikipedia.org/wiki/CUSIP)
|
11
14
|
|
12
|
-
WIP:
|
15
|
+
WIP:
|
13
16
|
[SEDOL](https://en.wikipedia.org/wiki/SEDOL),
|
14
17
|
[IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
|
15
18
|
|
@@ -18,7 +21,7 @@ WIP: [CUSIP](https://en.wikipedia.org/wiki/CUSIP),
|
|
18
21
|
Add this line to your application's Gemfile:
|
19
22
|
|
20
23
|
```ruby
|
21
|
-
gem 'sec_id', '~> 1.
|
24
|
+
gem 'sec_id', '~> 1.1'
|
22
25
|
```
|
23
26
|
|
24
27
|
And then execute:
|
@@ -114,6 +117,27 @@ isin.restore! # => 'US5949181045'
|
|
114
117
|
isin.calculate_check_digit # => 5
|
115
118
|
```
|
116
119
|
|
120
|
+
### SecId::CUSIP full example
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
# class level
|
124
|
+
SecId::CUSIP.valid?('594918104') # => true
|
125
|
+
SecId::CUSIP.valid_format?('59491810') # => true
|
126
|
+
SecId::CUSIP.restore!('59491810') # => '594918104'
|
127
|
+
SecId::CUSIP.check_digit('59491810') # => 5
|
128
|
+
|
129
|
+
# instance level
|
130
|
+
cusip = SecId::CUSIP.new('594918104')
|
131
|
+
cusip.cusip # => '594918104'
|
132
|
+
cusip.cusip6 # => '594918'
|
133
|
+
cusip.issue # => '10'
|
134
|
+
cusip.check_digit # => 4
|
135
|
+
cusip.valid? # => true
|
136
|
+
cusip.valid_format? # => true
|
137
|
+
cusip.restore! # => '594918104'
|
138
|
+
cusip.calculate_check_digit # => 4
|
139
|
+
```
|
140
|
+
|
117
141
|
## Development
|
118
142
|
|
119
143
|
After checking out the repo, run `bin/setup` to install dependencies.
|
data/lib/sec_id.rb
CHANGED
data/lib/sec_id/base.rb
CHANGED
@@ -2,7 +2,27 @@
|
|
2
2
|
|
3
3
|
module SecId
|
4
4
|
class Base
|
5
|
-
|
5
|
+
CHAR_TO_DIGITS = {
|
6
|
+
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
|
7
|
+
'5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
|
8
|
+
'A' => [1, 0], 'B' => [1, 1], 'C' => [1, 2], 'D' => [1, 3], 'E' => [1, 4],
|
9
|
+
'F' => [1, 5], 'G' => [1, 6], 'H' => [1, 7], 'I' => [1, 8], 'J' => [1, 9],
|
10
|
+
'K' => [2, 0], 'L' => [2, 1], 'M' => [2, 2], 'N' => [2, 3], 'O' => [2, 4],
|
11
|
+
'P' => [2, 5], 'Q' => [2, 6], 'R' => [2, 7], 'S' => [2, 8], 'T' => [2, 9],
|
12
|
+
'U' => [3, 0], 'V' => [3, 1], 'W' => [3, 2], 'X' => [3, 3], 'Y' => [3, 4], 'Z' => [3, 5],
|
13
|
+
'*' => [3, 6], '@' => [3, 7], '#' => [3, 8]
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
CHAR_TO_DIGIT = {
|
17
|
+
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
|
18
|
+
'5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
|
19
|
+
'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14,
|
20
|
+
'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19,
|
21
|
+
'K' => 20, 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24,
|
22
|
+
'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29,
|
23
|
+
'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35,
|
24
|
+
'*' => 36, '@' => 37, '#' => 38
|
25
|
+
}.freeze
|
6
26
|
|
7
27
|
attr_reader :identifier, :check_digit
|
8
28
|
|
@@ -50,14 +70,15 @@ module SecId
|
|
50
70
|
private
|
51
71
|
|
52
72
|
def digitized_identifier
|
53
|
-
|
73
|
+
raise NotImplementedError
|
54
74
|
end
|
55
75
|
|
56
76
|
def char_to_digits(char)
|
57
|
-
|
77
|
+
CHAR_TO_DIGITS.fetch(char)
|
78
|
+
end
|
58
79
|
|
59
|
-
|
60
|
-
|
80
|
+
def char_to_digit(char)
|
81
|
+
CHAR_TO_DIGIT.fetch(char)
|
61
82
|
end
|
62
83
|
|
63
84
|
def mod_10(sum)
|
data/lib/sec_id/cusip.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SecId
|
4
|
+
# https://en.wikipedia.org/wiki/CUSIP
|
5
|
+
class CUSIP < Base
|
6
|
+
ID_REGEX = /\A
|
7
|
+
(?<identifier>
|
8
|
+
(?<cusip6>[A-Z0-9]{5}[A-Z0-9*@#])
|
9
|
+
(?<issue>[A-Z0-9*@#]{2}))
|
10
|
+
(?<check_digit>\d)?
|
11
|
+
\z/x.freeze
|
12
|
+
|
13
|
+
VALID_COUNTRY_CODES_FOR_CONVERSION_TO_ISIN = %w[US CA].freeze
|
14
|
+
|
15
|
+
attr_reader :cusip, :cusip6, :issue
|
16
|
+
|
17
|
+
def initialize(cusip)
|
18
|
+
@cusip = cusip.to_s.strip.upcase
|
19
|
+
cusip_parts = @cusip.match(ID_REGEX) || {}
|
20
|
+
|
21
|
+
@identifier = cusip_parts[:identifier]
|
22
|
+
@cusip6 = cusip_parts[:cusip6]
|
23
|
+
@issue = cusip_parts[:issue]
|
24
|
+
@check_digit = cusip_parts[:check_digit].to_i if cusip_parts[:check_digit]
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
return false unless valid_format?
|
29
|
+
|
30
|
+
check_digit == calculate_check_digit
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid_format?
|
34
|
+
identifier ? true : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def restore!
|
38
|
+
@check_digit = calculate_check_digit
|
39
|
+
@cusip = to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def calculate_check_digit
|
43
|
+
return mod_10(modified_luhn_sum) if valid_format?
|
44
|
+
|
45
|
+
raise InvalidFormatError, "CUSIP '#{cusip}' is invalid and check-digit cannot be calculated!"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# https://en.wikipedia.org/wiki/Luhn_algorithm
|
51
|
+
def modified_luhn_sum
|
52
|
+
sum = 0
|
53
|
+
|
54
|
+
digitized_identifier.reverse.each_slice(2) do |even, odd|
|
55
|
+
double_even = (even || 0) * 2
|
56
|
+
double_even -= 9 if double_even > 9
|
57
|
+
sum += div_10_mod_10(double_even) + div_10_mod_10(odd || 0)
|
58
|
+
end
|
59
|
+
|
60
|
+
sum
|
61
|
+
end
|
62
|
+
|
63
|
+
def digitized_identifier
|
64
|
+
@digitized_identifier ||= identifier.each_char.map(&method(:char_to_digit))
|
65
|
+
end
|
66
|
+
|
67
|
+
def div_10_mod_10(number)
|
68
|
+
(number / 10) + (number % 10)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/sec_id/isin.rb
CHANGED
@@ -38,9 +38,9 @@ module SecId
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def calculate_check_digit
|
41
|
-
|
41
|
+
return mod_10(luhn_sum) if valid_format?
|
42
42
|
|
43
|
-
|
43
|
+
raise InvalidFormatError, "ISIN '#{isin}' is invalid and check-digit cannot be calculated!"
|
44
44
|
end
|
45
45
|
|
46
46
|
private
|
@@ -57,5 +57,9 @@ module SecId
|
|
57
57
|
|
58
58
|
sum
|
59
59
|
end
|
60
|
+
|
61
|
+
def digitized_identifier
|
62
|
+
@digitized_identifier ||= identifier.each_char.flat_map(&method(:char_to_digits))
|
63
|
+
end
|
60
64
|
end
|
61
65
|
end
|
data/lib/sec_id/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sec_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leonid Svyatov
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- ".rspec"
|
107
107
|
- ".rubocop.yml"
|
108
108
|
- ".travis.yml"
|
109
|
+
- CHANGELOG.md
|
109
110
|
- Gemfile
|
110
111
|
- LICENSE.txt
|
111
112
|
- README.md
|
@@ -114,6 +115,7 @@ files:
|
|
114
115
|
- bin/setup
|
115
116
|
- lib/sec_id.rb
|
116
117
|
- lib/sec_id/base.rb
|
118
|
+
- lib/sec_id/cusip.rb
|
117
119
|
- lib/sec_id/isin.rb
|
118
120
|
- lib/sec_id/version.rb
|
119
121
|
- sec_id.gemspec
|