caruby-tissue 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +4 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/bin/crtdump +31 -0
- data/bin/crtexample +18 -0
- data/bin/crtextract +47 -0
- data/bin/crtmigrate +17 -0
- data/bin/crtsmoke +27 -0
- data/examples/galena/README.md +53 -0
- data/examples/galena/bin/migrate.rb +42 -0
- data/examples/galena/bin/seed.rb +43 -0
- data/examples/galena/conf/extract/simple_fields.yaml +4 -0
- data/examples/galena/conf/migration/filter_fields.yaml +7 -0
- data/examples/galena/conf/migration/filter_migration.yaml +9 -0
- data/examples/galena/conf/migration/frozen_fields.yaml +11 -0
- data/examples/galena/conf/migration/frozen_migration.yaml +9 -0
- data/examples/galena/conf/migration/general_fields.yaml +42 -0
- data/examples/galena/conf/migration/general_migration.yaml +9 -0
- data/examples/galena/conf/migration/simple_fields.yaml +30 -0
- data/examples/galena/conf/migration/simple_migration.yaml +7 -0
- data/examples/galena/conf/migration/small_fields.yaml +24 -0
- data/examples/galena/conf/migration/small_migration.yaml +9 -0
- data/examples/galena/data/filter.csv +1 -0
- data/examples/galena/data/frozen.csv +1 -0
- data/examples/galena/data/general.csv +1 -0
- data/examples/galena/data/minimal.csv +1 -0
- data/examples/galena/data/simple.csv +1 -0
- data/examples/galena/data/small.csv +1 -0
- data/examples/galena/doc/CaTissue.html +93 -0
- data/examples/galena/doc/CaTissue/CollectionProtocolRegistration.html +181 -0
- data/examples/galena/doc/CaTissue/Participant.html +241 -0
- data/examples/galena/doc/CaTissue/SpecimenCollectionGroup.html +190 -0
- data/examples/galena/doc/CaTissue/StorageContainer.html +179 -0
- data/examples/galena/doc/CaTissue/TissueSpecimen.html +320 -0
- data/examples/galena/doc/Galena.html +290 -0
- data/examples/galena/doc/Galena/Seed.html +203 -0
- data/examples/galena/doc/Galena/Seed/Defaults.html +646 -0
- data/examples/galena/doc/_index.html +188 -0
- data/examples/galena/doc/class_list.html +36 -0
- data/examples/galena/doc/css/common.css +1 -0
- data/examples/galena/doc/css/full_list.css +53 -0
- data/examples/galena/doc/css/style.css +307 -0
- data/examples/galena/doc/file.README.html +108 -0
- data/examples/galena/doc/file_list.html +38 -0
- data/examples/galena/doc/frames.html +13 -0
- data/examples/galena/doc/index.html +108 -0
- data/examples/galena/doc/js/app.js +202 -0
- data/examples/galena/doc/js/full_list.js +149 -0
- data/examples/galena/doc/js/jquery.js +154 -0
- data/examples/galena/doc/method_list.html +179 -0
- data/examples/galena/doc/top-level-namespace.html +112 -0
- data/examples/galena/lib/README.html +33 -0
- data/examples/galena/lib/galena.rb +8 -0
- data/examples/galena/lib/galena/cli/seed.rb +43 -0
- data/examples/galena/lib/galena/migration/filter_shims.rb +43 -0
- data/examples/galena/lib/galena/migration/frozen_shims.rb +54 -0
- data/examples/galena/lib/galena/seed/defaults.rb +97 -0
- data/lib/catissue.rb +26 -0
- data/lib/catissue/cli/command.rb +51 -0
- data/lib/catissue/cli/example.rb +31 -0
- data/lib/catissue/cli/migrate.rb +60 -0
- data/lib/catissue/cli/smoke.rb +45 -0
- data/lib/catissue/database.rb +451 -0
- data/lib/catissue/database/annotation/annotatable_service.rb +25 -0
- data/lib/catissue/database/annotation/annotation_service.rb +79 -0
- data/lib/catissue/database/annotation/annotator.rb +84 -0
- data/lib/catissue/database/annotation/entity_manager.rb +10 -0
- data/lib/catissue/database/annotation/integration_service.rb +87 -0
- data/lib/catissue/database/controlled_value_finder.rb +43 -0
- data/lib/catissue/database/controlled_values.rb +162 -0
- data/lib/catissue/domain/abstract_domain_object.rb +8 -0
- data/lib/catissue/domain/abstract_position.rb +22 -0
- data/lib/catissue/domain/abstract_specimen.rb +288 -0
- data/lib/catissue/domain/abstract_specimen_collection_group.rb +25 -0
- data/lib/catissue/domain/address.rb +13 -0
- data/lib/catissue/domain/cancer_research_group.rb +11 -0
- data/lib/catissue/domain/capacity.rb +34 -0
- data/lib/catissue/domain/check_in_check_out_event_parameter.rb +19 -0
- data/lib/catissue/domain/collection_event_parameters.rb +13 -0
- data/lib/catissue/domain/collection_protocol.rb +177 -0
- data/lib/catissue/domain/collection_protocol_event.rb +108 -0
- data/lib/catissue/domain/collection_protocol_registration.rb +108 -0
- data/lib/catissue/domain/consent_tier_response.rb +13 -0
- data/lib/catissue/domain/consent_tier_status.rb +29 -0
- data/lib/catissue/domain/container.rb +234 -0
- data/lib/catissue/domain/container_position.rb +21 -0
- data/lib/catissue/domain/container_type.rb +131 -0
- data/lib/catissue/domain/department.rb +13 -0
- data/lib/catissue/domain/disposal_event_parameters.rb +13 -0
- data/lib/catissue/domain/embedded_event_parameters.rb +10 -0
- data/lib/catissue/domain/external_identifier.rb +22 -0
- data/lib/catissue/domain/frozen_event_parameters.rb +10 -0
- data/lib/catissue/domain/institution.rb +13 -0
- data/lib/catissue/domain/new_specimen_array_order_item.rb +35 -0
- data/lib/catissue/domain/order_details.rb +25 -0
- data/lib/catissue/domain/participant.rb +138 -0
- data/lib/catissue/domain/participant_medical_identifier.rb +38 -0
- data/lib/catissue/domain/password.rb +11 -0
- data/lib/catissue/domain/race.rb +11 -0
- data/lib/catissue/domain/received_event_parameters.rb +25 -0
- data/lib/catissue/domain/scg_event_parameters.rb +11 -0
- data/lib/catissue/domain/site.rb +30 -0
- data/lib/catissue/domain/specimen.rb +456 -0
- data/lib/catissue/domain/specimen_array.rb +47 -0
- data/lib/catissue/domain/specimen_array_content.rb +19 -0
- data/lib/catissue/domain/specimen_array_type.rb +20 -0
- data/lib/catissue/domain/specimen_characteristics.rb +20 -0
- data/lib/catissue/domain/specimen_collection_group.rb +412 -0
- data/lib/catissue/domain/specimen_event_parameters.rb +111 -0
- data/lib/catissue/domain/specimen_position.rb +38 -0
- data/lib/catissue/domain/specimen_protocol.rb +34 -0
- data/lib/catissue/domain/specimen_requirement.rb +143 -0
- data/lib/catissue/domain/storage_container.rb +204 -0
- data/lib/catissue/domain/storage_type.rb +82 -0
- data/lib/catissue/domain/transfer_event_parameters.rb +53 -0
- data/lib/catissue/domain/user.rb +100 -0
- data/lib/catissue/extract/command.rb +31 -0
- data/lib/catissue/extract/delta.rb +62 -0
- data/lib/catissue/extract/extractor.rb +99 -0
- data/lib/catissue/migration/migrator.rb +101 -0
- data/lib/catissue/migration/shims.rb +108 -0
- data/lib/catissue/migration/uniquify.rb +111 -0
- data/lib/catissue/resource.rb +84 -0
- data/lib/catissue/util/controlled_value.rb +29 -0
- data/lib/catissue/util/location.rb +116 -0
- data/lib/catissue/util/log.rb +30 -0
- data/lib/catissue/util/person.rb +31 -0
- data/lib/catissue/util/position.rb +54 -0
- data/lib/catissue/util/storable.rb +34 -0
- data/lib/catissue/util/storage_type_holder.rb +30 -0
- data/lib/catissue/version.rb +7 -0
- metadata +212 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module CaTissue
|
4
|
+
# import the Java class
|
5
|
+
java_import('edu.wustl.catissuecore.domain.ParticipantMedicalIdentifier')
|
6
|
+
|
7
|
+
|
8
|
+
# The ParticipantMedicalIdentifier domain class.
|
9
|
+
class ParticipantMedicalIdentifier
|
10
|
+
include Resource
|
11
|
+
|
12
|
+
# Sets this ParticipantMedicalIdentifier's medical record number to the given value.
|
13
|
+
# A Numeric value is converted to a String.
|
14
|
+
def medical_record_number=(value)
|
15
|
+
value = value.to_s if value
|
16
|
+
setMedicalRecordNumber(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
set_secondary_key_attributes(:site, :medical_record_number)
|
20
|
+
|
21
|
+
add_mandatory_attributes(:participant)
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Adds defaults as follows:
|
26
|
+
# * The default site is the particiant registration protocol site, if unique.
|
27
|
+
def add_defaults_local
|
28
|
+
super
|
29
|
+
self.site ||= default_site
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_site
|
33
|
+
cprs = participant.registrations if participant
|
34
|
+
cp = cprs.first.protocol if cprs and cprs.size == 1
|
35
|
+
cp.sites.first if cp and cp.sites.size == 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'catissue/domain/scg_event_parameters'
|
2
|
+
|
3
|
+
module CaTissue
|
4
|
+
# import the Java class
|
5
|
+
java_import('edu.wustl.catissuecore.domain.ReceivedEventParameters')
|
6
|
+
|
7
|
+
class ReceivedEventParameters
|
8
|
+
include Resource, SCGEventParameters
|
9
|
+
|
10
|
+
add_attribute_aliases(:receiver => :user)
|
11
|
+
|
12
|
+
add_attribute_defaults(:received_quality => 'Not Specified')
|
13
|
+
|
14
|
+
add_mandatory_attributes(:received_quality)
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Returns the first SCG CP coordinator, if any.
|
19
|
+
def default_user
|
20
|
+
scg = specimen_collection_group || (specimen.specimen_collection_group if specimen) || return
|
21
|
+
cp = scg.collection_protocol || return
|
22
|
+
cp.coordinators.first || (cp.sites.first.coordinator if cp.sites.size === 1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module CaTissue
|
2
|
+
# A SCGEventParameters is a SpecimenEventParameters which can be owned by a SpecimenCollectionGroup.
|
3
|
+
module SCGEventParameters
|
4
|
+
# Returns the SpecimenEventParameters in others which matches this SCGEventParameters in the scope of an owner Specimen or SCG.
|
5
|
+
# This method relaxes {CaRuby::Resource#match_in_owner_scope} for a SCGEventParameters that matches any SpecimenEventParameters
|
6
|
+
# in others of the same class, since there can be at most one SCGEventParameters of a given class for a given SCG.
|
7
|
+
def match_in_owner_scope(others)
|
8
|
+
others.detect { |other| minimal_match?(other) }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CaTissue
|
2
|
+
# import the Java class
|
3
|
+
java_import('edu.wustl.catissuecore.domain.Site')
|
4
|
+
|
5
|
+
# The Site domain class.
|
6
|
+
class Site
|
7
|
+
include Resource
|
8
|
+
|
9
|
+
# caTissue alert - the Site SCG collection is ignored, since it is not fetched with the Site,
|
10
|
+
# the caCORE query builder doesn't support abstract types, and even if it worked it would
|
11
|
+
# have limited value but high fetch cost. The work-around, which is also the more natural
|
12
|
+
# mechanism, is to query on a concrete SCG subclass template which references the target Site.
|
13
|
+
remove_attribute(:abstract_specimen_collection_groups)
|
14
|
+
|
15
|
+
set_secondary_key_attributes(:name)
|
16
|
+
|
17
|
+
add_attribute_defaults(:activity_status => 'Active', :site_type => 'Not Specified')
|
18
|
+
|
19
|
+
add_mandatory_attributes(:activity_status, :address, :coordinator, :site_type)
|
20
|
+
|
21
|
+
add_dependent_attribute(:address)
|
22
|
+
|
23
|
+
# The site_type value constants.
|
24
|
+
class SiteType
|
25
|
+
COLLECTION = 'Collection Site'
|
26
|
+
LABORATORY = 'Laboratory'
|
27
|
+
REPOSITORY = 'Repository'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,456 @@
|
|
1
|
+
require 'caruby/util/inflector'
|
2
|
+
require 'caruby/util/uniquifier'
|
3
|
+
require 'caruby/util/validation'
|
4
|
+
require 'catissue/util/storable'
|
5
|
+
|
6
|
+
module CaTissue
|
7
|
+
# import the Java class
|
8
|
+
java_import('edu.wustl.catissuecore.domain.Specimen')
|
9
|
+
|
10
|
+
# The Specimen domain class.
|
11
|
+
class Specimen
|
12
|
+
include Validation, Storable, Resource
|
13
|
+
|
14
|
+
# caTissue alert - Bug #64: Some domain collection properties not initialized.
|
15
|
+
# Initialize consent_tier_statuses if necessary.
|
16
|
+
#
|
17
|
+
# @return [Java::JavaUtil::Set] the statuses
|
18
|
+
def consent_tier_statuses
|
19
|
+
getConsentTierStatusCollection or (self.consent_tier_statuses = Java::JavaUtil::LinkedHashSet.new)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the barcode to the given value. This method converts an Integer to a String.
|
23
|
+
def barcode=(value)
|
24
|
+
value = value.to_s if Integer === value
|
25
|
+
setBarcode(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
add_attribute_aliases(:requirement => :specimen_requirement, :position => :specimen_position)
|
29
|
+
|
30
|
+
add_attribute_defaults(:activity_status => 'Active', :collection_status => 'Collected')
|
31
|
+
|
32
|
+
add_mandatory_attributes(:initial_quantity, :available_quantity)
|
33
|
+
|
34
|
+
set_secondary_key_attributes(:label)
|
35
|
+
|
36
|
+
# The Specimen-SpecimenRequirement association is bi-directional.
|
37
|
+
set_attribute_inverse(:specimen_requirement, :specimens)
|
38
|
+
|
39
|
+
# Specimen children are constrained to Specimen.
|
40
|
+
set_attribute_type(:child_specimens, Specimen)
|
41
|
+
|
42
|
+
# Specimen parent is constrained to Specimen.
|
43
|
+
set_attribute_type(:parent_specimen, Specimen)
|
44
|
+
|
45
|
+
# Even though the inverse is declared in AbstractSpecimen, do so again here to
|
46
|
+
# ensure that there is no order dependency of the dependent declaration below
|
47
|
+
# on AbstractSpecimen metadata initialization.
|
48
|
+
set_attribute_inverse(:parent_specimen, :child_specimens)
|
49
|
+
|
50
|
+
# A child Specimen is auto-generated from a SpecimenRequirement template if it
|
51
|
+
# is part of a hierarchy built by SCG create. Unlike SpecimenRequirement,
|
52
|
+
# Specimen children are cascaded.
|
53
|
+
add_dependent_attribute(:child_specimens, :autogenerated, :no_cascade_update_to_create)
|
54
|
+
|
55
|
+
# caTissue alert - Specimen consent_tier_statuses is cascaded but not fetched.
|
56
|
+
add_dependent_attribute(:consent_tier_statuses, :unfetched)
|
57
|
+
|
58
|
+
# caTissue alert - Bug #163: ExternalIdentifer not fetched with Specimen in API.
|
59
|
+
# Although the caTissue 1.1.2 Hibernate config specifies that external_identifiers
|
60
|
+
# are cascaded and fetched with the owner Specimen, they are not fetched with
|
61
|
+
# the owner Specimen. Work-around is to marked them as a unfetched.
|
62
|
+
#
|
63
|
+
# caTissue has complicated undocumented logic for updating external identifiers
|
64
|
+
# in NewSpecimenBizLogic.setExternalIdentifier
|
65
|
+
add_dependent_attribute(:external_identifiers, :unfetched)
|
66
|
+
|
67
|
+
# caTissue alert - Specimen position update is cascaded in Hibernate, but updateObject
|
68
|
+
# is precluded by the caTissue business logic. Position change is performed by a
|
69
|
+
# TransferEventParameters proxy instead. SpecimenPosition work-around is to
|
70
|
+
# designate a save proxy.
|
71
|
+
add_dependent_attribute(:specimen_position)
|
72
|
+
|
73
|
+
# Although label is the key, it is auto-generated if not provided in the create.
|
74
|
+
qualify_attribute(:label, :optional)
|
75
|
+
|
76
|
+
# The available flag is set on the server.
|
77
|
+
qualify_attribute(:is_available, :volatile)
|
78
|
+
|
79
|
+
# Oddly, the seldom-used biohazards are fetched along with Specimen.
|
80
|
+
qualify_attribute(:biohazards, :fetched)
|
81
|
+
|
82
|
+
# caTissue alert - the Specimen parent_changed flag is ignored in a caCORE update
|
83
|
+
# or create Specimen argument and is set on the server. Mark the attribute as unsaved.
|
84
|
+
qualify_attribute(:parent_changed, :unsaved)
|
85
|
+
|
86
|
+
# caTissue alert - Bug #159: Update pending Specimen ignores availableQuantity.
|
87
|
+
# available_quantity is not reflected in the caCORE create or update result.
|
88
|
+
# This is true even though the caTissue GUI supports available_quantity update.
|
89
|
+
# Work-around is to set the :autogenerated flag, which will refetch a saved Specimen
|
90
|
+
# and reupdate the Specimen if the stored available_quantity differs from the save
|
91
|
+
# argument.
|
92
|
+
qualify_attribute(:available_quantity, :autogenerated)
|
93
|
+
|
94
|
+
MERGEABLE_RQMT_ATTRS = nondomain_java_attributes - primary_key_attributes
|
95
|
+
|
96
|
+
MERGEABLE_SPC_CHR_ATTRS = SpecimenCharacteristics.nondomain_java_attributes - SpecimenCharacteristics.primary_key_attributes
|
97
|
+
|
98
|
+
# Specimen storage is constrained on the basis of the +specimen_class+.
|
99
|
+
alias :storable_type :specimen_class
|
100
|
+
|
101
|
+
def initialize(params=nil)
|
102
|
+
super
|
103
|
+
# work around caTissue Bug #64
|
104
|
+
self.consent_tier_statuses ||= Java::JavaUtil::LinkedHashSet.new
|
105
|
+
end
|
106
|
+
|
107
|
+
# Overrides {Resource#owner} to return the parent_specimen, if it exists, or the specimen_collection_group otherwise.
|
108
|
+
def owner
|
109
|
+
parent_specimen or specimen_collection_group
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [Boolean] whether this Specimen collection status is +Pending+
|
113
|
+
def pending?
|
114
|
+
collection_status == 'Pending'
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Boolean] whether this Specimen collection status is +Collected+
|
118
|
+
def collected?
|
119
|
+
collection_status == 'Collected'
|
120
|
+
end
|
121
|
+
|
122
|
+
def merge_attribute_value(attribute, oldval, newval)
|
123
|
+
# caTissue alert - remove the autogenerated blank ExternalIdentifier.
|
124
|
+
# @see https://cabig-kc.nci.nih.gov/Biospecimen/forums/viewtopic.php?f=19&t=436&sid=ef98f502fc0ab242781b7759a0eaff36
|
125
|
+
# @see CaTissue::Database#query_safe
|
126
|
+
if attribute == :external_identifiers and newval then
|
127
|
+
CaTissue::Specimen.remove_empty_external_identifier(newval)
|
128
|
+
end
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
# # Restores this disposed Specimen by deleting the DisposalEventParameters and resetting the availability and activity status.
|
133
|
+
# # Returns the deleted DisposalEventParameters, or nil if none.
|
134
|
+
# def recover
|
135
|
+
# # TODO - test this
|
136
|
+
# dep = event_parameters.detect { |ep| CaTissue::DisposalEventParameters === ep }
|
137
|
+
# return if dep.nil?
|
138
|
+
# dep.delete
|
139
|
+
# self.available = true
|
140
|
+
# self.activity_status = 'Active'
|
141
|
+
# update
|
142
|
+
# end
|
143
|
+
|
144
|
+
# Override default {CaRuby::Resource#merge_attributes} to ignore a source SpecimenRequirement parent_specimen.
|
145
|
+
def merge_attributes(other, attributes=nil)
|
146
|
+
case other
|
147
|
+
when SpecimenRequirement then
|
148
|
+
# merge with the default requirement merge attributes if necessary
|
149
|
+
attributes ||= MERGEABLE_RQMT_ATTRS
|
150
|
+
super(other, attributes)
|
151
|
+
# copy the requirement characteristics
|
152
|
+
sc = other.specimen_characteristics
|
153
|
+
self.specimen_characteristics ||= sc.copy(MERGEABLE_SPC_CHR_ATTRS) if sc
|
154
|
+
when Hash then
|
155
|
+
# the requirement template
|
156
|
+
rqmt = other[:specimen_requirement] || other[:requirement]
|
157
|
+
# merge the attribute => value hash
|
158
|
+
super
|
159
|
+
# merge the SpecimenRequirement after the hash
|
160
|
+
merge_attributes(rqmt) if rqmt
|
161
|
+
else
|
162
|
+
super
|
163
|
+
end
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
# Raises a ValidationError when one the following conditions holds:
|
168
|
+
# * a top-level Specimen does not have a SGC
|
169
|
+
# * the available_quantity exceeds the initial_quantity
|
170
|
+
# * the availability flag is set and the available_quantity is zero
|
171
|
+
#
|
172
|
+
# caTissue alert - Bug #160: Missing Is Available? validation.
|
173
|
+
# Updating Specimen with the availablity flag set and available_quantity zero
|
174
|
+
# silently leaves the availablity flag unset.
|
175
|
+
def validate
|
176
|
+
super
|
177
|
+
if parent.nil? and specimen_collection_group.nil? then
|
178
|
+
raise ValidationError.new("Top-level specimen #{self} is missing specimen collection group")
|
179
|
+
end
|
180
|
+
if available_quantity and initial_quantity and available_quantity > initial_quantity then
|
181
|
+
raise ValidationError.new("#{self} available quantity #{available_quantity} cannot exceed initial quantity #{initial_quantity}")
|
182
|
+
end
|
183
|
+
if available? and available_quantity.zero? then
|
184
|
+
raise ValidationError.new("#{self} availablility flag cannot be set when the avaialble quantity is zero.")
|
185
|
+
end
|
186
|
+
if collected? then
|
187
|
+
unless event_parameters.detect { |ep| CaTissue::CollectionEventParameters === ep } then
|
188
|
+
raise ValidationError.new("#{self} is missing CollectionEventParameters.")
|
189
|
+
end
|
190
|
+
unless event_parameters.detect { |ep| CaTissue::ReceivedEventParameters === ep } then
|
191
|
+
raise ValidationError.new("#{self} is missing ReceivedEventParameters.")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns the Specimen in others which matches this Specimen in the scope of an owner SCG.
|
197
|
+
# This method relaxes {CaRuby::Resource#match_in_owner_scope} to include a match on at least one external identifier.
|
198
|
+
def match_in_owner_scope(others)
|
199
|
+
super or others.detect do |other|
|
200
|
+
external_identifiers.any? do |eid|
|
201
|
+
other.external_identifiers.detect { |oeid| eid.name == oeid.name and eid.value == oeid.value }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# @return the SpecimenPosition class which this Specimen's Storable can occupy
|
207
|
+
def position_class
|
208
|
+
CaTissue::SpecimenPosition
|
209
|
+
end
|
210
|
+
|
211
|
+
# @return this Specimen +position+ Location
|
212
|
+
def location
|
213
|
+
position.location if position
|
214
|
+
end
|
215
|
+
|
216
|
+
# Creates a new Specimen or CaTissue::SpecimenRequirement from the given symbol => value params hash.
|
217
|
+
#
|
218
|
+
# The default class is inferred from the _class_ parameter, if given, or inherited
|
219
|
+
# from this parent specimen otherwise. The inferred class is the camel-case parameter value
|
220
|
+
# with +Specimen+ appended, e.g. :tissue => +TissueSpecimen+. This class name is resolved to
|
221
|
+
# a class in the CaTissue module context.
|
222
|
+
#
|
223
|
+
# The supported :type parameter value includes the permissible caTissue specimen type String
|
224
|
+
# values as well as the shortcut tissue type symbols :fresh, :fixed and :frozen.
|
225
|
+
#
|
226
|
+
# If a SpecimenRequirement parameter is provided, then that SpecimenRequirement's attribute
|
227
|
+
# values are merged into the new Specimen after the other parameters are merged. Thus, params
|
228
|
+
# takes precedence over the SpecimenRequirement.
|
229
|
+
#
|
230
|
+
# If the :count parameter is set to a number greater than one, then the specimen is aliquoted
|
231
|
+
# into the specified number of samples.
|
232
|
+
#
|
233
|
+
# This method is a convenience method to create either a Specimen or CaTissue::SpecimenRequirement.
|
234
|
+
# Although CaTissue::SpecimenRequirement is a direct CaTissue::AbstractSpecimen subclass rather than
|
235
|
+
# a Specimen subclass, the create functionality overlaps and Specimen is the friendlier
|
236
|
+
# class to define this utility method as opposed to the more obscure CaTissue::AbstractSpecimen.
|
237
|
+
#
|
238
|
+
# @param [<{Symbol => Object}>] params the create parameter hash. Besides the listed params options,
|
239
|
+
# this hash can include additional target Specimen attribute => value entries for any supported
|
240
|
+
# Specimen Java property attribute
|
241
|
+
# @option params [Symbol, String, Class] :class the required target specimen class,
|
242
|
+
# e.g. :molecular, +TissueSpecimen+ or CaTissue::TissueSpecimen
|
243
|
+
# @option params [Symbol, String, Class] :type the optional target specimen type symbol or string,
|
244
|
+
# e.g. :frozen or +Frozen Tissue Block+
|
245
|
+
# @option params [Numeric] :quantity the optional target specimen intial quantity
|
246
|
+
# @option params [CaTissue::SpecimenRequirement] :requirement the optional requirement with additional
|
247
|
+
# target attribute values
|
248
|
+
# @raise [ArgumentError] if the specimen class option is not recognized
|
249
|
+
def self.create_specimen(params)
|
250
|
+
raise ArgumentError.new("Specimen create params argument type unsupported: #{params.class}") unless Hash === params
|
251
|
+
# standardize the class, type and quantity params
|
252
|
+
spc_cls = params.delete(:class)
|
253
|
+
params[:specimen_class] ||= spc_cls if spc_cls
|
254
|
+
spc_type = params.delete(:type)
|
255
|
+
params[:specimen_type] ||= spc_type if spc_type
|
256
|
+
qty = params.delete(:quantity)
|
257
|
+
params[:initial_quantity] ||= qty if qty
|
258
|
+
|
259
|
+
# the specimen_class as a Class, Symbol or String
|
260
|
+
cls_opt = params[:specimen_class]
|
261
|
+
# standardize the specimen_class parameter as a permissible caTissue value
|
262
|
+
standardize_class_parameter(params)
|
263
|
+
# if the specimen_class was not specified as a Class, then infer the specimen domain class from the
|
264
|
+
# parameter prefix and Specimen suffix
|
265
|
+
if Class === cls_opt then
|
266
|
+
klass = cls_opt
|
267
|
+
else
|
268
|
+
class_name = params[:specimen_class] + 'Specimen'
|
269
|
+
klass = CaTissue.domain_type_with_name(class_name)
|
270
|
+
raise ArgumentError.new("Specimen class #{class_name} is not recognized for parameter #{cls_opt}") if klass.nil?
|
271
|
+
end
|
272
|
+
|
273
|
+
# add a default available quantity to a Specimen but not a SpecimenRequirement
|
274
|
+
params[:available_quantity] ||= params[:initial_quantity] if klass <= self
|
275
|
+
|
276
|
+
# make the specimen
|
277
|
+
klass.new(params)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Convenience method which returns the SCG collection protocol.
|
281
|
+
#
|
282
|
+
# @return [CaTissue::CollectionProtocol] the SCG collection protocol
|
283
|
+
def collection_protocol
|
284
|
+
specimen_collection_group.collection_protocol
|
285
|
+
end
|
286
|
+
|
287
|
+
# Withdraws consent for this Specimen.
|
288
|
+
#
|
289
|
+
# _Experimental_. TODO - test this method.
|
290
|
+
#
|
291
|
+
# If a consent_tier is provided, then the SCG CaTissue::ConsentTierStatus with this consent tier is withdrawn.
|
292
|
+
# Otherwise, if there is a single SCG CaTissue::ConsentTierStatus, then that consent tier is withdrawn.
|
293
|
+
# Otherwise an exception is thrown.
|
294
|
+
#
|
295
|
+
# @param [CaTissue::ConsentTier, nil] optional consent tier of the SCG CaTissue::ConsentTierStatus to withdraw
|
296
|
+
# @raise [ValidationError] if an unambiguous SCG CaTissue::ConsentTierStatus to withdraw could not be determined
|
297
|
+
def withdraw_consent(consent_tier=nil)
|
298
|
+
statuses = specimen_collection_group.consent_tier_statuses
|
299
|
+
status = if consent_tier then
|
300
|
+
statuses.detect { |cts| cts.consent_tier.identifier == consent_tier.identifier } or
|
301
|
+
raise ValidationError.new("SCG #{specimen_collection_group} consent status not found for consent '#{consent_tier.statement}'")
|
302
|
+
elsif specimen_collection_group.consent_tier_statuses.size == 1 then
|
303
|
+
statuses.first
|
304
|
+
elsif specimen_collection_group.consent_tier_statuses.size == 0 then
|
305
|
+
raise ValidationError.new("Specimen #{self} SCG does not have a consent tier status.")
|
306
|
+
else
|
307
|
+
raise ValidationError.new("Specimen #{self} SCG consent tier is ambiguous:#{consent_tier_statuses.select { |cts| "\n #{cts.statement}" }.to_series('or')}.")
|
308
|
+
end
|
309
|
+
ct = status.consent_tier
|
310
|
+
cts = consent_tier_statuses.detect { |item| item.consent_tier == ct }
|
311
|
+
consent_tier_statuses << cts = ConsentTierStatus.new(:consent_tier => ct) if cts.nil?
|
312
|
+
cts.status = 'Withdrawn'
|
313
|
+
end
|
314
|
+
|
315
|
+
# Permanently dispose of this specimen by creating a CaTissue::DisposalEventParameters with
|
316
|
+
# status 'Closed' and the optional reason.
|
317
|
+
def dispose(reason=nil)
|
318
|
+
CaTissue::DisposalEventParameters.new(:specimen => self, :reason => reason)
|
319
|
+
end
|
320
|
+
|
321
|
+
protected
|
322
|
+
|
323
|
+
def self.remove_empty_external_identifier(eids)
|
324
|
+
bogus = eids.detect { |eid| eid.name.nil? }
|
325
|
+
if bogus then
|
326
|
+
logger.debug { "Work around caTissue bug by removing empty fetched #{bogus.specimen.qp} #{bogus.qp} from #{eids.qp}..." }
|
327
|
+
# dissociate the specimen
|
328
|
+
bogus.specimen = nil
|
329
|
+
# remove the bogus eid
|
330
|
+
eids.delete(bogus)
|
331
|
+
end
|
332
|
+
eids
|
333
|
+
end
|
334
|
+
|
335
|
+
private
|
336
|
+
|
337
|
+
# Adds this Specimen's defaults, as follows:
|
338
|
+
# * The default specimen_collection_group is the parent specimen_collection_group.
|
339
|
+
# * Add default collection and received event parameters if this Specimen is collected.
|
340
|
+
# * If the is_available flag is set to false then the default available quantity is
|
341
|
+
# zero, otherwise the default available quantity is the initial quantity.
|
342
|
+
# * The default is_available flag is true if the available quantity is greater than zero.
|
343
|
+
#
|
344
|
+
# The motivation for the is_available flag default is that an initial quantity of zero can indicate
|
345
|
+
# unknown amount as well a known zero amount, and therefore the available flag should be set to
|
346
|
+
# false only if it is known that there was an amount but that amount is exhausted.
|
347
|
+
#
|
348
|
+
# caTissue alert - initial_quantity cannot be null (cf. Bug #160).
|
349
|
+
#
|
350
|
+
# caTissue alert - the default available status must be left nil rather than set to false, since
|
351
|
+
# caTissue allows a nil available status on insert but not a false value, even though a nil status
|
352
|
+
# is set to false (0 database value) when the record is inserted.
|
353
|
+
#
|
354
|
+
# caTissue alert - a collected Specimen without a collection and received event parameters
|
355
|
+
# results in the dreaded 'Severe Error' server message. TODO - isolate and report.
|
356
|
+
def add_defaults_local
|
357
|
+
super
|
358
|
+
self.specimen_collection_group ||= parent.specimen_collection_group if parent
|
359
|
+
add_default_event_parameters
|
360
|
+
|
361
|
+
# The default available quantity is 0 if the is_available flag is false,
|
362
|
+
# otherwise the initial quantity.
|
363
|
+
self.available_quantity ||= is_available == false ? 0.0 : initial_quantity
|
364
|
+
|
365
|
+
# The specimen is available by default if there is a positive available quantity.
|
366
|
+
# If is_available is set to false, then set it to nil to work around a caTissue bug.
|
367
|
+
self.is_available ||= available_quantity.zero? ? nil : true
|
368
|
+
end
|
369
|
+
|
370
|
+
# Adds the default collection and received event parameters if the collection status
|
371
|
+
# is +Collected+.
|
372
|
+
def add_default_event_parameters
|
373
|
+
if collection_status == 'Collected' and specimen_collection_group then
|
374
|
+
unless event_parameters.detect { |ep| CaTissue::CollectionEventParameters === ep } then
|
375
|
+
CaTissue::CollectionEventParameters.new(:specimen => self, :user => specimen_collection_group.collector)
|
376
|
+
end
|
377
|
+
unless event_parameters.detect { |ep| CaTissue::ReceivedEventParameters === ep } then
|
378
|
+
CaTissue::ReceivedEventParameters.new(:specimen => self, :user => specimen_collection_group.receiver)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Sets the :specimen_class parameter to a permissible caTissue value.
|
384
|
+
def self.standardize_class_parameter(params)
|
385
|
+
opt = params[:specimen_class]
|
386
|
+
if opt.nil? then
|
387
|
+
rqmt = params[:specimen_requirement] || params[:requirement]
|
388
|
+
if rqmt then
|
389
|
+
rqmt.add_defaults unless rqmt.specimen_class
|
390
|
+
opt = rqmt.specimen_class
|
391
|
+
end
|
392
|
+
end
|
393
|
+
raise ArgumentError.new("Specimen class is missing from the create parameters") if opt.nil?
|
394
|
+
# Convert the class option Symbol to a capitalized String without a class path prefix or the
|
395
|
+
# Specimen[Requirement] suffix.
|
396
|
+
params[:specimen_class] = opt.to_s[/(\w+?)(Specimen(Requirement)?)?$/, 1].capitalize_first
|
397
|
+
end
|
398
|
+
|
399
|
+
def set_aliquot_parameters(params, count)
|
400
|
+
super
|
401
|
+
# default available quantity
|
402
|
+
self.available_quantity ||= initial_quantity
|
403
|
+
# apportion the parent quantity
|
404
|
+
params[:initial_quantity] ||= available_quantity / count
|
405
|
+
end
|
406
|
+
|
407
|
+
# Delegate to {AbstractSpecimen#create_derived} and add a default label if necessary. The default label
|
408
|
+
# is this Specimen label appended with an underscore and the number of children, e.g. +TB-0023434_1+
|
409
|
+
# for the first child of a parent with label +TB-0023434+.
|
410
|
+
def create_derived(params)
|
411
|
+
spc = super
|
412
|
+
spc.label ||= "#{label}_#{children.size}" if label
|
413
|
+
spc.specimen_collection_group = specimen_collection_group
|
414
|
+
# if the derived specimen is the same type as this parent specimen,
|
415
|
+
# then decrement this parent's quantity by the derived specimen amount
|
416
|
+
decrement_derived_quantity(spc) if specimen_type == spc.specimen_type
|
417
|
+
spc
|
418
|
+
end
|
419
|
+
|
420
|
+
def self.specimen_class_symbol_to_class(symbol)
|
421
|
+
name = symbol.to_s
|
422
|
+
class_name = name[0, 1].upcase + name[1..-1]
|
423
|
+
suffix = 'Specimen'
|
424
|
+
class_name << suffix unless class_name.rindex(suffix) == class_name.length - suffix.length
|
425
|
+
CaTissue.domain_type_with_name(class_name) or raise ArgumentError.new("Specimen class #{class_name} is not recognized for specimen type parameter #{symbol}")
|
426
|
+
end
|
427
|
+
|
428
|
+
# def default_specimen_characteristics
|
429
|
+
# return if specimen_collection_group.nil?
|
430
|
+
# rqmts = specimen_collection_group.requirements
|
431
|
+
# # match the requirement specimen type
|
432
|
+
# rqmt = specimen_collection_group.requirements.detect { |rqmt| specimen_type == rqmt.specimen_type }
|
433
|
+
# # fallback is match on requirement class and generic requirment specimen type
|
434
|
+
# rqmt ||= specimen_collection_group.requirements.detect do |rqmt|
|
435
|
+
# specimen_class === rqmt.specimen_class and rqmt.specimen_type.nil? or rqmt.specimen_type == 'Not Specified'
|
436
|
+
# end
|
437
|
+
# rqmt.specimen_characteristics.copy if rqmt
|
438
|
+
# end
|
439
|
+
|
440
|
+
# Decrements this parent's available quantity by the given child's initial quantity, if the specimen types are the same and there
|
441
|
+
# are the relevant quantities.
|
442
|
+
def decrement_derived_quantity(child)
|
443
|
+
return unless specimen_type == child.specimen_type and child.initial_quantity
|
444
|
+
if available_quantity.nil? then
|
445
|
+
raise ValidationError.new("Derived specimen has an initial quantity #{child.initial_quantity} but the parent is missing an available quantity")
|
446
|
+
elsif (available_quantity - child.initial_quantity).abs < 0.00000001 then
|
447
|
+
# rounding error
|
448
|
+
self.available_quantity = 0.0
|
449
|
+
elsif child.initial_quantity <= available_quantity then
|
450
|
+
self.available_quantity -= child.initial_quantity
|
451
|
+
else
|
452
|
+
raise ValidationError.new("Derived specimen initial quantity #{child.initial_quantity} exceeds parent available quantity #{available_quantity}")
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|