sec_id 6.0.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/MIGRATION.md +147 -0
  4. data/README.md +116 -44
  5. data/lib/sec_id/active_model.rb +1 -1
  6. data/lib/sec_id/base.rb +63 -14
  7. data/lib/sec_id/cei.rb +7 -7
  8. data/lib/sec_id/cik.rb +1 -1
  9. data/lib/sec_id/concerns/checkable.rb +90 -46
  10. data/lib/sec_id/concerns/generatable.rb +5 -5
  11. data/lib/sec_id/concerns/normalizable.rb +4 -4
  12. data/lib/sec_id/concerns/validatable.rb +7 -7
  13. data/lib/sec_id/cusip.rb +8 -8
  14. data/lib/sec_id/deprecation.rb +26 -0
  15. data/lib/sec_id/detector.rb +3 -26
  16. data/lib/sec_id/dti.rb +8 -14
  17. data/lib/sec_id/figi.rb +9 -9
  18. data/lib/sec_id/iban.rb +20 -20
  19. data/lib/sec_id/isin.rb +9 -9
  20. data/lib/sec_id/lei.rb +10 -10
  21. data/lib/sec_id/occ.rb +1 -1
  22. data/lib/sec_id/scanner.rb +17 -22
  23. data/lib/sec_id/sedol.rb +11 -11
  24. data/lib/sec_id/upi.rb +76 -0
  25. data/lib/sec_id/valoren.rb +2 -2
  26. data/lib/sec_id/version.rb +1 -1
  27. data/lib/sec_id/wkn.rb +1 -1
  28. data/lib/sec_id.rb +11 -6
  29. data/sec_id.gemspec +2 -2
  30. data/sig/sec_id/base.rbs +15 -4
  31. data/sig/sec_id/cei.rbs +4 -0
  32. data/sig/sec_id/concerns/checkable.rbs +17 -7
  33. data/sig/sec_id/concerns/generatable.rbs +1 -1
  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/detector.rbs +0 -6
  38. data/sig/sec_id/dti.rbs +6 -1
  39. data/sig/sec_id/figi.rbs +4 -0
  40. data/sig/sec_id/iban.rbs +7 -3
  41. data/sig/sec_id/isin.rbs +4 -0
  42. data/sig/sec_id/lei.rbs +5 -1
  43. data/sig/sec_id/scanner.rbs +2 -4
  44. data/sig/sec_id/sedol.rbs +4 -0
  45. data/sig/sec_id/upi.rbs +29 -0
  46. data/sig/sec_id.rbs +5 -2
  47. metadata +7 -3
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.0.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
@@ -85,7 +89,7 @@ module SecID
85
89
  # @return [Hash] hash with :input and :candidates keys
86
90
  def explain(str, types: nil)
87
91
  input = str.to_s.strip
88
- target_keys = types || identifier_list.map { |k| k.short_name.downcase.to_sym }
92
+ target_keys = types || identifier_list.map(&:type_key)
89
93
  candidates = target_keys.map do |key|
90
94
  instance = self[key].new(input)
91
95
  { type: key, valid: instance.valid?, errors: instance.errors.details }
@@ -143,8 +147,7 @@ module SecID
143
147
 
144
148
  # @return [void]
145
149
  def register_identifier(klass)
146
- key = klass.name.split('::').last.downcase.to_sym
147
- identifier_map[key] = klass
150
+ identifier_map[klass.type_key] = klass
148
151
  identifier_list << klass
149
152
  @detector = nil
150
153
  @scanner = nil
@@ -196,6 +199,7 @@ module SecID
196
199
  end
197
200
  end
198
201
 
202
+ require 'sec_id/deprecation'
199
203
  require 'sec_id/deep_freeze'
200
204
  require 'sec_id/errors'
201
205
  require 'sec_id/concerns/normalizable'
@@ -220,6 +224,7 @@ require 'sec_id/cfi'
220
224
  require 'sec_id/fisn'
221
225
  require 'sec_id/bic'
222
226
  require 'sec_id/dti'
227
+ require 'sec_id/upi'
223
228
 
224
229
  # Auto-activate the ActiveModel validator inside Rails; a no-op everywhere else (keeps the
225
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
@@ -33,13 +33,20 @@ module SecID
33
33
  # RBS forbids narrowing an inherited ivar's type.
34
34
  def identifier: () -> String?
35
35
 
36
+ def self.type_key: () -> Symbol
37
+ # `@api private` in YARD, but public here: Detector and Scanner call it from outside.
38
+ def self.detection_priority: () -> Array[Integer | Float | nil]
36
39
  def self.short_name: () -> String
37
40
  def self.full_name: () -> String
38
41
  def self.id_length: () -> (Integer | ::Range[Integer] | ::Array[Integer])
