sec_id 6.1.0 → 7.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.
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,7 +15,7 @@ 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
@@ -30,7 +30,7 @@ module SecID
30
30
  VALID_CHARS_REGEX = /\A[A-Z0-9]+\z/
31
31
 
32
32
  # Regular expression for parsing IBAN components.
33
- # Note: Check digit positioning is handled in initialize, not in the regex.
33
+ # Note: Checksum positioning is handled in initialize, not in the regex.
34
34
  ID_REGEX = /\A
35
35
  (?<country_code>[A-Z]{2})
36
36
  (?<rest>[A-Z0-9]{13,32})
@@ -62,7 +62,7 @@ module SecID
62
62
  # @return [String, nil] the account number (extracted from BBAN if country rules define it)
63
63
  attr_reader :account_number
64
64
 
65
- # @return [String, nil] the national check digit (extracted from BBAN if country rules define it)
65
+ # @return [String, nil] the national checksum (extracted from BBAN if country rules define it)
66
66
  attr_reader :national_check
67
67
 
68
68
  # @param iban [String] the IBAN string to parse
@@ -72,7 +72,7 @@ module SecID
72
72
  rest = iban_parts[:rest]
73
73
 
74
74
  if @country_code && rest
75
- extract_check_digit_and_bban(rest)
75
+ extract_checksum_and_bban(rest)
76
76
  @identifier = "#{@country_code}#{@bban}" if @bban
77
77
  end
78
78
 
@@ -82,19 +82,19 @@ module SecID
82
82
  # @return [String]
83
83
  # @raise [InvalidFormatError] if the IBAN format is invalid
84
84
  def restore
85
- cd = calculate_check_digit
85
+ cd = calculate_checksum
86
86
  "#{country_code}#{cd.to_s.rjust(2, '0')}#{bban}"
87
87
  end
88
88
 
89
89
  # @return [Integer] the calculated 2-digit check value (1-98)
90
90
  # @raise [InvalidFormatError] if the IBAN format is invalid
91
- def calculate_check_digit
91
+ def calculate_checksum
92
92
  validate_format_for_calculation!
93
93
  mod97(numeric_string_for_check)
94
94
  end
