sec_id 5.1.0 → 6.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +38 -0
  4. data/MIGRATION.md +101 -0
  5. data/README.md +262 -15
  6. data/lib/sec_id/active_model.rb +144 -0
  7. data/lib/sec_id/base.rb +41 -1
  8. data/lib/sec_id/bic/country_codes.rb +46 -0
  9. data/lib/sec_id/bic.rb +123 -0
  10. data/lib/sec_id/cei.rb +19 -7
  11. data/lib/sec_id/cfi/attribute_set.rb +49 -0
  12. data/lib/sec_id/cfi/classification.rb +107 -0
  13. data/lib/sec_id/cfi/field.rb +56 -0
  14. data/lib/sec_id/cfi/tables.rb +1033 -0
  15. data/lib/sec_id/cfi.rb +153 -208
  16. data/lib/sec_id/cik.rb +13 -0
  17. data/lib/sec_id/concerns/checkable.rb +2 -2
  18. data/lib/sec_id/concerns/generatable.rb +71 -0
  19. data/lib/sec_id/concerns/normalizable.rb +1 -0
  20. data/lib/sec_id/concerns/validatable.rb +14 -3
  21. data/lib/sec_id/cusip.rb +18 -7
  22. data/lib/sec_id/deep_freeze.rb +18 -0
  23. data/lib/sec_id/detector.rb +28 -7
  24. data/lib/sec_id/dti.rb +104 -0
  25. data/lib/sec_id/errors.rb +7 -0
  26. data/lib/sec_id/figi.rb +18 -0
  27. data/lib/sec_id/fisn.rb +31 -0
  28. data/lib/sec_id/iban/country_rules.rb +7 -6
  29. data/lib/sec_id/iban.rb +26 -1
  30. data/lib/sec_id/isin.rb +20 -15
  31. data/lib/sec_id/lei.rb +13 -0
  32. data/lib/sec_id/occ.rb +21 -0
  33. data/lib/sec_id/railtie.rb +14 -0
  34. data/lib/sec_id/scanner.rb +140 -0
  35. data/lib/sec_id/sedol.rb +16 -0
  36. data/lib/sec_id/valoren.rb +13 -0
  37. data/lib/sec_id/version.rb +2 -1
  38. data/lib/sec_id/wkn.rb +16 -0
  39. data/lib/sec_id.rb +112 -32
  40. data/sec_id.gemspec +11 -5
  41. data/sig/manifest.yaml +8 -0
  42. data/sig/sec_id/base.rbs +72 -0
  43. data/sig/sec_id/bic/country_codes.rbs +5 -0
  44. data/sig/sec_id/bic.rbs +30 -0
  45. data/sig/sec_id/cei.rbs +26 -0
  46. data/sig/sec_id/cfi/attribute_set.rbs +68 -0
  47. data/sig/sec_id/cfi/classification.rbs +23 -0
  48. data/sig/sec_id/cfi/field.rbs +337 -0
  49. data/sig/sec_id/cfi/tables.rbs +68 -0
  50. data/sig/sec_id/cfi.rbs +56 -0
  51. data/sig/sec_id/cik.rbs +19 -0
  52. data/sig/sec_id/concerns/checkable.rbs +41 -0
  53. data/sig/sec_id/concerns/generatable.rbs +26 -0
  54. data/sig/sec_id/concerns/normalizable.rbs +34 -0
  55. data/sig/sec_id/concerns/validatable.rbs +42 -0
  56. data/sig/sec_id/cusip.rbs +29 -0
  57. data/sig/sec_id/deep_freeze.rbs +6 -0
  58. data/sig/sec_id/detector.rbs +35 -0
  59. data/sig/sec_id/dti.rbs +27 -0
  60. data/sig/sec_id/errors.rbs +28 -0
  61. data/sig/sec_id/figi.rbs +31 -0
  62. data/sig/sec_id/fisn.rbs +24 -0
  63. data/sig/sec_id/iban/country_rules.rbs +10 -0
  64. data/sig/sec_id/iban.rbs +54 -0
  65. data/sig/sec_id/isin.rbs +40 -0
  66. data/sig/sec_id/lei.rbs +33 -0
  67. data/sig/sec_id/occ.rbs +45 -0
  68. data/sig/sec_id/scanner.rbs +46 -0
  69. data/sig/sec_id/sedol.rbs +34 -0
  70. data/sig/sec_id/valoren.rbs +29 -0
  71. data/sig/sec_id/version.rbs +3 -0
  72. data/sig/sec_id/wkn.rbs +15 -0
  73. data/sig/sec_id.rbs +58 -0
  74. metadata +57 -7
  75. data/lib/sec_id/concerns/identifier_metadata.rb +0 -56
