Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: import samples additional attributes - flash point and refractive index #2214

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions app/api/helpers/report_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,10 @@ def build_sql_reaction_sample(columns, c_id, ids, checkedAll = false)
# deleted_at: ['wp.deleted_at', nil, 10],
molecule_name: ['mn."name"', '"molecule name"', 1],
molarity_value: ['s."molarity_value"', '"molarity_value"', 0],
molarity_unit: ['s."molarity_unit"', '"molarity_unit"', 0],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/ModuleLength: Module has too many lines. [615/100]

dry_solvent: ['s."dry_solvent"', '"dry_solvent"', 0],
flash_point: ['s."flash_point"', '"flash point"', 0],
refractive_index: ['s."refractive_index"', '"refractive index"', 0],
},
sample_id: {
external_label: ['s.external_label', '"sample external label"', 0],
Expand Down Expand Up @@ -634,14 +637,18 @@ def build_sql_reaction_sample(columns, c_id, ids, checkedAll = false)
}.freeze

def custom_column_query(table, col, selection, user_id, attrs)
if col == 'user_labels'
selection << "labels_by_user_sample(#{user_id}, s_id) as user_labels"
elsif col == 'literature'
selection << "literatures_by_element('Sample', s_id) as literatures"
elsif col == 'cas'
selection << "s.xref->>'cas' as cas"
column_map = {
'user_labels' => "labels_by_user_sample(#{user_id}, s_id) as user_labels",
'literature' => "literatures_by_element('Sample', s_id) as literatures",
'cas' => "s.xref->>'cas' as cas",
'refractive_index' => "s.xref->>'refractive_index' as refractive_index",
'flash_point' => "s.xref->>'flash_point' as flash_point",
}

if column_map[col]
selection << column_map[col]
elsif (s = attrs[table][col.to_sym])
selection << ("#{s[1] && s[0]} as #{s[1] || s[0]}")
selection << "#{s[1] && s[0]} as #{s[1] || s[0]}"
end
end

