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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/MIGRATION.md +147 -0
- data/README.md +108 -45
- data/lib/sec_id/base.rb +36 -17
- data/lib/sec_id/cei.rb +8 -7
- data/lib/sec_id/cik.rb +1 -1
- data/lib/sec_id/concerns/checkable.rb +90 -46
- data/lib/sec_id/concerns/generatable.rb +5 -5
- data/lib/sec_id/concerns/normalizable.rb +4 -4
- data/lib/sec_id/concerns/suggestable.rb +168 -0
- data/lib/sec_id/concerns/validatable.rb +7 -7
- data/lib/sec_id/cusip.rb +9 -8
- data/lib/sec_id/deprecation.rb +26 -0
- data/lib/sec_id/dti.rb +9 -14
- data/lib/sec_id/figi.rb +10 -9
- data/lib/sec_id/iban.rb +21 -20
- data/lib/sec_id/isin.rb +10 -9
- data/lib/sec_id/lei.rb +11 -10
- data/lib/sec_id/occ.rb +1 -1
- data/lib/sec_id/sedol.rb +12 -11
- data/lib/sec_id/suggestion.rb +59 -0
- data/lib/sec_id/upi.rb +77 -0
- data/lib/sec_id/valoren.rb +2 -2
- data/lib/sec_id/version.rb +1 -1
- data/lib/sec_id/wkn.rb +1 -1
- data/lib/sec_id.rb +43 -3
- data/sec_id.gemspec +5 -4
- data/sig/sec_id/base.rbs +16 -5
- data/sig/sec_id/cei.rbs +4 -0
- data/sig/sec_id/concerns/checkable.rbs +18 -7
- data/sig/sec_id/concerns/generatable.rbs +1 -1
- data/sig/sec_id/concerns/suggestable.rbs +32 -0
- data/sig/sec_id/concerns/validatable.rbs +1 -1
- data/sig/sec_id/cusip.rbs +4 -0
- data/sig/sec_id/deprecation.rbs +6 -0
- data/sig/sec_id/dti.rbs +6 -1
- data/sig/sec_id/figi.rbs +4 -0
- data/sig/sec_id/iban.rbs +7 -3
- data/sig/sec_id/isin.rbs +4 -0
- data/sig/sec_id/lei.rbs +5 -1
- data/sig/sec_id/sedol.rbs +4 -0
- data/sig/sec_id/suggestion.rbs +23 -0
- data/sig/sec_id/upi.rbs +29 -0
- data/sig/sec_id.rbs +7 -2
- 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
|
data/lib/sec_id/valoren.rb
CHANGED
|
@@ -6,7 +6,7 @@ module SecID
|
|
|
6
6
|
#
|
|
7
7
|
# Format: 5-9 numeric digits
|
|
8
8
|
#
|
|
9
|
-
# @note Valoren identifiers have no
|
|
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
|
|
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)
|
data/lib/sec_id/version.rb
CHANGED
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
|
|
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
|
|
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
|
|
18
|
-
class
|
|
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
|
|
15
|
-
spec.description = 'Validate, normalize, parse, convert, generate, and
|
|
16
|
-
'Auto-detect identifier type from any string. Calculate and restore
|
|
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
|
|
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
|
-
#
|
|
58
|
-
# (they raise/NoMethodError otherwise);
|
|
59
|
-
#
|
|
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
|
|
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.@
|
|
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
|
|
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
|
|
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
|
|
14
|
-
def restore!: (SecID::input
|
|
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
|
|
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
|
|
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
|
-
@
|
|
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
|
|
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
|
|
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
|
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
|
|
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
|
|
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
|
|
49
|
+
def extract_checksum_and_bban: (String rest) -> void
|
|
46
50
|
def expected_bban_length_for_country: () -> Integer?
|
|
47
|
-
def
|
|
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
|
|
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
|
data/sig/sec_id/upi.rbs
ADDED
|
@@ -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
|