cqm-models 1.0.3 → 1.1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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