-
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 43 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
43 changes: 43 additions & 0 deletions
43
app/models/labware_creators/common_file_handling/csv_file/row_base.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,43 @@ | ||
# frozen_string_literal: true | ||
|
||
# Part of the Labware creator classes | ||
module LabwareCreators | ||
module CommonFileHandling | ||
# | ||
# This is an abstract class for handling rows within csv files. | ||
# It provides a simple wrapper for handling and validating an individual row. | ||
# | ||
class CsvFile::RowBase | ||
include ActiveModel::Validations | ||
|
||
def initialize(index, row_data) | ||
@index = index | ||
@row_data = row_data | ||
|
||
initialize_context_specific_fields | ||
end | ||
|
||
# Override in subclass | ||
# Example: | ||
# @my_field_1 = (@row_data[0] || '').strip.upcase | ||
# @my_field_2 = (@row_data[1] || '').strip.upcase | ||
def initialize_context_specific_fields | ||
raise 'Method should be implemented within subclasses' | ||
end | ||
|
||
# Override in subclass | ||
# For use in error messages | ||
# e.g. "row #{index + 2} [#{@my_field_1}]" | ||
def to_s | ||
raise 'Method should be implemented within subclasses' | ||
end | ||
|
||
# Check for whether the row is empty | ||
# Here all? returns true for an empty array, and nil? returns true for nil elements. | ||
# So if @row_data is either empty or all nil, empty? will return true. | ||
def empty? | ||
@row_data.all?(&:nil?) | ||
end | ||
end | ||
end | ||
end |
41 changes: 41 additions & 0 deletions
41
app/models/labware_creators/common_file_handling/csv_file/row_for_tube_rack.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,41 @@ | ||
# frozen_string_literal: true | ||
|
||
# Part of the Labware creator classes | ||
module LabwareCreators | ||
require_dependency 'labware_creators/common_file_handling/csv_file_for_tube_rack' | ||
|
||
module CommonFileHandling | ||
# | ||
# This is an shared class for handling rows within tube rack csv files. | ||
# It provides a simple wrapper for handling and validating an individual row | ||
# A row in this file should contain a tube location (coordinate within rack) | ||
# and a tube barcode | ||
# i.e. Tube Position, Tube Barcode | ||
# | ||
class CsvFile::RowForTubeRack < CsvFile::RowBase | ||
TUBE_LOCATION_NOT_RECOGNISED = 'contains an invalid coordinate, in %s' | ||
TUBE_BARCODE_MISSING = 'cannot be empty, in %s' | ||
|
||
attr_reader :tube_position, :tube_barcode, :index | ||
|
||
validates :tube_position, | ||
inclusion: { | ||
in: WellHelpers.column_order, | ||
message: ->(object, _data) { TUBE_LOCATION_NOT_RECOGNISED % object } | ||
}, | ||
unless: :empty? | ||
validates :tube_barcode, presence: { message: ->(object, _data) { TUBE_BARCODE_MISSING % object } } | ||
|
||
def initialize_context_specific_fields | ||
@tube_position = (@row_data[0] || '').strip.upcase | ||
@tube_barcode = (@row_data[1] || '').strip.upcase | ||
end | ||
|
||
def to_s | ||
# NB. index is zero based and no header row here | ||
row_number = @index + 1 | ||
@tube_position.present? ? "row #{row_number} [#{@tube_position}]" : "row #{row_number}" | ||
end | ||
end | ||
end | ||
end |
29 changes: 29 additions & 0 deletions
29
...els/labware_creators/common_file_handling/csv_file/row_for_tube_rack_with_rack_barcode.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,29 @@ | ||
# frozen_string_literal: true | ||
|
||
# Part of the Labware creator classes | ||
module LabwareCreators | ||
require_dependency 'labware_creators/common_file_handling/csv_file_for_tube_rack_with_rack_barcode' | ||
|
||
module CommonFileHandling | ||
# | ||
# This is a shared class for handling rows within tube rack csv files. | ||
# It provides a simple wrapper for handling and validating an individual row | ||
# A row in this file should contain a tube rack barcode, the tube location (coordinate | ||
# within the rack) and the tube barcode | ||
# i.e. Tube Rack Barcode, Tube Position, Tube Barcode | ||
# | ||
class CsvFile::RowForTubeRackWithRackBarcode < CsvFile::RowForTubeRack | ||
attr_reader :tube_rack_barcode | ||
|
||
TUBE_RACK_BARCODE_MISSING = 'cannot be empty, in %s' | ||
|
||
validates :tube_rack_barcode, presence: { message: ->(object, _data) { TUBE_RACK_BARCODE_MISSING % object } } | ||
|
||
def initialize_context_specific_fields | ||
@tube_rack_barcode = (@row_data[0] || '').strip.upcase | ||
@tube_position = (@row_data[1] || '').strip.upcase | ||
@tube_barcode = (@row_data[2] || '').strip.upcase | ||
end | ||
end | ||
end | ||
end |
66 changes: 66 additions & 0 deletions
66
app/models/labware_creators/common_file_handling/csv_file_base.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,66 @@ | ||
# frozen_string_literal: true | ||
|
||
require './lib/nested_validation' | ||
require 'csv' | ||
|
||
# Part of the Labware creator classes | ||
module LabwareCreators | ||
# | ||
# This is an abstract class for handling csv files. | ||
# | ||
class CommonFileHandling::CsvFileBase | ||
include ActiveModel::Validations | ||
extend NestedValidation | ||
|
||
validate :must_be_correctly_parsed | ||
|
||
def initialize(file) | ||
initialize_variables(file) | ||
rescue StandardError => e | ||
reset_variables | ||
@parse_error = e.message | ||
ensure | ||
file.rewind | ||
end | ||
|
||
# Override in subclass if needed | ||
def initialize_variables(file) | ||
@filename = file.original_filename | ||
@data = CSV.parse(file.read) | ||
remove_bom | ||
@parsed = true | ||
end | ||
|
||
# Override in subclass if needed | ||
def reset_variables | ||
@filename = nil | ||
@data = [] | ||
@parsed = false | ||
end | ||
|
||
private | ||
|
||
# Checks for file parsed correctly | ||
def must_be_correctly_parsed | ||
return if @parsed | ||
|
||
errors.add(:base, "Could not read csv: #{@parse_error}") | ||
end | ||
|
||
# Removes the byte order marker (BOM) from the first string in the @data array, if present. | ||
# | ||
# @return [void] | ||
def remove_bom | ||
return unless @data.present? && @data[0][0].present? | ||
|
||
# byte order marker will appear at beginning of in first string in @data array | ||
s = @data[0][0] | ||
|
||
# NB. had to make byte order marker string mutable here otherwise get frozen string error | ||
bom = +"\xEF\xBB\xBF" | ||
s_mod = s.gsub!(bom.force_encoding(Encoding::BINARY), '') | ||
|
||
@data[0][0] = s_mod unless s_mod.nil? | ||
end | ||
end | ||
end |
131 changes: 131 additions & 0 deletions
131
app/models/labware_creators/common_file_handling/csv_file_for_tube_rack.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,131 @@ | ||
# frozen_string_literal: true | ||
|
||
# Part of the Labware creator classes | ||
module LabwareCreators | ||
# | ||
# This is an abstract class for handling tube rack csv files. | ||
# | ||
# Takes the user uploaded tube rack scan csv file, validates the content and extracts the information. | ||
# | ||
# This version of the rack scan file contains 2 columns, the first is the tube location (coordinate within rack) | ||
# and the second is the tube barcode. | ||
# | ||
# Example of file content (NB. no header line): | ||
# A1,FX05653780 | ||
# A2,NO READ | ||
# etc. | ||
# | ||
class CommonFileHandling::CsvFileForTubeRack < CommonFileHandling::CsvFileBase | ||
validates_nested :tube_rack_scan | ||
validate :check_no_duplicate_rack_positions | ||
validate :check_no_duplicate_tube_barcodes | ||
|
||
NO_TUBE_TEXTS = ['NO READ', 'NOSCAN', ''].freeze | ||
NO_DUPLICATE_RACK_POSITIONS_MSG = 'contains duplicate rack positions (%s)' | ||
NO_DUPLICATE_TUBE_BARCODES_MSG = 'contains duplicate tube barcodes (%s)' | ||
|
||
# | ||
# Extracts tube details by rack location from the uploaded csv file. | ||
# This hash is useful when we want the details for a rack location. | ||
# | ||
# @return [Hash] e.g. { 'A1' => { details for this location }, 'B1' => etc. } | ||
# | ||
def position_details | ||
@position_details ||= generate_position_details_hash | ||
end | ||
|
||
# This hash is useful when we want to know the location of a tube barcode | ||
# within the rack. | ||
# | ||
# @return [Hash] eg. { 'FX00000001' => 'A1', 'FX00000002 => 'B1' etc. } | ||
# | ||
def location_by_barcode_details | ||
@location_by_barcode_details ||= | ||
position_details.each_with_object({}) { |(position, details), hash| hash[details['tube_barcode']] = position } | ||
end | ||
|
||
private | ||
|
||
# Returns an array of Row objects representing the tube rack scan data in the CSV file. | ||
# | ||
# @return [Array<Row>] An array of Row objects. | ||
def tube_rack_scan | ||
@tube_rack_scan ||= | ||
@data[0..].each_with_index.map do |row_data, index| | ||
CommonFileHandling::CsvFile::RowForTubeRack.new(index, row_data) | ||
end | ||
end | ||
|
||
# Checks for duplicate rack positions in the tube rack scan. | ||
# If any duplicates are found, an error message is added to the errors object. | ||
# The error message includes the duplicated rack positions. | ||
# This method is used to ensure that each rack position in the tube rack scan is unique. | ||
def check_no_duplicate_rack_positions | ||
return unless @parsed | ||
|
||
duplicated_rack_positions = | ||
tube_rack_scan.group_by(&:tube_position).select { |_position, tubes| tubes.size > 1 }.keys.join(',') | ||
|
||
return if duplicated_rack_positions.empty? | ||
|
||
errors.add(:base, format(NO_DUPLICATE_RACK_POSITIONS_MSG, duplicated_rack_positions)) | ||
end | ||
|
||
# Checks for duplicate tube barcodes in the tube rack scan. | ||
# If any duplicates are found, they are added to the errors object. | ||
# The error message includes the duplicated tube barcodes. | ||
# 'NO READ' and 'NOSCAN' values are ignored and not considered as duplicates. | ||
# This method is used to ensure that each tube barcode in the tube rack scan is unique. | ||
def check_no_duplicate_tube_barcodes | ||
return unless @parsed | ||
|
||
duplicates = tube_rack_scan.group_by(&:tube_barcode).select { |_tube_barcode, tubes| tubes.size > 1 }.keys | ||
|
||
# remove any NO READ or NOSCAN or empty string values from the duplicates | ||
duplicates = duplicates.reject { |barcode| NO_TUBE_TEXTS.include?(barcode) } | ||
|
||
return if duplicates.empty? | ||
|
||
errors.add(:base, format(NO_DUPLICATE_TUBE_BARCODES_MSG, duplicates.join(','))) | ||
end | ||
|
||
# Generates a hash of position details based on the tube rack scan data in the CSV file. | ||
# | ||
# @return [Hash] A hash of position details, where the keys are positions and the values | ||
# are hashes containing the tube barcode for each position. | ||
def generate_position_details_hash | ||
andrewsparkes marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return {} unless valid? | ||
|
||
tube_rack_scan.each_with_object({}) do |row, position_details_hash| | ||
# ignore blank rows in file | ||
next if row.empty? | ||
|
||
# filter out locations with no tube scanned | ||
next if NO_TUBE_TEXTS.include? row.tube_barcode.strip.upcase | ||
|
||
position = row.tube_position | ||
|
||
# we will use this hash later to create the tubes and store the | ||
# rack barcode in the tube metadata | ||
position_details_hash[position] = format_position_details(row) | ||
end | ||
end | ||
|
||
# Override in subclasses if needed. | ||
# Formats the tube barcode details for a given row. | ||
# This method strips leading and trailing whitespace from the tube barcode and converts it to uppercase. | ||
# @param row [CSV::Row] The row of the CSV file. | ||
# @return [Hash] Hash containing the formatted tube barcode. | ||
def format_position_details(row) | ||
{ 'tube_barcode' => format_barcode(row.tube_barcode) } | ||
end | ||
|
||
# Formats the given barcode. | ||
# This method removes leading and trailing whitespace from the barcode and converts it to uppercase. | ||
# @param barcode [String] The barcode to format. | ||
# @return [String] The formatted barcode. | ||
def format_barcode(barcode) | ||
barcode.strip.upcase | ||
end | ||
end | ||
end |
54 changes: 54 additions & 0 deletions
54
app/models/labware_creators/common_file_handling/csv_file_for_tube_rack_with_rack_barcode.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,54 @@ | ||
# frozen_string_literal: true | ||
|
||
# Part of the Labware creator classes | ||
module LabwareCreators | ||
# | ||
# This is an abstract class for handling tube rack csv files which contain rack barcodes. | ||
# | ||
# Takes the user uploaded tube rack scan csv file, validates the content and extracts the information. | ||
# | ||
# This version of the rack scan file contains 3 columns, the first is the tube rack barcode, the | ||
# second it the tube location (coordinate within rack) and the third is the tube barcode. | ||
# | ||
# Example of file content (NB. no header line): | ||
# TR00012345,A1,FX05653780 | ||
# TR00012345,A2,NO READ | ||
# etc. | ||
# | ||
# | ||
class CommonFileHandling::CsvFileForTubeRackWithRackBarcode < CommonFileHandling::CsvFileForTubeRack | ||
validate :check_for_rack_barcodes_the_same | ||
|
||
RACK_BARCODES_NOT_CONSISTENT_MSG = 'should not contain different rack barcodes (%s)' | ||
|
||
private | ||
|
||
# Returns an array of Row objects representing the tube rack scan data in the CSV file. | ||
# | ||
# @return [Array<Row>] An array of Row objects. | ||
def tube_rack_scan | ||
@tube_rack_scan ||= | ||
@data[0..].each_with_index.map do |row_data, index| | ||
CommonFileHandling::CsvFile::RowForTubeRackWithRackBarcode.new(index, row_data) | ||
end | ||
end | ||
|
||
def check_for_rack_barcodes_the_same | ||
return unless @parsed | ||
|
||
tube_rack_barcodes = tube_rack_scan.group_by(&:tube_rack_barcode).keys | ||
|
||
return unless tube_rack_barcodes.size > 1 | ||
|
||
barcodes_str = tube_rack_barcodes.join(',') | ||
errors.add(:base, format(RACK_BARCODES_NOT_CONSISTENT_MSG, barcodes_str)) | ||
end | ||
|
||
def format_position_details(row) | ||
{ | ||
'tube_rack_barcode' => format_barcode(row.tube_rack_barcode), | ||
'tube_barcode' => format_barcode(row.tube_barcode) | ||
} | ||
end | ||
end | ||
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.
The error messages could flow better, I think, but I guess you are copy pasting from an existing class? What does it end up as, something like:
?
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.
The 'in' part is the to_s method that gives you a row number reference (plus well coord if available)
e.g.
Tube barcode cannot be empty, in row 11 [G5]