data/lib/sec_id/sedol.rb CHANGED
@@ -17,9 +17,13 @@ module SecID
17
17
  class SEDOL < Base
18
18
  include Checkable
19
19
 
20
+ # Human-readable name of the standard.
20
21
  FULL_NAME = 'Stock Exchange Daily Official List'
22
+ # Valid length(s) of a normalized identifier.
21
23
  ID_LENGTH = 7
24
+ # A representative valid identifier.
22
25
  EXAMPLE = 'B0YBKJ7'
26
+ # Pattern matching the identifier's permitted character set.
23
27
  VALID_CHARS_REGEX = /\A[0-9BCDFGHJKLMNPQRSTVWXYZ]+\z/
24
28
 
25
29
  # Regular expression for parsing SEDOL components.
@@ -32,6 +36,9 @@ module SecID
32
36
  # Weights applied to each character position in the check digit calculation.
33
37
  CHARACTER_WEIGHTS = [1, 3, 1, 7, 3, 9].freeze
34
38
 
39
+ # Characters valid in a SEDOL body (alphanumeric excluding vowels).
40
+ GENERATE_CHARSET = ALPHANUMERIC.grep(VALID_CHARS_REGEX).freeze
41
+
35
42
  # @param sedol [String] the SEDOL string to parse
36
43
  def initialize(sedol)
37
44
  sedol_parts = parse sedol
@@ -60,6 +67,15 @@ module SecID
60
67
  mod10(weighted_sum)
61
68
  end
62
69
 
70
+ # Generates a random SEDOL body: 6 characters excluding vowels.
71
+ #
72
+ # @param random [Random] source of randomness
73
+ # @return [String] a 6-character SEDOL body without check digit
74
+ def self.generate_body(random)
75
+ random_string(GENERATE_CHARSET, 6, random: random)
76
+ end
77
+ private_class_method :generate_body
78
+
63
79
  private
64
80
 
65
81
  # @return [Hash]
@@ -17,9 +17,13 @@ module SecID
17
17
  # @example Normalize a Valoren to 9 digits
18
18
  # SecID::Valoren.normalize('3886335') #=> '003886335'
19
19
  class Valoren < Base
20
+ # Human-readable name of the standard.
20
21
  FULL_NAME = 'Valoren Number'
22
+ # Valid length(s) of a normalized identifier.
21
23
  ID_LENGTH = (5..9)
24
+ # A representative valid identifier.
22
25
  EXAMPLE = '3886335'
26
+ # Pattern matching the identifier's permitted character set.
23
27
  VALID_CHARS_REGEX = /\A[0-9]+\z/
24
28
 
25
29
  # Regular expression for parsing Valoren components.
@@ -77,5 +81,14 @@ module SecID
77
81
  def to_s
78
82
  full_id
79
83
  end
84
+
85
+ # Generates a random Valoren: an integer rendered without leading zeros.
86
+ #
87
+ # @param random [Random] source of randomness
88
+ # @return [String] a 5-9 digit Valoren
89
+ def self.generate_body(random)
90
+ random.rand(10_000..999_999_999).to_s
91
+ end
92
+ private_class_method :generate_body
80
93
  end
