Module: Labimotion::MttHelpers

Extended by:
Grape::API::Helpers
Defined in:
lib/labimotion/helpers/mtt_helpers.rb

Overview

MTT Helpers

Constant Summary collapse

TPA_EXPIRATION =
72.hours

Instance Method Summary collapse

Instance Method Details

#create_analysis_with_csv(element, user, csv_data, dose_resp_request, output) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 182

def create_analysis_with_csv(element, user, csv_data, dose_resp_request, output)
  analysis, dataset = create_analysis_with_dataset(
    element: element,
    analysis_name: "MTT Analysis #{dose_resp_request.request_id}-#{output&.id}",
    dataset_name: 'new',
    analysis_attributes: {}
  )

  # Determine content type based on file extension
  content_type = case File.extname(csv_data[:filename]).downcase
                 when '.xlsx'
                   'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
                 when '.xls'
                   'application/vnd.ms-excel'
                 when '.csv'
                   'text/csv'
                 else
                   'application/octet-stream'
                 end

  # Create a temporary file for the attachment
  temp_file = Tempfile.new([File.basename(csv_data[:filename], '.*'), File.extname(csv_data[:filename])])
  begin
    temp_file.binmode
    temp_file.write(csv_data[:content])
    temp_file.rewind

    # Create attachment for CSV/Excel file
    attachment = Attachment.new(
      filename: csv_data[:filename],
      file_path: temp_file.path,
      created_by: user.id,
      created_for: user.id,
      attachable_type: 'Container',
      attachable_id: dataset.id,
      content_type: content_type
    )
    attachment.save! if attachment.valid?

    { analysis: analysis, dataset: dataset, attachment: attachment }
  ensure
    temp_file.close
    temp_file.unlink
  end
end

#create_analysis_with_dataset(element:, analysis_name: 'New Analysis', dataset_name: 'New Dataset', analysis_attributes: {}) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 228

def create_analysis_with_dataset(
  element:,
  analysis_name: 'New Analysis',
  dataset_name: 'New Dataset',
  analysis_attributes: {}
)
  # Ensure the element has a root container
  ensure_root_container(element)

  # Get or create the analyses container
  analyses_container = element.container.children.find_or_create_by(container_type: 'analyses')

  # Prepare default extended_metadata for analysis
   = {
    'content' => '{"ops":[{"insert":""}]}',
    'report' => true
  }
   = .merge(analysis_attributes[:extended_metadata] || {})

  # Create the analysis container
  analysis_container = analyses_container.children.create(
    container_type: 'analysis',
    name: analysis_name,
    description: analysis_attributes[:description] || '',
    extended_metadata: 
  )

  # Create the dataset container nested under the analysis
  dataset_container = analysis_container.children.create(
    container_type: 'dataset',
    name: dataset_name,
    description: '',
    extended_metadata: {}
  )

  [analysis_container, dataset_container]
end

#download_json_to_external_appObject



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 57

def download_json_to_external_app
  # Get token from route params
  token = params[:token]
  dose_resp_request = validate_token(token)

  # Validate user access
  validate_user_access(dose_resp_request)

  # Track access and update state
  # dose_resp_request.track_access!
  dose_resp_request.mark_processing! if dose_resp_request.state == Labimotion::DoseRespRequest::STATE_INITIAL
  # Return wellplates metadata as JSON
  # Access the wellplates array from the metadata structure
  wellplates_data = dose_resp_request.&.dig('wellplates') ||
                    dose_resp_request.&.dig(:wellplates) ||
                    []

  response_data = {
    id: dose_resp_request.id.to_s,
    request_id: dose_resp_request.request_id,
    element_info: extract_element_properties(dose_resp_request.element),
    wellplates: wellplates_data
  }

  status 200
  response_data
rescue => e
  error!("Error: #{e.message}", 500)
end

#ensure_root_container(element) ⇒ Object



266
267
268
269
270
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 266

def ensure_root_container(element)
  return if element.container.present?

  element.container = Container.create_root_container
end

#extract_element_properties(element) ⇒ Object



386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 386