95
95
 
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!`.
96
+ # Generates a random IBAN body for a numeric-BBAN country with placeholder checksum.
97
+ # The default {Generatable::ClassMethods#generate} restores the real checksum via `restore!`.
98
98
  #
99
99
  # @param random [Random] source of randomness
100
100
  # @return [String] a country code + "00" + numeric BBAN
@@ -126,9 +126,9 @@ module SecID
126
126
 
127
127
  # @return [String]
128
128
  def to_s
129
- return full_id unless check_digit
129
+ return full_id unless checksum
130
130
 
131
- "#{country_code}#{check_digit.to_s.rjust(2, '0')}#{bban}"
131
+ "#{country_code}#{checksum.to_s.rjust(2, '0')}#{bban}"
132
132
  end
133
133
 
134
134
  # @return [String, nil]
@@ -140,7 +140,7 @@ module SecID
140
140
 
141
141
  # @return [Hash]
142
142
  def components
143
- hash = { country_code:, bban:, check_digit: }
143
+ hash = { country_code:, bban:, checksum: }
144
144
  hash[:bank_code] = bank_code if bank_code
145
145
  hash[:branch_code] = branch_code if branch_code
146
146
  hash[:account_number] = account_number if account_number
@@ -149,7 +149,7 @@ module SecID
149
149
  end
150
150
 
151
151
  # @return [Integer]
152
- def check_digit_width
152
+ def checksum_width
153
153
  2
154
154
  end
155
155
 
@@ -177,14 +177,14 @@ module SecID
177
177
 
178
178
  # @param rest [String] the IBAN string after country code
179
179
  # @return [void]
180
- def extract_check_digit_and_bban(rest)
180
+ def extract_checksum_and_bban(rest)
181
181
  expected = expected_bban_length_for_country
182
182
 
183
- if check_digits?(rest, expected)
184
- @check_digit = rest[0, 2].to_i
183
+ if checksum?(rest, expected)
184
+ @checksum = rest[0, 2].to_i
185
185
  @bban = rest[2..]
186
186
  else
187
- @check_digit = nil
187
+ @checksum = nil
188
188
  @bban = rest
189
189
  end
190
190
  end
@@ -197,11 +197,11 @@ module SecID
197
197
  # @param rest [String] the IBAN string after country code
198
198
  # @param expected_bban_length [Integer, nil] the expected BBAN length for the country
199
199
  # @return [Boolean]
200
- def check_digits?(rest, expected_bban_length)
200
+ def checksum?(rest, expected_bban_length)
201
201
  return false unless rest[0, 2].match?(/\A\d{2}\z/)
202
202
  return true unless expected_bban_length
203
203
 
204
- # If we know expected BBAN length, check if rest matches with or without check digits
204
+ # If we know expected BBAN length, check if rest matches with or without checksum
205
205
  rest.length == expected_bban_length + 2 || rest.length != expected_bban_length
206
206
  end
207
207
 
data/lib/sec_id/isin.rb CHANGED
@@ -4,14 +4,14 @@ 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
@@ -30,7 +30,7 @@ module SecID
30
30
  (?<identifier>
31
31
  (?<country_code>[A-Z]{2})
32
32
  (?<nsin>[A-Z0-9]{9}))
33
- (?<check_digit>\d)?
33
+ (?<checksum>\d)?
34
34
  \z/x
35
35
 
36
36
  # Country codes that use CUSIP Global Services (CGS) for NSIN assignment.
@@ -71,19 +71,19 @@ module SecID
71
71
  @identifier = isin_parts[:identifier]
72
72
  @country_code = isin_parts[:country_code]
73
73
  @nsin = isin_parts[:nsin]
74
- @check_digit = isin_parts[:check_digit]&.to_i
74
+ @checksum = isin_parts[:checksum]&.to_i
75
75
  end
76
76
 
77
77
  # @return [String, nil]
78
78
  def to_pretty_s
79
79
  return nil unless valid?
80
80
 
81
- "#{country_code} #{nsin} #{check_digit}"
81
+ "#{country_code} #{nsin} #{checksum}"
82
82
  end
83
83
 
84
- # @return [Integer] the calculated check digit (0-9)
84
+ # @return [Integer] the calculated checksum (0-9)
85
85
  # @raise [InvalidFormatError] if the ISIN format is invalid
86
- def calculate_check_digit
86
+ def calculate_checksum
87
87
  validate_format_for_calculation!
88
88
  mod10(luhn_sum_standard(reversed_digits_multi(identifier)))
89
89
  end
@@ -91,7 +91,7 @@ module SecID
91
91
  # Generates a random ISIN body: 2-letter country code + 9-character NSIN.
92
92
  #
93
93
  # @param random [Random] source of randomness
94
- # @return [String] an 11-character ISIN body without check digit
94
+ # @return [String] an 11-character ISIN body without checksum
95
95
  def self.generate_body(random)
96
96
  "#{random_string(ALPHA, 2, random: random)}#{random_string(ALPHANUMERIC, 9, random: random)}"
97
97
  end
@@ -177,6 +177,6 @@ module SecID
177
177
  private
178
178
 
179
179
  # @return [Hash]
180
- def components = { country_code:, nsin:, check_digit: }
180
+ def components = { country_code:, nsin:, checksum: }
181
181
  end
182
182
  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,8 +12,8 @@ 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
19
 
@@ -32,7 +32,7 @@ module SecID
32
32
  (?<lou_id>[0-9A-Z]{4})
33
33
  (?<reserved>[0-9A-Z]{2})
34
34
  (?<entity_id>[0-9A-Z]{12}))
35
- (?<check_digit>\d{2})?
35
+ (?<checksum>\d{2})?
36
36
  \z/x
37
37
 
38
38
  # @return [String, nil] the 4-character Local Operating Unit (LOU) identifier
@@ -51,7 +51,7 @@ module SecID
51
51
  @lou_id = lei_parts[:lou_id]
52
52
  @reserved = lei_parts[:reserved]
53
53
  @entity_id = lei_parts[:entity_id]
54
- @check_digit = lei_parts[:check_digit]&.to_i
54
+ @checksum = lei_parts[:checksum]&.to_i
55
55
  end
56
56
 
57
57
  # @return [String, nil]
@@ -61,9 +61,9 @@ module SecID
61
61
  to_s.scan(/.{1,4}/).join(' ')
62
62
  end
63
63
 
64
- # @return [Integer] the calculated 2-digit check digit (1-98)
64
+ # @return [Integer] the calculated 2-digit checksum (1-98)
65
65
  # @raise [InvalidFormatError] if the LEI format is invalid
66
- def calculate_check_digit
66
+ def calculate_checksum
67
67
  validate_format_for_calculation!
68
68
  mod97("#{numeric_identifier}00")
69
69
  end
@@ -71,7 +71,7 @@ module SecID
71
71
  # Generates a random LEI body: 18 alphanumeric characters.
72
72
  #
73
73
  # @param random [Random] source of randomness
74
- # @return [String] an 18-character LEI body without check digits
74
+ # @return [String] an 18-character LEI body without checksum
75
75
  def self.generate_body(random)
76
76
  random_string(ALPHANUMERIC, 18, random: random)
77
77
  end
@@ -80,10 +80,10 @@ module SecID
80
80
  private
81
81
 
82
82
  # @return [Hash]
83
- def components = { lou_id:, reserved:, entity_id:, check_digit: }
83
+ def components = { lou_id:, reserved:, entity_id:, checksum: }
84
84
 
85
85
  # @return [Integer]
86
- def check_digit_width
86
+ def checksum_width
87
87
  2
88
88
  end
89
89
 
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,8 +12,8 @@ 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
19
 
@@ -30,10 +30,10 @@ module SecID
30
30
  # Excludes vowels (A, E, I, O, U) from valid characters.
31
31
  ID_REGEX = /\A
32
32
  (?<identifier>[0-9BCDFGHJKLMNPQRSTVWXYZ]{6})
33
- (?<check_digit>\d)?
33
+ (?<checksum>\d)?
34
34
  \z/x
35
35
 
36
- # Weights applied to each character position in the check digit calculation.
36
+ # Weights applied to each character position in the checksum calculation.
37
37
  CHARACTER_WEIGHTS = [1, 3, 1, 7, 3, 9].freeze
38
38
 
39
39
  # Characters valid in a SEDOL body (alphanumeric excluding vowels).
@@ -43,14 +43,14 @@ module SecID
43
43
  def initialize(sedol)
44
44
  sedol_parts = parse sedol
45
45
  @identifier = sedol_parts[:identifier]
46
- @check_digit = sedol_parts[:check_digit]&.to_i
46
+ @checksum = sedol_parts[:checksum]&.to_i
47
47
  end
48
48
 
49
49
  # Valid country codes for SEDOL to ISIN conversion.
50
50
  ISIN_COUNTRY_CODES = Set.new(%w[GB IE GG IM JE FK]).freeze
51
51
 
52
52
  # @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
53
+ # @return [ISIN] a new ISIN instance with calculated checksum
54
54
  # @raise [InvalidFormatError] if the country code is not valid for SEDOL
55
55
  def to_isin(country_code = 'GB')
56
56
  unless ISIN_COUNTRY_CODES.include?(country_code)
@@ -60,9 +60,9 @@ module SecID
60
60
  ISIN.new("#{country_code}00#{restore}").restore!
61
61
  end
62
62
 
63
- # @return [Integer] the calculated check digit (0-9)
63
+ # @return [Integer] the calculated checksum (0-9)
64
64
  # @raise [InvalidFormatError] if the SEDOL format is invalid
65
- def calculate_check_digit
65
+ def calculate_checksum
66
66
  validate_format_for_calculation!
67
67
  mod10(weighted_sum)
68
68
  end
@@ -70,7 +70,7 @@ module SecID
70
70
  # Generates a random SEDOL body: 6 characters excluding vowels.
71
71
  #
72
72
  # @param random [Random] source of randomness
73
- # @return [String] a 6-character SEDOL body without check digit
73
+ # @return [String] a 6-character SEDOL body without checksum
74
74
  def self.generate_body(random)
75
75
  random_string(GENERATE_CHARSET, 6, random: random)
76
76
  end
@@ -79,7 +79,7 @@ module SecID
79
79
  private
80
80
 
81
81
  # @return [Hash]
82
- def components = { check_digit: }
82
+ def components = { checksum: }
83
83
 
84
84
  # NOTE: Not idiomatic Ruby, but optimized for performance.
85
85
  #
data/lib/sec_id/upi.rb ADDED
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecID
4
+ # Unique Product Identifier (UPI, ISO 4914) - a 12-character code that identifies
5
+ # OTC derivative products for regulatory reporting, issued by the ANNA Derivatives
6
+ # Service Bureau.
7
+ #
8
+ # Format: fixed 'QZ' prefix + 9-character body + 1 check character, drawn from a
9
+ # 30-symbol alphabet (digits 0-9 plus consonants; vowels and 'Y' never appear).
10
+ # Validated fully offline via ISO 7064 hybrid MOD 31,30 over the 11 preceding characters.
11
+ #
12
+ # @see https://www.anna-dsb.com
13
+ #
14
+ # @example Validate a UPI
15
+ # SecID::UPI.valid?('QZRBG6ZTKS42') #=> true
16
+ #
17
+ # @example Restore check character
18
+ # SecID::UPI.restore('QZRBG6ZTKS4') #=> 'QZRBG6ZTKS42'
19
+ class UPI < Base
20
+ include Checkable
21
+
22
+ # Human-readable name of the standard.
23
+ FULL_NAME = 'Unique Product Identifier'
24
+ # Valid length of a normalized identifier.
25
+ ID_LENGTH = 12
26
+ # A representative valid identifier.
27
+ EXAMPLE = 'QZRBG6ZTKS42'
28
+ # Pattern matching the identifier's permitted character set (digits + consonants, no vowels/Y).
29
+ VALID_CHARS_REGEX = /\A[0-9B-DF-HJ-NP-TV-XZ]+\z/
30
+
31
+ # Regular expression for parsing UPI components: fixed 'QZ' prefix, 9-character body,
32
+ # optional check character.
33
+ ID_REGEX = /\A
34
+ (?<identifier>QZ[0-9B-DF-HJ-NP-TV-XZ]{9})
35
+ (?<checksum>[0-9B-DF-HJ-NP-TV-XZ])?
36
+ \z/x
37
+
38
+ # The 30-symbol UPI alphabet, ordered by check-character value (0-29).
39
+ ALPHABET = '0123456789BCDFGHJKLMNPQRSTVWXZ'.chars.freeze
40
+
41
+ # Maps each alphabet character to its check-character value (0-29).
42
+ ALPHABET_VALUE = ALPHABET.each_with_index.to_h.freeze
43
+
44
+ # Characters valid in a UPI body (same alphabet as VALID_CHARS_REGEX).
45
+ GENERATE_CHARSET = ALPHANUMERIC.grep(VALID_CHARS_REGEX).freeze
46
+
47
+ # @param upi [String] the UPI string to parse
48
+ def initialize(upi)
49
+ upi_parts = parse upi
50
+ @identifier = upi_parts[:identifier]
51
+ @checksum = upi_parts[:checksum]
52
+ end
53
+
54
+ # @return [String] the calculated check character
55
+ # @raise [InvalidFormatError] if the UPI format is invalid
56
+ def calculate_checksum
57
+ validate_format_for_calculation!
58
+ mod31_30_check_char(identifier, ALPHABET, ALPHABET_VALUE)
59
+ end
60
+
61
+ # Generates a random UPI body: the fixed 'QZ' prefix plus 9 characters drawn
62
+ # from the UPI alphabet.
63
+ #
64
+ # @param random [Random] source of randomness
65
+ # @return [String] an 11-character UPI body without check character
66
+ def self.generate_body(random)
67
+ "QZ#{random_string(GENERATE_CHARSET, 9, random: random)}"
68
+ end
69
+ private_class_method :generate_body
70
+
71
+ private
72
+
73
+ # @return [Hash]
74
+ def components = { checksum: }
75
+ end
76
+ end
@@ -6,7 +6,7 @@ module SecID
6
6
  #
7
7
  # Format: 5-9 numeric digits
8
8
  #
9
- # @note Valoren identifiers have no check digit and validation is based solely on format.
9
+ # @note Valoren identifiers have no checksum and validation is based solely on format.
10
10
  #
11
11
  # @see https://en.wikipedia.org/wiki/Valoren_number
12
12
  #
@@ -52,7 +52,7 @@ module SecID
52
52
  end
53
53
 
54
54
  # @param country_code [String] the ISO 3166-1 alpha-2 country code (default: 'CH')
55
- # @return [ISIN] a new ISIN instance with calculated check digit
55
+ # @return [ISIN] a new ISIN instance with calculated checksum
56
56
  # @raise [InvalidFormatError] if the country code is not CH or LI
57
57
  def to_isin(country_code = 'CH')
58
58
  unless ISIN_COUNTRY_CODES.include?(country_code)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module SecID
4
4
  # Current gem version.
5
- VERSION = '6.1.0'
5
+ VERSION = '7.0.0'
6
6
  end
data/lib/sec_id/wkn.rb CHANGED
@@ -38,7 +38,7 @@ module SecID
38
38
  end
39
39
 
40
40
  # @param country_code [String] the ISO 3166-1 alpha-2 country code (default: 'DE')
41
- # @return [ISIN] a new ISIN instance with calculated check digit
41
+ # @return [ISIN] a new ISIN instance with calculated checksum
42
42
  # @raise [InvalidFormatError] if the country code is not DE
43
43
  # @raise [InvalidFormatError] if the WKN format is invalid
44
44
  def to_isin(country_code = 'DE')
data/lib/sec_id.rb CHANGED
@@ -4,7 +4,7 @@ require 'sec_id/version'
4
4
 
5
5
  # Toolkit for securities identifiers — validate, normalize, parse, detect, convert,
6
6
  # generate, and classify ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN, CIK, OCC, WKN,
7
- # Valoren, CFI, FISN, BIC, and DTI.
7
+ # Valoren, CFI, FISN, BIC, DTI, and UPI.
8
8
  #
9
9
  # @see https://github.com/svyatov/sec_id
10
10
  module SecID
@@ -14,8 +14,12 @@ module SecID
14
14
  # Raised for invalid format, length, or characters.
15
15
  class InvalidFormatError < Error; end
16
16
 
17
- # Raised when the check digit does not match the calculated value.
18
- class InvalidCheckDigitError < Error; end
17
+ # Raised when the checksum does not match the calculated value.
18
+ class InvalidChecksumError < Error; end
19
+
20
+ # @deprecated Use {InvalidChecksumError}. Kept as a v7 bridge (same class object,
21
+ # so `rescue` under either name keeps working); removed in v8.
22
+ InvalidCheckDigitError = InvalidChecksumError
19
23
 
20
24
  # Raised for type-specific structural errors (invalid prefix, category, group, BBAN, or date).
21
25
  class InvalidStructureError < Error; end
@@ -195,6 +199,7 @@ module SecID
195
199
  end
196
200
  end
197
201
 
202
+ require 'sec_id/deprecation'
198
203
  require 'sec_id/deep_freeze'
199
204
  require 'sec_id/errors'
200
205
  require 'sec_id/concerns/normalizable'
@@ -219,6 +224,7 @@ require 'sec_id/cfi'
219
224
  require 'sec_id/fisn'
220
225
  require 'sec_id/bic'
221
226
  require 'sec_id/dti'
227
+ require 'sec_id/upi'
222
228
 
223
229
  # Auto-activate the ActiveModel validator inside Rails; a no-op everywhere else (keeps the
224
230
  # default require zero-dependency). See SecID::Railtie and lib/sec_id/active_model.rb.
data/sec_id.gemspec CHANGED
@@ -13,11 +13,11 @@ Gem::Specification.new do |spec|
13
13
  spec.summary = 'A Ruby toolkit for securities identifiers — validate, parse, normalize, detect, convert, ' \
14
14
  'generate, and classify.'
15
15
  spec.description = 'Validate, normalize, parse, convert, generate, and classify securities identifiers. ' \
16
- 'Auto-detect identifier type from any string. Calculate and restore check digits. Generate ' \
16
+ 'Auto-detect identifier type from any string. Calculate and restore checksums. Generate ' \
17
17
  'format-valid identifiers as test fixtures. Decode CFI codes to full ISO 10962:2021 ' \
18
18
  'classifications. Includes an opt-in ActiveModel/Rails validator that adds no runtime ' \
19
19
  'dependency. Supports ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN, CIK, OCC, WKN, Valoren, ' \
20
- 'CFI, FISN, BIC, and DTI.'
20
+ 'CFI, FISN, BIC, DTI, and UPI.'
21
21
  spec.homepage = 'https://github.com/svyatov/sec_id'
22
22
  spec.license = 'MIT'
23
23
 
data/sig/sec_id/base.rbs CHANGED
@@ -42,7 +42,11 @@ module SecID
42
42
  def self.length_values: () -> (::Array[Integer] | ::Range[Integer])
43
43
  def self.length_specificity: () -> (Integer | Float | nil)
44
44
  def self.example: () -> String
45
+ def self.has_checksum?: () -> bool
46
+
47
+ # @deprecated v7 bridge alias for `has_checksum?`; removed in v8.
45
48
  def self.has_check_digit?: () -> bool
49
+
46
50
  def self.inherited: (singleton(Base) subclass) -> void
47
51
 
48
52
  def initialize: (SecID::input _sec_id_number) -> void
@@ -54,24 +58,25 @@ module SecID
54
58
  def as_json: (*untyped) -> Hash[Symbol, untyped]
55
59
  def deconstruct_keys: (::Array[Symbol]? _keys) -> Hash[Symbol, untyped]
56
60
 
57
- # Check-digit surface. Only the types that `include Checkable` implement these
61
+ # Checksum surface. Only the types that `include Checkable` implement these
58
62
  # (they raise/NoMethodError otherwise); declared on Base so `Generatable#generate`
