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
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SecID
4
+ # Immutable value object representing one repair candidate returned by `suggest`.
5
+ #
6
+ # Carries the corrected, re-validated identifier plus the what-changed diff (edit
7
+ # kind, position, from/to characters) and a confidence tier — the honest surface
8
+ # that keeps a candidate from reading as an authoritative correction.
9
+ #
10
+ # Coordinate system by edit kind:
11
+ # - `:substitution` — `position` is the 0-based index of the changed character;
12
+ # `from`/`to` are the single characters before/after.
13
+ # - `:transposition` — `position` is the left index of the swapped pair;
14
+ # `from`/`to` are the two-character adjacent substring before/after the swap.
15
+ # - `:checksum` — `position` is `nil`; `from`/`to` are the old/new check-character
16
+ # string. `confidence` is `nil` (it is the fallback hypothesis, ranked last).
17
+ #
18
+ # @!attribute [r] type
19
+ # @return [Symbol] the identifier type key (e.g. :isin)
20
+ # @!attribute [r] identifier
21
+ # @return [SecID::Base] the valid, parsed corrected identifier
22
+ # @!attribute [r] edit
23
+ # @return [Symbol] :substitution, :transposition, or :checksum
24
+ # @!attribute [r] position
25
+ # @return [Integer, nil] the edit position (nil for :checksum)
26
+ # @!attribute [r] from
27
+ # @return [String] the original character(s)
28
+ # @!attribute [r] to
29
+ # @return [String] the replacement character(s)
30
+ # @!attribute [r] confidence
31
+ # @return [Symbol, nil] :high (homoglyph), :medium (transposition), or nil (:checksum)
32
+ #
33
+ # @example
34
+ # suggestion = SecID::ISIN.suggest('US5949181O45').first
35
+ # suggestion.to_s #=> 'US5949181045'
36
+ # suggestion.edit #=> :substitution
37
+ # suggestion.confidence #=> :high
38
+ Suggestion = Data.define(:type, :identifier, :edit, :position, :from, :to, :confidence)
39
+
40
+ # Reopened to add the domain enumerations and serialization helpers per the repo
41
+ # convention (see {Base#as_json}).
42
+ class Suggestion
43
+ # The edit kinds `edit` can take, in rank order (see {Suggestable::RANK}).
44
+ EDIT_KINDS = %i[substitution transposition checksum].freeze
45
+
46
+ # The non-nil confidence tiers `confidence` can take; `:checksum` candidates carry `nil`.
47
+ CONFIDENCE_LEVELS = %i[high medium].freeze
48
+
49
+ # @return [Hash] the symbol-keyed field hash (JSON-compatible)
50
+ def as_json(*)
51
+ to_h
52
+ end
53
+
54
+ # @return [String] the corrected identifier string
55
+ def to_s
56
+ identifier.to_s
57
+ end
58
+ end
59
+ end
data/lib/sec_id/upi.rb ADDED
@@ -0,0 +1,77 @@
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
+ include Suggestable
22
+
23
+ # Human-readable name of the standard.
24
+ FULL_NAME = 'Unique Product Identifier'
25
+ # Valid length of a normalized identifier.
26
+ ID_LENGTH = 12
27
+ # A representative valid identifier.
28
+ EXAMPLE = 'QZRBG6ZTKS42'
29
+ # Pattern matching the identifier's permitted character set (digits + consonants, no vowels/Y).
30
+ VALID_CHARS_REGEX = /\A[0-9B-DF-HJ-NP-TV-XZ]+\z/
31
+
32
+ # Regular expression for parsing UPI components: fixed 'QZ' prefix, 9-character body,
33
+ # optional check character.
34
+ ID_REGEX = /\A
35
+ (?<identifier>QZ[0-9B-DF-HJ-NP-TV-XZ]{9})
36
+ (?<checksum>[0-9B-DF-HJ-NP-TV-XZ])?
37
+ \z/x
38
+
39
+ # The 30-symbol UPI alphabet, ordered by check-character value (0-29).
40
+ ALPHABET = '0123456789BCDFGHJKLMNPQRSTVWXZ'.chars.freeze
41
+
42
+ # Maps each alphabet character to its check-character value (0-29).
43
+ ALPHABET_VALUE = ALPHABET.each_with_index.to_h.freeze
44
+
45
+ # Characters valid in a UPI body (same alphabet as VALID_CHARS_REGEX).
46
+ GENERATE_CHARSET = ALPHANUMERIC.grep(VALID_CHARS_REGEX).freeze
47
+
48
+ # @param upi [String] the UPI string to parse
49
+ def initialize(upi)
50
+ upi_parts = parse upi
51
+ @identifier = upi_parts[:identifier]
52
+ @checksum = upi_parts[:checksum]
53
+ end
54
+
55
+ # @return [String] the calculated check character
56
+ # @raise [InvalidFormatError] if the UPI format is invalid
57
+ def calculate_checksum
58
+ validate_format_for_calculation!
59
+ mod31_30_check_char(identifier, ALPHABET, ALPHABET_VALUE)
60
+ end
61
+
62
+ # Generates a random UPI body: the fixed 'QZ' prefix plus 9 characters drawn
63
+ # from the UPI alphabet.
64
+ #
65
+ # @param random [Random] source of randomness
66
+ # @return [String] an 11-character UPI body without check character
67
+ def self.generate_body(random)
68
+ "QZ#{random_string(GENERATE_CHARSET, 9, random: random)}"
69
+ end
70
+ private_class_method :generate_body
71
+
72
+ private
73
+
74
+ # @return [Hash]
75
+ def components = { checksum: }
76
+ end
77
+ 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.1.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
@@ -139,8 +143,40 @@ module SecID
139
143
  self[key].generate(random: random)
