unitsdb 2.1.1 → 2.2.1
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/release.yml +8 -1
- data/.gitignore +2 -0
- data/.gitmodules +4 -3
- data/.rubocop.yml +13 -8
- data/.rubocop_todo.yml +217 -100
- data/CLAUDE.md +55 -0
- data/Gemfile +4 -1
- data/README.adoc +283 -16
- data/data/dimensions.yaml +1864 -0
- data/data/prefixes.yaml +874 -0
- data/data/quantities.yaml +3715 -0
- data/data/scales.yaml +97 -0
- data/data/schemas/dimensions-schema.yaml +153 -0
- data/data/schemas/prefixes-schema.yaml +155 -0
- data/data/schemas/quantities-schema.yaml +117 -0
- data/data/schemas/scales-schema.yaml +106 -0
- data/data/schemas/unit_systems-schema.yaml +116 -0
- data/data/schemas/units-schema.yaml +215 -0
- data/data/unit_systems.yaml +78 -0
- data/data/units.yaml +14052 -0
- data/exe/unitsdb +7 -1
- data/lib/unitsdb/cli.rb +42 -15
- data/lib/unitsdb/commands/_modify.rb +40 -4
- data/lib/unitsdb/commands/base.rb +6 -2
- data/lib/unitsdb/commands/check_si/si_formatter.rb +488 -0
- data/lib/unitsdb/commands/check_si/si_matcher.rb +487 -0
- data/lib/unitsdb/commands/check_si/si_ttl_parser.rb +103 -0
- data/lib/unitsdb/commands/check_si/si_updater.rb +254 -0
- data/lib/unitsdb/commands/check_si.rb +54 -35
- data/lib/unitsdb/commands/get.rb +11 -10
- data/lib/unitsdb/commands/normalize.rb +21 -7
- data/lib/unitsdb/commands/qudt/check.rb +150 -0
- data/lib/unitsdb/commands/qudt/formatter.rb +194 -0
- data/lib/unitsdb/commands/qudt/matcher.rb +746 -0
- data/lib/unitsdb/commands/qudt/ttl_parser.rb +403 -0
- data/lib/unitsdb/commands/qudt/update.rb +126 -0
- data/lib/unitsdb/commands/qudt/updater.rb +189 -0
- data/lib/unitsdb/commands/qudt.rb +82 -0
- data/lib/unitsdb/commands/release.rb +12 -9
- data/lib/unitsdb/commands/search.rb +12 -11
- data/lib/unitsdb/commands/ucum/check.rb +42 -29
- data/lib/unitsdb/commands/ucum/formatter.rb +2 -1
- data/lib/unitsdb/commands/ucum/matcher.rb +23 -9
- data/lib/unitsdb/commands/ucum/update.rb +14 -13
- data/lib/unitsdb/commands/ucum/updater.rb +40 -6
- data/lib/unitsdb/commands/ucum/xml_parser.rb +0 -2
- data/lib/unitsdb/commands/ucum.rb +44 -4
- data/lib/unitsdb/commands/validate/identifiers.rb +2 -4
- data/lib/unitsdb/commands/validate/qudt_references.rb +111 -0
- data/lib/unitsdb/commands/validate/references.rb +36 -19
- data/lib/unitsdb/commands/validate/si_references.rb +3 -5
- data/lib/unitsdb/commands/validate/ucum_references.rb +105 -0
- data/lib/unitsdb/commands/validate.rb +67 -11
- data/lib/unitsdb/commands.rb +20 -0
- data/lib/unitsdb/database.rb +90 -52
- data/lib/unitsdb/dimension.rb +1 -4
- data/lib/unitsdb/dimension_details.rb +0 -1
- data/lib/unitsdb/dimensions.rb +0 -2
- data/lib/unitsdb/errors.rb +7 -0
- data/lib/unitsdb/prefix.rb +0 -4
- data/lib/unitsdb/prefix_reference.rb +0 -2
- data/lib/unitsdb/prefixes.rb +0 -1
- data/lib/unitsdb/quantities.rb +0 -2
- data/lib/unitsdb/quantity.rb +0 -6
- data/lib/unitsdb/qudt.rb +100 -0
- data/lib/unitsdb/root_unit_reference.rb +0 -3
- data/lib/unitsdb/scale.rb +0 -4
- data/lib/unitsdb/scale_reference.rb +0 -2
- data/lib/unitsdb/scales.rb +0 -2
- data/lib/unitsdb/si_derived_base.rb +0 -2
- data/lib/unitsdb/ucum.rb +14 -10
- data/lib/unitsdb/unit.rb +0 -10
- data/lib/unitsdb/unit_reference.rb +0 -2
- data/lib/unitsdb/unit_system.rb +1 -3
- data/lib/unitsdb/unit_system_reference.rb +0 -2
- data/lib/unitsdb/unit_systems.rb +0 -2
- data/lib/unitsdb/units.rb +0 -2
- data/lib/unitsdb/utils.rb +32 -21
- data/lib/unitsdb/version.rb +5 -1
- data/lib/unitsdb.rb +62 -14
- data/unitsdb.gemspec +6 -3
- metadata +52 -13
- data/lib/unitsdb/commands/si_formatter.rb +0 -485
- data/lib/unitsdb/commands/si_matcher.rb +0 -470
- data/lib/unitsdb/commands/si_ttl_parser.rb +0 -100
- data/lib/unitsdb/commands/si_updater.rb +0 -212
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unitsdb
|
|
4
|
+
module Commands
|
|
5
|
+
module Qudt
|
|
6
|
+
# Matcher for QUDT and UnitsDB entities
|
|
7
|
+
module Matcher
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Match QUDT entities to UnitsDB entities (QUDT → UnitsDB)
|
|
11
|
+
def match_qudt_to_db(entity_type, qudt_entities, db_entities)
|
|
12
|
+
puts "Matching QUDT #{entity_type} to UnitsDB #{entity_type}..."
|
|
13
|
+
|
|
14
|
+
# Initialize result arrays
|
|
15
|
+
matches = []
|
|
16
|
+
missing_matches = []
|
|
17
|
+
unmatched_qudt = []
|
|
18
|
+
|
|
19
|
+
# Process each QUDT entity
|
|
20
|
+
qudt_entities.each do |qudt_entity|
|
|
21
|
+
match_data = find_db_match_for_qudt(qudt_entity, db_entities,
|
|
22
|
+
entity_type)
|
|
23
|
+
|
|
24
|
+
if match_data[:match]
|
|
25
|
+
matches << { qudt_entity: qudt_entity,
|
|
26
|
+
db_entity: match_data[:match] }
|
|
27
|
+
elsif match_data[:potential_match]
|
|
28
|
+
missing_matches << { qudt_entity: qudt_entity,
|
|
29
|
+
db_entity: match_data[:potential_match] }
|
|
30
|
+
else
|
|
31
|
+
unmatched_qudt << qudt_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
[matches, missing_matches, unmatched_qudt]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Match UnitsDB entities to QUDT entities (UnitsDB → QUDT)
|
|
39
|
+
def match_db_to_qudt(entity_type, qudt_entities, db_entities)
|
|
40
|
+
puts "Matching UnitsDB #{entity_type} to QUDT #{entity_type}..."
|
|
41
|
+
|
|
42
|
+
# Initialize result arrays
|
|
43
|
+
matches = []
|
|
44
|
+
missing_refs = []
|
|
45
|
+
unmatched_db = []
|
|
46
|
+
|
|
47
|
+
# Process each UnitsDB entity
|
|
48
|
+
db_entities.each do |db_entity|
|
|
49
|
+
# Skip entities that already have QUDT references
|
|
50
|
+
if has_qudt_reference?(db_entity)
|
|
51
|
+
matches << { db_entity: db_entity,
|
|
52
|
+
qudt_entity: find_referenced_qudt_entity(db_entity,
|
|
53
|
+
qudt_entities) }
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
match_data = find_qudt_match_for_db(db_entity, qudt_entities,
|
|
58
|
+
entity_type)
|
|
59
|
+
|
|
60
|
+
if match_data[:match]
|
|
61
|
+
missing_refs << { db_entity: db_entity,
|
|
62
|
+
qudt_entity: match_data[:match] }
|
|
63
|
+
else
|
|
64
|
+
unmatched_db << db_entity
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
[matches, missing_refs, unmatched_db]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if a UnitsDB entity already has a QUDT reference
|
|
72
|
+
def has_qudt_reference?(entity)
|
|
73
|
+
return false unless entity.respond_to?(:references) && entity.references
|
|
74
|
+
|
|
75
|
+
entity.references.any? { |ref| ref.authority == "qudt" }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Find the referenced QUDT entity based on the reference URI
|
|
79
|
+
def find_referenced_qudt_entity(db_entity, qudt_entities)
|
|
80
|
+
return nil unless db_entity.respond_to?(:references) && db_entity.references
|
|
81
|
+
|
|
82
|
+
qudt_ref = db_entity.references.find { |ref| ref.authority == "qudt" }
|
|
83
|
+
return nil unless qudt_ref
|
|
84
|
+
|
|
85
|
+
ref_uri = qudt_ref.uri
|
|
86
|
+
qudt_entities.find { |qudt_entity| qudt_entity.uri == ref_uri }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Get the ID of a UnitsDB entity
|
|
90
|
+
def get_entity_id(entity)
|
|
91
|
+
entity.respond_to?(:id) ? entity.id : nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Find a matching UnitsDB entity for a QUDT entity
|
|
95
|
+
def find_db_match_for_qudt(qudt_entity, db_entities, entity_type)
|
|
96
|
+
result = { match: nil, potential_match: nil }
|
|
97
|
+
|
|
98
|
+
# Different matching logic based on entity type
|
|
99
|
+
case entity_type
|
|
100
|
+
when "units"
|
|
101
|
+
result = match_unit_qudt_to_db(qudt_entity, db_entities)
|
|
102
|
+
when "quantities"
|
|
103
|
+
result = match_quantity_qudt_to_db(qudt_entity, db_entities)
|
|
104
|
+
when "dimensions"
|
|
105
|
+
result = match_dimension_qudt_to_db(qudt_entity, db_entities)
|
|
106
|
+
when "unit_systems"
|
|
107
|
+
result = match_unit_system_qudt_to_db(qudt_entity, db_entities)
|
|
108
|
+
when "prefixes"
|
|
109
|
+
result = match_prefix_qudt_to_db(qudt_entity, db_entities)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
result
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Find a matching QUDT entity for a UnitsDB entity
|
|
116
|
+
def find_qudt_match_for_db(db_entity, qudt_entities, entity_type)
|
|
117
|
+
result = { match: nil }
|
|
118
|
+
|
|
119
|
+
# Different matching logic based on entity type
|
|
120
|
+
case entity_type
|
|
121
|
+
when "units"
|
|
122
|
+
result = match_unit_db_to_qudt(db_entity, qudt_entities)
|
|
123
|
+
when "quantities"
|
|
124
|
+
result = match_quantity_db_to_qudt(db_entity, qudt_entities)
|
|
125
|
+
when "dimensions"
|
|
126
|
+
result = match_dimension_db_to_qudt(db_entity, qudt_entities)
|
|
127
|
+
when "unit_systems"
|
|
128
|
+
result = match_unit_system_db_to_qudt(db_entity, qudt_entities)
|
|
129
|
+
when "prefixes"
|
|
130
|
+
result = match_prefix_db_to_qudt(db_entity, qudt_entities)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
result
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Match QUDT unit to UnitsDB unit
|
|
137
|
+
def match_unit_qudt_to_db(qudt_unit, db_units)
|
|
138
|
+
result = { match: nil, potential_match: nil }
|
|
139
|
+
|
|
140
|
+
# PRIORITY 0: Try SI exact match first (highest confidence)
|
|
141
|
+
if qudt_unit.si_exact_match
|
|
142
|
+
si_match = find_unit_by_si_reference(qudt_unit.si_exact_match,
|
|
143
|
+
db_units)
|
|
144
|
+
if si_match
|
|
145
|
+
result[:match] = si_match
|
|
146
|
+
return result
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# PRIORITY 1: Try symbol match (most reliable)
|
|
151
|
+
if qudt_unit.symbol
|
|
152
|
+
symbol_match = db_units.find do |db_unit|
|
|
153
|
+
db_unit.symbols&.any? do |symbol|
|
|
154
|
+
symbol.ascii&.downcase == qudt_unit.symbol.downcase ||
|
|
155
|
+
symbol.unicode&.downcase == qudt_unit.symbol.downcase
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
if symbol_match
|
|
160
|
+
result[:match] = symbol_match
|
|
161
|
+
return result
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# PRIORITY 2: Try exact label match
|
|
166
|
+
if qudt_unit.label
|
|
167
|
+
label_match = db_units.find do |db_unit|
|
|
168
|
+
db_unit.names&.any? do |name_obj|
|
|
169
|
+
name_obj.value.downcase == qudt_unit.label.downcase
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if label_match
|
|
174
|
+
result[:match] = label_match
|
|
175
|
+
return result
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# PRIORITY 3: Try normalized name matching (remove common variations)
|
|
180
|
+
if qudt_unit.label
|
|
181
|
+
normalized_match = db_units.find do |db_unit|
|
|
182
|
+
db_unit.names&.any? do |name_obj|
|
|
183
|
+
normalize_name(qudt_unit.label) == normalize_name(name_obj.value)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if normalized_match
|
|
188
|
+
result[:match] = normalized_match
|
|
189
|
+
return result
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# PRIORITY 4: Try partial name match for potential matches (be conservative)
|
|
194
|
+
if qudt_unit.label && qudt_unit.label.length > 3
|
|
195
|
+
partial_matches = db_units.select do |db_unit|
|
|
196
|
+
db_unit.names&.any? do |name_obj|
|
|
197
|
+
name_obj.value.downcase.include?(qudt_unit.label.downcase) ||
|
|
198
|
+
qudt_unit.label.downcase.include?(name_obj.value.downcase)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if partial_matches.any?
|
|
203
|
+
result[:potential_match] =
|
|
204
|
+
partial_matches.first
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
result
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Match UnitsDB unit to QUDT unit
|
|
212
|
+
def match_unit_db_to_qudt(db_unit, qudt_units)
|
|
213
|
+
result = { match: nil }
|
|
214
|
+
|
|
215
|
+
# PRIORITY 1: Try symbol match first (most reliable)
|
|
216
|
+
if db_unit.symbols && !db_unit.symbols.empty?
|
|
217
|
+
symbol_match = qudt_units.find do |qudt_unit|
|
|
218
|
+
qudt_unit.symbol && db_unit.symbols.any? do |symbol|
|
|
219
|
+
qudt_unit.symbol.downcase == symbol.ascii&.downcase ||
|
|
220
|
+
qudt_unit.symbol.downcase == symbol.unicode&.downcase
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if symbol_match
|
|
225
|
+
result[:match] = symbol_match
|
|
226
|
+
return result
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# PRIORITY 2: Try exact name match
|
|
231
|
+
if db_unit.names && !db_unit.names.empty?
|
|
232
|
+
db_unit_names = db_unit.names.map do |name_obj|
|
|
233
|
+
name_obj.value.downcase
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
name_match = qudt_units.find do |qudt_unit|
|
|
237
|
+
qudt_unit.label && db_unit_names.include?(qudt_unit.label.downcase)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
if name_match
|
|
241
|
+
result[:match] = name_match
|
|
242
|
+
return result
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# PRIORITY 3: Try normalized name matching
|
|
246
|
+
normalized_match = qudt_units.find do |qudt_unit|
|
|
247
|
+
qudt_unit.label && db_unit_names.any? do |db_name|
|
|
248
|
+
normalize_name(qudt_unit.label) == normalize_name(db_name)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
result[:match] = normalized_match if normalized_match
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
result
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Match QUDT quantity kind to UnitsDB quantity
|
|
259
|
+
def match_quantity_qudt_to_db(qudt_quantity, db_quantities)
|
|
260
|
+
result = { match: nil, potential_match: nil }
|
|
261
|
+
|
|
262
|
+
# Try exact label match first
|
|
263
|
+
if qudt_quantity.label
|
|
264
|
+
label_match = db_quantities.find do |db_quantity|
|
|
265
|
+
db_quantity.names&.any? do |name_obj|
|
|
266
|
+
name_obj.value.downcase == qudt_quantity.label.downcase
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
if label_match
|
|
271
|
+
result[:match] = label_match
|
|
272
|
+
return result
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Try symbol match if available
|
|
277
|
+
if qudt_quantity.symbol
|
|
278
|
+
symbol_match = db_quantities.find do |db_quantity|
|
|
279
|
+
db_quantity.names&.any? do |name_obj|
|
|
280
|
+
name_obj.value.downcase == qudt_quantity.symbol.downcase
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
if symbol_match
|
|
285
|
+
result[:match] = symbol_match
|
|
286
|
+
return result
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Try normalized name matching (remove common variations)
|
|
291
|
+
if qudt_quantity.label
|
|
292
|
+
normalized_match = db_quantities.find do |db_quantity|
|
|
293
|
+
db_quantity.names&.any? do |name_obj|
|
|
294
|
+
normalize_name(qudt_quantity.label) == normalize_name(name_obj.value)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
if normalized_match
|
|
299
|
+
result[:match] = normalized_match
|
|
300
|
+
return result
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Try partial name match for potential matches
|
|
305
|
+
if qudt_quantity.label
|
|
306
|
+
partial_matches = db_quantities.select do |db_quantity|
|
|
307
|
+
db_quantity.names&.any? do |name_obj|
|
|
308
|
+
name_obj.value.downcase.include?(qudt_quantity.label.downcase) ||
|
|
309
|
+
qudt_quantity.label.downcase.include?(name_obj.value.downcase)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
if partial_matches.any?
|
|
314
|
+
result[:potential_match] =
|
|
315
|
+
partial_matches.first
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
result
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Match UnitsDB quantity to QUDT quantity kind
|
|
323
|
+
def match_quantity_db_to_qudt(db_quantity, qudt_quantities)
|
|
324
|
+
result = { match: nil }
|
|
325
|
+
|
|
326
|
+
# Try name match first
|
|
327
|
+
if db_quantity.names && !db_quantity.names.empty?
|
|
328
|
+
db_quantity_names = db_quantity.names.map do |name_obj|
|
|
329
|
+
name_obj.value.downcase
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
name_match = qudt_quantities.find do |qudt_quantity|
|
|
333
|
+
qudt_quantity.label && db_quantity_names.include?(qudt_quantity.label.downcase)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
if name_match
|
|
337
|
+
result[:match] = name_match
|
|
338
|
+
return result
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Try normalized name matching
|
|
342
|
+
normalized_match = qudt_quantities.find do |qudt_quantity|
|
|
343
|
+
qudt_quantity.label && db_quantity_names.any? do |db_name|
|
|
344
|
+
normalize_name(qudt_quantity.label) == normalize_name(db_name)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
result[:match] = normalized_match if normalized_match
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
result
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Match QUDT dimension vector to UnitsDB dimension
|
|
355
|
+
def match_dimension_qudt_to_db(qudt_dimension, db_dimensions)
|
|
356
|
+
result = { match: nil, potential_match: nil }
|
|
357
|
+
|
|
358
|
+
# Try dimensional analysis match first (most reliable for dimension vectors)
|
|
359
|
+
dimensional_match = db_dimensions.find do |db_dimension|
|
|
360
|
+
dimensions_match?(qudt_dimension, db_dimension)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
if dimensional_match
|
|
364
|
+
result[:match] = dimensional_match
|
|
365
|
+
return result
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Try exact label match as fallback
|
|
369
|
+
if qudt_dimension.label
|
|
370
|
+
label_match = db_dimensions.find do |db_dimension|
|
|
371
|
+
db_dimension.names&.any? do |name_obj|
|
|
372
|
+
name_obj.value.downcase == qudt_dimension.label.downcase
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
if label_match
|
|
377
|
+
result[:potential_match] = label_match
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
result
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Match UnitsDB dimension to QUDT dimension vector
|
|
385
|
+
def match_dimension_db_to_qudt(db_dimension, qudt_dimensions)
|
|
386
|
+
result = { match: nil }
|
|
387
|
+
|
|
388
|
+
# Try dimensional analysis match first (most reliable)
|
|
389
|
+
dimensional_match = qudt_dimensions.find do |qudt_dimension|
|
|
390
|
+
dimensions_match?(qudt_dimension, db_dimension)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
if dimensional_match
|
|
394
|
+
result[:match] = dimensional_match
|
|
395
|
+
return result
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Try name match as fallback
|
|
399
|
+
if db_dimension.names && !db_dimension.names.empty?
|
|
400
|
+
db_dimension_names = db_dimension.names.map do |name_obj|
|
|
401
|
+
name_obj.value.downcase
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
name_match = qudt_dimensions.find do |qudt_dimension|
|
|
405
|
+
qudt_dimension.label && db_dimension_names.include?(qudt_dimension.label.downcase)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
result[:match] = name_match if name_match
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
result
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Match QUDT system of units to UnitsDB unit system
|
|
415
|
+
def match_unit_system_qudt_to_db(qudt_system, db_unit_systems)
|
|
416
|
+
result = { match: nil, potential_match: nil }
|
|
417
|
+
|
|
418
|
+
# Try exact label match first
|
|
419
|
+
if qudt_system.label
|
|
420
|
+
label_match = db_unit_systems.find do |db_system|
|
|
421
|
+
db_system.names&.any? do |name_obj|
|
|
422
|
+
name_obj.value.downcase == qudt_system.label.downcase
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
if label_match
|
|
427
|
+
result[:match] = label_match
|
|
428
|
+
return result
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Try abbreviation match
|
|
433
|
+
if qudt_system.abbreviation
|
|
434
|
+
abbrev_match = db_unit_systems.find do |db_system|
|
|
435
|
+
db_system.names&.any? do |name_obj|
|
|
436
|
+
name_obj.value.downcase == qudt_system.abbreviation.downcase
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
if abbrev_match
|
|
441
|
+
result[:match] = abbrev_match
|
|
442
|
+
return result
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Try smart matching for known systems
|
|
447
|
+
if qudt_system.abbreviation
|
|
448
|
+
smart_match = db_unit_systems.find do |db_system|
|
|
449
|
+
db_system.names&.any? do |name_obj|
|
|
450
|
+
case qudt_system.abbreviation.downcase
|
|
451
|
+
when "si"
|
|
452
|
+
# Match SI abbreviation to any system containing "si"
|
|
453
|
+
name_obj.value.downcase.include?("si")
|
|
454
|
+
when "cgs"
|
|
455
|
+
# Match CGS abbreviation to any system containing "cgs"
|
|
456
|
+
name_obj.value.downcase.include?("cgs")
|
|
457
|
+
when "imperial"
|
|
458
|
+
# Match Imperial to any system containing "imperial"
|
|
459
|
+
name_obj.value.downcase.include?("imperial")
|
|
460
|
+
when "us customary"
|
|
461
|
+
# Match US Customary to any system containing "us" or "customary"
|
|
462
|
+
name_obj.value.downcase.include?("us") || name_obj.value.downcase.include?("customary")
|
|
463
|
+
else
|
|
464
|
+
false
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
if smart_match
|
|
470
|
+
result[:match] = smart_match
|
|
471
|
+
return result
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
result
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Match UnitsDB unit system to QUDT system of units
|
|
479
|
+
def match_unit_system_db_to_qudt(db_system, qudt_systems)
|
|
480
|
+
result = { match: nil }
|
|
481
|
+
|
|
482
|
+
# Try name match first
|
|
483
|
+
if db_system.names && !db_system.names.empty?
|
|
484
|
+
db_system_names = db_system.names.map do |name_obj|
|
|
485
|
+
name_obj.value.downcase
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
name_match = qudt_systems.find do |qudt_system|
|
|
489
|
+
(qudt_system.label && db_system_names.include?(qudt_system.label.downcase)) ||
|
|
490
|
+
(qudt_system.abbreviation && db_system_names.include?(qudt_system.abbreviation.downcase))
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
if name_match
|
|
494
|
+
result[:match] = name_match
|
|
495
|
+
return result
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Try smart matching for known systems
|
|
499
|
+
smart_match = qudt_systems.find do |qudt_system|
|
|
500
|
+
db_system_names.any? do |db_name|
|
|
501
|
+
if db_name.include?("si") && qudt_system.abbreviation&.downcase == "si"
|
|
502
|
+
true
|
|
503
|
+
elsif db_name.include?("cgs") && qudt_system.abbreviation&.downcase == "cgs"
|
|
504
|
+
true
|
|
505
|
+
elsif db_name.include?("imperial") && qudt_system.abbreviation&.downcase == "imperial"
|
|
506
|
+
true
|
|
507
|
+
elsif (db_name.include?("us") || db_name.include?("customary")) && qudt_system.abbreviation&.downcase == "us customary"
|
|
508
|
+
true
|
|
509
|
+
else
|
|
510
|
+
false
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
result[:match] = smart_match if smart_match
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
result
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Check if QUDT dimension vector matches UnitsDB dimension
|
|
522
|
+
def dimensions_match?(qudt_dimension, db_dimension)
|
|
523
|
+
return false unless qudt_dimension.respond_to?(:dimension_exponent_for_length)
|
|
524
|
+
|
|
525
|
+
# Map QUDT dimension exponents to UnitsDB dimension structure
|
|
526
|
+
qudt_exponents = {
|
|
527
|
+
length: qudt_dimension.dimension_exponent_for_length || 0,
|
|
528
|
+
mass: qudt_dimension.dimension_exponent_for_mass || 0,
|
|
529
|
+
time: qudt_dimension.dimension_exponent_for_time || 0,
|
|
530
|
+
electric_current: qudt_dimension.dimension_exponent_for_electric_current || 0,
|
|
531
|
+
thermodynamic_temperature: qudt_dimension.dimension_exponent_for_thermodynamic_temperature || 0,
|
|
532
|
+
amount_of_substance: qudt_dimension.dimension_exponent_for_amount_of_substance || 0,
|
|
533
|
+
luminous_intensity: qudt_dimension.dimension_exponent_for_luminous_intensity || 0,
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
# Get UnitsDB dimension exponents from direct properties
|
|
537
|
+
db_exponents = {
|
|
538
|
+
length: get_dimension_power(db_dimension, :length),
|
|
539
|
+
mass: get_dimension_power(db_dimension, :mass),
|
|
540
|
+
time: get_dimension_power(db_dimension, :time),
|
|
541
|
+
electric_current: get_dimension_power(db_dimension,
|
|
542
|
+
:electric_current),
|
|
543
|
+
thermodynamic_temperature: get_dimension_power(db_dimension,
|
|
544
|
+
:thermodynamic_temperature),
|
|
545
|
+
amount_of_substance: get_dimension_power(db_dimension,
|
|
546
|
+
:amount_of_substance),
|
|
547
|
+
luminous_intensity: get_dimension_power(db_dimension,
|
|
548
|
+
:luminous_intensity),
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
# Compare all dimension exponents
|
|
552
|
+
qudt_exponents == db_exponents
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Get dimension power from UnitsDB dimension entity
|
|
556
|
+
def get_dimension_power(db_dimension, dimension_type)
|
|
557
|
+
return 0 unless db_dimension.respond_to?(dimension_type)
|
|
558
|
+
|
|
559
|
+
dimension_property = db_dimension.send(dimension_type)
|
|
560
|
+
return 0 unless dimension_property.respond_to?(:power)
|
|
561
|
+
|
|
562
|
+
dimension_property.power || 0
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
# Normalize names by removing common variations and punctuation
|
|
566
|
+
def normalize_name(name)
|
|
567
|
+
return "" unless name
|
|
568
|
+
|
|
569
|
+
name.downcase
|
|
570
|
+
.gsub(/\s+/, " ") # normalize whitespace
|
|
571
|
+
.gsub(/[-_]/, " ") # convert dashes/underscores to spaces
|
|
572
|
+
.gsub(/[()\\]/, "") # remove parentheses and brackets
|
|
573
|
+
.gsub(/\bof\b/, "") # remove "of"
|
|
574
|
+
.gsub(/\bper\b/, "/") # convert "per" to "/"
|
|
575
|
+
.strip
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Find a UnitsDB unit that has an SI reference matching the given SI URI
|
|
579
|
+
def find_unit_by_si_reference(si_uri, db_units)
|
|
580
|
+
return nil unless si_uri
|
|
581
|
+
|
|
582
|
+
# Extract the SI unit identifier from the URI
|
|
583
|
+
# Example: "http://qudt.org/vocab/unit/M" -> "M"
|
|
584
|
+
si_identifier = si_uri.split("/").last
|
|
585
|
+
|
|
586
|
+
# Look for a UnitsDB unit that has an SI reference with this identifier
|
|
587
|
+
db_units.find do |db_unit|
|
|
588
|
+
next unless db_unit.respond_to?(:references) && db_unit.references
|
|
589
|
+
|
|
590
|
+
db_unit.references.any? do |ref|
|
|
591
|
+
ref.authority == "si" && (
|
|
592
|
+
ref.uri&.end_with?(si_identifier) ||
|
|
593
|
+
ref.uri&.include?(si_identifier)
|
|
594
|
+
)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# Match QUDT prefix to UnitsDB prefix
|
|
600
|
+
def match_prefix_qudt_to_db(qudt_prefix, db_prefixes)
|
|
601
|
+
result = { match: nil, potential_match: nil }
|
|
602
|
+
|
|
603
|
+
# PRIORITY 1: Try UCUM code match first (most reliable for prefixes)
|
|
604
|
+
if qudt_prefix.ucum_code
|
|
605
|
+
ucum_match = db_prefixes.find do |db_prefix|
|
|
606
|
+
db_prefix.respond_to?(:references) && db_prefix.references&.any? do |ref|
|
|
607
|
+
ref.authority == "ucum" && ref.uri&.include?(qudt_prefix.ucum_code)
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
if ucum_match
|
|
612
|
+
result[:match] = ucum_match
|
|
613
|
+
return result
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# PRIORITY 2: Try symbol match (very reliable for prefixes)
|
|
618
|
+
if qudt_prefix.symbol
|
|
619
|
+
symbol_match = db_prefixes.find do |db_prefix|
|
|
620
|
+
db_prefix.symbols&.any? do |symbol|
|
|
621
|
+
symbol.ascii&.downcase == qudt_prefix.symbol.downcase ||
|
|
622
|
+
symbol.unicode&.downcase == qudt_prefix.symbol.downcase
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
if symbol_match
|
|
627
|
+
result[:match] = symbol_match
|
|
628
|
+
return result
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
# PRIORITY 3: Try exact label match
|
|
633
|
+
if qudt_prefix.label
|
|
634
|
+
label_match = db_prefixes.find do |db_prefix|
|
|
635
|
+
db_prefix.names&.any? do |name_obj|
|
|
636
|
+
name_obj.value.downcase == qudt_prefix.label.downcase
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
if label_match
|
|
641
|
+
result[:match] = label_match
|
|
642
|
+
return result
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
# PRIORITY 4: Try multiplier match (for prefixes with same scale factor)
|
|
647
|
+
if qudt_prefix.prefix_multiplier
|
|
648
|
+
multiplier_match = db_prefixes.find do |db_prefix|
|
|
649
|
+
db_prefix.respond_to?(:factor) &&
|
|
650
|
+
(db_prefix.factor - qudt_prefix.prefix_multiplier).abs < 1e-10
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
if multiplier_match
|
|
654
|
+
result[:match] = multiplier_match
|
|
655
|
+
return result
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# PRIORITY 5: Try normalized name matching
|
|
660
|
+
if qudt_prefix.label
|
|
661
|
+
normalized_match = db_prefixes.find do |db_prefix|
|
|
662
|
+
db_prefix.names&.any? do |name_obj|
|
|
663
|
+
normalize_name(qudt_prefix.label) == normalize_name(name_obj.value)
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
if normalized_match
|
|
668
|
+
result[:potential_match] = normalized_match
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
result
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# Match UnitsDB prefix to QUDT prefix
|
|
676
|
+
def match_prefix_db_to_qudt(db_prefix, qudt_prefixes)
|
|
677
|
+
result = { match: nil }
|
|
678
|
+
|
|
679
|
+
# PRIORITY 1: Try symbol match first (most reliable)
|
|
680
|
+
if db_prefix.symbols && !db_prefix.symbols.empty?
|
|
681
|
+
symbol_match = qudt_prefixes.find do |qudt_prefix|
|
|
682
|
+
qudt_prefix.symbol && db_prefix.symbols.any? do |symbol|
|
|
683
|
+
qudt_prefix.symbol.downcase == symbol.ascii&.downcase ||
|
|
684
|
+
qudt_prefix.symbol.downcase == symbol.unicode&.downcase
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
if symbol_match
|
|
689
|
+
result[:match] = symbol_match
|
|
690
|
+
return result
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
# PRIORITY 2: Try exact name match
|
|
695
|
+
if db_prefix.names && !db_prefix.names.empty?
|
|
696
|
+
db_prefix_names = db_prefix.names.map do |name_obj|
|
|
697
|
+
name_obj.value.downcase
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
name_match = qudt_prefixes.find do |qudt_prefix|
|
|
701
|
+
qudt_prefix.label && db_prefix_names.include?(qudt_prefix.label.downcase)
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
if name_match
|
|
705
|
+
result[:match] = name_match
|
|
706
|
+
return result
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
# PRIORITY 3: Try normalized name matching
|
|
710
|
+
normalized_match = qudt_prefixes.find do |qudt_prefix|
|
|
711
|
+
qudt_prefix.label && db_prefix_names.any? do |db_name|
|
|
712
|
+
normalize_name(qudt_prefix.label) == normalize_name(db_name)
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
if normalized_match
|
|
717
|
+
result[:match] = normalized_match
|
|
718
|
+
return result
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# PRIORITY 4: Try multiplier match (for prefixes with same scale factor)
|
|
723
|
+
if db_prefix.respond_to?(:factor) && db_prefix.factor
|
|
724
|
+
multiplier_match = qudt_prefixes.find do |qudt_prefix|
|
|
725
|
+
qudt_prefix.prefix_multiplier &&
|
|
726
|
+
(qudt_prefix.prefix_multiplier - db_prefix.factor).abs < 1e-10
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
result[:match] = multiplier_match if multiplier_match
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
result
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# Check if an entity has been manually verified (has a special flag)
|
|
736
|
+
def manually_verified?(entity)
|
|
737
|
+
return false unless entity.respond_to?(:references) && entity.references
|
|
738
|
+
|
|
739
|
+
entity.references.any? do |ref|
|
|
740
|
+
ref.authority == "qudt" && ref.respond_to?(:verified) && ref.verified
|
|
741
|
+
end
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
end
|
|
746
|
+
end
|