81
94
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecID
4
- VERSION = '5.1.0'
4
+ # Current gem version.
5
+ VERSION = '6.0.0'
5
6
  end
data/lib/sec_id/wkn.rb CHANGED
@@ -13,9 +13,13 @@ module SecID
13
13
  # SecID::WKN.valid?('514000') #=> true
14
14
  # SecID::WKN.valid?('CBK100') #=> true
15
15
  class WKN < Base
16
+ # Human-readable name of the standard.
16
17
  FULL_NAME = 'Wertpapierkennnummer'
18
+ # Valid length(s) of a normalized identifier.
17
19
  ID_LENGTH = 6
20
+ # A representative valid identifier.
18
21
  EXAMPLE = '514000'
22
+ # Pattern matching the identifier's permitted character set.
19
23
  VALID_CHARS_REGEX = /\A[0-9A-HJ-NP-Z]+\z/
20
24
 
21
25
  # Regular expression for parsing WKN components.
@@ -24,6 +28,9 @@ module SecID
24
28
  (?<identifier>[0-9A-HJ-NP-Z]{6})
25
29
  \z/x
26
30
 
31
+ # Characters valid in a WKN (alphanumeric excluding I and O).
32
+ GENERATE_CHARSET = ALPHANUMERIC.grep(VALID_CHARS_REGEX).freeze
33
+
27
34
  # @param wkn [String] the WKN string to parse
28
35
  def initialize(wkn)
29
36
  wkn_parts = parse(wkn)
@@ -40,5 +47,14 @@ module SecID
40
47
 
41
48
  ISIN.new("#{country_code}000#{identifier}").restore!
42
49
  end
50
+
51
+ # Generates a random WKN: 6 characters excluding I and O.
52
+ #
53
+ # @param random [Random] source of randomness
54
+ # @return [String] a 6-character WKN
55
+ def self.generate_body(random)
56
+ random_string(GENERATE_CHARSET, 6, random: random)
57
+ end
58
+ private_class_method :generate_body
43
59
  end
44
60
  end
data/lib/sec_id.rb CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  require 'sec_id/version'
4
4
 
5
+ # Toolkit for securities identifiers — validate, normalize, parse, detect, convert,
6
+ # generate, and classify ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN, CIK, OCC, WKN,
7
+ # Valoren, CFI, FISN, BIC, and DTI.
8
+ #
9
+ # @see https://github.com/svyatov/sec_id
5
10
  module SecID
6
11
  # Base error class for all SecID errors.
7
12
  class Error < StandardError; end
@@ -15,6 +20,9 @@ module SecID
15
20
  # Raised for type-specific structural errors (invalid prefix, category, group, BBAN, or date).
16
21
  class InvalidStructureError < Error; end
17
22
 
23
+ # Raised when multiple identifier types match and on_ambiguous: :raise is used.
24
+ class AmbiguousMatchError < Error; end
25
+
18
26
  class << self
19
27
  # Looks up an identifier class by its symbol key.
20
28
  #
@@ -49,50 +57,103 @@ module SecID
49
57
  # @return [Boolean]
50
58
  # @raise [ArgumentError] if any key in types is unknown
51
59
  def valid?(str, types: nil)
52
- return detect(str).any? if types.nil?
60
+ return detector.matches?(str) if types.nil?
53
61
 
54
62
  types.any? { |key| self[key].valid?(str) }
55
63
  end
56
64
 
