sec_id 1.1.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f48ba902d7677a13e7d53d251473ba5a8b5c0379c06b09ceacda30df79a31d1
4
- data.tar.gz: 1e54b0d518ca576ad853ff0d170026bbf9e0267d419c8a20209e5334a6d15276
3
+ metadata.gz: 2306a429fff8833b4ba24039424d20249a1cb6ebb9e9928707c61f7396933835
4
+ data.tar.gz: 21a9583ecde80ec5a671bbba7ea3492a99c1f128b091a51ea8b3c50d1c3887f5
5
5
  SHA512:
6
- metadata.gz: 10c3e1c32582736216141a539161a6d8aead43bdde27c4ad25ac95fd02ce4009959585248c24a740b0b8752554d36d31dfe2e3d28b1ec1ca92b9b8be2c69b1e7
7
- data.tar.gz: 41a279f6df4045093a9c870c3ddd03845fe508d0d729bc145f59d96351b3580d51285fbae1aa0bef2c176b4c526d4452a5423796b348028ecbe63a9346430f12
6
+ metadata.gz: 42545c4091db82631984837eb0abda9d34eb78447f25280665835c00e395b750a062037602987451aed333c7b7d233df4df042ea89a60dc1635f0d8904844f57
7
+ data.tar.gz: c1517768cc621f93d92183b4f47e7159e628d02a0f4936ca9658b0b4743498b300afb6d0a7f140e5616776e6b156aa313299dd02a1429c6f3b74897eecf02211
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.0] - 2019-02-03
4
+
5
+ ### Added
6
+
7
+ - SEDOL numbers support: `SecId::SEDOL`
8
+
9
+ ### Updated
10
+
11
+ - **Breaking change**
12
+
13
+ API for accessing full number is unified across all classes:
14
+
15
+ ```
16
+ SecId::ISIN#full_number # previously SecId::ISIN#isin
17
+ SecId::CUSIP#full_number # previously SecId::CUSIP#cusip
18
+ SecId::SEDOL#full_number
19
+ ```
20
+
21
+ ### Fixed
22
+
23
+ - CUSIP check-digit algorithm fixed
24
+
3
25
  ## [1.1.0] - 2019-02-03
4
26
 
5
27
  ### Added
data/README.md CHANGED
@@ -10,10 +10,10 @@ Check-digit calculation is also available.
10
10
 
11
11
  Currently supported standards:
12
12
  [ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number),
