labimotion 2.0.0.rc2 → 2.0.0.rc8

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.
@@ -8,47 +8,174 @@ module Labimotion
8
8
  DEFAULT_ROW_WIDTH = 100
9
9
  DEFAULT_ROW_HEIGHT = 20
10
10
 
11
- def initialize(**args)
11
+ def initialize(id)
12
12
  @xfile = Axlsx::Package.new
13
- @file_extension = 'xlsx'
14
13
  @xfile.workbook.styles.fonts.first.name = 'Calibri'
14
+ @file_extension = 'xlsx'
15
+
16
+ @id = id
17
+ @dataset = Labimotion::Dataset.find_by(element_id: @id, element_type: 'Container')
18
+ return if @dataset.nil?
19
+
20
+ @klass = @dataset.dataset_klass
21
+ @ols_term_id = @klass.ols_term_id
22
+ @label = @klass.label
23
+ @analysis = @dataset&.element&.parent
24
+ @element = @analysis&.root&.containable if @analysis.present?
25
+ @element_type = @element.class.name if @element.present?
26
+ @spectra_values = []
15
27
  end
16
28
 
17
- def res_name(id)
18
- element_name = Container.find(id)&.root_element&.short_label
19
- ols = ols_name(id)
29
+ def read
30
+ @xfile.to_stream.read
31
+ end
32
+
33
+ def res_name
34
+ element_name = Container.find(@id)&.root_element&.short_label
35
+ ols = ols_name
20
36
  "#{element_name}_#{ols.gsub(' ', '_')}.xlsx"
21
37
  rescue StandardError => e
22
38
  Labimotion.log_exception(e)
23
39
  end
24
40
 
25
- def ols_name(id)
26
- ds = Labimotion::Dataset.find_by(element_id: id, element_type: 'Container')
27
- return nil if ds.nil?
41
+ def export
42
+ return if @dataset.nil? || @analysis.nil? || @element.nil?
43
+
44
+ description
45
+ dataset_info
46
+ spectra_info
47
+ chemwiki_info
48
+ rescue StandardError => e
49
+ Labimotion.log_exception(e)
50
+ end
51
+
52
+ private
53
+
54
+ def chemwiki_info
55
+ @spectra_values
56
+ config = Labimotion::MapperUtils.load_config(File.read(Constants::Mapper::WIKI_CONFIG))
57
+ return if config.nil? || config[@ols_term_id].nil?
58
+
59
+ sheet = @xfile.workbook.add_worksheet(name: 'ChemWiki')
60
+ map_index = config.dig(@ols_term_id, 'index')
61
+ map_mapper = config.dig(@ols_term_id, 'mapper')
62
+ map_source = config.dig(@ols_term_id, 'source')
63
+
64
+ return if map_index.nil? || map_mapper.nil? || map_source.nil?
65
+
66
+ return unless map_index.is_a?(Array) && map_mapper.is_a?(Hash) && map_source.is_a?(Hash)
28
67
 
29
- name = ds.dataset_klass.label
68
+ # sheet.column_info.each { |col| col.width = map_width }
69
+
70
+ if @spectra_values.length == 0
71
+ @spectra_values.push([])
72
+ end
73
+
74
+ @spectra_values.each_with_index do |spdata, idx|
75
+ array_header = []
76
+ array_data = []
77
+ map_index.each do |key|
78
+ mapper = map_mapper.dig(key)
79
+ next if mapper.nil? || mapper['sources'].nil? || !mapper.is_a?(Hash)
80
+
81
+ col_header = ''
82
+ col_value = ''
83
+ mapper['sources'].each_with_index do |source_key, idx|
84
+ source = map_source&.dig(source_key)
85
+ next if source.nil? || source['title'].nil?
86
+
87
+ col_header = source['title']
88
+
89
+ if col_value.present?
90
+ col_value += (mapper['separator'] || '') + source_data(source, spdata)
91
+ else
92
+ col_value = source_data(source, spdata)
93
+ end
94
+ end
95
+ array_header.push(col_header || '')
96
+ array_data.push(col_value || '')
97
+ end
98
+ if idx == 0
99
+ sheet.add_row(array_header)
100
+ end
101
+ sheet.add_row(array_data)
102
+
103
+ ## for Redox potential
104
+ last_row = sheet.rows.last
105
+ last_row.cells[3].type = :string
106
+ end
107
+
108
+
109
+ rescue StandardError => e
110
+ Labimotion.log_exception(e)
111
+ end
30
112
 
