Class: SampleManifest

Inherits:
ApplicationRecord show all
Extended by:
Document::Associations, StateMachine
Includes:
ModelExtensions::SampleManifest, BarcodePrinterBehaviour, CoreBehaviour, Uuid::Uuidable
Defined in:
app/models/sample_manifest.rb

Overview

A SampleManifest is the primary way in which new samples enter Sequencescape. When the manifest is generated Sequencescape registers the labware, and reserves a series of Sanger sample ids for the potential samples. It also generates a SampleManifestExcel spreadsheet which gets sent to the customer.

The labware that gets generated is determined by the #asset_type which switches out the CoreBehaviour#core_behaviour module CoreBehaviour. This is concerned with generating Labware and receptacles, generating any event specific to the asset type, and setting manifest specific properties on Aliquot

All samples in a given manifest will initially belong to a single Study, although it is possible for them to become associated with additional studies over time.

Defined Under Namespace

Modules: Associations, BarcodePrinterBehaviour, CoreBehaviour, LibraryPlateBehaviour, LibraryTubeBehaviour, MultiplexedLibraryBehaviour, PlateBehaviour, SampleTubeBehaviour, SharedTubeBehaviour, StateMachine, TubeRackBehaviour, UnspecifiedBehaviour Classes: Generator, Uploader

Constant Summary collapse

LIMIT_ERROR_LENGTH =

While the maximum length of the column is 65536 we place a shorter restriction to allow for: 1) Subsequent serialization by the delayed job 2) The addition of a ‘too many errors’ message

50_000
INDIVIDUAL_ERROR_LIMIT =

In addition we truncate individual messages, this ensures that we don’t inadvertently filter out ALL our errors if the first message is especially long. We don’t re-use the figure above as that would prevent any display of subsequent messages, which probably indicate a different issue.

LIMIT_ERROR_LENGTH / 10
SAMPLES_PER_EVENT =

Samples have a similar issue when generating update events This limit sets a very comfortable safety margin.

3000

Constants included from CoreBehaviour

CoreBehaviour::BEHAVIOURS

Constants included from BarcodePrinterBehaviour

BarcodePrinterBehaviour::ASSET_TYPE_TO_PRINTER_TYPE

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from StateMachine

extended

Methods included from Document::Associations

has_uploaded_document

Methods included from CoreBehaviour

#core_behaviour, included

Methods included from BarcodePrinterBehaviour

#applicable_barcode_printers

Methods included from Uuid::Uuidable

included, #unsaved_uuid!, #uuid

Methods inherited from ApplicationRecord

alias_association, convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!

Methods included from Squishify

extended

Instance Attribute Details

#invalid_wellsObject



145
146
147
# File 'app/models/sample_manifest.rb', line 145

def invalid_wells
  @invalid_wells || []
end

#only_first_labelObject

Returns the value of attribute only_first_label.



51
52
53
# File 'app/models/sample_manifest.rb', line 51

def only_first_label
  @only_first_label
end

#overrideObject

Returns the value of attribute override.



51
52
53
# File 'app/models/sample_manifest.rb', line 51

def override
  @override
end

#rows_per_wellObject

Number of rows per well in the manifest file, specified in manifest_types.yml. Used to pre-populate the spreadsheet with a sufficient number of rows, in the case where we are importing a plate containing pools, and want to provide sample-level information for each pool. Developed initially for the scRNA Core pipeline.

Uses a default value of 1 if not set.



141
142
143
# File 'app/models/sample_manifest.rb', line 141

def rows_per_well
  @rows_per_well || 1
end

Instance Method Details

#barcode_printerObject

Needed for the UI to work!



60
61
# File 'app/models/sample_manifest.rb', line 60

def barcode_printer
end

#create_sample(sanger_sample_id) ⇒ Object



192
193
194
195
196
# File 'app/models/sample_manifest.rb', line 192

def create_sample(sanger_sample_id)
  Sample
    .create!(name: sanger_sample_id, sanger_sample_id: sanger_sample_id, sample_manifest: self)
    .tap { |sample| sample.events.created_using_sample_manifest!(user) }
end

#create_sample_and_aliquot(sanger_sample_id, asset) ⇒ Object



188
189
190
# File 'app/models/sample_manifest.rb', line 188

def create_sample_and_aliquot(sanger_sample_id, asset)
  core_behaviour.generate_sample_and_aliquot(sanger_sample_id, asset)
end

#created_broadcast_eventObject



198
199
200
# File 'app/models/sample_manifest.rb', line 198

def created_broadcast_event
  BroadcastEvent::SampleManifestCreated.create!(seed: self, user: user)
end

#default_asset_typeObject



122
123
124
# File 'app/models/sample_manifest.rb', line 122

def default_asset_type
  self.asset_type = 'plate' if asset_type.blank?
