sec_id 6.1.0 → 7.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/MIGRATION.md +147 -0
  4. data/README.md +108 -45
  5. data/lib/sec_id/base.rb +36 -17
  6. data/lib/sec_id/cei.rb +8 -7
  7. data/lib/sec_id/cik.rb +1 -1
  8. data/lib/sec_id/concerns/checkable.rb +90 -46
  9. data/lib/sec_id/concerns/generatable.rb +5 -5
  10. data/lib/sec_id/concerns/normalizable.rb +4 -4
  11. data/lib/sec_id/concerns/suggestable.rb +168 -0
  12. data/lib/sec_id/concerns/validatable.rb +7 -7
  13. data/lib/sec_id/cusip.rb +9 -8
  14. data/lib/sec_id/deprecation.rb +26 -0
  15. data/lib/sec_id/dti.rb +9 -14
  16. data/lib/sec_id/figi.rb +10 -9
  17. data/lib/sec_id/iban.rb +21 -20
  18. data/lib/sec_id/isin.rb +10 -9
  19. data/lib/sec_id/lei.rb +11 -10
  20. data/lib/sec_id/occ.rb +1 -1
  21. data/lib/sec_id/sedol.rb +12 -11
  22. data/lib/sec_id/suggestion.rb +59 -0
  23. data/lib/sec_id/upi.rb +77 -0
  24. data/lib/sec_id/valoren.rb +2 -2
  25. data/lib/sec_id/version.rb +1 -1
  26. data/lib/sec_id/wkn.rb +1 -1
  27. data/lib/sec_id.rb +43 -3
  28. data/sec_id.gemspec +5 -4
  29. data/sig/sec_id/base.rbs +16 -5
  30. data/sig/sec_id/cei.rbs +4 -0
  31. data/sig/sec_id/concerns/checkable.rbs +18 -7
  32. data/sig/sec_id/concerns/generatable.rbs +1 -1
  33. data/sig/sec_id/concerns/suggestable.rbs +32 -0
  34. data/sig/sec_id/concerns/validatable.rbs +1 -1
  35. data/sig/sec_id/cusip.rbs +4 -0
  36. data/sig/sec_id/deprecation.rbs +6 -0
  37. data/sig/sec_id/dti.rbs +6 -1
  38. data/sig/sec_id/figi.rbs +4 -0
  39. data/sig/sec_id/iban.rbs +7 -3
  40. data/sig/sec_id/isin.rbs +4 -0
  41. data/sig/sec_id/lei.rbs +5 -1
  42. data/sig/sec_id/sedol.rbs +4 -0
  43. data/sig/sec_id/suggestion.rbs +23 -0
  44. data/sig/sec_id/upi.rbs +29 -0
  45. data/sig/sec_id.rbs +7 -2
  46. metadata +14 -5
@@ -8,7 +8,7 @@ module SecID
8
8
  module Validatable
9
9
  # Maps error-code symbols to their exception classes; unmapped codes default to InvalidFormatError.