140
144
  end
141
145
 
146
+ # Returns confidence-ranked repair candidates for a checksum-failing identifier,
147
+ # inferred across all format-compatible checksum types (or the given `types:`).
148
+ #
149
+ # @note Repair is only defined for checksum types; non-checksum types have no
150
+ # oracle and are silently skipped.
151
+ #
152
+ # @param str [String, nil] the identifier string to repair
153
+ # @param types [Array<Symbol>, nil] restrict to specific types (non-checksum types are ignored)
154
+ # @return [Array<Suggestion>] cross-type ranked candidates, each carrying its `type`;
155
+ # empty when no format-compatible checksum type matches
156
+ # @raise [ArgumentError] if any key in types is unknown
157
+ def suggest(str, types: nil)
158
+ input = str.to_s.strip.upcase
159
+ suggestable_types(input, types)
160
+ .flat_map { |klass| klass.suggest(input) }
161
+ .sort_by { |suggestion| [Suggestable::RANK.fetch(suggestion.edit), self[suggestion.type].detection_priority.last] }
162
+ end
163
+
142
164
  private
143
165
 
166
+ # Resolves the checksum types eligible to repair `input` (KTD4): the given `types:`
167
+ # narrowed to checksum types, or every checksum type whose length and charset admit it.
168
+ #
169
+ # @param input [String] the normalized (stripped, upcased) input
170
+ # @param types [Array<Symbol>, nil]
171
+ # @return [Array<Class>]
172
+ def suggestable_types(input, types)
173
+ return types.map { |key| self[key] }.select(&:has_checksum?) if types
174
+
175
+ identifier_list.select do |klass|
176
+ klass.has_checksum? && klass.length_values.include?(input.length) && input.match?(klass::VALID_CHARS_REGEX)
177
+ end
178
+ end
179
+
144
180
  # @return [void]
145
181
  def register_identifier(klass)
146
182
  identifier_map[klass.type_key] = klass
@@ -195,13 +231,16 @@ module SecID
195
231
  end
196
232
  end
197
233
 
234
+ require 'sec_id/deprecation'
198
235
  require 'sec_id/deep_freeze'
199
236
  require 'sec_id/errors'
200
237
  require 'sec_id/concerns/normalizable'
201
238
  require 'sec_id/concerns/validatable'
202
239
  require 'sec_id/concerns/checkable'
203
240
  require 'sec_id/concerns/generatable'
241
+ require 'sec_id/concerns/suggestable'
204
242
  require 'sec_id/base'
243
+ require 'sec_id/suggestion'
205
244
  require 'sec_id/detector'
206
245
  require 'sec_id/scanner'
207
246
  require 'sec_id/isin'
@@ -219,6 +258,7 @@ require 'sec_id/cfi'
219
258
  require 'sec_id/fisn'
220
259
  require 'sec_id/bic'
221
260
  require 'sec_id/dti'
261
+ require 'sec_id/upi'
222
262
 
223
263
  # Auto-activate the ActiveModel validator inside Rails; a no-op everywhere else (keeps the
224
264
  # default require zero-dependency). See SecID::Railtie and lib/sec_id/active_model.rb.
data/sec_id.gemspec CHANGED
@@ -11,13 +11,14 @@ Gem::Specification.new do |spec|
11
11
  spec.email = ['leonid@svyatov.ru']