end

#default_filenameObject



130
131
132
# File 'app/models/sample_manifest.rb', line 130

def default_filename
  "#{study_id}stdy_manifest_#{id}_#{created_at.to_fs(:dmy)}"
end

#find_or_create_qc_assay!Object

rubocop:disable Naming/MemoizedInstanceVariableName



250
251
252
# File 'app/models/sample_manifest.rb', line 250

def find_or_create_qc_assay!
  @qc_assay ||= QcAssay.find_or_create_by!(lot_number: "sample_manifest_id:#{id}")
end

#generateObject



179
180
181
182
183
184
185
186
# File 'app/models/sample_manifest.rb', line 179

def generate
  ActiveRecord::Base.transaction do
    self.barcodes = []
    core_behaviour.generate
  end
  created_broadcast_event
  nil
end

#indexed_manifest_assetsObject



216
217
218
# File 'app/models/sample_manifest.rb', line 216

def indexed_manifest_assets
  sample_manifest_assets.includes(*core_behaviour.included_resources).index_by(&:sanger_sample_id)
end

#nameObject



126
127
128
# File 'app/models/sample_manifest.rb', line 126

def name
  "Manifest_#{id}"
end

#poolsObject

Used in manifest upload code to determine if pools are present, so that tag_depth can be set on the aliquots if needed.

Returns a hash of receptacle to array of sample manifest assets. Returns nil if all receptacles only contain 0 or 1 sample.



154
155
156
157
158
159
160
161
162
163
164
# File 'app/models/sample_manifest.rb', line 154

def pools
  @pools ||=
    begin
      # Sample manifest assets are a join table between sample manifests and samples,
      # created upfront when the manifest is generated.
      # Here we use them to see how many samples are in each receptacle.
      receptacle_to_smas = sample_manifest_assets.group_by(&:asset)

      receptacle_to_smas.values.all? { |smas| smas.size <= 1 } ? nil : receptacle_to_smas
    end
end

#purposeObject

Fall back to stock plate by default



227
228
229
# File 'app/models/sample_manifest.rb', line 227

def purpose
  super || default_purpose
end

#purpose_idObject



231
232
233
# File 'app/models/sample_manifest.rb', line 231

def purpose_id
  super || purpose.id
end

#qc_assayObject

Upon upload, sample manifests might generate qc_results for certain specialised fields. We want to keep one qc_assay per sample manifest.



245
246
247
# File 'app/models/sample_manifest.rb', line 245

def qc_assay
  @qc_assay ||= QcAssay.find_by(lot_number: "sample_manifest_id:#{id}")
end

#templateObject



63
64
# File 'app/models/sample_manifest.rb', line 63

def template
end

#truncate_errorsObject

rubocop:todo Metrics/MethodLength



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'app/models/sample_manifest.rb', line 101

def truncate_errors # rubocop:todo Metrics/MethodLength
  if last_errors && last_errors.join.length > LIMIT_ERROR_LENGTH
    # First we truncate individual error messages. This ensures that it the first message is already
    # longer than out max limit, we still show something.
    full_last_errors = last_errors.map { |error| error.truncate(INDIVIDUAL_ERROR_LIMIT) }

    removed_errors = 0

    while full_last_errors.join.length > LIMIT_ERROR_LENGTH
      full_last_errors.pop
      removed_errors += 1
    end

    if removed_errors.positive?
      full_last_errors << "There were too many errors to record. #{removed_errors} additional errors are not shown."
    end

    self.last_errors = full_last_errors
  end
end

#tube_rack_purposeObject



235
236
237
# File 'app/models/sample_manifest.rb', line 235

def tube_rack_purpose
  super || default_tube_rack_purpose
end

#tube_rack_purpose_idObject



239
240
241
# File 'app/models/sample_manifest.rb', line 239

def tube_rack_purpose_id
  super || tube_rack_purpose.id
end

#update_barcodesObject

updates the manifest barcode list e.g. after applying a foreign barcode



221
222
223
224
# File 'app/models/sample_manifest.rb', line 221

def update_barcodes
  self.barcodes = labware.map(&:human_barcode)
  save!
end

#updated_broadcast_event(user_updating_manifest, updated_samples_ids) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'app/models/sample_manifest.rb', line 202

def updated_broadcast_event(user_updating_manifest, updated_samples_ids)
  # We chunk samples into groups of 3000 to avoid issues with the column size in broadcast_events.properties
  # In practice we have 11 characters per sample with current id lengths. This allows for up to 21 characters
  updated_samples_ids.each_slice(SAMPLES_PER_EVENT) do |chunked_sample_ids|
    BroadcastEvent::SampleManifestUpdated.create!(
      seed: self,
      user: user_updating_manifest,
      properties: {
        updated_samples_ids: chunked_sample_ids
      }
    )
  end
end