59
- # and `Checkable::ClassMethods` type-check without narrowing on `has_check_digit?`.
63
+ # and `Checkable::ClassMethods` type-check without narrowing on `has_checksum?`.
60
64
  def restore: () -> String
61
65
  def restore!: () -> self
62
- def calculate_check_digit: () -> (Integer | String)
66
+ def calculate_checksum: () -> (Integer | String)
63
67
 
64
68
  def comparison_id: () -> String
65
69
 
66
70
  private
67
71
 
72
+ def components_with_deprecation_bridge: () -> Hash[Symbol, untyped]
68
73
  def components: () -> Hash[Symbol, untyped]
69
74
  def parse: (SecID::input sec_id_number) -> (::MatchData | ::Hash[Symbol, untyped])
70
75
 
71
76
  @full_id: String
72
77
  @identifier: untyped
73
78
  self.@short_name: String
74
- self.@has_check_digit: bool
79
+ self.@has_checksum: bool
75
80
  self.@type_key: Symbol
76
81
  self.@detection_priority: Array[Integer | Float | nil]
77
82
  end
data/sig/sec_id/cei.rbs CHANGED
@@ -15,7 +15,11 @@ module SecID
15
15
  attr_reader entity_id: String?
16
16
 
17
17
  def initialize: (SecID::input cei) -> void
