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 18 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
18 commits
Select commit Hold shift + click to select a range
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
10 changes: 5 additions & 5 deletions app/models/robots/bed/plate_to_tube_racks_bed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@
# 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)
# RobotController uses machine barcode for initialising LabwareMetadata
labware.tubes.each do |tube|
labware.racked_tubes.tubes.each do |tube|

Check warning on line 19 in app/models/robots/bed/plate_to_tube_racks_bed.rb

View check run for this annotation

Codecov / codecov/patch

app/models/robots/bed/plate_to_tube_racks_bed.rb#L19

Added line #L19 was not covered by tests
LabwareMetadata.new(user_uuid: user_uuid, barcode: tube.barcode.machine).update!(
created_with_robot: robot_barcode
)
Expand All @@ -25,7 +25,7 @@

# 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 +51,7 @@
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
92 changes: 41 additions & 51 deletions app/models/robots/plate_to_tube_racks_robot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,7 @@ module Robots
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 @@ -74,20 +65,22 @@ def verify(params)
# wrapper objects for tube racks.
#
# @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 rack wrapper objects
#
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 +96,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 is what labwares should be scanned, given the 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 +136,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 Down Expand Up @@ -192,10 +190,28 @@ def add_plate_to_labware_store(plate)
# @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.
Expand All @@ -216,37 +232,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.

9 changes: 6 additions & 3 deletions app/sequencescape/sequencescape/api/v2/tube_rack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

self.tube_rack = true

DEFAULT_TUBE_RACK_INCLUDES = [:purpose, 'racked_tubes', 'racked_tubes.tube'].freeze
STATES_TO_FILTER_OUT = %w[cancelled failed].freeze
STATE_EMPTY = 'empty'
STATE_MIXED = 'mixed'
Expand Down Expand Up @@ -54,12 +55,14 @@
nil
end

def self.find_by(params)
options = params.dup
includes = options.delete(:includes) || DEFAULT_INCLUDES
def self.find_by(options, includes: DEFAULT_TUBE_RACK_INCLUDES)
Sequencescape::Api::V2::TubeRack.includes(*includes).find(**options).first
end

def self.find_all(options, includes: DEFAULT_TUBE_RACK_INCLUDES)
Sequencescape::Api::V2::TubeRack.includes(*includes).where(**options).all

Check warning on line 63 in app/sequencescape/sequencescape/api/v2/tube_rack.rb

View check run for this annotation

Codecov / codecov/patch

app/sequencescape/sequencescape/api/v2/tube_rack.rb#L63

Added line #L63 was not covered by tests
end

# This method sorts the racked tubes by their coordinate, taking into account both row and column parts.
# Sorts by column first and then by row.
#
Expand Down
4 changes: 1 addition & 3 deletions app/views/labware/_basic_relative.html.erb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<%# A simple representation of any labware type not currently handled otherwise -%>
<%# locals: (labware:, open_in_new_window: false) -%>
<%
labware_path = limber_plate_path(labware.uuid) + "#relatives"
%>
<% labware_path = limber_plate_path(labware.uuid) + "#relatives" %>
<div class="relative-item">
<%= labware.purpose.name %> - <%= link_to labware_path, class: 'card-link stretched-link', target: (open_in_new_window ? '_blank' : '_self') do %>
<%= labware.barcode.human.present? ? labware.barcode.human : labware.barcode.machine %>
Expand Down
2 changes: 2 additions & 0 deletions app/views/labware/_child_labware_item.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<% else %>
<%= render partial: 'labware/simple_tube', locals: { tube: labware, open_in_new_window: open_in_new_window } %>
<% end %>
<% elsif labware.tube_rack? %>
<%= render partial: 'labware/tube_rack', locals: { tube_rack: labware, open_in_new_window: open_in_new_window } %>
<% else %>
<%= render partial: 'labware/basic_relative', locals: { labware: labware, open_in_new_window: open_in_new_window } %>
<% end %>
2 changes: 2 additions & 0 deletions app/views/labware/_parent_labware_item.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<% open_in_new_window = false %>
<% if labware.tube? %>
<%= render partial: 'labware/simple_tube', locals: { tube: labware, open_in_new_window: open_in_new_window } %>
<% elsif labware.tube_rack? %>
<%= render partial: 'labware/tube_rack', locals: { tube_rack: labware, open_in_new_window: open_in_new_window } %>
<% else %>
<%= render partial: 'labware/basic_relative', locals: { labware: labware, open_in_new_window: open_in_new_window } %>
<% end %>
8 changes: 8 additions & 0 deletions app/views/labware/_tube_rack.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<%# locals: (tube_rack:, open_in_new_window: false) -%>
<% tube_rack_path = limber_tube_rack_path(tube_rack.uuid) + "#relatives" %>
<div class="relative-item">
<%= tube_rack.purpose.name %> - <%= link_to tube_rack_path, class: 'card-link stretched-link', target: (open_in_new_window ? '_blank' : '_self') do %>
<%= tube_rack.barcode.human.present? ? tube_rack.barcode.human : tube_rack.barcode.machine %>
<% end %>
<%= content_tag(:span, state_badge(tube_rack.state), class: 'float-right') %>
</div>
4 changes: 2 additions & 2 deletions config/robots.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3256,8 +3256,8 @@
# LRC PBMC Bank to LRC TR Bank Seq and LRC TR Bank Spare tube racks
# Transfers 1:2 (2nd rack optional)
custom_robot(
'hamilton-lrc-pbmc-bank-to-lrc-bank-seq-and-lrc-bank-spare-tube-racks',
name: 'Hamilton LRC PBMC Bank => LRC TR Bank Seq and LRC Bank Spare Tube Racks',
'hamilton-lrc-pbmc-bank-to-lrc-tr-bank-seq-and-lrc-tr-bank-spare-tube-racks',
name: 'Hamilton LRC PBMC Bank => LRC TR Bank Seq and LRC TR Bank Spare Tube Racks',
beds: {
bed(12).barcode => {
purpose: 'LRC PBMC Bank',
Expand Down
7 changes: 7 additions & 0 deletions spec/factories/plate_purpose_factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
uuid { 'example-purpose-uuid' }
end

# Basic v2 Tube Rack Purpose
factory :v2_tube_rack_purpose, class: Sequencescape::Api::V2::TubeRackPurpose, traits: [:barcoded_v2] do
skip_create
sequence(:name) { |n| "Limber Example TubeRackPurpose #{n}" }
uuid { 'example-tr-purpose-uuid' }
end

# Basic V1 Plate Purpose
factory :plate_purpose, class: Sequencescape::PlatePurpose, traits: [:api_object] do
name { 'Limber Example Purpose' }
Expand Down
Loading
Loading