Module: SecID::Suggestable

Included in:
CEI, CUSIP, DTI, FIGI, IBAN, ISIN, LEI, SEDOL, UPI
Defined in:
lib/sec_id/concerns/suggestable.rb

Overview

Repair engine for checksum-failing identifiers: enumerates plausible single-character edits (homoglyph/OCR substitutions and adjacent transpositions), keeps only those that re-validate, and returns them as confidence-ranked Suggestion objects.

Included by the 9 checksum types. The candidate space is the human-error net — HOMOGLYPHS plus adjacent transpositions — never a full coincidental net. valid? is the oracle, so no false candidate ever escapes; the HOMOGLYPHS table bounds recall.

Examples:

SecID::ISIN.suggest('US5949181O45')
#=> [#<SecID::Suggestion edit=:substitution ...>, #<SecID::Suggestion edit=:checksum ...>]

See Also:

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

HOMOGLYPHS =

Homoglyph / OCR confusion table (KTD5 seed): each character maps to the characters it is commonly mistyped as or misread from. A bidirectional expansion of the seed pairs 0/Q, 0/D, 1/I, 1/L, I/L, 2/Z, 5/S, 6/G, 8/B. Membership bounds recall — widen it (see benchmark/suggest_precision.rb) to raise it.

{
  '0' => %w[O Q D], 'O' => %w[0], 'Q' => %w[0], 'D' => %w[0],
  '1' => %w[I L], 'I' => %w[1 L], 'L' => %w[1 I],
  '2' => %w[Z], 'Z' => %w[2], '5' => %w[S], 'S' => %w[5],
  '6' => %w[G], 'G' => %w[6], '8' => %w[B], 'B' => %w[8]
}.freeze
RANK =

Rank weight per edit kind: body substitutions first, then adjacent transpositions, then the checksum-recompute fallback last (R6/R7).

{ substitution: 0, transposition: 1, checksum: 2 }.freeze

Instance Method Summary collapse

Instance Method Details

#suggestArray<Suggestion>

Enumerates plausible edits and returns the re-validating ones, confidence-ranked.

Returns:

  • (Array<Suggestion>)

    ranked candidates; empty when already valid or unparseable



53
54
55
56
57
58
59
# File 'lib/sec_id/concerns/suggestable.rb', line 53

def suggest
  return [] if valid?
  return [] unless valid_format?

  surviving = candidate_strings.uniq.map { |candidate| self.class.new(candidate) }.select(&:valid?)
  surviving.filter_map { |candidate| classify(candidate) }.sort_by { |suggestion| RANK.fetch(suggestion.edit) }
end