18
+ def calculate_checksum: () -> Integer
19
+ def self.checksum: (SecID::input id) -> Integer
20
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
18
21
  def calculate_check_digit: () -> Integer
22
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
19
23
  def self.check_digit: (SecID::input id) -> Integer
20
24
  private def self.generate_body: (Random random) -> String
21
25
 
@@ -1,26 +1,35 @@
1
1
  module SecID
2
- # Provides check-digit validation and calculation for identifiers with a check digit.
2
+ # Provides checksum validation and calculation for identifiers with a checksum.
3
3
  module Checkable : SecID::Base
4
4
  CHAR_TO_DIGITS: Hash[String, Integer | Array[Integer]]
5
5
  CHAR_TO_DIGIT: Hash[String, Integer]
6
6
 
7
7
  def self.included: (Module base) -> void
8
8
 
9
- attr_reader check_digit: (Integer | String)?
9
+ attr_reader checksum: (Integer | String)?
10
10
 
11
11
  # Class methods added when Checkable is included.
12
12
  module ClassMethods : SecID::_IdentifierClass
13
- def restore: (SecID::input id_without_check_digit) -> String
14
- def restore!: (SecID::input id_without_check_digit) -> Base
13
+ def restore: (SecID::input id_without_checksum) -> String
14
+ def restore!: (SecID::input id_without_checksum) -> Base
15
+ def checksum: (SecID::input id) -> (Integer | String)
16
+
17
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
15
18
  def check_digit: (SecID::input id) -> (Integer | String)