39
42
  def self.length_values: () -> (::Array[Integer] | ::Range[Integer])
40
43
  def self.length_specificity: () -> (Integer | Float | nil)
41
44
  def self.example: () -> String
45
+ def self.has_checksum?: () -> bool
46
+
47
+ # @deprecated v7 bridge alias for `has_checksum?`; removed in v8.
42
48
  def self.has_check_digit?: () -> bool
49
+
43
50
  def self.inherited: (singleton(Base) subclass) -> void
44
51
 
45
52
  def initialize: (SecID::input _sec_id_number) -> void
@@ -49,24 +56,28 @@ module SecID
49
56
  def hash: () -> Integer
50
57
  def to_h: () -> Hash[Symbol, untyped]
51
58
  def as_json: (*untyped) -> Hash[Symbol, untyped]
59
+ def deconstruct_keys: (::Array[Symbol]? _keys) -> Hash[Symbol, untyped]
52
60
 
53
- # Check-digit surface. Only the types that `include Checkable` implement these
61
+ # Checksum surface. Only the types that `include Checkable` implement these
54
62
  # (they raise/NoMethodError otherwise); declared on Base so `Generatable#generate`
55
- # and `Checkable::ClassMethods` type-check without narrowing on `has_check_digit?`.
63
+ # and `Checkable::ClassMethods` type-check without narrowing on `has_checksum?`.
56
64
  def restore: () -> String
57
65
  def restore!: () -> self
58
- def calculate_check_digit: () -> (Integer | String)
66
+ def calculate_checksum: () -> (Integer | String)
59
67
 
60
68
  def comparison_id: () -> String
61
69
 
62
70
  private
63
71
 
72
+ def components_with_deprecation_bridge: () -> Hash[Symbol, untyped]
64
73
  def components: () -> Hash[Symbol, untyped]
65
74
  def parse: (SecID::input sec_id_number) -> (::MatchData | ::Hash[Symbol, untyped])
66
75
 
67
76
  @full_id: String
68
77
  @identifier: untyped
69
78
  self.@short_name: String
70
- self.@has_check_digit: bool
79
+ self.@has_checksum: bool
80
+ self.@type_key: Symbol
81
+ self.@detection_priority: Array[Integer | Float | nil]
71
82
  end
72
83
  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
@@ -0,0 +1,6 @@
1
+ module SecID
2
+ # Emits standardized, silenceable deprecation warnings for the v7 → v8 rename bridge.
3
+ module Deprecation
4
+ def self.warn: (old: String, new: String, ?removed_in: String) -> void
5
+ end
6
+ end
@@ -3,8 +3,6 @@ module SecID
3
3
  #
4
4
  # @api private
5
5
  class Detector
6
- type priority = Array[Integer | Float | nil]
7
-
8
6
  def initialize: (Array[singleton(Base)] identifier_list) -> void
9
7
  def call: (SecID::input str) -> Array[Symbol]
10
8
  def matches?: (SecID::input str) -> bool
@@ -17,8 +15,6 @@ module SecID
17
15
  def precompute: () -> void
18
16
  def build_discriminator_sets: () -> void
19
17
  def build_length_table: () -> void
20
- def build_priority_table: () -> void
21
- def build_key_table: () -> void
22
18
  def stage1_special_chars: (String upcased) -> Array[singleton(Base)]?
23
19
  def stage2_length: (Integer length) -> Array[singleton(Base)]
24
20
  def stage3_charset: (String upcased, Array[singleton(Base)] candidates) -> Array[singleton(Base)]
@@ -29,7 +25,5 @@ module SecID
29
25
  @space_only_types: Array[singleton(Base)]
30
26
  @special_types: Array[singleton(Base)]
31
27
  @candidates_by_length: Hash[Integer, Array[singleton(Base)]]
32
- @priority_for: Hash[singleton(Base), priority]
33
- @key_for: Hash[singleton(Base), Symbol]
34
28
  end
35
29
  end
data/sig/sec_id/dti.rbs CHANGED
@@ -15,13 +15,18 @@ module SecID
15
15
  GRANDFATHERED_CODES: Hash[String, String]
16
16
 
17
17
  def initialize: (SecID::input dti) -> void
18
+ def calculate_checksum: () -> String
19
+ def self.checksum: (SecID::input id) -> String
20
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
18
21
  def calculate_check_digit: () -> String
22
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
23
+ def self.check_digit: (SecID::input id) -> String
19
24
  private def self.generate_body: (Random random) -> String
20
25
 
21
26
  private
22
27
 
23
28
  def components: () -> Hash[Symbol, untyped]
24
- def grandfathered_check_digit: (untyped base) -> String?
29
+ def grandfathered_checksum: (untyped base) -> String?
25
30
  def iso7064_mod31_30_check_char: (untyped base) -> String
26
31
  end
27
32
  end
