Class: Plate
Overview
https://github.com/sanger/sequencescape/raw/master/docs/images/plate.jpg
A plate is a piece of labware made up of a number of wells. This class represents the physical piece of
plastic.
- PlatePurpose: describes the role a plate has in the lab. In some cases a plate's purpose may change as it gets
processed.
- Well: Plates can have multiple wells (most often 96 or 384) each of which can contain multiple samples.
- PlateType: Identifies the plates form factor, typically provided to robots to ensure tips are positioned
correctly.
Defined Under Namespace
Modules: FluidigmBehaviour, PoolingMetadata
Classes: Creator, CreatorParameters, QuadCreator, SampleTubeFactory
Constant Summary
Constants included
from Metadata
Metadata::SECTION_FIELDS
Instance Attribute Summary
Attributes inherited from Labware
#storage_location_service
Class Method Summary
collapse
Instance Method Summary
collapse
has_qc_files
Methods included from Metadata
has_metadata
included
#any_barcode_matching?, #barcode_format, #barcode_number, #cgap_barcode, #cgap_barcode=, #external_barcode, #external_barcode=, #external_identifier, #fluidigm_barcode, #fluidigm_barcode=, #foreign_barcode=, included, #infinium_barcode, #infinium_barcode=, #prefix, #primary_barcode, #printable_target, #sanger_barcode
included
#change_owner_to, included
included
included
included, #json_root
Methods inherited from Labware
#ancestor_of_purpose, #ancestors_of_purpose, #child, #display_name, #external_identifier, find_by_barcode, find_from_any_barcode, #generate_name, #labware, #labwhere_location, labwhere_locations, map_retention_instructions, #parent, #poly_metadata, #received_date, #retention_instructions, #role, #scanned_in_date, search_for_count_of_labware, search_for_labware, #source_plate, #source_plates, #spiked_in_buffer, #storage_location
included
included
included, #unsaved_uuid!, #uuid
Methods inherited from Asset
#ancestor_of_purpose, #asset_type_for_request_types, #barcode_number, #contained_samples, #get_qc_result_value_for, #has_stock_asset?, #label, #label=, #original_stock_plates, #prefix, #printable?, #printable_target, #register_stock!, #request_types, #type, #update_from_qc
#has_many_events, #has_many_lab_events, #has_one_event_with_family
#event_date, #fluidigm_stamp_date, #gel_qc_date, #pico_date, #qc_started_date, #sequenom_stamp_date
alias_association, convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!
Methods included from Squishify
extended
#broadcast_except_externally_managed, included
Class Method Details
.create_with_barcode!(*args) ⇒ Object
334
335
336
337
338
|
# File 'app/models/plate.rb', line 334
def self.create_with_barcode!(*args, &)
attributes = args.
attributes[:sanger_barcode] ||= PlateBarcode.create_barcode
create!(attributes, &)
end
|
.plate_ids_from_requests(requests) ⇒ Object
302
303
304
|
# File 'app/models/plate.rb', line 302
def self.plate_ids_from_requests(requests)
with_requests(requests).pluck(:id)
end
|
Instance Method Details
467
468
469
|
# File 'app/models/plate.rb', line 467
def ()
.()
end
|
#all_submission_ids ⇒ Object
Prioritised the submissions that have been made from the plate
then falls back onto the ones under which the plate was made
182
183
184
|
# File 'app/models/plate.rb', line 182
def all_submission_ids
submission_ids_as_source.presence || submission_ids
end
|
#buffer_required? ⇒ Boolean
348
349
350
|
# File 'app/models/plate.rb', line 348
def buffer_required?
wells.any?(&:buffer_required?)
end
|
#cherrypick_completed ⇒ Void
Called when cherrypicking is completed to allow the plate to trigger any callbacks,
such as broadcasting Fluidigm plates to the warehouse.
This behaviour varies based on the PlatePurpose
159
160
161
|
# File 'app/models/plate.rb', line 159
def cherrypick_completed
plate_purpose.cherrypick_completed(self)
end
|
200
201
202
|
# File 'app/models/plate.rb', line 200
def
@comments ||= CommentsProxy::Plate.new(self)
end
|
#compatible_purposes ⇒ Object
404
405
406
|
# File 'app/models/plate.rb', line 404
def compatible_purposes
PlatePurpose.compatible_with_purpose(purpose)
end
|
#convert_to(new_purpose) ⇒ Object
400
401
402
|
# File 'app/models/plate.rb', line 400
def convert_to(new_purpose)
update!(plate_purpose: new_purpose)
end
|
#details ⇒ Object
298
299
300
|
# File 'app/models/plate.rb', line 298
def details
purpose.try(:name) || 'Unknown plate purpose'
end
|
#find_well_by_name(well_name) ⇒ Object
Also known as:
find_well_by_map_description
277
278
279
|
# File 'app/models/plate.rb', line 277
def find_well_by_name(well_name)
wells.loaded? ? wells.indexed_by_location[well_name] : wells.located_at_position(well_name).first
end
|
#generate_barcode ⇒ Object
Plates use a different counter to tubes, and prior to the foreign barcodes update
this method would have fallen back to Barcodable#generate tubes, and potentially generated
an invalid plate barcode. In the future we probably want to scrap this approach entirely,
and generate all barcodes in the plate style. (That is, as part of the factory on, eg. plate purpose)
458
459
460
461
|
# File 'app/models/plate.rb', line 458
def generate_barcode
raise StandardError,
"#generate_barcode has been called on plate, which wasn't supposed to happen, and probably indicates a bug."
end
|
#height ⇒ Object
375
376
377
|
# File 'app/models/plate.rb', line 375
def height
asset_shape&.plate_height(size)
end
|
#invalid_positions(positions) ⇒ Array
Given a list of well map_descriptions (eg. A1), returns those not present on the plate
359
360
361
|
# File 'app/models/plate.rb', line 359
def invalid_positions(positions)
(positions.uniq - unique_positions_on_plate).sort
end
|
#iteration ⇒ Object
190
191
192
193
194
195
196
197
198
|
# File 'app/models/plate.rb', line 190
def iteration
iter =
siblings .where(plate_purpose_id:, sti_type:) .where("#{self.class.table_name}.created_at <= ?", created_at) .count(:id)
iter.zero? ? nil : iter end
|
#maps ⇒ Object
273
274
275
|
# File 'app/models/plate.rb', line 273
def maps
Map.where_plate_size(size).where_plate_shape(asset_shape)
end
|
#name_for_label ⇒ Object
367
368
369
|
# File 'app/models/plate.rb', line 367
def name_for_label
name
end
|
#number_of_blank_samples ⇒ Object
340
341
342
|
# File 'app/models/plate.rb', line 340
def number_of_blank_samples
wells.with_blank_samples.count
end
|
#occupied_well_count ⇒ Integer
Counts the number of wells containing one or more aliquots.
149
150
151
|
# File 'app/models/plate.rb', line 149
def occupied_well_count
wells.with_contents.count
end
|
#pick_as_control? ⇒ false
When Cherrypicking, especially on the Hamilton, control plates get placed
on a seperate bed. ControlPlates overide this.
486
487
488
|
# File 'app/models/plate.rb', line 486
def pick_as_control?
false
end
|
#plate_columns ⇒ Object
286
287
288
|
# File 'app/models/plate.rb', line 286
def plate_columns
(1..width)
end
|
#plate_rows ⇒ Object
282
283
284
|
# File 'app/models/plate.rb', line 282
def plate_rows
('A'..('A'.getbyte(0) + height - 1).chr.to_s).to_a
end
|
#plate_type ⇒ Object
290
291
292
|
# File 'app/models/plate.rb', line 290
def plate_type
labware_type&.name || Sequencescape::Application.config.plate_default_type
end
|
#plate_type=(plate_type) ⇒ Object
294
295
296
|
# File 'app/models/plate.rb', line 294
def plate_type=(plate_type)
self.labware_type = PlateType.find_by(name: plate_type)
end
|
#pre_cap_groups ⇒ Object
Adds pre-capture pooling information, we need to delegate this to the stock plate, as we need all the wells
Currently used in Transfer::BetweenPlates to set submission id, we should switch to doing this
directly via Limber with transfer request collections
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
# File 'app/models/plate.rb', line 207
def pre_cap_groups Request
.include_request_metadata
.for_pre_cap_grouping_of(self)
.each_with_object({}) do |request, groups|
next if request.group_id.nil?
groups[request.group_id] = { wells: request.group_into.split(',') }.tap do |pool_information|
pool_information[:pre_capture_plex_level] ||= request.request_metadata.pre_capture_plex_level
pool_information[:submission_id] ||= request.submission_id
end
end
end
|
#priority ⇒ Object
223
224
225
|
# File 'app/models/plate.rb', line 223
def priority
waiting_submissions.maximum(:priority) || in_progress_submissions.maximum(:priority) || 0
end
|
#receptacles_with_position ⇒ Object
Used to unify interface with TubeRacks. Returns a list of all receptacles wells
with position information included for aid performance
116
117
118
|
# File 'app/models/plate.rb', line 116
def receptacles_with_position
wells.includes(:map)
end
|
471
472
473
|
# File 'app/models/plate.rb', line 471
def related_studies
studies
end
|
#sanger_barcode=(barcode) ⇒ Object
463
464
465
|
# File 'app/models/plate.rb', line 463
def sanger_barcode=(barcode)
barcodes << barcode
end
|
#scored? ⇒ Boolean
344
345
346
|
# File 'app/models/plate.rb', line 344
def scored?
wells.any?(&:get_gel_pass)
end
|
#state ⇒ String
The state of a plate loosely defines what has happened to it. In most cases it is determined
by aggregating the state of transfer requests into the wells, although exact behaviour is determined
by the PlatePurpose. State typically only works for pipeline application plates. In general:
- pending: The plate has been registered, but it empty.
- started: The plate contains samples, but required further processing
- passed: Work on the plate is complete, and it can be transferred to another target
- failed: The plate failed QC and can not be progressed further
- cancelled: The plate is no longer required and should be ignored.
131
132
133
|
# File 'app/models/plate.rb', line 131
def state
plate_purpose&.state_of(self)
end
|
#stock_plate ⇒ Plate?
Deprecated. Do not use this for new behaviour.
Attempts to find the 'stock_plate' for the plate. However this is a fairly
nebulous concept. Often it means the plate that first entered a pipeline,
but in other cases it can be the XP plate part way through the process. Further
complication comes from tubes which pool across multiple plates, where identifying
a single stock plate is meaningless. In other scenarios, you split plates out again
and the asset link graph is insufficient.
JG: 2021-02-11:
See https://github.com/sanger/sequencescape/issues/3040 for more information
328
329
330
|
# File 'app/models/plate.rb', line 328
def stock_plate
@stock_plate ||= stock_plate? ? self : lookup_stock_plate
end
|
#stock_plate? ⇒ Boolean
306
307
308
309
310
|
# File 'app/models/plate.rb', line 306
def stock_plate?
return true if plate_purpose.nil?
plate_purpose.stock_plate? && plate_purpose.attached?(self)
end
|
#stock_wells ⇒ Object
This method returns a map from the wells on the plate to their stock well.
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
|
# File 'app/models/plate.rb', line 384
def stock_wells if stock_plate?
wells.with_pool_id.index_with { |w| [w] }
else
wells
.include_stock_wells
.with_pool_id
.each_with_object({}) do |w, store|
storted_stock_wells = w.stock_wells.sort_by { |sw| sw.map.column_order }
store[w] = storted_stock_wells unless storted_stock_wells.empty?
end
.tap { |stock_wells_hash| raise "No stock plate associated with #{id}" if stock_wells_hash.empty? }
end
end
|
#subject_type ⇒ Object
450
451
452
|
# File 'app/models/plate.rb', line 450
def subject_type
'plate'
end
|
#submission_ids ⇒ Object
172
173
174
|
# File 'app/models/plate.rb', line 172
def submission_ids
@submission_ids ||= in_progress_submissions.ids
end
|
#submission_ids_as_source ⇒ Object
176
177
178
|
# File 'app/models/plate.rb', line 176
def submission_ids_as_source
@submission_ids_as_source ||= waiting_submissions.ids
end
|
#submissions ⇒ Object
186
187
188
|
# File 'app/models/plate.rb', line 186
def submissions
waiting_submissions.presence || in_progress_submissions
end
|
#team ⇒ Object
Finds the product line (= team) of the requests coming out of this plate's 'stock plate'.
Written at a time when requests weren't recorded on the aliquot, so could be re-written in a less convoluted way.
435
436
437
438
439
440
441
442
443
444
445
446
447
|
# File 'app/models/plate.rb', line 435
def team
ProductLine
.joins(
[
'INNER JOIN request_types ON request_types.product_line_id = product_lines.id',
'INNER JOIN requests ON requests.request_type_id = request_types.id',
'INNER JOIN well_links ON well_links.source_well_id = requests.asset_id AND well_links.type = "stock"',
'INNER JOIN receptacles AS re ON re.id = well_links.target_well_id'
]
)
.find_by(['re.labware_id = ?', id])
.try(:name) || 'UNKNOWN'
end
|
#unique_positions_on_plate ⇒ Object
363
364
365
|
# File 'app/models/plate.rb', line 363
def unique_positions_on_plate
maps.distinct.pluck(:description)
end
|
#update_qc_values_with_parser(parser) ⇒ Object
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
|
# File 'app/models/plate.rb', line 412
def update_qc_values_with_parser(parser)
ActiveRecord::Base.transaction do
qc_assay = QcAssay.new
parser.each_well_and_parameters do |position, well_updates|
well = well_hash[position] || next
well_updates.each do |attribute, value|
QcResult.create!(
asset: well,
key: attribute,
unit_value: value,
assay_type: parser.assay_type,
assay_version: parser.assay_version,
qc_assay: qc_assay
)
end
end
end
true
end
|
#update_volume(volume_change) ⇒ Void
Modifies the recorded volume information of all wells on a plate by volume_change
140
141
142
|
# File 'app/models/plate.rb', line 140
def update_volume(volume_change)
ActiveRecord::Base.transaction { wells.each { |well| well.update_volume(volume_change) } }
end
|
#well_hash ⇒ Object
408
409
410
|
# File 'app/models/plate.rb', line 408
def well_hash
@well_hash ||= wells.include_map.includes(:well_attribute).index_by(&:map_description)
end
|
#wells_in_column_order ⇒ Object
479
480
481
|
# File 'app/models/plate.rb', line 479
def wells_in_column_order
wells.loaded? ? wells.sort_by(&:column_order) : wells.in_column_major_order
end
|
#wells_in_row_order ⇒ Object
475
476
477
|
# File 'app/models/plate.rb', line 475
def wells_in_row_order
wells.loaded? ? wells.sort_by(&:row_order) : wells.in_row_major_order
end
|
#width ⇒ Object
379
380
381
|
# File 'app/models/plate.rb', line 379
def width
asset_shape&.plate_width(size)
end
|