57
- # Parses a string into the most specific matching identifier instance.
58
- #
65
+ # @param text [String, nil] the text to scan
66
+ # @param types [Array<Symbol>, nil] restrict to specific types
67
+ # @return [Array<Match>]
68
+ # @raise [ArgumentError] if any key in types is unknown
69
+ def extract(text, types: nil)
70
+ scan(text, types: types).to_a
71
+ end
72
+
73
+ # @param text [String, nil] the text to scan
74
+ # @param types [Array<Symbol>, nil] restrict to specific types
75
+ # @return [Enumerator<Match>] if no block given
76
+ # @yieldparam match [Match]
77
+ # @raise [ArgumentError] if any key in types is unknown
78
+ def scan(text, types: nil, &)
79
+ classes = types&.map { |key| self[key] }
80
+ scanner.call(text, classes: classes, &)
81
+ end
82
+
83
+ # @param str [String, nil] the identifier string to explain
84
+ # @param types [Array<Symbol>, nil] restrict to specific types
85
+ # @return [Hash] hash with :input and :candidates keys
86
+ def explain(str, types: nil)
87
+ input = str.to_s.strip
88
+ target_keys = types || identifier_list.map { |k| k.short_name.downcase.to_sym }
89
+ candidates = target_keys.map do |key|
90
+ instance = self[key].new(input)
91
+ { type: key, valid: instance.valid?, errors: instance.errors.details }
92
+ end
93
+ { input: input, candidates: candidates }
94
+ end
95
+
59
96
  # @param str [String, nil] the identifier string to parse
60
97
  # @param types [Array<Symbol>, nil] restrict to specific types (e.g. [:isin, :cusip])
61
- # @return [SecID::Base, nil] a valid identifier instance, or nil if no match
62
- # @raise [ArgumentError] if any key in types is unknown
63
- def parse(str, types: nil)
64
- types.nil? ? parse_any(str) : parse_from(str, types)
98
+ # @param on_ambiguous [:first, :raise, :all] how to handle multiple matches
99
+ # @return [SecID::Base, nil, Array<SecID::Base>] depends on on_ambiguous mode
100
+ # @raise [AmbiguousMatchError] when on_ambiguous: :raise and multiple types match
101
+ def parse(str, types: nil, on_ambiguous: :first)
102
+ case on_ambiguous
103
+ when :first then types.nil? ? parse_any(str) : parse_from(str, types)
104
+ when :raise then parse_strict(str, types)
105
+ when :all then parse_all(str, types)
106
+ else raise ArgumentError, "Unknown on_ambiguous mode: #{on_ambiguous.inspect}"
107
+ end
65
108
  end
66
109
 
67
- # Parses a string into the most specific matching identifier instance, raising on failure.
68
- #
69
110
  # @param str [String, nil] the identifier string to parse
70
111
  # @param types [Array<Symbol>, nil] restrict to specific types (e.g. [:isin, :cusip])
71
- # @return [SecID::Base] a valid identifier instance
112
+ # @param on_ambiguous [:first, :raise, :all] how to handle multiple matches
113
+ # @return [SecID::Base, Array<SecID::Base>] depends on on_ambiguous mode
72
114
  # @raise [InvalidFormatError] if no matching identifier type is found
73
- # @raise [ArgumentError] if any key in types is unknown
74
- def parse!(str, types: nil)
75
- parse(str, types: types) || raise(InvalidFormatError, parse_error_message(str, types))
115
+ # @raise [AmbiguousMatchError] when on_ambiguous: :raise and multiple types match
116
+ def parse!(str, types: nil, on_ambiguous: :first)
117
+ result = parse(str, types: types, on_ambiguous: on_ambiguous)
118
+
119
+ if on_ambiguous == :all
120
+ raise(InvalidFormatError, parse_error_message(str, types)) if result.empty?
121
+
122
+ return result
123
+ end
124
+
125
+ result || raise(InvalidFormatError, parse_error_message(str, types))
126
+ end
127
+
128
+ # Generates a new, format-valid identifier instance of the given type.
129
+ #
130
+ # @note Generated identifiers are valid in format only — they are not real,
131
+ # registered securities.
132
+ #
133
+ # @param key [Symbol] identifier type (e.g. :isin, :cusip)
134
+ # @param random [Random] seedable source for reproducible output
135
+ # @return [SecID::Base] a generated, format-valid instance of the type mapped to `key`
136
+ # (e.g. {SecID::ISIN} for `:isin`)
137
+ # @raise [ArgumentError] if key is unknown
138
+ def generate(key, random: Random.new)
139
+ self[key].generate(random: random)
76
140
  end
77
141
 
78
142
  private
79
143
 
80
- # @param klass [Class] the identifier class to register
81
144
  # @return [void]
82
145
  def register_identifier(klass)
83
146
  key = klass.name.split('::').last.downcase.to_sym
84
147
  identifier_map[key] = klass
85
148
  identifier_list << klass
86
149
  @detector = nil
150
+ @scanner = nil
87
151
  end
88
152
 
89
- # @return [SecID::Base, nil]
90
153
  def parse_any(str)
91
- key = detect(str).first
92
- key && self[key].new(str)
154
+ detector.first_match(str)
93
155
  end
94
156
 
95
- # @return [SecID::Base, nil]
96
157
  def parse_from(str, types)
97
158
  types.each do |key|
98
159
  instance = self[key].new(str)
@@ -101,36 +162,49 @@ module SecID
101
162
  nil
102
163
  end
103
164
 
104
- # @return [String]
105
- def parse_error_message(str, types)
106
- base = "No matching identifier type found for #{str.to_s.strip.inspect}"
107
- types ? "#{base} among #{types.inspect}" : base
165
+ def parse_strict(str, types)
166
+ candidates = resolve_candidates(str, types)
167
+ raise AmbiguousMatchError, ambiguous_message(str, candidates) if candidates.size > 1
168
+
169
+ candidates.first && self[candidates.first].new(str)
108
170
  end
109
171
 
110
- # @return [Detector]
111
- def detector
112
- @detector ||= Detector.new(identifier_list)
172
+ def parse_all(str, types)
173
+ resolve_candidates(str, types).map { |key| self[key].new(str) }
113
174
  end
114
175
 
115
- # @return [Hash{Symbol => Class}]
116
- def identifier_map
117
- @identifier_map ||= {}
176
+ # @return [Array<Symbol>]
177
+ def resolve_candidates(str, types)
178
+ types ? types.select { |key| self[key].valid?(str) } : detect(str)
118
179
  end
119
180
 
120
- # @return [Array<Class>]
121
- def identifier_list
122
- @identifier_list ||= []
181
+ # @return [String]
182
+ def ambiguous_message(str, candidates)
183
+ "Ambiguous identifier #{str.to_s.strip.inspect}: matches #{candidates.inspect}"
184
+ end
185
+
186
+ # @return [String]
187
+ def parse_error_message(str, types)
188
+ base = "No matching identifier type found for #{str.to_s.strip.inspect}"
189
+ types ? "#{base} among #{types.inspect}" : base
123
190
  end
191
+
192
+ def detector = @detector ||= Detector.new(identifier_list)
193
+ def scanner = @scanner ||= Scanner.new(identifier_list)
194
+ def identifier_map = @identifier_map ||= {}
195
+ def identifier_list = @identifier_list ||= []
124
196
  end
125
197
  end
126
198
 
199
+ require 'sec_id/deep_freeze'
127
200
  require 'sec_id/errors'
128
- require 'sec_id/concerns/identifier_metadata'
129
201
  require 'sec_id/concerns/normalizable'
130
202
  require 'sec_id/concerns/validatable'
131
203
  require 'sec_id/concerns/checkable'
204
+ require 'sec_id/concerns/generatable'
132
205
  require 'sec_id/base'
133
206
  require 'sec_id/detector'
207
+ require 'sec_id/scanner'
134
208
  require 'sec_id/isin'
135
209
  require 'sec_id/cusip'
136
210
  require 'sec_id/sedol'
@@ -144,3 +218,9 @@ require 'sec_id/valoren'
144
218
  require 'sec_id/cei'
145
219
  require 'sec_id/cfi'
146
220
  require 'sec_id/fisn'
221
+ require 'sec_id/bic'
222
+ require 'sec_id/dti'
223
+
224
+ # Auto-activate the ActiveModel validator inside Rails; a no-op everywhere else (keeps the
225
+ # default require zero-dependency). See SecID::Railtie and lib/sec_id/active_model.rb.
226
+ require 'sec_id/railtie' if defined?(Rails::Railtie)
data/sec_id.gemspec CHANGED
@@ -10,19 +10,25 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ['Leonid Svyatov']
11
11
  spec.email = ['leonid@svyatov.ru']
12
12
 
13
- spec.summary = 'Validate securities identification numbers with ease!'
14
- spec.description = 'Validate, calculate check digits, and parse components of securities identifiers. ' \
15
- 'Supports ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN, CIK, OCC, WKN, Valoren, CFI, ' \
16
- 'and FISN standards.'
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 ' \
17
+ 'format-valid identifiers as test fixtures. Decode CFI codes to full ISO 10962:2021 ' \
18
+ 'classifications. Includes an opt-in ActiveModel/Rails validator that adds no runtime ' \
19
+ 'dependency. Supports ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN, CIK, OCC, WKN, Valoren, ' \
20
+ 'CFI, FISN, BIC, and DTI.'
17
21
  spec.homepage = 'https://github.com/svyatov/sec_id'
18
22
  spec.license = 'MIT'
19
23
 
20
24
  spec.required_ruby_version = '>= 3.2.0'
21
25
 
22
26
  spec.require_paths = ['lib']
23
- spec.files = Dir['lib/**/*.rb'] + %w[CHANGELOG.md LICENSE.txt MIGRATION.md README.md sec_id.gemspec]
27
+ spec.files = Dir['lib/**/*.rb'] + Dir['sig/**/*'] +
28
+ %w[.yardopts CHANGELOG.md LICENSE.txt MIGRATION.md README.md sec_id.gemspec]
24
29
 
25
30
  spec.metadata['rubygems_mfa_required'] = 'true'
31
+ spec.metadata['documentation_uri'] = 'https://rubydoc.info/gems/sec_id'
26
32
  spec.metadata['source_code_uri'] = 'https://github.com/svyatov/sec_id'
27
33
  spec.metadata['changelog_uri'] = 'https://github.com/svyatov/sec_id/blob/main/CHANGELOG.md'
28
34
  spec.metadata['bug_tracker_uri'] = 'https://github.com/svyatov/sec_id/issues'
