cqm-models 2.0.0 → 3.0.3

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