-
Notifications
You must be signed in to change notification settings - Fork 8
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
DPL-914 scRNA create tube rack (plate) from banked pmbc tubes #1586
Merged
andrewsparkes
merged 45 commits into
develop
from
dpl-914-scrna-create-tube-rack-banked-pmbc-tubes
Mar 22, 2024
Merged
Changes from 29 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
ddca014
added automatic state changer for tubes
andrewsparkes fec6cdd
added csv file and row for tube rack scan
andrewsparkes 3eee14f
added purpose config factory
andrewsparkes 4f86957
added custom page to upload tube rack scan
andrewsparkes f2cac66
added config changes for purposes
andrewsparkes 22084d2
refactor to make easier to read
andrewsparkes 9f62936
refactor to make easier to read 2
andrewsparkes 11579a0
added delegates
andrewsparkes 64d3dff
added helper methods for tubes
andrewsparkes 94af82f
added test csv files
andrewsparkes da72890
added relationship to request_type on v2 request
andrewsparkes 42aaed0
added requests_as_source to factory
andrewsparkes 95c9cda
refactor to match plate version
andrewsparkes bfbabb5
initial version of labware creator and partial test - wip
andrewsparkes 5657947
doc updates
andrewsparkes 1979d99
added cached relationships into factories
andrewsparkes 4677de4
spec test files for multi stamp tubes tests
andrewsparkes bf50c49
added tests for the various validations
andrewsparkes 3f17011
updated docs
andrewsparkes e822cae
merged in develop
andrewsparkes bad5a3b
fix tests
andrewsparkes 4aa152b
removed unneeded relationship
andrewsparkes 215bc5d
modified includes and has requests to fetch in progress requests
andrewsparkes 9f9d755
update docs
andrewsparkes 28cf1cb
modified test to use ancestor requests
andrewsparkes 2bf79b0
fix codepilot change
andrewsparkes 583370c
added class type plate
andrewsparkes 075cf29
Merge branch 'develop' into dpl-914-scrna-create-tube-rack-banked-pmb…
andrewsparkes fa569e7
moved v2 aliquots into v2 receptacle
andrewsparkes 82d4a80
WIP: file upload not working
andrewsparkes e69c18b
added missing requires and uploader
andrewsparkes d357dd4
Fix file uploading in test by adding API V1 actions for qc_files to c…
yoldas 174f424
added test for runtime error check
andrewsparkes 3c12d57
Update app/views/plate_creation/multi_stamp_tubes_using_tube_rack_sca…
andrewsparkes a40de67
refactored to reduce code duplication for tube rack scan files
andrewsparkes 01456df
Merge branch 'dpl-914-scrna-create-tube-rack-banked-pmbc-tubes' of gi…
andrewsparkes d592564
removed unneeded method
andrewsparkes 9a2c6d7
fixed comment
andrewsparkes 3d20dd7
refactor of csv file upload handling
andrewsparkes a3f9b75
fix initialisations and file paths
andrewsparkes 19a7545
Update app/models/labware_creators/common_file_handling/csv_file/row_…
andrewsparkes d383ea8
changes after feedback
andrewsparkes 1f3329c
merged develop in
andrewsparkes 2114559
Change tube.aliquots to tube.receptacle.aliquots in includes
yoldas 47cad6a
Merge branch 'develop' into dpl-914-scrna-create-tube-rack-banked-pmb…
andrewsparkes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
256 changes: 256 additions & 0 deletions
256
app/models/labware_creators/multi_stamp_tubes_using_tube_rack_scan.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
# frozen_string_literal: true | ||
|
||
require_dependency 'form' | ||
require_dependency 'labware_creators/base' | ||
|
||
module LabwareCreators | ||
# Handles the creation of a plate from a number of tubes using an uploaded tube rack scan file. | ||
# | ||
# The parents are standard tubes. | ||
# | ||
# The user uploads a tube rack scan of the tube barcodes and their positions, and this creator will | ||
# transfer the tubes into wells on the plate. | ||
# | ||
# Inputs: | ||
# 1) A parent tube - the user has click the add plate button on a specific tube | ||
# 2) A tube rack scan CSV file - this is a scan of the rack of 2D tube barcodes (the rack is not being tracked) | ||
# | ||
# Outputs: | ||
# 1) A child plate - tubes are stamped into corresponding locations in the plate according to the scan file | ||
# | ||
# Validations - Error message to users if any of these are not met: | ||
# 1) The user must always upload a scan file. Validations of the file must pass and it should parse correctly and | ||
# contain at least one tube barcode. | ||
# 2) The tube barcodes must be unique within the file (exclude NO SCAN, NO READ types). | ||
# 3) The scanned child tube barcode(s) must already exist in the system. List any that do already exist with tube | ||
# barcode and scan file location. | ||
# 4) The labware purpose type of each tube must match the one of the expected ones from a list in the plate purpose | ||
# config. List any that do not match with tube barcode, scan file location and purpose type, and list the expected | ||
# type(s). | ||
# 5) The request on the tube must be active, and must match to one of the expected ones from a list in the plate | ||
# purpose config. This is to check that the tubes are at the appropriate stage of their pipeline to transfer. | ||
# | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice docs, thank you. |
||
# rubocop:disable Metrics/ClassLength | ||
class MultiStampTubesUsingTubeRackScan < Base | ||
include LabwareCreators::CustomPage | ||
include SupportParent::TubeOnly | ||
|
||
self.page = 'multi_stamp_tubes_using_tube_rack_scan' | ||
self.attributes += [:file] | ||
|
||
attr_accessor :file | ||
|
||
validates :file, presence: true | ||
|
||
validates_nested :csv_file, if: :file | ||
|
||
# NB. CsvFile checks for duplicated barcodes within the uploaded file (ignores no scan types) | ||
validate :tubes_must_exist_in_lims, if: :file | ||
validate :tubes_must_be_of_expected_purpose_type, if: :file | ||
validate :tubes_must_have_active_requests_of_expected_type, if: :file | ||
|
||
EXPECTED_REQUEST_STATES = %w[pending started].freeze | ||
|
||
def save | ||
# NB. need the && true!! | ||
super && upload_tube_rack_file && true | ||
end | ||
|
||
# Creates child plate, performs transfers. | ||
# | ||
# @return [Boolean] true if the child plate was created successfully. | ||
def create_labware! | ||
plate_creation = | ||
api.pooled_plate_creation.create!(parents: parent_tube_uuids, child_purpose: purpose_uuid, user: user_uuid) | ||
|
||
@child = plate_creation.child | ||
child_v2 = Sequencescape::Api::V2.plate_with_wells(@child.uuid) | ||
|
||
transfer_material_from_parent!(child_v2) | ||
|
||
yield(@child) if block_given? | ||
true | ||
end | ||
|
||
# Creates transfer requests for the given transfer request attributes and performs the transfers. | ||
# | ||
# @return [void] | ||
def perform_transfers | ||
api.transfer_request_collection.create!(user: user_uuid, transfer_requests: transfer_request_attributes) | ||
end | ||
|
||
# Returns a CsvFile object for the tube rack scan CSV file, or nil if the file doesn't exist. | ||
def csv_file | ||
@csv_file ||= CsvFile.new(file) if file | ||
end | ||
|
||
def file_valid? | ||
file.present? && csv_file&.valid? | ||
end | ||
|
||
# Fetches all tubes from the CSV file and stores them in a hash indexed by barcode. | ||
# This method uses memoization to avoid fetching the tubes more than once. | ||
def parent_tubes | ||
@parent_tubes ||= | ||
csv_file | ||
.position_details | ||
.each_with_object({}) do |(_tube_posn, foreign_barcode), tubes| | ||
search_params = { barcode: foreign_barcode, includes: Sequencescape::Api::V2::Tube::DEFAULT_INCLUDES } | ||
|
||
tubes[foreign_barcode] = Sequencescape::Api::V2::Tube.find_by(**search_params) | ||
end | ||
end | ||
|
||
# Validates that all parent tubes in the CSV file exist in the LIMS. | ||
# Adds an error message for each tube that doesn't exist. | ||
def tubes_must_exist_in_lims | ||
return unless file_valid? | ||
|
||
parent_tubes.each do |foreign_barcode, tube_in_db| | ||
next if tube_in_db.present? | ||
|
||
msg = | ||
"Tube barcode #{foreign_barcode} not found in the LIMS. " \ | ||
'Please check the tube barcodes in the scan file are valid tubes.' | ||
errors.add(:base, msg) | ||
end | ||
end | ||
|
||
# Validates that all tubes in the parent_tubes hash are of the expected purpose type. | ||
# If a tube is not of the expected purpose type, an error message is added to the errors object. | ||
# Tubes that are not found in the database or are of the expected purpose type are skipped. | ||
# This method is used to ensure that all tubes are of the correct type before starting the transfer. | ||
def tubes_must_be_of_expected_purpose_type | ||
andrewsparkes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return unless file_valid? | ||
|
||
parent_tubes.each do |foreign_barcode, tube_in_db| | ||
# NB. should be catching missing tubes in previous validation | ||
next if tube_in_db.blank? || expected_tube_purpose_names.include?(tube_in_db.purpose.name) | ||
|
||
msg = | ||
"Tube barcode #{foreign_barcode} does not match to one of the expected tube purposes (one of type(s): #{ | ||
expected_tube_purpose_names.join(', ') | ||
})" | ||
errors.add(:base, msg) | ||
end | ||
end | ||
|
||
# Validates that all tubes in the parent_tubes hash have at least one active request of the expected type. | ||
# If a tube does not have an active request of the expected type, an error message is added to the errors object. | ||
# Tubes that are not found in the database or already have an expected active request are skipped. | ||
# This method is used to ensure that all tubes are ready for processing before starting the transfer. | ||
def tubes_must_have_active_requests_of_expected_type | ||
andrewsparkes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return unless file_valid? | ||
|
||
parent_tubes.each do |foreign_barcode, tube_in_db| | ||
# NB. should be catching missing tubes in previous validation | ||
next if tube_in_db.blank? || tube_has_expected_active_request?(tube_in_db) | ||
|
||
msg = | ||
"Tube barcode #{foreign_barcode} does not have an expected active request (one of type(s): #{ | ||
expected_request_type_keys.join(', ') | ||
})" | ||
errors.add(:base, msg) | ||
end | ||
end | ||
|
||
def expected_request_type_keys | ||
purpose_config.dig(:creator_class, :args, :expected_request_type_keys).to_a | ||
end | ||
|
||
def expected_tube_purpose_names | ||
purpose_config.dig(:creator_class, :args, :expected_tube_purpose_names).to_a | ||
end | ||
|
||
def filename_for_tube_rack_scan | ||
purpose_config.dig(:creator_class, :args, :filename_for_tube_rack_scan) | ||
end | ||
|
||
private | ||
|
||
# Returns an array of unique UUIDs for all parent tubes. | ||
# @return [Array<String>] An array of UUIDs. | ||
def parent_tube_uuids | ||
parent_tubes.values.pluck(:uuid).uniq | ||
end | ||
|
||
# Uploads the tube rack scan CSV file to the parent tube using api v1. | ||
# NB. This is a bit limited as it is only linked to one tube. | ||
def upload_tube_rack_file | ||
# TODO: this is failing with URL error despite values for all elements | ||
# parent.qc_files.create_from_file!(file, filename_for_tube_rack_scan) | ||
true | ||
end | ||
|
||
# Returns an array of active requests of the expected type for the given tube. | ||
# @param tube [Sequencescape::Api::V2::Tube] The tube to get the requests for. | ||
# @return [Array<Sequencescape::Api::V2::Request>] An array of requests. | ||
def active_requests_of_expected_type(tube) | ||
tube.receptacle.requests_as_source.select do |req| | ||
expected_request_type_keys.include?(req.request_type.key) && EXPECTED_REQUEST_STATES.include?(req.state) | ||
end | ||
end | ||
|
||
# Checks if the given tube has any active requests of the expected type. | ||
# @param tube_in_db [Sequencescape::Api::V2::Tube] The tube to check. | ||
# @return [Boolean] True if the tube has any active requests of the expected type, false otherwise. | ||
def tube_has_expected_active_request?(tube_in_db) | ||
active_requests_of_expected_type(tube_in_db).any? | ||
end | ||
|
||
# Transfers material from the parent tubes to the given child plate. | ||
# @param child_plate [Sequencescape::Api::V2::Plate] The plate to transfer material to. | ||
def transfer_material_from_parent!(child_plate) | ||
api.transfer_request_collection.create!( | ||
user: user_uuid, | ||
transfer_requests: transfer_request_attributes(child_plate) | ||
) | ||
end | ||
|
||
# Returns an array of hashes representing the transfer requests for the given child plate. | ||
# Each hash includes the UUIDs of the parent tube and child well, and the UUID of the outer request. | ||
# @param child_plate [Sequencescape::Api::V2::Plate] The plate to get the transfer requests for. | ||
# @return [Array<Hash>] An array of hashes representing the transfer requests. | ||
def transfer_request_attributes(child_plate) | ||
parent_tubes.each_with_object([]) do |(foreign_barcode, parent_tube), tube_transfers| | ||
tube_transfers << | ||
request_hash( | ||
parent_tube.uuid, | ||
child_plate | ||
.wells | ||
.detect { |child_well| child_well.location == csv_file.location_by_barcode_details[foreign_barcode] } | ||
&.uuid, | ||
{ outer_request: source_tube_outer_request_uuid(parent_tube) } | ||
) | ||
end | ||
end | ||
|
||
# Generates a transfer request hash for the given source well UUID, target tube UUID, and additional parameters. | ||
# | ||
# @param source_tube_uuid [String] The UUID of the source tube. | ||
# @param target_plate_uuid [String] The UUID of the target plate. | ||
# @param additional_parameters [Hash] Additional parameters to include in the transfer request hash. | ||
# @return [Hash] A transfer request hash. | ||
def request_hash(source_tube_uuid, target_plate_uuid, additional_parameters) | ||
{ 'source_asset' => source_tube_uuid, 'target_asset' => target_plate_uuid }.merge(additional_parameters) | ||
end | ||
|
||
# Returns the UUID of the first active request of the expected type for the given tube. | ||
# It assumes that there should be exactly one such request. | ||
# If no such request is found, it raises an error. | ||
# This method is used to get the UUID of the outer request when creating a transfer. | ||
# | ||
# @param tube [Sequencescape::Api::V2::Tube] The tube to get the request UUID for. | ||
# @return [String] The UUID of the first active request of the expected type. | ||
# @raise [RuntimeError] If no active request of the expected type is found for the tube. | ||
def source_tube_outer_request_uuid(tube) | ||
requests = active_requests_of_expected_type(tube) | ||
|
||
return requests.first.uuid if requests.first.uuid.present? | ||
|
||
# The validation to check for suitable requests should have caught this | ||
raise "No active request of expected type found for tube #{tube.human_barcode}" | ||
end | ||
end | ||
# rubocop:enable Metrics/ClassLength | ||
end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice and easy to read, thanks for the sensible method order and informative comments.