Expand Down
15 changes: 9 additions & 6 deletions app/packs/src/components/contextActions/ModalExport.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,18 @@ export default class ModalExport extends React.Component {
{ value: ['molarity_value', 'molarity_unit'], text: 'molarity', checked: false },
{ value: 'density', text: 'density', checked: false },
{ value: 'molfile', text: 'molfile', checked: false },
// {value: "purity", text: "purity", checked: false},
{ value: "solvent", text: "solvent", checked: false },
{ value: "location", text: "location", checked: false },
{ value: "is_top_secret", text: "is top secret?", checked: false },
{ value: "dry_solvent", text: "dry solvent", checked: false },
{ value: 'purity', text: 'purity', checked: false },
{ value: 'solvent', text: 'solvent', checked: false },
{ value: 'location', text: 'location', checked: false },
{ value: 'is_top_secret', text: 'is top secret?', checked: false },
{ value: 'anhydrous', text: 'dry solvent', checked: false },
// {value: "ancestry", text: "ancestry", checked: false},
{ value: 'imported_readout', text: 'imported readout', checked: false },
// {value: "identifier", text: "identifier", checked: false},
{ value: 'melting_point', text: 'melting point', checked: false },
{ value: 'boiling_point', text: 'boiling point', checked: false },
{ value: 'refractive_index', text: 'refractive index', checked: false },
{ value: 'flash_point', text: 'flash point', checked: false },
{ value: 'created_at', text: 'created at', checked: true },
{ value: 'updated_at', text: 'updated at', checked: false },
{ value: 'user_labels', text: 'user labels', checked: false },
Expand Down Expand Up @@ -104,6 +106,7 @@ export default class ModalExport extends React.Component {
{ value: 'vendor', text: 'vendor', checked: false },
{ value: 'order_number', text: 'order number', checked: false },
{ value: 'amount', text: 'amount', checked: false },
{ value: 'volume', text: 'volume', checked: false },
{ value: 'price', text: 'price', checked: false },
{ value: 'person', text: 'person', checked: false },
{ value: 'required_date', text: 'required date', checked: false },
Expand All @@ -112,7 +115,7 @@ export default class ModalExport extends React.Component {
{
value: ['safety_sheet_link_merck', 'safety_sheet_link_thermofischer'],
text: 'safety sheet link',
checked: false
checked: false,
},
{ value: ['product_link_merck', 'product_link_thermofischer'], text: 'product link', checked: false },
{ value: 'pictograms', text: 'pictograms', checked: false },
Expand Down
21 changes: 11 additions & 10 deletions lib/export/export_chemicals.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Export
class ExportChemicals
CHEMICAL_FIELDS = %w[
chemical_sample_id cas status vendor order_number amount price person required_date
chemical_sample_id cas status vendor order_number amount volume price person required_date
ordered_date required_by pictograms h_statements p_statements safety_sheet_link_merck
safety_sheet_link_thermofischer product_link_merck product_link_thermofischer
host_building host_room host_cabinet host_group owner borrowed_by current_building
Expand All @@ -20,6 +20,7 @@ class ExportChemicals
person: ['c."chemical_data"->0->\'person\'', '"person"', nil],
price: ['c."chemical_data"->0->\'price\'', '"price"', nil],
amount: ['c."chemical_data"->0->\'amount\'', '"amount"', nil],
volume: ['c."chemical_data"->0->\'volume\'', '"volume"', nil],
order_number: ['c."chemical_data"->0->\'order_number\'', '"order_number"', nil],
required_date: ['c."chemical_data"->0->\'required_date\'', '"required_date"', nil],
required_by: ['c."chemical_data"->0->\'required_by\'', '"required_by"', nil],
Expand Down Expand Up @@ -67,7 +68,7 @@ def self.format_chemical_results(result)
end

def self.construct_column_name(column_name, index, columns_index)
format_chemical_column = ['p statements', 'h statements', 'amount', 'safety sheet link thermofischer',
format_chemical_column = ['p statements', 'h statements', 'amount', 'volume', 'safety sheet link thermofischer',
'safety sheet link merck', 'product link thermofischer', 'product link merck'].freeze
if column_name.is_a?(String) && CHEMICAL_FIELDS.include?(column_name)
column_name = column_name.tr('_', ' ')
Expand All @@ -84,12 +85,12 @@ def self.construct_column_name_hash(columns_index, column_name, index)
columns_index['p_statements'] = index
when 'h statements'
columns_index['h_statements'] = index
when 'amount'
columns_index['amount'] = index
when 'safety sheet link merck', 'safety sheet link thermofischer'
columns_index['safety_sheet_link'].push(index)
when 'product link merck', 'product link thermofischer'
columns_index['product_link'].push(index)
else
columns_index[column_name] = index
end
end

Expand All @@ -110,8 +111,8 @@ def self.format_row(row, columns_index, indexes_to_delete)
case index
when columns_index['p_statements'], columns_index['h_statements']
value = format_p_and_h_statements(value)
when columns_index['amount']
value = format_chemical_amount(value)
when columns_index['amount'], columns_index['volume']
value = format_chemical_amount_or_volume(value)
when columns_index['safety_sheet_link'][0]
value = format_link(value, row, columns_index['safety_sheet_link'][1], indexes_to_delete)
when columns_index['product_link'][0]
Expand All @@ -126,10 +127,10 @@ def self.format_p_and_h_statements(value)
keys.join('-')
end

def self.format_chemical_amount(value)
amount_value_unit = JSON.parse(value).values
sorted = amount_value_unit.sort_by { |element| [element.is_a?(Integer) || element.is_a?(Float) ? 0 : 1, element] }
sorted.join
def self.format_chemical_amount_or_volume(value)
value_unit = JSON.parse(value).values
sorted = value_unit.sort_by { |element| [element.is_a?(Integer) || element.is_a?(Float) ? 0 : 1, element] }
sorted.join(' ')
end

def self.format_link(value, row, next_index, indexes_to_delete)
Expand Down
8 changes: 8 additions & 0 deletions lib/export/export_excel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ def filter_with_permission_and_detail_level(sample)
string.split(',').join(' - ')
elsif column == 'solvent'
extract_label_from_solvent_column(sample[column]) || ''
elsif column == 'refractive index'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/AbcSize: Assignment Branch Condition size for filter_with_permission_and_detail_level is too high. [<13, 34, 28> 45.92/25]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/CyclomaticComplexity: Cyclomatic complexity for filter_with_permission_and_detail_level is too high. [20/7]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/MethodLength: Method has too many lines. [32/30]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/PerceivedComplexity: Perceived complexity for filter_with_permission_and_detail_level is too high. [22/8]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/AbcSize: Assignment Branch Condition size for filter_with_permission_and_detail_level is too high. [<13, 35, 30> 47.9/25]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/CyclomaticComplexity: Cyclomatic complexity for filter_with_permission_and_detail_level is too high. [21/7]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/MethodLength: Method has too many lines. [34/30]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/PerceivedComplexity: Perceived complexity for filter_with_permission_and_detail_level is too high. [23/8]

sample['refractive_index']
elsif column == 'flash point'
flash_point_format(sample['flash_point'])
elsif column == 'molarity'
"#{sample['molarity_value']} #{sample['molarity_unit']}"
elsif column == 'density'
"#{sample['density']} g/ml"
else
sample[column]
end
Expand Down
36 changes: 27 additions & 9 deletions lib/export/export_sdf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ def read

private

def concatenate_data(sample, data, headers = @headers)
headers.each do |column|
next unless column

raw_value = case column
when 'molarity'
"#{sample['molarity_value']} #{sample['molarity_unit']}"
when 'flash point'
sample['flash_point']
when 'refractive index'
sample['refractive_index']
when 'density'
"#{sample['density']} g/mL"
else
sample[column]
end
column_data = format_field(column, raw_value)
data.concat(column_data)
end
data
end

def filter_with_permission_and_detail_level(sample)
if sample['shared_sync'] == 'f' || sample['shared_sync'] == false
data = validate_molfile(sample['molfile'])
Expand All @@ -44,10 +66,7 @@ def filter_with_permission_and_detail_level(sample)
end
data = data.rstrip
data += "\n"
@headers.each do |column|
column_data = format_field(column, sample[column])
data.concat(column_data)
end
data = concatenate_data(sample, data)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/AbcSize: Assignment Branch Condition size for filter_with_permission_and_detail_level is too high. [<12, 23, 12> 28.58/25]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/CyclomaticComplexity: Cyclomatic complexity for filter_with_permission_and_detail_level is too high. [10/7]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metrics/PerceivedComplexity: Perceived complexity for filter_with_permission_and_detail_level is too high. [11/8]

else
# return no data if molfile not allowed
return nil if sample['dl_s'].zero?
Expand All @@ -62,12 +81,8 @@ def filter_with_permission_and_detail_level(sample)
# NB: as of now , only dl 0 and 10 are implemented
dl = 10 if dl.positive?
headers = instance_variable_get("headers#{sample['dl_s']}#{dl}")
headers.each do |column|
next unless column
data = concatenate_data(sample, data, headers)

column_data = format_field(column, sample[column])
data.concat(column_data)
end
end
data.concat("\$\$\$\$\n")
end
Expand All @@ -81,11 +96,14 @@ def extract_reference_values(raw_value)
def format_field(column, raw_value)
field = column.gsub(/\s+/, '_').upcase
reference_values = ['melting pt', 'boiling pt']
flash_point = ['flash point', 'flash_point']
sample_column =
if reference_values.include?(column)
extract_reference_values(raw_value)
elsif column == 'solvent'
extract_label_from_solvent_column(raw_value) || ''
elsif flash_point.include?(column)
flash_point_format(raw_value)
else
raw_value
end
Expand Down
25 changes: 23 additions & 2 deletions lib/export/export_table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,32 @@ def generate_headers_reaction
}
end

def flash_point_format(value)
return if value.blank?

# Add quotes around unquoted keys & values
value = value.gsub(/(\w+):/, '"\1":')
value = value.gsub(/:\s*([^",{}\s]+)/, ':"\1"')

flash_point = JSON.parse(value)
"#{flash_point['value']} #{flash_point['unit']}"
rescue JSON::ParserError => e
Rails.logger.warn("Failed to parse flash_point JSON: #{e.message}")
nil
end

def format_headers(headers)
headers.map! do |header|
header.tr('_', ' ')
header = header.tr('_', ' ')
if header.scan('molarity value').first == 'molarity value'
'molarity'
elsif header == 'molarity unit'
nil
else
header
end
end
headers
headers.compact
end

def generate_headers_sample
Expand Down
58 changes: 40 additions & 18 deletions lib/import/import_chemicals.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Import
class ImportChemicals
SAFETY_PHRASES = %w[pictograms h_statements p_statements].freeze
AMOUNT = %w[amount].freeze
VOLUME = %w[volume].freeze
SAFETY_SHEET = %w[safety_sheet_link_merck product_link_merck].freeze
KEYS_TO_EXCLUDE = SAFETY_SHEET + %w[cas].freeze
SIGMA_ALDRICH_PATTERN = /(sigmaaldrich|merck)/.freeze
Expand All @@ -14,13 +15,14 @@ class ImportChemicals
THERMOFISCHER_PATTERN => 'Alfa',
}.freeze
CHEMICAL_FIELDS = [
'cas', 'status', 'vendor', 'order number', 'amount', 'price', 'person', 'required date', 'ordered date',
'cas', 'status', 'vendor', 'order number', 'amount', 'volume', 'price', 'person', 'required date', 'ordered date',
'required by', 'pictograms', 'h statements', 'p statements', 'safety sheet link', 'product link', 'host building',
'host room', 'host cabinet', 'host group', 'owner', 'borrowed by', 'current building', 'current room',
'current cabinet', 'current group', 'disposal info', 'important notes'
].freeze
GHS_VALUES = %w[GHS01 GHS02 GHS03 GHS04 GHS05 GHS06 GHS07 GHS08 GHS09].freeze
AMOUNT_UNITS = %w[g mg μg].freeze
VOLUME_UNITS = %w[ml l μl].freeze

def self.build_chemical(row, header)
chemical = Chemical.new
Expand All @@ -46,28 +48,32 @@ def self.skip_import?(value, column_header)
value.blank? || column_header.nil?
end

def self.process_column(chemical, column_header, value)
map_column = CHEMICAL_FIELDS.find { |e| e == column_header.downcase.rstrip }
key = to_snake_case(column_header)
format_value = value.strip
def self.build_chemical_data(map_column, chemical, key, formated_value)
if map_column.present? && should_process_key(key)
chemical['chemical_data'][0][key] = format_value
chemical['chemical_data'][0][key] = formated_value
elsif SAFETY_SHEET.include?(key)
set_safety_sheet(chemical, key, format_value)
set_safety_sheet(chemical, key, formated_value)
elsif SAFETY_PHRASES.include?(key)
set_safety_phrases(chemical, key, format_value)
elsif AMOUNT.include?(key)
set_amount(chemical, format_value)
set_safety_phrases(chemical, key, formated_value)
elsif AMOUNT.include?(key) || VOLUME.include?(key)
set_chemical_amount_or_volume(chemical, key, formated_value)
end
end

def self.process_column(chemical, column_header, value)
map_column = CHEMICAL_FIELDS.find { |e| e == column_header.downcase.rstrip }
key = to_snake_case(column_header)
formated_value = value.strip
build_chemical_data(map_column, chemical, key, formated_value)
end

def self.to_snake_case(column_header)
key = column_header.downcase.rstrip.gsub(/\s+/, '_')
key == 'owner' ? 'host_owner' : key
end

def self.should_process_key(key)
KEYS_TO_EXCLUDE.exclude?(key) && (AMOUNT + SAFETY_PHRASES).exclude?(key)
KEYS_TO_EXCLUDE.exclude?(key) && (AMOUNT + VOLUME + SAFETY_PHRASES).exclude?(key)
end

def self.set_safety_sheet(chemical, key, value)
Expand Down Expand Up @@ -159,14 +165,30 @@ def self.set_safety_phrases(chemical, key, value)
assign_phrases(key, values, phrases)
end

def self.set_amount(chemical, value)
chemical['chemical_data'][0]['amount'] = {} if chemical['chemical_data'][0]['amount'].nil?
quantity = value.to_f
unit = value.gsub(/\d+(\.\d+)?/, '')
return chemical unless AMOUNT_UNITS.include?(unit)
def self.extract_quantity(value)
value.to_f
end

def self.extract_unit(value)
value.gsub(/[0-9.]+/, '').strip
end

def self.format_value_unit(chemical, key, value, unit)
chemical['chemical_data'][0][key] ||= {}
chemical['chemical_data'][0][key]['value'] = value
chemical['chemical_data'][0][key]['unit'] = unit
chemical
end

def self.set_chemical_amount_or_volume(chemical, key, value)
quantity = extract_quantity(value)
unit = extract_unit(value)

return chemical if key == 'amount' && AMOUNT_UNITS.exclude?(unit)

return chemical if key == 'volume' && VOLUME_UNITS.exclude?(unit)

chemical['chemical_data'][0]['amount']['value'] = quantity
chemical['chemical_data'][0]['amount']['unit'] = unit
format_value_unit(chemical, key, quantity, unit)
end
end
end
Loading
Loading