sec_id 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](https://badge.fury.io/rb/sec_id)
|
3
3
|
[](https://travis-ci.org/svyatov/sec_id)
|
4
|
+
[](https://codeclimate.com/github/svyatov/sec_id/maintainability)
|
5
|
+
[](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
|