data/sig/manifest.yaml ADDED
@@ -0,0 +1,8 @@
1
+ # Standard-library signatures these RBS files reference. Consumers' RBS/Steep
2
+ # load these automatically when they depend on sec_id. (The gem has no runtime
3
+ # gem dependencies, so none are auto-derived from the gemspec.)
4
+ #
5
+ # Set (ISIN, SEDOL, FIGI, Valoren, BIC) is a core class in rbs 4.0 — always
6
+ # loaded, so it needs no manifest entry.
7
+ dependencies:
8
+ - name: date # Date in OCC
@@ -0,0 +1,72 @@
1
+ module SecID
2
+ # Accepted raw input for parsing/validating an identifier: a String, an Integer
3
+ # (Valoren/CIK/CUSIP accept numeric input), or nil.
4
+ type input = String | Integer | nil
5
+
6
+ # The class-object surface the concern ClassMethods modules call through `self`
7
+ # once extended onto an identifier class (they construct instances via `new`).
8
+ interface _IdentifierClass
9
+ def new: (SecID::input) -> Base
10
+ end
11
+
12
+ # Base class for securities identifiers, providing a common interface for
13
+ # validation, normalization, parsing, and generation.
14
+ class Base
15
+ include Normalizable
16
+ include Validatable
17
+ include Generatable
18
+ extend Normalizable::ClassMethods
19
+ extend Validatable::ClassMethods
20
+ extend Generatable::ClassMethods
21
+
22
+ # Metadata constants each subclass overrides with its concrete shape. Declared
23
+ # here (as the widest shape) so the shared concern method bodies type-check.
24
+ FULL_NAME: String
25
+ ID_LENGTH: Integer | ::Range[Integer] | ::Array[Integer]
26
+ EXAMPLE: String
27
+ VALID_CHARS_REGEX: ::Regexp
28
+ ID_REGEX: ::Regexp
29
+
30
+ attr_reader full_id: String
31
+ # Reader is nilable (nil for invalid input, checked by `valid_format?`); the ivar is
32
+ # typed `untyped` because subclasses use `@identifier` as non-nil after validation and
33
+ # RBS forbids narrowing an inherited ivar's type.
34
+ def identifier: () -> String?
35
+
36
+ def self.short_name: () -> String
37
+ def self.full_name: () -> String
38
+ def self.id_length: () -> (Integer | ::Range[Integer] | ::Array[Integer])
39
+ def self.length_values: () -> (::Array[Integer] | ::Range[Integer])
40
+ def self.length_specificity: () -> (Integer | Float | nil)
41
+ def self.example: () -> String
42
+ def self.has_check_digit?: () -> bool
43
+ def self.inherited: (singleton(Base) subclass) -> void
44
+
45
+ def initialize: (SecID::input _sec_id_number) -> void
46
+
47
+ def ==: (Base other) -> bool
48
+ alias eql? ==
49
+ def hash: () -> Integer
50
+ def to_h: () -> Hash[Symbol, untyped]
51
+ def as_json: (*untyped) -> Hash[Symbol, untyped]
52
+
53
+ # Check-digit surface. Only the types that `include Checkable` implement these
54
+ # (they raise/NoMethodError otherwise); declared on Base so `Generatable#generate`
55
+ # and `Checkable::ClassMethods` type-check without narrowing on `has_check_digit?`.
56
+ def restore: () -> String
57
+ def restore!: () -> self
58
+ def calculate_check_digit: () -> (Integer | String)
59
+
60
+ def comparison_id: () -> String
61
+
62
+ private
63
+
64
+ def components: () -> Hash[Symbol, untyped]
65
+ def parse: (SecID::input sec_id_number) -> (::MatchData | ::Hash[Symbol, untyped])
66
+
67
+ @full_id: String
68
+ @identifier: untyped
69
+ self.@short_name: String
70
+ self.@has_check_digit: bool
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ module SecID
2
+ class BIC < Base
3
+ COUNTRY_CODES: Set[String]
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ module SecID
2
+ # Business Identifier Code (BIC / SWIFT code).
3
+ class BIC < Base
4
+ FULL_NAME: String
5
+ ID_LENGTH: Array[Integer]
6
+ EXAMPLE: String
7
+ VALID_CHARS_REGEX: ::Regexp
8
+ ID_REGEX: ::Regexp
9
+
10
+ def self.countries: () -> Array[String]
11
+
12
+ attr_reader bank_code: String?
13
+ attr_reader country_code: String?
14
+ attr_reader location_code: String?
15
+ attr_reader branch_code: String?
16
+
17
+ def initialize: (SecID::input bic) -> void
18
+ private def self.generate_body: (Random random) -> String
19
+
20
+ private
21
+
22
+ def components: () -> Hash[Symbol, untyped]
23
+ def valid_format?: () -> bool
24
+ def detect_errors: () -> Array[Symbol]
25
+ def validation_message: (Symbol code) -> String?
26
+ def recognized_country?: () -> bool
27
+
28
+ self.@countries: Array[String]
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ module SecID
2
+ # CUSIP Entity Identifier (CEI).
3
+ class CEI < 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
+
13
+ attr_reader prefix: String?
14
+ attr_reader numeric: String?
15
+ attr_reader entity_id: String?
16
+
17
+ def initialize: (SecID::input cei) -> void
18
+ def calculate_check_digit: () -> Integer
19
+ def self.check_digit: (SecID::input id) -> Integer
20
+ private def self.generate_body: (Random random) -> String
21
+
22
+ private
23
+
24
+ def components: () -> Hash[Symbol, untyped]
25
+ end
26
+ end
@@ -0,0 +1,68 @@
1
+ # Generated by `rake sig:cfi` from SecID::CFI::Tables. Do not edit by hand.
2
+ module SecID
3
+ class CFI < Base
4
+ # The ordered collection of decoded attribute Fields.
5
+ class AttributeSet
6
+ include Enumerable[Field]
7
+
8
+ # `fields` is typed `untyped` because `#initialize` calls
9
+ # `define_singleton_method(field.meaning)` and `field.meaning` is nilable.
10
+ def initialize: (untyped fields) -> void
11
+ def each: () { (Field) -> void } -> self
12
+ | () -> Enumerator[Field, self]
13
+ def []: (Symbol meaning) -> Field?
14
+ def empty?: () -> bool
15
+ def to_h: () -> Hash[untyped, untyped]
16
+ def as_json: (*untyped) -> Hash[Symbol, untyped]
17
+
18
+ # Per-meaning readers — the union of every attribute meaning. Each instance
19
+ # only answers the meanings actually present; an absent one raises
20
+ # NoMethodError at runtime. Use #[] for the presence-uncertain path.
21
+ def asset_class: () -> Field
22
+ def assets: () -> Field
23
+ def barrier_dependency: () -> Field
24
+ def calculation_frequency: () -> Field
25
+ def call_put: () -> Field
26
+ def closed_open: () -> Field
27
+ def components: () -> Field
28
+ def composition: () -> Field
29
+ def currency: () -> Field
30
+ def delivery: () -> Field
31
+ def distribution: () -> Field
32
+ def distribution_policy: () -> Field
33
+ def equity_type: () -> Field
34
+ def exercise_style: () -> Field
35
+ def form: () -> Field
36
+ def further_grouping: () -> Field
37
+ def guarantee: () -> Field
38
+ def income: () -> Field
39
+ def interest_rate_type: () -> Field
40
+ def interest_type: () -> Field
41
+ def investment_strategy: () -> Field
42
+ def issuer_type: () -> Field
43
+ def long_short: () -> Field
44
+ def notional: () -> Field
45
+ def option_style: () -> Field
46
+ def ownership_restrictions: () -> Field
47
+ def payment_status: () -> Field
48
+ def redemption: () -> Field
49
+ def repayment: () -> Field
50
+ def return_trigger: () -> Field
51
+ def return_type: () -> Field
52
+ def security_type: () -> Field
53
+ def standardized: () -> Field
54
+ def strategy_style: () -> Field
55
+ def termination: () -> Field
56
+ def type: () -> Field
57
+ def underlying: () -> Field
58
+ def underlying_assets: () -> Field
59
+ def underlying_fund_type: () -> Field
60
+ def valuation: () -> Field
61
+ def voting_right: () -> Field
62
+ def warrant_type: () -> Field
63
+ def weighting: () -> Field
64
+
65
+ @fields: Array[Field]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ module SecID
2
+ class CFI < Base
3
+ # The decoded ISO 10962:2021 classification of a valid CFI, built by {CFI#decode}.
4
+ class Classification
5
+ attr_reader category: Field
6
+ attr_reader group: Field
7
+ attr_reader attributes: AttributeSet
8
+
9
+ def initialize: (String category_code, String group_code, Array[String] letters) -> void
10
+ def to_s: () -> String
11
+ def to_h: () -> Hash[Symbol, untyped]
12
+ def as_json: (*untyped) -> Hash[Symbol, untyped]
13
+
14
+ private
15
+
16
+ def build_category: (String code) -> Field
17
+ def build_group: (String category_code, String code) -> Field
18
+ def build_attributes: (String category_code, String group_code, Array[String] letters) -> AttributeSet
19
+ def build_attribute: (untyped position, String letter) -> Field
20
+ def decode_value: (untyped value_map, String letter) -> [Symbol, String]
21
+ end
22
+ end
23
+ end