Module: Unitsdb::Commands::CheckSi::SiMatcher
- Defined in:
- lib/unitsdb/commands/check_si/si_matcher.rb
Overview
Matcher for SI digital-framework entities and UnitsDB entities.
All iteration is typed — entities expose identifiers, names,
short, references, and (for units/prefixes) symbols as
declared Lutaml attributes, so we read them directly.
Constant Summary collapse
- SI_AUTHORITY =
"si-digital-framework"- SYMBOL_ENTITY_TYPES =
%w[units prefixes].freeze
Class Attribute Summary collapse
-
.match_details ⇒ Object
Returns the value of attribute match_details.
Class Method Summary collapse
-
.find_entity_id(entity) ⇒ Object
UnitsDB top-level entities are identified by their first Identifier's id; if none is present, fall back to short.
- .find_matching_entities(entity_type, ttl_entity, db_entities) ⇒ Object
- .find_matching_prefixes(ttl_prefix, prefixes) ⇒ Object
- .find_matching_quantities(ttl_quantity, quantities) ⇒ Object
-
.find_matching_units(ttl_unit, units) ⇒ Object
---- Per-entity-type matchers (open for extension: add to MATCHERS to support a new type) ----.
-
.format_entity_name(entity) ⇒ Object
First localized name (LocalizedString instance) or nil.
-
.match_db_to_ttl(entity_type, ttl_entities, db_entities) ⇒ Object
Match database entities to TTL entities (to_si direction).
-
.match_entity_names?(entity_type, db_entity, ttl_entity) ⇒ Boolean
---- Detailed match (returns a hash with match metadata) ----.
-
.match_ttl_to_db(entity_type, ttl_entities, db_entities) ⇒ Object
Match TTL entities to database entities (from_si direction).
- .name_matches?(names, ttl_entity) ⇒ Boolean
- .name_matches_any?(names, ttl_entity, keys) ⇒ Boolean
-
.short_matches?(short, ttl_entity) ⇒ Boolean
---- Generic match primitives ----.
- .short_matches_any?(short, ttl_entity, keys) ⇒ Boolean
- .symbol_matches?(symbols, ttl_entity) ⇒ Boolean (also: prefix_symbol_matches?)
Class Attribute Details
.match_details ⇒ Object
Returns the value of attribute match_details.
15 16 17 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 15 def match_details @match_details end |
Class Method Details
.find_entity_id(entity) ⇒ Object
UnitsDB top-level entities are identified by their first Identifier's id; if none is present, fall back to short.
188 189 190 191 192 193 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 188 def find_entity_id(entity) identifier = entity.identifiers.first return identifier.id if identifier&.id entity.short end |
.find_matching_entities(entity_type, ttl_entity, db_entities) ⇒ Object
200 201 202 203 204 205 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 200 def find_matching_entities(entity_type, ttl_entity, db_entities) finder = MATCHERS[entity_type] return [] unless finder finder.call(ttl_entity, db_entities) end |
.find_matching_prefixes(ttl_prefix, prefixes) ⇒ Object
225 226 227 228 229 230 231 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 225 def find_matching_prefixes(ttl_prefix, prefixes) prefixes.select do |prefix| short_matches?(prefix.short, ttl_prefix) || name_matches?(prefix.names, ttl_prefix) || prefix_symbol_matches?(prefix.symbols, ttl_prefix) end.uniq end |
.find_matching_quantities(ttl_quantity, quantities) ⇒ Object
218 219 220 221 222 223 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 218 def find_matching_quantities(ttl_quantity, quantities) quantities.select do |quantity| short_matches_any?(quantity.short, ttl_quantity, i[name label alt_label]) || name_matches_any?(quantity.names, ttl_quantity, i[name label alt_label]) end.uniq end |
.find_matching_units(ttl_unit, units) ⇒ Object
---- Per-entity-type matchers (open for extension: add to MATCHERS to support a new type) ----
210 211 212 213 214 215 216 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 210 def find_matching_units(ttl_unit, units) units.select do |unit| short_matches?(unit.short, ttl_unit) || name_matches?(unit.names, ttl_unit) || symbol_matches?(unit.symbols, ttl_unit) end.uniq end |
.format_entity_name(entity) ⇒ Object
First localized name (LocalizedString instance) or nil.
196 197 198 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 196 def format_entity_name(entity) entity.names.first end |
.match_db_to_ttl(entity_type, ttl_entities, db_entities) ⇒ Object
Match database entities to TTL entities (to_si direction)
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 121 def match_db_to_ttl(entity_type, ttl_entities, db_entities) matches = [] missing_refs = [] matched_db_ids = [] processed_db_ids = {} nist_id_to_display = build_nist_id_to_display(entity_type, db_entities) db_entities.each do |db_entity| entity_id = find_entity_id(db_entity) display_id = nist_id_to_display[entity_id] || entity_id next if processed_db_ids[entity_id] processed_db_ids[entity_id] = true has_reference = false (db_entity.references || []).each do |ref| next unless ref. == SI_AUTHORITY has_reference = true ttl_entity = ttl_entities.find { |e| e[:uri] == ref.uri } matches << { entity_id: display_id, db_entity: db_entity, ttl_uri: ref.uri, ttl_entity: ttl_entity, } end if has_reference matched_db_ids << entity_id next end matching_ttl = [] match_types = {} ttl_entities.each do |ttl_entity| match_result = match_entity_names?(entity_type, db_entity, ttl_entity) next unless match_result[:match] matching_ttl << ttl_entity match_types[ttl_entity[:uri]] = match_result[:match_type] match_details["#{entity_id}:#{ttl_entity[:uri]}"] = match_result end next if matching_ttl.empty? matched_db_ids << entity_id missing_refs << { entity_id: display_id, db_entity: db_entity, ttl_entities: matching_ttl, match_types: match_types, } end unmatched_db = db_entities.reject do |entity| matched_db_ids.include?(find_entity_id(entity)) end [matches, missing_refs, unmatched_db] end |
.match_entity_names?(entity_type, db_entity, ttl_entity) ⇒ Boolean
---- Detailed match (returns a hash with match metadata) ----
275 276 277 278 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 275 def match_entity_names?(entity_type, db_entity, ttl_entity) matcher = DetailedMatcher.new(db_entity, ttl_entity, entity_type) matcher.call end |
.match_ttl_to_db(entity_type, ttl_entities, db_entities) ⇒ Object
Match TTL entities to database entities (from_si direction)
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 22 def match_ttl_to_db(entity_type, ttl_entities, db_entities) matches = [] missing_matches = [] matched_ttl_uris = [] processed_pairs = {} entity_matches = {} db_entities.each do |entity| references = entity.references || [] references.each do |ref| next unless ref. == SI_AUTHORITY matched_ttl_uris << ref.uri ttl_entity = ttl_entities.find { |e| e[:uri] == ref.uri } next unless ttl_entity matches << { entity_id: entity.short, entity_name: format_entity_name(entity), si_uri: ttl_entity[:uri], si_name: ttl_entity[:name], si_label: ttl_entity[:label], si_alt_label: ttl_entity[:alt_label], si_symbol: ttl_entity[:symbol], entity: entity, } end end ttl_entities.each do |ttl_entity| next if matched_ttl_uris.include?(ttl_entity[:uri]) matching_entities = find_matching_entities(entity_type, ttl_entity, db_entities) next if matching_entities.empty? matched_ttl_uris << ttl_entity[:uri] matching_entities.each do |entity| entity_id = entity.short pair_key = "#{entity_id}:#{ttl_entity[:uri]}" next if processed_pairs[pair_key] processed_pairs[pair_key] = true match_result = match_entity_names?(entity_type, entity, ttl_entity) next unless match_result[:match] match_details[pair_key] = match_result has_reference = (entity.references || []).any? do |ref| ref.uri == ttl_entity[:uri] && ref. == SI_AUTHORITY end match_data = { entity_id: entity_id, entity_name: format_entity_name(entity), si_uri: ttl_entity[:uri], si_name: ttl_entity[:name], si_label: ttl_entity[:label], si_alt_label: ttl_entity[:alt_label], si_symbol: ttl_entity[:symbol], entity: entity, match_type: match_result[:match_type], match_details: match_result, match_types: { ttl_entity[:uri] => match_result[:match_type] }, } if has_reference matches << match_data else entity_matches[entity_id] ||= [] entity_matches[entity_id] << { uri: ttl_entity[:uri], name: ttl_entity[:name], label: ttl_entity[:label], } missing_matches << match_data unless missing_matches.any? { |m| m[:entity_id] == entity_id } end end end missing_matches.each do |match| si_matches = entity_matches[match[:entity_id]] next unless si_matches && si_matches.size > 1 match[:multiple_si] = si_matches end unmatched_ttl = ttl_entities.reject do |entity| matched_ttl_uris.include?(entity[:uri]) || entity[:uri].end_with?("/units/") || entity[:uri].end_with?("/quantities/") || entity[:uri].end_with?("/prefixes/") end [matches, missing_matches, unmatched_ttl] end |
.name_matches?(names, ttl_entity) ⇒ Boolean
252 253 254 255 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 252 def name_matches?(names, ttl_entity) targets = [ttl_entity[:name]&.downcase, ttl_entity[:label]&.downcase].compact names.any? { |n| targets.include?(n.value&.downcase) } end |
.name_matches_any?(names, ttl_entity, keys) ⇒ Boolean
257 258 259 260 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 257 def name_matches_any?(names, ttl_entity, keys) targets = keys.filter_map { |k| ttl_entity[k]&.downcase } names.any? { |n| targets.include?(n.value&.downcase) } end |
.short_matches?(short, ttl_entity) ⇒ Boolean
---- Generic match primitives ----
241 242 243 244 245 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 241 def short_matches?(short, ttl_entity) target = ttl_entity[:name]&.downcase target_label = ttl_entity[:label]&.downcase short && [target, target_label].include?(short.downcase) end |
.short_matches_any?(short, ttl_entity, keys) ⇒ Boolean
247 248 249 250 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 247 def short_matches_any?(short, ttl_entity, keys) targets = keys.map { |k| ttl_entity[k]&.downcase } short && targets.include?(short.downcase) end |
.symbol_matches?(symbols, ttl_entity) ⇒ Boolean Also known as: prefix_symbol_matches?
262 263 264 265 266 267 268 |
# File 'lib/unitsdb/commands/check_si/si_matcher.rb', line 262 def symbol_matches?(symbols, ttl_entity) ttl_symbol = ttl_entity[:symbol] return false unless ttl_symbol needle = ttl_symbol.downcase symbols.any? { |s| s.ascii.to_s.downcase == needle } end |