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
UnderRepWellCommentsToBroadcast::UNDER_REPRESENTED_KEY
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 supplied array.
-
#assigned_user ⇒ Object
-
#batch_id_matches(scanned_batch_id) ⇒ 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
-
#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
-
#set_position_based_on_asset_barcode ⇒ Object
Sets the position of the requests in the batch based on their asset barcodes.
-
#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
-
#tube_barcode_matches(request, scanned_barcode) ⇒ Object
-
#underrun ⇒ Object
-
#update_batch_state(reason, comment) ⇒ Object
-
#verify_amp_plate_layout(barcodes, user = nil) ⇒ Bool
Used in the Ultima sequencing pipelines to check AMP plates are in the correct position.
-
#verify_amp_plate_position(request, barcodes) ⇒ Object
-
#verify_tube_layout(barcodes, user = nil) ⇒ Bool
Used in Sequencing pipelines to check tubes are in the correct position on the flowcell.
-
#verify_tube_position(request, barcodes) ⇒ Object
has_many_events, has_many_lab_events, has_one_event_with_family
#comments, #request_with_under_represented_wells, #under_represented_well_comments
#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
545
546
547
|
# File 'app/models/batch.rb', line 545
def self.barcode_without_pick_number(code)
code.split('-').first
end
|
549
550
551
552
553
554
555
556
|
# File 'app/models/batch.rb', line 549
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
559
560
561
562
563
564
565
566
|
# File 'app/models/batch.rb', line 559
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
528
529
530
|
# File 'app/models/batch.rb', line 528
def self.prefix
'BA'
end
|
.valid_barcode?(code) ⇒ Boolean
532
533
534
535
536
537
538
539
540
541
542
543
|
# File 'app/models/batch.rb', line 532
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
117
118
119
120
121
|
# File 'app/models/batch.rb', line 117
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 supplied array.
214
215
216
217
218
219
220
221
222
223
224
225
226
|
# File 'app/models/batch.rb', line 214
def assign_positions_to_requests!(request_ids_in_position_order)
request_ids_in_batch = batch_requests.map(&:request_id)
missing_requests = request_ids_in_batch.any? { |id| request_ids_in_position_order.exclude?(id) }
= request_ids_in_position_order.any? { |id| request_ids_in_batch.exclude?(id) }
raise StandardError, 'Can only sort all the requests in the batch at once' if missing_requests ||
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
230
231
232
|
# File 'app/models/batch.rb', line 230
def assigned_user
assignee.try(:login) || ''
end
|
#batch_id_matches(scanned_batch_id) ⇒ Object
374
375
376
|
# File 'app/models/batch.rb', line 374
def batch_id_matches(scanned_batch_id)
scanned_batch_id == id.to_s
end
|
#control ⇒ Object
198
199
200
|
# File 'app/models/batch.rb', line 198
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.
399
400
401
402
403
404
405
406
|
# File 'app/models/batch.rb', line 399
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.
601
602
603
|
# File 'app/models/batch.rb', line 601
def displayed_status
failed? ? 'failed' : state
end
|
#downstream_requests_needing_asset(request) {|next_requests_needing_asset| ... } ⇒ Object
582
583
584
585
|
# File 'app/models/batch.rb', line 582
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
186
187
188
|
# File 'app/models/batch.rb', line 186
def event_with_description(name)
lab_events.order(id: :desc).find_by(description: name)
end
|
#eventful_studies ⇒ Object
127
128
129
|
# File 'app/models/batch.rb', line 127
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
# File 'app/models/batch.rb', line 136
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
# File 'app/models/batch.rb', line 154
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
177
178
179
|
# File 'app/models/batch.rb', line 177
def failed?
production_state == 'fail'
end
|
#first_output_plate ⇒ Object
273
274
275
|
# File 'app/models/batch.rb', line 273
def first_output_plate
Plate.output_by_batch(self).with_wells_and_requests.first
end
|
#flowcell ⇒ Object
131
132
133
|
# File 'app/models/batch.rb', line 131
def flowcell
self if sequencing?
end
|
#has_control? ⇒ Boolean
202
203
204
|
# File 'app/models/batch.rb', line 202
def has_control?
control.present?
end
|
#has_event(event_name) ⇒ Object
Tests whether this Batch has any associated LabEvents
182
183
184
|
# File 'app/models/batch.rb', line 182
def has_event(event_name)
lab_events.any? { |event| event_name.downcase == event.description.try(:downcase) }
end
|
#id_dup ⇒ Object
295
296
297
|
# File 'app/models/batch.rb', line 295
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
243
244
245
|
# File 'app/models/batch.rb', line 243
def input_labware_report
pipeline.input_labware requests
end
|
256
257
258
|
# File 'app/models/batch.rb', line 256
def input_plate_group
source_assets.group_by(&:plate)
end
|
#npg_set_state ⇒ Object
574
575
576
577
578
579
580
|
# File 'app/models/batch.rb', line 574
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
252
253
254
|
# File 'app/models/batch.rb', line 252
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?
261
262
263
|
# File 'app/models/batch.rb', line 261
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
277
278
279
|
# File 'app/models/batch.rb', line 277
def output_plate_purpose
output_plates[0].plate_purpose unless output_plates[0].nil?
end
|
#output_plate_role ⇒ Object
281
282
283
|
# File 'app/models/batch.rb', line 281
def output_plate_role
requests.first.try(:role)
end
|
#output_plates ⇒ Object
265
266
267
268
269
270
271
|
# File 'app/models/batch.rb', line 265
def output_plates
return output_labware.sort_by(&:id) if output_labware.loaded?
output_labware.reorder(id: :asc)
end
|
591
592
593
|
# File 'app/models/batch.rb', line 591
def pick_information?
pipeline.pick_information?(self)
end
|
#plate_barcode(barcode) ⇒ Object
291
292
293
|
# File 'app/models/batch.rb', line 291
def plate_barcode(barcode)
barcode.presence || requests.first.target_asset.plate.human_barcode
end
|
#plate_group_barcodes ⇒ Object
285
286
287
288
289
|
# File 'app/models/batch.rb', line 285
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
505
506
507
|
# File 'app/models/batch.rb', line 505
def plate_ids_in_study(study)
Plate.plate_ids_from_requests(requests.for_studies(study))
end
|
#rebroadcast ⇒ Object
587
588
589
|
# File 'app/models/batch.rb', line 587
def rebroadcast
messengers.each(&:resend)
end
|
#release_pending_requests ⇒ Object
378
379
380
381
382
|
# File 'app/models/batch.rb', line 378
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
385
386
387
388
389
390
391
392
393
394
395
|
# File 'app/models/batch.rb', line 385
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
570
571
572
|
# File 'app/models/batch.rb', line 570
def request_count
requests.count
end
|
#reset!(current_user) ⇒ Object
rubocop:todo Metrics/MethodLength
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
|
# File 'app/models/batch.rb', line 421
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
408
409
410
411
412
413
414
415
416
417
418
|
# File 'app/models/batch.rb', line 408
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
190
191
192
|
# File 'app/models/batch.rb', line 190
def robot_id
event_with_description('Cherrypick Layout Set')&.descriptor_value('robot_id')
end
|
#robot_verified!(user_id) ⇒ Object
517
518
519
520
521
522
523
524
525
526
|
# File 'app/models/batch.rb', line 517
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
|
#set_position_based_on_asset_barcode ⇒ Object
Sets the position of the requests in the batch based on their asset barcodes. This was done at Lab request to make it easier to order the tubes in the batch.
208
209
210
211
|
# File 'app/models/batch.rb', line 208
def set_position_based_on_asset_barcode
request_ids_in_position_order = requests.sort_by { |r| r.asset.human_barcode }.map(&:id)
assign_positions_to_requests!(request_ids_in_position_order)
end
|
#source_labware ⇒ Object
Source Labware returns the physical pieces of labware (ie. a plate for wells, but tubes for tubes)
300
301
302
|
# File 'app/models/batch.rb', line 300
def source_labware
input_labware
end
|
#start_requests ⇒ Object
234
235
236
|
# File 'app/models/batch.rb', line 234
def start_requests
requests.with_assets_for_starting_requests.not_failed.map(&:start!)
end
|
#subject_type ⇒ Object
123
124
125
|
# File 'app/models/batch.rb', line 123
def subject_type
sequencing? ? 'flowcell' : 'batch'
end
|
#swap(current_user, batch_info = {}) ⇒ Object
rubocop:todo Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
|
# File 'app/models/batch.rb', line 445
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
509
510
511
512
513
514
515
|
# File 'app/models/batch.rb', line 509
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
|
#tube_barcode_matches(request, scanned_barcode) ⇒ Object
370
371
372
|
# File 'app/models/batch.rb', line 370
def tube_barcode_matches(request, scanned_barcode)
scanned_barcode == request.asset.machine_barcode || scanned_barcode == request.asset.human_barcode
end
|
#underrun ⇒ Object
194
195
196
|
# File 'app/models/batch.rb', line 194
def underrun
has_limit? ? (item_limit - batch_requests.size) : 0
end
|
#update_batch_state(reason, comment) ⇒ Object
169
170
171
172
173
174
175
|
# File 'app/models/batch.rb', line 169
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_amp_plate_layout(barcodes, user = nil) ⇒ Bool
Used in the Ultima sequencing pipelines to check AMP plates are in the correct position.
Verifies that provided barcodes are in the correct locations according to the request 'position' within the batch. Logs an event if the layout is correct.
348
349
350
351
352
353
354
355
356
357
|
# File 'app/models/batch.rb', line 348
def verify_amp_plate_layout(barcodes, user = nil)
requests.each { |request| verify_amp_plate_position(request, barcodes) }
if errors.empty?
lab_events.create(description: 'AMP plate layout verified', user: user)
true
else
false
end
end
|
#verify_amp_plate_position(request, barcodes) ⇒ Object
359
360
361
362
363
364
365
366
367
368
|
# File 'app/models/batch.rb', line 359
def verify_amp_plate_position(request, barcodes)
divider = '-'
scanned_barcode = barcodes[request.position - 1]
scanned_batch_id, tube_barcode = scanned_barcode.split(divider)
unless batch_id_matches(scanned_batch_id) && tube_barcode_matches(request, tube_barcode)
expected_barcode = "#{id}#{divider}#{request.asset.human_barcode}"
errors.add(:base, "The barcode at position #{request.position} is incorrect: expected #{expected_barcode}.")
end
end
|
#verify_tube_layout(barcodes, user = nil) ⇒ Bool
Used in Sequencing pipelines to check tubes are in the correct position on the flowcell.
Verifies that provided barcodes are in the correct locations according to the request 'position' within the batch. Logs an event if the layout is correct.
316
317
318
319
320
321
322
323
324
325
|
# File 'app/models/batch.rb', line 316
def verify_tube_layout(barcodes, user = nil)
requests.each { |request| verify_tube_position(request, barcodes) }
if errors.empty?
lab_events.create(description: 'Tube layout verified', user: user)
true
else
false
end
end
|
#verify_tube_position(request, barcodes) ⇒ Object
327
328
329
330
331
332
333
334
|
# File 'app/models/batch.rb', line 327
def verify_tube_position(request, barcodes)
scanned_barcode = barcodes[request.position - 1]
unless tube_barcode_matches(request, scanned_barcode)
expected_barcode = request.asset.human_barcode
errors.add(:base, "The tube at position #{request.position} is incorrect: expected #{expected_barcode}.")
end
end
|