cqm-models 2.0.0 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.eslintrc.json +1 -1
- data/.github/workflows/gitleaks_github_actions.yml +28 -0
- data/.travis.yml +2 -2
- data/README.md +12 -9
- data/app/assets/javascripts/AdverseEvent.js +9 -3
- data/app/assets/javascripts/AllDataElements.js +18 -6
- data/app/assets/javascripts/AllergyIntolerance.js +8 -2
- data/app/assets/javascripts/AssessmentOrder.js +8 -2
- data/app/assets/javascripts/AssessmentPerformed.js +11 -3
- data/app/assets/javascripts/AssessmentRecommended.js +8 -2
- data/app/assets/javascripts/CareGoal.js +10 -3
- data/app/assets/javascripts/CommunicationPerformed.js +12 -6
- data/app/assets/javascripts/DeviceApplied.js +9 -2
- data/app/assets/javascripts/DeviceOrder.js +8 -2
- data/app/assets/javascripts/DeviceRecommended.js +8 -2
- data/app/assets/javascripts/Diagnosis.js +8 -2
- data/app/assets/javascripts/DiagnosticStudyOrder.js +8 -2
- data/app/assets/javascripts/DiagnosticStudyPerformed.js +9 -2
- data/app/assets/javascripts/DiagnosticStudyRecommended.js +8 -2
- data/app/assets/javascripts/EncounterOrder.js +9 -2
- data/app/assets/javascripts/EncounterPerformed.js +11 -5
- data/app/assets/javascripts/EncounterRecommended.js +8 -2
- data/app/assets/javascripts/FamilyHistory.js +8 -2
- data/app/assets/javascripts/ImmunizationAdministered.js +9 -2
- data/app/assets/javascripts/ImmunizationOrder.js +8 -2
- data/app/assets/javascripts/InterventionOrder.js +8 -2
- data/app/assets/javascripts/InterventionPerformed.js +9 -2
- data/app/assets/javascripts/InterventionRecommended.js +8 -2
- data/app/assets/javascripts/LaboratoryTestOrder.js +8 -2
- data/app/assets/javascripts/LaboratoryTestPerformed.js +9 -2
- data/app/assets/javascripts/LaboratoryTestRecommended.js +8 -2
- data/app/assets/javascripts/MedicationActive.js +9 -2
- data/app/assets/javascripts/MedicationAdministered.js +9 -2
- data/app/assets/javascripts/MedicationDischarge.js +9 -2
- data/app/assets/javascripts/MedicationDispensed.js +10 -4
- data/app/assets/javascripts/MedicationOrder.js +9 -4
- data/app/assets/javascripts/Participation.js +8 -2
- data/app/assets/javascripts/PatientCareExperience.js +8 -2
- data/app/assets/javascripts/PatientCharacteristic.js +7 -2
- data/app/assets/javascripts/PatientCharacteristicBirthdate.js +7 -2
- data/app/assets/javascripts/PatientCharacteristicClinicalTrialParticipant.js +7 -2
- data/app/assets/javascripts/PatientCharacteristicEthnicity.js +7 -2
- data/app/assets/javascripts/PatientCharacteristicExpired.js +7 -2
- data/app/assets/javascripts/PatientCharacteristicPayer.js +7 -2
- data/app/assets/javascripts/PatientCharacteristicRace.js +7 -2
- data/app/assets/javascripts/PatientCharacteristicSex.js +7 -2
- data/app/assets/javascripts/PhysicalExamOrder.js +8 -2
- data/app/assets/javascripts/PhysicalExamPerformed.js +9 -2
- data/app/assets/javascripts/PhysicalExamRecommended.js +8 -2
- data/app/assets/javascripts/ProcedureOrder.js +10 -3
- data/app/assets/javascripts/ProcedurePerformed.js +11 -3
- data/app/assets/javascripts/ProcedureRecommended.js +9 -3
- data/app/assets/javascripts/ProviderCareExperience.js +8 -2
- data/app/assets/javascripts/QDMPatient.js +25 -9
- data/app/assets/javascripts/RelatedPerson.js +42 -0
- data/app/assets/javascripts/SubstanceAdministered.js +9 -2
- data/app/assets/javascripts/SubstanceOrder.js +9 -2
- data/app/assets/javascripts/SubstanceRecommended.js +8 -2
- data/app/assets/javascripts/Symptom.js +8 -2
- data/app/assets/javascripts/attributes/CarePartner.js +33 -0
- data/app/assets/javascripts/attributes/Component.js +22 -1
- data/app/assets/javascripts/attributes/DiagnosisComponent.js +34 -0
- data/app/assets/javascripts/attributes/Entity.js +58 -0
- data/app/assets/javascripts/attributes/FacilityLocation.js +4 -1
- data/app/assets/javascripts/attributes/Identifier.js +23 -0
- data/app/assets/javascripts/attributes/Organization.js +33 -0
- data/app/assets/javascripts/attributes/PatientEntity.js +32 -0
- data/app/assets/javascripts/attributes/Practitioner.js +35 -0
- data/app/assets/javascripts/attributes/ResultComponent.js +31 -0
- data/app/assets/javascripts/basetypes/Any.js +24 -14
- data/app/assets/javascripts/basetypes/AnyEntity.js +44 -0
- data/app/assets/javascripts/basetypes/Code.js +8 -11
- data/app/assets/javascripts/basetypes/DataElement.js +17 -6
- data/app/assets/javascripts/basetypes/Interval.js +2 -2
- data/app/assets/javascripts/basetypes/QDMDate.js +36 -0
- data/app/assets/javascripts/basetypes/Quantity.js +1 -1
- data/app/assets/javascripts/basetypes/Ratio.js +1 -1
- data/app/assets/javascripts/cqm/Provider.js +0 -2
- data/app/models/cqm/individual_result.rb +2 -6
- data/app/models/cqm/provider.rb +4 -4
- data/app/models/hqmfOid_to_datatype_map.json +5 -1
- data/app/models/model_finder.rb +1 -0
- data/app/models/models.rb +12 -5
- data/app/models/qdm/adverse_event.rb +3 -2
- data/app/models/qdm/allergy_intolerance.rb +2 -1
- data/app/models/qdm/assessment_order.rb +2 -1
- data/app/models/qdm/assessment_performed.rb +5 -2
- data/app/models/qdm/assessment_recommended.rb +2 -1
- data/app/models/qdm/attributes/attribute.rb +1 -0
- data/app/models/qdm/attributes/care_partner.rb +10 -0
- data/app/models/qdm/attributes/component.rb +1 -1
- data/app/models/qdm/attributes/diagnosis_component.rb +10 -0
- data/app/models/qdm/attributes/entity.rb +15 -0
- data/app/models/qdm/attributes/facility_location.rb +1 -1
- data/app/models/qdm/{id.rb → attributes/identifier.rb} +3 -4
- data/app/models/qdm/attributes/organization.rb +10 -0
- data/app/models/qdm/attributes/patient_entity.rb +9 -0
- data/app/models/qdm/attributes/practitioner.rb +12 -0
- data/app/models/qdm/{result_component.rb → attributes/result_component.rb} +1 -3
- data/app/models/qdm/basetypes/code.rb +9 -10
- data/app/models/qdm/basetypes/data_element.rb +25 -18
- data/app/models/qdm/basetypes/date.rb +39 -0
- data/app/models/qdm/basetypes/interval.rb +8 -11
- data/app/models/qdm/basetypes/quantity.rb +1 -0
- data/app/models/qdm/basetypes/ratio.rb +1 -0
- data/app/models/qdm/care_goal.rb +4 -2
- data/app/models/qdm/communication_performed.rb +6 -5
- data/app/models/qdm/device_applied.rb +3 -1
- data/app/models/qdm/device_order.rb +2 -1
- data/app/models/qdm/device_recommended.rb +2 -1
- data/app/models/qdm/diagnosis.rb +2 -1
- data/app/models/qdm/diagnostic_study_order.rb +2 -1
- data/app/models/qdm/diagnostic_study_performed.rb +3 -1
- data/app/models/qdm/diagnostic_study_recommended.rb +2 -1
- data/app/models/qdm/encounter_order.rb +3 -1
- data/app/models/qdm/encounter_performed.rb +3 -2
- data/app/models/qdm/encounter_recommended.rb +2 -1
- data/app/models/qdm/family_history.rb +2 -1
- data/app/models/qdm/immunization_administered.rb +3 -1
- data/app/models/qdm/immunization_order.rb +2 -1
- data/app/models/qdm/intervention_order.rb +2 -1
- data/app/models/qdm/intervention_performed.rb +3 -1
- data/app/models/qdm/intervention_recommended.rb +2 -1
- data/app/models/qdm/laboratory_test_order.rb +2 -1
- data/app/models/qdm/laboratory_test_performed.rb +3 -1
- data/app/models/qdm/laboratory_test_recommended.rb +2 -1
- data/app/models/qdm/medication_active.rb +3 -1
- data/app/models/qdm/medication_administered.rb +3 -1
- data/app/models/qdm/medication_discharge.rb +3 -1
- data/app/models/qdm/medication_dispensed.rb +4 -3
- data/app/models/qdm/medication_order.rb +3 -3
- data/app/models/qdm/participation.rb +2 -1
- data/app/models/qdm/patient.rb +1 -1
- data/app/models/qdm/patient_care_experience.rb +2 -1
- data/app/models/qdm/patient_characteristic.rb +1 -1
- data/app/models/qdm/patient_characteristic_birthdate.rb +1 -1
- data/app/models/qdm/patient_characteristic_clinical_trial_participant.rb +1 -1
- data/app/models/qdm/patient_characteristic_ethnicity.rb +1 -1
- data/app/models/qdm/patient_characteristic_expired.rb +1 -1
- data/app/models/qdm/patient_characteristic_payer.rb +1 -1
- data/app/models/qdm/patient_characteristic_race.rb +1 -1
- data/app/models/qdm/patient_characteristic_sex.rb +1 -1
- data/app/models/qdm/physical_exam_order.rb +2 -1
- data/app/models/qdm/physical_exam_performed.rb +3 -1
- data/app/models/qdm/physical_exam_recommended.rb +2 -1
- data/app/models/qdm/procedure_order.rb +4 -2
- data/app/models/qdm/procedure_performed.rb +5 -2
- data/app/models/qdm/procedure_recommended.rb +3 -2
- data/app/models/qdm/provider_care_experience.rb +2 -1
- data/app/models/qdm/related_person.rb +13 -0
- data/app/models/qdm/substance_administered.rb +3 -1
- data/app/models/qdm/substance_order.rb +3 -1
- data/app/models/qdm/substance_recommended.rb +2 -1
- data/app/models/qdm/symptom.rb +2 -1
- data/bin/validate_generator.sh +1 -1
- data/cqm-models.gemspec +5 -5
- data/data/oids_qdm_5.5.json +337 -0
- data/dist/browser.js +12898 -6908
- data/dist/index.js +12897 -6907
- data/lib/generate_entities.rb +56 -0
- data/lib/generate_models.rb +77 -39
- data/lib/generate_patients.rb +20 -14
- data/lib/generate_types.rb +54 -14
- data/lib/generator_helpers.rb +14 -0
- data/modelinfo/qdm-modelinfo-5.5.xml +982 -0
- data/package.json +4 -4
- data/templates/entity_extension.rb.erb +6 -0
- data/templates/identifier_extension.rb.erb +58 -0
- data/templates/{id_template.js.erb → identifier_template.js.erb} +6 -5
- data/templates/models_template.rb.erb +19 -4
- data/templates/mongoose_template.js.erb +86 -12
- data/templates/patient_template.js.erb +0 -4
- data/yarn.lock +525 -392
- metadata +44 -22
- data/app/assets/javascripts/Id.js +0 -21
- data/app/assets/javascripts/ProviderCharacteristic.js +0 -36
- data/app/assets/javascripts/ResultComponent.js +0 -33
- data/app/models/qdm/provider_characteristic.rb +0 -12
@@ -0,0 +1,56 @@
|
|
1
|
+
require_relative '../app/models/models'
|
2
|
+
require('generate_types')
|
3
|
+
|
4
|
+
module QDM
|
5
|
+
# BaseTypeGeneration contains functions to randomly generate basetypes used by PatientGeneration
|
6
|
+
module EntityGeneration
|
7
|
+
def self.generate_entities(data_element)
|
8
|
+
data_element.performer = generate_entity if data_element.respond_to? 'performer'
|
9
|
+
data_element.recorder = generate_entity if data_element.respond_to? 'recorder'
|
10
|
+
data_element.requester = generate_entity if data_element.respond_to? 'requester'
|
11
|
+
data_element.sender = generate_entity if data_element.respond_to? 'sender'
|
12
|
+
data_element.recipient = generate_entity if data_element.respond_to? 'recipient'
|
13
|
+
data_element.participant = generate_entity if data_element.respond_to? 'participant'
|
14
|
+
data_element.prescriber = generate_entity if data_element.respond_to? 'prescriber'
|
15
|
+
data_element.dispenser = generate_entity if data_element.respond_to? 'dispenser'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.generate_entity
|
19
|
+
case Random.rand(4)
|
20
|
+
when 0 then return generate_patient_entity
|
21
|
+
when 1 then return generate_care_partner_entity
|
22
|
+
when 2 then return generate_organization_entity
|
23
|
+
when 3 then return generate_practitioner_entity
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.generate_patient_entity
|
28
|
+
patient_entity = QDM::PatientEntity.new
|
29
|
+
patient_entity.identifier = QDM::BaseTypeGeneration.generate_qdm_id
|
30
|
+
patient_entity
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.generate_care_partner_entity
|
34
|
+
care_partner_entity = QDM::CarePartner.new
|
35
|
+
care_partner_entity.identifier = QDM::BaseTypeGeneration.generate_qdm_id
|
36
|
+
care_partner_entity.relationship = QDM::BaseTypeGeneration.generate_code_field
|
37
|
+
care_partner_entity
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.generate_organization_entity
|
41
|
+
organization_entity = QDM::Organization.new
|
42
|
+
organization_entity.identifier = QDM::BaseTypeGeneration.generate_qdm_id
|
43
|
+
organization_entity.type = QDM::BaseTypeGeneration.generate_code_field
|
44
|
+
organization_entity
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.generate_practitioner_entity
|
48
|
+
practitioner_entity = QDM::Practitioner.new
|
49
|
+
practitioner_entity.identifier = QDM::BaseTypeGeneration.generate_qdm_id
|
50
|
+
practitioner_entity.role = QDM::BaseTypeGeneration.generate_code_field
|
51
|
+
practitioner_entity.specialty = QDM::BaseTypeGeneration.generate_code_field
|
52
|
+
practitioner_entity.qualification = QDM::BaseTypeGeneration.generate_code_field
|
53
|
+
practitioner_entity
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/generate_models.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
|
1
|
+
# TODO: May need to add Entity casting to Any if entities need to be handled
|
2
|
+
# TODO: HQMF OIDS - Specifically RelatedPerson
|
3
|
+
|
4
|
+
# ! /usr/bin/env ruby
|
2
5
|
require 'nokogiri'
|
3
6
|
require 'active_support/all'
|
4
7
|
require 'rails/generators'
|
5
8
|
require 'erb'
|
6
9
|
require 'json'
|
7
10
|
require_relative './generators/custom_mongo/model_generator'
|
11
|
+
require_relative './generator_helpers'
|
8
12
|
|
9
13
|
###############################################################################
|
10
14
|
# Helpers
|
@@ -13,10 +17,11 @@ require_relative './generators/custom_mongo/model_generator'
|
|
13
17
|
# Lookups for modelinfo 'element types' to Ruby+Mongoid types.
|
14
18
|
TYPE_LOOKUP_RB = {
|
15
19
|
'System.DateTime': 'DateTime',
|
20
|
+
'System.Date': 'Date',
|
16
21
|
'System.Integer': 'Integer',
|
17
22
|
'System.Quantity': 'Quantity',
|
18
23
|
'System.Code': 'Code',
|
19
|
-
'QDM.
|
24
|
+
'QDM.Identifier': 'Identifier',
|
20
25
|
'System.Any': 'Any',
|
21
26
|
'interval<System.DateTime>': 'Interval',
|
22
27
|
'interval<System.Quantity>': 'Interval',
|
@@ -25,6 +30,8 @@ TYPE_LOOKUP_RB = {
|
|
25
30
|
'list<QDM.Id>': 'Array',
|
26
31
|
'list<QDM.ResultComponent>': 'Array',
|
27
32
|
'list<QDM.FacilityLocation>': 'Array',
|
33
|
+
'list<QDM.DiagnosisComponent>': 'Array',
|
34
|
+
'list<System.String>': 'Array',
|
28
35
|
'list<System.Code>': 'Array',
|
29
36
|
'System.Decimal': 'Float',
|
30
37
|
'System.Time': 'Time',
|
@@ -34,11 +41,12 @@ TYPE_LOOKUP_RB = {
|
|
34
41
|
# Lookups for modelinfo 'element types' to JavaScript+Mongoose types.
|
35
42
|
TYPE_LOOKUP_JS = {
|
36
43
|
'System.DateTime': 'DateTime',
|
44
|
+
'System.Date': 'QDMDate',
|
37
45
|
'System.Integer': 'Number',
|
38
46
|
'System.Quantity': 'Quantity',
|
39
47
|
'System.Code': 'Code',
|
40
48
|
'System.Any': 'Any',
|
41
|
-
'QDM.
|
49
|
+
'QDM.Identifier': 'IdentifierSchema',
|
42
50
|
'interval<System.DateTime>': 'Interval',
|
43
51
|
'interval<System.Quantity>': 'Interval',
|
44
52
|
'list<QDM.Component>': '[]',
|
@@ -46,6 +54,8 @@ TYPE_LOOKUP_JS = {
|
|
46
54
|
'list<QDM.Id>': '[]',
|
47
55
|
'list<QDM.ResultComponent>': '[]',
|
48
56
|
'list<QDM.FacilityLocation>': '[]',
|
57
|
+
'list<QDM.DiagnosisComponent>': '[]',
|
58
|
+
'list<System.String>': '[]',
|
49
59
|
'list<System.Code>': '[Code]',
|
50
60
|
'System.Decimal': 'Number',
|
51
61
|
'System.Time': 'DateTime',
|
@@ -65,11 +75,13 @@ puts Dir.entries('.')
|
|
65
75
|
# Open specified modelinfo file
|
66
76
|
modelinfo_file = ARGV[0]
|
67
77
|
raise 'Please provide a valid modelinfo file path and name.' if modelinfo_file.blank? || !File.file?(modelinfo_file)
|
78
|
+
|
68
79
|
modelinfo = File.open(modelinfo_file) { |f| Nokogiri::XML(f) }
|
69
80
|
|
70
81
|
# Open specified HQMF oid file
|
71
82
|
oids_file = ARGV[1]
|
72
83
|
raise 'Please provide a valid HQMF oid file path and name.' if oids_file.blank? || !File.file?(oids_file)
|
84
|
+
|
73
85
|
oids = JSON.parse(File.read(oids_file))
|
74
86
|
|
75
87
|
# If this script was run with a third parameter of 'TEST', then generate the models in a
|
@@ -129,7 +141,8 @@ modelinfo.xpath('//ns4:typeInfo').each do |type|
|
|
129
141
|
hqmfOid_to_datatype_map[extra_info['hqmf_oid']] = datatype_name if extra_info['hqmf_oid'].present?
|
130
142
|
end
|
131
143
|
|
132
|
-
|
144
|
+
# Add the qdmVersion attribute unless the base type is one that will provide it
|
145
|
+
attributes << { name: 'qdmVersion', type: 'System.String', default: qdm_version } unless ['QDM.Entity', 'QDM.Component'].include? type['baseType']
|
133
146
|
|
134
147
|
datatypes[datatype_name] = { attributes: attributes }
|
135
148
|
end
|
@@ -185,7 +198,7 @@ file_path = 'app/assets/javascripts/'
|
|
185
198
|
file_path = 'tmp/' if IS_TEST
|
186
199
|
datatype_custom_templates = {
|
187
200
|
QDMPatient: 'templates/patient_template.js.erb',
|
188
|
-
|
201
|
+
Identifier: 'templates/identifier_template.js.erb'
|
189
202
|
}
|
190
203
|
|
191
204
|
datatypes.each do |datatype, info|
|
@@ -195,8 +208,8 @@ datatypes.each do |datatype, info|
|
|
195
208
|
renderer = ERB.new(File.read(datatype_custom_templates[datatype.to_sym]), nil, '-')
|
196
209
|
end
|
197
210
|
attrs_with_extras = info[:attributes] # this field gets used in the template
|
198
|
-
#
|
199
|
-
unless
|
211
|
+
# QDMPatients don't need _type
|
212
|
+
unless datatype.to_s == 'QDMPatient'
|
200
213
|
attrs_with_extras << { name: '_type', type: 'System.String', default: "QDM::#{datatype.underscore.camelize}" } # Add Class
|
201
214
|
end
|
202
215
|
puts ' ' + file_path + datatype + '.js'
|
@@ -219,6 +232,14 @@ unless IS_TEST
|
|
219
232
|
contents = File.read(file_path)
|
220
233
|
contents.gsub!(%r{\/FacilityLocation.js}, '/attributes/FacilityLocation.js')
|
221
234
|
contents.gsub!(%r{\/Component.js}, '/attributes/Component.js')
|
235
|
+
contents.gsub!(%r{\/CarePartner.js}, '/attributes/CarePartner.js')
|
236
|
+
contents.gsub!(%r{\/DiagnosisComponent.js}, '/attributes/DiagnosisComponent.js')
|
237
|
+
contents.gsub!(%r{\/Entity.js}, '/attributes/Entity.js')
|
238
|
+
contents.gsub!(%r{\/Organization.js}, '/attributes/Organization.js')
|
239
|
+
contents.gsub!(%r{\/PatientEntity.js}, '/attributes/PatientEntity.js')
|
240
|
+
contents.gsub!(%r{\/Practitioner.js}, '/attributes/Practitioner.js')
|
241
|
+
contents.gsub!(%r{\/ResultComponent.js}, '/attributes/ResultComponent.js')
|
242
|
+
contents.gsub!(%r{\/Identifier.js}, '/attributes/Identifier.js')
|
222
243
|
File.open(file_path, 'w') { |file| file.puts contents }
|
223
244
|
end
|
224
245
|
|
@@ -243,14 +264,16 @@ Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
|
243
264
|
# Make facilityLocation of type QDM::FacilityLocation
|
244
265
|
contents.gsub!(/field :facilityLocation, type: Code/, 'field :facilityLocation, type: QDM::FacilityLocation')
|
245
266
|
|
246
|
-
# Make
|
247
|
-
contents.gsub!(/
|
248
|
-
|
249
|
-
|
250
|
-
contents.gsub!(/
|
251
|
-
|
252
|
-
|
253
|
-
contents.gsub!(/
|
267
|
+
# Make Entity subclasses of type QDM::Entity
|
268
|
+
contents.gsub!(/field :participant/, "embeds_one :participant, class_name: 'QDM::Entity'")
|
269
|
+
contents.gsub!(/field :sender/, "embeds_one :sender, class_name: 'QDM::Entity'")
|
270
|
+
contents.gsub!(/field :recipient/, "embeds_one :recipient, class_name: 'QDM::Entity'")
|
271
|
+
contents.gsub!(/field :recorder/, "embeds_one :recorder, class_name: 'QDM::Entity'")
|
272
|
+
contents.gsub!(/field :performer/, "embeds_one :performer, class_name: 'QDM::Entity'")
|
273
|
+
contents.gsub!(/field :requester/, "embeds_one :requester, class_name: 'QDM::Entity'")
|
274
|
+
contents.gsub!(/field :prescriber/, "embeds_one :prescriber, class_name: 'QDM::Entity'")
|
275
|
+
contents.gsub!(/field :dispenser/, "embeds_one :dispenser, class_name: 'QDM::Entity'")
|
276
|
+
contents.gsub!(/field :identifier, type: Identifier/, "embeds_one :identifier, class_name: 'QDM::Identifier'")
|
254
277
|
|
255
278
|
File.open(file_name, 'w') { |file| file.puts contents }
|
256
279
|
end
|
@@ -258,28 +281,35 @@ end
|
|
258
281
|
# JavaScript post processing
|
259
282
|
js_models_path = 'app/assets/javascripts/'
|
260
283
|
js_models_path = 'tmp/' if IS_TEST
|
261
|
-
|
284
|
+
Dir.glob(js_models_path + '*.js').each do |file_name|
|
262
285
|
contents = File.read(file_name)
|
263
286
|
|
264
287
|
# Replace 'Any' type placeholder (these attributes could point to anything).
|
265
288
|
contents.gsub!(/: Any/, ': Any')
|
266
289
|
|
267
|
-
# Component, Facility,
|
290
|
+
# Component, Facility, Diagnoses
|
268
291
|
contents.gsub!(/facilityLocations: \[\]/, 'facilityLocations: [FacilityLocationSchema]')
|
269
292
|
contents.gsub!(/facilityLocation: Code/, 'facilityLocation: FacilityLocationSchema')
|
270
293
|
contents.gsub!(/components: \[\]/, 'components: [ComponentSchema]')
|
271
294
|
contents.gsub!(/component: Code/, 'component: ComponentSchema')
|
272
|
-
contents.gsub!(/
|
295
|
+
contents.gsub!(/diagnoses: \[\]/, 'diagnoses: [DiagnosisComponentSchema]')
|
296
|
+
contents.gsub!(/sender: Any/, 'sender: AnyEntity')
|
297
|
+
contents.gsub!(/recipient: Any/, 'recipient: AnyEntity')
|
298
|
+
contents.gsub!(/participant: Any/, 'participant: AnyEntity')
|
299
|
+
contents.gsub!(/recorder: Any/, 'recorder: AnyEntity')
|
300
|
+
contents.gsub!(/performer: Any/, 'performer: AnyEntity')
|
301
|
+
contents.gsub!(/requester: Any/, 'requester: AnyEntity')
|
302
|
+
contents.gsub!(/prescriber: Any/, 'prescriber: AnyEntity')
|
303
|
+
contents.gsub!(/dispenser: Any/, 'dispenser: AnyEntity')
|
304
|
+
contents.gsub!(/relatedTo: \[\]/, 'relatedTo: [String]')
|
273
305
|
|
274
306
|
File.open(file_name, 'w') { |file| file.puts contents }
|
275
307
|
end
|
276
308
|
|
277
309
|
# Inject Ruby Patient model extensions
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
rb_patient.gsub!(/end/, renderer.result(binding))
|
282
|
-
File.open(ruby_models_path + 'patient.rb', 'w') { |file| file.write(rb_patient) }
|
310
|
+
GeneratorHelpers.inject_extension('templates/patient_extension.rb.erb', ruby_models_path + 'patient.rb')
|
311
|
+
# Inject Ruby Entity model extensions
|
312
|
+
GeneratorHelpers.inject_extension('templates/entity_extension.rb.erb', ruby_models_path + 'entity.rb') if datatypes['Entity']
|
283
313
|
|
284
314
|
# Make sure Ruby models are in the correct module
|
285
315
|
ruby_models_path = 'app/models/qdm/'
|
@@ -288,23 +318,29 @@ Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
|
288
318
|
contents = File.read(file_name)
|
289
319
|
contents.gsub!('Qdm', 'QDM')
|
290
320
|
contents.gsub!('Code', 'QDM::Code')
|
291
|
-
contents.gsub!('
|
321
|
+
contents.gsub!('Date\n', 'QDM::Date\n') # \n so DateTime does not get overwritten
|
322
|
+
contents.gsub!(' Identifier', ' QDM::Identifier')
|
292
323
|
contents.gsub!('Interval', 'QDM::Interval')
|
293
324
|
contents.gsub!('Quantity', 'QDM::Quantity')
|
294
325
|
File.open(file_name, 'w') { |file| file.puts contents }
|
295
326
|
end
|
296
327
|
|
328
|
+
types_not_inherited_by_data_element = ['/patient.rb', '/identifier.rb', '/component.rb', '/facility_location.rb', '/entity.rb', '/organization.rb', '/patient_entity.rb', '/practitioner.rb', '/care_partner.rb', '/diagnosis_component.rb', '/result_component.rb']
|
329
|
+
types_inherited_by_attribute = ['/component', '/facility_location', '/entity', '/diagnosis_component', '/identifier']
|
330
|
+
types_inherited_by_entity = ['/patient_entity', '/care_partner', '/practitioner', '/organization']
|
331
|
+
types_inherited_by_component = ['/result_component']
|
332
|
+
|
297
333
|
# Set embedded in for datatypes
|
298
334
|
Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
299
335
|
contents = File.read(file_name)
|
300
|
-
|
301
|
-
if File.basename(file_name) == 'id.rb'
|
336
|
+
if ['entity.rb', 'organization.rb', 'patient_entity.rb', 'practitioner.rb', 'care_partner.rb'].any? { |sub_string| sub_string.include?(File.basename(file_name)) }
|
302
337
|
contents.gsub!(/ include Mongoid::Document\n/, " include Mongoid::Document\n embedded_in :data_element\n")
|
303
|
-
|
304
|
-
not_embedded_in_patient_files = ['patient.rb', 'component.rb', 'facility_location.rb']
|
305
|
-
next if not_embedded_in_patient_files.include?(File.basename(file_name))
|
306
|
-
contents.gsub!(/ include Mongoid::Document\n/, " include Mongoid::Document\n embedded_in :patient\n")
|
338
|
+
File.open(file_name, 'w') { |file| file.puts contents }
|
307
339
|
end
|
340
|
+
# TODO: Might be able to make this list by finding baseType="System.Any" in model info file instead of hard-coding.
|
341
|
+
next if types_not_inherited_by_data_element.any? { |sub_string| sub_string.include?(File.basename(file_name)) }
|
342
|
+
|
343
|
+
contents.gsub!(/ include Mongoid::Document\n/, " include Mongoid::Document\n embedded_in :patient\n")
|
308
344
|
File.open(file_name, 'w') { |file| file.puts contents }
|
309
345
|
end
|
310
346
|
|
@@ -312,8 +348,10 @@ end
|
|
312
348
|
Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
313
349
|
contents = ''
|
314
350
|
File.open(file_name).each_line.with_index do |line, index|
|
315
|
-
line.gsub!("\n", " < DataElement\n") if index.zero? &&
|
316
|
-
line.gsub!("\n", " < Attribute\n") if index.zero? &&
|
351
|
+
line.gsub!("\n", " < DataElement\n") if index.zero? && types_not_inherited_by_data_element.none? { |sub_string| file_name.include?(sub_string) }
|
352
|
+
line.gsub!("\n", " < Attribute\n") if index.zero? && types_inherited_by_attribute.any? { |sub_string| file_name.include?(sub_string) }
|
353
|
+
line.gsub!("\n", " < Entity\n") if index.zero? && types_inherited_by_entity.any? { |sub_string| file_name.include?(sub_string) }
|
354
|
+
line.gsub!("\n", " < Component\n") if index.zero? && types_inherited_by_component.any? { |sub_string| file_name.include?(sub_string) }
|
317
355
|
contents += "module QDM\n # #{file_name}\n #{line.gsub('QDM::', '')}" if index.zero?
|
318
356
|
contents += ' ' unless index.zero? || line.blank?
|
319
357
|
contents += line unless index.zero?
|
@@ -328,13 +366,13 @@ if IS_TEST
|
|
328
366
|
Dir.mkdir(ruby_models_path + 'attributes')
|
329
367
|
Dir.mkdir(js_models_path + 'attributes')
|
330
368
|
end
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
File.rename ruby_models_path + '
|
337
|
-
File.rename js_models_path + '
|
369
|
+
|
370
|
+
attribute_list = types_inherited_by_attribute + types_inherited_by_component + types_inherited_by_entity
|
371
|
+
existing_types_moving_to_attributes = attribute_list.select { |sub_string| File.exist?(ruby_models_path + sub_string + '.rb') }
|
372
|
+
existing_types_moving_to_attributes.each do |type|
|
373
|
+
modified_name = type[1..-1]
|
374
|
+
File.rename ruby_models_path + modified_name + '.rb', ruby_models_path + 'attributes/' + modified_name + '.rb'
|
375
|
+
File.rename js_models_path + modified_name.camelize + '.js', js_models_path + 'attributes/' + modified_name.camelize + '.js'
|
338
376
|
end
|
339
377
|
|
340
378
|
puts 'Create hqmfOid to datatype map as json file'
|
data/lib/generate_patients.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require('nokogiri')
|
2
2
|
require_relative '../app/models/models'
|
3
3
|
require('generate_types')
|
4
|
+
require('generate_entities')
|
4
5
|
|
5
6
|
module QDM
|
6
7
|
# PatientGeneration module can be used to generate patients with dataElements that are populated
|
@@ -8,13 +9,14 @@ module QDM
|
|
8
9
|
module PatientGeneration
|
9
10
|
# Generates patient(s) with fully-loaded dataElements if new_patient_for_each_type is false then a
|
10
11
|
# single patient will be returned that has every data element on it
|
11
|
-
def self.
|
12
|
+
def self.generate_exhaustive_data_element_patients(new_patient_for_each_type = true, model_info_file = 'qdm-modelinfo-5.5.xml')
|
12
13
|
datatypes = get_datatypes(File.join(File.dirname(__FILE__), "../modelinfo/#{model_info_file}"))
|
13
14
|
patients = []
|
14
15
|
cqm_patient = nil
|
15
16
|
qdm_patient = nil
|
16
17
|
datatypes.each do |type|
|
17
18
|
next if type.include? 'PatientCharacteristic'
|
19
|
+
|
18
20
|
# 1 Patient Per data element type containing negated and non-negated type (if negatable)
|
19
21
|
if cqm_patient.nil? || qdm_patient.nil? || new_patient_for_each_type
|
20
22
|
cqm_patient = QDM::BaseTypeGeneration.generate_cqm_patient(type)
|
@@ -33,7 +35,7 @@ module QDM
|
|
33
35
|
data_element = generate_loaded_datatype(type)
|
34
36
|
qdm_patient.dataElements.push(data_element)
|
35
37
|
# if type is negatable, add a negated version to the patient
|
36
|
-
if data_element.fields.
|
38
|
+
if data_element.fields.key?('negationRationale')
|
37
39
|
negated_data_element = generate_loaded_datatype(type, true)
|
38
40
|
qdm_patient.dataElements.push(negated_data_element)
|
39
41
|
end
|
@@ -49,10 +51,11 @@ module QDM
|
|
49
51
|
modelinfo.xpath('//ns4:typeInfo').each do |type|
|
50
52
|
# Grab the name of this QDM datatype
|
51
53
|
datatype_name = type.attributes['name'].value.split('.').last
|
52
|
-
exclusion_array = %w[Component
|
54
|
+
exclusion_array = %w[Component Identifier Patient ResultComponent FacilityLocation]
|
53
55
|
# Store datatype and its attributes (reject irrelevant datatypes)
|
54
56
|
next if datatype_name.include?('Negative') || datatype_name.include?('Positive') ||
|
55
57
|
datatype_name.include?('QDMBaseType') || (exclusion_array.include? datatype_name)
|
58
|
+
|
56
59
|
datatypes.push(datatype_name)
|
57
60
|
end
|
58
61
|
datatypes
|
@@ -64,10 +67,11 @@ module QDM
|
|
64
67
|
fields.each do |field_name|
|
65
68
|
# Ignore these fields, they are used by mongo
|
66
69
|
next if %w[_id _type].include? field_name
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
+
|
71
|
+
populate_fields(field_name, data_element, negate_data_element) if !data_element[field_name] || data_element[field_name] == []
|
70
72
|
end
|
73
|
+
QDM::EntityGeneration.generate_entities(data_element)
|
74
|
+
data_element.identifier = QDM::BaseTypeGeneration.generate_qdm_id if data_element.respond_to? 'identifier'
|
71
75
|
data_element
|
72
76
|
end
|
73
77
|
|
@@ -81,17 +85,19 @@ module QDM
|
|
81
85
|
data_element[field_name] = QDM::BaseTypeGeneration.generate_code_field if negate_data_element
|
82
86
|
elsif field_name == 'components'
|
83
87
|
data_element[field_name] = [QDM::BaseTypeGeneration.generate_component]
|
84
|
-
elsif field_name == '
|
85
|
-
# TODO: Result can be MANY Integer, Decimal, Code, Quantity or Ratio randomize this
|
86
|
-
data_element[field_name] = QDM::BaseTypeGeneration.generate_code_field
|
87
|
-
elsif %w[diagnoses dataElementCodes].include? field_name
|
88
|
+
elsif field_name == 'dataElementCodes'
|
88
89
|
# TODO: Populate dataElementCodes with codes specifically for data element type
|
90
|
+
# might need to build a large map of data element type to relevent codes
|
91
|
+
# should also be done for <data element type>.attribute -> relevant code for the coded attributes
|
89
92
|
data_element[field_name] = [QDM::BaseTypeGeneration.generate_code_field]
|
93
|
+
elsif field_name == 'diagnoses'
|
94
|
+
data_element[field_name] = [QDM::BaseTypeGeneration.generate_diagnosis]
|
95
|
+
elsif field_name == 'result'
|
96
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_result
|
90
97
|
elsif field_name == 'facilityLocations'
|
91
|
-
#
|
92
|
-
data_element[field_name] =
|
98
|
+
# create up to 5 facility locations
|
99
|
+
data_element[field_name] = (0..rand(5)).map { QDM::BaseTypeGeneration.generate_facility_location }
|
93
100
|
elsif field_name == 'facilityLocation'
|
94
|
-
# TODO: Randomize number of facility locations added
|
95
101
|
data_element[field_name] = QDM::BaseTypeGeneration.generate_facility_location
|
96
102
|
elsif field_name == 'targetOutcome'
|
97
103
|
# TODO: Randomize type of targetOutcome, use code for now
|
@@ -112,7 +118,7 @@ module QDM
|
|
112
118
|
data_element[field_name] = QDM::BaseTypeGeneration.generate_date_interval_field
|
113
119
|
elsif field_type == QDM::Quantity
|
114
120
|
data_element[field_name] = QDM::BaseTypeGeneration.generate_quantity
|
115
|
-
elsif field_type == QDM::
|
121
|
+
elsif field_type == QDM::Identifier
|
116
122
|
data_element[field_name] = QDM::BaseTypeGeneration.generate_qdm_id
|
117
123
|
elsif field_type == Integer
|
118
124
|
# TODO: randomize value
|
data/lib/generate_types.rb
CHANGED
@@ -3,11 +3,20 @@ require_relative '../app/models/models'
|
|
3
3
|
module QDM
|
4
4
|
# BaseTypeGeneration contains functions to randomly generate basetypes used by PatientGeneration
|
5
5
|
module BaseTypeGeneration
|
6
|
+
def self.generate_diagnosis
|
7
|
+
diagnosis_component = QDM::DiagnosisComponent.new
|
8
|
+
diagnosis_component.code = generate_code_field
|
9
|
+
diagnosis_component.presentOnAdmissionIndicator = generate_code_field
|
10
|
+
diagnosis_component.rank = 1
|
11
|
+
diagnosis_component
|
12
|
+
end
|
13
|
+
|
6
14
|
def self.generate_component
|
7
15
|
component = QDM::Component.new
|
8
16
|
component.code = generate_code_field
|
9
|
-
#
|
10
|
-
|
17
|
+
# Component.result type values: Integer, Decimal, Code, Quantity, Ratio, DateTime, Time (from modelinfo file)
|
18
|
+
# low occurence rate in 2020/2021 measures, so may not randomized to include DateTime/Time yet
|
19
|
+
component.result = generate_result
|
11
20
|
component
|
12
21
|
end
|
13
22
|
|
@@ -19,23 +28,39 @@ module QDM
|
|
19
28
|
end
|
20
29
|
|
21
30
|
def self.generate_code_field
|
22
|
-
#
|
23
|
-
|
31
|
+
# relevant code systems as of March 2020
|
32
|
+
code_sys = ['2.16.840.1.113883.6.96', '2.16.840.1.113883.6.90', '2.16.840.1.113883.6.103',
|
33
|
+
'2.16.840.1.113883.6.104', '2.16.840.1.113883.6.12', '2.16.840.1.113883.6.88',
|
34
|
+
'2.16.840.1.113883.6.1', '2.16.840.1.113883.12.292', '2.16.840.1.113883.6.285',
|
35
|
+
'2.16.840.1.113883.6.4', '2.16.840.1.113883.6.14', '2.16.840.1.113883.6.13']
|
36
|
+
|
37
|
+
# 1-15 digit codes
|
38
|
+
code = rand(1_000_000_000_000_000)
|
39
|
+
QDM::Code.new(code.to_s, code_sys[rand(12)], 'Display Name', '2018-09')
|
24
40
|
end
|
25
41
|
|
26
42
|
def self.generate_datetime
|
27
|
-
#
|
28
|
-
DateTime.
|
43
|
+
# Does not give a random time... just randomizes the date +/-365000 days
|
44
|
+
rand(DateTime.now - 36_500..DateTime.now + 36_500)
|
29
45
|
end
|
30
46
|
|
31
47
|
def self.generate_quantity
|
32
|
-
# TODO:
|
33
|
-
|
48
|
+
# TODO: randomize for no unit situation?
|
49
|
+
|
50
|
+
# pulled from realistic units (UCUM)
|
51
|
+
# https://vsac.nlm.nih.gov/valueset/2.16.840.1.113883.1.11.12839/expansion/Latest
|
52
|
+
unit = ['%', '/10*10', '10*12/L', 'K', 'U/(10.g){feces}', '[IU]/g', 'cm', 'erg',
|
53
|
+
'g/L', 'kat', 'mA', 'osm', 's/{control}', '{CPM}', '{Pan_Bio\'U}', '{mutation}']
|
54
|
+
QDM::Quantity.new(rand(0.0..10_000.0), unit[rand(16)])
|
34
55
|
end
|
35
56
|
|
36
57
|
def self.generate_date_interval_field
|
37
|
-
|
38
|
-
|
58
|
+
date1 = generate_datetime
|
59
|
+
date2 = generate_datetime
|
60
|
+
|
61
|
+
# endure low before high
|
62
|
+
# randomly true/false lowClosed and highClosed parameters
|
63
|
+
QDM::Interval.new([date1, date2].min, [date1, date2].max, rand(2).zero?, rand(2).zero?)
|
39
64
|
end
|
40
65
|
|
41
66
|
def self.generate_cqm_patient(type)
|
@@ -54,11 +79,26 @@ module QDM
|
|
54
79
|
end
|
55
80
|
|
56
81
|
def self.generate_qdm_id
|
57
|
-
#
|
58
|
-
qdm_id = QDM::
|
59
|
-
qdm_id.value = 'TestValue'
|
60
|
-
qdm_id.namingSystem = 'TestNamingSystem'
|
82
|
+
# could also randomize length of string
|
83
|
+
qdm_id = QDM::Identifier.new
|
84
|
+
qdm_id.value = random_string(12) # 'TestValue'
|
85
|
+
qdm_id.namingSystem = random_string(12) # 'TestNamingSystem'
|
61
86
|
qdm_id
|
62
87
|
end
|
88
|
+
|
89
|
+
def self.generate_result
|
90
|
+
# minimum allowed set Integer (converts incorrectly for tests), Decimal, Code, Quantity, Ratio
|
91
|
+
# sometimes allowed to be DateTime, Time, or Date in certain contexts (from modelinfo file)
|
92
|
+
case Random.rand(3)
|
93
|
+
when 0 then return rand(0.0..10_000.0)
|
94
|
+
when 1 then return generate_code_field
|
95
|
+
when 2 then return generate_quantity
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.random_string(length)
|
100
|
+
# create and array of size length and map to a random character from ! (33) to ~ (126)
|
101
|
+
[*'a'..'z', *'A'..'Z'].sample(length).join
|
102
|
+
end
|
63
103
|
end
|
64
104
|
end
|