cqm-converter 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,202 @@
1
+ # CQM Converter module for HDS models.
2
+ module CQM::Converter
3
+ # CQM Converter class utilities.
4
+ class Utils
5
+ # Convert QDM codes structure to HDS codes structure.
6
+ def self.qdm_codes_to_hds_codes(qdm_codes)
7
+ codes = {}
8
+ qdm_codes.each do |qdm_code|
9
+ qdm_code = qdm_code.stringify_keys
10
+ code_system = qdm_code['codeSystem']
11
+ code_system = qdm_code['code_system'] if qdm_code['code_system']
12
+ codes[code_system] = [] unless codes.key? code_system
13
+ codes[code_system] << qdm_code['code'] if qdm_code['code']
14
+ end
15
+ codes
16
+ end
17
+
18
+ # Convert HDS codes structure to QDM codes structure.
19
+ def self.hds_codes_to_qdm_codes(hds_codes)
20
+ qdm_codes = []
21
+ hds_codes.each do |code_system, codes|
22
+ codes.each do |code|
23
+ qdm_codes << { codeSystem: code_system, code: code }
24
+ end
25
+ end
26
+ qdm_codes
27
+ end
28
+
29
+ # This helper method looks at the current state of cqm-models, and builds a hash of
30
+ # datatype models and their attributes.
31
+ def self.gather_qdm_model_attrs
32
+ qdm_model_attrs = {}
33
+ QDM.constants.each do |model|
34
+ if QDM.const_get(model).respond_to?('fields')
35
+ qdm_model_attrs[model.to_s] = QDM.const_get(model).fields.keys.map! { |a| a.camelize(:lower) }
36
+ end
37
+ end
38
+ # TODO: These fields are currently not supported. See:
39
+ # https://github.com/projecttacoma/cql_qdm_patientapi/search?q=does+not+currently+support
40
+ qdm_model_attrs['SubstanceAdministered'].delete('frequency')
41
+ qdm_model_attrs['SubstanceOrder'].delete('refills')
42
+ qdm_model_attrs['PatientCharacteristicExpired'].delete('cause')
43
+ qdm_model_attrs['MedicationDispensed'].delete('refills')
44
+ qdm_model_attrs['MedicationDispensed'].delete('frequency')
45
+ qdm_model_attrs['MedicationOrder'].delete('refills')
46
+ qdm_model_attrs['MedicationOrder'].delete('frequency')
47
+ qdm_model_attrs['MedicationActive'].delete('frequency')
48
+ qdm_model_attrs['MedicationAdministered'].delete('frequency')
49
+ qdm_model_attrs['MedicationDischarge'].delete('refills')
50
+ qdm_model_attrs['MedicationDischarge'].delete('frequency')
51
+ qdm_model_attrs
52
+ end
53
+
54
+ # Parse the cql_qdm_patientapi datatypes to infer how to construct corresponding
55
+ # HDS entries.
56
+ def self.gather_qdm_to_hds_mappings(qdm_model_attrs = nil)
57
+ qdm_model_attrs = gather_qdm_model_attrs if qdm_model_attrs.nil?
58
+ cql_qdm_patientapi_spec = Gem::Specification.find_by_name('cql_qdm_patientapi')
59
+ datatypes = Dir.glob(cql_qdm_patientapi_spec.gem_dir + '/app/assets/javascripts/datatypes/*.coffee')
60
+ # Read in all datatypes
61
+ datatypes_contents = ''
62
+ datatypes.each do |datatype|
63
+ datatypes_contents += File.read(datatype) + "\n"
64
+ end
65
+ datatypes_contents += 'class'
66
+ # Construct the mappings
67
+ qdm_to_hds_mappings = {}
68
+ qdm_model_attrs.each do |datatype, attributes|
69
+ datatype_pattern = /#{datatype} extends CQL_QDM.QDMDatatype.*?class/m
70
+ next unless (dc_class = datatype_pattern.match(datatypes_contents))
71
+ qdm_to_hds_mappings[datatype] = {}
72
+ attributes.each do |attribute|
73
+ attribute_pattern = /@_#{attribute}(Low|High| ).*?@entry.*?$/
74
+ dc_class.to_s.to_enum(:scan, attribute_pattern).map do
75
+ dc_attr = Regexp.last_match
76
+ # Handle possible mixed values.
77
+ if dc_attr.to_s.include? 'Low'
78
+ qdm_to_hds_mappings[datatype][attribute] = {} unless qdm_to_hds_mappings[datatype][attribute]
79
+ qdm_to_hds_mappings[datatype][attribute][:low] = dc_attr.to_s[/@entry.(.*?)(\)|$|\?)/m, 1]
80
+ elsif dc_attr.to_s.include? 'High'
81
+ qdm_to_hds_mappings[datatype][attribute] = {} unless qdm_to_hds_mappings[datatype][attribute]
82
+ qdm_to_hds_mappings[datatype][attribute][:high] = dc_attr.to_s[/@entry.(.*?)(\)|$|\?)/m, 1]
83
+ else
84
+ qdm_to_hds_mappings[datatype][attribute] = dc_attr.to_s[/@entry.(.*?)(\)|$|\?)/m, 1]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ qdm_to_hds_mappings
90
+ end
91
+
92
+ # Builds JavaScript to assist the HDS Record to QDM Patient conversion.
93
+ def self.hds_to_qdm_js(js_dependencies, record, qdm_model_attrs)
94
+ record_json = JSON.parse(record.to_json)
95
+ # Bonnie changes start_date and end_date in the front end before calculation,
96
+ # and this is what is expected by the cql_qdm_patientapi, so make that change
97
+ # before generating the executable JavaScript.
98
+ record_json = record_json.deep_transform_keys { |key| key.to_s == 'start_date' ? 'start_time' : key }
99
+ record_json = record_json.deep_transform_keys { |key| key.to_s == 'end_date' ? 'end_time' : key }
100
+ <<-JS
101
+ window = {};
102
+ #{js_dependencies};
103
+ function PatientWrapper() {
104
+ }
105
+ PatientWrapper.prototype.get = function(attr) {
106
+ const contents = #{record_json.to_json};
107
+ return contents[attr];
108
+ }
109
+ cql = window.cql;
110
+ patient = new CQL_QDM.CQLPatient(new PatientWrapper());
111
+ raw_datatypes = patient.buildDatatypes();
112
+ qdm_model_attrs = #{qdm_model_attrs.to_json};
113
+ processed_datatypes = {};
114
+ // Loop over each QDM datatype.
115
+ Object.keys(raw_datatypes).forEach(function(key) {
116
+ processed_datatypes[key] = [];
117
+ // Collect the values of the QDM attributes.
118
+ raw_datatypes[key].forEach(function(datatype) {
119
+ results = {};
120
+ qdm_model_attrs[datatype.constructor.name].forEach(function(method) {
121
+ // Call the datatype attribute method to get its value.
122
+ if (datatype[method]) {
123
+ result = datatype[method]();
124
+ // Handle CQL execution engine type results.
125
+ if (result && result['toJSON']) {
126
+ results[method] = result.toJSON();
127
+ } else {
128
+ results[method] = result;
129
+ }
130
+ }
131
+ });
132
+ // Add codes to result.
133
+ results['dataElementCodes'] = datatype['getCode']();
134
+ // Add description to result.
135
+ results['description'] = datatype['entry']['description'];
136
+ // Add oid to result.
137
+ results['hqmfOid'] = datatype['entry']['oid'];
138
+ processed_datatypes[key].push(results);
139
+ });
140
+ });
141
+ return processed_datatypes;
142
+ JS
143
+ end
144
+
145
+ # Adjust improper date times from the cql_qdm_patientapi.
146
+ def self.date_time_adjuster(results)
147
+ if results.is_a?(Hash) && results.key?('year') && results.key?('minute')
148
+ DateTime.new(results['year'], results['month'], results['day'], results['hour'], results['minute'], results['second'], results['millisecond']).to_s
149
+ elsif results.is_a?(Hash)
150
+ results.each do |key, value|
151
+ results[key] = date_time_adjuster(value)
152
+ end
153
+ elsif results.is_a?(Array)
154
+ results.map! { |result| date_time_adjuster(result) }
155
+ else
156
+ results
157
+ end
158
+ end
159
+
160
+ # Remove any 'infinity' dates. The cql_qdm_patientapi adds an end time
161
+ # of 'infinity' if an event does not have an end time. Remove this when
162
+ # converting back to HDS from QDM.
163
+ def self.fix_infinity_dates(results)
164
+ if results.is_a?(Hash) && results.key?('end_time')
165
+ results.delete('end_time') if results['end_time'].to_s == '253402300799'
166
+ elsif results.is_a?(Hash)
167
+ results.each do |key, value|
168
+ results[key] = fix_infinity_dates(value)
169
+ end
170
+ elsif results.is_a?(Array)
171
+ results.map! { |result| fix_infinity_dates(result) }
172
+ else
173
+ results
174
+ end
175
+ end
176
+
177
+ # Helper method to handle mismatched HDS Class names for QDM things.
178
+ def self.qdm_to_hds_class_type(category)
179
+ if category.to_s.include? 'diagnostic'
180
+ 'procedure'
181
+ elsif category.to_s.include? 'physical_exam'
182
+ 'procedure'
183
+ elsif category.to_s.include? 'intervention'
184
+ 'procedure'
185
+ elsif category.to_s.include? 'device'
186
+ 'medical_equipment'
187
+ elsif category.to_s.include? 'laboratory'
188
+ 'vital_sign'
189
+ elsif category.to_s.include? 'substance'
190
+ 'medication'
191
+ elsif category.to_s.include? 'immunization'
192
+ 'medication'
193
+ else
194
+ category
195
+ end
196
+ end
197
+
198
+ def self.code_system_helper(code_system)
199
+ HealthDataStandards::Util::CodeSystemHelper::CODE_SYSTEMS.key(code_system)
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,16 @@
1
+ namespace :cqm do
2
+ namespace :hds do
3
+ desc %(Convert an HDS Record, represented as a JSON file, into a QDM
4
+ Patient, represented as JSON.
5
+
6
+ You must specify an input HDS Record JSON file. The result will be
7
+ output to STDOUT.
8
+
9
+ $ rake cqm:hds:to_qdm RECORD=spec/fixtures/hds/records/ep/1.json)
10
+ task :to_qdm do
11
+ converter = CQM::Converter::HDSRecord.new
12
+ record = Record.new.from_json(File.read(ENV['RECORD']))
13
+ puts JSON.pretty_generate(JSON.parse(converter.to_qdm(record).to_json(except: '_id')))
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ namespace :cqm do
2
+ namespace :qdm do
3
+ desc %(Convert a QDM Patient, represented as a JSON file, into an HDS
4
+ Record, represented as JSON.
5
+
6
+ You must specify an input QDM Patient JSON file. The result will be
7
+ output to STDOUT.
8
+
9
+ $ rake cqm:qdm:to_hds RECORD=spec/fixtures/qdm/patients/ep/1.json)
10
+ task :to_hds do
11
+ converter = CQM::Converter::QDMPatient.new
12
+ patient = QDM::Patient.new.from_json(File.read(ENV['RECORD']))
13
+ puts JSON.pretty_generate(JSON.parse(converter.to_hds(patient).to_json(except: '_id', methods: :_type)))
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,230 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cqm-converter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - aholmes@mitre.org
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-04-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coffee-script
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cqm-models
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.7.2
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.7.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: execjs
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: health-data-standards
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 4.0.3
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 4.0.3
111
+ - !ruby/object:Gem::Dependency
112
+ name: momentjs-rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: sprockets
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ description:
182
+ email:
183
+ - aholmes@mitre.org
184
+ executables: []
185
+ extensions: []
186
+ extra_rdoc_files: []
187
+ files:
188
+ - ".github/PULL_REQUEST_TEMPLATE.md"
189
+ - ".gitignore"
190
+ - ".rspec"
191
+ - ".rubocop.yml"
192
+ - ".travis.yml"
193
+ - Gemfile
194
+ - LICENSE.txt
195
+ - README.md
196
+ - Rakefile
197
+ - bin/console
198
+ - bin/setup
199
+ - cqm-converter.gemspec
200
+ - lib/cqm/converter.rb
201
+ - lib/cqm/converter/hds_record.rb
202
+ - lib/cqm/converter/qdm_patient.rb
203
+ - lib/cqm/converter/utils.rb
204
+ - lib/tasks/hds.rake
205
+ - lib/tasks/qdm.rake
206
+ homepage: https://github.com/projecttacoma/cqm-converter
207
+ licenses:
208
+ - Apache-2.0
209
+ metadata: {}
210
+ post_install_message:
211
+ rdoc_options: []
212
+ require_paths:
213
+ - lib
214
+ required_ruby_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ version: '0'
219
+ required_rubygems_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ requirements: []
225
+ rubyforge_project:
226
+ rubygems_version: 2.5.2
227
+ signing_key:
228
+ specification_version: 4
229
+ summary: HDS <=> QDM Model Converter
230
+ test_files: []