labimotion 2.0.0.rc2 → 2.0.0.rc6

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.
@@ -1,301 +1,258 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'labimotion/libs/dataset_builder'
3
4
  require 'labimotion/version'
5
+ require 'labimotion/utils/mapper_utils'
4
6
  require 'labimotion/utils/utils'
5
7
 
6
8
  module Labimotion
7
9
  ## NmrMapper
8
10
  class NmrMapper
9
- def self.process_ds(id, current_user = {})
10
- att = Attachment.find_by(id: id, con_state: Labimotion::ConState::NMR)
11
- return if att.nil?
12
-
13
- result = is_brucker_binary(id)
14
- if result[:is_bagit] == true
15
- att.update_column(:con_state, Labimotion::ConState::CONVERTED)
16
- Labimotion::Converter.metadata(id, current_user)
17
- Labimotion::ConState::COMPLETED
18
- elsif result[:metadata] == nil
19
- Labimotion::ConState::NONE
20
- else
21
- ds = Container.find_by(id: att.attachable_id)
22
- return if ds.nil? || ds.parent&.container_type != 'analysis'
23
-
24
- data = process(att, id, result[:metadata])
25
- generate_ds(id, att.attachable_id, data, current_user)
26
- Labimotion::ConState::COMPLETED
11
+ # Constants specific to NMR mapping
12
+ module Constants
13
+ # Duplicate fields that need special handling
14
+ DUP_FIELDS = %w[time version].freeze
15
+
16
+ # Field groups for different processing stages
17
+ FG_FINALIZE = %w[set.temperature general.date general.time software.Name software.Version].freeze
18
+ FG_SYSTEM = %w[general.creator sample_details.label sample_details.id].freeze
19
+
20
+ # Valid NMR nuclei types
21
+ OBSERVED = %w[1H 13C].freeze
22
+
23
+ # OLS terms for different NMR types
24
+ module OlsTerms
25
+ NMR_1H = 'CHMO:0000593'
26
+ NMR_13C = 'CHMO:0000595'
27
27
  end
28
28
  end
29
29
 
30
- def self.is_brucker_binary(id)
31
- att = Attachment.find_by(id: id, con_state: Labimotion::ConState::NMR)
32
- return if att.nil?
33
-
34
- if att&.attachment_attacher&.file&.url
35
- Zip::File.open(att.attachment_attacher.file.url) do |zip_file|
36
- zip_file.each do |entry|
37
- if entry.name.include?('/pdata/') && entry.name.include?('parm.txt')
38
- metadata = entry.get_input_stream.read.force_encoding('UTF-8')
39
- return { is_bagit: false, metadata: metadata }
40
- elsif entry.name.include?('metadata/') && entry.name.include?('converter.json')
41
- return { is_bagit: true, metadata: nil }
42
- end
43
- end
44
- end
45
- end
46
- { is_bagit: false, metadata: nil }
47
- end
30
+ class << self
31
+ def process_ds(id, current_user = {})
32
+ att = find_attachment(id)
33
+ return Labimotion::ConState::NONE if att.nil?
48
34
 
49
- def self.process(att, id, content)
50
- return if att.nil? || content.nil?
35
+ result = process(att)
36
+ return Labimotion::ConState::NONE if result.nil?
51
37
 
52
- lines = content.split("\n").reject(&:empty?)
53
- metadata = {}
54
- lines.map do |ln|
55
- arr = ln.split(/\s+/)
56
- metadata[arr[0]] = arr[1..-1].join(' ') if arr.length > 1
38
+ handle_process_result(result, att, id, current_user)
57
39
  end
58
- ols = 'CHMO:0000593' if metadata['NUC1'] == '1H'
59
- ols = 'CHMO:0000595' if metadata['NUC1'] == '13C'
60
-
61
- { content: { metadata: metadata, ols: ols } }
62
- # if content.present? && att.present?
63
- # Labimotion::NmrMapper.ts('write', att.attachable_id,
64
- # content: { metadata: metadata, ols: ols })
65
- # end
66
- end
67
40
 