data/sig/sec_id/figi.rbs CHANGED
@@ -17,7 +17,11 @@ module SecID
17
17
 
18
18
  def initialize: (SecID::input figi) -> void
19
19
  def to_pretty_s: () -> String?
20
+ def calculate_checksum: () -> Integer
21
+ def self.checksum: (SecID::input id) -> Integer
22
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
20
23
  def calculate_check_digit: () -> Integer
24
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
21
25
  def self.check_digit: (SecID::input id) -> Integer
22
26
  private def self.generate_body: (Random random) -> String
23
27
 
data/sig/sec_id/iban.rbs CHANGED
@@ -26,7 +26,11 @@ module SecID
26
26
 
27
27
  def initialize: (SecID::input iban) -> void
28
28
  def restore: () -> String
29
+ def calculate_checksum: () -> Integer
30
+ def self.checksum: (SecID::input id) -> Integer
31
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
29
32
  def calculate_check_digit: () -> Integer
33
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
30
34
  def self.check_digit: (SecID::input id) -> Integer
31
35
  private def self.generate_body: (Random random) -> String
32
36
  def valid_bban_format?: () -> bool
@@ -38,13 +42,13 @@ module SecID
38
42
  private
39
43
 
40
44
  def components: () -> Hash[Symbol, untyped]
41
- def check_digit_width: () -> Integer
45
+ def checksum_width: () -> Integer
42
46
  def valid_format?: () -> bool
43
47
  def detect_errors: () -> Array[Symbol]
44
48
  def validation_message: (Symbol code) -> String?
45
- def extract_check_digit_and_bban: (String rest) -> void
49
+ def extract_checksum_and_bban: (String rest) -> void
46
50
  def expected_bban_length_for_country: () -> Integer?
47
- def check_digits?: (untyped rest, Integer? expected_bban_length) -> bool
51
+ def checksum?: (untyped rest, Integer? expected_bban_length) -> bool
48
52
  def extract_bban_components: () -> void
49
53
  def valid_bban_length_only?: () -> bool
50
54
  def numeric_string_for_check: () -> String
data/sig/sec_id/isin.rbs CHANGED
@@ -19,7 +19,11 @@ module SecID
19
19
 
20
20
  def initialize: (SecID::input isin) -> void
21
21
  def to_pretty_s: () -> String?
22
+ def calculate_checksum: () -> Integer
23
+ def self.checksum: (SecID::input id) -> Integer
24
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
22
25
  def calculate_check_digit: () -> Integer
26
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
23
27
  def self.check_digit: (SecID::input id) -> Integer
24
28
  private def self.generate_body: (Random random) -> String
25
29
  def to_cusip: () -> CUSIP
data/sig/sec_id/lei.rbs CHANGED
@@ -20,14 +20,18 @@ module SecID
20
20
 
21
21
  def initialize: (SecID::input lei) -> void
22
22
  def to_pretty_s: () -> String?
23
+ def calculate_checksum: () -> Integer
24
+ def self.checksum: (SecID::input id) -> Integer
25
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
23
26
  def calculate_check_digit: () -> Integer
27
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
24
28
  def self.check_digit: (SecID::input id) -> Integer
25
29
  private def self.generate_body: (Random random) -> String
26
30
 
27
31
  private
28
32
 
29
33
  def components: () -> Hash[Symbol, untyped]
30
- def check_digit_width: () -> Integer
34
+ def checksum_width: () -> Integer
31
35
  def numeric_identifier: () -> String
32
36
  end
33
37
  end
@@ -28,8 +28,8 @@ module SecID
28
28
  private
29
29
 
30
30
  def precompute: () -> void
31
- def build_key_table: () -> void
32
- def build_priority_table: () -> void
31
+ def build_candidate_partitions: () -> void
32
+ def build_length_table: () -> void
33
33
  def scan_text: (String input, Array[singleton(Base)] target_classes) { (Match) -> void } -> void
34
34
  def identify_candidate: (untyped match_data, Array[singleton(Base)] target_classes) -> Match?
35
35
  def try_classes: (String raw, String cleaned, Integer start_pos, Array[singleton(Base)] classes) -> Match?
@@ -40,7 +40,5 @@ module SecID
40
40
  @occ_classes: Array[singleton(Base)]
41
41
  @simple_classes: Array[singleton(Base)]
42
42
  @candidates_by_length: Hash[Integer, Array[singleton(Base)]]
43
- @priority_for: Hash[singleton(Base), Array[Integer | Float | nil]]
44
- @key_for: Hash[singleton(Base), Symbol]
45
43
  end
46
44
  end
data/sig/sec_id/sedol.rbs CHANGED
@@ -19,7 +19,11 @@ module SecID
19
19
 
20
20
  def initialize: (SecID::input sedol) -> void
