cqm-models 1.0.3 → 1.1.1.0
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.
- checksums.yaml +5 -5
- data/.eslintrc.json +10 -2
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -4
- data/.rubocop.yml +3 -0
- data/.travis.yml +31 -1
- data/README.md +59 -2
- data/app/assets/javascripts/AdverseEvent.js +16 -7
- data/app/assets/javascripts/AllDataElements.js +6 -6
- data/app/assets/javascripts/AllergyIntolerance.js +16 -7
- data/app/assets/javascripts/AssessmentOrder.js +15 -6
- data/app/assets/javascripts/AssessmentPerformed.js +16 -7
- data/app/assets/javascripts/AssessmentRecommended.js +16 -7
- data/app/assets/javascripts/CareGoal.js +16 -7
- data/app/assets/javascripts/CommunicationPerformed.js +15 -6
- data/app/assets/javascripts/DeviceApplied.js +16 -7
- data/app/assets/javascripts/DeviceOrder.js +16 -7
- data/app/assets/javascripts/DeviceRecommended.js +16 -7
- data/app/assets/javascripts/Diagnosis.js +16 -7
- data/app/assets/javascripts/DiagnosticStudyOrder.js +16 -7
- data/app/assets/javascripts/DiagnosticStudyPerformed.js +16 -7
- data/app/assets/javascripts/DiagnosticStudyRecommended.js +16 -7
- data/app/assets/javascripts/EncounterOrder.js +16 -7
- data/app/assets/javascripts/EncounterPerformed.js +16 -7
- data/app/assets/javascripts/EncounterRecommended.js +16 -7
- data/app/assets/javascripts/FamilyHistory.js +16 -7
- data/app/assets/javascripts/Id.js +7 -3
- data/app/assets/javascripts/ImmunizationAdministered.js +16 -7
- data/app/assets/javascripts/ImmunizationOrder.js +16 -7
- data/app/assets/javascripts/IndividualResult.js +7 -2
- data/app/assets/javascripts/InterventionOrder.js +16 -7
- data/app/assets/javascripts/InterventionPerformed.js +16 -7
- data/app/assets/javascripts/InterventionRecommended.js +16 -7
- data/app/assets/javascripts/LaboratoryTestOrder.js +16 -7
- data/app/assets/javascripts/LaboratoryTestPerformed.js +16 -7
- data/app/assets/javascripts/LaboratoryTestRecommended.js +16 -7
- data/app/assets/javascripts/MedicationActive.js +16 -7
- data/app/assets/javascripts/MedicationAdministered.js +16 -7
- data/app/assets/javascripts/MedicationDischarge.js +16 -7
- data/app/assets/javascripts/MedicationDispensed.js +16 -7
- data/app/assets/javascripts/MedicationOrder.js +16 -7
- data/app/assets/javascripts/Participation.js +15 -6
- data/app/assets/javascripts/PatientCareExperience.js +16 -7
- data/app/assets/javascripts/PatientCharacteristic.js +16 -7
- data/app/assets/javascripts/PatientCharacteristicBirthdate.js +16 -7
- data/app/assets/javascripts/PatientCharacteristicClinicalTrialParticipant.js +16 -7
- data/app/assets/javascripts/PatientCharacteristicEthnicity.js +16 -7
- data/app/assets/javascripts/PatientCharacteristicExpired.js +16 -7
- data/app/assets/javascripts/PatientCharacteristicPayer.js +16 -7
- data/app/assets/javascripts/PatientCharacteristicRace.js +16 -7
- data/app/assets/javascripts/PatientCharacteristicSex.js +16 -7
- data/app/assets/javascripts/PhysicalExamOrder.js +16 -7
- data/app/assets/javascripts/PhysicalExamPerformed.js +16 -7
- data/app/assets/javascripts/PhysicalExamRecommended.js +16 -7
- data/app/assets/javascripts/ProcedureOrder.js +16 -7
- data/app/assets/javascripts/ProcedurePerformed.js +16 -7
- data/app/assets/javascripts/ProcedureRecommended.js +16 -7
- data/app/assets/javascripts/ProviderCareExperience.js +16 -7
- data/app/assets/javascripts/ProviderCharacteristic.js +16 -7
- data/app/assets/javascripts/QDMPatient.js +262 -0
- data/app/assets/javascripts/Result.js +7 -2
- data/app/assets/javascripts/ResultComponent.js +14 -6
- data/app/assets/javascripts/SubstanceAdministered.js +16 -7
- data/app/assets/javascripts/SubstanceOrder.js +16 -7
- data/app/assets/javascripts/SubstanceRecommended.js +16 -7
- data/app/assets/javascripts/Symptom.js +16 -7
- data/app/assets/javascripts/attributes/Component.js +30 -0
- data/app/assets/javascripts/attributes/FacilityLocation.js +30 -0
- data/app/assets/javascripts/basetypes/Any.js +1 -1
- data/app/assets/javascripts/basetypes/Code.js +1 -1
- data/app/assets/javascripts/basetypes/DataElement.js +2 -1
- data/app/assets/javascripts/basetypes/DateTime.js +5 -1
- data/app/assets/javascripts/basetypes/Interval.js +1 -1
- data/app/assets/javascripts/basetypes/Quantity.js +1 -1
- data/app/assets/javascripts/basetypes/Ratio.js +1 -1
- data/app/assets/javascripts/browser.js +3 -0
- data/app/assets/javascripts/cqm/AllCQMModels.js +24 -0
- data/app/assets/javascripts/cqm/CQLLibrary.js +32 -0
- data/app/assets/javascripts/cqm/CQLStatementDependency.js +28 -0
- data/app/assets/javascripts/cqm/Concept.js +17 -0
- data/app/assets/javascripts/cqm/Measure.js +108 -0
- data/app/assets/javascripts/{MeasurePackage.js → cqm/MeasurePackage.js} +9 -3
- data/app/assets/javascripts/cqm/Patient.js +38 -0
- data/app/assets/javascripts/cqm/PopulationSet.js +88 -0
- data/app/assets/javascripts/cqm/Provider.js +44 -0
- data/app/assets/javascripts/cqm/ValueSet.js +26 -0
- data/app/assets/javascripts/index.js +18 -8
- data/app/models/{qdm/tacoma → cqm}/concept.rb +4 -4
- data/app/models/cqm/cql_library.rb +22 -0
- data/app/models/cqm/cql_statement_dependency.rb +24 -0
- data/app/models/cqm/measure.rb +105 -0
- data/app/models/{qdm/tacoma → cqm}/measure_package.rb +2 -2
- data/app/models/cqm/patient.rb +20 -0
- data/app/models/cqm/population_set.rb +86 -0
- data/app/models/cqm/provider.rb +110 -0
- data/app/models/cqm/valueset.rb +15 -0
- data/app/models/hqmfOid_to_datatype_map.json +55 -0
- data/app/models/model_finder.rb +12 -0
- data/app/models/models.rb +18 -6
- 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 +1 -0
- data/app/models/qdm/assessment_performed.rb +2 -1
- data/app/models/qdm/assessment_recommended.rb +2 -1
- data/app/models/qdm/attributes/attribute.rb +63 -0
- data/app/models/qdm/{component.rb → attributes/component.rb} +1 -1
- data/app/models/qdm/{facility_location.rb → attributes/facility_location.rb} +1 -1
- data/app/models/qdm/basetypes/data_element.rb +21 -6
- data/app/models/qdm/basetypes/interval.rb +2 -1
- data/app/models/qdm/care_goal.rb +2 -1
- data/app/models/qdm/communication_performed.rb +1 -0
- data/app/models/qdm/device_applied.rb +2 -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 -2
- data/app/models/qdm/diagnostic_study_recommended.rb +2 -1
- data/app/models/qdm/encounter_order.rb +3 -2
- data/app/models/qdm/encounter_performed.rb +2 -1
- data/app/models/qdm/encounter_recommended.rb +3 -2
- data/app/models/qdm/family_history.rb +2 -1
- data/app/models/qdm/immunization_administered.rb +2 -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 +2 -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 +2 -1
- data/app/models/qdm/laboratory_test_recommended.rb +2 -1
- data/app/models/qdm/medication_active.rb +2 -1
- data/app/models/qdm/medication_administered.rb +2 -1
- data/app/models/qdm/medication_discharge.rb +2 -1
- data/app/models/qdm/medication_dispensed.rb +4 -3
- data/app/models/qdm/medication_order.rb +3 -2
- data/app/models/qdm/participation.rb +1 -0
- data/app/models/qdm/patient.rb +7 -4
- data/app/models/qdm/patient_care_experience.rb +2 -1
- data/app/models/qdm/patient_characteristic.rb +2 -1
- data/app/models/qdm/patient_characteristic_birthdate.rb +2 -1
- data/app/models/qdm/patient_characteristic_clinical_trial_participant.rb +2 -1
- data/app/models/qdm/patient_characteristic_ethnicity.rb +2 -1
- data/app/models/qdm/patient_characteristic_expired.rb +2 -1
- data/app/models/qdm/patient_characteristic_payer.rb +2 -1
- data/app/models/qdm/patient_characteristic_race.rb +2 -1
- data/app/models/qdm/patient_characteristic_sex.rb +2 -1
- data/app/models/qdm/physical_exam_order.rb +2 -1
- data/app/models/qdm/physical_exam_performed.rb +2 -1
- data/app/models/qdm/physical_exam_recommended.rb +2 -1
- data/app/models/qdm/procedure_order.rb +2 -1
- data/app/models/qdm/procedure_performed.rb +2 -1
- data/app/models/qdm/procedure_recommended.rb +2 -1
- data/app/models/qdm/provider_care_experience.rb +2 -1
- data/app/models/qdm/provider_characteristic.rb +2 -1
- data/app/models/qdm/substance_administered.rb +2 -1
- data/app/models/qdm/substance_order.rb +2 -1
- data/app/models/qdm/substance_recommended.rb +2 -1
- data/app/models/qdm/symptom.rb +2 -1
- data/bin/build_cql_execution.sh +8 -0
- data/bin/validate_browser.sh +17 -0
- data/bin/validate_generator.sh +12 -0
- data/cqm-models.gemspec +5 -2
- data/data/oids_qdm_5.4.json +321 -0
- data/data/{oids.json → oids_unversioned.json} +0 -0
- data/dist/browser.js +102937 -0
- data/dist/index.js +36637 -36540
- data/lib/generate_models.rb +89 -93
- data/lib/generate_patients.rb +125 -0
- data/lib/generate_types.rb +64 -0
- data/lib/generators/custom_mongo/model.rb.tt +20 -0
- data/lib/generators/custom_mongo/model_generator.rb +34 -0
- data/notice.md +9 -0
- data/package.json +12 -6
- data/templates/id_template.js.erb +8 -3
- data/templates/index_template.js.erb +18 -8
- data/templates/models_template.rb.erb +20 -4
- data/templates/mongoose_template.js.erb +23 -15
- data/templates/patient_extension.rb.erb +7 -4
- data/templates/patient_template.js.erb +88 -87
- data/yarn.lock +552 -347
- metadata +86 -19
- data/app/assets/javascripts/Component.js +0 -26
- data/app/assets/javascripts/Concept.js +0 -14
- data/app/assets/javascripts/FacilityLocation.js +0 -26
- data/app/assets/javascripts/Measure.js +0 -78
- data/app/assets/javascripts/Patient.js +0 -262
- data/app/assets/javascripts/ValueSet.js +0 -30
- data/app/models/qdm/tacoma/measure.rb +0 -174
- data/app/models/qdm/tacoma/valueset.rb +0 -68
data/lib/generate_models.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#! /usr/bin/env ruby
|
|
2
|
-
|
|
3
2
|
require 'nokogiri'
|
|
4
3
|
require 'active_support/all'
|
|
5
4
|
require 'rails/generators'
|
|
6
5
|
require 'erb'
|
|
7
6
|
require 'json'
|
|
7
|
+
require_relative './generators/custom_mongo/model_generator'
|
|
8
8
|
|
|
9
9
|
###############################################################################
|
|
10
10
|
# Helpers
|
|
@@ -82,10 +82,15 @@ qdm_version = modelinfo.xpath('//ns4:modelInfo').first.attributes['version'].val
|
|
|
82
82
|
# Datatypes (keys are the datatype name, values are the datatype attributes)
|
|
83
83
|
datatypes = {}
|
|
84
84
|
|
|
85
|
+
# We will export a hqmfOid to datatype map
|
|
86
|
+
hqmfOid_to_datatype_map = {}
|
|
87
|
+
|
|
85
88
|
# Loop through each typeInfo node (each of these is a QDM datatype)
|
|
86
89
|
modelinfo.xpath('//ns4:typeInfo').each do |type|
|
|
87
90
|
# Grab the name of this QDM datatype
|
|
88
91
|
datatype_name = type.attributes['name'].value.split('.').last
|
|
92
|
+
# Reject irrelevant datatypes
|
|
93
|
+
next if datatype_name.include?('Negative') || datatype_name.include?('Positive') || datatype_name.include?('QDMBaseType')
|
|
89
94
|
|
|
90
95
|
# Grab the QDM attributes for this datatype
|
|
91
96
|
attributes = []
|
|
@@ -106,9 +111,27 @@ modelinfo.xpath('//ns4:typeInfo').each do |type|
|
|
|
106
111
|
attributes << { name: attribute_name, type: attribute_type }
|
|
107
112
|
end
|
|
108
113
|
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
# Add the label as qdmTitle
|
|
115
|
+
qdm_title = type['label']
|
|
116
|
+
if qdm_title.nil? # If there's no label, check if there is a "positive" profile
|
|
117
|
+
positive_profile = modelinfo.at_xpath("/ns4:modelInfo/ns4:typeInfo[@xsi:type='ns4:ProfileInfo'][@identifier='Positive#{datatype_name}']")
|
|
118
|
+
qdm_title = positive_profile['label'] unless positive_profile.nil?
|
|
119
|
+
end
|
|
120
|
+
attributes << { name: 'qdmTitle', type: 'System.String', default: qdm_title } unless qdm_title.nil?
|
|
121
|
+
|
|
122
|
+
# Add the extra info that is manually maintained in the "oids" file
|
|
123
|
+
extra_info = oids[datatype_name.underscore]
|
|
124
|
+
if extra_info.present?
|
|
125
|
+
attributes << { name: 'hqmfOid', type: 'System.String', default: extra_info['hqmf_oid'] } if extra_info['hqmf_oid'].present?
|
|
126
|
+
attributes << { name: 'qrdaOid', type: 'System.String', default: extra_info['qrda_oid'] } if extra_info['qrda_oid'].present?
|
|
127
|
+
attributes << { name: 'qdmCategory', type: 'System.String', default: extra_info['qdm_category'] } if extra_info['qdm_category'].present?
|
|
128
|
+
attributes << { name: 'qdmStatus', type: 'System.String', default: extra_info['qdm_status'] } if extra_info['qdm_status'].present?
|
|
129
|
+
hqmfOid_to_datatype_map[extra_info['hqmf_oid']] = datatype_name if extra_info['hqmf_oid'].present?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
attributes << { name: 'qdmVersion', type: 'System.String', default: qdm_version }
|
|
133
|
+
|
|
134
|
+
datatypes[datatype_name] = { attributes: attributes }
|
|
112
135
|
end
|
|
113
136
|
|
|
114
137
|
###############################################################################
|
|
@@ -118,25 +141,25 @@ end
|
|
|
118
141
|
puts 'Generating Ruby models...'
|
|
119
142
|
|
|
120
143
|
# Do a quick sanity check on attribute types
|
|
121
|
-
datatypes.each do |datatype,
|
|
122
|
-
attributes.each do |attribute|
|
|
144
|
+
datatypes.each do |datatype, info|
|
|
145
|
+
info[:attributes].each do |attribute|
|
|
123
146
|
raise 'Unsupported type from modelinfo file for Ruby types: ' + attribute[:type] + 'from: ' + datatype if TYPE_LOOKUP_RB[attribute[:type]].blank?
|
|
124
147
|
raise 'Unsupported type from modelinfo file for JavaScript types: ' + attribute[:type] + 'from: ' + datatype if TYPE_LOOKUP_JS[attribute[:type]].blank?
|
|
125
148
|
end
|
|
126
149
|
end
|
|
127
150
|
|
|
128
151
|
# Create Ruby models
|
|
129
|
-
extra_fields_rb = [
|
|
130
|
-
'hqmfOid:String',
|
|
131
|
-
'qrdaOid:String',
|
|
132
|
-
'qdmCategory:String',
|
|
133
|
-
'qdmStatus:String',
|
|
134
|
-
'qdmVersion:String'
|
|
135
|
-
]
|
|
136
152
|
base_module = 'QDM::'
|
|
137
153
|
base_module = 'Test::QDM::' if IS_TEST
|
|
138
|
-
datatypes.each do |datatype,
|
|
139
|
-
|
|
154
|
+
datatypes.each do |datatype, info|
|
|
155
|
+
name = base_module + datatype
|
|
156
|
+
attributes = info[:attributes].map do |attribute|
|
|
157
|
+
attribute = attribute.dup
|
|
158
|
+
attribute[:type] = TYPE_LOOKUP_RB[attribute[:type]]
|
|
159
|
+
attribute
|
|
160
|
+
end
|
|
161
|
+
generator_args = [name, attributes]
|
|
162
|
+
Rails::Generators.invoke('custom_mongo:model', generator_args)
|
|
140
163
|
end
|
|
141
164
|
|
|
142
165
|
# Create require file (if not in test mode)
|
|
@@ -147,6 +170,12 @@ unless IS_TEST
|
|
|
147
170
|
File.open(file_path, 'w') { |file| file.puts renderer.result(binding) }
|
|
148
171
|
end
|
|
149
172
|
|
|
173
|
+
# Javascript PatientSchema for was renamed to QDMPatient since it just contains the QDM data
|
|
174
|
+
if datatypes['Patient']
|
|
175
|
+
datatypes['QDMPatient'] = datatypes['Patient']
|
|
176
|
+
datatypes.delete('Patient')
|
|
177
|
+
end
|
|
178
|
+
|
|
150
179
|
puts 'Generating JavaScript models...'
|
|
151
180
|
|
|
152
181
|
# Create JavaScript models
|
|
@@ -154,25 +183,22 @@ template = File.read('templates/mongoose_template.js.erb')
|
|
|
154
183
|
default_renderer = ERB.new(template, nil, '-')
|
|
155
184
|
file_path = 'app/assets/javascripts/'
|
|
156
185
|
file_path = 'tmp/' if IS_TEST
|
|
157
|
-
extra_fields_js = [
|
|
158
|
-
{ name: 'hqmfOid', type: 'System.String' },
|
|
159
|
-
{ name: 'qrdaOid', type: 'System.String' },
|
|
160
|
-
{ name: 'qdmCategory', type: 'System.String' },
|
|
161
|
-
{ name: 'qdmStatus', type: 'System.String' },
|
|
162
|
-
{ name: 'qdmVersion', type: 'System.String' },
|
|
163
|
-
{ name: '_type', type: 'System.String' }
|
|
164
|
-
]
|
|
165
186
|
datatype_custom_templates = {
|
|
166
|
-
|
|
187
|
+
QDMPatient: 'templates/patient_template.js.erb',
|
|
167
188
|
Id: 'templates/id_template.js.erb'
|
|
168
189
|
}
|
|
169
|
-
|
|
190
|
+
|
|
191
|
+
datatypes.each do |datatype, info|
|
|
170
192
|
renderer = default_renderer
|
|
171
193
|
if datatype_custom_templates.key?(datatype.to_sym)
|
|
172
194
|
puts "using custom template for #{datatype}"
|
|
173
195
|
renderer = ERB.new(File.read(datatype_custom_templates[datatype.to_sym]), nil, '-')
|
|
174
196
|
end
|
|
175
|
-
attrs_with_extras = attributes
|
|
197
|
+
attrs_with_extras = info[:attributes] # this field gets used in the template
|
|
198
|
+
# Custom datatypes don't need _type
|
|
199
|
+
unless datatype_custom_templates.key?(datatype.to_sym)
|
|
200
|
+
attrs_with_extras << { name: '_type', type: 'System.String', default: "QDM::#{datatype.underscore.camelize}" } # Add Class
|
|
201
|
+
end
|
|
176
202
|
puts ' ' + file_path + datatype + '.js'
|
|
177
203
|
File.open(file_path + datatype + '.js', 'w') { |file| file.puts renderer.result(binding) }
|
|
178
204
|
end
|
|
@@ -182,12 +208,18 @@ unless IS_TEST
|
|
|
182
208
|
indtemplate = File.read('templates/index_template.js.erb')
|
|
183
209
|
renderer = ERB.new(indtemplate, nil, '-')
|
|
184
210
|
file_path = 'app/assets/javascripts/index.js'
|
|
211
|
+
puts ' ' + file_path
|
|
185
212
|
File.open(file_path, 'w') { |file| file.puts renderer.result(binding) }
|
|
186
213
|
|
|
187
214
|
alltemplate = File.read('templates/all_data_elements_template.js.erb')
|
|
188
215
|
renderer = ERB.new(alltemplate, nil, '-')
|
|
189
216
|
file_path = 'app/assets/javascripts/AllDataElements.js'
|
|
217
|
+
puts ' ' + file_path
|
|
190
218
|
File.open(file_path, 'w') { |file| file.puts renderer.result(binding) }
|
|
219
|
+
contents = File.read(file_path)
|
|
220
|
+
contents.gsub!(%r{\/FacilityLocation.js}, '/attributes/FacilityLocation.js')
|
|
221
|
+
contents.gsub!(%r{\/Component.js}, '/attributes/Component.js')
|
|
222
|
+
File.open(file_path, 'w') { |file| file.puts contents }
|
|
191
223
|
end
|
|
192
224
|
|
|
193
225
|
###############################################################################
|
|
@@ -205,44 +237,21 @@ Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
|
|
205
237
|
# Cut out the 'Any' type placeholder (these attributes could point to anything).
|
|
206
238
|
contents.gsub!(/, type: Any/, '')
|
|
207
239
|
|
|
208
|
-
# Add QDM version
|
|
209
|
-
contents.gsub!(/field :qdmVersion, type: String/, "field :qdmVersion, type: String, default: '#{qdm_version}'")
|
|
210
|
-
|
|
211
240
|
# Add default to [] for facilityLocations
|
|
212
241
|
contents.gsub!(/field :facilityLocations, type: Array/, 'field :facilityLocations, type: Array, default: []')
|
|
213
242
|
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
if oids[dc_name].present? && oids[dc_name]['hqmf_oid'].present?
|
|
217
|
-
contents.gsub!(/ field :hqmfOid, type: String\n/, " field :hqmfOid, type: String, default: '#{oids[dc_name]['hqmf_oid']}'\n")
|
|
218
|
-
else
|
|
219
|
-
contents.gsub!(/ field :hqmfOid, type: String\n/, '') # Don't include this field
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Add QRDA oid (if it exists in the given QRDA oid mapping file)
|
|
223
|
-
if oids[dc_name].present? && oids[dc_name]['qrda_oid'].present?
|
|
224
|
-
contents.gsub!(/ field :qrdaOid, type: String\n/, " field :qrdaOid, type: String, default: '#{oids[dc_name]['qrda_oid']}'\n")
|
|
225
|
-
else
|
|
226
|
-
contents.gsub!(/ field :qrdaOid, type: String\n/, '') # Don't include this field
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# Add category
|
|
230
|
-
if oids[dc_name].present? && oids[dc_name]['qdm_category'].present?
|
|
231
|
-
contents.gsub!(/ field :qdmCategory, type: String\n/, " field :qdmCategory, type: String, default: '#{oids[dc_name]['qdm_category']}'\n")
|
|
232
|
-
else
|
|
233
|
-
contents.gsub!(/ field :qdmCategory, type: String\n/, '') # Don't include this field
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# Add status
|
|
237
|
-
if oids[dc_name].present? && oids[dc_name]['qdm_status'].present?
|
|
238
|
-
contents.gsub!(/ field :qdmStatus, type: String\n/, " field :qdmStatus, type: String, default: '#{oids[dc_name]['qdm_status']}'\n")
|
|
239
|
-
else
|
|
240
|
-
contents.gsub!(/ field :qdmStatus, type: String\n/, '') # Don't include this field
|
|
241
|
-
end
|
|
243
|
+
# Make facilityLocation of type QDM::FacilityLocation
|
|
244
|
+
contents.gsub!(/field :facilityLocation, type: Code/, 'field :facilityLocation, type: QDM::FacilityLocation')
|
|
242
245
|
|
|
243
246
|
# Make relatedTo embeds_many instead of field
|
|
244
247
|
contents.gsub!(/ field :relatedTo, type: Array\n/, " embeds_many :relatedTo, class_name: 'QDM::Id'\n")
|
|
245
248
|
|
|
249
|
+
# Make prescriberId embeds_many instead of field
|
|
250
|
+
contents.gsub!(/ field :prescriberId, type: Id\n/, " embeds_one :prescriberId, class_name: 'QDM::Id'\n")
|
|
251
|
+
|
|
252
|
+
# Make dispenserId embeds_many instead of field
|
|
253
|
+
contents.gsub!(/ field :dispenserId, type: Id\n/, " embeds_one :dispenserId, class_name: 'QDM::Id'\n")
|
|
254
|
+
|
|
246
255
|
File.open(file_name, 'w') { |file| file.puts contents }
|
|
247
256
|
end
|
|
248
257
|
|
|
@@ -255,41 +264,6 @@ files = Dir.glob(js_models_path + '*.js').each do |file_name|
|
|
|
255
264
|
# Replace 'Any' type placeholder (these attributes could point to anything).
|
|
256
265
|
contents.gsub!(/: Any/, ': Any')
|
|
257
266
|
|
|
258
|
-
# Add QDM version
|
|
259
|
-
contents.gsub!(/qdmVersion: String/, "qdmVersion: { type: String, default: '#{qdm_version}' }")
|
|
260
|
-
|
|
261
|
-
# Add HQMF oid (if it exists in the given HQMF oid mapping file)
|
|
262
|
-
dc_name = File.basename(file_name.underscore, '.*')
|
|
263
|
-
if oids[dc_name].present? && oids[dc_name]['hqmf_oid'].present?
|
|
264
|
-
contents.gsub!(/ hqmfOid: String,\n/, " hqmfOid: { type: String, default: '#{oids[dc_name]['hqmf_oid']}' },\n")
|
|
265
|
-
else
|
|
266
|
-
contents.gsub!(/ hqmfOid: String,\n/, '') # Don't include this field
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Add QRDA oid (if it exists in the given QRDA oid mapping file)
|
|
270
|
-
if oids[dc_name].present? && oids[dc_name]['qrda_oid'].present?
|
|
271
|
-
contents.gsub!(/ qrdaOid: String,\n/, " qrdaOid: { type: String, default: '#{oids[dc_name]['qrda_oid']}' },\n")
|
|
272
|
-
else
|
|
273
|
-
contents.gsub!(/ qrdaOid: String,\n/, '') # Don't include this field
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Add category
|
|
277
|
-
if oids[dc_name].present? && oids[dc_name]['qdm_category'].present?
|
|
278
|
-
contents.gsub!(/ qdmCategory: String,\n/, " qdmCategory: { type: String, default: '#{oids[dc_name]['qdm_category']}' },\n")
|
|
279
|
-
else
|
|
280
|
-
contents.gsub!(/ qdmCategory: String,\n/, '') # Don't include this field
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Add status
|
|
284
|
-
if oids[dc_name].present? && oids[dc_name]['qdm_status'].present?
|
|
285
|
-
contents.gsub!(/ qdmStatus: String,\n/, " qdmStatus: { type: String, default: '#{oids[dc_name]['qdm_status']}' },\n")
|
|
286
|
-
else
|
|
287
|
-
contents.gsub!(/ qdmStatus: String,\n/, '') # Don't include this field
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Add class
|
|
291
|
-
contents.gsub!(/ _type: String,\n/, " _type: { type: String, default: '#{dc_name.camelize}' },\n")
|
|
292
|
-
|
|
293
267
|
# Component, Facility, and Id types
|
|
294
268
|
contents.gsub!(/facilityLocations: \[\]/, 'facilityLocations: [FacilityLocationSchema]')
|
|
295
269
|
contents.gsub!(/facilityLocation: Code/, 'facilityLocation: FacilityLocationSchema')
|
|
@@ -314,6 +288,7 @@ Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
|
|
314
288
|
contents = File.read(file_name)
|
|
315
289
|
contents.gsub!('Qdm', 'QDM')
|
|
316
290
|
contents.gsub!('Code', 'QDM::Code')
|
|
291
|
+
contents.gsub!(' Id', ' QDM::Id')
|
|
317
292
|
contents.gsub!('Interval', 'QDM::Interval')
|
|
318
293
|
contents.gsub!('Quantity', 'QDM::Quantity')
|
|
319
294
|
File.open(file_name, 'w') { |file| file.puts contents }
|
|
@@ -337,7 +312,8 @@ end
|
|
|
337
312
|
Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
|
338
313
|
contents = ''
|
|
339
314
|
File.open(file_name).each_line.with_index do |line, index|
|
|
340
|
-
line.gsub!("\n", " < DataElement\n") if index.zero? && !file_name.include?('/patient.rb') && !file_name.include?('/id.rb')
|
|
315
|
+
line.gsub!("\n", " < DataElement\n") if index.zero? && !file_name.include?('/patient.rb') && !file_name.include?('/id.rb') && !file_name.include?('/component.rb') && !file_name.include?('/facility_location.rb')
|
|
316
|
+
line.gsub!("\n", " < Attribute\n") if index.zero? && (file_name.include?('/component.rb') || file_name.include?('/facility_location.rb'))
|
|
341
317
|
contents += "module QDM\n # #{file_name}\n #{line.gsub('QDM::', '')}" if index.zero?
|
|
342
318
|
contents += ' ' unless index.zero? || line.blank?
|
|
343
319
|
contents += line unless index.zero?
|
|
@@ -346,4 +322,24 @@ Dir.glob(ruby_models_path + '*.rb').each do |file_name|
|
|
|
346
322
|
File.open(file_name, 'w') { |file| file.puts contents }
|
|
347
323
|
end
|
|
348
324
|
|
|
325
|
+
puts 'Moving Attribute Schemas To Their Own Directory'
|
|
326
|
+
# Create ruby attributes directory if it doesn't exist, directory won't exist in test mode
|
|
327
|
+
if IS_TEST
|
|
328
|
+
Dir.mkdir(ruby_models_path + 'attributes')
|
|
329
|
+
Dir.mkdir(js_models_path + 'attributes')
|
|
330
|
+
end
|
|
331
|
+
if File.exist?(ruby_models_path + 'facility_location.rb')
|
|
332
|
+
File.rename ruby_models_path + 'facility_location.rb', ruby_models_path + 'attributes/facility_location.rb'
|
|
333
|
+
File.rename js_models_path + 'FacilityLocation.js', js_models_path + 'attributes/FacilityLocation.js'
|
|
334
|
+
end
|
|
335
|
+
if File.exist?(ruby_models_path + 'component.rb')
|
|
336
|
+
File.rename ruby_models_path + 'component.rb', ruby_models_path + 'attributes/component.rb'
|
|
337
|
+
File.rename js_models_path + 'Component.js', js_models_path + 'attributes/Component.js'
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
puts 'Create hqmfOid to datatype map as json file'
|
|
341
|
+
f = File.open('app/models/hqmfOid_to_datatype_map.json', 'w')
|
|
342
|
+
f.write(JSON.pretty_generate(hqmfOid_to_datatype_map))
|
|
343
|
+
f.close
|
|
344
|
+
|
|
349
345
|
puts 'Done.'
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
require('nokogiri')
|
|
2
|
+
require_relative '../app/models/models'
|
|
3
|
+
require('generate_types')
|
|
4
|
+
|
|
5
|
+
module QDM
|
|
6
|
+
# PatientGeneration module can be used to generate patients with dataElements that are populated
|
|
7
|
+
# with every possible attribute that said type supports
|
|
8
|
+
module PatientGeneration
|
|
9
|
+
# Generates patient(s) with fully-loaded dataElements if new_patient_for_each_type is false then a
|
|
10
|
+
# single patient will be returned that has every data element on it
|
|
11
|
+
def self.generate_exhastive_data_element_patients(new_patient_for_each_type = true, model_info_file = 'qdm-modelinfo-5.4.xml')
|
|
12
|
+
datatypes = get_datatypes(File.join(File.dirname(__FILE__), "../modelinfo/#{model_info_file}"))
|
|
13
|
+
patients = []
|
|
14
|
+
cqm_patient = nil
|
|
15
|
+
qdm_patient = nil
|
|
16
|
+
datatypes.each do |type|
|
|
17
|
+
next if type.include? 'PatientCharacteristic'
|
|
18
|
+
# 1 Patient Per data element type containing negated and non-negated type (if negatable)
|
|
19
|
+
if cqm_patient.nil? || qdm_patient.nil? || new_patient_for_each_type
|
|
20
|
+
cqm_patient = QDM::BaseTypeGeneration.generate_cqm_patient(type)
|
|
21
|
+
qdm_patient = QDM::BaseTypeGeneration.generate_qdm_patient
|
|
22
|
+
# Add patient characteristics
|
|
23
|
+
sex = generate_loaded_datatype('QDM::PatientCharacteristicSex')
|
|
24
|
+
race = generate_loaded_datatype('QDM::PatientCharacteristicRace')
|
|
25
|
+
ethnicity = generate_loaded_datatype('QDM::PatientCharacteristicEthnicity')
|
|
26
|
+
birthdate = generate_loaded_datatype('QDM::PatientCharacteristicBirthdate')
|
|
27
|
+
qdm_patient.dataElements.push(sex)
|
|
28
|
+
qdm_patient.dataElements.push(race)
|
|
29
|
+
qdm_patient.dataElements.push(ethnicity)
|
|
30
|
+
qdm_patient.dataElements.push(birthdate)
|
|
31
|
+
cqm_patient.qdmPatient = qdm_patient
|
|
32
|
+
end
|
|
33
|
+
data_element = generate_loaded_datatype(type)
|
|
34
|
+
qdm_patient.dataElements.push(data_element)
|
|
35
|
+
# if type is negatable, add a negated version to the patient
|
|
36
|
+
if data_element.fields.keys.include? 'negationRationale'
|
|
37
|
+
negated_data_element = generate_loaded_datatype(type, true)
|
|
38
|
+
qdm_patient.dataElements.push(negated_data_element)
|
|
39
|
+
end
|
|
40
|
+
patients.push(cqm_patient)
|
|
41
|
+
end
|
|
42
|
+
patients
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.get_datatypes(modelinfo_file)
|
|
46
|
+
modelinfo = File.open(modelinfo_file) { |f| Nokogiri::XML(f) }
|
|
47
|
+
datatypes = []
|
|
48
|
+
# Loop through each typeInfo node (each of these is a QDM datatype)
|
|
49
|
+
modelinfo.xpath('//ns4:typeInfo').each do |type|
|
|
50
|
+
# Grab the name of this QDM datatype
|
|
51
|
+
datatype_name = type.attributes['name'].value.split('.').last
|
|
52
|
+
exclusion_array = %w[Component Id Patient ResultComponent FacilityLocation]
|
|
53
|
+
# Store datatype and its attributes (reject irrelevant datatypes)
|
|
54
|
+
next if datatype_name.include?('Negative') || datatype_name.include?('Positive') ||
|
|
55
|
+
datatype_name.include?('QDMBaseType') || (exclusion_array.include? datatype_name)
|
|
56
|
+
datatypes.push(datatype_name)
|
|
57
|
+
end
|
|
58
|
+
datatypes
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.generate_loaded_datatype(data_element_type, negate_data_element = false)
|
|
62
|
+
data_element = QDM.const_get(data_element_type).new
|
|
63
|
+
fields = data_element.typed_attributes.keys
|
|
64
|
+
fields.each do |field_name|
|
|
65
|
+
# Ignore these fields, they are used by mongo
|
|
66
|
+
next if %w[_id _type].include? field_name
|
|
67
|
+
if !data_element[field_name] || data_element[field_name] == []
|
|
68
|
+
populate_fields(field_name, data_element, negate_data_element)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
data_element
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.populate_fields(field_name, data_element, negate_data_element)
|
|
75
|
+
# There are certain fields that we want to populate manually
|
|
76
|
+
if field_name == 'description'
|
|
77
|
+
# Skip the setting of patient description for now
|
|
78
|
+
elsif field_name == 'codeListId'
|
|
79
|
+
# Skip the setting code list id. It is not used in a patient's data_element, only in the measure's
|
|
80
|
+
elsif field_name == 'negationRationale'
|
|
81
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_code_field if negate_data_element
|
|
82
|
+
elsif field_name == 'components'
|
|
83
|
+
data_element[field_name] = [QDM::BaseTypeGeneration.generate_component]
|
|
84
|
+
elsif field_name == 'result'
|
|
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
|
+
# TODO: Populate dataElementCodes with codes specifically for data element type
|
|
89
|
+
data_element[field_name] = [QDM::BaseTypeGeneration.generate_code_field]
|
|
90
|
+
elsif field_name == 'facilityLocations'
|
|
91
|
+
# TODO: Randomize number of facility locations added
|
|
92
|
+
data_element[field_name] = [QDM::BaseTypeGeneration.generate_facility_location]
|
|
93
|
+
elsif field_name == 'facilityLocation'
|
|
94
|
+
# TODO: Randomize number of facility locations added
|
|
95
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_facility_location
|
|
96
|
+
elsif field_name == 'targetOutcome'
|
|
97
|
+
# TODO: Randomize type of targetOutcome, use code for now
|
|
98
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_code_field
|
|
99
|
+
else
|
|
100
|
+
# Fallback to populating fields by expected type
|
|
101
|
+
populate_field_by_type(field_name, data_element)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.populate_field_by_type(field_name, data_element)
|
|
106
|
+
field_type = data_element.fields[field_name].type
|
|
107
|
+
if field_type == QDM::Code
|
|
108
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_code_field
|
|
109
|
+
elsif field_type == DateTime
|
|
110
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_datetime
|
|
111
|
+
elsif field_type == QDM::Interval
|
|
112
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_date_interval_field
|
|
113
|
+
elsif field_type == QDM::Quantity
|
|
114
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_quantity
|
|
115
|
+
elsif field_type == QDM::Id
|
|
116
|
+
data_element[field_name] = QDM::BaseTypeGeneration.generate_qdm_id
|
|
117
|
+
elsif field_type == Integer
|
|
118
|
+
# TODO: randomize value
|
|
119
|
+
data_element[field_name] = 3
|
|
120
|
+
else
|
|
121
|
+
puts("Unknown Type in patient generator for field #{field_name}, type #{field_type} in #{data_element._type}")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require_relative '../app/models/models'
|
|
2
|
+
|
|
3
|
+
module QDM
|
|
4
|
+
# BaseTypeGeneration contains functions to randomly generate basetypes used by PatientGeneration
|
|
5
|
+
module BaseTypeGeneration
|
|
6
|
+
def self.generate_component
|
|
7
|
+
component = QDM::Component.new
|
|
8
|
+
component.code = generate_code_field
|
|
9
|
+
# TODO: Randomize the type of the result
|
|
10
|
+
component.result = generate_code_field
|
|
11
|
+
component
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.generate_facility_location
|
|
15
|
+
facility_location = QDM::FacilityLocation.new
|
|
16
|
+
facility_location.code = generate_code_field
|
|
17
|
+
facility_location.locationPeriod = generate_date_interval_field
|
|
18
|
+
facility_location
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.generate_code_field
|
|
22
|
+
# TODO: use code with all parameters, possibly randomize parameter values and optional information
|
|
23
|
+
QDM::Code.new('1234', 'SNOMEDCT')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.generate_datetime
|
|
27
|
+
# TODO: Randomize dateTime
|
|
28
|
+
DateTime.new(2019)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.generate_quantity
|
|
32
|
+
# TODO: Randomize value and parameters for Quantity
|
|
33
|
+
QDM::Quantity.new(100, 'mg')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.generate_date_interval_field
|
|
37
|
+
# TODO: Randomize dates in interval and open/closed parameters
|
|
38
|
+
QDM::Interval.new(DateTime.new(2018), DateTime.new(2019))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.generate_cqm_patient(type)
|
|
42
|
+
cqm_patient = CQM::Patient.new
|
|
43
|
+
cqm_patient.givenNames = [type]
|
|
44
|
+
cqm_patient.familyName = "#{type} Test Patient"
|
|
45
|
+
cqm_patient
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.generate_qdm_patient
|
|
49
|
+
qdm_patient = QDM::Patient.new
|
|
50
|
+
qdm_patient.extendedData = {}
|
|
51
|
+
qdm_patient.extendedData['medical_record_number'] = '1'
|
|
52
|
+
qdm_patient.birthDatetime = generate_datetime
|
|
53
|
+
qdm_patient
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.generate_qdm_id
|
|
57
|
+
# TODO: randomize values
|
|
58
|
+
qdm_id = QDM::Id.new
|
|
59
|
+
qdm_id.value = 'TestValue'
|
|
60
|
+
qdm_id.namingSystem = 'TestNamingSystem'
|
|
61
|
+
qdm_id
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|