68
- def self.fetch_content(id)
69
- atts = Attachment.where(attachable_id: id)
70
- return if atts.nil?
41
+ def process(att)
42
+ config = Labimotion::MapperUtils.load_brucker_config
43
+ return if config.nil?
71
44
 
72
- atts.each do |att|
73
- content = Labimotion::NmrMapper.ts('read', att.id)
74
- return content if content.present?
45
+ attacher = att&.attachment_attacher
46
+ extracted_data = Labimotion::MapperUtils.extract_data_from_zip(attacher&.file&.url, config['sourceMap'])
47
+ return if extracted_data.nil?
48
+
49
+ extracted_data[:parameters] = config['sourceMap']['parameters']
50
+ extracted_data
75
51
  end
76
- end
77
52
 
53
+ def generate_ds(_id, cid, data, current_user = {}, element = nil)
54
+ return if data.nil? || cid.nil?
78
55
 
79
- def self.generate_ds(id, cid, data, current_user = {})
80
- return if data.nil? || cid.nil?
56
+ obj = Labimotion::NmrMapper.build_ds(cid, data[:content])
57
+ return if obj.nil? || obj[:ols].nil?
81
58
 
82
- obj = Labimotion::NmrMapper.build_ds(cid, data[:content])
83
- return if obj.nil? || obj[:ols].nil?
59
+ Labimotion::NmrMapper.update_ds(cid, obj, current_user, element)
60
+ end
84
61
 
85
- Labimotion::NmrMapper.update_ds_1h(cid, obj, current_user) if obj[:ols] == 'CHMO:0000593'
86
- Labimotion::NmrMapper.update_ds_1h(cid, obj, current_user) if obj[:ols] == 'CHMO:0000595'
87
- end
62
+ def update_ds(_cid, obj, current_user, element)
63
+ dataset = obj[:dataset]
64
+ dataset.properties = process_prop(obj, current_user, element)
65
+ dataset.save!
66
+ end
88
67
 
89
- def self.update_ds_13c(id, obj)
90
- # dataset = obj[:dataset]
91
- # metadata = obj[:metadata]
92
- # new_prop = dataset.properties
68
+ def build_ds(id, content)
69
+ ds = find_container(id)
70
+ return if ds.nil? || content.nil?
93
71
 
94
- # dataset.properties = new_prop
95
- # dataset.save!
96
- end
72
+ Labimotion::DatasetBuilder.build(ds, content)
73
+ end
97
74
 
98
- def self.set_data(prop, field, idx, layer_name, field_name, value)
99
- return if field['field'] != field_name || value&.empty?
75
+ private
100
76
 
101
- field['value'] = value
102
- prop[Labimotion::Prop::LAYERS][layer_name][Labimotion::Prop::FIELDS][idx] = field
103
- prop
104
- end
77
+ def finalize_ds(new_prop)
78
+ Constants::FG_FINALIZE.each do |field_path|
79
+ field = Labimotion::Utils.find_field(new_prop, field_path)
80
+ next unless field
105
81
 
106
- def self.update_ds_1h(id, obj, current_user)
107
- dataset = obj[:dataset]
108
- metadata = obj[:metadata]
109
- new_prop = dataset.properties
110
- new_prop.dig(Labimotion::Prop::LAYERS, 'general', Labimotion::Prop::FIELDS)&.each_with_index do |fi, idx|
111
- # new_prop = set_data(new_prop, fi, idx, 'general', 'title', metadata['NAME'])
112
- if fi['field'] == 'title' && metadata['NAME'].present?
113
- ## fi['label'] = fi['label']
114
- fi['value'] = metadata['NAME']
115
- fi['device'] = metadata['NAME']
116
- fi['dkey'] = 'NAME'
117
- new_prop[Labimotion::Prop::LAYERS]['general'][Labimotion::Prop::FIELDS][idx] = fi
82
+ update_finalized_field(field)
118
83
  end
