Skip to content

Commit

Permalink
Merge pull request #1936 from sanger/Y24-319-scrna-pooling-driver-file
Browse files Browse the repository at this point in the history
Y24-319 - scRNA Pooling driver file changes
  • Loading branch information
andrewsparkes authored Sep 25, 2024
2 parents 2cafcb4 + 730224f commit cbb94f7
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 130 deletions.
16 changes: 4 additions & 12 deletions app/models/presenters/qc_threshold_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,16 @@ def thresholds
end

def value_for(qc_result)
indexed_thresholds[qc_result.key].value_for(qc_result)
indexed_thresholds[qc_result.key]&.value_for(qc_result)
end

private

def indexed_thresholds
@indexed_thresholds ||=
all_thresholds.index_with { |key| Threshold.new(key, well_results.fetch(key, []), configuration.fetch(key, {})) }
configuration.keys.index_with do |key|
Threshold.new(key, well_results.fetch(key, []), configuration.fetch(key, {}))
end
end

#
Expand All @@ -175,15 +177,5 @@ def well_results
@well_results ||= plate.wells.flat_map(&:all_latest_qc).group_by(&:key)
end

#
# An array of all qc results associated with the plate or configuration.
# Configured qc results are displayed first
#
# @return [Array<String>] All qc results associated with the plate or configuration
#
def all_thresholds
(configuration.keys + well_results.keys).uniq
end

attr_reader :configuration, :plate
end
4 changes: 4 additions & 0 deletions app/sequencescape/sequencescape/api/v2/well.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def latest_live_cell_count
latest_qc(key: 'live_cell_count', units: 'cells/ml')
end

def latest_total_cell_count
latest_qc(key: 'total_cell_count', units: 'cells/ml')
end

def latest_cell_viability
latest_qc(key: 'viability', units: '%')
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
default_required_number_of_cells = args[:default_required_number_of_cells]
study_required_number_of_cells_key = args[:study_required_number_of_cells_key]

maximum_sample_volume = 50.0 # microlitres
maximum_sample_volume = 60.0 # microlitres
resuspension_volume_per_sample = 2.2 # microlitres
minimum_resuspension_volume = 10.0 # microlitres
millilitres_to_microlitres = 1_000.0
wastage_accountment = 0.95238 # Accounting for wastage of material during transfer between labware
desired_chip_loading_concentration = 2400

# Make a mapping { src_barcode => { src_location => src_well } }

Expand All @@ -39,7 +41,7 @@
transfer_request_data = []
each_source_metadata_for_plate(@plate) do |src_barcode, src_location, dest_well|
src_well = ancestor_plates_wells[src_barcode][src_location]
cell_count = src_well.latest_live_cell_count # cells / millilitres
cell_count = src_well.latest_total_cell_count # cells / millilitres
next if cell_count.nil?

# Use the study's required number of cells if available, otherwise use the default
Expand All @@ -58,6 +60,8 @@
@plate.labware_barcode.human,
dest_well.location,
'%0.2f' % required_volume,
# We pass in the required number of cells so that we can calculate the resuspension volume later
required_number_of_cells
]
end

Expand All @@ -67,8 +71,11 @@

rows_array = transfer_request_data.map do |data|
samples_in_pool = samples_by_dest_well[data[3]].count
resuspension_volume = [samples_in_pool * resuspension_volume_per_sample, minimum_resuspension_volume].max
data + ['%0.2f' % resuspension_volume]
required_number_of_cells = data[5]
resuspension_volume = [(samples_in_pool * required_number_of_cells * wastage_accountment) / desired_chip_loading_concentration, minimum_resuspension_volume].max
# Replace required number of cells with resuspension volume
data[5] = '%0.2f' % resuspension_volume
data
end
%>
<%= rows_array.sort_by{ |a| [ a[0], WellHelpers.well_coordinate(a[1])] }.map(&:to_csv).join %>
6 changes: 3 additions & 3 deletions config/purposes/scrna_core_cdna_prep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ LRC PBMC Defrost PBS:
viability:
units: '%'
default_threshold: 50
live_cell_count:
name: Cell count
total_cell_count:
name: Total cell count
units: 'cells/ml'
default_threshold: 400000
decimal_places: 0
Expand All @@ -53,7 +53,7 @@ LRC PBMC Pools:
name: Presenters::DonorPoolingPlatePresenter
args:
# The default number of cells to calculate the required volumes.
default_required_number_of_cells: 5000
default_required_number_of_cells: 30000
# poly_metadatum key for study specific required number of cells.
study_required_number_of_cells_key: scrna_core_pbmc_donor_pooling_required_number_of_cells
# Constants used to calculate source well volume
Expand Down
2 changes: 1 addition & 1 deletion config/purposes/scrna_core_cell_extraction.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ LRC PBMC Bank:
units: '%'
default_threshold: 50
live_cell_count:
name: Cell count
name: Live cell count
units: 'cells/ml'
default_threshold: 400000
decimal_places: 0
Expand Down
1 change: 1 addition & 0 deletions spec/factories/purpose_config_factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
printer_type { '96 Well Plate' }
pmb_template { 'sqsc_96plate_label_template' }
file_links { [{ name: 'Download Concentration CSV', id: 'concentrations' }] }
qc_thresholds { {} }

# Sets up a stock plate configuration
factory :stock_plate_config do
Expand Down
12 changes: 11 additions & 1 deletion spec/features/failing_thresholds_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@
# Setup stubs
background do
# Set-up the plate config
create :purpose_config, uuid: 'stock-plate-purpose-uuid'
create :purpose_config,
uuid: 'stock-plate-purpose-uuid',
qc_thresholds: {
molarity: {
name: 'molarity',
default_threshold: 20,
max: 50,
min: 5,
units: 'nM'
}
}
create :purpose_config, uuid: 'child-purpose-0'

# We look up the user
Expand Down
76 changes: 15 additions & 61 deletions spec/models/presenters/qc_threshold_presenter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,55 +27,12 @@
context 'with no configuration' do
let(:configuration) { {} }

it 'reads the thresholds from the wells' do
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity'),
have_attributes(name: 'concentration'),
have_attributes(name: 'viability'),
have_attributes(name: 'volume')
)
end

it 'sets limits derived from the wells' do
# NOTE: Maximum and minimum values need to have the same (or lower) precision
# as the step, otherwise the browser will adopt this precision
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity', max: 52.13, min: 7.99),
have_attributes(name: 'concentration', max: 10.0, min: 10.0),
have_attributes(name: 'viability', min: 0, max: 100),
have_attributes(name: 'volume', min: 1000.0, max: 1000.0)
)
end

it 'picks the most precise set of units' do
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity', units: 'nM'),
have_attributes(name: 'concentration', units: 'ng/ul'),
have_attributes(name: 'viability', units: '%'),
have_attributes(name: 'volume', units: 'ul')
)
end

it 'sets defaults to 0' do
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity', default: 0),
have_attributes(name: 'concentration', default: 0),
have_attributes(name: 'viability', default: 0),
have_attributes(name: 'volume', default: 0)
)
end

it 'picks a step of 0.01' do
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity', step: 0.01),
have_attributes(name: 'concentration', step: 0.01),
have_attributes(name: 'viability', step: 0.01),
have_attributes(name: 'volume', step: 0.01)
)
it 'shows no thresholds' do
expect(presenter.thresholds).to eq([])
end

it 'is enabled' do
expect(presenter.thresholds).to all be_enabled
expect(presenter.thresholds).to all be_disabled
end
end

Expand Down Expand Up @@ -104,7 +61,9 @@
[create(:qc_result, key: 'concentration', value: '50', units: 'nM')]
]
end
let(:configuration) { {} }
let(:configuration) do
{ concentration: { name: 'concentration', default_threshold: 10, max: 100, min: 5, units: 'ng/nl' } }
end

it 'is disabled' do
expect(presenter.thresholds.first).to_not be_enabled
Expand Down Expand Up @@ -144,8 +103,6 @@
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity'),
have_attributes(name: 'cell count'),
have_attributes(name: 'concentration'),
have_attributes(name: 'viability'),
have_attributes(name: 'volume')
)
end
Expand All @@ -154,8 +111,6 @@
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'cell count', max: 5, min: 0),
have_attributes(name: 'molarity', max: 50, min: 5),
have_attributes(name: 'concentration', max: 10.0, min: 10.0),
have_attributes(name: 'viability', min: 0, max: 100),
have_attributes(name: 'volume', min: 1.0, max: 1.0) # 1.0 as units specify ml
)
end
Expand All @@ -164,41 +119,40 @@
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity', units: 'nM'),
have_attributes(name: 'cell count', units: 'cells/ml'),
have_attributes(name: 'volume', units: 'ml'),
have_attributes(name: 'concentration', units: 'ng/ul'),
have_attributes(name: 'viability', units: '%')
have_attributes(name: 'volume', units: 'ml')
)
end

it 'picks the configured defaults' do
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity', default: 20),
have_attributes(name: 'cell count', default: 2),
have_attributes(name: 'volume', default: 0),
have_attributes(name: 'concentration', default: 0),
have_attributes(name: 'viability', default: 0)
have_attributes(name: 'volume', default: 0)
)
end

it 'picks the configured step' do
expect(presenter.thresholds).to contain_exactly(
have_attributes(name: 'molarity', step: 0.01),
have_attributes(name: 'cell count', step: 1),
have_attributes(name: 'volume', step: 0.01),
have_attributes(name: 'concentration', step: 0.01),
have_attributes(name: 'viability', step: 0.01)
have_attributes(name: 'volume', step: 0.01)
)
end
end
end

describe '#value_for' do
let(:configuration) { {} }
let(:configuration) { { volume: { name: 'volume', default_threshold: 20, max: 50, min: 1, units: 'ul' } } }
let(:qc_to_convert) { create(:qc_result, key: 'volume', value: '1', units: 'ml') }
let(:invalid_qc) { create(:qc_result, key: 'concentration', value: '1', units: 'nM') }

# Value for converts all scalar values to an appropriate unit
it 'converts values to the thresholds unit' do
expect(presenter.value_for(qc_to_convert)).to eq 1000.0
end

it 'returns nil if the key is not configured' do
expect(presenter.value_for(invalid_qc)).to eq nil
end
end
end
69 changes: 69 additions & 0 deletions spec/sequencescape/api/v2/well_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,73 @@
end
end
end

describe '#latest_total_cell_count' do
let(:earlier_total_cell_count) do
create(
:qc_result,
key: 'total_cell_count',
value: '1000000',
units: 'cells/ml',
created_at: Time.utc(2020, 1, 2, 3, 4, 5)
)
end
let(:later_total_cell_count) do
create(
:qc_result,
key: 'total_cell_count',
value: '1350000',
units: 'cells/ml',
created_at: Time.utc(2020, 2, 3, 4, 5, 6)
)
end
let(:concentration_result) do
create(
:qc_result_concentration,
created_at: Time.utc(2020, 11, 12, 13, 14, 15) # Latest of all the creation times
)
end

context 'when well has a single concentration result' do
let(:well) { create(:v2_well, qc_results: [concentration_result]) }

it 'returns nil' do
expect(well.latest_total_cell_count).to be_nil
end
end

context 'when well has a single cell count result' do
let(:well) { create(:v2_well, qc_results: [earlier_total_cell_count]) }

it 'returns the correct QC result' do
expect(well.latest_total_cell_count).to be(earlier_total_cell_count)
end
end

context 'when well has a two concentration results in date order' do
let(:well) { create(:v2_well, qc_results: [earlier_total_cell_count, later_total_cell_count]) }

it 'returns the later QC result by creation date' do
expect(well.latest_total_cell_count).to be(later_total_cell_count)
end
end

context 'when well has a two concentration results in reverse date order' do
let(:well) { create(:v2_well, qc_results: [later_total_cell_count, earlier_total_cell_count]) }

it 'returns the later QC result by creation date' do
expect(well.latest_total_cell_count).to be(later_total_cell_count)
end
end

context 'when well has a mixed concentration result' do
let(:well) do
create(:v2_well, qc_results: [concentration_result, later_total_cell_count, earlier_total_cell_count])
end

it 'returns the later QC result for total cell count' do
expect(well.latest_total_cell_count).to be(later_total_cell_count)
end
end
end
end
Loading

0 comments on commit cbb94f7

Please sign in to comment.