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-102 tube rack banking bed verification #2156

Open
wants to merge 20 commits into
base: y24-088-tuberacks-epic
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1c09f5c
created tube rack purpose factory and linked to tube rack factory
andrewsparkes Jan 13, 2025
43bf911
added helper stub method for tube rack look up
andrewsparkes Jan 13, 2025
d48098a
changed refs from wrapper to tube rack, and use racked_tubes
andrewsparkes Jan 13, 2025
f818f26
removed unneeded tube rack wrapper
andrewsparkes Jan 13, 2025
7081e89
changes to remove tube rack wrapper and use tube rack directly
andrewsparkes Jan 13, 2025
67fed60
added find_all method for tube rack
andrewsparkes Jan 13, 2025
3344917
added find_by option and includes for tube racks
andrewsparkes Jan 16, 2025
1d234af
renamed variable
andrewsparkes Jan 16, 2025
64bb10b
possibly temporary change to cope with tube racks in relatives links
andrewsparkes Jan 16, 2025
f722394
changes to robot classes to allow bed verification for tuberacks
andrewsparkes Jan 16, 2025
f0c4df1
added redirect to tube rack presenter view for contingency tube rack
andrewsparkes Jan 16, 2025
47beb4c
Merge branch 'develop' into Y24-102-tube-rack-banking-bed-verification
andrewsparkes Jan 16, 2025
10cb9d5
linted
andrewsparkes Jan 16, 2025
5b6d83d
better comments
andrewsparkes Jan 17, 2025
14da677
fix test for redirection
andrewsparkes Jan 17, 2025
6938fba
switch to use uuid for tube rack lookup
andrewsparkes Jan 17, 2025
111233c
Merge branch 'y24-088-tuberacks-epic' into Y24-102-tube-rack-banking-…
andrewsparkes Jan 20, 2025
08dfaaa
refactored parent and child links to tube rack
andrewsparkes Jan 20, 2025
87ffcbb
added labware metadata robot barcode for tube rack, plus test
andrewsparkes Jan 29, 2025
a689f37
fixed comments
andrewsparkes Jan 29, 2025
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
8 changes: 3 additions & 5 deletions app/models/labware_creators/plate_split_to_tube_racks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,12 @@ def perform_transfers
)
end

# We will create multiple child tube racks, redirect back to the parent plate
# We redirect to the contingency tube rack that we have just created.
def redirection_target
# NB. if we want to change this to the first tube rack use: child_tube_racks[SEQ_TUBE_RACK_NAME]
parent
child_tube_racks[SPR_TUBE_RACK_NAME]
end

# We will want to see the list of tubes in the rack
# TODO: as these are racked_tubes and not child tubes, does the tube rack presenter have a relatives tab?
# Display the relatives tab on the child tube rack page.
def anchor
'relatives_tab'
end
Expand Down
4 changes: 2 additions & 2 deletions app/models/robots/bed/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ def formatted_message
private

def correct_labware_purpose
return true if Array(purpose).include?(labware.purpose_name)
return true if Array(purpose).include?(labware.purpose.name)

error("Labware #{labware.human_barcode} is a #{labware.purpose_name} not a #{purpose_labels} labware.")
error("Labware #{labware.human_barcode} is a #{labware.purpose.name} not a #{purpose_labels} labware.")
end