84
+ new_prop
85
+ end
119
86
 
120
- if fi['field'] == 'date' && metadata['Date_'].present?
121
- ## fi['label'] = fi['label']
122
- fi['value'] = metadata['Date_']
123
- fi['device'] = metadata['Date_']
124
- fi['dkey'] = 'Date_'
125
- new_prop[Labimotion::Prop::LAYERS]['general'][Labimotion::Prop::FIELDS][idx] = fi
126
- end
87
+ def sys_to_ds(new_prop, element, current_user)
88
+ Constants::FG_SYSTEM.each do |field_path|
89
+ field = Labimotion::Utils.find_field(new_prop, field_path)
90
+ next unless field
127
91
 
128
- if fi['field'] == 'time' && metadata['Time'].present?
129
- ## fi['label'] = fi['label']
130
- fi['value'] = metadata['Time']
131
- fi['device'] = metadata['Time']
132
- fi['dkey'] = 'Time'
133
- new_prop[Labimotion::Prop::LAYERS]['general'][Labimotion::Prop::FIELDS][idx] = fi
92
+ update_system_field(field, current_user, element)
134
93
  end
94
+ new_prop
95
+ end
96
+
97
+ def params_to_ds(obj, new_prop)
98
+ metadata = obj[:metadata]
99
+ parameters = obj[:parameters]
135
100
 
136
- if fi['field'] == 'creator' && current_user.present?
137
- ## fi['label'] = fi['label']
138
- fi['value'] = current_user.name
139
- new_prop[Labimotion::Prop::LAYERS]['general'][Labimotion::Prop::FIELDS][idx] = fi
101
+ parameters.each do |param_key, field_path|
102
+ next if skip_parameter?(metadata, param_key)
103
+
104
+ update_param_field(new_prop, field_path, param_key, metadata)
140
105
  end
106
+ new_prop
141
107
  end
142
- element = Container.find(id)&.root_element
143
- element.present? && element&.class&.name == 'Sample' && new_prop.dig(Labimotion::Prop::LAYERS, 'sample_details',
144
- Labimotion::Prop::FIELDS)&.each_with_index do |fi, idx|
145
- if fi['field'] == 'label'
146
- fi['value'] = element.short_label
147
- new_prop[Labimotion::Prop::LAYERS]['sample_details'][Labimotion::Prop::FIELDS][idx] = fi
148
- end
149
- if fi['field'] == 'id'
150
- fi['value'] = element.id
151
- new_prop[Labimotion::Prop::LAYERS]['sample_details'][Labimotion::Prop::FIELDS][idx] = fi
152
- end
108
+
109
+ def process_prop(obj, current_user, element)
110
+ new_prop = obj[:dataset].properties
111
+ new_prop
112
+ .then { |prop| params_to_ds(obj, prop) }
113
+ .then { |prop| sys_to_ds(prop, element, current_user) }
114
+ .then { |prop| finalize_ds(prop) }
115
+ .then { |prop| Labimotion::VocabularyHandler.update_vocabularies(prop, current_user, element) }
153
116
  end
154
117
 
155
- new_prop.dig(Labimotion::Prop::LAYERS, 'instrument', Labimotion::Prop::FIELDS)&.each_with_index do |fi, idx|
156
- if fi['field'] == 'instrument' && metadata['INSTRUM'].present?
157
- ## fi['label'] = fi['label']
158
- fi['value'] = metadata['INSTRUM']
159
- fi['device'] = metadata['INSTRUM']
160
- fi['dkey'] = 'INSTRUM'
161
- new_prop[Labimotion::Prop::LAYERS]['instrument'][Labimotion::Prop::FIELDS][idx] = fi
162
- end
118
+ def find_attachment(id)
119
+ Attachment.find_by(id: id, con_state: Labimotion::ConState::NMR)
163
120
  end