21
21
  def to_isin: (?String country_code) -> ISIN
22
+ def calculate_checksum: () -> Integer
23
+ def self.checksum: (SecID::input id) -> Integer
24
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
22
25
  def calculate_check_digit: () -> Integer
26
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
23
27
  def self.check_digit: (SecID::input id) -> Integer
24
28
  private def self.generate_body: (Random random) -> String
25
29
 
@@ -0,0 +1,29 @@
1
+ module SecID
2
+ # Unique Product Identifier (UPI, ISO 4914).
3
+ class UPI < Base
4
+ include Checkable
5
+ extend Checkable::ClassMethods
6
+
7
+ FULL_NAME: String
8
+ ID_LENGTH: Integer
9
+ EXAMPLE: String
10
+ VALID_CHARS_REGEX: ::Regexp
11
+ ID_REGEX: ::Regexp
12
+ ALPHABET: Array[String]
13
+ ALPHABET_VALUE: Hash[String, Integer]
14
+ GENERATE_CHARSET: Array[String]
15
+
16
+ def initialize: (SecID::input upi) -> void
17
+ def calculate_checksum: () -> String
18
+ def self.checksum: (SecID::input id) -> String
19
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
20
+ def calculate_check_digit: () -> String
21
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
22
+ def self.check_digit: (SecID::input id) -> String
23
+ private def self.generate_body: (Random random) -> String
24
+
25
+ private
26
+
27
+ def components: () -> Hash[Symbol, untyped]
28
+ end
29
+ end
data/sig/sec_id.rbs CHANGED
@@ -7,10 +7,13 @@ module SecID
7
7
  class InvalidFormatError < Error
8
8
  end
9
9
 
10
- # Raised when the check digit does not match the calculated value.
11
- class InvalidCheckDigitError < Error
10
+ # Raised when the checksum does not match the calculated value.
11
+ class InvalidChecksumError < Error
12
12
  end
13
13
 
14
+ # @deprecated v7 bridge constant alias for `InvalidChecksumError` (same class object); removed in v8.
15
+ InvalidCheckDigitError: singleton(InvalidChecksumError)
16
+
14
17
  # Raised for type-specific structural errors.
15
18
  class InvalidStructureError < Error
16
19
  end
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: 6.0.0
4
+ version: 7.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
@@ -11,10 +11,10 @@ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Validate, normalize, parse, convert, generate, and classify securities
13
13
  identifiers. Auto-detect identifier type from any string. Calculate and restore
14
- check digits. Generate format-valid identifiers as test fixtures. Decode CFI codes
14
+ checksums. Generate format-valid identifiers as test fixtures. Decode CFI codes
15
15
  to full ISO 10962:2021 classifications. Includes an opt-in ActiveModel/Rails validator
16
16
  that adds no runtime dependency. Supports ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN,
17
- CIK, OCC, WKN, Valoren, CFI, FISN, BIC, and DTI.
17
+ CIK, OCC, WKN, Valoren, CFI, FISN, BIC, DTI, and UPI.
18
18
  email:
19
19
  - leonid@svyatov.ru
20
20
  executables: []
@@ -44,6 +44,7 @@ files:
44
44
  - lib/sec_id/concerns/validatable.rb
45
45
  - lib/sec_id/cusip.rb
46
46
  - lib/sec_id/deep_freeze.rb
47
+ - lib/sec_id/deprecation.rb
47
48
  - lib/sec_id/detector.rb
48
49
  - lib/sec_id/dti.rb
49
50
  - lib/sec_id/errors.rb
@@ -57,6 +58,7 @@ files:
57
58
  - lib/sec_id/railtie.rb
58
59
  - lib/sec_id/scanner.rb
59
60
  - lib/sec_id/sedol.rb
61
+ - lib/sec_id/upi.rb
60
62
  - lib/sec_id/valoren.rb
61
63
  - lib/sec_id/version.rb
62
64
  - lib/sec_id/wkn.rb
@@ -79,6 +81,7 @@ files:
79
81
  - sig/sec_id/concerns/validatable.rbs
80
82
  - sig/sec_id/cusip.rbs
81
83
  - sig/sec_id/deep_freeze.rbs
84
+ - sig/sec_id/deprecation.rbs
82
85
  - sig/sec_id/detector.rbs
83
86
  - sig/sec_id/dti.rbs
84
87
  - sig/sec_id/errors.rbs
@@ -91,6 +94,7 @@ files:
91
94
  - sig/sec_id/occ.rbs
92
95
  - sig/sec_id/scanner.rbs
93
96
  - sig/sec_id/sedol.rbs
97
+ - sig/sec_id/upi.rbs
94
98
  - sig/sec_id/valoren.rbs
95
99
  - sig/sec_id/version.rbs
96
100
  - sig/sec_id/wkn.rbs