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.
Files changed (188) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintrc.json +10 -2
  3. data/.github/PULL_REQUEST_TEMPLATE.md +0 -4
  4. data/.rubocop.yml +3 -0
  5. data/.travis.yml +31 -1
  6. data/README.md +59 -2
  7. data/app/assets/javascripts/AdverseEvent.js +16 -7
  8. data/app/assets/javascripts/AllDataElements.js +6 -6
  9. data/app/assets/javascripts/AllergyIntolerance.js +16 -7
  10. data/app/assets/javascripts/AssessmentOrder.js +15 -6
  11. data/app/assets/javascripts/AssessmentPerformed.js +16 -7
  12. data/app/assets/javascripts/AssessmentRecommended.js +16 -7
  13. data/app/assets/javascripts/CareGoal.js +16 -7
  14. data/app/assets/javascripts/CommunicationPerformed.js +15 -6
  15. data/app/assets/javascripts/DeviceApplied.js +16 -7
  16. data/app/assets/javascripts/DeviceOrder.js +16 -7
  17. data/app/assets/javascripts/DeviceRecommended.js +16 -7
  18. data/app/assets/javascripts/Diagnosis.js +16 -7
  19. data/app/assets/javascripts/DiagnosticStudyOrder.js +16 -7
  20. data/app/assets/javascripts/DiagnosticStudyPerformed.js +16 -7
  21. data/app/assets/javascripts/DiagnosticStudyRecommended.js +16 -7
  22. data/app/assets/javascripts/EncounterOrder.js +16 -7
  23. data/app/assets/javascripts/EncounterPerformed.js +16 -7
  24. data/app/assets/javascripts/EncounterRecommended.js +16 -7
  25. data/app/assets/javascripts/FamilyHistory.js +16 -7
  26. data/app/assets/javascripts/Id.js +7 -3
  27. data/app/assets/javascripts/ImmunizationAdministered.js +16 -7
  28. data/app/assets/javascripts/ImmunizationOrder.js +16 -7
  29. data/app/assets/javascripts/IndividualResult.js +7 -2
  30. data/app/assets/javascripts/InterventionOrder.js +16 -7
  31. data/app/assets/javascripts/InterventionPerformed.js +16 -7
  32. data/app/assets/javascripts/InterventionRecommended.js +16 -7
  33. data/app/assets/javascripts/LaboratoryTestOrder.js +16 -7
  34. data/app/assets/javascripts/LaboratoryTestPerformed.js +16 -7
  35. data/app/assets/javascripts/LaboratoryTestRecommended.js +16 -7
  36. data/app/assets/javascripts/MedicationActive.js +16 -7
  37. data/app/assets/javascripts/MedicationAdministered.js +16 -7
  38. data/app/assets/javascripts/MedicationDischarge.js +16 -7
  39. data/app/assets/javascripts/MedicationDispensed.js +16 -7
  40. data/app/assets/javascripts/MedicationOrder.js +16 -7
  41. data/app/assets/javascripts/Participation.js +15 -6
  42. data/app/assets/javascripts/PatientCareExperience.js +16 -7
  43. data/app/assets/javascripts/PatientCharacteristic.js +16 -7
  44. data/app/assets/javascripts/PatientCharacteristicBirthdate.js +16 -7
  45. data/app/assets/javascripts/PatientCharacteristicClinicalTrialParticipant.js +16 -7
  46. data/app/assets/javascripts/PatientCharacteristicEthnicity.js +16 -7
  47. data/app/assets/javascripts/PatientCharacteristicExpired.js +16 -7
  48. data/app/assets/javascripts/PatientCharacteristicPayer.js +16 -7
  49. data/app/assets/javascripts/PatientCharacteristicRace.js +16 -7
  50. data/app/assets/javascripts/PatientCharacteristicSex.js +16 -7
  51. data/app/assets/javascripts/PhysicalExamOrder.js +16 -7
  52. data/app/assets/javascripts/PhysicalExamPerformed.js +16 -7
  53. data/app/assets/javascripts/PhysicalExamRecommended.js +16 -7
  54. data/app/assets/javascripts/ProcedureOrder.js +16 -7
  55. data/app/assets/javascripts/ProcedurePerformed.js +16 -7
  56. data/app/assets/javascripts/ProcedureRecommended.js +16 -7
  57. data/app/assets/javascripts/ProviderCareExperience.js +16 -7
  58. data/app/assets/javascripts/ProviderCharacteristic.js +16 -7
  59. data/app/assets/javascripts/QDMPatient.js +262 -0
  60. data/app/assets/javascripts/Result.js +7 -2
  61. data/app/assets/javascripts/ResultComponent.js +14 -6
  62. data/app/assets/javascripts/SubstanceAdministered.js +16 -7
  63. data/app/assets/javascripts/SubstanceOrder.js +16 -7
  64. data/app/assets/javascripts/SubstanceRecommended.js +16 -7
  65. data/app/assets/javascripts/Symptom.js +16 -7
  66. data/app/assets/javascripts/attributes/Component.js +30 -0
  67. data/app/assets/javascripts/attributes/FacilityLocation.js +30 -0
  68. data/app/assets/javascripts/basetypes/Any.js +1 -1
  69. data/app/assets/javascripts/basetypes/Code.js +1 -1
  70. data/app/assets/javascripts/basetypes/DataElement.js +2 -1
  71. data/app/assets/javascripts/basetypes/DateTime.js +5 -1
  72. data/app/assets/javascripts/basetypes/Interval.js +1 -1
  73. data/app/assets/javascripts/basetypes/Quantity.js +1 -1
  74. data/app/assets/javascripts/basetypes/Ratio.js +1 -1
  75. data/app/assets/javascripts/browser.js +3 -0
  76. data/app/assets/javascripts/cqm/AllCQMModels.js +24 -0
  77. data/app/assets/javascripts/cqm/CQLLibrary.js +32 -0
  78. data/app/assets/javascripts/cqm/CQLStatementDependency.js +28 -0
  79. data/app/assets/javascripts/cqm/Concept.js +17 -0
  80. data/app/assets/javascripts/cqm/Measure.js +108 -0
  81. data/app/assets/javascripts/{MeasurePackage.js → cqm/MeasurePackage.js} +9 -3
  82. data/app/assets/javascripts/cqm/Patient.js +38 -0
  83. data/app/assets/javascripts/cqm/PopulationSet.js +88 -0
  84. data/app/assets/javascripts/cqm/Provider.js +44 -0
  85. data/app/assets/javascripts/cqm/ValueSet.js +26 -0
  86. data/app/assets/javascripts/index.js +18 -8
  87. data/app/models/{qdm/tacoma → cqm}/concept.rb +4 -4
  88. data/app/models/cqm/cql_library.rb +22 -0
  89. data/app/models/cqm/cql_statement_dependency.rb +24 -0
  90. data/app/models/cqm/measure.rb +105 -0
  91. data/app/models/{qdm/tacoma → cqm}/measure_package.rb +2 -2
  92. data/app/models/cqm/patient.rb +20 -0
  93. data/app/models/cqm/population_set.rb +86 -0
  94. data/app/models/cqm/provider.rb +110 -0
  95. data/app/models/cqm/valueset.rb +15 -0
  96. data/app/models/hqmfOid_to_datatype_map.json +55 -0
  97. data/app/models/model_finder.rb +12 -0
  98. data/app/models/models.rb +18 -6
  99. data/app/models/qdm/adverse_event.rb +3 -2
  100. data/app/models/qdm/allergy_intolerance.rb +2 -1
  101. data/app/models/qdm/assessment_order.rb +1 -0
  102. data/app/models/qdm/assessment_performed.rb +2 -1
  103. data/app/models/qdm/assessment_recommended.rb +2 -1
  104. data/app/models/qdm/attributes/attribute.rb +63 -0
  105. data/app/models/qdm/{component.rb → attributes/component.rb} +1 -1
  106. data/app/models/qdm/{facility_location.rb → attributes/facility_location.rb} +1 -1
  107. data/app/models/qdm/basetypes/data_element.rb +21 -6
  108. data/app/models/qdm/basetypes/interval.rb +2 -1
  109. data/app/models/qdm/care_goal.rb +2 -1
  110. data/app/models/qdm/communication_performed.rb +1 -0
  111. data/app/models/qdm/device_applied.rb +2 -1
  112. data/app/models/qdm/device_order.rb +2 -1
  113. data/app/models/qdm/device_recommended.rb +2 -1
  114. data/app/models/qdm/diagnosis.rb +2 -1
  115. data/app/models/qdm/diagnostic_study_order.rb +2 -1
  116. data/app/models/qdm/diagnostic_study_performed.rb +3 -2
  117. data/app/models/qdm/diagnostic_study_recommended.rb +2 -1
  118. data/app/models/qdm/encounter_order.rb +3 -2
  119. data/app/models/qdm/encounter_performed.rb +2 -1
  120. data/app/models/qdm/encounter_recommended.rb +3 -2
  121. data/app/models/qdm/family_history.rb +2 -1
  122. data/app/models/qdm/immunization_administered.rb +2 -1
  123. data/app/models/qdm/immunization_order.rb +2 -1
  124. data/app/models/qdm/intervention_order.rb +2 -1
  125. data/app/models/qdm/intervention_performed.rb +2 -1
  126. data/app/models/qdm/intervention_recommended.rb +2 -1
  127. data/app/models/qdm/laboratory_test_order.rb +2 -1
  128. data/app/models/qdm/laboratory_test_performed.rb +2 -1
  129. data/app/models/qdm/laboratory_test_recommended.rb +2 -1
  130. data/app/models/qdm/medication_active.rb +2 -1
  131. data/app/models/qdm/medication_administered.rb +2 -1
  132. data/app/models/qdm/medication_discharge.rb +2 -1
  133. data/app/models/qdm/medication_dispensed.rb +4 -3
  134. data/app/models/qdm/medication_order.rb +3 -2
  135. data/app/models/qdm/participation.rb +1 -0
  136. data/app/models/qdm/patient.rb +7 -4
  137. data/app/models/qdm/patient_care_experience.rb +2 -1
  138. data/app/models/qdm/patient_characteristic.rb +2 -1
  139. data/app/models/qdm/patient_characteristic_birthdate.rb +2 -1
  140. data/app/models/qdm/patient_characteristic_clinical_trial_participant.rb +2 -1
  141. data/app/models/qdm/patient_characteristic_ethnicity.rb +2 -1
  142. data/app/models/qdm/patient_characteristic_expired.rb +2 -1
  143. data/app/models/qdm/patient_characteristic_payer.rb +2 -1
  144. data/app/models/qdm/patient_characteristic_race.rb +2 -1
  145. data/app/models/qdm/patient_characteristic_sex.rb +2 -1
  146. data/app/models/qdm/physical_exam_order.rb +2 -1
  147. data/app/models/qdm/physical_exam_performed.rb +2 -1
  148. data/app/models/qdm/physical_exam_recommended.rb +2 -1
  149. data/app/models/qdm/procedure_order.rb +2 -1
  150. data/app/models/qdm/procedure_performed.rb +2 -1
  151. data/app/models/qdm/procedure_recommended.rb +2 -1
  152. data/app/models/qdm/provider_care_experience.rb +2 -1
  153. data/app/models/qdm/provider_characteristic.rb +2 -1
  154. data/app/models/qdm/substance_administered.rb +2 -1
  155. data/app/models/qdm/substance_order.rb +2 -1
  156. data/app/models/qdm/substance_recommended.rb +2 -1
  157. data/app/models/qdm/symptom.rb +2 -1
  158. data/bin/build_cql_execution.sh +8 -0
  159. data/bin/validate_browser.sh +17 -0
  160. data/bin/validate_generator.sh +12 -0
  161. data/cqm-models.gemspec +5 -2
  162. data/data/oids_qdm_5.4.json +321 -0
  163. data/data/{oids.json → oids_unversioned.json} +0 -0
  164. data/dist/browser.js +102937 -0
  165. data/dist/index.js +36637 -36540
  166. data/lib/generate_models.rb +89 -93
  167. data/lib/generate_patients.rb +125 -0
  168. data/lib/generate_types.rb +64 -0
  169. data/lib/generators/custom_mongo/model.rb.tt +20 -0
  170. data/lib/generators/custom_mongo/model_generator.rb +34 -0
  171. data/notice.md +9 -0
  172. data/package.json +12 -6
  173. data/templates/id_template.js.erb +8 -3
  174. data/templates/index_template.js.erb +18 -8
  175. data/templates/models_template.rb.erb +20 -4
  176. data/templates/mongoose_template.js.erb +23 -15
  177. data/templates/patient_extension.rb.erb +7 -4
  178. data/templates/patient_template.js.erb +88 -87
  179. data/yarn.lock +552 -347
  180. metadata +86 -19
  181. data/app/assets/javascripts/Component.js +0 -26
  182. data/app/assets/javascripts/Concept.js +0 -14
  183. data/app/assets/javascripts/FacilityLocation.js +0 -26
  184. data/app/assets/javascripts/Measure.js +0 -78
  185. data/app/assets/javascripts/Patient.js +0 -262
  186. data/app/assets/javascripts/ValueSet.js +0 -30
  187. data/app/models/qdm/tacoma/measure.rb +0 -174
  188. data/app/models/qdm/tacoma/valueset.rb +0 -68
@@ -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
- # Store datatype and its attributes (reject irrelevant datatypes)
110
- next if datatype_name.include?('Negative') || datatype_name.include?('Positive') || datatype_name.include?('QDMBaseType')
111
- datatypes[datatype_name] = attributes
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, attributes|
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, attributes|
139
- Rails::Generators.invoke('mongoid:model', [base_module + datatype] + attributes.collect { |attribute| attribute[:name] + ':' + TYPE_LOOKUP_RB[attribute[:type]] } + extra_fields_rb)
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
- Patient: 'templates/patient_template.js.erb',
187
+ QDMPatient: 'templates/patient_template.js.erb',
167
188
  Id: 'templates/id_template.js.erb'
168
189
  }
169
- datatypes.each do |datatype, attributes|
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 + extra_fields_js # this field gets used in the template
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
- # Add HQMF oid (if it exists in the given HQMF oid mapping file)
215
- dc_name = File.basename(file_name, '.*')
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