164
121
 
165
- new_prop.dig(Labimotion::Prop::LAYERS, 'equipment', Labimotion::Prop::FIELDS)&.each_with_index do |fi, idx|
166
- if fi['field'] == 'probehead' && metadata['PROBHD'].present?
167
- ## fi['label'] = fi['label']
168
- fi['value'] = metadata['PROBHD']
169
- fi['device'] = metadata['PROBHD']
170
- fi['dkey'] = 'PROBHD'
171
- new_prop[Labimotion::Prop::LAYERS]['equipment'][Labimotion::Prop::FIELDS][idx] = fi
122
+ def handle_process_result(result, att, id, current_user)
123
+ if result[:is_bagit]
124
+ handle_bagit_result(att, id, current_user)
125
+ elsif invalid_metadata?(result)
126
+ Labimotion::ConState::NONE
127
+ else
128
+ handle_nmr_result(result, att, current_user)
172
129
  end
173
130
  end
174
131
 
175
- new_prop.dig(Labimotion::Prop::LAYERS, 'sample_preparation', Labimotion::Prop::FIELDS)&.each_with_index do |fi, idx|
176
- if fi['field'] == 'solvent' && metadata['SOLVENT'].present?
177
- ## fi['label'] = fi['label']
178
- fi['value'] = metadata['SOLVENT']
179
- fi['device'] = metadata['SOLVENT']
180
- fi['dkey'] = 'SOLVENT'
181
- fi['value'] = 'chloroform-D1 (CDCl3)' if metadata['SOLVENT'] == 'CDCl3'
182
- new_prop[Labimotion::Prop::LAYERS]['sample_preparation'][Labimotion::Prop::FIELDS][idx] = fi
183
- end
132
+ def handle_bagit_result(att, id, current_user)
133
+ att.update_column(:con_state, Labimotion::ConState::CONVERTED)
134
+ Labimotion::Converter.metadata(id, current_user)
135
+ Labimotion::ConState::COMPLETED
184
136
  end
185
137
 
186
- new_prop.dig(Labimotion::Prop::LAYERS, 'set', Labimotion::Prop::FIELDS)&.each_with_index do |fi, idx|
187
- if fi['field'] == 'temperature' && metadata['TE'].present?
188
- ## fi['label'] = fi['label']
189
- fi['value'] = metadata['TE'].split(/\s+/).first
190
- fi['device'] = metadata['TE']
191
- fi['dkey'] = 'TE'
192
- fi['value_system'] = metadata['TE'].split(/\s+/).last
193
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
194
- end
195
- if fi['field'] == 'ns' && metadata['NS'].present?
196
- ## fi['label'] = fi['label']
197
- fi['value'] = metadata['NS']
198
- fi['device'] = metadata['NS']
199
- fi['dkey'] = 'NS'
200
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
201
- end
202
- if fi['field'] == 'PULPROG' && metadata['PULPROG'].present?
203
- ## fi['label'] = fi['label']
204
- fi['value'] = metadata['PULPROG']
205
- fi['device'] = metadata['PULPROG']
206
- fi['dkey'] = 'PULPROG'
207
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
208
- end
209
- if fi['field'] == 'td' && metadata['TD'].present?
210
- ## fi['label'] = fi['label']
211
- fi['value'] = metadata['TD']
212
- fi['device'] = metadata['TD']
213
- fi['dkey'] = 'TD'
214
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
215
- end
216
- if fi['field'] == 'done' && metadata['D1'].present?
217
- ## fi['label'] = fi['label']
218
- fi['value'] = metadata['D1']
219
- fi['device'] = metadata['D1']
220
- fi['dkey'] = 'D1'
221
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
222
- end
223
- if fi['field'] == 'sf' && metadata['SF'].present?
224
- ## fi['label'] = fi['label']
225
- fi['value'] = metadata['SF']
226
- fi['device'] = metadata['SF']
227
- fi['dkey'] = 'SF'
228
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
138
+ def invalid_metadata?(result)
139
+ result[:metadata].nil? ||
140
+ Constants::OBSERVED.exclude?(result[:metadata]['NUC1'])
141
+ end
142
+
143
+ def handle_nmr_result(result, att, current_user)
144
+ ds = find_container(att.attachable_id)
145
+ return Labimotion::ConState::NONE unless valid_container?(ds)
146
+
147
+ prepare_mapper_result(ds, result, current_user)
148
+ Labimotion::ConState::COMPLETED
149
+ end
150
+
151
+ def find_container(cid)
152
+ Container.find_by(id: cid)
153
+ end
154
+
155
+ def valid_container?(container)
156
+ container.present? &&
157
+ container.parent&.container_type == 'analysis' &&
158
+ container.root_element.present?
159
+ end
160
+
161
+ def prepare_mapper_result(container, result, current_user)
162
+ metadata = result[:metadata]
163
+ ols = determine_ols_term(metadata['NUC1'])
164
+
165
+ data = {
166
+ content: {
167
+ metadata: metadata,
168
+ ols: ols,
169
+ parameters: result[:parameters]
170
+ }
171
+ }
172
+
173
+ generate_ds(nil, container.id, data, current_user, container.root_element)
174
+ end
175
+
176
+ def determine_ols_term(nuc1)
177
+ case nuc1
178
+ when '1H' then Constants::OlsTerms::NMR_1H
179
+ when '13C' then Constants::OlsTerms::NMR_13C
229
180
  end
