unitsdb 2.2.1 → 2.2.4
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/.github/workflows/opal.yml +36 -0
- data/.gitignore +3 -0
- data/Gemfile +1 -0
- data/Rakefile +3 -1
- data/lib/unitsdb/cli.rb +5 -41
- data/lib/unitsdb/commands/_modify.rb +1 -34
- data/lib/unitsdb/commands/check_si/si_formatter.rb +6 -6
- data/lib/unitsdb/commands/check_si/si_matcher.rb +202 -292
- data/lib/unitsdb/commands/check_si/si_updater.rb +16 -36
- data/lib/unitsdb/commands/entity_presenter.rb +98 -0
- data/lib/unitsdb/commands/get.rb +16 -113
- data/lib/unitsdb/commands/qudt/formatter.rb +16 -27
- data/lib/unitsdb/commands/qudt/matcher.rb +18 -28
- data/lib/unitsdb/commands/qudt/updater.rb +8 -11
- data/lib/unitsdb/commands/qudt.rb +1 -34
- data/lib/unitsdb/commands/search.rb +33 -188
- data/lib/unitsdb/commands/thor.rb +41 -0
- data/lib/unitsdb/commands/ucum/formatter.rb +9 -18
- data/lib/unitsdb/commands/ucum/matcher.rb +4 -4
- data/lib/unitsdb/commands/ucum/updater.rb +3 -5
- data/lib/unitsdb/commands/ucum.rb +1 -34
- data/lib/unitsdb/commands/validate/qudt_references.rb +29 -70
- data/lib/unitsdb/commands/validate/references.rb +5 -303
- data/lib/unitsdb/commands/validate/si_references.rb +30 -66
- data/lib/unitsdb/commands/validate/ucum_references.rb +30 -64
- data/lib/unitsdb/commands/validate.rb +1 -36
- data/lib/unitsdb/commands.rb +2 -0
- data/lib/unitsdb/config.rb +170 -4
- data/lib/unitsdb/database/loader.rb +135 -0
- data/lib/unitsdb/database/reference_validator.rb +227 -0
- data/lib/unitsdb/database/uniqueness_validator.rb +80 -0
- data/lib/unitsdb/database.rb +127 -588
- data/lib/unitsdb/dimension.rb +2 -27
- data/lib/unitsdb/dimension_details.rb +2 -0
- data/lib/unitsdb/dimension_reference.rb +2 -0
- data/lib/unitsdb/dimensions.rb +2 -2
- data/lib/unitsdb/external_reference.rb +2 -0
- data/lib/unitsdb/identifier.rb +2 -0
- data/lib/unitsdb/localized_string.rb +2 -0
- data/lib/unitsdb/opal.rb +43 -0
- data/lib/unitsdb/prefix.rb +2 -13
- data/lib/unitsdb/prefix_reference.rb +2 -0
- data/lib/unitsdb/prefixes.rb +2 -2
- data/lib/unitsdb/quantities.rb +2 -1
- data/lib/unitsdb/quantity.rb +2 -2
- data/lib/unitsdb/quantity_reference.rb +2 -2
- data/lib/unitsdb/qudt.rb +5 -0
- data/lib/unitsdb/root_unit_reference.rb +2 -2
- data/lib/unitsdb/scale.rb +2 -2
- data/lib/unitsdb/scale_properties.rb +2 -1
- data/lib/unitsdb/scale_reference.rb +2 -0
- data/lib/unitsdb/scales.rb +2 -1
- data/lib/unitsdb/si_derived_base.rb +2 -1
- data/lib/unitsdb/symbol_presentations.rb +2 -2
- data/lib/unitsdb/ucum.rb +7 -0
- data/lib/unitsdb/unit.rb +2 -34
- data/lib/unitsdb/unit_reference.rb +2 -0
- data/lib/unitsdb/unit_system.rb +2 -2
- data/lib/unitsdb/unit_system_reference.rb +2 -0
- data/lib/unitsdb/unit_systems.rb +2 -2
- data/lib/unitsdb/units.rb +2 -2
- data/lib/unitsdb/version.rb +1 -1
- data/lib/unitsdb.rb +134 -27
- data/unitsdb.gemspec +1 -0
- metadata +23 -2
|
@@ -3,10 +3,18 @@
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
5
|
module CheckSi
|
|
6
|
-
# Matcher for SI entities and UnitsDB entities
|
|
6
|
+
# Matcher for SI digital-framework entities and UnitsDB entities.
|
|
7
|
+
# All iteration is typed — entities expose `identifiers`, `names`,
|
|
8
|
+
# `short`, `references`, and (for units/prefixes) `symbols` as
|
|
9
|
+
# declared Lutaml attributes, so we read them directly.
|
|
7
10
|
module SiMatcher
|
|
8
11
|
SI_AUTHORITY = "si-digital-framework"
|
|
9
|
-
|
|
12
|
+
SYMBOL_ENTITY_TYPES = %w[units prefixes].freeze
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
attr_accessor :match_details
|
|
16
|
+
end
|
|
17
|
+
self.match_details = {}
|
|
10
18
|
|
|
11
19
|
module_function
|
|
12
20
|
|
|
@@ -15,14 +23,12 @@ module Unitsdb
|
|
|
15
23
|
matches = []
|
|
16
24
|
missing_matches = []
|
|
17
25
|
matched_ttl_uris = []
|
|
18
|
-
processed_pairs = {}
|
|
19
|
-
entity_matches = {}
|
|
26
|
+
processed_pairs = {}
|
|
27
|
+
entity_matches = {}
|
|
20
28
|
|
|
21
|
-
# First pass: find direct references
|
|
22
29
|
db_entities.each do |entity|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
entity.references.each do |ref|
|
|
30
|
+
references = entity.references || []
|
|
31
|
+
references.each do |ref|
|
|
26
32
|
next unless ref.authority == SI_AUTHORITY
|
|
27
33
|
|
|
28
34
|
matched_ttl_uris << ref.uri
|
|
@@ -42,42 +48,33 @@ module Unitsdb
|
|
|
42
48
|
end
|
|
43
49
|
end
|
|
44
50
|
|
|
45
|
-
# Second pass: find matching entities
|
|
46
51
|
ttl_entities.each do |ttl_entity|
|
|
47
52
|
next if matched_ttl_uris.include?(ttl_entity[:uri])
|
|
48
53
|
|
|
49
|
-
matching_entities = find_matching_entities(entity_type, ttl_entity,
|
|
50
|
-
db_entities)
|
|
54
|
+
matching_entities = find_matching_entities(entity_type, ttl_entity, db_entities)
|
|
51
55
|
next if matching_entities.empty?
|
|
52
56
|
|
|
53
57
|
matched_ttl_uris << ttl_entity[:uri]
|
|
54
58
|
|
|
55
59
|
matching_entities.each do |entity|
|
|
56
60
|
entity_id = entity.short
|
|
57
|
-
entity_name = format_entity_name(entity)
|
|
58
|
-
|
|
59
|
-
# Create a unique key for this entity-ttl pair to avoid duplicates
|
|
60
61
|
pair_key = "#{entity_id}:#{ttl_entity[:uri]}"
|
|
61
62
|
next if processed_pairs[pair_key]
|
|
62
63
|
|
|
63
64
|
processed_pairs[pair_key] = true
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
match_result = match_entity_names?(entity_type, entity,
|
|
67
|
-
ttl_entity)
|
|
66
|
+
match_result = match_entity_names?(entity_type, entity, ttl_entity)
|
|
68
67
|
next unless match_result[:match]
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
@match_details[pair_key] = match_result
|
|
69
|
+
match_details[pair_key] = match_result
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
has_reference = entity.references&.any? do |ref|
|
|
71
|
+
has_reference = (entity.references || []).any? do |ref|
|
|
75
72
|
ref.uri == ttl_entity[:uri] && ref.authority == SI_AUTHORITY
|
|
76
73
|
end
|
|
77
74
|
|
|
78
75
|
match_data = {
|
|
79
76
|
entity_id: entity_id,
|
|
80
|
-
entity_name:
|
|
77
|
+
entity_name: format_entity_name(entity),
|
|
81
78
|
si_uri: ttl_entity[:uri],
|
|
82
79
|
si_name: ttl_entity[:name],
|
|
83
80
|
si_label: ttl_entity[:label],
|
|
@@ -92,35 +89,24 @@ module Unitsdb
|
|
|
92
89
|
if has_reference
|
|
93
90
|
matches << match_data
|
|
94
91
|
else
|
|
95
|
-
# Group by entity_id for multiple SI matches
|
|
96
92
|
entity_matches[entity_id] ||= []
|
|
97
93
|
entity_matches[entity_id] << {
|
|
98
94
|
uri: ttl_entity[:uri],
|
|
99
95
|
name: ttl_entity[:name],
|
|
100
96
|
label: ttl_entity[:label],
|
|
101
97
|
}
|
|
102
|
-
|
|
103
|
-
# Add first occurrence of this entity to missing_matches
|
|
104
|
-
missing_matches << match_data unless missing_matches.any? do |m|
|
|
105
|
-
m[:entity_id] == entity_id
|
|
106
|
-
end
|
|
98
|
+
missing_matches << match_data unless missing_matches.any? { |m| m[:entity_id] == entity_id }
|
|
107
99
|
end
|
|
108
100
|
end
|
|
109
101
|
end
|
|
110
102
|
|
|
111
|
-
# Update missing_matches to include multiple SI entities
|
|
112
103
|
missing_matches.each do |match|
|
|
113
|
-
|
|
114
|
-
si_matches
|
|
104
|
+
si_matches = entity_matches[match[:entity_id]]
|
|
105
|
+
next unless si_matches && si_matches.size > 1
|
|
115
106
|
|
|
116
|
-
|
|
117
|
-
if si_matches && si_matches.size > 1
|
|
118
|
-
match[:multiple_si] =
|
|
119
|
-
si_matches
|
|
120
|
-
end
|
|
107
|
+
match[:multiple_si] = si_matches
|
|
121
108
|
end
|
|
122
109
|
|
|
123
|
-
# Find unmatched TTL entities
|
|
124
110
|
unmatched_ttl = ttl_entities.reject do |entity|
|
|
125
111
|
matched_ttl_uris.include?(entity[:uri]) ||
|
|
126
112
|
entity[:uri].end_with?("/units/") ||
|
|
@@ -136,78 +122,49 @@ module Unitsdb
|
|
|
136
122
|
matches = []
|
|
137
123
|
missing_refs = []
|
|
138
124
|
matched_db_ids = []
|
|
139
|
-
processed_db_ids = {}
|
|
125
|
+
processed_db_ids = {}
|
|
140
126
|
|
|
141
|
-
|
|
142
|
-
nist_id_to_display = {}
|
|
143
|
-
|
|
144
|
-
# Build mappings for each entity type
|
|
145
|
-
db_entities.each do |entity|
|
|
146
|
-
next unless entity.respond_to?(:identifiers) && entity.identifiers&.first&.id&.start_with?("NIST")
|
|
147
|
-
|
|
148
|
-
nist_id = entity.identifiers.first.id
|
|
149
|
-
|
|
150
|
-
# For quantities and prefixes, we want to show the "short" field
|
|
151
|
-
nist_id_to_display[nist_id] = entity.short if %w[quantities
|
|
152
|
-
prefixes].include?(entity_type) && entity.respond_to?(:short)
|
|
153
|
-
end
|
|
127
|
+
nist_id_to_display = build_nist_id_to_display(entity_type, db_entities)
|
|
154
128
|
|
|
155
129
|
db_entities.each do |db_entity|
|
|
156
130
|
entity_id = find_entity_id(db_entity)
|
|
131
|
+
display_id = nist_id_to_display[entity_id] || entity_id
|
|
157
132
|
|
|
158
|
-
# For display purposes - use original display names
|
|
159
|
-
display_id = entity_id
|
|
160
|
-
|
|
161
|
-
# Apply the NIST ID mapping if available
|
|
162
|
-
display_id = nist_id_to_display[entity_id] if entity_id.start_with?("NIST") && nist_id_to_display[entity_id]
|
|
163
|
-
|
|
164
|
-
# Skip if we've already processed this entity
|
|
165
133
|
next if processed_db_ids[entity_id]
|
|
166
134
|
|
|
167
135
|
processed_db_ids[entity_id] = true
|
|
168
|
-
has_reference = false
|
|
169
136
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
next unless ref.authority == SI_AUTHORITY
|
|
174
|
-
|
|
175
|
-
has_reference = true
|
|
176
|
-
# Find the matching TTL entity for display
|
|
177
|
-
ttl_entity = ttl_entities.find { |e| e[:uri] == ref.uri }
|
|
137
|
+
has_reference = false
|
|
138
|
+
(db_entity.references || []).each do |ref|
|
|
139
|
+
next unless ref.authority == SI_AUTHORITY
|
|
178
140
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
141
|
+
has_reference = true
|
|
142
|
+
ttl_entity = ttl_entities.find { |e| e[:uri] == ref.uri }
|
|
143
|
+
matches << {
|
|
144
|
+
entity_id: display_id,
|
|
145
|
+
db_entity: db_entity,
|
|
146
|
+
ttl_uri: ref.uri,
|
|
147
|
+
ttl_entity: ttl_entity,
|
|
148
|
+
}
|
|
186
149
|
end
|
|
187
150
|
|
|
188
|
-
# If already has reference, continue to next entity
|
|
189
151
|
if has_reference
|
|
190
152
|
matched_db_ids << entity_id
|
|
191
153
|
next
|
|
192
154
|
end
|
|
193
155
|
|
|
194
|
-
# Find matching TTL entities
|
|
195
156
|
matching_ttl = []
|
|
196
157
|
match_types = {}
|
|
197
158
|
|
|
198
159
|
ttl_entities.each do |ttl_entity|
|
|
199
|
-
match_result = match_entity_names?(entity_type, db_entity,
|
|
200
|
-
ttl_entity)
|
|
160
|
+
match_result = match_entity_names?(entity_type, db_entity, ttl_entity)
|
|
201
161
|
next unless match_result[:match]
|
|
202
162
|
|
|
203
163
|
matching_ttl << ttl_entity
|
|
204
164
|
match_types[ttl_entity[:uri]] = match_result[:match_type]
|
|
205
|
-
|
|
206
|
-
# Save detailed match info
|
|
207
|
-
@match_details["#{entity_id}:#{ttl_entity[:uri]}"] = match_result
|
|
165
|
+
match_details["#{entity_id}:#{ttl_entity[:uri]}"] = match_result
|
|
208
166
|
end
|
|
209
167
|
|
|
210
|
-
# If found matches, add to missing_refs
|
|
211
168
|
next if matching_ttl.empty?
|
|
212
169
|
|
|
213
170
|
matched_db_ids << entity_id
|
|
@@ -219,7 +176,6 @@ module Unitsdb
|
|
|
219
176
|
}
|
|
220
177
|
end
|
|
221
178
|
|
|
222
|
-
# Find unmatched db entities
|
|
223
179
|
unmatched_db = db_entities.reject do |entity|
|
|
224
180
|
matched_db_ids.include?(find_entity_id(entity))
|
|
225
181
|
end
|
|
@@ -227,260 +183,214 @@ module Unitsdb
|
|
|
227
183
|
[matches, missing_refs, unmatched_db]
|
|
228
184
|
end
|
|
229
185
|
|
|
230
|
-
#
|
|
186
|
+
# UnitsDB top-level entities are identified by their first
|
|
187
|
+
# Identifier's id; if none is present, fall back to short.
|
|
231
188
|
def find_entity_id(entity)
|
|
232
|
-
|
|
233
|
-
return
|
|
234
|
-
entity.identifiers.first.respond_to?(:id)
|
|
189
|
+
identifier = entity.identifiers.first
|
|
190
|
+
return identifier.id if identifier&.id
|
|
235
191
|
|
|
236
192
|
entity.short
|
|
237
193
|
end
|
|
238
194
|
|
|
239
|
-
#
|
|
195
|
+
# First localized name (LocalizedString instance) or nil.
|
|
240
196
|
def format_entity_name(entity)
|
|
241
|
-
return nil unless entity.respond_to?(:names) && entity.names&.first
|
|
242
|
-
|
|
243
197
|
entity.names.first
|
|
244
|
-
|
|
245
|
-
# # Special handling for sidereal names - use comma format
|
|
246
|
-
# if name.include?("sidereal")
|
|
247
|
-
# if name.start_with?("sidereal ")
|
|
248
|
-
# # For names that already start with "sidereal " - strip it
|
|
249
|
-
# base_name = name.gsub("sidereal ", "")
|
|
250
|
-
# return "#{base_name}, sidereal"
|
|
251
|
-
# elsif name.end_with?(" sidereal")
|
|
252
|
-
# # For names that already have comma format but missing comma
|
|
253
|
-
# parts = name.split
|
|
254
|
-
# return "#{parts.first}, #{parts.last}"
|
|
255
|
-
# end
|
|
256
|
-
# end
|
|
257
|
-
|
|
258
|
-
# # Handle other special cases
|
|
259
|
-
# return name if name == "year (365 days)"
|
|
260
|
-
|
|
261
|
-
# # Default to the original name
|
|
262
198
|
end
|
|
263
199
|
|
|
264
|
-
# Find matching entities for a TTL entity
|
|
265
200
|
def find_matching_entities(entity_type, ttl_entity, db_entities)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
find_matching_quantities(ttl_entity, db_entities)
|
|
271
|
-
when "prefixes"
|
|
272
|
-
find_matching_prefixes(ttl_entity, db_entities)
|
|
273
|
-
else
|
|
274
|
-
[]
|
|
275
|
-
end
|
|
201
|
+
finder = MATCHERS[entity_type]
|
|
202
|
+
return [] unless finder
|
|
203
|
+
|
|
204
|
+
finder.call(ttl_entity, db_entities)
|
|
276
205
|
end
|
|
277
206
|
|
|
278
|
-
#
|
|
207
|
+
# ---- Per-entity-type matchers (open for extension: add to
|
|
208
|
+
# MATCHERS to support a new type) ----
|
|
209
|
+
|
|
279
210
|
def find_matching_units(ttl_unit, units)
|
|
280
|
-
|
|
211
|
+
units.select do |unit|
|
|
212
|
+
short_matches?(unit.short, ttl_unit) ||
|
|
213
|
+
name_matches?(unit.names, ttl_unit) ||
|
|
214
|
+
symbol_matches?(unit.symbols, ttl_unit)
|
|
215
|
+
end.uniq
|
|
216
|
+
end
|
|
281
217
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
end
|
|
218
|
+
def find_matching_quantities(ttl_quantity, quantities)
|
|
219
|
+
quantities.select do |quantity|
|
|
220
|
+
short_matches_any?(quantity.short, ttl_quantity, %i[name label alt_label]) ||
|
|
221
|
+
name_matches_any?(quantity.names, ttl_quantity, %i[name label alt_label])
|
|
222
|
+
end.uniq
|
|
223
|
+
end
|
|
289
224
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
end
|
|
225
|
+
def find_matching_prefixes(ttl_prefix, prefixes)
|
|
226
|
+
prefixes.select do |prefix|
|
|
227
|
+
short_matches?(prefix.short, ttl_prefix) ||
|
|
228
|
+
name_matches?(prefix.names, ttl_prefix) ||
|
|
229
|
+
prefix_symbol_matches?(prefix.symbols, ttl_prefix)
|
|
230
|
+
end.uniq
|
|
231
|
+
end
|
|
298
232
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
233
|
+
MATCHERS = {
|
|
234
|
+
"units" => method(:find_matching_units),
|
|
235
|
+
"quantities" => method(:find_matching_quantities),
|
|
236
|
+
"prefixes" => method(:find_matching_prefixes),
|
|
237
|
+
}.freeze
|
|
303
238
|
|
|
304
|
-
|
|
305
|
-
end
|
|
239
|
+
# ---- Generic match primitives ----
|
|
306
240
|
|
|
307
|
-
|
|
241
|
+
def short_matches?(short, ttl_entity)
|
|
242
|
+
target = ttl_entity[:name]&.downcase
|
|
243
|
+
target_label = ttl_entity[:label]&.downcase
|
|
244
|
+
short && [target, target_label].include?(short.downcase)
|
|
308
245
|
end
|
|
309
246
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
quantities.each do |quantity|
|
|
315
|
-
# Match by short
|
|
316
|
-
if quantity.short&.downcase == ttl_quantity[:name]&.downcase ||
|
|
317
|
-
quantity.short&.downcase == ttl_quantity[:label]&.downcase ||
|
|
318
|
-
quantity.short&.downcase == ttl_quantity[:alt_label]&.downcase
|
|
319
|
-
matching_quantities << quantity
|
|
320
|
-
next
|
|
321
|
-
end
|
|
247
|
+
def short_matches_any?(short, ttl_entity, keys)
|
|
248
|
+
targets = keys.map { |k| ttl_entity[k]&.downcase }
|
|
249
|
+
short && targets.include?(short.downcase)
|
|
250
|
+
end
|
|
322
251
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
name.downcase == ttl_quantity[:alt_label]&.downcase
|
|
328
|
-
end
|
|
252
|
+
def name_matches?(names, ttl_entity)
|
|
253
|
+
targets = [ttl_entity[:name]&.downcase, ttl_entity[:label]&.downcase].compact
|
|
254
|
+
names.any? { |n| targets.include?(n.value&.downcase) }
|
|
255
|
+
end
|
|
329
256
|
|
|
330
|
-
|
|
331
|
-
|
|
257
|
+
def name_matches_any?(names, ttl_entity, keys)
|
|
258
|
+
targets = keys.filter_map { |k| ttl_entity[k]&.downcase }
|
|
259
|
+
names.any? { |n| targets.include?(n.value&.downcase) }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def symbol_matches?(symbols, ttl_entity)
|
|
263
|
+
ttl_symbol = ttl_entity[:symbol]
|
|
264
|
+
return false unless ttl_symbol
|
|
332
265
|
|
|
333
|
-
|
|
266
|
+
needle = ttl_symbol.downcase
|
|
267
|
+
symbols.any? { |s| s.ascii.to_s.downcase == needle }
|
|
334
268
|
end
|
|
335
269
|
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
matching_prefixes = []
|
|
270
|
+
# Prefixes in 2.0 carry a `symbols` collection, just like Units.
|
|
271
|
+
alias prefix_symbol_matches? symbol_matches?
|
|
339
272
|
|
|
340
|
-
|
|
341
|
-
# Match by short
|
|
342
|
-
if prefix.short&.downcase == ttl_prefix[:name]&.downcase ||
|
|
343
|
-
prefix.short&.downcase == ttl_prefix[:label]&.downcase
|
|
344
|
-
matching_prefixes << prefix
|
|
345
|
-
next
|
|
346
|
-
end
|
|
273
|
+
# ---- Detailed match (returns a hash with match metadata) ----
|
|
347
274
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
end
|
|
353
|
-
matching_prefixes << prefix
|
|
354
|
-
next
|
|
355
|
-
end
|
|
275
|
+
def match_entity_names?(entity_type, db_entity, ttl_entity)
|
|
276
|
+
matcher = DetailedMatcher.new(db_entity, ttl_entity, entity_type)
|
|
277
|
+
matcher.call
|
|
278
|
+
end
|
|
356
279
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
280
|
+
# Encapsulates the per-entity detailed match strategies.
|
|
281
|
+
class DetailedMatcher
|
|
282
|
+
def initialize(db_entity, ttl_entity, entity_type)
|
|
283
|
+
@db_entity = db_entity
|
|
284
|
+
@ttl = ttl_entity
|
|
285
|
+
@entity_type = entity_type
|
|
286
|
+
end
|
|
361
287
|
|
|
362
|
-
|
|
288
|
+
def call
|
|
289
|
+
short_to_name || short_to_label || name_to_name ||
|
|
290
|
+
name_to_label || name_to_alt_label || sidereal_demotion ||
|
|
291
|
+
symbol_potential || NO_MATCH
|
|
363
292
|
end
|
|
364
293
|
|
|
365
|
-
|
|
366
|
-
end
|
|
294
|
+
NO_MATCH = { match: false }.freeze
|
|
367
295
|
|
|
368
|
-
|
|
369
|
-
def match_entity_names?(entity_type, db_entity, ttl_entity)
|
|
370
|
-
match_details = { match: false }
|
|
296
|
+
private
|
|
371
297
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
298
|
+
def short_to_name
|
|
299
|
+
return unless @db_entity.short&.downcase == @ttl[:name]&.downcase
|
|
300
|
+
|
|
301
|
+
exact_match("short_to_name",
|
|
302
|
+
"UnitsDB short '#{@db_entity.short}' matches SI name '#{@ttl[:name]}'")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def short_to_label
|
|
306
|
+
return unless @db_entity.short && @ttl[:label] &&
|
|
307
|
+
@db_entity.short.downcase == @ttl[:label].downcase
|
|
308
|
+
|
|
309
|
+
exact_match("short_to_label",
|
|
310
|
+
"UnitsDB short '#{@db_entity.short}' matches SI label '#{@ttl[:label]}'")
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def name_to_name
|
|
314
|
+
db_name = find_name_match(@ttl[:name])
|
|
315
|
+
return unless db_name
|
|
316
|
+
|
|
317
|
+
exact_match("name_to_name",
|
|
318
|
+
"UnitsDB name '#{db_name}' matches SI name '#{@ttl[:name]}'")
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def name_to_label
|
|
322
|
+
return unless @ttl[:label]
|
|
323
|
+
|
|
324
|
+
db_name = find_name_match(@ttl[:label])
|
|
325
|
+
return unless db_name
|
|
326
|
+
|
|
327
|
+
exact_match("name_to_label",
|
|
328
|
+
"UnitsDB name '#{db_name}' matches SI label '#{@ttl[:label]}'")
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def name_to_alt_label
|
|
332
|
+
return unless @ttl[:alt_label]
|
|
333
|
+
|
|
334
|
+
db_name = find_name_match(@ttl[:alt_label])
|
|
335
|
+
return unless db_name
|
|
336
|
+
|
|
337
|
+
exact_match("name_to_alt_label",
|
|
338
|
+
"UnitsDB name '#{db_name}' matches SI alt_label '#{@ttl[:alt_label]}'")
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# A `sidereal_*` short counts as a partial match unless the
|
|
342
|
+
# TTL name/label acknowledges the sidereal form.
|
|
343
|
+
def sidereal_demotion
|
|
344
|
+
prior = short_to_name || short_to_label
|
|
345
|
+
return unless prior && prior[:exact]
|
|
346
|
+
return unless @db_entity.short&.include?("sidereal_")
|
|
347
|
+
return if @ttl[:name]&.include?("sidereal") || @ttl[:label]&.include?("sidereal")
|
|
348
|
+
|
|
349
|
+
potential_match("partial_match",
|
|
350
|
+
"UnitsDB '#{@db_entity.short}' partially matches SI '#{@ttl[:name]}'")
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def symbol_potential
|
|
354
|
+
return unless SYMBOL_ENTITY_TYPES.include?(@entity_type)
|
|
355
|
+
return unless @ttl[:symbol]
|
|
356
|
+
|
|
357
|
+
needle = @ttl[:symbol].downcase
|
|
358
|
+
match = @db_entity.symbols.find { |s| s.ascii.to_s.downcase == needle }
|
|
359
|
+
return unless match
|
|
360
|
+
|
|
361
|
+
potential_match("symbol_match",
|
|
362
|
+
"UnitsDB symbol '#{match.ascii}' matches SI symbol '#{@ttl[:symbol]}'")
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def find_name_match(ttl_value)
|
|
366
|
+
return unless ttl_value
|
|
367
|
+
|
|
368
|
+
needle = ttl_value.downcase
|
|
369
|
+
@db_entity.names.find { |n| n.value&.downcase == needle }
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def exact_match(desc, details)
|
|
373
|
+
{
|
|
384
374
|
match: true,
|
|
385
375
|
exact: true,
|
|
386
376
|
match_type: "Exact match",
|
|
387
|
-
match_desc:
|
|
388
|
-
details:
|
|
377
|
+
match_desc: desc,
|
|
378
|
+
details: details,
|
|
389
379
|
}
|
|
390
|
-
# Match by names - EXACT match
|
|
391
|
-
elsif db_entity.respond_to?(:names) && db_entity.names
|
|
392
|
-
# Match by TTL name
|
|
393
|
-
db_name_match = db_entity.names.find do |name|
|
|
394
|
-
name.downcase == ttl_entity[:name].downcase
|
|
395
|
-
end
|
|
396
|
-
if db_name_match
|
|
397
|
-
match_details = {
|
|
398
|
-
match: true,
|
|
399
|
-
exact: true,
|
|
400
|
-
match_type: "Exact match",
|
|
401
|
-
match_desc: "name_to_name",
|
|
402
|
-
details: "UnitsDB name '#{db_name_match}' matches SI name '#{ttl_entity[:name]}'",
|
|
403
|
-
}
|
|
404
|
-
# Match by TTL label
|
|
405
|
-
elsif ttl_entity[:label]
|
|
406
|
-
db_name_match = db_entity.names.find do |name|
|
|
407
|
-
name.downcase == ttl_entity[:label].downcase
|
|
408
|
-
end
|
|
409
|
-
if db_name_match
|
|
410
|
-
match_details = {
|
|
411
|
-
match: true,
|
|
412
|
-
exact: true,
|
|
413
|
-
match_type: "Exact match",
|
|
414
|
-
match_desc: "name_to_label",
|
|
415
|
-
details: "UnitsDB name '#{db_name_match}' matches SI label '#{ttl_entity[:label]}'",
|
|
416
|
-
}
|
|
417
|
-
end
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
# Match by TTL alt_label
|
|
421
|
-
if !match_details[:match] && ttl_entity[:alt_label]
|
|
422
|
-
db_name_match = db_entity.names.find do |name|
|
|
423
|
-
name.downcase == ttl_entity[:alt_label].downcase
|
|
424
|
-
end
|
|
425
|
-
if db_name_match
|
|
426
|
-
match_details = {
|
|
427
|
-
match: true,
|
|
428
|
-
exact: true,
|
|
429
|
-
match_type: "Exact match",
|
|
430
|
-
match_desc: "name_to_alt_label",
|
|
431
|
-
details: "UnitsDB name '#{db_name_match}' matches SI alt_label '#{ttl_entity[:alt_label]}'",
|
|
432
|
-
}
|
|
433
|
-
end
|
|
434
|
-
end
|
|
435
380
|
end
|
|
436
381
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
!(ttl_entity[:name]&.include?("sidereal") || ttl_entity[:label]&.include?("sidereal"))
|
|
440
|
-
match_details = {
|
|
382
|
+
def potential_match(desc, details)
|
|
383
|
+
{
|
|
441
384
|
match: true,
|
|
442
385
|
exact: false,
|
|
443
386
|
match_type: "Potential match",
|
|
444
|
-
match_desc:
|
|
445
|
-
details:
|
|
387
|
+
match_desc: desc,
|
|
388
|
+
details: details,
|
|
446
389
|
}
|
|
447
390
|
end
|
|
448
|
-
|
|
449
|
-
# Match by symbol if available (units and prefixes) - POTENTIAL match
|
|
450
|
-
if !match_details[:match] && %w[units
|
|
451
|
-
prefixes].include?(entity_type) && ttl_entity[:symbol]
|
|
452
|
-
if entity_type == "units" && db_entity.respond_to?(:symbols) && db_entity.symbols
|
|
453
|
-
matching_symbol = db_entity.symbols.find do |sym|
|
|
454
|
-
sym.respond_to?(:ascii) && sym.ascii && sym.ascii.downcase == ttl_entity[:symbol].downcase
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
if matching_symbol
|
|
458
|
-
match_details = {
|
|
459
|
-
match: true,
|
|
460
|
-
exact: false,
|
|
461
|
-
match_type: "Potential match",
|
|
462
|
-
match_desc: "symbol_match",
|
|
463
|
-
details: "UnitsDB symbol '#{matching_symbol.ascii}' matches SI symbol '#{ttl_entity[:symbol]}'",
|
|
464
|
-
}
|
|
465
|
-
end
|
|
466
|
-
elsif entity_type == "prefixes" && db_entity.respond_to?(:symbol) && db_entity.symbol
|
|
467
|
-
if db_entity.symbol.respond_to?(:ascii) &&
|
|
468
|
-
db_entity.symbol.ascii &&
|
|
469
|
-
db_entity.symbol.ascii.downcase == ttl_entity[:symbol].downcase
|
|
470
|
-
|
|
471
|
-
match_details = {
|
|
472
|
-
match: true,
|
|
473
|
-
exact: false,
|
|
474
|
-
match_type: "Potential match",
|
|
475
|
-
match_desc: "symbol_match",
|
|
476
|
-
details: "UnitsDB symbol '#{db_entity.symbol.ascii}' matches SI symbol '#{ttl_entity[:symbol]}'",
|
|
477
|
-
}
|
|
478
|
-
end
|
|
479
|
-
end
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
match_details
|
|
483
391
|
end
|
|
392
|
+
|
|
393
|
+
private_constant :DetailedMatcher, :MATCHERS
|
|
484
394
|
end
|
|
485
395
|
end
|
|
486
396
|
end
|