Class: Study
- Inherits:
-
ApplicationRecord
show all
- Extended by:
- Attributable::Association::Target, EventfulRecord, Metadata
- Includes:
- AASM, Api::StudyIo::Extensions, Commentable, DataRelease, EventfulRecord, ModelExtensions::Study, ReferenceGenome::Associations, Role::Authorized, SampleManifest::Associations, SharedBehaviour::Named, StudyReport::StudyDetails, Uuid::Uuidable
- Defined in:
- app/models/study.rb
Overview
Note:
This is really quite convoluted, and couples together administrative organization alongside accessioning and data-access rules. It results in samples being tied to an EGAS/ERP far too early in their lifecycle, and as a result we often need to perform ‘sample moves’. Although we do need to know if samples are open(ENA) or managed(EGA) at the point of accessioning.
A Study is a collection of various samples and the work done on them. They are perhaps slightly overloaded, and provide: - A means of grouping together samples for administrative purposes - A means of generating EGAS/ERP study accession numbers at the ENA/EGA - @see Accessionable::Study - These accession numbers are used at data release to group samples together for publication - For managed/EGA studies, also ties the data to an Accessionable::Dac and Accessionable::Policy - A means of generating the aforementioned Accessionable::Dac and Accessionable::Policy @note These should DEFINITELY be separate entities - A means of tying data to internal data-release timings - A means to apply internal data access policies to released sequencing data - A means to tie interested parties to the samples and the work done on them - A way of specifying common ways of filtering/processing generated data. eg. filter human sequence - The service with which a Sample will be accessioned (eg. EGA/ENA)
When a Sample enters Sequencescape it will usually be associated with a single Study, usually determined by the Study associated with the SampleManifest. This study will be recorded on the Aliquot in the stock Receptacle, and additionally a StudySample will record this association.
When work is requested an Order will be created, specifying a list of receptacles and the Study for which this work is being performed. This will set initial study id on request and in turn will be recorded on any downstream aliquots. Critically, it is the study specified on the Aliquot in the Lane which will influence processes like data release and data access.
Defined Under Namespace
Classes: Metadata, PolyMetadataHandler
Constant Summary
collapse
- STOCK_PLATE_PURPOSES =
['Stock Plate', 'Stock RNA Plate'].freeze
- YES =
'Yes'
- NO =
'No'
- YES_OR_NO =
[YES, NO].freeze
- Other_type =
'Other'
- STUDY_SRA_HOLDS =
%w[Hold Public].freeze
- DATA_RELEASE_STRATEGY_OPEN =
'open'
- DATA_RELEASE_STRATEGY_MANAGED =
'managed'
- DATA_RELEASE_STRATEGY_NOT_APPLICABLE =
'not applicable'
- DATA_RELEASE_STRATEGIES =
[
DATA_RELEASE_STRATEGY_OPEN,
DATA_RELEASE_STRATEGY_MANAGED,
DATA_RELEASE_STRATEGY_NOT_APPLICABLE
].freeze
- DATA_RELEASE_TIMING_STANDARD =
'standard'
- DATA_RELEASE_TIMING_NEVER =
'never'
- DATA_RELEASE_TIMING_DELAYED =
'delayed'
- DATA_RELEASE_TIMING_IMMEDIATE =
'immediate'
- DATA_RELEASE_TIMINGS =
[
DATA_RELEASE_TIMING_STANDARD,
DATA_RELEASE_TIMING_IMMEDIATE,
DATA_RELEASE_TIMING_DELAYED
].freeze
- DATA_RELEASE_PREVENTION_REASONS =
['data validity', 'legal', 'replication of data subset'].freeze
- DATA_RELEASE_DELAY_FOR_OTHER =
'other'
- DATA_RELEASE_DELAY_REASONS_STANDARD =
['phd study', DATA_RELEASE_DELAY_FOR_OTHER].freeze
- DATA_RELEASE_DELAY_REASONS_ASSAY =
['phd study', 'assay of no other use', DATA_RELEASE_DELAY_FOR_OTHER].freeze
- DATA_RELEASE_DELAY_PERIODS =
['3 months', '6 months', '9 months', '12 months', '18 months'].freeze
Constants included
from Metadata
Metadata::SECTION_FIELDS
StudyReport::StudyDetails::BATCH_SIZE
Instance Attribute Summary collapse
Instance Method Summary
collapse
has_many_events, has_many_lab_events, has_one_event_with_family
Methods included from Metadata
has_metadata
default, extended, for_select_association
included
included
included
#after_comment_addition
#accession_required?, #do_not_enforce_accessioning, #for_array_express?, #valid_data_release_properties?
included, #unsaved_uuid!, #uuid
included, #render_class
#each_stock_well_id_in_study_in_batches, #progress_report_header, #progress_report_on_all_assets
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
#approval ⇒ Object
Returns the value of attribute approval.
96
97
98
|
# File 'app/models/study.rb', line 96
def approval
@approval
end
|
#run_count ⇒ Object
Returns the value of attribute run_count.
96
97
98
|
# File 'app/models/study.rb', line 96
def run_count
@run_count
end
|
#total_price ⇒ Object
Returns the value of attribute total_price.
96
97
98
|
# File 'app/models/study.rb', line 96
def total_price
@total_price
end
|
Instance Method Details
#abbreviation ⇒ Object
506
507
508
509
|
# File 'app/models/study.rb', line 506
def abbreviation
abbreviation = study_metadata.study_name_abbreviation
abbreviation.presence || "#{id}STDY"
end
|
#accession_all_samples ⇒ Object
502
503
504
|
# File 'app/models/study.rb', line 502
def accession_all_samples
samples.find_each(&:accession) if accession_number?
end
|
#accession_number? ⇒ Boolean
498
499
500
|
# File 'app/models/study.rb', line 498
def accession_number?
ebi_accession_number.present?
end
|
#accession_service ⇒ Object
#approved? ⇒ Boolean
515
516
517
518
|
# File 'app/models/study.rb', line 515
def approved?
true
end
|
#asset_progress(assets = nil) {|initial_requests.asset_statistics(wheres)| ... } ⇒ Object
Yields information on the state of all assets in a convenient fashion for displaying in a table.
436
437
438
439
440
|
# File 'app/models/study.rb', line 436
def asset_progress(assets = nil)
wheres = {}
wheres = { asset_id: assets.map(&:id) } if assets.present?
yield(initial_requests.asset_statistics(wheres))
end
|
#completed ⇒ Object
421
422
423
424
425
426
427
|
# File 'app/models/study.rb', line 421
def completed
counts = requests.standard.group('state').count
total = counts.values.sum
failed = counts['failed'] || 0
cancelled = counts['cancelled'] || 0
(total - failed - cancelled) > 0 ? (counts.fetch('passed', 0) * 100) / (total - failed - cancelled) : 0
end
|
#dac_accession_number ⇒ Object
490
491
492
|
# File 'app/models/study.rb', line 490
def dac_accession_number
study_metadata.ega_dac_accession_number
end
|
#dac_refname ⇒ Object
459
460
461
|
# File 'app/models/study.rb', line 459
def dac_refname
"DAC for study - #{name} - ##{id}"
end
|
#dehumanise_abbreviated_name ⇒ Object
511
512
513
|
# File 'app/models/study.rb', line 511
def dehumanise_abbreviated_name
abbreviation.downcase.gsub(/ +/, '_')
end
|
#each_well_for_qc_report_in_batches(exclude_existing, product_criteria, plate_purposes = nil) ⇒ Object
386
387
388
389
390
391
392
393
394
395
396
397
398
|
# File 'app/models/study.rb', line 386
def each_well_for_qc_report_in_batches(exclude_existing, product_criteria, plate_purposes = nil)
base_scope =
Well
.on_plate_purpose_included(PlatePurpose.where(name: plate_purposes || STOCK_PLATE_PURPOSES))
.for_study_through_aliquot(self)
.without_blank_samples
.includes(:well_attribute, :aliquots, :map, samples: :sample_metadata)
.readonly(true)
scope = exclude_existing ? base_scope.without_report(product_criteria) : base_scope
scope.find_in_batches { |wells| yield wells }
end
|
#ebi_accession_number ⇒ Object
486
487
488
|
# File 'app/models/study.rb', line 486
def ebi_accession_number
study_metadata.study_ebi_accession_number
end
|
#ethical_approval_required? ⇒ Boolean
520
521
522
523
|
# File 'app/models/study.rb', line 520
def ethical_approval_required?
study_metadata.contains_human_dna == Study::YES && study_metadata.contaminated_human_dna == Study::NO &&
study_metadata.commercially_available == Study::NO
end
|
#locale ⇒ Object
482
483
484
|
# File 'app/models/study.rb', line 482
def locale
funding_source
end
|
#mailing_list_of_managers ⇒ Object
544
545
546
547
|
# File 'app/models/study.rb', line 544
def mailing_list_of_managers
configured_managers = managers.pluck(:email).compact.uniq
configured_managers.empty? ? configatron.fetch(:ssr_emails, User.all_administrators_emails) : configured_managers
end
|
#mark_active ⇒ Object
413
414
415
|
# File 'app/models/study.rb', line 413
def mark_active
logger.warn "Study activation failed! #{errors.map(&:to_s)}" unless active?
end
|
#mark_deactive ⇒ Object
409
410
411
|
# File 'app/models/study.rb', line 409
def mark_deactive
logger.warn "Study deactivation failed! #{errors.map(&:to_s)}" unless inactive?
end
|
#owner ⇒ Object
Returns the study owner (user) if exists or nil TODO - Should be “owners” and return all owners or empty array - done TODO - Look into this is the person that created it really the owner? If so, then an owner should be created when a study is created.
478
479
480
|
# File 'app/models/study.rb', line 478
def owner
owners.first
end
|
#policy_accession_number ⇒ Object
494
495
496
|
# File 'app/models/study.rb', line 494
def policy_accession_number
study_metadata.ega_policy_accession_number
end
|
Returns the PolyMetadatum object associated with the given key.
566
567
568
|
# File 'app/models/study.rb', line 566
def poly_metadatum_by_key(key)
poly_metadata.find { |pm| pm.key == key.to_s }
end
|
#rebroadcast ⇒ Object
553
554
555
|
# File 'app/models/study.rb', line 553
def rebroadcast
broadcast
end
|
#request_progress {|@stats_cache ||= initial_requests.progress_statistics| ... } ⇒ Object
Yields information on the state of all request types in a convenient fashion for displaying in a table. Used initial requests, which won’t capture cross study sequencing requests.
431
432
433
|
# File 'app/models/study.rb', line 431
def request_progress
yield(@stats_cache ||= initial_requests.progress_statistics) if block_given?
end
|
#sample_progress(samples = nil) ⇒ Object
Yields information on the state of all samples in a convenient fashion for displaying in a table.
443
444
445
446
447
448
449
450
451
452
453
|
# File 'app/models/study.rb', line 443
def sample_progress(samples = nil)
if samples.blank?
requests.sample_statistics_new
else
yield(requests.where(aliquots: { sample_id: samples.pluck(:id) }).sample_statistics_new)
end
end
|
#send_samples_to_service? ⇒ Boolean
536
537
538
|
# File 'app/models/study.rb', line 536
def send_samples_to_service?
accession_service.no_study_accession_needed || (!study_metadata.never_release? && accession_number?)
end
|
#study ⇒ Object
469
470
471
|
# File 'app/models/study.rb', line 469
def study
self
end
|
#study_status ⇒ Object
455
456
457
|
# File 'app/models/study.rb', line 455
def study_status
inactive? ? 'closed' : 'open'
end
|
#subject_type ⇒ Object
549
550
551
|
# File 'app/models/study.rb', line 549
def subject_type
'study'
end
|
417
418
419
|
# File 'app/models/study.rb', line 417
def
.each_with_object([]) { |c, array| array << c.description if c.description.present? }.join(', ')
end
|
#unprocessed_submissions? ⇒ Boolean
463
464
465
466
|
# File 'app/models/study.rb', line 463
def unprocessed_submissions?
study.orders.any? { |o| o.submission.nil? || o.submission.unprocessed? }
end
|
#validate_ena_required_fields! ⇒ Object
540
541
542
|
# File 'app/models/study.rb', line 540
def validate_ena_required_fields!
valid?(:accession) or raise ActiveRecord::RecordInvalid, self
end
|
#validate_ethically_approved ⇒ Object
373
374
375
376
377
378
379
380
381
382
383
384
|
# File 'app/models/study.rb', line 373
def validate_ethically_approved
return true if valid_ethically_approved?
message =
if ethical_approval_required?
'should be either true or false for this study.'
else
'should be not applicable (null) not false.'
end
errors.add(:ethically_approved, message)
false
end
|
#warnings ⇒ Object
400
401
402
403
404
405
406
407
|
# File 'app/models/study.rb', line 400
def warnings
if study_metadata.managed? && study_metadata.data_access_group.blank?
'No user group specified for a managed study. Please specify a valid Unix user group to ensure study data is visible to the correct people.'
end
end
|