Class: LabwareCreators::DonorPoolingPlate

Inherits:
Base
  • Object
show all
Includes:
CustomPage, DonorPoolingCalculator, DonorPoolingValidator, SupportParent::PlateOnly
Defined in:
app/models/labware_creators/donor_pooling_plate.rb

Overview

This labware creator receives barcodes for a configured number of source plates from the user. It pools samples from the passed wells into a destination plate. It’s used for scRNA Donor Pooling to create ‘LRC PBMC Pools’ plates from ‘LRC PBMC Defrost PBS’ plates.

The creator imposes restrictions:

  • It doesn’t allow combining samples from different studies or projects.

  • It doesn’t allow samples with the same donor_id in the same pool.

  • All wells must have cell count data unless they are failed.

  • The number of pools must not exceed the number configured for the samples.

The number of pools is determined by a lookup table based on sample count. Tag depth index is added to aliquot attributes to avoid tag clashes.

Constant Summary collapse

SOURCE_PLATE_INCLUDES =

Define related objects to be included when retrieving source plates using the Sequencescape::API::V2.Plate.find_all method. The ‘includes’ argument of the method is expected to be an array of strings.

%w[
  purpose
  wells.aliquots.study
  wells.aliquots.project
  wells.aliquots.request
  wells.aliquots.sample.sample_metadata
  wells.requests_as_source
  wells.qc_results
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

This class inherits a constructor from LabwareCreators::Base

Instance Attribute Details

#barcodesObject

Returns the value of attribute barcodes.



34
35
36
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 34

def barcodes
  @barcodes
end

#minimal_barcodesObject (readonly)

Returns the value of attribute minimal_barcodes.



40
41
42
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 40

def minimal_barcodes
  @minimal_barcodes
end

Instance Method Details

#build_poolsArray<Array<Well>>

Builds the pools for the destination plate. The wells are first grouped by study and project, then split by donor_ids, and finally distributed across pools.

Returns:

  • (Array<Array<Well>>)

    An array of well groups distributed across pools.



218
219
220
221
222
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 218

def build_pools
  groups = split_single_group_by_study_and_project(source_wells_for_pooling)
  groups = split_groups_by_unique_donor_ids(groups)
  distribute_groups_across_pools(groups, number_of_pools)
end

#default_number_of_poolsInteger

The default number of pools to be created if the count is not found in the lookup table.

Returns:

  • (Integer)

    The default number of pools.



59
60
61
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 59

def default_number_of_pools
  purpose_config.dig(:creator_class, :args, :default_number_of_pools)
end

#distribute_groups_across_pools(groups, number_of_pools) ⇒ Array<Array<Well>> Originally defined in module DonorPoolingCalculator

Distributes samples across pools based on group sizes. It sorts the groups by size and splits the largest group into two until the number of groups equals the number of pools or until all groups have a size of 1. The input groups are the result of applying conditions, hence they cannot be mixed.

If the request number of pools is 6 and the input groups are

[1, 2, 3], [4, 5], [6, 7, 8, 9]

where the numbers denote wells,

the result will be:

[3], [1], [2], [4, 5], [6, 7], [8, 9]

for which the steps are:

[1, 2, 3], [4, 5], [6, 7, 8, 9]

-> 3 pools (input)

[4, 5], [6, 7], [8, 9], [1, 2, 3]

-> 4 pools

[3], [4, 5], [6, 7], [8, 9], [1, 2]

-> 5 pools

[3], [1], [2], [4, 5], [6, 7], [8, 9]

-> 6 pools (output)

Parameters:

  • groups (Array<Array<Well>>)

    Array of well groups to be distributed.

Returns:

  • (Array<Array<Well>>)

    Array of distributed groups.

#labware_wellsArray<Well>

Returns all passed wells from the source plates in column order.

Returns:

  • (Array<Well>)

    An array of passed wells.



83
84
85
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 83

def labware_wells
  source_plates.flat_map { |plate| plate.wells_in_columns.select(&:passed?) }
end

#max_number_of_source_platesInteger

Returns the number of source plates from the purpose configuration.

Returns:

  • (Integer)

    The number of source plates.



66
67
68
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 66

def max_number_of_source_plates
  @max_number_of_source_plates ||= purpose_config.dig(:creator_class, :args, :max_number_of_source_plates)
end

#number_of_poolsInteger

Returns the number of pools based on the sample count from the lookup table.

Returns:

  • (Integer)

    The number of pools.



134
135
136
137
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 134

def number_of_pools
  id = purpose_config.dig(:creator_class, :args, :pooling)
  Settings.poolings[id][:number_of_pools][source_wells_for_pooling.count] || default_number_of_pools
end

#number_of_pools_must_not_exceed_configuredvoid Originally defined in module DonorPoolingValidator

This method returns an undefined value.

Validates that the number of calculated pools does not exceed the configured number of pools. If the number of calculated pools is greater, an error is added to the :source_plates attribute.

#parentObject Originally defined in module SupportParent::PlateOnly

#poolsArray<Pool>

Returns the pools for the destination plate.

Returns:

  • (Array<Pool>)

    An array of pools.



116
117
118
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 116

def pools
  @pools ||= build_pools
end

#request_hash(source_well, dest_plate, additional_parameters) ⇒ Hash

Generates a hash representing a transfer request from a source well to a destination well. Additional parameters generated by the well filter are merged into the request hash, i.e.‘outer_request’ and ‘submission_id’. tag_depth is added to the aliquot attributes.

Parameters:

Returns:

  • (Hash)

    A hash representing a transfer request.



174
175
176
177
178
179
180
181
182
183
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 174

def request_hash(source_well, dest_plate, additional_parameters)
  dest_location = transfer_hash[source_well][:dest_locn]
  {
    'source_asset' => source_well.uuid,
    'target_asset' => dest_plate.well_at_location(dest_location)&.uuid,
    :aliquot_attributes => {
      'tag_depth' => tag_depth_hash[source_well]
    }
  }.merge(additional_parameters)
end

#source_barcodes_must_be_differentvoid Originally defined in module DonorPoolingValidator

This method returns an undefined value.

Validates that all source barcodes are unique. If any barcodes are duplicated, an error is added to the :source_barcodes attribute.

#source_barcodes_must_be_enteredvoid Originally defined in module DonorPoolingValidator

This method returns an undefined value.

Validates that at least one source barcode has been entered. If no barcodes are entered, an error is added to the :source_barcodes attribute.

#source_platesArray<Plate>

Returns all source plates associated with the minimal barcodes.

Returns:

  • (Array<Plate>)

    An array of source plates.



90
91
92
93
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 90

def source_plates
  @source_plates ||=
    Sequencescape::Api::V2::Plate.find_all({ barcode: minimal_barcodes }, includes: SOURCE_PLATE_INCLUDES)
end

#source_plates_must_existvoid Originally defined in module DonorPoolingValidator

This method returns an undefined value.

Validates that all source plates corresponding to the minimal barcodes exist. If the number of source plates does not match the number of minimal barcodes, an error is added to the :source_plates attribute.

#source_wells_for_poolingArray<Well>

Returns the source wells for pooling. The wells are filtered using the well_filter.

Returns:

  • (Array<Well>)

    An array of source wells for pooling.



99
100
101
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 99

def source_wells_for_pooling
  well_filter.filtered.map(&:first) # The first element is the well.
end

#source_wells_to_platesHash

Returns a hash mapping each source well to its source plate. The hash contains all source wells independent of the filtering.

Returns:

  • (Hash)

    A hash where the keys are wells and the values are the plates that each well belongs to.



108
109
110
111
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 108

def source_wells_to_plates
  @source_wells_to_plates ||=
    source_plates.each_with_object({}) { |plate, hash| plate.wells.each { |well| hash[well] = plate } }
end

#split_groups_by_unique_donor_ids(groups) ⇒ Array<Array<Well>> Originally defined in module DonorPoolingCalculator

Splits groups ensuring unique donor_ids within each group. Iterates over each group, creating subgroups with wells from a unique donor. The first occurrences of unique donor_ids are grouped, then the second occurrences, and so on. This prevents combining samples with the same donor_id. The result is flattened to a single array of subgroups.

If the input groups are [[w1, w2, w3, w4], [w5, w6, w7], [w8, w9]] where w1, w2, w3, w4, w5, w6, w7, w8, and w9 are wells with (donor_id),

w1(1) w2(2) w3(3) w4(1) w5(4) w6(4) w7(5) w8(6) w9(7)

the result will be:

[w1, w2, w3], [w4], [w5, w7], [w6], [w8, w9]

Note that the input groups are not mixed. donor_ids are unique within each result subgroup.

Parameters:

  • groups (Array<Array<Well>>)

    Array of well groups to be split.

Returns:

  • (Array<Array<Well>>)

    Array of subgroups split by donor ID.

#split_single_group_by_study_and_project(group) ⇒ Array<Array<Well>> Originally defined in module DonorPoolingCalculator

Splits wells into groups by study and project. Wells are grouped based on the study and project of the first aliquot in each well (only one aliquot is expected per well). Returns an array of groups, where each group is an array of wells with the same study and project.

If the input group is [w1, w2, w3, w4, w5, w6, w7, w8, w9] where w1, w2, w3, w4, w5, w6, w7, w8, and w9 are wells with (study_id, project_id),

w1(1,1) w2(1,2) w3(1,3) w4(1,1) w5(1,2) w6(1,3) w7(1,1) w8(2,1) w9(2,2)

the result will be:

[w1, w4, w7], [w2, w5], [w3, w6], [w8], [w9]

Parameters:

  • group (Array<Well>)

    The group of wells to be split.

Returns:

  • (Array<Array<Well>>)

    An array of well groups.

#split_single_group_by_unique_donor_ids(group) ⇒ Array<Array<Well>> Originally defined in module DonorPoolingCalculator

Splits a single group of wells by donor_ids. This method is used by the ‘split_groups_by_unique_donor_ids’ method. It iteratively segregates wells with the first encountered instance of each unique donor_id into a separate subgroup. This process continues until there are no wells left in the original group. The result is a collection of subgroups, each containing wells from distinct donors.

If the input group is [w1, w2, w3, w4, w5, w6, w7, w8, w9] where w1, w2, w3, w4, w5, w6, w7, w8, and w9 are wells with (donor_id),

w1(1) w2(2) w3(3) w4(1) w5(2) w6(4) w7(5) w8(5) w9(5)

the result will be:

[w1, w2, w3, w6, w7], [w4, w5, w8], [w9]

Parameters:

  • group (Array<Well>)

    The group of wells to split.

Returns:

  • (Array<Array<Well>>)

    An array of subgroups, each containing wells from different donors.

#tag_depth_hashHash

Returns a hash mapping each source well to its index in its pool plus one. The tag depth is used as an aliquot attribute in the transfer request. It is recorded in Sequencescape to avoid tag clashes.

Returns:

  • (Hash)

    A hash where keys are wells and values are tag depths.



204
205
206
207
208
209
210
211
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 204

def tag_depth_hash
  @tag_depth_hash ||=
    pools
      .each_with_index
      .with_object({}) do |(pool, _pool_index), hash|
        pool.each_with_index { |well, index| hash[well] = (index + 1).to_s }
      end
end

#transfer_hashHash

Returns a mapping between each source well to a destination location.

Returns:

  • (Hash)

    A hash where each key is a source well and each value is a hash with a single key-value pair: { dest_locn: destination_location }.



189
190
191
192
193
194
195
196
197
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 189

def transfer_hash
  @transfer_hash ||=
    pools
      .each_with_index
      .with_object({}) do |(pool, index), result|
        dest_location = WellHelpers.well_at_column_index(index) # column order, 96 wells
        pool.each { |source_well| result[source_well] = { dest_locn: dest_location } }
      end
end

#transfer_material_from_parent!(dest_uuid) ⇒ Boolean

Creates transfer requests from source wells to the destination plate in Sequencescape.

Parameters:

  • dest_uuid (String)

    The UUID of the destination plate.

Returns:

  • (Boolean)

    Returns true if no exception is raised.



144
145
146
147
148
149
150
151
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 144

def transfer_material_from_parent!(dest_uuid)
  dest_plate = Sequencescape::Api::V2::Plate.find_by(uuid: dest_uuid)
  api.transfer_request_collection.create!(
    user: user_uuid,
    transfer_requests: transfer_request_attributes(dest_plate)
  )
  true
end

#transfer_request_attributes(dest_plate) ⇒ Array<Hash>

Generates the attributes for transfer requests from the source wells to the destination plate.

Parameters:

Returns:

  • (Array<Hash>)

    An array of hashes, each representing the attributes for a transfer request.



159
160
161
162
163
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 159

def transfer_request_attributes(dest_plate)
  well_filter.filtered.filter_map do |source_well, additional_parameters|
    request_hash(source_well, dest_plate, additional_parameters)
  end
end

#unique_donor_ids(group) ⇒ Array<String> Originally defined in module DonorPoolingCalculator

Returns the unique donor_ids from a group of wells. Used by the ‘split_single_group_by_unique_donor_ids’ method.

If the input group is [w1, w2, w3, w4, w5, w6, w7, w8, w9] where w1, w2, w3, w4, w5, w6, w7, w8, and w9 are wells with (donor_id),

w1(1) w2(2) w3(3) w4(1) w5(2) w6(4) w7(5) w8(5) w9(5)

the result will be:

1, 2, 3, 4, 5

Parameters:

  • group (Array<Well>)

    The group of wells from which to retrieve donor_ids.

Returns:

  • (Array<String>)

    An array of unique donor_ids.

#well_filterWellFilter

Returns the WellFilter instance associated with this creator. The filter uses the callback method ‘labware_wells’ to get the list of wells to filter, which specifies wells in ‘passed’ state from the source plates. The ‘source_wells_for_pooling’ method is used to get the filtered wells.

Returns:



76
77
78
# File 'app/models/labware_creators/donor_pooling_plate.rb', line 76

def well_filter
  @well_filter ||= WellFilter.new(creator: self)
end

#wells_with_aliquots_must_have_cell_countvoid Originally defined in module DonorPoolingValidator

This method returns an undefined value.

Validates that wells with aliquots have a latest_live_cell_count. It uses the locations_with_missing_cell_count method to find any wells that are missing a cell count. If any such wells are found, it adds an error message to the source_plates attribute, formatted with the barcodes of the plates and the wells that are missing a cell count. Note that the well filter already excludes failed wells. This validation ensures that all wells with aliquots have a cell count unless they are failed.

#wells_with_aliquots_must_have_donor_idvoid Originally defined in module DonorPoolingValidator

This method returns an undefined value.

Validates that all wells with aliquots must have a donor_id. It uses the locations_with_missing_donor_id method to find any wells that are missing a donor_id. If any such wells are found, it adds an error message to the source_plates attribute, formatted with the barcodes of the plates and the wells that are missing a donor_id.