def extract_element_properties(element)
  props = {
    id: element.id.to_s,
    name: element.name
  }

  layers = element.properties.dig('layers', 'general_information', 'fields') ||
           element.properties.dig(:layers, :general_information, :fields) || []

  endpoint_field = layers.find { |f| f['field'] == 'Endpoint' || f[:field] == 'Endpoint' }
  props[:endpoint] = endpoint_field['value'] || endpoint_field[:value] if endpoint_field

  props
end

#extract_readout_titles(wellplate) ⇒ Object

{

  success: response.is_a?(Net::HTTPSuccess),
  status: response.code,
  body: (JSON.parse(response.body) rescue response.body),
  message: response.message
}

rescue StandardError => e

{
  success: false,
  error: e.message,
  backtrace: e.backtrace.first(5)
}

end



302
303
304
305
306
307
308
309
310
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 302

def extract_readout_titles(wellplate)
  # Extract readout titles from wellplate
  if wellplate.respond_to?(:readout_titles)
    titles = wellplate.readout_titles
    return titles if titles.is_a?(Array)
    return JSON.parse(titles) if titles.is_a?(String)
  end
  []
end

#extract_readouts(well) ⇒ Object



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 342

def extract_readouts(well)
  # Extract readouts from well
  # Readouts are typically stored as JSON data in the well
  readouts = if well.respond_to?(:readouts) && well.readouts.is_a?(Array)
               well.readouts
             elsif well.respond_to?(:readouts) && well.readouts.is_a?(String)
               JSON.parse(well.readouts) rescue []
             elsif well.respond_to?(:readouts) && well.readouts.is_a?(Hash)
               well.readouts.values rescue []
             else
               []
             end

  # Filter out empty readouts (both unit and value are blank)
  readouts.select do |readout|
    readout.is_a?(Hash) &&
    (readout['unit'].to_s.present? || readout['value'].to_s.present? ||
     readout[:unit].to_s.present? || readout[:value].to_s.present?)
  end
end

#extract_sample(well) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 363

def extract_sample(well)
  # Extract sample information from well
  if well.respond_to?(:sample) && well.sample.present?
    sample = well.sample
    return {
      id: sample.id,
      short_label: sample.short_label,
      conc: sample.try(:molarity_value) || 0
    }
  end
  nil
end

#extract_wells(wellplate) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 312

def extract_wells(wellplate)
  # Extract wells data from wellplate
  wells = wellplate.wells || []
  wells.map do |well|
    # Position might be stored as a hash or separate fields
    position = if well.respond_to?(:position) && well.position.is_a?(Hash)
                 well.position
               elsif well.respond_to?(:position_x)
                 { x: well.position_x, y: well.position_y }
               else
                 { x: 0, y: 0 }
               end

    well_data = {
      id: well.id,
      position: position
    }

    # Only include readouts if they have values
    readouts = extract_readouts(well)
    well_data[:readouts] = readouts if readouts.present?

    # Only include sample if it exists
    sample = extract_sample(well)
    well_data[:sample] = sample if sample.present?

    well_data
  end
end

#generate_element_metadata(element) ⇒ Object



402
403
404
405
406
407
408
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 402

def (element)
  {
    id: wellplate.id.to_s,
    readoutTitles: extract_readout_titles(wellplate),
    wells: extract_wells(wellplate)
  }
end

#generate_json_data(wellplates) ⇒ Object



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 411

def generate_json_data(wellplates)
  # # Generate wellplates metadata
   = (wellplates)

  # Generate JSON structure
  json_data = {
    id: element.id.to_s,
    request_id: dose_resp_request.id.to_s,
    wellplates: 
  }
  # Save to JSON file
  filename = "mtt_request_#{element.id}_#{dose_resp_request.id}.json"
  filepath = Rails.root.join('tmp', filename)
  File.write(filepath, JSON.pretty_generate(json_data))
  json_data
end

#generate_wellplates_metadata(wellplates) ⇒ Object



376
377
378
379
380
381
382
383
384
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 376

def (wellplates)
  wellplates.map do |wellplate|
    {
      id: wellplate.id.to_s,
      readoutTitles: extract_readout_titles(wellplate),
      wells: extract_wells(wellplate)
    }
  end
end

#get_external_app_urlObject



24
25
26
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 24

def get_external_app_url
  ENV['MTT_EXTERNAL_APP_URL'] || 'http://localhost:4050'
end

#process_zip_file(tempfile) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 157

