Class: Batch
Overview
A Batch groups 1 or more requests together to enable processing in a Pipeline. All requests in a batch get usually processed together, although it is possible for requests to get removed from a batch in a handful of cases.
Defined Under Namespace
Modules: PipelineBehaviour, RequestBehaviour, StateMachineBehaviour
Classes: RequestFailAndRemover
Constant Summary
collapse
- DEFAULT_VOLUME =
13
StandardNamedScopes::SORT_FIELDS, StandardNamedScopes::SORT_ORDERS
SequencingQcBatch::VALID_QC_STATES
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
-
#all_requests_are_ready? ⇒ Boolean
-
#assign_positions_to_requests!(request_ids_in_position_order) ⇒ Object
Sets the position of the requests in the batch to their index in the array.
-
#assigned_user ⇒ Object
-
#control ⇒ Object
-
#detach_request(request, current_user = nil) ⇒ Object
Remove a request from the batch and reset it to a point where it can be put back into the pending queue.
-
#displayed_status ⇒ Object
Summarise the state encapsulated by state and production_state Essentially a ‘fail’ production_state over-rides the ‘state’ We don’t use production_state directly as it it ‘fail’ rather than ‘ failed’ qc_state it kept separate as its a fairly distinct concept and is summarised elsewhere in the interface.
-
#downstream_requests_needing_asset(request) {|next_requests_needing_asset| ... } ⇒ Object
-
#event_with_description(name) ⇒ Object
-
#eventful_studies ⇒ Object
-
#fail(reason, comment, ignore_requests = false) ⇒ Object
Fail was removed from State Machine (as a state) to allow the addition of qc_state column and features.
-
#fail_requests(requests_to_fail, reason, comment, fail_but_charge = false) ⇒ Object
Fail specific requests on this batch.
-
#failed? ⇒ Boolean
-
#first_output_plate ⇒ Object
-
#flowcell ⇒ Object
-
#has_control? ⇒ Boolean
-
#has_event(event_name) ⇒ Object
Tests whether this Batch has any associated LabEvents.
-
#id_dup ⇒ Object
-
#input_labware_report ⇒ Labware::ActiveRecord_Relation
Returns a list of input labware including their barcodes, purposes, and a count of the number of requests associated with the batch.
-
#input_plate_group ⇒ Object
-
#npg_set_state ⇒ Object
-
#output_labware_report ⇒ Labware::ActiveRecord_Relation
Returns a list of output labware including their barcodes, purposes, and a count of the number of requests associated with the batch.
-
#output_plate_group ⇒ Object
-
#output_plate_purpose ⇒ Object
-
#output_plate_role ⇒ Object
-
#output_plates ⇒ Object
-
#pick_information? ⇒ Boolean
-
#plate_barcode(barcode) ⇒ Object
-
#plate_group_barcodes ⇒ Object
-
#plate_ids_in_study(study) ⇒ Object
rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity.
-
#rebroadcast ⇒ Object
-
#release_pending_requests ⇒ Object
rubocop:enable Metrics/MethodLength.
-
#remove_request_ids(request_ids, reason = nil, comment = nil) ⇒ Object
Remove the request from the batch and remove asset information.
-
#request_count ⇒ Object
-
#reset!(current_user) ⇒ Object
rubocop:todo Metrics/MethodLength.
-
#return_request_to_inbox(request, current_user = nil) ⇒ Object
-
#robot_id ⇒ Object
-
#robot_verified!(user_id) ⇒ Object
-
#source_labware ⇒ Object
Source Labware returns the physical pieces of labware (ie. a plate for wells, but tubes for tubes).
-
#start_requests ⇒ Object
-
#subject_type ⇒ Object
-
#swap(current_user, batch_info = {}) ⇒ Object
rubocop:todo Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize.
-
#total_volume_to_cherrypick ⇒ Object
-
#underrun ⇒ Object
-
#update_batch_state(reason, comment) ⇒ Object
-
#verify_tube_layout(barcodes, user = nil) ⇒ Bool
Verifies that provided barcodes are in the correct locations according to the request organization within the batch.
has_many_events, has_many_lab_events, has_one_event_with_family
#complete_with_user!, #editable?, #finished?, included, #release_with_user!, #start_with_user!
#has_item_limit?, included, #last_completed_task
included
included, #unsaved_uuid!, #uuid
#after_comment_addition
adjacent_state_helper, included, #processing_in_manual_qc?, #qc_manual_in_progress, #qc_previous_state!, #qc_states, state_transition_helper
included
alias_association, convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!
Methods included from Squishify
extended
Instance Attribute Details
#production_state ⇒ Object
Also referenced in StateMachineBehaviour. Either nil, or fail. This is updated in Batch#fail_requests and Batch#fail. The former is used via BatchesController#fail_items, the latter seems to be unused. Is intended to take precedence over both other states to track failures in-spite of QC results.
#qc_state ⇒ Object
Primarily for sequencing batches. See SequencingQcBatch. Holds the sequencing QC state
Class Method Details
.barcode_without_pick_number(code) ⇒ Object
489
490
491
|
# File 'app/models/batch.rb', line 489
def self.barcode_without_pick_number(code)
code.split('-').first
end
|
493
494
495
496
497
498
499
500
|
# File 'app/models/batch.rb', line 493
def self.(code)
split_code = code.split('-')
return Integer(split_code.last) if split_code.size > 1
1
end
|
.find_by_barcode(code) ⇒ Object
Also known as:
find_from_barcode
503
504
505
506
507
508
509
510
|
# File 'app/models/batch.rb', line 503
def find_by_barcode(code)
split_code = barcode_without_pick_number(code)
human_batch_barcode = Barcode.number_to_human(split_code)
batch = Batch.find_by(barcode: human_batch_barcode)
batch ||= Batch.find_by(id: human_batch_barcode)
batch
end
|
.prefix ⇒ Object
472
473
474
|
# File 'app/models/batch.rb', line 472
def self.prefix
'BA'
end
|
.valid_barcode?(code) ⇒ Boolean
476
477
478
479
480
481
482
483
484
485
486
487
|
# File 'app/models/batch.rb', line 476
def self.valid_barcode?(code)
begin
split_code = barcode_without_pick_number(code)
Barcode.barcode_to_human!(split_code, prefix)
rescue StandardError
return false
end
return false if find_from_barcode(code).nil?
true
end
|
Instance Method Details
#all_requests_are_ready? ⇒ Boolean
116
117
118
119
120
|
# File 'app/models/batch.rb', line 116
def all_requests_are_ready?
errors.add :base, 'All requests must be ready to be added to a batch' unless requests.all?(&:ready?)
end
|
#assign_positions_to_requests!(request_ids_in_position_order) ⇒ Object
Sets the position of the requests in the batch to their index in the array.
206
207
208
209
210
211
212
213
214
215
|
# File 'app/models/batch.rb', line 206
def assign_positions_to_requests!(request_ids_in_position_order)
disparate_ids = batch_requests.map(&:request_id) - request_ids_in_position_order
raise StandardError, 'Can only sort all requests at once' unless disparate_ids.empty?
BatchRequest.transaction do
batch_requests.each do |batch_request|
batch_request.move_to_position!(request_ids_in_position_order.index(batch_request.request_id) + 1)
end
end
end
|
#assigned_user ⇒ Object
219
220
221
|
# File 'app/models/batch.rb', line 219
def assigned_user
assignee.try(:login) || ''
end
|
#control ⇒ Object
197
198
199
|
# File 'app/models/batch.rb', line 197
def control
requests.detect { |request| request.try(:asset).try(:resource?) }
end
|
#detach_request(request, current_user = nil) ⇒ Object
Remove a request from the batch and reset it to a point where it can be put back into the pending queue.
343
344
345
346
347
348
349
350
|
# File 'app/models/batch.rb', line 343
def detach_request(request, current_user = nil)
ActiveRecord::Base.transaction do
unless current_user.nil?
request.("Used to belong to Batch #{id} removed at #{Time.zone.now}", current_user)
end
pipeline.detach_request_from_batch(self, request)
end
end
|
#displayed_status ⇒ Object
Summarise the state encapsulated by state and production_state Essentially a ‘fail’ production_state over-rides the ‘state’ We don’t use production_state directly as it it ‘fail’ rather than ‘ failed’ qc_state it kept separate as its a fairly distinct concept and is summarised elsewhere in the interface.
545
546
547
|
# File 'app/models/batch.rb', line 545
def displayed_status
failed? ? 'failed' : state
end
|
#downstream_requests_needing_asset(request) {|next_requests_needing_asset| ... } ⇒ Object
526
527
528
529
|
# File 'app/models/batch.rb', line 526
def downstream_requests_needing_asset(request)
next_requests_needing_asset = request.next_requests.select { |r| r.asset_id.blank? }
yield(next_requests_needing_asset) if next_requests_needing_asset.present?
end
|
#event_with_description(name) ⇒ Object
185
186
187
|
# File 'app/models/batch.rb', line 185
def event_with_description(name)
lab_events.order(id: :desc).find_by(description: name)
end
|
#eventful_studies ⇒ Object
126
127
128
|
# File 'app/models/batch.rb', line 126
def eventful_studies
requests.reduce([]) { |studies, request| studies.concat(request.eventful_studies) }.uniq
end
|
#fail(reason, comment, ignore_requests = false) ⇒ Object
Fail was removed from State Machine (as a state) to allow the addition of qc_state column and features
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
# File 'app/models/batch.rb', line 135
def fail(reason, , ignore_requests = false)
raise StandardError, 'Can not fail batch without failing requests' if ignore_requests
failures.create(reason: reason, comment: , notify_remote: false)
requests.each do |request|
request.failures.create(reason: reason, comment: , notify_remote: true)
EventSender.send_fail_event(request, reason, , id) unless request.asset && request.asset.resource?
end
self.production_state = 'fail'
save!
end
|
#fail_requests(requests_to_fail, reason, comment, fail_but_charge = false) ⇒ Object
Fail specific requests on this batch
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
# File 'app/models/batch.rb', line 153
def fail_requests(requests_to_fail, reason, , fail_but_charge = false) ActiveRecord::Base.transaction do
requests
.find(requests_to_fail)
.each do |request|
logger.debug "SENDING FAIL FOR REQUEST #{request.id}, BATCH #{id}, WITH REASON #{reason}"
request.customer_accepts_responsibility! if fail_but_charge
request.failures.create(reason: reason, comment: , notify_remote: true)
EventSender.send_fail_event(request, reason, , id)
end
update_batch_state(reason, )
end
end
|
#failed? ⇒ Boolean
176
177
178
|
# File 'app/models/batch.rb', line 176
def failed?
production_state == 'fail'
end
|
#first_output_plate ⇒ Object
262
263
264
|
# File 'app/models/batch.rb', line 262
def first_output_plate
Plate.output_by_batch(self).with_wells_and_requests.first
end
|
#flowcell ⇒ Object
130
131
132
|
# File 'app/models/batch.rb', line 130
def flowcell
self if sequencing?
end
|
#has_control? ⇒ Boolean
201
202
203
|
# File 'app/models/batch.rb', line 201
def has_control?
control.present?
end
|
#has_event(event_name) ⇒ Object
Tests whether this Batch has any associated LabEvents
181
182
183
|
# File 'app/models/batch.rb', line 181
def has_event(event_name)
lab_events.any? { |event| event_name.downcase == event.description.try(:downcase) }
end
|
#id_dup ⇒ Object
284
285
286
|
# File 'app/models/batch.rb', line 284
def id_dup
id
end
|
Returns a list of input labware including their barcodes, purposes, and a count of the number of requests associated with the batch. Output depends on Pipeline. Some pipelines return an empty relationship
232
233
234
|
# File 'app/models/batch.rb', line 232
def input_labware_report
pipeline.input_labware requests
end
|
245
246
247
|
# File 'app/models/batch.rb', line 245
def input_plate_group
source_assets.group_by(&:plate)
end
|
#npg_set_state ⇒ Object
518
519
520
521
522
523
524
|
# File 'app/models/batch.rb', line 518
def npg_set_state
if all_requests_qced?
self.state = 'released'
qc_complete
save!
end
end
|
#output_labware_report ⇒ Labware::ActiveRecord_Relation
Returns a list of output labware including their barcodes, purposes, and a count of the number of requests associated with the batch. Output depends on Pipeline. Some pipelines return an empty relationship
241
242
243
|
# File 'app/models/batch.rb', line 241
def output_labware_report
pipeline.output_labware requests.with_target
end
|
#output_plate_group ⇒ Object
This looks odd. Why would a request have the same asset as target asset? Why are we filtering them out here?
250
251
252
|
# File 'app/models/batch.rb', line 250
def output_plate_group
requests.select { |r| r.target_asset != r.asset }.map(&:target_asset).select(&:present?).group_by(&:plate)
end
|
#output_plate_purpose ⇒ Object
266
267
268
|
# File 'app/models/batch.rb', line 266
def output_plate_purpose
output_plates[0].plate_purpose unless output_plates[0].nil?
end
|
#output_plate_role ⇒ Object
270
271
272
|
# File 'app/models/batch.rb', line 270
def output_plate_role
requests.first.try(:role)
end
|
#output_plates ⇒ Object
254
255
256
257
258
259
260
|
# File 'app/models/batch.rb', line 254
def output_plates
return output_labware.sort_by(&:id) if output_labware.loaded?
output_labware.reorder(id: :asc)
end
|
535
536
537
|
# File 'app/models/batch.rb', line 535
def pick_information?
pipeline.pick_information?(self)
end
|
#plate_barcode(barcode) ⇒ Object
280
281
282
|
# File 'app/models/batch.rb', line 280
def plate_barcode(barcode)
barcode.presence || requests.first.target_asset.plate.human_barcode
end
|
#plate_group_barcodes ⇒ Object
274
275
276
277
278
|
# File 'app/models/batch.rb', line 274
def plate_group_barcodes
return nil unless pipeline.group_by_parent || requests.first.target_asset.is_a?(Well)
output_plate_group.presence || input_plate_group
end
|
#plate_ids_in_study(study) ⇒ Object
rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
449
450
451
|
# File 'app/models/batch.rb', line 449
def plate_ids_in_study(study)
Plate.plate_ids_from_requests(requests.for_studies(study))
end
|
#rebroadcast ⇒ Object
531
532
533
|
# File 'app/models/batch.rb', line 531
def rebroadcast
messengers.each(&:resend)
end
|
#release_pending_requests ⇒ Object
rubocop:enable Metrics/MethodLength
322
323
324
325
326
|
# File 'app/models/batch.rb', line 322
def release_pending_requests
requests.each { |request| detach_request(request) if request.started? }
end
|
#remove_request_ids(request_ids, reason = nil, comment = nil) ⇒ Object
Remove the request from the batch and remove asset information
329
330
331
332
333
334
335
336
337
338
339
|
# File 'app/models/batch.rb', line 329
def remove_request_ids(request_ids, reason = nil, = nil)
ActiveRecord::Base.transaction do
Request
.find(request_ids)
.each do |request|
request.failures.create(reason: reason, comment: , notify_remote: true)
detach_request(request)
end
update_batch_state(reason, )
end
end
|
#request_count ⇒ Object
514
515
516
|
# File 'app/models/batch.rb', line 514
def request_count
requests.count
end
|
#reset!(current_user) ⇒ Object
rubocop:todo Metrics/MethodLength
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
|
# File 'app/models/batch.rb', line 365
def reset!(current_user) ActiveRecord::Base.transaction do
discard!
requests.each do |request|
request.batch = nil
return_request_to_inbox(request, current_user)
end
if requests.last.submission_id.present?
Request
.where(submission_id: requests.last.submission_id, state: 'pending')
.where.not(request_type_id: pipeline.request_type_ids)
.find_each do |request|
request.asset_id = nil
request.save!
end
end
end
end
|
#return_request_to_inbox(request, current_user = nil) ⇒ Object
352
353
354
355
356
357
358
359
360
361
362
|
# File 'app/models/batch.rb', line 352
def return_request_to_inbox(request, current_user = nil)
ActiveRecord::Base.transaction do
unless current_user.nil?
request.(
"Used to belong to Batch #{id} returned to inbox unstarted at #{Time.zone.now}",
current_user
)
end
request.return_pending_to_inbox!
end
end
|
#robot_id ⇒ Object
189
190
191
|
# File 'app/models/batch.rb', line 189
def robot_id
event_with_description('Cherrypick Layout Set')&.descriptor_value('robot_id')
end
|
#robot_verified!(user_id) ⇒ Object
461
462
463
464
465
466
467
468
469
470
|
# File 'app/models/batch.rb', line 461
def robot_verified!(user_id)
return if has_event('robot verified')
pipeline.robot_verified!(self)
lab_events.create(
description: 'Robot verified',
message: 'Robot verification completed and source volumes updated.',
user_id: user_id
)
end
|
#source_labware ⇒ Object
Source Labware returns the physical pieces of labware (ie. a plate for wells, but tubes for tubes)
289
290
291
|
# File 'app/models/batch.rb', line 289
def source_labware
input_labware
end
|
#start_requests ⇒ Object
223
224
225
|
# File 'app/models/batch.rb', line 223
def start_requests
requests.with_assets_for_starting_requests.not_failed.map(&:start!)
end
|
#subject_type ⇒ Object
122
123
124
|
# File 'app/models/batch.rb', line 122
def subject_type
sequencing? ? 'flowcell' : 'batch'
end
|
#swap(current_user, batch_info = {}) ⇒ Object
rubocop:todo Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
|
# File 'app/models/batch.rb', line 389
def swap(current_user, batch_info = {}) return false if batch_info.empty?
batch_request_left =
BatchRequest.find_by(batch_id: batch_info['batch_1']['id'], position: batch_info['batch_1']['lane']) or
errors.add('Swap: ', 'The first lane cannot be found')
batch_request_right =
BatchRequest.find_by(batch_id: batch_info['batch_2']['id'], position: batch_info['batch_2']['lane']) or
errors.add('Swap: ', 'The second lane cannot be found')
return unless batch_request_left.present? && batch_request_right.present?
ActiveRecord::Base.transaction do
batch_request_left.request.lab_events.each do |event|
event.update!(batch_id: batch_request_right.batch_id) if event.batch_id == batch_request_left.batch_id
end
batch_request_right.request.lab_events.each do |event|
event.update!(batch_id: batch_request_left.batch_id) if event.batch_id == batch_request_right.batch_id
end
original_left_batch_id, original_left_position, original_right_request_id =
batch_request_left.batch_id,
batch_request_left.position,
batch_request_right.request_id
batch_request_right.destroy
batch_request_left.update!(batch_id: batch_request_right.batch_id, position: batch_request_right.position)
batch_request_right =
BatchRequest.create!(
batch_id: original_left_batch_id,
position: original_left_position,
request_id: original_right_request_id
)
batch_request_left.batch.lab_events.create!(
description: 'Lane swap',
message:
"Lane #{batch_request_right.position} moved to #{batch_request_left.batch_id} lane #{batch_request_left.position}",
user_id: current_user.id
)
batch_request_right.batch.lab_events.create!(
description: 'Lane swap',
message:
"Lane #{batch_request_left.position} moved to #{batch_request_right.batch_id} lane #{batch_request_right.position}",
user_id: current_user.id
)
end
true
end
|
#total_volume_to_cherrypick ⇒ Object
453
454
455
456
457
458
459
|
# File 'app/models/batch.rb', line 453
def total_volume_to_cherrypick
request = requests.first
return DEFAULT_VOLUME unless request.asset.is_a?(Well)
return DEFAULT_VOLUME unless request.target_asset.is_a?(Well)
request.target_asset.get_requested_volume
end
|
#underrun ⇒ Object
193
194
195
|
# File 'app/models/batch.rb', line 193
def underrun
has_limit? ? (item_limit - batch_requests.size) : 0
end
|
#update_batch_state(reason, comment) ⇒ Object
168
169
170
171
172
173
174
|
# File 'app/models/batch.rb', line 168
def update_batch_state(reason, )
if requests.all?(&:terminated?)
failures.create(reason: reason, comment: , notify_remote: false)
self.production_state = 'fail'
save!
end
end
|
#verify_tube_layout(barcodes, user = nil) ⇒ Bool
Verifies that provided barcodes are in the correct locations according to the request organization within the batch. Either returns true, and logs the event or returns false.
rubocop:todo Metrics/MethodLength
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
|
# File 'app/models/batch.rb', line 304
def verify_tube_layout(barcodes, user = nil) requests.each do |request|
barcode = barcodes[request.position - 1]
unless barcode == request.asset.machine_barcode
expected_barcode = request.asset.human_barcode
errors.add(:base, "The tube at position #{request.position} is incorrect: expected #{expected_barcode}.")
end
end
if errors.empty?
lab_events.create(description: 'Tube layout verified', user: user)
true
else
false
end
end
|