230
- if fi['field'] == 'sfoone' && metadata['SFO1'].present?
231
- ## fi['label'] = fi['label']
232
- fi['value'] = metadata['SFO1']
233
- fi['device'] = metadata['SFO1']
234
- fi['dkey'] = 'SFO1'
235
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
181
+ end
182
+
183
+ def skip_parameter?(metadata, param_key)
184
+ metadata[param_key.to_s].blank? &&
185
+ Constants::DUP_FIELDS.exclude?(param_key.to_s.downcase)
186
+ end
187
+
188
+ def update_param_field(properties, field_path, param_key, metadata)
189
+ field = Labimotion::Utils.find_field(properties, field_path)
190
+ return unless field
191
+
192
+ update_field_value(field, param_key, metadata)
193
+ update_field_extend(field, param_key, metadata)
194
+ end
195
+
196
+ def update_duplicate_field(field_name, metadata)
197
+ case field_name
198
+ when 'time' then metadata['DATE']
199
+ when 'Version' then metadata['TITLE']
236
200
  end
237
- if fi['field'] == 'sfotwo' && metadata['SFO2'].present?
238
- ## fi['label'] = fi['label']
239
- fi['value'] = metadata['SFO2']
240
- fi['device'] = metadata['SFO2']
241
- fi['dkey'] = 'SFO2'
242
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
201
+ end
202
+
203
+ def update_field_value(field, param_key, metadata)
204
+ field['value'] = format_field_value(field, param_key, metadata)
205
+ rescue StandardError => e
206
+ Rails.logger.error "Error converting value for #{param_key}: #{e.message}"
207
+ field['value'] = ''
208
+ end
209
+
210
+ def format_field_value(field, param_key, metadata)
211
+ if field['type'] == 'integer'
212
+ Integer(metadata[param_key.to_s])
213
+ elsif Constants::DUP_FIELDS.include?(param_key.to_s.downcase)
214
+ update_duplicate_field(field['field'], metadata)
215
+ else
216
+ metadata[param_key.to_s]
243
217
  end