def correct_labware_state
Expand Down
21 changes: 15 additions & 6 deletions app/models/robots/bed/plate_to_tube_racks_bed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,37 @@ module Robots::Bed
# This bed hosts the parent plate or a tube-rack. It uses the robot to find
# its labware and child labware. When it is used as a source bed, it should
# host the Plate. When it is used as a destination bed, it should host a
# tube-rack wrapper.
# Tube Rack.
#
class PlateToTubeRacksBed < Robots::Bed::Base
# Updates the metadata of the labware with the robot barcode.
# This method is called inside the robot controller's start action for
# tube-rack wrappers and it sets the created_with_robot metadata field.
# Tube Racks and it sets the created_with_robot metadata field.
#
# @param robot_barcode [String] the robot barcode
# @return [void]
#
def labware_created_with_robot(robot_barcode)
yoldas marked this conversation as resolved.
Show resolved Hide resolved
# RobotController uses machine barcode for initialising LabwareMetadata
labware.tubes.each do |tube|
LabwareMetadata.new(user_uuid: user_uuid, barcode: tube.barcode.machine).update!(
# First write the robot barcode to the tube rack
# This will be used if we verify barcode on the next bed verification
LabwareMetadata.new(user_uuid: user_uuid, barcode: labware.barcode.machine).update!(
created_with_robot: robot_barcode
)

# Next write the robot barcode to the racked tubes in the tube rack
# This is just so the tube (which can be used independently of the rack) also has a record
# of the robot that created it
labware.racked_tubes.each do |racked_tube|
LabwareMetadata.new(user_uuid: user_uuid, barcode: racked_tube.tube.barcode.machine).update!(
created_with_robot: robot_barcode
)
end
end

# Returns an array of labware from the robot's labware store for barcodes.
#
# @return [Array<TubeRackWrapper>] child tube-rack wrappers
# @return [Array<TubeRack>] child tube racks
#
def child_labware
return [] if labware.blank?
Expand All @@ -51,7 +60,7 @@ def load(barcodes)
def transition
return if target_state.nil? || labware.nil? # We have nothing to do

labware.tubes.each { |tube| change_tube_state(tube) }
labware.racked_tubes.each { |racked_tube| change_tube_state(racked_tube.tube) }
end

# Changes the state of one tube to the target state. This method is called
Expand Down
129 changes: 54 additions & 75 deletions app/models/robots/plate_to_tube_racks_robot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,23 @@

module Robots
# This plate to tube racks robot takes one parent plate, and transfers it to
# its child tubes that are on multiple tube racks. The tube racks handled by
# this robot are not actual recorded labware. Their barcodes are extracted
# from the metadata of the tubes and they are accessed using wrapper objects.
# its child tubes that are on multiple tube racks.
# When robot controller calls the robot's verify or perform_transfer actions,
# the robot will first initialize its labware store with the plate and tube
# rack objects. The plate information comes from the Sequencescape API call,
# however the tube rack information comes from the metadata of the downstream
# tubes included in the same API response. Therefore, the bed verification of
# the tube racks depend on the verification of the plate.
#
# The destination tube racks are distinguished by their barcodes. We assume
# that the tubes on the same tube rack have the same labware purpose. When
# multiple tubes of the same purpose and the same position are found, the
# latest tube is assumed to be on the tube rack and the other tubes are
# ignored. We also assume that there cannot be multiple tube racks with the
# tubes of the same labware purpose on the robot at the same time.
# rack objects. This information comes from the Sequencescape API call on the
# plate, where the tube racks are children of the plate.
# Therefore, the bed verification of the tube racks depend on the verification
# of the plate.
#
# For bed verification, only the etched barcode of the tube racks are scanned,
# not the individual tubes. The number of tube racks to be verified not only
# depends on the robot's configured relationships but also whether the plate
# has children with those purposes.
# has children with those purposes. e.g. in scRNA one of the tube racks is optional.
#
class PlateToTubeRacksRobot < Robots::SplittingRobot
attr_writer :relationships # Hash from robot config into @relationships

# Option for including downstream tubes and metadata in Plate API response.
PLATE_INCLUDES = 'purpose,wells,wells.downstream_tubes,wells.downstream_tubes.custom_metadatum_collection'

# Returns the well order for getting wells from the plate.
#
# @return [Symbol] the well order
#
def well_order
:coordinate
end
PLATE_INCLUDES = 'purpose'

# Returns the bed class for this robot.
#
Expand Down Expand Up @@ -70,24 +52,25 @@ def verify(params)

# Returns an array of labware from the robot's labware store for barcodes.
# This method is called by the robot's beds when they need to find their
# labware. The labware returned can be Plate objects or labware-like
# wrapper objects for tube racks.
# labware. The labware returned can be Plate objects or Tube Rack objects.
#
# @param barcodes [Array<String>] array of barcodes
# @return [Array<Plate, TubeRackWrapper>]
# @return [Array<Plate, TubeRack>]
#
def find_bed_labware(barcodes)
barcodes.filter_map { |barcode| labware_store[barcode] }
end

# Returns an array of child labware from the robot's labware store for
# Returns an array of child tube racks from the robot's labware store for
# the given Plate.
#
# @param plate [Plate] the parent plate
# @return [Array<TubeRackWrapper>] array of tube rack wrapper objects
# @return [Array<TubeRack>] array of tube racks
#
def child_labware(plate)
labware_store.values.select { |labware| labware.respond_to?(:parent) && labware.parent.uuid == plate.uuid }
labware_store.values.select do |labware|
labware.respond_to?(:parents) && labware.parents&.first&.uuid == plate.uuid
end
end

private
Expand All @@ -103,16 +86,25 @@ def prepare_robot(bed_labwares)

# Prepares the labware store before handling robot actions. This method is
# called before the robot's bed verification and perform transfer actions.
# NB. This says what tube racks should be scanned, given the parent plate barcode scanned.
# i.e. the plate barcode is scanned, and the expected tube rack children are determined.
#
# @param bed_labwares [Hash] the hash from request parameters
# @return [void]
#
def prepare_labware_store(bed_labwares)
return if labware_store.present?

stripped_barcodes(bed_labwares).each do |barcode|
plate = find_plate(barcode)

# skip non plates
next if plate.blank?

# add the parent plate to the labware store
add_plate_to_labware_store(plate)

# determine the expected tube racks for this parent plate and add to the labware store
add_tube_racks_to_labware_store(plate)
end
end
Expand All @@ -134,16 +126,12 @@ def prepare_labware_store(bed_labwares)
# that were already recorded by the prepare_labware_store method. We
# override the bed configuration based on availability of labware here.
#
# NB. The child labware are tube-rack wrapper objects, not actual labware.
# The information about tube-racks are found using the metadata of the
# downstream tubes, included in the Sequencescape API response.
#
# @ return [void]
#
def prepare_beds
@relationships.each do |relationship|
relationship_children = relationship.dig('options', 'children')
labware_store_purposes = labware_store.values.map(&:purpose_name)
labware_store_purposes = labware_store.values.map { |labware| labware.purpose.name }

bed_barcodes_to_remove =
relationship_children.select { |barcode| labware_store_purposes.exclude?(beds[barcode].purpose) }
Expand All @@ -154,10 +142,10 @@ def prepare_beds

# Deletes the beds and their relationships from the robot's configuration.
# This method is called by the prepare_beds method after finding which
# beds should not be verified. For the scRNA Core pipeline, this means either we need to
# verify the parent bed first as it has a problem, or we have to remove
# the sequencing tube-rack from the robot's config as the parent has only
# contingency-only tube rack to be verified.
# beds should not be verified. For the scRNA Core pipeline, there can be
# a contingency rack only, or both contingency and sequencing tube racks.
# If there is not sequencing rack required for this parent, we delete it from
# the robot's configuration.
#
# @param barcodes [Array<String>] array of barcodes to be removed
# @param relationship_children [Array<String>] array of child barcodes
Expand Down Expand Up @@ -187,18 +175,35 @@ def add_plate_to_labware_store(plate)
labware_store[plate.barcode.human] = plate
end

# Adds the tube racks wrappers from plate includes to the labware store.
# Adds the tube racks children of the plate to the labware store.
#
# @param plate [Plate] the parent plate
# @return [void]
#
# rubocop:disable Metrics/AbcSize
def add_tube_racks_to_labware_store(plate)
find_tube_racks(plate).each { |rack| labware_store[rack.barcode.human] = rack }
plate.children.each do |asset|
# NB. children of plate are currently Assets, whereas we need TubeRack objects

# skip when child is anything other than a tube rack
next unless asset.type == 'tube_racks'

# fetch tube rack from API
tube_rack = find_tube_rack(asset.uuid)

# cycle beds, if tube rack matches purpose and state from config, add it
beds.each_value do |bed|
if bed.purpose == tube_rack.purpose.name && bed.states.include?(tube_rack.state)
labware_store[tube_rack.barcode.human] = tube_rack
end
end
end
end

# rubocop:enable Metrics/AbcSize

# Returns the labware store. The hash is indexed by the labware barcode.
# The values are either Plate objects or labware-like wrapper objects for
# tube racks.
# The values are either Plate objects or Tube Rack objects.
#
# @return [Hash<String, Labware>] the labware store
#
Expand All @@ -216,37 +221,11 @@ def find_plate(barcode)
Sequencescape::Api::V2::Plate.find_all({ barcode: [barcode] }, includes: PLATE_INCLUDES).first
end

# Returns an array of tube rack wrapper objects that from the downstream tubes
# of the given plate.
#
# @param plate [Plate] the parent plate
# @return [Array<TubeRackWrapper>] array of tube rack wrapper objects
#
def find_tube_racks(plate)
plate
.wells
.sort_by(&well_order)
.each_with_object([]) do |well, racks|
next if well.downstream_tubes.blank?
well.downstream_tubes.each do |tube|
barcode = tube.custom_metadatum_collection.metadata[:tube_rack_barcode]
find_or_create_tube_rack_wrapper(racks, barcode, plate).push_tube(tube)
end
end
end

# Returns an existing or new tube rack wrapper object.
#
# @param racks [Array<TubeRackWrapper>] the tube racks found so far
# @param barcode[String] the barcode of the tube rack
# @param plate [Plate] the parent plate
# @return [TubeRackWrapper] the tube rack wrapper object
#
def find_or_create_tube_rack_wrapper(racks, barcode, plate)
rack = racks.detect { |tube_rack| tube_rack.barcode.human == barcode }
return rack if rack.present?
labware_barcode = LabwareBarcode.new(human: barcode, machine: barcode)
racks.push(TubeRackWrapper.new(labware_barcode, plate)).last
def find_tube_rack(uuid)
Sequencescape::Api::V2::TubeRack.find_all(
{ uuid: [uuid] },
includes: Sequencescape::Api::V2::TubeRack::DEFAULT_TUBE_RACK_INCLUDES
).first
end
end
end
72 changes: 0 additions & 72 deletions app/models/robots/tube_rack_wrapper.rb

This file was deleted.

Loading
Loading