-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DPL-823 [Part 1 - download] Import PBMC pool plates into Sequencescape #4017
Changes from 16 commits
cca9a73
c2ed6d7
38119c3
46a0d8c
00ae902
eed57ed
a4be4d2
1dc48a0
ce77810
6563cd1
5361d1e
00d2e26
23628e1
f4afdf7
3f029c1
b2dcbf0
a3ab53f
b56a3af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ | |
# for the potential samples. It also generates a {SampleManifestExcel} | ||
# spreadsheet which gets sent to the customer. | ||
# | ||
# The labware that gets generate is determined by the {#asset_type} which | ||
# The labware that gets generated is determined by the {#asset_type} which | ||
# switches out the {#core_behaviour} module {SampleManifest::CoreBehaviour}. | ||
# This is concerned with generating {Labware} and {Receptacle receptacles}, | ||
# generating any event specific to the asset type, and setting manifest specific | ||
|
@@ -49,6 +49,7 @@ def self.included(base) | |
has_uploaded_document :generated, differentiator: 'generated' | ||
|
||
attr_accessor :override, :only_first_label | ||
attr_writer :rows_per_well | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SampleManifest#rows_per_well is a writable attribute |
||
|
||
class_attribute :spreadsheet_offset | ||
class_attribute :spreadsheet_header_row | ||
|
@@ -128,6 +129,13 @@ def default_filename | |
"#{study_id}stdy_manifest_#{id}_#{created_at.to_formatted_s(:dmy)}" | ||
end | ||
|
||
# Use a default value of 1 for rows_per_well if not set | ||
def rows_per_well | ||
1 | ||
# TODO: replace above line with below line to turn the rows_per_well feature on, when DPL-823 is complete | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO found |
||
# @rows_per_well || 1 | ||
end | ||
|
||
scope :pending_manifests, | ||
-> { | ||
order(id: :desc).includes(:uploaded_document).references(:uploaded_document).where(documents: { id: nil }) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,15 @@ def initialize(manifest) | |
|
||
def generate | ||
@plates = generate_plates(purpose) | ||
|
||
sanger_sample_ids = insert_sanger_sample_ids | ||
well_data = build_well_data(sanger_sample_ids) | ||
|
||
build_wells_async(well_data) | ||
|
||
@details_array = build_details_array(well_data) | ||
|
||
@manifest.update!(barcodes: @plates.map(&:human_barcode)) | ||
end | ||
|
||
def acceptable_purposes | ||
|
@@ -27,24 +36,6 @@ def included_resources | |
[{ sample: :sample_metadata, asset: { plate: :barcodes } }] | ||
end | ||
|
||
def generate_wells(well_data, plates) | ||
# Generate the wells, samples & requests asynchronously. | ||
generate_wells_for_plates(well_data, plates) do |this_plates_well_data, plate| | ||
generate_wells_asynchronously(this_plates_well_data.map { |map, sample_id| [map.id, sample_id] }, plate.id) | ||
end | ||
|
||
# Ensure we maintain the information we need for printing labels and generating | ||
# the CSV file | ||
@plates = plates.sort_by(&:human_barcode) | ||
|
||
@details_array = | ||
plates.flat_map do |plate| | ||
well_data | ||
.slice!(0, plate.size) | ||
.map { |map, sample_id| { barcode: plate.human_barcode, position: map.description, sample_id: sample_id } } | ||
end | ||
end | ||
|
||
def io_samples | ||
samples.map do |sample| | ||
container = sample.primary_receptacle | ||
|
@@ -83,65 +74,60 @@ def labware=(labware) | |
|
||
# We use the barcodes here as we may need to reference the plates before the delayed job has passed | ||
def labware | ||
plates | labware_from_barcodes | ||
plates | Labware.with_barcode(barcodes) | ||
end | ||
alias printables labware | ||
|
||
# Called by {SampleManifest::GenerateWellsJob} and builds the wells | ||
def generate_wells_job(wells_for_plate, plate) | ||
wells_for_plate.map do |map, sanger_sample_id| | ||
plate | ||
.wells | ||
.create!(map: map) do |well| | ||
SampleManifestAsset.create(sanger_sample_id: sanger_sample_id, asset: well, sample_manifest: @manifest) | ||
end | ||
end | ||
RequestFactory.create_assets_requests(plate.wells, study) | ||
plate.events.created_using_sample_manifest!(@manifest.user) | ||
end | ||
|
||
private | ||
|
||
# This method ensures that each of the plates is handled by an individual job. If it doesn't do this we run | ||
# the risk that the 'handler' column in the database for the delayed job will not be large enough and will | ||
# truncate the data. | ||
def generate_wells_for_plates(well_data, plates) | ||
cloned_well_data = well_data.dup | ||
plates.each { |plate| yield(cloned_well_data.slice!(0, plate.size), plate) } | ||
end | ||
|
||
def labware_from_barcodes | ||
Labware.with_barcode(barcodes) | ||
def generate_plates(purpose) | ||
Array.new(count) { purpose.create!(:without_wells) }.sort_by(&:human_barcode) | ||
end | ||
|
||
def generate_wells_asynchronously(map_ids_to_sample_ids, plate_id) | ||
Delayed::Job.enqueue SampleManifest::GenerateWellsJob.new(@manifest.id, map_ids_to_sample_ids, plate_id) | ||
def insert_sanger_sample_ids | ||
sanger_sample_ids = generate_sanger_ids(@plates.sum(&:size) * @manifest.rows_per_well) | ||
sanger_sample_ids.map do |sanger_sample_id| | ||
SangerSampleId.generate_sanger_sample_id!(study.abbreviation, sanger_sample_id) | ||
end | ||
end | ||
|
||
# rubocop:todo Metrics/MethodLength | ||
def generate_plates(purpose) # rubocop:todo Metrics/AbcSize | ||
study_abbreviation = study.abbreviation | ||
|
||
well_data = [] | ||
plates = Array.new(count) { purpose.create!(:without_wells) }.sort_by(&:human_barcode) | ||
|
||
plates.each do |plate| | ||
sanger_sample_ids = generate_sanger_ids(plate.size) | ||
# output: | ||
# plate_id => { map_id => [sanger_sample_id, sanger_sample_id, ...] } | ||
def build_well_data(sanger_sample_ids) | ||
@plates.each_with_object({}) do |plate, well_data| | ||
well_data[plate.id] = {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SampleManifest::PlateBehaviour::Base#build_well_data calls 'plate.id' 2 times There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SampleManifest::PlateBehaviour::Base#build_well_data refers to 'plate' more than self (maybe move it to another class?) |
||
|
||
plate.maps.in_column_major_order.each do |well_map| | ||
sanger_sample_id = sanger_sample_ids.shift | ||
generated_sanger_sample_id = SangerSampleId.generate_sanger_sample_id!(study_abbreviation, sanger_sample_id) | ||
|
||
well_data << [well_map, generated_sanger_sample_id] | ||
well_data[plate.id][well_map.id] = sanger_sample_ids.shift(@manifest.rows_per_well) | ||
end | ||
end | ||
end | ||
|
||
generate_wells(well_data, plates) | ||
@manifest.update!(barcodes: plates.map(&:human_barcode)) | ||
# Each of the plates is handled by an individual job. | ||
# If it doesn't do this we run the risk that the 'handler' column in the database | ||
# for the delayed job will not be large enough and will truncate the data. | ||
def build_wells_async(well_data) | ||
@plates.each do |plate| | ||
Delayed::Job.enqueue SampleManifest::GenerateWellsJob.new(@manifest.id, well_data[plate.id], plate.id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SampleManifest::PlateBehaviour::Base#build_wells_async calls 'plate.id' 2 times |
||
end | ||
end | ||
|
||
plates | ||
# output: | ||
# [{barcode, position, sanger_sample_id}, {barcode, position, sanger_sample_id}, ...] | ||
def build_details_array(well_data) | ||
@details_array = | ||
@plates.flat_map do |plate| | ||
well_data[plate.id].flat_map do |map_id, sanger_sample_ids| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SampleManifest::PlateBehaviour::Base#build_details_array refers to 'plate' more than self (maybe move it to another class?) |
||
sanger_sample_ids.map do |sanger_sample_id| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SampleManifest::PlateBehaviour::Base#build_details_array contains iterators nested 3 deep |
||
{ | ||
barcode: plate.human_barcode, | ||
position: plate.maps.find(map_id).description, | ||
sample_id: sanger_sample_id | ||
} | ||
end | ||
end | ||
end | ||
sdjmchattie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
# rubocop:enable Metrics/MethodLength | ||
end | ||
|
||
class Core < Base | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,15 @@ | ||
# scRNA Core pipeline purposes | ||
# Most are defined in the Limber config, but the below also need to be defined in Sequencescape. | ||
# They are in a separate file so it can be 'feature flagged off' until needed. | ||
--- | ||
# The 'LRC PBMC Pools' purpose is controlled by Limber. However, it has been | ||
# added here to create submission and request type records for scRNA Core cDNA | ||
# Prep stage. | ||
--- | ||
LRC PBMC Pools: | ||
stock_plate: false | ||
cherrypickable_target: false | ||
# The 'LRC PBMC Pools Input' purpose is included here so that it's available for | ||
# sample manifests. | ||
LRC PBMC Pools Input: | ||
stock_plate: true | ||
cherrypickable_target: false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# frozen_string_literal: true | ||
class AddRowsPerWellToSampleManifests < ActiveRecord::Migration[6.0] | ||
def change | ||
add_column :sample_manifests, :rows_per_well, :integer | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps the work around for this variable renaming pain is to use that Ruby syntax where you define a method to pass the values to instead of write it out in full like you have here. I don't know if this works when you have two values coming in, but I know you can push the values through another method when you write it something like: