Skip to content
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

Y24-375 scRNA - refactored shared constants to move them into a central config file #2050

Closed
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 8 additions & 24 deletions app/models/validators/required_number_of_cells_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module Validators
# for the user to configure the option after seeing the warning and download
# the driver file again to use the correct value.
class RequiredNumberOfCellsValidator < ActiveModel::Validator
STUDIES_WITHOUT_REQUIRED_NUMBER_OF_CELLS =
STUDIES_WITHOUT_REQUIRED_NUMBER_OF_CELLS_PER_SAMPLE_PER_POOL =
'The required number of cells is not configured for all studies ' \
'going into pooling on this plate. If not provided, the default ' \
'value %s will be used for the samples of the following studies: ' \
Expand All @@ -33,16 +33,16 @@ class RequiredNumberOfCellsValidator < ActiveModel::Validator
def validate(presenter)
return unless presenter.labware.state == 'pending'

studies = source_studies_without_config(presenter, study_required_number_of_cells_key(presenter))
study_required_number_of_cells_per_sample_in_pool_key =
Rails.application.config.scrna_config[:study_required_number_of_cells_per_sample_in_pool_key]
default_value = Rails.application.config.scrna_config[:required_number_of_cells_per_sample_in_pool]

studies = source_studies_without_config(presenter, study_required_number_of_cells_per_sample_in_pool_key)
return if studies.empty?

formatted_string =
format(
STUDIES_WITHOUT_REQUIRED_NUMBER_OF_CELLS,
default_required_number_of_cells(presenter),
studies.join(', ')
)
presenter.errors.add(:required_number_of_cells, formatted_string)
format(STUDIES_WITHOUT_REQUIRED_NUMBER_OF_CELLS_PER_SAMPLE_PER_POOL, default_value, studies.join(', '))
presenter.errors.add(:required_number_of_cells_per_sample_in_pool, formatted_string)
end

private
Expand All @@ -65,21 +65,5 @@ def source_studies_without_config(presenter, key)
.map(&:name)
.uniq
end

# Retrieves the default required number of cells from the purpose config.
#
# @return [Integer] the default required number of cells
# :reek:UtilityFunction { public_methods_only: true }
def default_required_number_of_cells(presenter)
Settings.purposes[presenter.labware.purpose.uuid][:presenter_class][:args][:default_required_number_of_cells]
end

# Retrieves the poly_metadatum key for the study-specific required number of cells.
#
# @return [String] the poly_metadatum key
# :reek:UtilityFunction { public_methods_only: true }
def study_required_number_of_cells_key(presenter)
Settings.purposes[presenter.labware.purpose.uuid][:presenter_class][:args][:study_required_number_of_cells_key]
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ module HasPolyMetadata
# @param key [String] the key of the PolyMetadatum to find
# @return [PolyMetadatum, nil] the found PolyMetadatum object, or nil if no match is found
def poly_metadatum_by_key(key)
# if no poly_metadata exist for this model instance, or no key value is passed, return nil
return nil if poly_metadata.blank? || key.blank?

poly_metadata.find { |pm| pm.key == key }
end
end
Expand Down
1 change: 1 addition & 0 deletions app/sequencescape/sequencescape/api/v2/well.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

