Class: ActiveSanction::Name
- Inherits:
-
Object
- Object
- ActiveSanction::Name
- Extended by:
- T::Sig
- Defined in:
- lib/active_sanction/name.rb
Overview
A single name variant attached to an entity. Entities routinely carry more aliases than primary names -- OFAC ships 19,321 primary names against 20,147 aliases -- so an alias is the common case, not the exception.
ActiveSanction::Name.new(
value: "AERO-CARIBBEAN",
kind: :aka,
quality: :good,
script: :latin
)
A pure data holder: it stores what the publisher said and nothing more. The normalized and phonetic forms a search actually compares against are built by the normalizer (#26) and the indexer (#31), which need the untouched original to work from.
Instances are frozen on construction and compare by value.
Constant Summary collapse
- KINDS =
OFAC's ALT.CSV supplies
alt_typeas aka / fka / nka directly, and the distinction matters downstream: a former name (fka) is still a real hit, but ranking it identically to a currently-used one costs precision. T.let(%i[primary aka fka nka].freeze, T::Array[Symbol])
- QUALITIES =
The UN consolidated list grades each alias Good or Low. A Low alias is a weaker signal -- the scorer (#32) penalizes it -- so the grade has to survive parsing rather than being flattened away here. Every other source publishes no grade at all, which is
nil: unstated, not good. T.let(%i[good low].freeze, T::Array[Symbol])
- SCRIPTS =
The writing system
valueis published in, which is what tells the normalizer (#26) which transliteration path to take -- a Cyrillic name folded by the Latin rules comes out as noise. Adapters map their source's own vocabulary onto these: OFAC labels some names by language rather than script, so "Farsi" arrives here as :arabic.Closed, so a typo is caught at the boundary instead of quietly minting a script nothing downstream handles. It is sized to what the lists actually publish rather than to all ~200 of ISO 15924; a source shipping one we have not seen is a one-line addition here, which the raised message asks for by name.
T.let(%i[ latin cyrillic arabic hebrew greek han kana hangul thai devanagari bengali tamil myanmar khmer armenian georgian ethiopic syriac ].freeze, T::Array[Symbol])
Instance Attribute Summary collapse
- #kind ⇒ Symbol readonly
-
#quality ⇒ Symbol?
readonly
nil where the source publishes no grade, which is every source but the UN.
- #script ⇒ Symbol? readonly
-
#value ⇒ String
readonly
The publisher's own string, stripped of surrounding whitespace and otherwise untouched.
Class Method Summary collapse
-
.from_h(hash) ⇒ T.attached_class
Rebuilds a name from #to_h output.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
(also: #eql?)
Class is part of the comparison to keep #== and #hash agreeing, which is what Hash and Set rely on.
-
#alias? ⇒ Boolean
Every kind except :primary.
- #hash ⇒ Integer
-
#initialize(value:, kind: :primary, quality: nil, script: nil) ⇒ void
constructor
kinddefaults to :primary because that is what a source with no alias data at all means: Canada publishes no aliases, so every Canadian name is a primary one. - #inspect ⇒ String
-
#low_quality? ⇒ Boolean
nil quality is not low quality: only the UN grades aliases, so an ungraded name must not be penalized for a field its source never publishes.
- #primary? ⇒ Boolean
- #to_h ⇒ Hash{Symbol => T.untyped}
- #to_s ⇒ String
Constructor Details
#initialize(value:, kind: :primary, quality: nil, script: nil) ⇒ void
kind defaults to :primary because that is what a source with no alias
data at all means: Canada publishes no aliases, so every Canadian name is
a primary one.
Untyped on purpose, and the same choice Entity makes: every one of these is the publisher's text arriving as whatever the parser made of it. The coercions below say what happens to it, in messages written for whoever has to fix the record.
105 106 107 108 109 110 111 |
# File 'lib/active_sanction/name.rb', line 105 def initialize(value:, kind: :primary, quality: nil, script: nil) @value = T.let(value!(value), String) @kind = T.let(enum!(:kind, kind), Symbol) @quality = T.let(quality.nil? ? nil : enum!(:quality, quality), T.nilable(Symbol)) @script = T.let(script.nil? ? nil : enum!(:script, script), T.nilable(Symbol)) freeze end |
Instance Attribute Details
#kind ⇒ Symbol (readonly)
69 70 71 |
# File 'lib/active_sanction/name.rb', line 69 def kind @kind end |
#quality ⇒ Symbol? (readonly)
nil where the source publishes no grade, which is every source but the UN. See #low_quality?: unstated is not low.
74 75 76 |
# File 'lib/active_sanction/name.rb', line 74 def quality @quality end |
#script ⇒ Symbol? (readonly)
77 78 79 |
# File 'lib/active_sanction/name.rb', line 77 def script @script end |
#value ⇒ String (readonly)
The publisher's own string, stripped of surrounding whitespace and otherwise untouched.
66 67 68 |
# File 'lib/active_sanction/name.rb', line 66 def value @value end |
Class Method Details
.from_h(hash) ⇒ T.attached_class
Rebuilds a name from #to_h output. Accepts string keys and string values for the enum members, so a name that has been through JSON round-trips without a separate coercion step.
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/active_sanction/name.rb', line 83 def self.from_h(hash) attributes = hash.to_h.transform_keys(&:to_sym) unknown = attributes.keys - MEMBERS raise InvalidArgument, "unknown Name attribute(s): #{unknown.join(", ")}" if unknown.any? # `new(**hash)` past a required keyword parameter is one of the few # things Sorbet cannot check statically. #initialize validates what # arrives, which is where a bad round-trip is caught. T.unsafe(self).new(**attributes) end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Class is part of the comparison to keep #== and #hash agreeing, which is what Hash and Set rely on.
139 140 141 142 143 |
# File 'lib/active_sanction/name.rb', line 139 def ==(other) return false unless other.instance_of?(self.class) to_h == other.to_h end |
#alias? ⇒ Boolean
Every kind except :primary. Reads better at call sites than !primary?
and keeps the definition of "alias" in one place if a kind is ever added.
119 |
# File 'lib/active_sanction/name.rb', line 119 def alias? = !primary? |
#hash ⇒ Integer
147 148 149 |
# File 'lib/active_sanction/name.rb', line 147 def hash [self.class, to_h].hash end |
#inspect ⇒ String
152 153 154 |
# File 'lib/active_sanction/name.rb', line 152 def inspect "#<#{self.class} #{value.inspect} kind=#{kind.inspect}#{" quality=#{quality.inspect}" if quality}>" end |
#low_quality? ⇒ Boolean
nil quality is not low quality: only the UN grades aliases, so an ungraded
name must not be penalized for a field its source never publishes.
Compared by identity because symbols are interned and quality is
nilable: nil == :low is a call on NilClass, which Sorbet will not make.
126 |
# File 'lib/active_sanction/name.rb', line 126 def low_quality? = quality.equal?(:low) |
#primary? ⇒ Boolean
114 |
# File 'lib/active_sanction/name.rb', line 114 def primary? = kind == :primary |
#to_h ⇒ Hash{Symbol => T.untyped}
129 130 131 |
# File 'lib/active_sanction/name.rb', line 129 def to_h { value: value, kind: kind, quality: quality, script: script } end |
#to_s ⇒ String
134 |
# File 'lib/active_sanction/name.rb', line 134 def to_s = value |