16
19
  end
17
20
 
18
21
  def valid?: () -> bool
19
22
  def restore: () -> String
20
23
  def restore!: () -> self
21
- def calculate_check_digit: () -> (Integer | String)
24
+ def calculate_checksum: () -> (Integer | String)
22
25
  def to_s: () -> String
23
26
 
27
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
28
+ def check_digit: () -> (Integer | String)?
29
+
30
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
31
+ def calculate_check_digit: () -> (Integer | String)
32
+
24
33
  private
25
34
 
26
35
  def luhn_sum_double_add_double: (Array[Integer] digits) -> Integer
@@ -29,13 +38,14 @@ module SecID
29
38
  def reversed_digits_single: (untyped id) -> Array[Integer]
30
39
  def reversed_digits_multi: (untyped id) -> Array[Integer]
31
40
  def error_codes: () -> Array[Symbol]
32
- def check_digit_width: () -> Integer
41
+ def checksum_width: () -> Integer
33
42
  def validation_message: (Symbol code) -> String?
34
43
  def validate_format_for_calculation!: () -> void
35
44
  def mod10: (Integer sum) -> Integer
36
45
  def div10mod10: (Integer number) -> Integer
37
46
  def mod97: (String numeric_string) -> Integer
47
+ def mod31_30_check_char: (untyped base, Array[String] alphabet, Hash[String, Integer] alphabet_value) -> String
38
48
 