12
12
 
13
13
  spec.summary = 'A Ruby toolkit for securities identifiers — validate, parse, normalize, detect, convert, ' \
14
- 'generate, and classify.'
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 ' \
14
+ 'generate, classify, and repair.'
15
+ spec.description = 'Validate, normalize, parse, convert, generate, classify, and repair securities identifiers. ' \
16
+ 'Auto-detect identifier type from any string. Calculate and restore checksums. Suggest ' \
17
+ 'confidence-ranked corrections for fat-fingered or OCR-mangled identifiers. Generate ' \
17
18
  'format-valid identifiers as test fixtures. Decode CFI codes to full ISO 10962:2021 ' \
18
19
  'classifications. Includes an opt-in ActiveModel/Rails validator that adds no runtime ' \
19
20
  'dependency. Supports ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN, CIK, OCC, WKN, Valoren, ' \
20
- 'CFI, FISN, BIC, and DTI.'
21
+ 'CFI, FISN, BIC, DTI, and UPI.'
21
22
  spec.homepage = 'https://github.com/svyatov/sec_id'
22
23
  spec.license = 'MIT'
23
24
 
data/sig/sec_id/base.rbs CHANGED
@@ -42,7 +42,14 @@ 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
+ # Only Suggestable (checksum) types implement `.suggest`; declared here so the
47
+ # module-level `SecID.suggest` can call it on any registered class object.
48
+ def self.suggest: (SecID::input str) -> Array[Suggestion]
49
+
50
+ # @deprecated v7 bridge alias for `has_checksum?`; removed in v8.
45
51
  def self.has_check_digit?: () -> bool
52
+
46
53
  def self.inherited: (singleton(Base) subclass) -> void
47
54
 
48
55
  def initialize: (SecID::input _sec_id_number) -> void
@@ -54,24 +61,28 @@ module SecID
54
61
  def as_json: (*untyped) -> Hash[Symbol, untyped]
55
62
  def deconstruct_keys: (::Array[Symbol]? _keys) -> Hash[Symbol, untyped]
56
63
 
57
- # Check-digit surface. Only the types that `include Checkable` implement these
58
- # (they raise/NoMethodError otherwise); declared on Base so `Generatable#generate`
59
- # and `Checkable::ClassMethods` type-check without narrowing on `has_check_digit?`.
64
+ # Checksum / repair surface. Only the types that `include Checkable` /
65
+ # `include Suggestable` implement these (they raise/NoMethodError otherwise);
66
+ # declared on Base so `Generatable#generate`, `Checkable::ClassMethods`, and the
67
+ # Base-self-typed `Suggestable` body type-check without narrowing on `has_checksum?`.
60
68
  def restore: () -> String
61
69
  def restore!: () -> self
62
- def calculate_check_digit: () -> (Integer | String)
70
+ def calculate_checksum: () -> (Integer | String)
71
+ attr_reader checksum: (Integer | String)?
72
+ def suggest: () -> Array[Suggestion]
63
73
 
64
74
  def comparison_id: () -> String
65
75
 
66
76
  private
67
77
 
78
+ def components_with_deprecation_bridge: () -> Hash[Symbol, untyped]
68
79
  def components: () -> Hash[Symbol, untyped]
69
80
  def parse: (SecID::input sec_id_number) -> (::MatchData | ::Hash[Symbol, untyped])
70
81
 
71
82
  @full_id: String
72
83
  @identifier: untyped
73
84
  self.@short_name: String
74
- self.@has_check_digit: bool
85
+ self.@has_checksum: bool
75
86
  self.@type_key: Symbol
76
87
  self.@detection_priority: Array[Integer | Float | nil]