31
- match = name.match(/\((.*?)\)/)
32
- name = match && match.length > 1 ? match[1] : name
33
113
 
34
- name = '1H NMR' if ds.dataset_klass.ols_term_id == 'CHMO:0000593'
35
- name = '13C NMR' if ds.dataset_klass.ols_term_id == 'CHMO:0000595'
114
+ def conv_value(field, properties)
115
+ return '' if field.nil?
116
+
117
+ case field['type']
118
+ when 'select'
119
+ return '' if field['value'].empty?
120
+ Labimotion::Utils.find_options_val(field, properties) || field['value']
121
+ else
122
+ field['value']
123
+ end
124
+ end
125
+
126
+ def source_data(source, spdata)
127
+ param = source['param']
128
+ return '' if param.nil? || source['type'].nil?
129
+
130
+ val = case source['type']
131
+ when 'string'
132
+ param
133
+ when 'spc'
134
+ spdata.length.positive? && param.is_a?(Integer) ? spdata.pluck(param).join(';') : ''
135
+ when 'sample'
136
+ @element.send(param) if @element.respond_to?(param)
137
+ when 'molecule'
138
+ @element.molecule.send(param) if @element_type == 'Sample' && @element&.molecule&.respond_to?(param)
139
+ when 'dataset'
140
+ field = Labimotion::Utils.find_field(@dataset.properties, param)
141
+ conv_value(field, @dataset.properties)
142
+ end
143
+ val || ''
144
+ rescue StandardError => e
145
+ Labimotion.log_exception(e)
146
+ end
147
+
148
+ def element_info
149
+ if @element.class.name == 'Sample'
150
+ sheet = @xfile.workbook.add_worksheet(name: 'Element')
151
+ sheet.add_row(['inchikey', @element.molecule_inchikey])
152
+ sheet.add_row(['molfile', @element.molfile])
153
+ end
154
+ rescue StandardError => e
155
+ Labimotion.log_exception(e)
156
+ end
157
+
158
+ def ols_name
159
+ match = @label.match(/\((.*?)\)/)
160
+ name = match && match.length > 1 ? match[1] : @label
161
+
162
+ name = '1H NMR' if @ols_term_id == 'CHMO:0000593'
163
+ name = '13C NMR' if @ols_term_id == 'CHMO:0000595'
36
164
  name.slice(0, 26)
37
165
  rescue StandardError => e
38
166
  Labimotion.log_exception(e)
39
167
  'ols_name'
40
168
  end
41
169
 
42
- def description(ds, id)
43
- wb = @xfile.workbook
170
+ def description
44
171
  sheet = @xfile.workbook.add_worksheet(name: 'Description')
45
172
  header_style = sheet.styles.add_style(sz: 12, fg_color: 'FFFFFF', bg_color: '00008B', border: { style: :thick, color: 'FF777777', edges: [:bottom] })