10
10
  ERROR_MAP = {
11
- invalid_check_digit: InvalidCheckDigitError,
11
+ invalid_checksum: InvalidChecksumError,
12
12
  invalid_prefix: InvalidStructureError,
13
13
  invalid_category: InvalidStructureError,
14
14
  invalid_group: InvalidStructureError,
@@ -43,7 +43,7 @@ module SecID
43
43
  #
44
44
  # @param id [String] the identifier to validate
45
45
  # @return [Base] the identifier instance
46
- # @raise [InvalidFormatError, InvalidCheckDigitError, InvalidStructureError]
46
+ # @raise [InvalidFormatError, InvalidChecksumError, InvalidStructureError]
47
47
  def validate!(id)
48
48
  new(id).validate!
49
49
  end
@@ -82,7 +82,7 @@ module SecID
82
82
  # Validates and returns self if valid, raises an exception otherwise.
83
83
  #
84
84
  # @return [self]
85
- # @raise [InvalidFormatError, InvalidCheckDigitError, InvalidStructureError]
85
+ # @raise [InvalidFormatError, InvalidChecksumError, InvalidStructureError]
86
86
  def validate!
87
87
  return self if valid?
88
88
 
@@ -119,7 +119,7 @@ module SecID
119
119
  end
120
120
 
121
121
  # Checks length against ID_LENGTH's three shapes: Range (bounds), Array
122
- # (discrete valid lengths), or Integer (allows an optional check digit).
122
+ # (discrete valid lengths), or Integer (allows an optional checksum).
123
123
  #
124
124
  # @return [Boolean]
125
125
  def valid_length?
@@ -130,7 +130,7 @@ module SecID
130
130
  case id_length
131
131
  when Range then id_length.cover?(len)
132
132
  when Array then id_length.include?(len)
133
- else ((id_length - check_digit_width)..id_length).cover?(len)
133
+ else ((id_length - checksum_width)..id_length).cover?(len)
134
134
  end
135
135
  end
136
136
 
@@ -139,8 +139,8 @@ module SecID
139
139
  full_id.match?(self.class::VALID_CHARS_REGEX)
140
140
  end
141
141
 
142
- # @return [Integer] width of the check digit (0 for non-checkable, overridden in Checkable)
143
- def check_digit_width
142
+ # @return [Integer] width of the checksum (0 for non-checkable, overridden in Checkable)
143
+ def checksum_width
144
144
  0
145
145
  end
146
146
 
data/lib/sec_id/cusip.rb CHANGED
@@ -4,7 +4,7 @@ module SecID
4
4
  # Committee on Uniform Securities Identification Procedures (CUSIP) - a 9-character
5
5
  # alphanumeric code that identifies North American securities.
6
6
  #
7
- # Format: 6-character issuer code (CUSIP-6) + 2-character issue number + 1-digit check digit
7
+ # Format: 6-character issuer code (CUSIP-6) + 2-character issue number + 1-digit checksum
8
8
  #
9
9
  # @see https://en.wikipedia.org/wiki/CUSIP
10
10
  #
@@ -16,6 +16,7 @@ module SecID
16
16
  # cusip.to_isin('US') #=> #<SecID::ISIN>
17
17
  class CUSIP < Base
18
18
  include Checkable
19
+ include Suggestable
19
20
 
20
21
  # Human-readable name of the standard.
21
22
  FULL_NAME = 'Committee on Uniform Securities Identification Procedures'
@@ -31,7 +32,7 @@ module SecID
31
32
  (?<identifier>
32
33
  (?<cusip6>[A-Z0-9]{5}[A-Z0-9*@#])
33
34
  (?<issue>[A-Z0-9*@#]{2}))
34
- (?<check_digit>\d)?
35
+ (?<checksum>\d)?
35
36
  \z/x
36
37
 
37
38
  # @return [String, nil] the 6-character issuer code
@@ -46,19 +47,19 @@ module SecID
46
47
  @identifier = cusip_parts[:identifier]
47
48
  @cusip6 = cusip_parts[:cusip6]
48
49
  @issue = cusip_parts[:issue]
49
- @check_digit = cusip_parts[:check_digit]&.to_i
50
+ @checksum = cusip_parts[:checksum]&.to_i
50
51
  end
51
52
 
52
53
  # @return [String, nil]
53
54
  def to_pretty_s
54
55
  return nil unless valid?
55
56
 
56
- "#{cusip6} #{issue} #{check_digit}"
57
+ "#{cusip6} #{issue} #{checksum}"
57
58
  end
58
59
 
59
- # @return [Integer] the calculated check digit (0-9)
60
+ # @return [Integer] the calculated checksum (0-9)
60
61
  # @raise [InvalidFormatError] if the CUSIP format is invalid
61
- def calculate_check_digit
62
+ def calculate_checksum
62
63
  validate_format_for_calculation!
63
64
  mod10(luhn_sum_double_add_double(reversed_digits_single(identifier)))
64
65
  end
@@ -66,7 +67,7 @@ module SecID
66
67
  # Generates a random CUSIP body: 8 alphanumeric characters.
67
68
  #
68
69
  # @param random [Random] source of randomness
69
- # @return [String] an 8-character CUSIP body without check digit
70
+ # @return [String] an 8-character CUSIP body without checksum
70
71
  def self.generate_body(random)
71
72
  random_string(ALPHANUMERIC, 8, random: random)
72
73
  end
@@ -91,6 +92,6 @@ module SecID
91
92
  private
92
93
 
93
94
  # @return [Hash]
94
- def components = { cusip6:, issue:, check_digit: }
95
+ def components = { cusip6:, issue:, checksum: }
95
96
  end
96
97
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecID
4
+ # Emits standardized, silenceable deprecation warnings for the v7 → v8 rename
5
+ # bridge. Stateless: warns on every call with no per-call-site dedup, so the
6
+ # migration signal is never suppressed by default.
7
+ #
8
+ # The warning is written via {Kernel#warn} at Ruby's default verbosity — it is
9
+ # visible unless the process runs with `-W0` / `$VERBOSE = nil`. No `category:`
10
+ # is passed, because `Warning[:deprecated]` defaults to `false` and would hide
11
+ # the bridge warning the rename relies on.
12
+ module Deprecation
13
+ # Emits a standardized deprecation warning for a renamed API name.
14
+ #
15
+ # @param old [String] the deprecated name
16
+ # @param new [String] the canonical replacement name
17
+ # @param removed_in [String] the version that removes the deprecated name
18
+ # @return [void]
19
+ def self.warn(old:, new:, removed_in: 'v8')
20
+ Kernel.warn(
21
+ "SecID: `#{old}` is deprecated and will be removed in #{removed_in}; use `#{new}` instead.",
22
+ uplevel: 2
23
+ )
24
+ end
25
+ end
26
+ end
data/lib/sec_id/dti.rb CHANGED
@@ -13,10 +13,11 @@ module SecID
13
13
  # @example Validate a DTI
14
14
  # SecID::DTI.valid?('X9J9K872S') #=> true
15
15
  #
16
- # @example Restore check digit
16
+ # @example Restore checksum
17
17
  # SecID::DTI.restore('X9J9K872') #=> 'X9J9K872S'
18
18
  class DTI < Base
19
19
  include Checkable
20
+ include Suggestable
20
21
 
21
22
  # Human-readable name of the standard.
22
23
  FULL_NAME = 'Digital Token Identifier'
@@ -33,7 +34,7 @@ module SecID
33
34
  (?<identifier>
34
35
  [1-9B-DF-HJ-NP-TV-XZ]
35
36
  [0-9B-DF-HJ-NP-TV-XZ]{7})
36
- (?<check_digit>[0-9B-DF-HJ-NP-TV-XZ])?
37
+ (?<checksum>[0-9B-DF-HJ-NP-TV-XZ])?
37
38
  \z/x
38
39
 
39
40
  # The 30-symbol DTI alphabet, ordered by check-character value (0-29).
@@ -55,14 +56,14 @@ module SecID
55
56
  def initialize(dti)
56
57
  dti_parts = parse dti
57
58
  @identifier = dti_parts[:identifier]
58
- @check_digit = dti_parts[:check_digit]
59
+ @checksum = dti_parts[:checksum]
59
60
  end
60
61
 
61
62
  # @return [String] the calculated or grandfathered check character
62
63
  # @raise [InvalidFormatError] if the DTI format is invalid
63
- def calculate_check_digit
64
+ def calculate_checksum
64
65
  validate_format_for_calculation!
65
- grandfathered_check_digit(identifier) || iso7064_mod31_30_check_char(identifier)
66
+ grandfathered_checksum(identifier) || iso7064_mod31_30_check_char(identifier)
66
67
  end
67
68
 
68
69
  # Generates a random DTI body: first character non-zero, 8 characters total,
@@ -79,11 +80,11 @@ module SecID
79
80
  private
80
81
 
81
82
  # @return [Hash]
82
- def components = { check_digit: }
83
+ def components = { checksum: }
83
84
 
84
85
  # @param base [String] the 8-character DTI base
85
86
  # @return [String, nil] the registered check character, or nil if not grandfathered
86
- def grandfathered_check_digit(base)
87
+ def grandfathered_checksum(base)
87
88
  GRANDFATHERED_CODES[base]&.delete_prefix(base)
88
89
  end
89
90
 
@@ -92,13 +93,7 @@ module SecID
92
93
  # @param base [String] the 8-character DTI base
93
94
  # @return [String] the single computed check character
94
95
  def iso7064_mod31_30_check_char(base)
95
- perm = 30
96
- base.each_char do |c|
97
- s = (perm + ALPHABET_VALUE.fetch(c)) % 30
98
- s = 30 if s.zero?
99
- perm = (s * 2) % 31
100
- end
101
- ALPHABET.fetch((31 - perm) % 30)
96
+ mod31_30_check_char(base, ALPHABET, ALPHABET_VALUE)
102
97
  end
103
98
  end
104
99
  end
data/lib/sec_id/figi.rb CHANGED
@@ -4,7 +4,7 @@ module SecID
4
4
  # Financial Instrument Global Identifier (FIGI) - a 12-character alphanumeric code
5
5
  # that uniquely identifies financial instruments.
6
6
  #
7
- # Format: 2-character prefix + 'G' + 8-character random part + 1-digit check digit
7
+ # Format: 2-character prefix + 'G' + 8-character random part + 1-digit checksum
8
8
  # Note: FIGI excludes vowels (A, E, I, O, U) from valid characters.
9
9
  #
10
10
  # @see https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier
@@ -12,10 +12,11 @@ module SecID
12
12
  # @example Validate a FIGI
13
13
  # SecID::FIGI.valid?('BBG000BLNQ16') #=> true
14
14
  #
15
- # @example Restore check digit
15
+ # @example Restore checksum
16
16
  # SecID::FIGI.restore!('BBG000BLNQ1') #=> #<SecID::FIGI>
17
17
  class FIGI < Base
18
18
  include Checkable
19
+ include Suggestable
19
20
 
20
21
  # Human-readable name of the standard.
21
22
  FULL_NAME = 'Financial Instrument Global Identifier'
@@ -33,7 +34,7 @@ module SecID
33
34
  (?<prefix>[B-DF-HJ-NP-TV-Z0-9]{2})
34
35
  G
35
36
  (?<random_part>[B-DF-HJ-NP-TV-Z0-9]{8}))
36
- (?<check_digit>\d)?
37
+ (?<checksum>\d)?
37
38
  \z/x
38
39
 
39
40
  # Country-code prefixes that are restricted from use in FIGI.
@@ -54,19 +55,19 @@ module SecID
54
55
  @identifier = figi_parts[:identifier]
55
56
  @prefix = figi_parts[:prefix]
56
57
  @random_part = figi_parts[:random_part]
57
- @check_digit = figi_parts[:check_digit]&.to_i
58
+ @checksum = figi_parts[:checksum]&.to_i
58
59
  end
59
60
 
60
61
  # @return [String, nil]
61
62
  def to_pretty_s
62
63
  return nil unless valid?
63
64
 
64
- "#{prefix}G #{random_part} #{check_digit}"
65
+ "#{prefix}G #{random_part} #{checksum}"
65
66
  end
66
67
 
67
- # @return [Integer] the calculated check digit (0-9)
68
+ # @return [Integer] the calculated checksum (0-9)
68
69
  # @raise [InvalidFormatError] if the FIGI format is invalid
69
- def calculate_check_digit
70
+ def calculate_checksum
70
71
  validate_format_for_calculation!
71
72
  mod10(luhn_sum_indexed(reversed_digits_single(identifier)))
72
73
  end
@@ -74,7 +75,7 @@ module SecID
74
75
  # Generates a random FIGI body: non-reserved 2-char prefix + 'G' + 8-char random part.
75
76
  #
76
77
  # @param random [Random] source of randomness
77
- # @return [String] an 11-character FIGI body without check digit
78
+ # @return [String] an 11-character FIGI body without checksum
78
79
  def self.generate_body(random)
79
80
  prefix = random_string(GENERATE_CHARSET, 2, random: random)
80
81
  prefix = random_string(GENERATE_CHARSET, 2, random: random) while RESTRICTED_PREFIXES.include?(prefix)
@@ -85,7 +86,7 @@ module SecID
85
86
  private
86
87
 
87
88
  # @return [Hash]
88
- def components = { prefix:, random_part:, check_digit: }
89
+ def components = { prefix:, random_part:, checksum: }
89
90
 
90
91
  # @return [Boolean]
91
92
  def valid_format?
data/lib/sec_id/iban.rb CHANGED
@@ -6,8 +6,8 @@ module SecID
6
6
  # International Bank Account Number (IBAN) - an international standard for identifying
7
7
  # bank accounts across national borders (ISO 13616).
8
8
  #
9
- # Format: 2-letter country code + 2-digit check digits + BBAN (Basic Bank Account Number, 11-30 chars)
10
- # Note: Unlike other SecID identifiers, the check digits are in positions 3-4, not at the end.
9
+ # Format: 2-letter country code + 2-digit checksum + BBAN (Basic Bank Account Number, 11-30 chars)
10
+ # Note: Unlike other SecID identifiers, the checksum are in positions 3-4, not at the end.
11
11
  #
12
12
  # @see https://en.wikipedia.org/wiki/International_Bank_Account_Number
13
13
  # @see https://www.iban.com/structure
@@ -15,10 +15,11 @@ module SecID
15
15
  # @example Validate an IBAN
16
16
  # SecID::IBAN.valid?('DE89370400440532013000') #=> true
17
17
  #
18
- # @example Restore check digits
18
+ # @example Restore checksum
19
19
  # SecID::IBAN.restore!('DE00370400440532013000') #=> #<SecID::IBAN>
20
20
  class IBAN < Base
21
21
  include Checkable
22
+ include Suggestable
22
23
 
23
24
  # Human-readable name of the standard.
24
25
  FULL_NAME = 'International Bank Account Number'
@@ -30,7 +31,7 @@ module SecID
30
31
  VALID_CHARS_REGEX = /\A[A-Z0-9]+\z/
31
32
 
32
33
  # Regular expression for parsing IBAN components.
33
- # Note: Check digit positioning is handled in initialize, not in the regex.
34
+ # Note: Checksum positioning is handled in initialize, not in the regex.
34
35
  ID_REGEX = /\A
35
36
  (?<country_code>[A-Z]{2})
36
37
  (?<rest>[A-Z0-9]{13,32})
@@ -62,7 +63,7 @@ module SecID
62
63
  # @return [String, nil] the account number (extracted from BBAN if country rules define it)
63
64
  attr_reader :account_number
64
65
 
65
- # @return [String, nil] the national check digit (extracted from BBAN if country rules define it)
66
+ # @return [String, nil] the national checksum (extracted from BBAN if country rules define it)
66
67
  attr_reader :national_check
67
68
 
68
69
  # @param iban [String] the IBAN string to parse
@@ -72,7 +73,7 @@ module SecID
72
73
  rest = iban_parts[:rest]
73
74
 
74
75
  if @country_code && rest
75
- extract_check_digit_and_bban(rest)
76
+ extract_checksum_and_bban(rest)
76
77
  @identifier = "#{@country_code}#{@bban}" if @bban
77
78
  end
78
79
 
@@ -82,19 +83,19 @@ module SecID
82
83
  # @return [String]
83
84
  # @raise [InvalidFormatError] if the IBAN format is invalid
84
85
  def restore
85
- cd = calculate_check_digit
86
+ cd = calculate_checksum
86
87
  "#{country_code}#{cd.to_s.rjust(2, '0')}#{bban}"
87
88
  end
88
89
 
89
90
  # @return [Integer] the calculated 2-digit check value (1-98)
90
91
  # @raise [InvalidFormatError] if the IBAN format is invalid
91
- def calculate_check_digit
92
+ def calculate_checksum
92
93
  validate_format_for_calculation!
93
94
  mod97(numeric_string_for_check)
94
95
  end
95
96
 
96
- # Generates a random IBAN body for a numeric-BBAN country with placeholder check digits.
97
- # The default {Generatable::ClassMethods#generate} restores the real check digits via `restore!`.
97
+ # Generates a random IBAN body for a numeric-BBAN country with placeholder checksum.
98
+ # The default {Generatable::ClassMethods#generate} restores the real checksum via `restore!`.
98
99
  #
99
100
  # @param random [Random] source of randomness
100
101
  # @return [String] a country code + "00" + numeric BBAN
@@ -126,9 +127,9 @@ module SecID
126
127
 
127
128
  # @return [String]
128
129
  def to_s
129
- return full_id unless check_digit
130
+ return full_id unless checksum
130
131
 
131
- "#{country_code}#{check_digit.to_s.rjust(2, '0')}#{bban}"
132
+ "#{country_code}#{checksum.to_s.rjust(2, '0')}#{bban}"
132
133
  end
133
134
 
134
135
  # @return [String, nil]
@@ -140,7 +141,7 @@ module SecID
140
141
 
141
142
  # @return [Hash]
142
143
  def components
143
- hash = { country_code:, bban:, check_digit: }
144
+ hash = { country_code:, bban:, checksum: }
144
145
  hash[:bank_code] = bank_code if bank_code
145
146
  hash[:branch_code] = branch_code if branch_code
146
147
  hash[:account_number] = account_number if account_number
@@ -149,7 +150,7 @@ module SecID
149
150
  end
150
151
 
151
152
  # @return [Integer]
152
- def check_digit_width
153
+ def checksum_width
153
154
  2
154
155
  end
155
156
 
@@ -177,14 +178,14 @@ module SecID
177
178
 
178
179
  # @param rest [String] the IBAN string after country code
179
180
  # @return [void]
180
- def extract_check_digit_and_bban(rest)
181
+ def extract_checksum_and_bban(rest)
181
182
  expected = expected_bban_length_for_country
182
183
 
183
- if check_digits?(rest, expected)
184
- @check_digit = rest[0, 2].to_i
184
+ if checksum?(rest, expected)
185
+ @checksum = rest[0, 2].to_i
185
186
  @bban = rest[2..]
186
187
  else
187
- @check_digit = nil
188
+ @checksum = nil
188
189
  @bban = rest
189
190
  end
190
191
  end
@@ -197,11 +198,11 @@ module SecID
197
198
  # @param rest [String] the IBAN string after country code
198
199
  # @param expected_bban_length [Integer, nil] the expected BBAN length for the country
199
200
  # @return [Boolean]
200
- def check_digits?(rest, expected_bban_length)
201
+ def checksum?(rest, expected_bban_length)
201
202
  return false unless rest[0, 2].match?(/\A\d{2}\z/)
202
203
  return true unless expected_bban_length
203
204
 
204
- # If we know expected BBAN length, check if rest matches with or without check digits
205
+ # If we know expected BBAN length, check if rest matches with or without checksum
205
206
  rest.length == expected_bban_length + 2 || rest.length != expected_bban_length
206
207
  end
207
208
 
data/lib/sec_id/isin.rb CHANGED
@@ -4,17 +4,18 @@ module SecID
4
4
  # International Securities Identification Number (ISIN) - a 12-character alphanumeric code
5
5
  # that uniquely identifies a security globally.
6
6
  #
7
- # Format: 2-letter country code + 9-character NSIN + 1-digit check digit
7
+ # Format: 2-letter country code + 9-character NSIN + 1-digit checksum
8
8
  #
9
9
  # @see https://en.wikipedia.org/wiki/International_Securities_Identification_Number
10
10
  #
11
11
  # @example Validate an ISIN
12
12
  # SecID::ISIN.valid?('US5949181045') #=> true
13
13
  #
14
- # @example Restore check digit
14
+ # @example Restore checksum
15
15
  # SecID::ISIN.restore!('US594918104') #=> #<SecID::ISIN>
16
16
  class ISIN < Base
17
17
  include Checkable
18
+ include Suggestable
18
19
 
19
20
  # Human-readable name of the standard.
20
21
  FULL_NAME = 'International Securities Identification Number'
@@ -30,7 +31,7 @@ module SecID
30
31
  (?<identifier>
31
32
  (?<country_code>[A-Z]{2})
32
33
  (?<nsin>[A-Z0-9]{9}))
33
- (?<check_digit>\d)?
34
+ (?<checksum>\d)?
34
35
  \z/x
35
36
 
36
37
  # Country codes that use CUSIP Global Services (CGS) for NSIN assignment.
@@ -71,19 +72,19 @@ module SecID
71
72
  @identifier = isin_parts[:identifier]
72
73
  @country_code = isin_parts[:country_code]
73
74
  @nsin = isin_parts[:nsin]
74
- @check_digit = isin_parts[:check_digit]&.to_i
75
+ @checksum = isin_parts[:checksum]&.to_i
75
76
  end
76
77
 
77
78
  # @return [String, nil]
78
79
  def to_pretty_s
79
80
  return nil unless valid?
80
81
 
81
- "#{country_code} #{nsin} #{check_digit}"
82
+ "#{country_code} #{nsin} #{checksum}"
82
83
  end
83
84
 
84
- # @return [Integer] the calculated check digit (0-9)
85
+ # @return [Integer] the calculated checksum (0-9)
85
86
  # @raise [InvalidFormatError] if the ISIN format is invalid
86
- def calculate_check_digit
87
+ def calculate_checksum
87
88
  validate_format_for_calculation!
88
89
  mod10(luhn_sum_standard(reversed_digits_multi(identifier)))
89
90
  end
@@ -91,7 +92,7 @@ module SecID
91
92
  # Generates a random ISIN body: 2-letter country code + 9-character NSIN.
92
93
  #
93
94
  # @param random [Random] source of randomness
94
- # @return [String] an 11-character ISIN body without check digit
95
+ # @return [String] an 11-character ISIN body without checksum
95
96
  def self.generate_body(random)
96
97
  "#{random_string(ALPHA, 2, random: random)}#{random_string(ALPHANUMERIC, 9, random: random)}"
97
98
  end
@@ -177,6 +178,6 @@ module SecID
177
178
  private
178
179
 
179
180
  # @return [Hash]
180
- def components = { country_code:, nsin:, check_digit: }
181
+ def components = { country_code:, nsin:, checksum: }
181
182
  end
182
183
  end
data/lib/sec_id/lei.rb CHANGED
@@ -4,7 +4,7 @@ module SecID
4
4
  # Legal Entity Identifier (LEI) - a 20-character alphanumeric code that
5
5
  # uniquely identifies legal entities participating in financial transactions.
6
6
  #
7
- # Format: 4-character LOU ID + 2-character reserved + 12-character entity ID + 2-digit check digit
7
+ # Format: 4-character LOU ID + 2-character reserved + 12-character entity ID + 2-digit checksum
8
8
  #
9
9
  # @see https://en.wikipedia.org/wiki/Legal_Entity_Identifier
10
10
  # @see https://www.gleif.org/en/about-lei/iso-17442-the-lei-code-structure
@@ -12,10 +12,11 @@ module SecID
12
12
  # @example Validate a LEI
13
13
  # SecID::LEI.valid?('529900T8BM49AURSDO55') #=> true
14
14
  #
15
- # @example Calculate check digit
16
- # SecID::LEI.check_digit('529900T8BM49AURSDO') #=> 55
15
+ # @example Calculate checksum
16
+ # SecID::LEI.checksum('529900T8BM49AURSDO') #=> 55
17
17
  class LEI < Base
18
18
  include Checkable
19
+ include Suggestable
19
20
 
20
21
  # Human-readable name of the standard.
21
22
  FULL_NAME = 'Legal Entity Identifier'
@@ -32,7 +33,7 @@ module SecID
32
33
  (?<lou_id>[0-9A-Z]{4})
33
34
  (?<reserved>[0-9A-Z]{2})
34
35
  (?<entity_id>[0-9A-Z]{12}))
35
- (?<check_digit>\d{2})?
36
+ (?<checksum>\d{2})?
36
37
  \z/x
37
38
 
38
39
  # @return [String, nil] the 4-character Local Operating Unit (LOU) identifier
@@ -51,7 +52,7 @@ module SecID
51
52
  @lou_id = lei_parts[:lou_id]
52
53
  @reserved = lei_parts[:reserved]
53
54
  @entity_id = lei_parts[:entity_id]
54
- @check_digit = lei_parts[:check_digit]&.to_i
55
+ @checksum = lei_parts[:checksum]&.to_i
55
56
  end
56
57
 
57
58
  # @return [String, nil]
@@ -61,9 +62,9 @@ module SecID
61
62
  to_s.scan(/.{1,4}/).join(' ')
62
63
  end
63
64
 
64
- # @return [Integer] the calculated 2-digit check digit (1-98)
65
+ # @return [Integer] the calculated 2-digit checksum (1-98)
65
66
  # @raise [InvalidFormatError] if the LEI format is invalid
66
- def calculate_check_digit
67
+ def calculate_checksum
67
68
  validate_format_for_calculation!
68
69
  mod97("#{numeric_identifier}00")
69
70
  end
@@ -71,7 +72,7 @@ module SecID
71
72
  # Generates a random LEI body: 18 alphanumeric characters.
72
73
  #
73
74
  # @param random [Random] source of randomness
74
- # @return [String] an 18-character LEI body without check digits
75
+ # @return [String] an 18-character LEI body without checksum
75
76
  def self.generate_body(random)
76
77
  random_string(ALPHANUMERIC, 18, random: random)
77
78
  end
@@ -80,10 +81,10 @@ module SecID
80
81
  private
81
82
 
82
83
  # @return [Hash]
83
- def components = { lou_id:, reserved:, entity_id:, check_digit: }
84
+ def components = { lou_id:, reserved:, entity_id:, checksum: }
84
85
 
85
86
  # @return [Integer]
86
- def check_digit_width
87
+ def checksum_width
87
88
  2
88
89
  end
89
90
 
data/lib/sec_id/occ.rb CHANGED
@@ -6,7 +6,7 @@ module SecID
6
6
  # OCC Option Symbol - standardized option symbol format used by Option Clearing Corporation.
7
7
  # Format: 6-char underlying (padded) + 6-char date (YYMMDD) + type (C/P) + 8-digit strike (in mills).
8
8
  #
9
- # @note OCC identifiers have no check digit and validation includes both format
9
+ # @note OCC identifiers have no checksum and validation includes both format
10
10
  # and date parseability checks.
11
11
  #
12
12
  # @see https://en.wikipedia.org/wiki/Option_symbol#The_OCC_Option_Symbol
data/lib/sec_id/sedol.rb CHANGED
@@ -4,7 +4,7 @@ module SecID
4
4
  # Stock Exchange Daily Official List (SEDOL) - a 7-character alphanumeric code
5
5
  # that identifies securities traded on the London Stock Exchange and other UK exchanges.
6
6
  #
7
- # Format: 6-character identifier + 1-digit check digit
7
+ # Format: 6-character identifier + 1-digit checksum
8
8
  # Note: SEDOL excludes vowels (A, E, I, O, U) to avoid forming words.
9
9
  #
10
10
  # @see https://en.wikipedia.org/wiki/SEDOL
@@ -12,10 +12,11 @@ module SecID
12
12
  # @example Validate a SEDOL
13
13
  # SecID::SEDOL.valid?('B19GKT4') #=> true
14
14
  #
15
- # @example Calculate check digit
16
- # SecID::SEDOL.check_digit('B19GKT') #=> 4
15
+ # @example Calculate checksum
16
+ # SecID::SEDOL.checksum('B19GKT') #=> 4
17
17
  class SEDOL < Base
18
18
  include Checkable
19
+ include Suggestable
19
20
 
20
21
  # Human-readable name of the standard.
21
22
  FULL_NAME = 'Stock Exchange Daily Official List'
@@ -30,10 +31,10 @@ module SecID
30
31
  # Excludes vowels (A, E, I, O, U) from valid characters.
31
32
  ID_REGEX = /\A
32
33
  (?<identifier>[0-9BCDFGHJKLMNPQRSTVWXYZ]{6})
33
- (?<check_digit>\d)?
34
+ (?<checksum>\d)?
34
35
  \z/x
35
36
 
36
- # Weights applied to each character position in the check digit calculation.
37
+ # Weights applied to each character position in the checksum calculation.
37
38
  CHARACTER_WEIGHTS = [1, 3, 1, 7, 3, 9].freeze
38
39
 
39
40
  # Characters valid in a SEDOL body (alphanumeric excluding vowels).
@@ -43,14 +44,14 @@ module SecID
43
44
  def initialize(sedol)
44
45
  sedol_parts = parse sedol
45
46
  @identifier = sedol_parts[:identifier]
46
- @check_digit = sedol_parts[:check_digit]&.to_i
47
+ @checksum = sedol_parts[:checksum]&.to_i
47
48
  end
48
49
 
49
50
  # Valid country codes for SEDOL to ISIN conversion.
50
51
  ISIN_COUNTRY_CODES = Set.new(%w[GB IE GG IM JE FK]).freeze
51
52
 
52
53
  # @param country_code [String] the ISO 3166-1 alpha-2 country code (default: 'GB')
53
- # @return [ISIN] a new ISIN instance with calculated check digit
54
+ # @return [ISIN] a new ISIN instance with calculated checksum
54
55
  # @raise [InvalidFormatError] if the country code is not valid for SEDOL
55
56
  def to_isin(country_code = 'GB')
56
57
  unless ISIN_COUNTRY_CODES.include?(country_code)
@@ -60,9 +61,9 @@ module SecID
60
61
  ISIN.new("#{country_code}00#{restore}").restore!
61
62
  end
62
63
 
63
- # @return [Integer] the calculated check digit (0-9)
64
+ # @return [Integer] the calculated checksum (0-9)
64
65
  # @raise [InvalidFormatError] if the SEDOL format is invalid
65
- def calculate_check_digit
66
+ def calculate_checksum
66
67
  validate_format_for_calculation!
67
68
  mod10(weighted_sum)
68
69
  end
@@ -70,7 +71,7 @@ module SecID
70
71
  # Generates a random SEDOL body: 6 characters excluding vowels.
71
72
  #
72
73
  # @param random [Random] source of randomness
73
- # @return [String] a 6-character SEDOL body without check digit
74
+ # @return [String] a 6-character SEDOL body without checksum
74
75
  def self.generate_body(random)
75
76
  random_string(GENERATE_CHARSET, 6, random: random)
76
77
  end
@@ -79,7 +80,7 @@ module SecID
79
80
  private
80
81
 
81
82
  # @return [Hash]
82
- def components = { check_digit: }
83
+ def components = { checksum: }
83
84
 
84
85
  # NOTE: Not idiomatic Ruby, but optimized for performance.
85
86
  #