labimotion 2.2.0.rc7 → 2.2.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.
- checksums.yaml +4 -4
- data/lib/labimotion/apis/generic_element_api.rb +0 -1
- data/lib/labimotion/apis/labimotion_api.rb +0 -3
- data/lib/labimotion/entities/element_entity.rb +0 -1
- data/lib/labimotion/helpers/element_helpers.rb +15 -21
- data/lib/labimotion/helpers/param_helpers.rb +0 -6
- data/lib/labimotion/libs/converter.rb +2 -1
- data/lib/labimotion/libs/nmr_mapper.rb +2 -2
- data/lib/labimotion/models/concerns/attachment_converter.rb +1 -0
- data/lib/labimotion/models/concerns/element_fetchable.rb +2 -2
- data/lib/labimotion/models/element.rb +2 -8
- data/lib/labimotion/utils/search.rb +2 -2
- data/lib/labimotion/version.rb +1 -1
- data/lib/labimotion.rb +1 -8
- metadata +2 -10
- data/lib/labimotion/apis/dose_resp_request_api.rb +0 -241
- data/lib/labimotion/apis/element_variation_api.rb +0 -45
- data/lib/labimotion/apis/mtt_api.rb +0 -238
- data/lib/labimotion/entities/element_variation_entity.rb +0 -29
- data/lib/labimotion/helpers/mtt_helpers.rb +0 -428
- data/lib/labimotion/models/dose_resp_output.rb +0 -27
- data/lib/labimotion/models/dose_resp_request.rb +0 -93
- data/lib/labimotion/models/element_variation.rb +0 -21
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'labimotion/version'
|
|
4
|
-
require 'labimotion/libs/export_element'
|
|
5
|
-
require 'labimotion/helpers/mtt_helpers'
|
|
6
|
-
require 'labimotion/models/dose_resp_output'
|
|
7
|
-
|
|
8
|
-
module Labimotion
|
|
9
|
-
# Generic Element API
|
|
10
|
-
class MttAPI < Grape::API
|
|
11
|
-
helpers Labimotion::ParamHelpers
|
|
12
|
-
helpers Labimotion::MttHelpers
|
|
13
|
-
|
|
14
|
-
namespace :public do
|
|
15
|
-
resource :mtt_apps do
|
|
16
|
-
route_param :token do
|
|
17
|
-
desc 'Download data from MTT app (GET endpoint)'
|
|
18
|
-
get do
|
|
19
|
-
download_json_to_external_app
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
desc 'Upload modified data from MTT app (POST endpoint)'
|
|
23
|
-
post do
|
|
24
|
-
upload_json_from_external_app
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
resource :mtt do
|
|
31
|
-
namespace :requests do
|
|
32
|
-
desc 'Get MTT requests for current user'
|
|
33
|
-
get do
|
|
34
|
-
# Get all requests created by current user
|
|
35
|
-
requests = Labimotion::DoseRespRequest
|
|
36
|
-
.includes(:dose_resp_outputs)
|
|
37
|
-
.where(created_by: current_user.id)
|
|
38
|
-
.order(created_at: :desc)
|
|
39
|
-
|
|
40
|
-
# Return formatted response
|
|
41
|
-
requests.map do |req|
|
|
42
|
-
{
|
|
43
|
-
id: req.id,
|
|
44
|
-
request_id: req.request_id,
|
|
45
|
-
element_id: req.element_id,
|
|
46
|
-
state: req.state,
|
|
47
|
-
state_name: case req.state
|
|
48
|
-
when Labimotion::DoseRespRequest::STATE_ERROR then 'error'
|
|
49
|
-
when Labimotion::DoseRespRequest::STATE_INITIAL then 'initial'
|
|
50
|
-
when Labimotion::DoseRespRequest::STATE_PROCESSING then 'processing'
|
|
51
|
-
when Labimotion::DoseRespRequest::STATE_COMPLETED then 'completed'
|
|
52
|
-
else 'unknown'
|
|
53
|
-
end,
|
|
54
|
-
created_at: req.created_at,
|
|
55
|
-
expires_at: req.expires_at,
|
|
56
|
-
expired: req.expired?,
|
|
57
|
-
revoked: req.revoked?,
|
|
58
|
-
active: req.active?,
|
|
59
|
-
resp_message: req.resp_message,
|
|
60
|
-
last_accessed_at: req.last_accessed_at,
|
|
61
|
-
access_count: req.access_count || 0,
|
|
62
|
-
outputs: req.dose_resp_outputs.map do |output|
|
|
63
|
-
{
|
|
64
|
-
id: output.id,
|
|
65
|
-
output_data: output.output_data,
|
|
66
|
-
notes: output.notes,
|
|
67
|
-
created_at: output.created_at
|
|
68
|
-
}
|
|
69
|
-
end
|
|
70
|
-
}
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
desc 'Delete one or multiple MTT requests'
|
|
75
|
-
params do
|
|
76
|
-
requires :ids, type: Array[Integer], desc: 'Array of request IDs to delete'
|
|
77
|
-
end
|
|
78
|
-
delete do
|
|
79
|
-
# Find requests belonging to current user
|
|
80
|
-
requests = Labimotion::DoseRespRequest.where(
|
|
81
|
-
id: params[:ids],
|
|
82
|
-
created_by: current_user.id
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
if requests.empty?
|
|
86
|
-
error!('No requests found or unauthorized', 404)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
deleted_count = requests.count
|
|
90
|
-
requests.destroy_all
|
|
91
|
-
|
|
92
|
-
{
|
|
93
|
-
success: true,
|
|
94
|
-
message: "Successfully deleted #{deleted_count} request(s)",
|
|
95
|
-
deleted_count: deleted_count
|
|
96
|
-
}
|
|
97
|
-
rescue StandardError => e
|
|
98
|
-
error!("Error deleting requests: #{e.message}", 500)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
desc 'Update MTT request'
|
|
102
|
-
params do
|
|
103
|
-
requires :id, type: Integer, desc: 'Request ID'
|
|
104
|
-
optional :state, type: Integer, desc: 'State (-1: error, 0: initial, 1: processing, 2: completed)', values: [-1, 0, 1, 2]
|
|
105
|
-
optional :resp_message, type: String, desc: 'Response message'
|
|
106
|
-
optional :wellplates_metadata, type: Hash, desc: 'Wellplates metadata'
|
|
107
|
-
optional :input_metadata, type: Hash, desc: 'Input metadata'
|
|
108
|
-
optional :revoked, type: Boolean, desc: 'Revoke access token'
|
|
109
|
-
end
|
|
110
|
-
patch ':id' do
|
|
111
|
-
# Find request belonging to current user
|
|
112
|
-
request = Labimotion::DoseRespRequest.find_by(
|
|
113
|
-
id: params[:id],
|
|
114
|
-
created_by: current_user.id
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
error!('Request not found or unauthorized', 404) unless request
|
|
118
|
-
|
|
119
|
-
# Prepare update attributes
|
|
120
|
-
update_attrs = {}
|
|
121
|
-
update_attrs[:state] = params[:state] if params.key?(:state)
|
|
122
|
-
update_attrs[:resp_message] = params[:resp_message] if params.key?(:resp_message)
|
|
123
|
-
update_attrs[:wellplates_metadata] = params[:wellplates_metadata] if params.key?(:wellplates_metadata)
|
|
124
|
-
update_attrs[:input_metadata] = params[:input_metadata] if params.key?(:input_metadata)
|
|
125
|
-
update_attrs[:revoked_at] = params[:revoked] ? Time.current : nil if params.key?(:revoked)
|
|
126
|
-
|
|
127
|
-
if request.update(update_attrs)
|
|
128
|
-
{
|
|
129
|
-
success: true,
|
|
130
|
-
message: 'Request updated successfully',
|
|
131
|
-
request: {
|
|
132
|
-
id: request.id,
|
|
133
|
-
request_id: request.request_id,
|
|
134
|
-
state: request.state,
|
|
135
|
-
state_name: case request.state
|
|
136
|
-
when Labimotion::DoseRespRequest::STATE_ERROR then 'error'
|
|
137
|
-
when Labimotion::DoseRespRequest::STATE_INITIAL then 'initial'
|
|
138
|
-
when Labimotion::DoseRespRequest::STATE_PROCESSING then 'processing'
|
|
139
|
-
when Labimotion::DoseRespRequest::STATE_COMPLETED then 'completed'
|
|
140
|
-
else 'unknown'
|
|
141
|
-
end,
|
|
142
|
-
resp_message: request.resp_message,
|
|
143
|
-
revoked: request.revoked?,
|
|
144
|
-
updated_at: request.updated_at
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
else
|
|
148
|
-
error!("Validation error: #{request.errors.full_messages.join(', ')}", 422)
|
|
149
|
-
end
|
|
150
|
-
rescue ActiveRecord::RecordNotFound
|
|
151
|
-
error!('Request not found', 404)
|
|
152
|
-
rescue StandardError => e
|
|
153
|
-
error!("Error updating request: #{e.message}", 500)
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
namespace :outputs do
|
|
158
|
-
desc 'Delete one or multiple MTT outputs'
|
|
159
|
-
params do
|
|
160
|
-
requires :ids, type: Array[Integer], desc: 'Array of output IDs to delete'
|
|
161
|
-
end
|
|
162
|
-
delete do
|
|
163
|
-
# Find outputs where the parent request belongs to current user
|
|
164
|
-
outputs = Labimotion::DoseRespOutput
|
|
165
|
-
.joins(:dose_resp_request)
|
|
166
|
-
.where(
|
|
167
|
-
id: params[:ids],
|
|
168
|
-
dose_resp_requests: { created_by: current_user.id }
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
if outputs.empty?
|
|
172
|
-
error!('No outputs found or unauthorized', 404)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
deleted_count = outputs.count
|
|
176
|
-
outputs.destroy_all
|
|
177
|
-
|
|
178
|
-
{
|
|
179
|
-
success: true,
|
|
180
|
-
message: "Successfully deleted #{deleted_count} output(s)",
|
|
181
|
-
deleted_count: deleted_count
|
|
182
|
-
}
|
|
183
|
-
rescue StandardError => e
|
|
184
|
-
error!("Error deleting outputs: #{e.message}", 500)
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
namespace :create_mtt_request do
|
|
189
|
-
desc 'Create MTT assay request'
|
|
190
|
-
params do
|
|
191
|
-
use :create_mtt_request_params
|
|
192
|
-
end
|
|
193
|
-
post do
|
|
194
|
-
# Find element and wellplates
|
|
195
|
-
element = Labimotion::Element.find_by(id: params[:id])
|
|
196
|
-
error!('Element not found', 404) unless element
|
|
197
|
-
#byebug
|
|
198
|
-
# Verify user has update permission
|
|
199
|
-
error!('Unauthorized', 403) unless ElementPolicy.new(current_user, element).update?
|
|
200
|
-
|
|
201
|
-
wellplates = Wellplate.where(id: params[:wellplate_ids])
|
|
202
|
-
error!('No wellplates found', 404) if wellplates.empty?
|
|
203
|
-
|
|
204
|
-
# Generate wellplates metadata
|
|
205
|
-
wellplates_metadata = generate_wellplates_metadata(wellplates)
|
|
206
|
-
|
|
207
|
-
# Create DoseRespRequest record with token
|
|
208
|
-
dose_resp_request = Labimotion::DoseRespRequest.create!(
|
|
209
|
-
element_id: element.id,
|
|
210
|
-
wellplates_metadata: { wellplates: wellplates_metadata },
|
|
211
|
-
input_metadata: {
|
|
212
|
-
wellplate_ids: params[:wellplate_ids],
|
|
213
|
-
element_id: element.id,
|
|
214
|
-
element_name: element.name,
|
|
215
|
-
created_at: Time.current
|
|
216
|
-
},
|
|
217
|
-
state: Labimotion::DoseRespRequest::STATE_INITIAL,
|
|
218
|
-
created_by: current_user&.id,
|
|
219
|
-
expires_at: TPA_EXPIRATION.from_now
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
# Generate external app URL with token
|
|
223
|
-
token_uri = token_url(dose_resp_request)
|
|
224
|
-
external_app_url = get_external_app_url
|
|
225
|
-
|
|
226
|
-
# Format: "#{@app.url}?url=#{CGI.escape(token_uri)}&type=ThirdPartyApp"
|
|
227
|
-
"#{external_app_url}?method=DoseResponse&url=#{CGI.escape(token_uri)}"
|
|
228
|
-
rescue ActiveRecord::RecordInvalid => e
|
|
229
|
-
error!("Validation error: #{e.message}", 422)
|
|
230
|
-
rescue ActiveRecord::RecordNotFound => e
|
|
231
|
-
error!("Not found: #{e.message}", 404)
|
|
232
|
-
rescue StandardError => e
|
|
233
|
-
error!("Error: #{e.message}", 500)
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'labimotion/entities/application_entity'
|
|
4
|
-
|
|
5
|
-
module Labimotion
|
|
6
|
-
class ElementVariationEntity < Labimotion::ApplicationEntity
|
|
7
|
-
expose :id
|
|
8
|
-
expose :element_id, as: :elementId
|
|
9
|
-
expose :variations
|
|
10
|
-
expose :layout
|
|
11
|
-
|
|
12
|
-
def variations
|
|
13
|
-
rows = object.variations_hash
|
|
14
|
-
rows.transform_values do |row|
|
|
15
|
-
next row unless row.is_a?(Hash)
|
|
16
|
-
|
|
17
|
-
row.symbolize_keys.slice(:uuid, :name, :properties, :metadata, :segments).tap do |slim|
|
|
18
|
-
slim[:properties] = (slim[:properties] || {})
|
|
19
|
-
slim[:metadata] = (slim[:metadata] || {}).slice('notes', 'analyses', 'group', :notes, :analyses, :group)
|
|
20
|
-
slim[:segments] = (slim[:segments] || {})
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def layout
|
|
26
|
-
object.layout_hash
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'grape'
|
|
4
|
-
require 'net/http'
|
|
5
|
-
require 'uri'
|
|
6
|
-
require 'zip'
|
|
7
|
-
|
|
8
|
-
module Labimotion
|
|
9
|
-
## MTT Helpers
|
|
10
|
-
module MttHelpers
|
|
11
|
-
extend Grape::API::Helpers
|
|
12
|
-
|
|
13
|
-
TPA_EXPIRATION = 72.hours
|
|
14
|
-
|
|
15
|
-
def token_url(dose_resp_request)
|
|
16
|
-
# Build the callback URL with token in path
|
|
17
|
-
api_base_url = ENV['PUBLIC_URL'] || 'http://172.28.156.100:3000'
|
|
18
|
-
callback_path = "/api/v1/public/mtt_apps/#{dose_resp_request.access_token}"
|
|
19
|
-
callback_url = "#{api_base_url}#{callback_path}"
|
|
20
|
-
|
|
21
|
-
callback_url
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def get_external_app_url
|
|
25
|
-
ENV['MTT_EXTERNAL_APP_URL'] || 'http://localhost:4050'
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def validate_token(token)
|
|
29
|
-
# Find the request by access token
|
|
30
|
-
request = Labimotion::DoseRespRequest.find_by(access_token: token)
|
|
31
|
-
error!('Token not found', 404) unless request
|
|
32
|
-
|
|
33
|
-
# Check expiration
|
|
34
|
-
error!('Token expired', 403) if request.expired?
|
|
35
|
-
|
|
36
|
-
# Check revocation
|
|
37
|
-
error!('Token revoked', 403) if request.revoked?
|
|
38
|
-
|
|
39
|
-
request
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def validate_user_access(dose_resp_request)
|
|
43
|
-
# Get element and user
|
|
44
|
-
element = dose_resp_request.element
|
|
45
|
-
error!('Element not found', 404) unless element
|
|
46
|
-
|
|
47
|
-
user = dose_resp_request.creator
|
|
48
|
-
error!('User not found', 404) unless user
|
|
49
|
-
|
|
50
|
-
# Check user has update permission on element using ElementPolicy
|
|
51
|
-
policy = ElementPolicy.new(user, element)
|
|
52
|
-
error!('Unauthorized', 403) unless policy.update?
|
|
53
|
-
|
|
54
|
-
{ element: element, user: user }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def download_json_to_external_app
|
|
58
|
-
# Get token from route params
|
|
59
|
-
token = params[:token]
|
|
60
|
-
dose_resp_request = validate_token(token)
|
|
61
|
-
|
|
62
|
-
# Validate user access
|
|
63
|
-
validate_user_access(dose_resp_request)
|
|
64
|
-
|
|
65
|
-
# Track access and update state
|
|
66
|
-
# dose_resp_request.track_access!
|
|
67
|
-
dose_resp_request.mark_processing! if dose_resp_request.state == Labimotion::DoseRespRequest::STATE_INITIAL
|
|
68
|
-
# Return wellplates metadata as JSON
|
|
69
|
-
# Access the wellplates array from the metadata structure
|
|
70
|
-
wellplates_data = dose_resp_request.wellplates_metadata&.dig('wellplates') ||
|
|
71
|
-
dose_resp_request.wellplates_metadata&.dig(:wellplates) ||
|
|
72
|
-
[]
|
|
73
|
-
|
|
74
|
-
response_data = {
|
|
75
|
-
id: dose_resp_request.id.to_s,
|
|
76
|
-
request_id: dose_resp_request.request_id,
|
|
77
|
-
element_info: extract_element_properties(dose_resp_request.element),
|
|
78
|
-
wellplates: wellplates_data
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
status 200
|
|
82
|
-
response_data
|
|
83
|
-
rescue => e
|
|
84
|
-
error!("Error: #{e.message}", 500)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def upload_json_from_external_app
|
|
88
|
-
# Get token from route params
|
|
89
|
-
token = params[:token]
|
|
90
|
-
dose_resp_request = validate_token(token)
|
|
91
|
-
|
|
92
|
-
# Validate user access
|
|
93
|
-
access_info = validate_user_access(dose_resp_request)
|
|
94
|
-
user = access_info[:user]
|
|
95
|
-
element = access_info[:element]
|
|
96
|
-
# Handle file upload
|
|
97
|
-
file_param = params['file'] || params[:file]
|
|
98
|
-
error!('No file uploaded', 400) unless file_param && file_param.is_a?(Hash) && file_param['tempfile']
|
|
99
|
-
|
|
100
|
-
tempfile = file_param['tempfile']
|
|
101
|
-
filename = file_param['filename'] || 'upload'
|
|
102
|
-
# Check if it's a zip file
|
|
103
|
-
if filename.end_with?('.zip')
|
|
104
|
-
# Process zip file
|
|
105
|
-
wellplates_data, csv_data = process_zip_file(tempfile)
|
|
106
|
-
|
|
107
|
-
error!('Missing JSON data in zip file', 400) unless wellplates_data
|
|
108
|
-
|
|
109
|
-
# Create analysis container and dataset with CSV if present
|
|
110
|
-
# if csv_data
|
|
111
|
-
# create_analysis_with_csv(element, user, csv_data, dose_resp_request, wellplates_data)
|
|
112
|
-
# end
|
|
113
|
-
# else
|
|
114
|
-
# # Handle single JSON file
|
|
115
|
-
# file_content = tempfile.read
|
|
116
|
-
# tempfile.rewind
|
|
117
|
-
# json_data = JSON.parse(file_content).with_indifferent_access
|
|
118
|
-
# wellplates_data = json_data[:Output]
|
|
119
|
-
|
|
120
|
-
# error!('Missing wellplates data', 400) unless wellplates_data
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
dose_resp_request.track_access!
|
|
124
|
-
|
|
125
|
-
# Save output data to dose_resp_outputs table
|
|
126
|
-
output = dose_resp_request.dose_resp_outputs.create!(
|
|
127
|
-
output_data: { Output: wellplates_data }
|
|
128
|
-
)
|
|
129
|
-
if csv_data
|
|
130
|
-
create_analysis_with_csv(element, user, csv_data, dose_resp_request, output)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
dose_resp_request.update!(
|
|
134
|
-
wellplates_metadata: { wellplates: wellplates_data },
|
|
135
|
-
resp_message: 'Data updated successfully'
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
# Mark as completed
|
|
139
|
-
dose_resp_request.mark_completed!
|
|
140
|
-
|
|
141
|
-
status 200
|
|
142
|
-
{
|
|
143
|
-
success: true,
|
|
144
|
-
message: 'Data updated successfully',
|
|
145
|
-
request_id: dose_resp_request.id
|
|
146
|
-
}
|
|
147
|
-
rescue JSON::ParserError => e
|
|
148
|
-
error!("Invalid JSON: #{e.message}", 400)
|
|
149
|
-
rescue ActiveRecord::RecordInvalid => e
|
|
150
|
-
dose_resp_request.mark_error!(e.message) if dose_resp_request
|
|
151
|
-
error!("Validation error: #{e.message}", 422)
|
|
152
|
-
rescue => e
|
|
153
|
-
dose_resp_request.mark_error!(e.message) if dose_resp_request
|
|
154
|
-
error!("Error: #{e.message}", 500)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def process_zip_file(tempfile)
|
|
158
|
-
wellplates_data = nil
|
|
159
|
-
csv_data = nil
|
|
160
|
-
|
|
161
|
-
Zip::File.open(tempfile.path) do |zip_file|
|
|
162
|
-
zip_file.each do |entry|
|
|
163
|
-
if entry.name.end_with?('.json')
|
|
164
|
-
# Read JSON file
|
|
165
|
-
json_content = entry.get_input_stream.read
|
|
166
|
-
json_data = JSON.parse(json_content).with_indifferent_access
|
|
167
|
-
wellplates_data = json_data[:Output]
|
|
168
|
-
elsif entry.name.end_with?('.xls', '.xlsx', '.csv')
|
|
169
|
-
# Read CSV/Excel file - extract just the basename without path
|
|
170
|
-
csv_content = entry.get_input_stream.read
|
|
171
|
-
csv_data = {
|
|
172
|
-
filename: File.basename(entry.name),
|
|
173
|
-
content: csv_content
|
|
174
|
-
}
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
[wellplates_data, csv_data]
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def create_analysis_with_csv(element, user, csv_data, dose_resp_request, output)
|
|
183
|
-
analysis, dataset = create_analysis_with_dataset(
|
|
184
|
-
element: element,
|
|
185
|
-
analysis_name: "MTT Analysis #{dose_resp_request.request_id}-#{output&.id}",
|
|
186
|
-
dataset_name: 'new',
|
|
187
|
-
analysis_attributes: {}
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
# Determine content type based on file extension
|
|
191
|
-
content_type = case File.extname(csv_data[:filename]).downcase
|
|
192
|
-
when '.xlsx'
|
|
193
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
194
|
-
when '.xls'
|
|
195
|
-
'application/vnd.ms-excel'
|
|
196
|
-
when '.csv'
|
|
197
|
-
'text/csv'
|
|
198
|
-
else
|
|
199
|
-
'application/octet-stream'
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Create a temporary file for the attachment
|
|
203
|
-
temp_file = Tempfile.new([File.basename(csv_data[:filename], '.*'), File.extname(csv_data[:filename])])
|
|
204
|
-
begin
|
|
205
|
-
temp_file.binmode
|
|
206
|
-
temp_file.write(csv_data[:content])
|
|
207
|
-
temp_file.rewind
|
|
208
|
-
|
|
209
|
-
# Create attachment for CSV/Excel file
|
|
210
|
-
attachment = Attachment.new(
|
|
211
|
-
filename: csv_data[:filename],
|
|
212
|
-
file_path: temp_file.path,
|
|
213
|
-
created_by: user.id,
|
|
214
|
-
created_for: user.id,
|
|
215
|
-
attachable_type: 'Container',
|
|
216
|
-
attachable_id: dataset.id,
|
|
217
|
-
content_type: content_type
|
|
218
|
-
)
|
|
219
|
-
attachment.save! if attachment.valid?
|
|
220
|
-
|
|
221
|
-
{ analysis: analysis, dataset: dataset, attachment: attachment }
|
|
222
|
-
ensure
|
|
223
|
-
temp_file.close
|
|
224
|
-
temp_file.unlink
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def create_analysis_with_dataset(
|
|
229
|
-
element:,
|
|
230
|
-
analysis_name: 'New Analysis',
|
|
231
|
-
dataset_name: 'New Dataset',
|
|
232
|
-
analysis_attributes: {}
|
|
233
|
-
)
|
|
234
|
-
# Ensure the element has a root container
|
|
235
|
-
ensure_root_container(element)
|
|
236
|
-
|
|
237
|
-
# Get or create the analyses container
|
|
238
|
-
analyses_container = element.container.children.find_or_create_by(container_type: 'analyses')
|
|
239
|
-
|
|
240
|
-
# Prepare default extended_metadata for analysis
|
|
241
|
-
default_metadata = {
|
|
242
|
-
'content' => '{"ops":[{"insert":""}]}',
|
|
243
|
-
'report' => true
|
|
244
|
-
}
|
|
245
|
-
extended_metadata = default_metadata.merge(analysis_attributes[:extended_metadata] || {})
|
|
246
|
-
|
|
247
|
-
# Create the analysis container
|
|
248
|
-
analysis_container = analyses_container.children.create(
|
|
249
|
-
container_type: 'analysis',
|
|
250
|
-
name: analysis_name,
|
|
251
|
-
description: analysis_attributes[:description] || '',
|
|
252
|
-
extended_metadata: extended_metadata
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
# Create the dataset container nested under the analysis
|
|
256
|
-
dataset_container = analysis_container.children.create(
|
|
257
|
-
container_type: 'dataset',
|
|
258
|
-
name: dataset_name,
|
|
259
|
-
description: '',
|
|
260
|
-
extended_metadata: {}
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
[analysis_container, dataset_container]
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
def ensure_root_container(element)
|
|
267
|
-
return if element.container.present?
|
|
268
|
-
|
|
269
|
-
element.container = Container.create_root_container
|
|
270
|
-
end
|
|
271
|
-
# def send_mtt_request(current_user, params)
|
|
272
|
-
|
|
273
|
-
# token_uri = token_url(current_user, params)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
# uri = URI.parse(api_url)
|
|
277
|
-
# http = Net::HTTP.new(uri.host, uri.port)
|
|
278
|
-
# http.use_ssl = (uri.scheme == 'https')
|
|
279
|
-
# http.read_timeout = 30
|
|
280
|
-
|
|
281
|
-
# # Ensure path is not empty, default to '/' if needed
|
|
282
|
-
# path = uri.path.empty? ? '/' : uri.path
|
|
283
|
-
# request = Net::HTTP::Post.new(path, { 'Content-Type' => 'application/json' })
|
|
284
|
-
# request.body = json_data.to_json
|
|
285
|
-
|
|
286
|
-
# response = http.request(request)
|
|
287
|
-
|
|
288
|
-
# {
|
|
289
|
-
# success: response.is_a?(Net::HTTPSuccess),
|
|
290
|
-
# status: response.code,
|
|
291
|
-
# body: (JSON.parse(response.body) rescue response.body),
|
|
292
|
-
# message: response.message
|
|
293
|
-
# }
|
|
294
|
-
# rescue StandardError => e
|
|
295
|
-
# {
|
|
296
|
-
# success: false,
|
|
297
|
-
# error: e.message,
|
|
298
|
-
# backtrace: e.backtrace.first(5)
|
|
299
|
-
# }
|
|
300
|
-
# end
|
|
301
|
-
|
|
302
|
-
def extract_readout_titles(wellplate)
|
|
303
|
-
# Extract readout titles from wellplate
|
|
304
|
-
if wellplate.respond_to?(:readout_titles)
|
|
305
|
-
titles = wellplate.readout_titles
|
|
306
|
-
return titles if titles.is_a?(Array)
|
|
307
|
-
return JSON.parse(titles) if titles.is_a?(String)
|
|
308
|
-
end
|
|
309
|
-
[]
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
def extract_wells(wellplate)
|
|
313
|
-
# Extract wells data from wellplate
|
|
314
|
-
wells = wellplate.wells || []
|
|
315
|
-
wells.map do |well|
|
|
316
|
-
# Position might be stored as a hash or separate fields
|
|
317
|
-
position = if well.respond_to?(:position) && well.position.is_a?(Hash)
|
|
318
|
-
well.position
|
|
319
|
-
elsif well.respond_to?(:position_x)
|
|
320
|
-
{ x: well.position_x, y: well.position_y }
|
|
321
|
-
else
|
|
322
|
-
{ x: 0, y: 0 }
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
well_data = {
|
|
326
|
-
id: well.id,
|
|
327
|
-
position: position
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
# Only include readouts if they have values
|
|
331
|
-
readouts = extract_readouts(well)
|
|
332
|
-
well_data[:readouts] = readouts if readouts.present?
|
|
333
|
-
|
|
334
|
-
# Only include sample if it exists
|
|
335
|
-
sample = extract_sample(well)
|
|
336
|
-
well_data[:sample] = sample if sample.present?
|
|
337
|
-
|
|
338
|
-
well_data
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def extract_readouts(well)
|
|
343
|
-
# Extract readouts from well
|
|
344
|
-
# Readouts are typically stored as JSON data in the well
|
|
345
|
-
readouts = if well.respond_to?(:readouts) && well.readouts.is_a?(Array)
|
|
346
|
-
well.readouts
|
|
347
|
-
elsif well.respond_to?(:readouts) && well.readouts.is_a?(String)
|
|
348
|
-
JSON.parse(well.readouts) rescue []
|
|
349
|
-
elsif well.respond_to?(:readouts) && well.readouts.is_a?(Hash)
|
|
350
|
-
well.readouts.values rescue []
|
|
351
|
-
else
|
|
352
|
-
[]
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
# Filter out empty readouts (both unit and value are blank)
|
|
356
|
-
readouts.select do |readout|
|
|
357
|
-
readout.is_a?(Hash) &&
|
|
358
|
-
(readout['unit'].to_s.present? || readout['value'].to_s.present? ||
|
|
359
|
-
readout[:unit].to_s.present? || readout[:value].to_s.present?)
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
def extract_sample(well)
|
|
364
|
-
# Extract sample information from well
|
|
365
|
-
if well.respond_to?(:sample) && well.sample.present?
|
|
366
|
-
sample = well.sample
|
|
367
|
-
return {
|
|
368
|
-
id: sample.id,
|
|
369
|
-
short_label: sample.short_label,
|
|
370
|
-
conc: sample.try(:molarity_value) || 0
|
|
371
|
-
}
|
|
372
|
-
end
|
|
373
|
-
nil
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
def generate_wellplates_metadata(wellplates)
|
|
377
|
-
wellplates.map do |wellplate|
|
|
378
|
-
{
|
|
379
|
-
id: wellplate.id.to_s,
|
|
380
|
-
readoutTitles: extract_readout_titles(wellplate),
|
|
381
|
-
wells: extract_wells(wellplate)
|
|
382
|
-
}
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
def extract_element_properties(element)
|
|
387
|
-
props = {
|
|
388
|
-
id: element.id.to_s,
|
|
389
|
-
name: element.name
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
layers = element.properties.dig('layers', 'general_information', 'fields') ||
|
|
393
|
-
element.properties.dig(:layers, :general_information, :fields) || []
|
|
394
|
-
|
|
395
|
-
endpoint_field = layers.find { |f| f['field'] == 'Endpoint' || f[:field] == 'Endpoint' }
|
|
396
|
-
props[:endpoint] = endpoint_field['value'] || endpoint_field[:value] if endpoint_field
|
|
397
|
-
|
|
398
|
-
props
|
|
399
|
-
end
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def generate_element_metadata(element)
|
|
403
|
-
{
|
|
404
|
-
id: wellplate.id.to_s,
|
|
405
|
-
readoutTitles: extract_readout_titles(wellplate),
|
|
406
|
-
wells: extract_wells(wellplate)
|
|
407
|
-
}
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
def generate_json_data(wellplates)
|
|
412
|
-
# # Generate wellplates metadata
|
|
413
|
-
wellplates_metadata = generate_wellplates_metadata(wellplates)
|
|
414
|
-
|
|
415
|
-
# Generate JSON structure
|
|
416
|
-
json_data = {
|
|
417
|
-
id: element.id.to_s,
|
|
418
|
-
request_id: dose_resp_request.id.to_s,
|
|
419
|
-
wellplates: wellplates_metadata
|
|
420
|
-
}
|
|
421
|
-
# Save to JSON file
|
|
422
|
-
filename = "mtt_request_#{element.id}_#{dose_resp_request.id}.json"
|
|
423
|
-
filepath = Rails.root.join('tmp', filename)
|
|
424
|
-
File.write(filepath, JSON.pretty_generate(json_data))
|
|
425
|
-
json_data
|
|
426
|
-
end
|
|
427
|
-
end
|
|
428
|
-
end
|