39
- @check_digit: (Integer | String)?
49
+ @checksum: (Integer | String)?
40
50
  end
41
51
  end
@@ -10,7 +10,7 @@ module SecID
10
10
  # The class-object surface Generatable's methods call once extended.
11
11
  interface _GeneratableClass
12
12
  def new: (SecID::input) -> Base
13
- def has_check_digit?: () -> bool
13
+ def has_checksum?: () -> bool
14
14
  end
15
15
 
16
16
  # Class methods added when Generatable is included.
@@ -33,7 +33,7 @@ module SecID
33
33
  def detect_errors: () -> Array[Symbol]
34
34
  def valid_length?: () -> bool
35
35
  def valid_characters?: () -> bool
36
- def check_digit_width: () -> Integer
36
+ def checksum_width: () -> Integer
37
37
  def validation_message: (Symbol code) -> String?
38
38
  def build_error: (Symbol code, String? message) -> Errors::detail
39
39
 
data/sig/sec_id/cusip.rbs CHANGED
@@ -16,7 +16,11 @@ module SecID
16
16
 
17
17
  def initialize: (SecID::input cusip) -> void
18
18
  def to_pretty_s: () -> String?
19
+ def calculate_checksum: () -> Integer
20
+ def self.checksum: (SecID::input id) -> Integer
21
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
19
22
  def calculate_check_digit: () -> Integer
23
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
20
24
  def self.check_digit: (SecID::input id) -> Integer
21
25
  private def self.generate_body: (Random random) -> String
22
26
  def to_isin: (String country_code) -> ISIN