13
- [CUSIP](https://en.wikipedia.org/wiki/CUSIP)
13
+ [CUSIP](https://en.wikipedia.org/wiki/CUSIP),
14
+ [SEDOL](https://en.wikipedia.org/wiki/SEDOL)
14
15
 
15
- WIP:
16
- [SEDOL](https://en.wikipedia.org/wiki/SEDOL),
16
+ Work in progress:
17
17
  [IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
18
18
 
19
19
  ## Installation
@@ -21,7 +21,7 @@ WIP:
21
21
  Add this line to your application's Gemfile:
22
22
 
23
23
  ```ruby
24
- gem 'sec_id', '~> 1.1'
24
+ gem 'sec_id', '~> 2.0'
25
25
  ```
26
26
 
27
27
  And then execute:
@@ -107,7 +107,7 @@ SecId::ISIN.check_digit('US594918104') # => 5
107
107
 
108
108
  # instance level
109
109
  isin = SecId::ISIN.new('US5949181045')
110
- isin.isin # => 'US5949181045'
110
+ isin.full_number # => 'US5949181045'
111
111
  isin.country_code # => 'US'
112
112
  isin.nsin # => '594918104'
113
113
  isin.check_digit # => 5
@@ -128,7 +128,7 @@ SecId::CUSIP.check_digit('59491810') # => 5
128
128
 
129
129
  # instance level
130
130
  cusip = SecId::CUSIP.new('594918104')
131
- cusip.cusip # => '594918104'
131
+ cusip.full_number # => '594918104'
132
132
  cusip.cusip6 # => '594918'
133
133
  cusip.issue # => '10'
134
134
  cusip.check_digit # => 4
@@ -138,6 +138,25 @@ cusip.restore! # => '594918104'
138
138
  cusip.calculate_check_digit # => 4
139
139
  ```
140
140
 
141
+ ### SecId::SEDOL full example
142
+
143
+ ```ruby
144
+ # class level
145
+ SecId::SEDOL.valid?('B0Z52W5') # => true
146
+ SecId::SEDOL.valid_format?('B0Z52W') # => true
147
+ SecId::SEDOL.restore!('B0Z52W') # => 'B0Z52W5'
148
+ SecId::SEDOL.check_digit('B0Z52W') # => 5
149
+
150
+ # instance level
151
+ cusip = SecId::SEDOL.new('B0Z52W5')
152
+ cusip.full_number # => 'B0Z52W5'
153
+ cusip.check_digit # => 5
154
+ cusip.valid? # => true
155
+ cusip.valid_format? # => true
156
+ cusip.restore! # => 'B0Z52W5'
157
+ cusip.calculate_check_digit # => 5
158
+ ```
159
+
141
160
  ## Development
142
161
 
143
162
  After checking out the repo, run `bin/setup` to install dependencies.
data/lib/sec_id/base.rb CHANGED
@@ -24,7 +24,7 @@ module SecId
24
24
  '*' => 36, '@' => 37, '#' => 38
25
25
  }.freeze
26
26
 
27
- attr_reader :identifier, :check_digit
27
+ attr_reader :full_number, :identifier, :check_digit
28
28
 
29
29
  def self.valid?(id)
30
30
  new(id).valid?
@@ -47,15 +47,18 @@ module SecId
47
47
  end
48
48
 
49
49
  def valid?
50
- raise NotImplementedError
50
+ return false unless valid_format?
51
+
52
+ check_digit == calculate_check_digit
51
53
  end
52
54
 
53
55
  def valid_format?
54
- raise NotImplementedError
56
+ identifier ? true : false
55
57
  end
56
58
 
57
59
  def restore!
58
- raise NotImplementedError
60
+ @check_digit = calculate_check_digit
61
+ @full_number = to_s
59
62
  end
60
63
 
61
64
  def calculate_check_digit
@@ -73,6 +76,11 @@ module SecId
73
76
  raise NotImplementedError
74
77
  end
75
78
 
79
+ def parse(sec_id_number)
80
+ @full_number = sec_id_number.to_s.strip.upcase
81
+ @full_number.match(self.class::ID_REGEX) || {}
82
+ end
83
+
76
84
  def char_to_digits(char)
77
85
  CHAR_TO_DIGITS.fetch(char)
78
86
  end
@@ -84,5 +92,9 @@ module SecId
84
92
  def mod_10(sum)
85
93
  (10 - (sum % 10)) % 10
86
94
  end
95
+
96
+ def div_10_mod_10(number)
97
+ (number / 10) + (number % 10)
98
+ end
87
99
  end
88
100
  end
data/lib/sec_id/cusip.rb CHANGED
@@ -10,39 +10,20 @@ module SecId
10
10
  (?<check_digit>\d)?
11
11
  \z/x.freeze
12
12
 
13
- VALID_COUNTRY_CODES_FOR_CONVERSION_TO_ISIN = %w[US CA].freeze
14
-
15
- attr_reader :cusip, :cusip6, :issue
13
+ attr_reader :cusip6, :issue
16
14
 
17
15
  def initialize(cusip)
18
- @cusip = cusip.to_s.strip.upcase
19
- cusip_parts = @cusip.match(ID_REGEX) || {}
20
-
16
+ cusip_parts = parse cusip
21
17
  @identifier = cusip_parts[:identifier]
22
18
  @cusip6 = cusip_parts[:cusip6]
23
19
  @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
20
+ @check_digit = cusip_parts[:check_digit]&.to_i
40
21
  end
41
22
 
42
23
  def calculate_check_digit
43
24
  return mod_10(modified_luhn_sum) if valid_format?
44
25
 
45
- raise InvalidFormatError, "CUSIP '#{cusip}' is invalid and check-digit cannot be calculated!"
26
+ raise InvalidFormatError, "CUSIP '#{full_number}' is invalid and check-digit cannot be calculated!"
46
27
  end
47
28
 
48
29
  private
@@ -53,7 +34,6 @@ module SecId
53
34
 
54
35
  digitized_identifier.reverse.each_slice(2) do |even, odd|
55
36
  double_even = (even || 0) * 2
56
- double_even -= 9 if double_even > 9
57
37
  sum += div_10_mod_10(double_even) + div_10_mod_10(odd || 0)
58
38
  end
59
39
 
@@ -63,9 +43,5 @@ module SecId
63
43
  def digitized_identifier
64
44
  @digitized_identifier ||= identifier.each_char.map(&method(:char_to_digit))
65
45
  end
66
-
67
- def div_10_mod_10(number)
68
- (number / 10) + (number % 10)
69
- end
70
46
  end
71
47
  end
data/lib/sec_id/isin.rb CHANGED
@@ -10,37 +10,20 @@ module SecId
10
10
  (?<check_digit>\d)?
11
11
  \z/x.freeze
12
12
 
13
- attr_reader :isin, :country_code, :nsin
13
+ attr_reader :country_code, :nsin
14
14
 
15
15
  def initialize(isin)
16
- @isin = isin.to_s.strip.upcase
17
- isin_parts = @isin.match(ID_REGEX) || {}
18
-
16
+ isin_parts = parse isin
19
17
  @identifier = isin_parts[:identifier]
20
18
  @country_code = isin_parts[:country_code]
21
19
  @nsin = isin_parts[:nsin]
22
- @check_digit = isin_parts[:check_digit].to_i if isin_parts[:check_digit]
23
- end
24
-
25
- def valid?
26
- return false unless valid_format?
27
-
28
- check_digit == calculate_check_digit
29
- end
30
-
31
- def valid_format?
32
- identifier ? true : false
33
- end
34
-
35
- def restore!
36
- @check_digit = calculate_check_digit
37
- @isin = to_s
20
+ @check_digit = isin_parts[:check_digit]&.to_i
38
21
  end
39
22
 
40
23
  def calculate_check_digit
41
24
  return mod_10(luhn_sum) if valid_format?
42
25
 
43
- raise InvalidFormatError, "ISIN '#{isin}' is invalid and check-digit cannot be calculated!"
26
+ raise InvalidFormatError, "ISIN '#{full_number}' is invalid and check-digit cannot be calculated!"
44
27
  end
45
28
 
46
29
  private
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecId
4
+ # https://en.wikipedia.org/wiki/SEDOL
5
+ class SEDOL < Base
6
+ ID_REGEX = /\A
7
+ (?<identifier>[0-9BCDFGHJKLMNPQRSTVWXYZ]{6})
8
+ (?<check_digit>\d)?
9
+ \z/x.freeze
10
+
11
+ CHARACTER_WEIGHTS = [1, 3, 1, 7, 3, 9].freeze
12
+
13
+ attr_reader :full_number
14
+
15
+ def initialize(sedol)
16
+ sedol_parts = parse sedol
17
+ @identifier = sedol_parts[:identifier]
18
+ @check_digit = sedol_parts[:check_digit]&.to_i
19
+ end
20
+
21
+ def calculate_check_digit
22
+ return mod_10(weighted_sum) if valid_format?
23
+
24
+ raise InvalidFormatError, "SEDOL '#{full_number}' is invalid and check-digit cannot be calculated!"
25
+ end
26
+
27
+ private
28
+
29
+ # NOTE: I know this isn't the most idiomatic Ruby code, but it's the fastest one
30
+ def weighted_sum
31
+ index = 0
32
+ sum = 0
33
+
34
+ while index < digitized_identifier.size
35
+ sum += digitized_identifier[index] * CHARACTER_WEIGHTS[index]
36
+ index += 1
37
+ end
38
+
39
+ sum
40
+ end
41
+
42
+ def digitized_identifier
43
+ @digitized_identifier ||= identifier.each_char.map(&method(:char_to_digit))
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecId
4
- VERSION = '1.1.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/sec_id.rb CHANGED
@@ -5,6 +5,7 @@ require 'sec_id/version'
5
5
  require 'sec_id/base'
6
6
  require 'sec_id/isin'
7
7
  require 'sec_id/cusip'
8
+ require 'sec_id/sedol'
8
9
 
9
10
  module SecId
10
11
  Error = Class.new(StandardError)
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.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
@@ -117,6 +117,7 @@ files:
117
117
  - lib/sec_id/base.rb
118
118
  - lib/sec_id/cusip.rb
119
119
  - lib/sec_id/isin.rb
120
+ - lib/sec_id/sedol.rb
120
121
  - lib/sec_id/version.rb
121
122
  - sec_id.gemspec
122
123
  homepage: https://github.com/svyatov/sec_id