def process_zip_file(tempfile)
  wellplates_data = nil
  csv_data = nil

  Zip::File.open(tempfile.path) do |zip_file|
    zip_file.each do |entry|
      if entry.name.end_with?('.json')
        # Read JSON file
        json_content = entry.get_input_stream.read
        json_data = JSON.parse(json_content).with_indifferent_access
        wellplates_data = json_data[:Output]
      elsif entry.name.end_with?('.xls', '.xlsx', '.csv')
        # Read CSV/Excel file - extract just the basename without path
        csv_content = entry.get_input_stream.read
        csv_data = {
          filename: File.basename(entry.name),
          content: csv_content
        }
      end
    end
  end

  [wellplates_data, csv_data]
end

#token_url(dose_resp_request) ⇒ Object



15
16
17
18
19
20
21
22
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 15

def token_url(dose_resp_request)
  # Build the callback URL with token in path
  api_base_url = ENV['PUBLIC_URL'] || 'http://172.28.156.100:3000'
  callback_path = "/api/v1/public/mtt_apps/#{dose_resp_request.access_token}"
  callback_url = "#{api_base_url}#{callback_path}"

  callback_url
end

#upload_json_from_external_appObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 87

def upload_json_from_external_app
  # Get token from route params
  token = params[:token]
  dose_resp_request = validate_token(token)

  # Validate user access
  access_info = validate_user_access(dose_resp_request)
  user = access_info[:user]
  element = access_info[:element]
  # Handle file upload
  file_param = params['file'] || params[:file]
  error!('No file uploaded', 400) unless file_param && file_param.is_a?(Hash) && file_param['tempfile']

  tempfile = file_param['tempfile']
  filename = file_param['filename'] || 'upload'
  # Check if it's a zip file
  if filename.end_with?('.zip')
    # Process zip file
    wellplates_data, csv_data = process_zip_file(tempfile)

    error!('Missing JSON data in zip file', 400) unless wellplates_data

    # Create analysis container and dataset with CSV if present
    # if csv_data
    #   create_analysis_with_csv(element, user, csv_data, dose_resp_request, wellplates_data)
    # end
  # else
  #   # Handle single JSON file
  #   file_content = tempfile.read
  #   tempfile.rewind
  #   json_data = JSON.parse(file_content).with_indifferent_access
  #   wellplates_data = json_data[:Output]

  #   error!('Missing wellplates data', 400) unless wellplates_data
  end

  dose_resp_request.track_access!

  # Save output data to dose_resp_outputs table
  output = dose_resp_request.dose_resp_outputs.create!(
    output_data: { Output: wellplates_data }
  )
  if csv_data
    create_analysis_with_csv(element, user, csv_data, dose_resp_request, output)
  end

  dose_resp_request.update!(
    wellplates_metadata: { wellplates: wellplates_data },
    resp_message: 'Data updated successfully'
  )

  # Mark as completed
  dose_resp_request.mark_completed!

  status 200
  {
    success: true,
    message: 'Data updated successfully',
    request_id: dose_resp_request.id
  }
rescue JSON::ParserError => e
  error!("Invalid JSON: #{e.message}", 400)
rescue ActiveRecord::RecordInvalid => e
  dose_resp_request.mark_error!(e.message) if dose_resp_request
  error!("Validation error: #{e.message}", 422)
rescue => e
  dose_resp_request.mark_error!(e.message) if dose_resp_request
  error!("Error: #{e.message}", 500)
end

#validate_token(token) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 28

def validate_token(token)
  # Find the request by access token
  request = Labimotion::DoseRespRequest.find_by(access_token: token)
  error!('Token not found', 404) unless request

  # Check expiration
  error!('Token expired', 403) if request.expired?

  # Check revocation
  error!('Token revoked', 403) if request.revoked?

  request
end

#validate_user_access(dose_resp_request) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/labimotion/helpers/mtt_helpers.rb', line 42

def validate_user_access(dose_resp_request)
  # Get element and user
  element = dose_resp_request.element
  error!('Element not found', 404) unless element

  user = dose_resp_request.creator
  error!('User not found', 404) unless user

  # Check user has update permission on element using ElementPolicy
  policy = ElementPolicy.new(user, element)
  error!('Unauthorized', 403) unless policy.update?

  { element: element, user: user }
end