77
88
  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,36 @@
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` is declared on Base (the repair surface) to keep the
10
+ # Base-self-typed Suggestable body type-checking; not redeclared here.
10
11
 
11
12
  # Class methods added when Checkable is included.
12
13
  module ClassMethods : SecID::_IdentifierClass
13
- def restore: (SecID::input id_without_check_digit) -> String
14
- def restore!: (SecID::input id_without_check_digit) -> Base
14
+ def restore: (SecID::input id_without_checksum) -> String
15
+ def restore!: (SecID::input id_without_checksum) -> Base
16
+ def checksum: (SecID::input id) -> (Integer | String)
17
+
18
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
15
19
  def check_digit: (SecID::input id) -> (Integer | String)
16
20
  end
17
21
 
18
22
  def valid?: () -> bool
19
23
  def restore: () -> String
20
24
  def restore!: () -> self
21
- def calculate_check_digit: () -> (Integer | String)
25
+ def calculate_checksum: () -> (Integer | String)
22
26
  def to_s: () -> String
23
27
 
28
+ # @deprecated v7 bridge alias for `checksum`; removed in v8.
29
+ def check_digit: () -> (Integer | String)?
30
+
31
+ # @deprecated v7 bridge alias for `calculate_checksum`; removed in v8.
32
+ def calculate_check_digit: () -> (Integer | String)
33
+
24
34
  private
25
35
 
26
36
  def luhn_sum_double_add_double: (Array[Integer] digits) -> Integer
@@ -29,13 +39,14 @@ module SecID
29
39
  def reversed_digits_single: (untyped id) -> Array[Integer]
30
40
  def reversed_digits_multi: (untyped id) -> Array[Integer]
31
41
  def error_codes: () -> Array[Symbol]
32
- def check_digit_width: () -> Integer
42
+ def checksum_width: () -> Integer
33
43
  def validation_message: (Symbol code) -> String?
34
44
  def validate_format_for_calculation!: () -> void
35
45
  def mod10: (Integer sum) -> Integer
36
46
  def div10mod10: (Integer number) -> Integer
37
47
  def mod97: (String numeric_string) -> Integer
48
+ def mod31_30_check_char: (untyped base, Array[String] alphabet, Hash[String, Integer] alphabet_value) -> String
38
49
 
39
- @check_digit: (Integer | String)?
50
+ @checksum: (Integer | String)?
40
51
  end
41
52
  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.
@@ -0,0 +1,32 @@
1
+ module SecID
2
+ # Repair engine for checksum-failing identifiers: enumerates plausible edits and
3
+ # returns the re-validating ones as confidence-ranked Suggestion objects.
4
+ module Suggestable : SecID::Base
5
+ HOMOGLYPHS: Hash[String, Array[String]]
6
+ RANK: Hash[Symbol, Integer]
7
+
8
+ def self.included: (Module base) -> void
9
+
10
+ # Class methods added when Suggestable is included.
11
+ module ClassMethods : SecID::_IdentifierClass
12
+ def suggest: (SecID::input str) -> Array[Suggestion]
13
+ end
14
+
15
+ def suggest: () -> Array[Suggestion]
16
+
17
+ private
18
+
19
+ def candidate_strings: () -> Array[String]
20
+ def homoglyph_candidates: () -> Array[String]
21
+ def transposition_candidates: () -> Array[String]
22
+ def replace_at: (String str, Integer index, String char) -> String
23
+ def swap_at: (String str, Integer index) -> String
24
+ def classify: (Base candidate) -> Suggestion?
25
+ def changed_indices: (Base candidate) -> Array[Integer]
26
+ def substitution_suggestion: (Base candidate, Integer index) -> Suggestion
27
+ def transposition_suggestion: (Base candidate, Array[Integer] indices) -> Suggestion?
28
+ def swap?: (Base candidate, Integer left, Integer right) -> bool
29
+ def checksum_suggestion: (Base candidate) -> Suggestion
30
+ def rendered_checksum: ((Integer | String)? value) -> String
31
+ end
32
+ end
@@ -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
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
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,23 @@
1
+ module SecID
2
+ # Immutable value object representing one repair candidate returned by `suggest`.
3
+ class Suggestion < Data
4
+ EDIT_KINDS: Array[Symbol]
5
+ CONFIDENCE_LEVELS: Array[Symbol]
6
+
7
+ attr_reader type: Symbol
8
+ attr_reader identifier: Base
9
+ attr_reader edit: Symbol
10
+ attr_reader position: Integer?
11
+ attr_reader from: String
12
+ attr_reader to: String
13
+ attr_reader confidence: Symbol?
14
+
15
+ def self.new: (type: Symbol, identifier: Base, edit: Symbol, position: Integer?, from: String, to: String, confidence: Symbol?) -> instance
16
+ | (Symbol type, Base identifier, Symbol edit, Integer? position, String from, String to, Symbol? confidence) -> instance
17
+ def self.[]: (type: Symbol, identifier: Base, edit: Symbol, position: Integer?, from: String, to: String, confidence: Symbol?) -> instance
18
+ | (Symbol type, Base identifier, Symbol edit, Integer? position, String from, String to, Symbol? confidence) -> instance
19
+
20
+ def as_json: (*untyped) -> Hash[Symbol, untyped]
21
+ def to_s: () -> String
22
+ end
23
+ end
@@ -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