244
- if fi['field'] == 'nucone' && metadata['NUC1'].present?
245
- ## fi['label'] = fi['label']
246
- fi['value'] = metadata['NUC1']
247
- fi['device'] = metadata['NUC1']
248
- fi['dkey'] = 'NUC1'
249
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
218
+ end
219
+
220
+ def update_finalized_field(field)
221
+ case field['field']
222
+ when 'temperature'
223
+ field['value_system'] = 'K'
224
+ when 'date', 'time'
225
+ field['value'] = Labimotion::MapperUtils.format_timestamp(field['value'], field['field'])
226
+ when 'Name'
227
+ field['value'] = 'TopSpin' if field['value'].to_s.include?('TopSpin')
228
+ when 'Version'
229
+ field['value'] = field['value'].to_s.split('TopSpin').last.strip if field['value'].to_s.include?('TopSpin')
250
230
  end
251
- if fi['field'] == 'nuctwo' && metadata['NUC2'].present?
252
- ## fi['label'] = fi['label']
253
- fi['value'] = metadata['NUC2']
254
- fi['device'] = metadata['NUC2']
255
- fi['dkey'] = 'NUC2'
256
- new_prop[Labimotion::Prop::LAYERS]['set'][Labimotion::Prop::FIELDS][idx] = fi
231
+ end
232
+
233
+ def update_system_field(field, current_user, element)
234
+ field['value'] = determine_field_value(field['field'], current_user, element)
235
+ end
236
+
237
+ def determine_field_value(field_name, current_user, element)
238
+ case field_name
239
+ when 'creator'
240
+ current_user&.name if current_user.present?
241
+ when 'label', 'id'
242
+ get_sample_value(field_name, element)
257
243
  end
258
244
  end
259
- new_prop = Labimotion::VocabularyHandler.update_vocabularies(new_prop, current_user, element)
260
- dataset.properties = new_prop
261
- dataset.save!
262
- end
263
245
 
264
- def self.ts(method, identifier, params = nil)
265
- Rails.cache.send(method, "#{Labimotion::NmrMapper.new.class.name}#{identifier}", params)
266
- end
246
+ def get_sample_value(field_name, element)
247
+ return unless element.present? && element.is_a?(Sample)
267
248
 
268
- def self.clean(id)
269
- Labimotion::NmrMapper.ts('delete', id)
270
- end
249
+ field_name == 'label' ? element.short_label : element.id
250
+ end
271
251
 
272
- def self.build_ds(id, content)
273
- ds = Container.find_by(id: id)
274
- return if ds.nil? || content.nil?
275
-
276
- ols = content[:ols]
277
- metadata = content[:metadata]
278
-
279
- return if ols.nil? || metadata.nil?
280
-
281
- klass = Labimotion::DatasetKlass.find_by(ols_term_id: ols)
282
- return if klass.nil?
283
-
284
- uuid = SecureRandom.uuid
285
- props = klass.properties_release
286
- props['uuid'] = uuid
287
- props['pkg'] = Labimotion::Utils.pkg(props['pkg'])
288
- props['klass'] = 'Dataset'
289
- dataset = Labimotion::Dataset.create!(
290
- uuid: uuid,
291
- dataset_klass_id: klass.id,
292
- element_type: 'Container',
293
- element_id: ds.id,
294
- properties: props,
295
- properties_release: klass.properties_release,
296
- klass_uuid: klass.uuid,
297
- )
298
- { dataset: dataset, metadata: metadata, ols: ols }
252
+ def update_field_extend(field, param_key, metadata)
253
+ field['device'] = metadata[param_key.to_s]
254
+ field['dkey'] = param_key.to_s
255
+ end
299
256
  end
300
257
  end
301
258
  end
@@ -58,6 +58,9 @@ module Labimotion
58
58
  after_create :update_counter
59
59
  before_destroy :delete_attachment
60
60
 
61
+ def user_labels
62
+ tag&.taggable_data&.fetch('user_labels', nil)
63
+ end
61
64
 
62
65
  def attachments
63
66
  Attachment.where(attachable_id: self.id, attachable_type: self.class.name)