class Sequencescape::Api::V2::Well < Sequencescape::Api::V2::Base # rubocop:todo Style/Documentation
include Sequencescape::Api::V2::Shared::HasRequests
include Sequencescape::Api::V2::Shared::HasPolyMetadata
has_many :qc_results
has_many :requests_as_source, class_name: 'Sequencescape::Api::V2::Request'
has_many :requests_as_target, class_name: 'Sequencescape::Api::V2::Request'
Expand Down
52 changes: 38 additions & 14 deletions app/views/exports/hamilton_gem_x_5p_chip_loading.csv.erb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<%# Driver file for Hamilton robot to transfer from LRC PBMC Pools (or Input) to LRC GEM x 5P chip %>
<%= CSV.generate_line [
'Workflow',
'Workflow',
@workflow
],
row_sep: ''
],
row_sep: ''
%>
<%= CSV.generate_line [], row_sep: "" %>
<%= CSV.generate_line [
Expand All @@ -19,14 +19,16 @@
row_sep: ""
%>
<%
# Configuration specific to this export
# Constants from config/initializers/scrna_config.rb
scrna_config = Rails.application.config.scrna_config

required_number_of_cells = 30000
wastage_factor = 0.95238 # accounting for wastage of material when transferring between labware
chip_loading_concentration = 2400
desired_number_of_cells_per_chip_well = 90000
desired_sample_volume = 37.5 # microlitres
volume_taken_for_cell_counting = 10.0 # microlitres
# fetch constants from scrna_config for use in calculations on source wells below
required_number_of_cells_per_sample_in_pool = scrna_config[:required_number_of_cells_per_sample_in_pool]
wastage_factor = scrna_config[:wastage_factor]
desired_chip_loading_concentration = scrna_config[:desired_chip_loading_concentration]
desired_chip_loading_volume = scrna_config[:desired_chip_loading_volume]
volume_taken_for_cell_counting = scrna_config[:volume_taken_for_cell_counting]
number_of_cells_per_chip_well_key = scrna_config[:number_of_cells_per_chip_well_key]
KatyTaylor marked this conversation as resolved.
Show resolved Hide resolved

# Destination wells are mapped to numbers: A1 -> 17, A2 -> 18, ..., A8 -> 24
mapping = (1..8).each_with_object({}) { |i, hash| hash["A#{i}"] = (16 + i).to_s }
Expand All @@ -35,11 +37,33 @@
ancestral_plate_wells = @ancestor_plate.wells.index_by(&:location)
rows_array = []
each_source_metadata_for_plate(@plate) do |src_barcode, src_location, dest_well|
number_of_samples = ancestral_plate_wells[src_location].aliquots.length
resuspension_volume = (number_of_samples * required_number_of_cells * wastage_factor).to_f / chip_loading_concentration
src_well = ancestral_plate_wells[src_location]
number_of_samples = src_well.aliquots.length

# Use the source well's number_of_cells_per_chip_well if available, otherwise error.
# Value should have been calculated and stored on the pool well polymetadata.
well_poly_metadatum = src_well.poly_metadatum_by_key(number_of_cells_per_chip_well_key)

# raise error if polymetadata is missing
raise "Missing poly metadata for number of cells per chip well for #{src_barcode} #{src_location}, " \
'cannot generate driver file' unless well_poly_metadatum
KatyTaylor marked this conversation as resolved.
Show resolved Hide resolved

# extract value from the polymetadata
number_of_cells_per_chip_well = well_poly_metadatum&.value.to_f.nonzero?

# calculations

# calculate volume we believe is remaining in the source well
resuspension_volume = (number_of_samples * required_number_of_cells_per_sample_in_pool * wastage_factor).to_f / desired_chip_loading_concentration
source_well_volume = resuspension_volume - volume_taken_for_cell_counting
sample_volume = desired_number_of_cells_per_chip_well.to_f/chip_loading_concentration
pbs_volume = desired_sample_volume - sample_volume

# calculate volume of the source sample well to take to load onto the chip
sample_volume = number_of_cells_per_chip_well/desired_chip_loading_concentration
andrewsparkes marked this conversation as resolved.
Show resolved Hide resolved

# calculate pbs buffer volume with which to top up the chip loading volume
pbs_volume = desired_chip_loading_volume - sample_volume

# add row to driver file output csv
rows_array << [
src_barcode,
src_location,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,15 @@
row_sep: ''
%>
<%
# Configuration specific to this export

args = Settings.purposes[@plate.purpose.uuid][:presenter_class][:args]
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 = 60.0 # microlitres
minimum_sample_volume = 5.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
# Constants from config/initializers/scrna_config.rb
scrna_config = Rails.application.config.scrna_config
required_number_of_cells_per_sample_in_pool = scrna_config[:required_number_of_cells_per_sample_in_pool]
maximum_sample_volume = scrna_config[:maximum_sample_volume]
minimum_sample_volume = scrna_config[:minimum_sample_volume]
minimum_resuspension_volume = scrna_config[:minimum_resuspension_volume]
millilitres_to_microlitres = scrna_config[:millilitres_to_microlitres]
wastage_factor = scrna_config[:wastage_factor]
desired_chip_loading_concentration = scrna_config[:desired_chip_loading_concentration]

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

Expand All @@ -45,15 +41,8 @@
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

poly_metadatum = src_well.aliquots.first.study.poly_metadatum_by_key(study_required_number_of_cells_key)
study_required_number_of_cells = poly_metadatum&.value.to_i.nonzero?
required_number_of_cells = study_required_number_of_cells || default_required_number_of_cells

# Calculate the sample volume required for the number of cells

required_volume = (millilitres_to_microlitres * required_number_of_cells / cell_count.value.to_f).clamp(minimum_sample_volume, maximum_sample_volume)
required_volume = (millilitres_to_microlitres * required_number_of_cells_per_sample_in_pool / cell_count.value.to_f).clamp(minimum_sample_volume, maximum_sample_volume)

transfer_request_data << [
src_barcode,
Expand All @@ -62,7 +51,7 @@
dest_well.location,
'%0.1f' % required_volume,
# We pass in the required number of cells so that we can calculate the resuspension volume later
required_number_of_cells
required_number_of_cells_per_sample_in_pool
]
end

Expand All @@ -72,8 +61,8 @@

rows_array = transfer_request_data.map do |data|
samples_in_pool = samples_by_dest_well[data[3]].count
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
required_number_of_cells_per_sample_in_pool = data[5]
resuspension_volume = [(samples_in_pool * required_number_of_cells_per_sample_in_pool * wastage_factor) / desired_chip_loading_concentration, minimum_resuspension_volume].max
# Replace required number of cells with resuspension volume
data[5] = '%0.1f' % resuspension_volume
data
Expand Down
21 changes: 11 additions & 10 deletions app/views/exports/hamilton_lrc_pbmc_pools_to_cellaca_count.csv.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,21 @@
<%
rows_array = []

# Reading constants
args = Settings.purposes[@plate.purpose.uuid][:presenter_class][:args]
required_number_of_cells = args[:required_number_of_cells]
wastage_factor = args[:wastage_factor]
desired_chip_loading_concentration = args[:desired_chip_loading_concentration]
# Constants from config/initializers/scrna_config.rb
scrna_config = Rails.application.config.scrna_config
required_number_of_cells_per_sample_in_pool = scrna_config[:required_number_of_cells_per_sample_in_pool]
wastage_factor = scrna_config[:wastage_factor]
desired_chip_loading_concentration = scrna_config[:desired_chip_loading_concentration]

filtered_wells = @plate.wells_in_columns.reject { |well| well.empty? || well.failed? }
filtered_wells.each do | well |
samples_in_pool = well.aliquots.size
source_well_volume = (samples_in_pool * required_number_of_cells * wastage_factor) / desired_chip_loading_concentration
filtered_wells.each do | src_well |
samples_in_pool = src_well.aliquots.size

source_well_volume = (samples_in_pool * required_number_of_cells_per_sample_in_pool * wastage_factor) / desired_chip_loading_concentration
row = [
@plate.labware_barcode.human,
well.location,
well.name,
src_well.location,
src_well.name,
'%0.1f' % source_well_volume
]
rows_array << row
Expand Down
31 changes: 31 additions & 0 deletions config/initializers/scrna_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

# Stores constants used in pooling and chip loading calculations for samples in the scRNA Core pipeline.
Rails.application.config.scrna_config = {
andrewsparkes marked this conversation as resolved.
Show resolved Hide resolved
# Maximum volume to take into the pools plate for each sample (in microlitres)
maximum_sample_volume: 60.0,
# Minimum volume to take into the pools plate for each sample (in microlitres)
minimum_sample_volume: 5.0,
# Minimum volume required for resuspension in microlitres
minimum_resuspension_volume: 10.0,
# Conversion factor from millilitres to microlitres
millilitres_to_microlitres: 1_000.0,
# Number of cells required for each sample going into the pool
required_number_of_cells_per_sample_in_pool: 30_000,
# Factor accounting for wastage of material when transferring between labware
wastage_factor: 0.95,
# Desired concentration of cells per microlitre for chip loading
desired_chip_loading_concentration: 2400,
# Desired volume in the chip well (in microlitres)
desired_chip_loading_volume: 37.5,
# Volume taken for cell counting in microlitres
volume_taken_for_cell_counting: 10.0,
# Key for the required number of cells metadata stored on Study (in poly_metadata)
study_required_number_of_cells_per_sample_in_pool_key: 'scrna_core_pbmc_donor_pooling_required_number_of_cells',
# Default viability threshold when passing/failing samples (in percent)
viability_default_threshold: 65,
# Default total cell count threshold when passing/failing samples
total_cell_count_default_threshold: 50_000,
# Key for the number of cells per chip well metadata stored on pool wells (in poly_metadata)
number_of_cells_per_chip_well_key: 'scrna_core_pbmc_donor_pooling_number_of_cells_per_chip_well'
}.freeze
17 changes: 4 additions & 13 deletions config/purposes/scrna_core_cdna_prep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ LRC PBMC Defrost PBS:
:qc_thresholds:
viability:
units: '%'
default_threshold: 65
default_threshold: <%= Rails.application.config.scrna_config[:viability_default_threshold] %>
total_cell_count:
name: Total cell count
units: 'cells/ml'
default_threshold: 500000
default_threshold: <%= Rails.application.config.scrna_config[:total_cell_count_default_threshold] %>
decimal_places: 0
:stock_plate: false
:input_plate: false
Expand All @@ -48,23 +48,14 @@ LRC PBMC Pools:
:qc_thresholds:
viability:
units: '%'
default_threshold: 65
default_threshold: <%= Rails.application.config.scrna_config[:viability_default_threshold] %>
total_cell_count:
name: Total cell count
units: 'cells/ml'
default_threshold: 500000
default_threshold: <%= Rails.application.config.scrna_config[:total_cell_count_default_threshold] %>
decimal_places: 0
:presenter_class:
name: Presenters::DonorPoolingPlatePresenter
args:
# The default number of cells to calculate the required volumes.
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
required_number_of_cells: 30_000
wastage_factor: 0.95238
desired_chip_loading_concentration: 2_400
:creator_class:
name: LabwareCreators::DonorPoolingPlate
args:
Expand Down
18 changes: 18 additions & 0 deletions spec/factories/well_factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,24 @@
well._cached_relationship(:transfer_requests_as_target) { evaluator.transfer_requests_as_target || [] }
end
end

factory :v2_well_with_polymetadata do
transient { poly_metadata { [] } }

after(:build) do |well, evaluator|
# initialise the poly_metadata array
well.poly_metadata = []

# add each polymetadatum to the well
evaluator.poly_metadata.each do |pm|
# set the relationship between the polymetadatum and the well
pm.relationships.metadatable = well

# link the polymetadatum to the well
well.poly_metadata.push(pm)
end
end
end
end

# API V1 collection of wells, used mainly for setting up the well association on v1 plates
Expand Down
Loading
Loading