From a2af4207470c25621e5d3fcf2f20b081de35d2ab Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Mon, 20 Nov 2023 11:04:26 +0000 Subject: [PATCH 1/9] added input started purpose type to get plate into an initial started state --- app/models/plate_purpose/input_started.rb | 33 +++++++++++++++++++ .../plate_purposes/009_bioscan_purposes.yml | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 app/models/plate_purpose/input_started.rb diff --git a/app/models/plate_purpose/input_started.rb b/app/models/plate_purpose/input_started.rb new file mode 100644 index 0000000000..707002bdb2 --- /dev/null +++ b/app/models/plate_purpose/input_started.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Input Plate purposes are the initial stock plates passing into +# external piplines. They have special behaviour governing their state. +# This essentially makes sure that all non-empty wells on a plate have requests +# out of them. This is intended to ensure that submissions have been +# correctly built before a plate has processed. +# +# - Input plates are progressed when all sample containing wells have requests out of them +# +# This version of the input class sets the state as started rather than passed. +class PlatePurpose::InputStarted < PlatePurpose::Input + READY_STATE = 'started' + + private + + # TODO: for some reason this private method needs to be here despite being a copy of + # the parent class method, otherwise the READY_STATE constant above isn't used over the + # one in the parent class. + def calculate_state_of_plate(wells_states) + unique_states = wells_states.uniq + return UNREADY_STATE if unique_states.include?(:unready) + + case unique_states.sort + when ['failed'], %w[cancelled failed] + 'failed' + when ['cancelled'] + 'cancelled' + else + READY_STATE + end + end +end diff --git a/config/default_records/plate_purposes/009_bioscan_purposes.yml b/config/default_records/plate_purposes/009_bioscan_purposes.yml index bdf1e20cc8..33c6725b3b 100644 --- a/config/default_records/plate_purposes/009_bioscan_purposes.yml +++ b/config/default_records/plate_purposes/009_bioscan_purposes.yml @@ -9,7 +9,7 @@ # This is the insects in ethanol plate for the start of the Lysate pipeline # It is the original source plate that has come in from the suppliers around the UK. LILYS-96 Stock: - type: PlatePurpose::Input + type: PlatePurpose::InputStarted stock_plate: true cherrypickable_target: false # This is the lysed material plate, ready for the Library prep pipeline. These plates From d0fb062cc770231c2bd2438918a776ffdb1482da Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Mon, 27 Nov 2023 11:52:40 +0000 Subject: [PATCH 2/9] Added input started state changer and inked to input started purpose --- app/models/plate_purpose/input_started.rb | 12 ++++++-- .../state_changer/input_started_plate.rb | 29 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 app/models/state_changer/input_started_plate.rb diff --git a/app/models/plate_purpose/input_started.rb b/app/models/plate_purpose/input_started.rb index 707002bdb2..780076d307 100644 --- a/app/models/plate_purpose/input_started.rb +++ b/app/models/plate_purpose/input_started.rb @@ -10,7 +10,11 @@ # # This version of the input class sets the state as started rather than passed. class PlatePurpose::InputStarted < PlatePurpose::Input - READY_STATE = 'started' + self.state_changer = StateChanger::InputStartedPlate + + UNREADY_STATE = 'pending' + PREP_STATE = 'started' + READY_STATE = 'passed' private @@ -27,7 +31,11 @@ def calculate_state_of_plate(wells_states) when ['cancelled'] 'cancelled' else - READY_STATE + if unique_states.all?('pending') + PREP_STATE + else + READY_STATE + end end end end diff --git a/app/models/state_changer/input_started_plate.rb b/app/models/state_changer/input_started_plate.rb new file mode 100644 index 0000000000..a4c6a13ae7 --- /dev/null +++ b/app/models/state_changer/input_started_plate.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module StateChanger + # This adds an additional started state to input plates, which is used + # by the input_started plate purpose. + class InputStartedPlate < InputPlate + # Target state of labware to state of associated requests. + # All other transitions will be ignored. + self.map_target_state_to_associated_request_state = { + 'failed' => 'failed', + 'passed' => 'started' + } + + private + + def associated_requests + receptacles.flat_map(&:requests_as_source) + end + + def _receptacles + labware.wells.includes(:requests_as_source) + end + + def transfer_requests + # We don't want to update any transfer requests + [] + end + end +end From 5237719b0cf23ce8478f377e2a053b01528a3a08 Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Mon, 27 Nov 2023 15:36:36 +0000 Subject: [PATCH 3/9] updated comment --- app/models/plate_purpose/input_started.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/plate_purpose/input_started.rb b/app/models/plate_purpose/input_started.rb index 780076d307..9330178dea 100644 --- a/app/models/plate_purpose/input_started.rb +++ b/app/models/plate_purpose/input_started.rb @@ -18,9 +18,8 @@ class PlatePurpose::InputStarted < PlatePurpose::Input private - # TODO: for some reason this private method needs to be here despite being a copy of - # the parent class method, otherwise the READY_STATE constant above isn't used over the - # one in the parent class. + # The state of the plate is determined by the state of the wells + # In this version we add an extra state PREP_STATE def calculate_state_of_plate(wells_states) unique_states = wells_states.uniq return UNREADY_STATE if unique_states.include?(:unready) From a2a0cc98feecde42c971b60ba158848a443410c1 Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Thu, 30 Nov 2023 16:29:14 +0000 Subject: [PATCH 4/9] added receptacle state to return well state for input started plate purpose --- app/models/receptacle.rb | 2 +- app/models/transfer/state.rb | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/models/receptacle.rb b/app/models/receptacle.rb index 4a6848ab46..44429bef48 100644 --- a/app/models/receptacle.rb +++ b/app/models/receptacle.rb @@ -7,7 +7,7 @@ class Receptacle < Asset # rubocop:todo Metrics/ClassLength include Uuid::Uuidable include Commentable - include Transfer::State + include Transfer::State::ReceptacleState include Aliquot::Remover include StudyReport::AssetDetails include Receptacle::DownstreamAliquotsRemoval::Mixin diff --git a/app/models/transfer/state.rb b/app/models/transfer/state.rb index bd35775706..df015ea767 100644 --- a/app/models/transfer/state.rb +++ b/app/models/transfer/state.rb @@ -30,6 +30,30 @@ def state_from(state_requests) ALL_STATES.detect { |s| unique_states.include?(s) } || default_state || 'unknown' end + module ReceptacleState + # We have to include this specifically because it does not implicitly include the + # methods from the Transfer::State module. + include Transfer::State + + def default_state + # Well state was 'unknown' without this change for our input plate because input + # plates do not have any transfer requests. + # Users wish to be able to fail wells at this point in the pipeline, and we need wells + # to be in state 'passed' for well failing to be allowed. + return 'passed' if input_started_plate_with_aliquots? + + nil + end + + private + + def input_started_plate_with_aliquots? + self.labware.purpose.type == 'PlatePurpose::InputStarted' && + %w[started passed].include?(self.labware.state) && + self.aliquots.present? + end + end + # Plate specific behaviour module PlateState def self.included(base) # rubocop:todo Metrics/MethodLength From a0d5f4ec189869cf37a9ad11c78065c2e87d9c01 Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Thu, 30 Nov 2023 17:02:30 +0000 Subject: [PATCH 5/9] linted --- app/models/plate_purpose/input_started.rb | 6 +----- app/models/state_changer/input_started_plate.rb | 5 +---- app/models/transfer/state.rb | 5 ++--- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/app/models/plate_purpose/input_started.rb b/app/models/plate_purpose/input_started.rb index 9330178dea..9b00386d98 100644 --- a/app/models/plate_purpose/input_started.rb +++ b/app/models/plate_purpose/input_started.rb @@ -30,11 +30,7 @@ def calculate_state_of_plate(wells_states) when ['cancelled'] 'cancelled' else - if unique_states.all?('pending') - PREP_STATE - else - READY_STATE - end + unique_states.all?('pending') ? PREP_STATE : READY_STATE end end end diff --git a/app/models/state_changer/input_started_plate.rb b/app/models/state_changer/input_started_plate.rb index a4c6a13ae7..1e15c8ef2b 100644 --- a/app/models/state_changer/input_started_plate.rb +++ b/app/models/state_changer/input_started_plate.rb @@ -6,10 +6,7 @@ module StateChanger class InputStartedPlate < InputPlate # Target state of labware to state of associated requests. # All other transitions will be ignored. - self.map_target_state_to_associated_request_state = { - 'failed' => 'failed', - 'passed' => 'started' - } + self.map_target_state_to_associated_request_state = { 'failed' => 'failed', 'passed' => 'started' } private diff --git a/app/models/transfer/state.rb b/app/models/transfer/state.rb index df015ea767..93c95aed18 100644 --- a/app/models/transfer/state.rb +++ b/app/models/transfer/state.rb @@ -48,9 +48,8 @@ def default_state private def input_started_plate_with_aliquots? - self.labware.purpose.type == 'PlatePurpose::InputStarted' && - %w[started passed].include?(self.labware.state) && - self.aliquots.present? + labware.purpose.type == 'PlatePurpose::InputStarted' && %w[started passed].include?(labware.state) && + aliquots.present? end end From b7dfd0cb8c7c9f56238b471a9973f3cbbe422487 Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Fri, 1 Dec 2023 10:36:14 +0000 Subject: [PATCH 6/9] added check for labware to fix failing tests --- app/models/transfer/state.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/models/transfer/state.rb b/app/models/transfer/state.rb index 93c95aed18..eef54d0e9e 100644 --- a/app/models/transfer/state.rb +++ b/app/models/transfer/state.rb @@ -38,8 +38,9 @@ module ReceptacleState def default_state # Well state was 'unknown' without this change for our input plate because input # plates do not have any transfer requests. - # Users wish to be able to fail wells at this point in the pipeline, and we need wells - # to be in state 'passed' for well failing to be allowed. + # This change was made specifically for the Bioscan pipeline, where the users wish to be able to fail + # wells at this point in the pipeline, and we need wells to be in state 'passed' for well failing to + # be allowed. return 'passed' if input_started_plate_with_aliquots? nil @@ -48,8 +49,13 @@ def default_state private def input_started_plate_with_aliquots? - labware.purpose.type == 'PlatePurpose::InputStarted' && %w[started passed].include?(labware.state) && - aliquots.present? + # Only set this default for receptacles with aliquots, in a labware of purpose type InputStarted. + # in specific states only. + # Had to add a labware.present? check here as many tests seem to fail otherwise, probably + # due to factory test data setup not having a labware (e.g. Well with no Plate) which would not + # happen in reality. + labware.present? && labware.purpose.type == 'PlatePurpose::InputStarted' && + %w[started passed].include?(labware.state) && aliquots.present? end end From 8b7e4e0d13703ab4ee1cf02767825a0a3cf04cca Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Fri, 1 Dec 2023 10:57:19 +0000 Subject: [PATCH 7/9] added check for labware purpose --- app/models/transfer/state.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/transfer/state.rb b/app/models/transfer/state.rb index eef54d0e9e..7517982332 100644 --- a/app/models/transfer/state.rb +++ b/app/models/transfer/state.rb @@ -51,10 +51,10 @@ def default_state def input_started_plate_with_aliquots? # Only set this default for receptacles with aliquots, in a labware of purpose type InputStarted. # in specific states only. - # Had to add a labware.present? check here as many tests seem to fail otherwise, probably - # due to factory test data setup not having a labware (e.g. Well with no Plate) which would not + # Had to add labware.present? and purpose.present? checks here as many tests seem to fail otherwise, probably + # due to incomplete factory test data setup (e.g. Well with no Plate, Tube with no purpose) which should not # happen in reality. - labware.present? && labware.purpose.type == 'PlatePurpose::InputStarted' && + labware.present? && labware.purpose.present? && labware.purpose.type == 'PlatePurpose::InputStarted' && %w[started passed].include?(labware.state) && aliquots.present? end end From e8c2feb8a2c6729da00e6af9b8267d8668715031 Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Fri, 1 Dec 2023 11:28:44 +0000 Subject: [PATCH 8/9] added check for labware purpose and refactored --- app/models/transfer/state.rb | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/models/transfer/state.rb b/app/models/transfer/state.rb index 7517982332..ecc950b4bd 100644 --- a/app/models/transfer/state.rb +++ b/app/models/transfer/state.rb @@ -38,7 +38,7 @@ module ReceptacleState def default_state # Well state was 'unknown' without this change for our input plate because input # plates do not have any transfer requests. - # This change was made specifically for the Bioscan pipeline, where the users wish to be able to fail + # This check was added specifically for the Bioscan pipeline, where the users wish to be able to fail # wells at this point in the pipeline, and we need wells to be in state 'passed' for well failing to # be allowed. return 'passed' if input_started_plate_with_aliquots? @@ -49,13 +49,20 @@ def default_state private def input_started_plate_with_aliquots? - # Only set this default for receptacles with aliquots, in a labware of purpose type InputStarted. - # in specific states only. - # Had to add labware.present? and purpose.present? checks here as many tests seem to fail otherwise, probably - # due to incomplete factory test data setup (e.g. Well with no Plate, Tube with no purpose) which should not - # happen in reality. - labware.present? && labware.purpose.present? && labware.purpose.type == 'PlatePurpose::InputStarted' && - %w[started passed].include?(labware.state) && aliquots.present? + # Had to add labware and purpose checks here as many tests seem to fail otherwise, probably + # due to incomplete factory test data setup (e.g. Well with no Plate, Tube with no purpose) + # which should not happen in reality. + return false unless labware&.purpose + + labware_of_input_started_type? && labware_in_valid_state? && aliquots.present? + end + + def labware_of_input_started_type? + labware.purpose.type == 'PlatePurpose::InputStarted' + end + + def labware_in_valid_state? + %w[started passed].include?(labware.state) end end From 8d249b83c28da57e1fc1b46facb23f0606ea1122 Mon Sep 17 00:00:00 2001 From: Andrew Sparkes Date: Fri, 1 Dec 2023 12:12:58 +0000 Subject: [PATCH 9/9] added comment for module --- app/models/transfer/state.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/transfer/state.rb b/app/models/transfer/state.rb index ecc950b4bd..2039490f31 100644 --- a/app/models/transfer/state.rb +++ b/app/models/transfer/state.rb @@ -30,6 +30,9 @@ def state_from(state_requests) ALL_STATES.detect { |s| unique_states.include?(s) } || default_state || 'unknown' end + # This transfer state is specific to receptacles + # Added so can specifically check the state on certain types of input plates + # and return a default state of passed to allow well failing. module ReceptacleState # We have to include this specifically because it does not implicitly include the # methods from the Transfer::State module.