diff --git a/app/models/bulk_submission.rb b/app/models/bulk_submission.rb
index 170ca24bc5..0f29773072 100644
--- a/app/models/bulk_submission.rb
+++ b/app/models/bulk_submission.rb
@@ -253,7 +253,8 @@ def process # rubocop:todo Metrics/CyclomaticComplexity
'scrna core cells per chip well',
'% phix requested',
'low diversity',
- 'ot recipe'
+ 'ot recipe',
+ 'wafer size'
].freeze
ALIAS_FIELDS = { 'plate barcode' => 'barcode', 'tube barcode' => 'barcode' }.freeze
@@ -360,7 +361,8 @@ def extract_request_options(details)
['scrna core cells per chip well', 'cells_per_chip_well'],
['% phix requested', 'percent_phix_requested'],
['low diversity', 'low_diversity'],
- ['ot recipe', 'ot_recipe']
+ ['ot recipe', 'ot_recipe'],
+ ['wafer size', 'wafer_size']
].each do |source_key, target_key|
assign_value_if_source_present(details, source_key, request_options, target_key)
end
diff --git a/app/models/ultima_sequencing_pipeline.rb b/app/models/ultima_sequencing_pipeline.rb
index 3a28998d3b..639c7ee349 100644
--- a/app/models/ultima_sequencing_pipeline.rb
+++ b/app/models/ultima_sequencing_pipeline.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Specialized sequencing pipeline for Ultima
+# Specialized sequencing pipeline for Ultima UG100
class UltimaSequencingPipeline < SequencingPipeline
def ot_recipe_consistent_for_batch?(batch)
ot_recipe_list = batch.requests.filter_map { |request| request.request_metadata.ot_recipe }
diff --git a/app/models/ultima_ug200_sequencing_pipeline.rb b/app/models/ultima_ug200_sequencing_pipeline.rb
new file mode 100644
index 0000000000..896f4e7696
--- /dev/null
+++ b/app/models/ultima_ug200_sequencing_pipeline.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# Specialized sequencing pipeline for Ultima UG200
+class UltimaUG200SequencingPipeline < UltimaSequencingPipeline
+ def wafer_size_consistent_for_batch?(batch)
+ wafer_size_list = batch.requests.filter_map { |request| request.request_metadata.wafer_size }
+
+ # There are some requests that don't have the wafer_size attribute
+ return false if wafer_size_list.size != batch.requests.size
+
+ (wafer_size_list.uniq.size == 1)
+ end
+end
diff --git a/app/models/ultima_ug200_sequencing_request.rb b/app/models/ultima_ug200_sequencing_request.rb
new file mode 100644
index 0000000000..0587f149dc
--- /dev/null
+++ b/app/models/ultima_ug200_sequencing_request.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+# Request class specific to the Ultima UG200 sequencing platform.
+# Includes wafer size and OT recipe.
+class UltimaUG200SequencingRequest < SequencingRequest
+ include Api::Messages::UseqWaferIo::LaneExtensions
+
+ FREE = 'Free'
+ FLEX = 'Flex'
+ OT_RECIPE_OPTIONS = [FREE, FLEX].freeze
+
+ has_metadata as: Request do
+ # Defining the sequencing request metadata here again, as 'has_metadata'
+ # does not automatically append these custom attributes to the request.
+ #
+ # The has_metadata call dynamically defines an inner Metadata class and
+ # takes the attributes from the block and adds them to the Metadata class.
+ # There is an assumption that the inner Metadata class is available in a
+ # sequencing request class defintion. Calling has_metadata again does not
+ # inherit the attributes given in the block supplied in the superclass.
+ # They need to be supplied again for this class for a proper inner Metadata
+ # class definition. In a future refactoring these attributes can be moved a
+ # class attribute and subclasses can merge its own attibutes to that. A
+ # common method can set up the inner Metadata class in the subclasses.
+ custom_attribute(:fragment_size_required_from, integer: true, minimum: 1)
+ custom_attribute(:fragment_size_required_to, integer: true, minimum: 1)
+
+ # TODO: the defaults set here do NOT work on the option lists in the bulk submission screen,
+ # but do work on the request additional sequencing screen for some reason.
+ custom_attribute(:ot_recipe, default: FREE, in: OT_RECIPE_OPTIONS, required: true)
+ enum :ot_recipe, { Free: 0, Flex: 1 }
+ custom_attribute(:wafer_size, default: '10TB', validator: true, required: true, selection: true)
+ end
+
+ # Delegate to request_metadata so the attributes are visible to the validator in the RSpec tests.
+ # This delegation has no real effect outside of the tests.
+ delegate :wafer_size, :ot_recipe, to: :request_metadata
+
+ # Generates unique wafer ID, concatenation of batch_for_opentrons,
+ # id_pool_lims, and request_order.
+ # @return [String] unique wafer ID for LIMS
+ def id_wafer_lims
+ "#{batch.id}_#{source_labware.human_barcode}_#{position}"
+ end
+end
diff --git a/app/validators/ultima_ug200_validator.rb b/app/validators/ultima_ug200_validator.rb
new file mode 100644
index 0000000000..1dc5c9889e
--- /dev/null
+++ b/app/validators/ultima_ug200_validator.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+class UltimaUG200Validator < UltimaValidator
+ WAFER_SIZE_CONSISTENT_MSG = 'Wafer size must be the same for both requests.'
+ OT_RECIPE_CONSISTENT_MSG = 'OT Recipe must be the same for both requests.'
+
+ # Used in _pipeline_limit.html to display custom validation warnings
+ def self.validation_info
+ 'Wafer Size and OT Recipe must be the same for both requests.'
+ end
+
+ # Validates that a batch contains the two requests.
+ def validate(record)
+ validate_exactly_two_requests(record)
+ requests_have_same_ot_recipe(record)
+ requests_have_same_wafer_size(record)
+ end
+
+ private
+
+ def requests_have_same_wafer_size(record)
+ return if record.pipeline.wafer_size_consistent_for_batch?(record)
+
+ record.errors.add(:base, WAFER_SIZE_CONSISTENT_MSG)
+ end
+
+ def requests_have_same_ot_recipe(record)
+ return if record.pipeline.ot_recipe_consistent_for_batch?(record)
+
+ record.errors.add(:base, OT_RECIPE_CONSISTENT_MSG)
+ end
+end
diff --git a/app/views/bulk_submissions/_guidance.html.erb b/app/views/bulk_submissions/_guidance.html.erb
index 6532b2d383..4706dfc832 100644
--- a/app/views/bulk_submissions/_guidance.html.erb
+++ b/app/views/bulk_submissions/_guidance.html.erb
@@ -47,4 +47,5 @@ used to split a submission up into orders, and as a result must have the same re
% PhiX requestedRequired percentage of PhiX needed.
Low DiversityLow Diversity value being "Yes" or "No"
OT RecipeOT (OpenTron liquid handler) Recipe value being "Free" or "Flex"
+ Wafer SizeWafer size value being "5TB", "10TB" or "20TB"
diff --git a/config/bulk_submission_excel/columns.yml b/config/bulk_submission_excel/columns.yml
index 02de62cb55..8e265391cb 100644
--- a/config/bulk_submission_excel/columns.yml
+++ b/config/bulk_submission_excel/columns.yml
@@ -367,3 +367,22 @@ ot_recipe:
range_name: :ot_recipe
conditional_formattings:
empty_cell:
+wafer_size:
+ heading: "Wafer Size"
+ unlocked: true
+ attribute: :wafer_size
+ validation:
+ options:
+ type: :list
+ formula1: "$A$1:$A$2"
+ allowBlank: false
+ showInputMessage: true
+ showErrorMessage: true
+ errorStyle: :stop
+ errorTitle: "Wafer Size"
+ error: "You must enter a value from the list provided."
+ promptTitle: "Wafer Size"
+ prompt: "Select a value type from the approved list"
+ range_name: :wafer_size
+ conditional_formattings:
+ empty_cell:
diff --git a/config/bulk_submission_excel/ranges.yml b/config/bulk_submission_excel/ranges.yml
index a1c091015a..b211a116cb 100644
--- a/config/bulk_submission_excel/ranges.yml
+++ b/config/bulk_submission_excel/ranges.yml
@@ -27,3 +27,8 @@ ot_recipe:
options:
- "Free"
- "Flex"
+wafer_size:
+ options:
+ - "5TB"
+ - "10TB"
+ - "20TB"
diff --git a/config/default_records/descriptors/005_ultima_ug200_descriptors.yml b/config/default_records/descriptors/005_ultima_ug200_descriptors.yml
new file mode 100644
index 0000000000..d5c77bcb3e
--- /dev/null
+++ b/config/default_records/descriptors/005_ultima_ug200_descriptors.yml
@@ -0,0 +1,85 @@
+---
+"OTR carrier Lot #":
+ name: "OTR carrier Lot #"
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Text
+ required: false
+ sorter: 0
+OTR carrier expiry:
+ name: OTR carrier expiry
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Date
+ required: false
+ sorter: 1
+"Reaction Mix 7 Lot #":
+ name: "Reaction Mix 7 Lot #"
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Text
+ required: false
+ sorter: 2
+Reaction Mix 7 expiry:
+ name: Reaction Mix 7 expiry
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Date
+ required: false
+ sorter: 3
+"NFW Lot #":
+ name: "NFW Lot #"
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Text
+ required: false
+ sorter: 4
+NFW expiry:
+ name: NFW expiry
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Date
+ required: false
+ sorter: 5
+"Oil Lot #":
+ name: "Oil Lot #"
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Text
+ required: false
+ sorter: 6
+Oil expiry:
+ name: Oil expiry
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Date
+ required: false
+ sorter: 7
+Pipette carousel:
+ name: Pipette carousel
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Text
+ required: false
+ sorter: 8
+Opentrons Inst. Name:
+ name: Opentrons Inst. Name
+ task: Opentrons
+ workflow: Ultima UG200
+ kind: Text
+ required: true
+ sorter: 9
+Assign Control Bead Tube:
+ name: Assign Control Bead Tube
+ task: Amp
+ workflow: Ultima UG200
+ kind: Text
+ required: false
+ sorter: 0
+UG AMP Inst. Name:
+ name: UG AMP Inst. Name
+ task: Amp
+ workflow: Ultima UG200
+ kind: Text
+ required: true
+ sorter: 1
diff --git a/config/default_records/pipeline_request_information_types/004_ultima_ug200_pipeline_request_information_types.yml b/config/default_records/pipeline_request_information_types/004_ultima_ug200_pipeline_request_information_types.yml
new file mode 100644
index 0000000000..e39f735c93
--- /dev/null
+++ b/config/default_records/pipeline_request_information_types/004_ultima_ug200_pipeline_request_information_types.yml
@@ -0,0 +1,16 @@
+---
+FragmentSizeRequiredFromInformationTypeForUltimaUG200:
+ pipeline_name: Ultima UG200
+ request_information_type_key: fragment_size_required_from
+
+FragmentSizeRequiredToInformationTypeForUltimaUG200:
+ pipeline_name: Ultima UG200
+ request_information_type_key: fragment_size_required_to
+
+WaferSizeInformationTypeForUltimaUG200:
+ pipeline_name: Ultima UG200
+ request_information_type_key: wafer_size
+
+OTRecipeInformationTypeForUltima:
+ pipeline_name: Ultima
+ request_information_type_key: ot_recipe
diff --git a/config/default_records/pipelines/004_ultima_ug200_pipelines.yml b/config/default_records/pipelines/004_ultima_ug200_pipelines.yml
new file mode 100644
index 0000000000..21b128df05
--- /dev/null
+++ b/config/default_records/pipelines/004_ultima_ug200_pipelines.yml
@@ -0,0 +1,17 @@
+---
+Ultima UG200:
+ name: Ultima UG200 Sequencing
+ sti_type: UltimaUG200SequencingPipeline
+ validator_class_name: UltimaUG200Validator
+ sorter: 10
+ max_size: 2
+ summary: 1
+ externally_managed: 0
+ group_name: Sequencing
+ control_request_type_id: 0
+ min_size: 1
+ request_type_keys:
+ - ultima_ug200_sequencing
+ workflow:
+ name: Ultima UG200
+ item_limit: 2
diff --git a/config/default_records/request_information_types/004_ultima_ug200_request_information_types.yml b/config/default_records/request_information_types/004_ultima_ug200_request_information_types.yml
new file mode 100644
index 0000000000..bde23fdb31
--- /dev/null
+++ b/config/default_records/request_information_types/004_ultima_ug200_request_information_types.yml
@@ -0,0 +1,7 @@
+---
+wafer_size:
+ name: Wafer size
+ key: wafer_size
+ label: Wafer size
+ width: 5
+ hide_in_inbox: 0
diff --git a/config/default_records/request_type_validators/003_ultima_ug200_request_type_validators.yml b/config/default_records/request_type_validators/003_ultima_ug200_request_type_validators.yml
new file mode 100644
index 0000000000..028e593253
--- /dev/null
+++ b/config/default_records/request_type_validators/003_ultima_ug200_request_type_validators.yml
@@ -0,0 +1,8 @@
+---
+WaferSizeRequestedUltimaUG200Sequencing:
+ request_type_key: ultima_ug200_sequencing
+ request_option: wafer_size
+ valid_options:
+ - 5TB
+ - 10TB
+ - 20TB
diff --git a/config/default_records/request_types/026_ultima_ug200_request_types.yml b/config/default_records/request_types/026_ultima_ug200_request_types.yml
new file mode 100644
index 0000000000..99dbf035b3
--- /dev/null
+++ b/config/default_records/request_types/026_ultima_ug200_request_types.yml
@@ -0,0 +1,12 @@
+# Request types for Ultima UG200 sequencing platform.
+---
+ultima_ug200_sequencing:
+ name: Ultima UG200 sequencing
+ asset_type: LibraryTube
+ order: 2
+ initial_state: pending
+ billable: true
+ # Using same product line name as Ultima UG100
+ product_line_name: Ultima
+ request_class_name: UltimaUG200SequencingRequest
+ request_purpose: standard
diff --git a/config/default_records/submission_templates/021_ultima_ug200_submission_templates.wip.yml b/config/default_records/submission_templates/021_ultima_ug200_submission_templates.wip.yml
new file mode 100644
index 0000000000..e5b5363585
--- /dev/null
+++ b/config/default_records/submission_templates/021_ultima_ug200_submission_templates.wip.yml
@@ -0,0 +1,17 @@
+# This submission template is associated with the Ultima PCR Free Library preparation pipeline
+# and the Ultima UG200 sequencing platform.
+---
+Limber-Htp - Ultima PCR Free - Ultima UG200 sequencing:
+ submission_class_name: "LinearSubmission"
+ related_records:
+ request_type_keys: ["limber_ultima_htp_pcr_free", "limber_multiplexing_ultima", "ultima_ug200_sequencing"]
+ order_role: PCR Free
+ product_line_name: Ultima
+ product_catalogue_name: GenericNoPCR
+Limber-Htp - Ultima PCR Free - Ultima UG200 sequencing Automated:
+ submission_class_name: "LinearSubmission"
+ related_records:
+ request_type_keys: ["ultima_ug200_sequencing"]
+ order_role: PCR Free
+ product_line_name: Ultima
+ product_catalogue_name: GenericNoPCR
diff --git a/config/default_records/tasks/005_ultima_ug200_tasks.yml b/config/default_records/tasks/005_ultima_ug200_tasks.yml
new file mode 100644
index 0000000000..c031066648
--- /dev/null
+++ b/config/default_records/tasks/005_ultima_ug200_tasks.yml
@@ -0,0 +1,13 @@
+---
+Opentrons:
+ name: Opentrons
+ workflow: Ultima UG200
+ sorted: 0
+ lab_activity: true
+ sti_type: SetDescriptorsTask
+Amp:
+ name: Amp
+ workflow: Ultima UG200
+ sorted: 1
+ lab_activity: true
+ sti_type: SetDescriptorsTask
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index 55a6a2b9bd..984e1a1a24 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -18,6 +18,7 @@
inflect.acronym 'ENA' # European Nucleotide Archive
inflect.acronym 'HTTP' # HyperText Transfer Protocol
inflect.acronym 'EBI' # European Bioinformatics Institute
+ inflect.acronym 'UG200' # Ultima UG200
end
# These inflection rules are supported but not enabled by default:
diff --git a/config/locales/metadata/en.yml b/config/locales/metadata/en.yml
index 08f1fbd67e..da8fa0683b 100644
--- a/config/locales/metadata/en.yml
+++ b/config/locales/metadata/en.yml
@@ -79,6 +79,9 @@ en:
ot_recipe:
label: OT Recipe
+ wafer_size:
+ label: Wafer Size
+
library_creation_request:
<<: *REQUEST
sequencing_request:
@@ -107,6 +110,9 @@ en:
ultima_sequencing_request:
<<: *REQUEST
+ ultima_ug200_sequencing_request:
+ <<: *REQUEST
+
pulldown:
requests:
wgs_library_request:
diff --git a/db/migrate/20260324112536_add_wafer_size_to_request_metadata.rb b/db/migrate/20260324112536_add_wafer_size_to_request_metadata.rb
new file mode 100644
index 0000000000..f1e811d8f0
--- /dev/null
+++ b/db/migrate/20260324112536_add_wafer_size_to_request_metadata.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# This migration adds a wafer_size column to the request_metadata table, which is used to store
+# the wafer size for Ultima sequencing requests. This is stored as a string, with possible
+# values of 5TB, 10TB, and 20TB at time of writing.
+class AddWaferSizeToRequestMetadata < ActiveRecord::Migration[7.1]
+ def change
+ add_column :request_metadata, :wafer_size, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 34cce1cefe..4f5e30ff8a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2026_03_17_142326) do
+ActiveRecord::Schema[7.2].define(version: 2026_03_24_112536) do
create_table "accession_sample_statuses", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.integer "sample_id", null: false
t.string "status", null: false
@@ -1191,6 +1191,7 @@
t.boolean "low_diversity"
t.integer "percent_phix_requested"
t.integer "ot_recipe"
+ t.string "wafer_size"
t.index ["request_id"], name: "index_request_metadata_on_request_id"
end
diff --git a/spec/data/bulk_submission_excel/columns.yml b/spec/data/bulk_submission_excel/columns.yml
index 03280783ee..ab5bd8739e 100644
--- a/spec/data/bulk_submission_excel/columns.yml
+++ b/spec/data/bulk_submission_excel/columns.yml
@@ -315,3 +315,22 @@ ot_recipe:
range_name: :ot_recipe
conditional_formattings:
empty_cell:
+wafer_size:
+ heading: "Wafer Size"
+ unlocked: true
+ attribute: :wafer_size
+ validation:
+ options:
+ type: :list
+ formula1: "$A$1:$A$2"
+ allowBlank: false
+ showInputMessage: true
+ showErrorMessage: true
+ errorStyle: :stop
+ errorTitle: "Wafer Size"
+ error: "You must enter a value from the list provided."
+ promptTitle: "Wafer Size"
+ prompt: "Select a value type from the approved list"
+ range_name: :wafer_size
+ conditional_formattings:
+ empty_cell:
diff --git a/spec/data/bulk_submission_excel/ranges.yml b/spec/data/bulk_submission_excel/ranges.yml
index a1c091015a..b211a116cb 100644
--- a/spec/data/bulk_submission_excel/ranges.yml
+++ b/spec/data/bulk_submission_excel/ranges.yml
@@ -27,3 +27,8 @@ ot_recipe:
options:
- "Free"
- "Flex"
+wafer_size:
+ options:
+ - "5TB"
+ - "10TB"
+ - "20TB"
diff --git a/spec/factories/request_factories.rb b/spec/factories/request_factories.rb
index 5a012985fc..ad180e5d76 100644
--- a/spec/factories/request_factories.rb
+++ b/spec/factories/request_factories.rb
@@ -124,6 +124,20 @@
end
end
+ factory(:ultima_ug200_sequencing_request) do
+ request_type factory: %i[ultima_ug200_sequencing]
+ request_purpose { :standard }
+ sti_type { 'UltimaUG200SequencingRequest' }
+ request_metadata_attributes do
+ {
+ fragment_size_required_from: 150,
+ fragment_size_required_to: 400,
+ ot_recipe: 'Free',
+ wafer_size: '10TB'
+ }
+ end
+ end
+
factory(:library_creation_request, parent: :request, class: 'LibraryCreationRequest') do
asset factory: %i[sample_tube]
request_type factory: %i[library_creation_request_type]
diff --git a/spec/factories/request_type_factories.rb b/spec/factories/request_type_factories.rb
index 4d18e1eb3d..87b2fecfd4 100644
--- a/spec/factories/request_type_factories.rb
+++ b/spec/factories/request_type_factories.rb
@@ -139,6 +139,11 @@
request_class { UltimaSequencingRequest }
end
+ factory :ultima_ug200_sequencing do
+ asset_type { 'LibraryTube' }
+ request_class { UltimaUG200SequencingRequest }
+ end
+
factory :miseq_sequencing_request_type do
request_class { MiSeqSequencingRequest }
asset_type { 'LibraryTube' }
diff --git a/spec/models/ultima_sequencing_request_spec.rb b/spec/models/ultima_sequencing_request_spec.rb
index d152679b4e..3cbaa4864f 100644
--- a/spec/models/ultima_sequencing_request_spec.rb
+++ b/spec/models/ultima_sequencing_request_spec.rb
@@ -33,7 +33,7 @@
end
context 'when ot_recipe value is not assigned' do
- it 'is invalid and displays required percent phix requested error message' do
+ it 'is invalid and displays required OT recipe error message' do
request.request_metadata.ot_recipe = nil
request.validate
expect(request.errors[:'request_metadata.ot_recipe']).to include("can't be blank")
diff --git a/spec/models/ultima_ug200_sequencing_pipeline_spec.rb b/spec/models/ultima_ug200_sequencing_pipeline_spec.rb
new file mode 100644
index 0000000000..361bce75a5
--- /dev/null
+++ b/spec/models/ultima_ug200_sequencing_pipeline_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+RSpec.describe UltimaUG200SequencingPipeline do
+ let(:pipeline) do
+ described_class.new(
+ workflow: Workflow.new,
+ request_types: [create(:ultima_ug200_sequencing)]
+ )
+ end
+
+ describe '#ot_recipe_consistent_for_batch?' do
+ it 'returns true when all requests have the same ot_recipe' do
+ batch = pipeline.batches.build
+ r1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' })
+ r2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' })
+ batch.requests << [r1, r2]
+
+ expect(pipeline.ot_recipe_consistent_for_batch?(batch)).to be true
+ end
+
+ it 'returns false when requests have different ot_recipes' do
+ batch = pipeline.batches.build
+ req1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' })
+ req2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { ot_recipe: 'Flex' })
+ batch.requests << [req1, req2]
+
+ expect(pipeline.ot_recipe_consistent_for_batch?(batch)).to be false
+ end
+
+ it 'returns false when some requests are missing ot_recipe' do
+ batch = pipeline.batches.build
+ r1 = create(:sequencing_request, request_metadata_attributes: { ot_recipe: 'Free' })
+ r2 = create(:sequencing_request, request_metadata_attributes: {}) # no ot_recipe
+ batch.requests << [r1, r2]
+
+ expect(pipeline.ot_recipe_consistent_for_batch?(batch)).to be false
+ end
+ end
+
+ describe '#wafer_size_consistent_for_batch?' do
+ it 'returns true when all requests have the same wafer_size' do
+ batch = pipeline.batches.build
+ r1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' })
+ r2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' })
+ batch.requests << [r1, r2]
+
+ expect(pipeline.wafer_size_consistent_for_batch?(batch)).to be true
+ end
+
+ it 'returns false when requests have different wafer_sizes' do
+ batch = pipeline.batches.build
+ req1 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '5TB' })
+ req2 = create(:ultima_ug200_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' })
+ batch.requests << [req1, req2]
+
+ expect(pipeline.wafer_size_consistent_for_batch?(batch)).to be false
+ end
+
+ # NB. Wafer size is a required field, so cannot be missing. Tested in the request spec.
+ end
+
+ describe '#post_release_batch' do
+ let(:batch) { create(:batch) }
+
+ it 'calls Messenger with UseqWaferIo template and useq_wafer root' do
+ allow(Messenger).to receive(:create!)
+ pipeline.post_release_batch(batch, create(:user))
+
+ expect(Messenger).to have_received(:create!).with(
+ hash_including(target: batch, template: 'UseqWaferIo', root: 'useq_wafer')
+ )
+ end
+ end
+end
diff --git a/spec/models/ultima_ug200_sequencing_request_spec.rb b/spec/models/ultima_ug200_sequencing_request_spec.rb
new file mode 100644
index 0000000000..a87bc38fd2
--- /dev/null
+++ b/spec/models/ultima_ug200_sequencing_request_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe UltimaUG200SequencingRequest do
+ let(:request) { create(:ultima_ug200_sequencing_request) }
+
+ describe 'Validations' do
+ context 'when all attributes are valid' do
+ it 'is valid' do
+ expect(request).to be_valid
+ end
+ end
+
+ context 'when fragment_size_required_from is less than 1' do
+ it 'is invalid and displays fragment size from error message' do
+ request.request_metadata.fragment_size_required_from = 0
+ request.validate
+ expect(request.errors[:'request_metadata.fragment_size_required_from']).to include(
+ 'must be greater than or equal to 1'
+ )
+ end
+ end
+
+ context 'when fragment_size_required_to is less than 1' do
+ it 'is invalid and displays fragment size to error message' do
+ request.request_metadata.fragment_size_required_to = 0
+ request.validate
+ expect(request.errors[:'request_metadata.fragment_size_required_to']).to include(
+ 'must be greater than or equal to 1'
+ )
+ end
+ end
+
+ context 'when wafer_size value is not assigned' do
+ it 'is invalid and displays required wafer size error message' do
+ request.request_metadata.wafer_size = nil
+ request.validate
+ expect(request.errors[:'request_metadata.wafer_size']).to include("can't be blank")
+ end
+ end
+
+ context 'when ot_recipe value is not assigned' do
+ it 'is invalid and displays required OT recipe error message' do
+ request.request_metadata.ot_recipe = nil
+ request.validate
+ expect(request.errors[:'request_metadata.ot_recipe']).to include("can't be blank")
+ end
+ end
+ end
+end
diff --git a/spec/validators/ultima_validator_spec.rb b/spec/validators/ultima_validator_spec.rb
index c651ef2695..1c1ad66aa5 100644
--- a/spec/validators/ultima_validator_spec.rb
+++ b/spec/validators/ultima_validator_spec.rb
@@ -37,6 +37,38 @@
end
end
+ context 'when batch contains two requests with the same Wafer Size' do
+ let(:pipeline) { UltimaSequencingPipeline.new }
+ let(:batch) { create(:batch, pipeline:) }
+ let(:request1) { create(:ultima_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' }) }
+ let(:request2) { create(:ultima_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' }) }
+
+ before do
+ batch.requests << [request1, request2]
+ end
+
+ it 'is valid' do
+ validator.validate(batch)
+ expect(batch.errors[:base]).to be_empty
+ end
+ end
+
+ context 'when batch contains two requests with different wafer_size' do
+ let(:pipeline) { UltimaSequencingPipeline.new }
+ let(:batch) { create(:batch, pipeline:) }
+ let(:request1) { create(:ultima_sequencing_request, request_metadata_attributes: { wafer_size: '5TB' }) }
+ let(:request2) { create(:ultima_sequencing_request, request_metadata_attributes: { wafer_size: '10TB' }) }
+
+ before do
+ batch.requests << [request1, request2]
+ end
+
+ it 'is invalid due to wafer_size mismatch' do
+ validator.validate(batch)
+ expect(batch.errors[:base]).to include(described_class::WAFER_SIZE_CONSISTENT_MSG)
+ end
+ end
+
context 'when batch contains a single request' do
let(:pipeline) { UltimaSequencingPipeline.new }
let(:batch) { create(:batch, pipeline:) }