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
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_TIMING_PUBLICATION =
'delay until publication'
- DATA_RELEASE_TIMINGS =
[
DATA_RELEASE_TIMING_STANDARD,
DATA_RELEASE_TIMING_IMMEDIATE,
DATA_RELEASE_TIMING_DELAYED
].freeze
- OLD_DATA_RELEASE_PREVENTION_REASONS =
['data validity', 'legal', 'replication of data subset'].freeze
- DATA_RELEASE_PREVENTION_REASON_OTHER =
'Other (please specify)'
- DATA_RELEASE_PREVENTION_REASONS =
[
'Pilot or validation studies - DAC approval not required',
'Collaborators will share data in a research repository - DAC approval not required',
'Prevent harm (e.g sensitive studies or biosecurity) - DAC approval required',
'Protecting IP - DAC approval required',
DATA_RELEASE_PREVENTION_REASON_OTHER
].freeze
- OLD_DATA_RELEASE_DELAY_FOR_OTHER =
'other'
- DATA_RELEASE_DELAY_FOR_OTHER =
'Other (please specify below)'
- OLD_DATA_RELEASE_DELAY_REASONS =
['other', 'phd study'].freeze
- DATA_RELEASE_DELAY_REASONS_STANDARD =
[
'PhD study',
'Capacity building',
'Intellectual property protection',
'Additional time to make data FAIR',
DATA_RELEASE_DELAY_FOR_OTHER
].freeze
- DATA_RELEASE_DELAY_REASONS_ASSAY =
['assay of no other use'].freeze
- DATA_RELEASE_DELAY_PERIODS =
['3 months', '6 months', '9 months', '12 months', '18 months'].freeze
- EBI_LIBRARY_STRATEGY_OPTIONS =
- EBI_LIBRARY_SOURCE_OPTIONS =
- EBI_LIBRARY_SELECTION_OPTIONS =
- REMAPPED_ATTRIBUTES =
{
contaminated_human_dna: YES_OR_NO,
remove_x_and_autosomes: YES_OR_NO,
study_sra_hold: STUDY_SRA_HOLDS,
contains_human_dna: YES_OR_NO,
commercially_available: YES_OR_NO
}.transform_values { |v| v.index_by { |b| b.downcase } }
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.
128
129
130
|
# File 'app/models/study.rb', line 128
def approval
@approval
end
|
#run_count ⇒ Object
Returns the value of attribute run_count.
128
129
130
|
# File 'app/models/study.rb', line 128
def run_count
@run_count
end
|
#total_price ⇒ Object
Returns the value of attribute total_price.
128
129
130
|
# File 'app/models/study.rb', line 128
def total_price
@total_price
end
|
Instance Method Details
#abbreviation ⇒ Object
[View source]
566
567
568
569
|
# File 'app/models/study.rb', line 566
def abbreviation
abbreviation = study_metadata.study_name_abbreviation
abbreviation.presence || "#{id}STDY"
end
|
#accession_all_samples ⇒ Object
[View source]
558
559
560
561
562
563
564
|
# File 'app/models/study.rb', line 558
def accession_all_samples
samples.find_each do |sample|
sample.accession if accession_number?
rescue AccessionService::AccessionServiceError => e
errors.add(:base, e.message)
end
end
|
#accession_number? ⇒ Boolean
[View source]
554
555
556
|
# File 'app/models/study.rb', line 554
def accession_number?
ebi_accession_number.present?
end
|
#approved? ⇒ Boolean
[View source]
575
576
577
578
|
# File 'app/models/study.rb', line 575
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.
[View source]
492
493
494
495
496
|
# File 'app/models/study.rb', line 492
def asset_progress(assets = nil)
wheres = {}
wheres = { asset_id: assets.map(&:id) } if assets.present?
yield(initial_requests.asset_statistics(wheres))
end
|
#completed ⇒ Object
[View source]
477
478
479
480
481
482
483
|
# File 'app/models/study.rb', line 477
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
[View source]
546
547
548
|
# File 'app/models/study.rb', line 546
def dac_accession_number
study_metadata.ega_dac_accession_number
end
|
#dac_refname ⇒ Object
[View source]
515
516
517
|
# File 'app/models/study.rb', line 515
def dac_refname
"DAC for study - #{name} - ##{id}"
end
|
#data_release_delay_options(assay_option: false) ⇒ Array<String>
Helper method for edit dropdowns to support backwards compatibility with old options.
[View source]
#data_release_prevention_options ⇒ Array<String>
Helper method for edit dropdowns to support backwards compatibility with old options.
[View source]
620
621
622
623
624
625
626
627
|
# File 'app/models/study.rb', line 620
def data_release_prevention_options
additional_options = []
if OLD_DATA_RELEASE_PREVENTION_REASONS.include? study_metadata.data_release_prevention_reason
additional_options << study_metadata.data_release_prevention_reason
end
DATA_RELEASE_PREVENTION_REASONS + additional_options
end
|
#dehumanise_abbreviated_name ⇒ Object
[View source]
571
572
573
|
# File 'app/models/study.rb', line 571
def dehumanise_abbreviated_name
abbreviation.downcase.gsub(/ +/, '_')
end
|
#each_well_for_qc_report_in_batches(exclude_existing, product_criteria, plate_purposes = nil) ⇒ Object
[View source]
442
443
444
445
446
447
448
449
450
451
452
453
454
|
# File 'app/models/study.rb', line 442
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
[View source]
542
543
544
|
# File 'app/models/study.rb', line 542
def ebi_accession_number
study_metadata.study_ebi_accession_number
end
|
#ethical_approval_required? ⇒ Boolean
[View source]
580
581
582
583
|
# File 'app/models/study.rb', line 580
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
[View source]
538
539
540
|
# File 'app/models/study.rb', line 538
def locale
funding_source
end
|
#mailing_list_of_managers ⇒ Object
[View source]
604
605
606
607
|
# File 'app/models/study.rb', line 604
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
[View source]
469
470
471
|
# File 'app/models/study.rb', line 469
def mark_active
logger.warn "Study activation failed! #{errors.map(&:to_s)}" unless active?
end
|
#mark_deactive ⇒ Object
[View source]
465
466
467
|
# File 'app/models/study.rb', line 465
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.
[View source]
534
535
536
|
# File 'app/models/study.rb', line 534
def owner
owners.first
end
|
#policy_accession_number ⇒ Object
[View source]
550
551
552
|
# File 'app/models/study.rb', line 550
def policy_accession_number
study_metadata.ega_policy_accession_number
end
|
#rebroadcast ⇒ Object
[View source]
613
614
615
|
# File 'app/models/study.rb', line 613
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.
[View source]
487
488
489
|
# File 'app/models/study.rb', line 487
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.
[View source]
499
500
501
502
503
504
505
506
507
508
509
|
# File 'app/models/study.rb', line 499
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
[View source]
596
597
598
|
# File 'app/models/study.rb', line 596
def send_samples_to_service?
accession_service.no_study_accession_needed || (!study_metadata.never_release? && accession_number?)
end
|
#study ⇒ Object
[View source]
525
526
527
|
# File 'app/models/study.rb', line 525
def study
self
end
|
#study_status ⇒ Object
[View source]
511
512
513
|
# File 'app/models/study.rb', line 511
def study_status
inactive? ? 'closed' : 'open'
end
|
#subject_type ⇒ Object
[View source]
609
610
611
|
# File 'app/models/study.rb', line 609
def subject_type
'study'
end
|
[View source]
473
474
475
|
# File 'app/models/study.rb', line 473
def
.each_with_object([]) { |c, array| array << c.description if c.description.present? }.join(', ')
end
|
#unprocessed_submissions? ⇒ Boolean
[View source]
519
520
521
522
|
# File 'app/models/study.rb', line 519
def unprocessed_submissions?
study.orders.any? { |o| o.submission.nil? || o.submission.unprocessed? }
end
|
#validate_ena_required_fields! ⇒ Object
[View source]
600
601
602
|
# File 'app/models/study.rb', line 600
def validate_ena_required_fields!
valid?(:accession) or raise ActiveRecord::RecordInvalid, self
end
|
#validate_ethically_approved ⇒ Object
[View source]
429
430
431
432
433
434
435
436
437
438
439
440
|
# File 'app/models/study.rb', line 429
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
[View source]
456
457
458
459
460
461
462
463
|
# File 'app/models/study.rb', line 456
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
|