46
- sheet.add_row(['File name', res_name(id)])
173
+ sheet.add_row(['File name', res_name])
47
174
  sheet.add_row(['Time', Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")] )
48
175
  sheet.add_row(['(This file is automatically generated by the system.)'])
49
176
  sheet.add_row([''])
50
177
  sheet.add_row([''])
51
- sheet.add_row(['Fields description of sheet:' + ds.dataset_klass.label])
178
+ sheet.add_row(['Fields description of sheet:' + @dataset.dataset_klass.label])
52
179
  sheet.add_row(['Fields', 'Field description'], style: header_style)
53
180
  sheet.add_row(['Layer Label', 'The label of the layer'])
54
181
  sheet.add_row(['Field Label', 'The label of the field'])
@@ -65,66 +192,211 @@ module Labimotion
65
192
  Labimotion.log_exception(e)
66
193
  end
67
194
 
68
- def export(id)
69
- ds = Labimotion::Dataset.find_by(element_id: id, element_type: 'Container')
70
- return if ds.nil?
71
-
72
- description(ds, id)
73
-
74
- wb = @xfile.workbook
75
- name = ols_name(id)
195
+ def dataset_info
196
+ name = ols_name
76
197
  return if name.nil?
77
198
 
199
+ sheet = create_dataset_sheet(name)
200
+ process_layers(sheet)
201
+ rescue StandardError => e
202
+ Labimotion.log_exception(e)
203
+ end
204
+
205
+ def create_dataset_sheet(name)
78
206
  sheet = @xfile.workbook.add_worksheet(name: name)
79
- sheet.add_row([ds.dataset_klass.label])
80
- header_style = sheet.styles.add_style(sz: 12, fg_color: 'FFFFFF', bg_color: '00008B', border: { style: :thick, color: 'FF777777', edges: [:bottom] })
81
- layer_style = sheet.styles.add_style(b: true, bg_color: 'CEECF5')
82
- sheet.add_row(header, style: header_style)
207
+ sheet.add_row([@dataset.dataset_klass.label])
208
+ sheet.add_row(header, style: create_header_style(sheet))
209
+ sheet
210
+ end
83
211
 
84
- layers = ds.properties[Labimotion::Prop::LAYERS] || {}
85
- options = ds.properties[Labimotion::Prop::SEL_OPTIONS]
86
- layer_keys = layers.keys.sort_by { |key| layers[key]['position'] }
87
- layer_keys.each do |key|
212
+ def process_layers(sheet)
213
+ layers = @dataset.properties[Labimotion::Prop::LAYERS] || {}
214
+ options = @dataset.properties[Labimotion::Prop::SEL_OPTIONS]
215
+
216
+ sort_layers(layers).each do |key|
88
217
  layer = layers[key]
89
- sheet.add_row([layer['label'], ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], style: layer_style)
90
- sorted_fields = layer[Labimotion::Prop::FIELDS].sort_by { |obj| obj['position'] }
91
- sorted_fields.each do |field|
92
- next if field['type'] == 'dummy'
93
-
94
- type = field['type']
95
- from_device = field['device'].present? ? 'Device' : ''
96
- from_device = field['system'].present? ? 'Chemotion' : from_device
97
- type = "#{field['type']}-#{field['option_layers']}" if field['type'] == Labimotion::FieldType::SELECT || field['type'] == Labimotion::FieldType::SYSTEM_DEFINED
98
-
99
- show_value = field['value'] =~ /\A\d+,\d+\z/ ? field['value']&.gsub(',', '.') : field['value']
100
- ols_short_form = (field['ontology'] && field['ontology']['short_form']) || ''
101
- ols_label = (field['ontology'] && field['ontology']['label']) || ''
102
- ols_iri = (field['ontology'] && field['ontology']['iri']) || ''
103
- sheet.add_row([' ', field['label'], nil, field['value_system'], field['field'], type, from_device, field['dkey'], nil, ols_short_form, ols_label, ols_iri].freeze)
104
-
105
- case field['type']
106
- when Labimotion::FieldType::SELECT
107
- sheet.rows.last.cells[2].type = :string
108
- sheet.rows.last.cells[8].type = :string
109
- show_value = opt_value(field, options) if show_value.present?
110
- when Labimotion::FieldType::SYSTEM_DEFINED, Labimotion::FieldType::INTEGER
111
- sheet.rows.last.cells[2].type = :float
112
- sheet.rows.last.cells[8].type = :float
113
- else
114
- sheet.rows.last.cells[2].type = :string
115
- sheet.rows.last.cells[8].type = :string
116
- end
117
- sheet.rows.last.cells[2].value = show_value
118
- sheet.rows.last.cells[8].value = field['system'] || field['device']
218
+ add_layer_row(sheet, layer)
219
+ process_fields(sheet, layer, options)
220
+ end
221
+ end
119
222
 
223
+ def sort_layers(layers)
224
+ layers.keys.sort_by { |key| layers[key]['position'] }
225
+ end
120
226
 
121
- end
122
- # sheet.column_widths nil, nil, nil, nil, 0, 0, 0, 0, 0
227
+ def add_layer_row(sheet, layer)
228
+ layer_style = sheet.styles.add_style(b: true, bg_color: 'CEECF5')
229
+ sheet.add_row([layer['label']] + [' '] * 8, style: layer_style)
230
+ end
231
+
232
+ def process_fields(sheet, layer, options)
233
+ sorted_fields = sort_fields(layer)
234
+
235
+ sorted_fields.each do |field|
236
+ next if field['type'] == 'dummy'
237
+
238
+ add_field_row(sheet, field)
239
+ set_field_types(sheet, field, options)
123
240
  end
241
+ end
242
+
243
+ def sort_fields(layer)
244
+ layer[Labimotion::Prop::FIELDS].sort_by { |obj| obj['position'] }
245
+ end
246
+
247
+ def add_field_row(sheet, field)
248
+ type = determine_field_type(field)
249
+ from_device = determine_source(field)
250
+ show_value = format_value(field['value'])
251
+ ontology_data = extract_ontology_data(field)
252
+
253
+ row_data = [
254
+ ' ',
255
+ field['label'],
256
+ nil,
257
+ field['value_system'],
258
+ field['field'],
259
+ type,
260
+ from_device,
261
+ field['dkey'],
262
+ nil
263
+ ] + ontology_data
264
+
265
+ sheet.add_row(row_data.freeze)
266
+ end
267
+
268
+ def determine_field_type(field)
269
+ return "#{field['type']}-#{field['option_layers']}" if [
270
+ Labimotion::FieldType::SELECT,
271
+ Labimotion::FieldType::SYSTEM_DEFINED
272
+ ].include?(field['type'])
273
+
274
+ field['type']
275
+ end
276
+
277
+ def determine_source(field)
278
+ return 'Chemotion' if field['system'].present?
279
+ return 'Device' if field['device'].present?
280
+ ''
281
+ end
282
+
283
+ def format_value(value)
284
+ return value&.gsub(',', '.') if value =~ /\A\d+,\d+\z/
285
+ value
286
+ end
287
+
288
+ def extract_ontology_data(field)
289
+ ontology = field['ontology'] || {}
290
+ [
291
+ ontology['short_form'] || '',
292
+ ontology['label'] || '',
293
+ ontology['iri'] || ''
294
+ ]
295
+ end
296
+
297
+ def set_field_types(sheet, field, options)
298
+ last_row = sheet.rows.last
299
+
300
+ case field['type']
301
+ when Labimotion::FieldType::SELECT
302
+ set_select_field_type(last_row, field, options)
303
+ when Labimotion::FieldType::SYSTEM_DEFINED, Labimotion::FieldType::INTEGER
304
+ set_numeric_field_type(last_row)
305
+ else
306
+ set_string_field_type(last_row)
307
+ end
308
+
309
+ set_field_values(last_row, field)
310
+ end
311
+
312
+ def set_select_field_type(row, field, options)
313
+ row.cells[2].type = :string
314
+ row.cells[8].type = :string
315
+ row.cells[2].value = opt_value(field, options) if field['value'].present?
316
+ end
317
+
318
+ def set_numeric_field_type(row)
319
+ row.cells[2].type = :float
320
+ row.cells[8].type = :float
321
+ end
322
+
323
+ def set_string_field_type(row)
324
+ row.cells[2].type = :string
325
+ row.cells[8].type = :string
326
+ end
327
+
328
+ def set_field_values(row, field)
329
+ row.cells[8].value = field['system'] || field['device']
330
+ end
331
+
332
+ def spectra_info
333
+ name_mapping = process_csv_files
334
+ return if name_mapping.nil? || name_mapping.length <= 1
335
+
336
+ create_mapping_sheet(name_mapping)
124
337
  rescue StandardError => e
125
338
  Labimotion.log_exception(e)
126
339
  end
127
340
 
341
+ def process_csv_files
342
+ cds_csv = Container.find(@id).attachments.where(aasm_state: 'csv').order(:filename)
343
+ return if cds_csv.empty?
344
+
345
+ cds_csv.each_with_index.map do |att, index|
346
+ process_single_csv(att, index)
347
+ end.compact
348
+ end
349
+
350
+ def process_single_csv(attachment, index)
351
+ sheet_name = "Sheet#{index + 1}"
352
+ sheet = @xfile.workbook.add_worksheet(name: sheet_name)
353
+
354
+ File.open(attachment.attachment_url) do |file|
355
+ first_line = file.readline.chomp
356
+ next unless cv_spc?(first_line)
357
+
358
+ process_csv_content(first_line, file, sheet)
359
+ end
360
+ [sheet_name, attachment.filename]
361
+ end
362
+
363
+ def process_csv_content(first_line, file, sheet)
364
+ lines = []
365
+ file.each_line.with_index do |line, index|
366
+ line = first_line if index.zero?
367
+ row_data = line.split(',')
368
+ sheet.add_row(row_data)
369
+ lines << row_data if cv_data?(row_data, index)
370
+ end
371
+ @spectra_values << lines if lines.any?
372
+ end
373
+
374
+ def create_mapping_sheet(name_mapping)
375
+ first_sheet = @xfile.workbook.worksheets&.first
376
+ return unless first_sheet
377
+
378
+ header_style = create_header_style(first_sheet)
379
+ first_sheet.add_row(['Sheet name', 'File name'], style: header_style)
380
+
381
+ name_mapping.each do |sheet_name, filename|
382
+ first_sheet.add_row([sheet_name.to_s, filename.to_s])
383
+ end
384
+ end
385
+
386
+
387
+ def create_header_style(sheet)
388
+ sheet.styles.add_style(
389
+ sz: 12,
390
+ fg_color: 'FFFFFF',
391
+ bg_color: '00008B',
392
+ border: {
393
+ style: :thick,
394
+ color: 'FF777777',
395
+ edges: [:bottom]
396
+ }
397
+ )
398
+ end
399
+
128
400
  def opt_value(field, options)
129
401
  return nil if field.nil? || options.nil? || field['value']&.empty? || field['option_layers']&.empty?
130
402
  return nil unless opts = options.fetch(field['option_layers'], nil)&.fetch('options', nil)
@@ -136,45 +408,18 @@ module Labimotion
136
408
  field['value']
137
409
  end
138
410
 
139
- def spectra(id)
140
- name_mapping = []
141
- wb = @xfile.workbook
142
- gds = Labimotion::Dataset.find_by(element_id: id, element_type: 'Container')
143
- cds = Container.find(id)
144
- cds_csv = cds.attachments.where(aasm_state: 'csv').order(:filename)
145
- csv_length = cds_csv.length
146
- return if csv_length.zero?
147
- cds_csv.each_with_index do |att, idx|
148
- sheet_name = "Sheet#{idx+1}"
149
- sheet = @xfile.workbook.add_worksheet(name: sheet_name)
150
- name_mapping.push([sheet_name, att.filename])
151
- File.open(att.attachment_url) do |fi|
152
- fi.each_line do |line|
153
- sheet.add_row(line.split(','))
154
- end
155
- end
156
- end
157
-
158
- if name_mapping.length > 1
159
- first_sheet = @xfile.workbook.worksheets&.first
160
- header_style = first_sheet&.styles.add_style(sz: 12, fg_color: 'FFFFFF', bg_color: '00008B', border: { style: :thick, color: 'FF777777', edges: [:bottom] })
161
- first_sheet&.add_row(['Sheet name', 'File name'], style: header_style)
162
- name_mapping&.each do |mapping|
163
- next if mapping.length < 2
164
-
165
- @xfile.workbook.worksheets&.first&.add_row([mapping[0].to_s, mapping[1].to_s])
166
- end
167
- end
411
+ def cv_spc?(first_line)
412
+ data = first_line.split(',')
413
+ data && data.length > 2 && data[2] == Constants::CHMO::CV
414
+ end
168
415
 
416
+ def cv_data?(data, idx)
417
+ data.length > 7 && idx > 7
169
418
  end
170
419
 
171
420
  def header
172
421
  ['Layer Label', 'Field Label', 'Value', 'Unit', 'Name', 'Type', 'Source?', 'Source identifier', 'Source data', 'Ontology', 'Ontology Label', 'iri'].freeze
173
422
  end
174
423
 
175
- def read
176
- @xfile.to_stream.read
177
- end
178
-
179
424
  end
180
425
  end