bonnie_bundler 2.2.3 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/Gemfile.lock +39 -39
- data/bonnie-bundler.gemspec +3 -3
- data/lib/bonnie_bundler.rb +0 -1
- data/lib/measures/loading/cql_loader.rb +234 -100
- data/lib/measures/loading/loader.rb +18 -24
- data/lib/models/cql_measure.rb +9 -0
- data/test/fixtures/CMSAWA_v5_6_Artifacts.zip +0 -0
- data/test/fixtures/CMSAWA_v5_6_Artifacts_missing_component.zip +0 -0
- data/test/fixtures/CMSAWA_v5_6_Artifacts_missing_composite_files.zip +0 -0
- data/test/fixtures/CMSAWA_v5_6_Artifacts_missing_file.zip +0 -0
- data/test/fixtures/not_mat_export.zip +0 -0
- data/test/fixtures/vcr_cassettes/load_composite_measure.yml +7305 -0
- data/test/fixtures/vcr_cassettes/load_composite_measure_with_missing_component.yml +6923 -0
- data/test/fixtures/vcr_cassettes/load_composite_measure_with_missing_composite_files.yml +57 -0
- data/test/fixtures/vcr_cassettes/load_composite_measure_with_missing_file.yml +5938 -0
- data/test/fixtures/vcr_cassettes/multi_library_webcalls.yml +156 -167
- data/test/fixtures/vcr_cassettes/valid_vsac_response.yml +216 -191
- data/test/fixtures/vcr_cassettes/valid_vsac_response_158.yml +112 -116
- data/test/fixtures/vcr_cassettes/valid_vsac_response_158_update.yml +121 -125
- data/test/fixtures/vcr_cassettes/valid_vsac_response_hospice.yml +523 -447
- data/test/fixtures/vcr_cassettes/valid_vsac_response_includes_draft.yml +388 -356
- data/test/fixtures/vcr_cassettes/valid_vsac_response_pvc_unused_libraries.yml +969 -2079
- data/test/fixtures/vcr_cassettes/valid_vsac_response_special_characters.yml +1715 -1715
- data/test/fixtures/vcr_cassettes/vs_loading_500_response.yml +26 -510
- data/test/fixtures/vcr_cassettes/vs_loading_empty_concept_list.yml +26 -26
- data/test/fixtures/vcr_cassettes/vs_loading_release.yml +106 -106
- data/test/fixtures/vcr_cassettes/vs_loading_version.yml +106 -106
- data/test/unit/composite_cql_loader_test.rb +102 -0
- data/test/unit/cql_loader_test.rb +18 -7
- data/test/unit/get_value_sets_from_measure_model_test.rb +1 -1
- data/test/unit/load_mat_export_test.rb +15 -9
- data/test/unit/measure_complexity_test.rb +1 -1
- data/test/unit/measure_diff_test.rb +4 -4
- data/test/unit/storing_mat_export_package_test.rb +1 -2
- data/test/unit/value_set_loading_test.rb +16 -16
- data/test/vcr_setup.rb +1 -1
- metadata +23 -8
- data/lib/measures/loading/base_loader_definition.rb +0 -61
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 973eb1a1c06145e1a7d06a4f1101bf2e35e179e9
|
|
4
|
+
data.tar.gz: 445d5d2057bfb716ce5cbc1e30baa45e58439ab3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3f26b65b570b975bcb4e804e6083f32ef8b0702fcf52c8da48e2b6083dd8de896605d4b5b0f4dd7cd6861b8612d1e9eca10949e8076927390883173884e49cd6
|
|
7
|
+
data.tar.gz: 70c68df147debef6e370d1e8366d338a0e030e1056775111e8224601f06b171b33e1eeceeb2568b8e3740dfda26fecac2c6187e077523e14fdc2a2332969ca34
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
bonnie_bundler (2.2.
|
|
4
|
+
bonnie_bundler (2.2.4)
|
|
5
5
|
diffy (~> 3.0.0)
|
|
6
|
-
health-data-standards (~> 4.3.
|
|
6
|
+
health-data-standards (~> 4.3.2)
|
|
7
7
|
hqmf2js (~> 1.4)
|
|
8
8
|
hquery-patient-api (~> 1.1)
|
|
9
9
|
mongoid (~> 5.0)
|
|
10
10
|
quality-measure-engine (~> 3.2)
|
|
11
|
-
rails (
|
|
11
|
+
rails (>= 4.2, < 6.0)
|
|
12
12
|
roo (~> 1.13)
|
|
13
13
|
rubyzip (~> 1.2, >= 1.2.1)
|
|
14
14
|
simplexml_parser (~> 1.0)
|
|
@@ -17,36 +17,36 @@ PATH
|
|
|
17
17
|
GEM
|
|
18
18
|
remote: https://rubygems.org/
|
|
19
19
|
specs:
|
|
20
|
-
actionmailer (4.2.
|
|
21
|
-
actionpack (= 4.2.
|
|
22
|
-
actionview (= 4.2.
|
|
23
|
-
activejob (= 4.2.
|
|
20
|
+
actionmailer (4.2.11)
|
|
21
|
+
actionpack (= 4.2.11)
|
|
22
|
+
actionview (= 4.2.11)
|
|
23
|
+
activejob (= 4.2.11)
|
|
24
24
|
mail (~> 2.5, >= 2.5.4)
|
|
25
25
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
26
|
-
actionpack (4.2.
|
|
27
|
-
actionview (= 4.2.
|
|
28
|
-
activesupport (= 4.2.
|
|
26
|
+
actionpack (4.2.11)
|
|
27
|
+
actionview (= 4.2.11)
|
|
28
|
+
activesupport (= 4.2.11)
|
|
29
29
|
rack (~> 1.6)
|
|
30
30
|
rack-test (~> 0.6.2)
|
|
31
31
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
32
32
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
33
|
-
actionview (4.2.
|
|
34
|
-
activesupport (= 4.2.
|
|
33
|
+
actionview (4.2.11)
|
|
34
|
+
activesupport (= 4.2.11)
|
|
35
35
|
builder (~> 3.1)
|
|
36
36
|
erubis (~> 2.7.0)
|
|
37
37
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
38
38
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
|
39
|
-
activejob (4.2.
|
|
40
|
-
activesupport (= 4.2.
|
|
39
|
+
activejob (4.2.11)
|
|
40
|
+
activesupport (= 4.2.11)
|
|
41
41
|
globalid (>= 0.3.0)
|
|
42
|
-
activemodel (4.2.
|
|
43
|
-
activesupport (= 4.2.
|
|
42
|
+
activemodel (4.2.11)
|
|
43
|
+
activesupport (= 4.2.11)
|
|
44
44
|
builder (~> 3.1)
|
|
45
|
-
activerecord (4.2.
|
|
46
|
-
activemodel (= 4.2.
|
|
47
|
-
activesupport (= 4.2.
|
|
45
|
+
activerecord (4.2.11)
|
|
46
|
+
activemodel (= 4.2.11)
|
|
47
|
+
activesupport (= 4.2.11)
|
|
48
48
|
arel (~> 6.0)
|
|
49
|
-
activesupport (4.2.
|
|
49
|
+
activesupport (4.2.11)
|
|
50
50
|
i18n (~> 0.7)
|
|
51
51
|
minitest (~> 5.1)
|
|
52
52
|
thread_safe (~> 0.3, >= 0.3.4)
|
|
@@ -65,7 +65,7 @@ GEM
|
|
|
65
65
|
coffee-script-source
|
|
66
66
|
execjs
|
|
67
67
|
coffee-script-source (1.12.2)
|
|
68
|
-
concurrent-ruby (1.
|
|
68
|
+
concurrent-ruby (1.1.3)
|
|
69
69
|
crack (0.4.3)
|
|
70
70
|
safe_yaml (~> 1.0.0)
|
|
71
71
|
crass (1.0.4)
|
|
@@ -84,7 +84,7 @@ GEM
|
|
|
84
84
|
globalid (0.4.1)
|
|
85
85
|
activesupport (>= 4.2.0)
|
|
86
86
|
hashdiff (0.3.6)
|
|
87
|
-
health-data-standards (4.3.
|
|
87
|
+
health-data-standards (4.3.2)
|
|
88
88
|
activesupport (~> 4.2.0)
|
|
89
89
|
builder (~> 3.1)
|
|
90
90
|
erubis (~> 2.7.0)
|
|
@@ -120,7 +120,7 @@ GEM
|
|
|
120
120
|
json (2.1.0)
|
|
121
121
|
libv8 (3.16.14.19)
|
|
122
122
|
log4r (1.1.10)
|
|
123
|
-
loofah (2.2.
|
|
123
|
+
loofah (2.2.3)
|
|
124
124
|
crass (~> 1.0.2)
|
|
125
125
|
nokogiri (>= 1.5.9)
|
|
126
126
|
macaddr (1.7.1)
|
|
@@ -145,7 +145,7 @@ GEM
|
|
|
145
145
|
mongoid (>= 2.0)
|
|
146
146
|
mongoid-tree (2.0.1)
|
|
147
147
|
mongoid (>= 4.0, < 6.0)
|
|
148
|
-
multi_json (1.
|
|
148
|
+
multi_json (1.13.1)
|
|
149
149
|
netrc (0.11.0)
|
|
150
150
|
nokogiri (1.8.5)
|
|
151
151
|
mini_portile2 (~> 2.3.0)
|
|
@@ -164,19 +164,19 @@ GEM
|
|
|
164
164
|
mongoid (~> 5.0)
|
|
165
165
|
rubyzip (~> 1.0)
|
|
166
166
|
zip-zip (~> 0.3)
|
|
167
|
-
rack (1.6.
|
|
167
|
+
rack (1.6.11)
|
|
168
168
|
rack-test (0.6.3)
|
|
169
169
|
rack (>= 1.0)
|
|
170
|
-
rails (4.2.
|
|
171
|
-
actionmailer (= 4.2.
|
|
172
|
-
actionpack (= 4.2.
|
|
173
|
-
actionview (= 4.2.
|
|
174
|
-
activejob (= 4.2.
|
|
175
|
-
activemodel (= 4.2.
|
|
176
|
-
activerecord (= 4.2.
|
|
177
|
-
activesupport (= 4.2.
|
|
170
|
+
rails (4.2.11)
|
|
171
|
+
actionmailer (= 4.2.11)
|
|
172
|
+
actionpack (= 4.2.11)
|
|
173
|
+
actionview (= 4.2.11)
|
|
174
|
+
activejob (= 4.2.11)
|
|
175
|
+
activemodel (= 4.2.11)
|
|
176
|
+
activerecord (= 4.2.11)
|
|
177
|
+
activesupport (= 4.2.11)
|
|
178
178
|
bundler (>= 1.3.0, < 2.0)
|
|
179
|
-
railties (= 4.2.
|
|
179
|
+
railties (= 4.2.11)
|
|
180
180
|
sprockets-rails
|
|
181
181
|
rails-deprecated_sanitizer (1.0.3)
|
|
182
182
|
activesupport (>= 4.2.0.alpha)
|
|
@@ -186,9 +186,9 @@ GEM
|
|
|
186
186
|
rails-deprecated_sanitizer (>= 1.0.1)
|
|
187
187
|
rails-html-sanitizer (1.0.4)
|
|
188
188
|
loofah (~> 2.2, >= 2.2.2)
|
|
189
|
-
railties (4.2.
|
|
190
|
-
actionpack (= 4.2.
|
|
191
|
-
activesupport (= 4.2.
|
|
189
|
+
railties (4.2.11)
|
|
190
|
+
actionpack (= 4.2.11)
|
|
191
|
+
activesupport (= 4.2.11)
|
|
192
192
|
rake (>= 0.8.7)
|
|
193
193
|
thor (>= 0.18.1, < 2.0)
|
|
194
194
|
rake (12.0.0)
|
|
@@ -228,7 +228,7 @@ GEM
|
|
|
228
228
|
therubyracer (0.12.3)
|
|
229
229
|
libv8 (~> 3.16.14.15)
|
|
230
230
|
ref
|
|
231
|
-
thor (0.20.
|
|
231
|
+
thor (0.20.3)
|
|
232
232
|
thread_safe (0.3.6)
|
|
233
233
|
tilt (1.4.1)
|
|
234
234
|
tzinfo (1.2.5)
|
|
@@ -265,4 +265,4 @@ DEPENDENCIES
|
|
|
265
265
|
webmock
|
|
266
266
|
|
|
267
267
|
BUNDLED WITH
|
|
268
|
-
1.16.
|
|
268
|
+
1.16.6
|
data/bonnie-bundler.gemspec
CHANGED
|
@@ -7,16 +7,16 @@ Gem::Specification.new do |s|
|
|
|
7
7
|
s.email = "pophealth-talk@googlegroups.com"
|
|
8
8
|
s.homepage = "http://github.com/projecttacoma/bonnie_bundler"
|
|
9
9
|
s.authors = ["The MITRE Corporation"]
|
|
10
|
-
s.version = '2.2.
|
|
10
|
+
s.version = '2.2.4'
|
|
11
11
|
s.license = 'Apache-2.0'
|
|
12
12
|
|
|
13
|
-
s.add_dependency 'health-data-standards', '~> 4.3.
|
|
13
|
+
s.add_dependency 'health-data-standards', '~> 4.3.2'
|
|
14
14
|
s.add_dependency 'quality-measure-engine', '~> 3.2'
|
|
15
15
|
s.add_dependency 'hquery-patient-api', '~> 1.1'
|
|
16
16
|
s.add_dependency 'simplexml_parser', '~> 1.0'
|
|
17
17
|
s.add_dependency 'hqmf2js', '~> 1.4'
|
|
18
18
|
|
|
19
|
-
s.add_dependency 'rails', '
|
|
19
|
+
s.add_dependency 'rails', '>= 4.2', '< 6.0'
|
|
20
20
|
s.add_dependency 'mongoid', '~> 5.0'
|
|
21
21
|
s.add_dependency 'rubyzip', '~> 1.2', '>= 1.2.1'
|
|
22
22
|
s.add_dependency 'zip-zip', '~> 0.3'
|
data/lib/bonnie_bundler.rb
CHANGED
|
@@ -14,7 +14,6 @@ require_relative 'models/measure.rb'
|
|
|
14
14
|
require_relative 'models/cql_measure.rb'
|
|
15
15
|
require_relative 'measures/loading/exceptions.rb'
|
|
16
16
|
require_relative 'measures/loading/loader.rb'
|
|
17
|
-
require_relative 'measures/loading/base_loader_definition.rb'
|
|
18
17
|
require_relative 'measures/loading/cql_loader.rb'
|
|
19
18
|
require_relative 'measures/loading/value_set_loader.rb'
|
|
20
19
|
require_relative 'measures/logic_extractor.rb'
|
|
@@ -1,62 +1,93 @@
|
|
|
1
1
|
module Measures
|
|
2
2
|
# Utility class for loading CQL measure definitions into the database from the MAT export zip
|
|
3
|
-
class CqlLoader
|
|
3
|
+
class CqlLoader
|
|
4
|
+
# Returns true if ths uploaded measure zip file is a composite measure
|
|
5
|
+
def self.composite_measure?(measure_dir)
|
|
6
|
+
# Look through all xml files at current directory level and find QDM
|
|
7
|
+
files = Dir.glob("#{measure_dir}/**.xml").select
|
|
8
|
+
begin
|
|
9
|
+
# Iterate over all files passed in, extract file to temporary directory.
|
|
10
|
+
files.each do |xml_file|
|
|
11
|
+
if xml_file && xml_file.size > 0
|
|
12
|
+
# Open up xml file and read contents.
|
|
13
|
+
doc = Nokogiri::XML.parse(File.read(xml_file))
|
|
14
|
+
# Check if root node in xml file matches either the HQMF file or ELM file.
|
|
15
|
+
if doc.root.name == 'QualityMeasureDocument' # Root node for HQMF XML
|
|
16
|
+
# Xpath to determine if it is a composite or not
|
|
17
|
+
doc.root.add_namespace_definition('cda', 'urn:hl7-org:v3')
|
|
18
|
+
return !doc.at_xpath('//cda:measureAttribute[cda:code[@code="MSRTYPE"]][cda:value[@code="COMPOSITE"]]').nil?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
rescue Exception => e
|
|
23
|
+
raise MeasureLoadingException.new "Error Checking MAT Export: #{e.message}"
|
|
24
|
+
end
|
|
25
|
+
false
|
|
26
|
+
end
|
|
4
27
|
|
|
28
|
+
# Verifies that the zip file contains a valid measure
|
|
29
|
+
# Works for both regular & composite measures
|
|
5
30
|
def self.mat_cql_export?(zip_file)
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
zip_xml_files = zip_file.glob(File.join('**','**.xml')).select {|x| !x.name.starts_with?('__MACOSX') }
|
|
31
|
+
# Extract contents of zip file while retaining the directory structure
|
|
32
|
+
original = Dir.pwd
|
|
33
|
+
Dir.mktmpdir do |tmp_dir|
|
|
34
|
+
current_directory = unzip_measure_contents(zip_file, tmp_dir)
|
|
35
|
+
# Check if measure contents are valid
|
|
36
|
+
return valid_measure_contents?(current_directory, true)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
15
39
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
# Returns the base directory of the measure
|
|
41
|
+
def self.unzip_measure_contents(zip_file, tmp_dir)
|
|
42
|
+
Zip::ZipFile.open(zip_file.path) do |zip_file|
|
|
43
|
+
zip_file.each do |f|
|
|
44
|
+
f_path = File.join(tmp_dir, f.name)
|
|
45
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
|
46
|
+
f.extract(f_path)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
current_directory = tmp_dir
|
|
50
|
+
# Detect if the zip file contents were stored into a single directory
|
|
51
|
+
if Dir.glob("#{current_directory}/*").count < 3
|
|
52
|
+
# If there is a single folder containing the zip file contents, step into it (ignore __MACOSX file if it exists)
|
|
53
|
+
Dir.glob("#{current_directory}/*").select.each do |file|
|
|
54
|
+
if !file.end_with?('__MACOSX') && File.directory?(file)
|
|
55
|
+
current_directory = file
|
|
56
|
+
break
|
|
57
|
+
end
|
|
21
58
|
end
|
|
22
59
|
end
|
|
60
|
+
return current_directory
|
|
23
61
|
end
|
|
24
62
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
cql_artifacts = process_cql(files, main_cql_library, user, vsac_options, vsac_ticket_granting_ticket, hqmf_model.hqmf_set_id)
|
|
39
|
-
|
|
40
|
-
# Create CQL Measure
|
|
41
|
-
hqmf_model.backfill_patient_characteristics_with_codes(cql_artifacts[:all_codes_and_code_names])
|
|
42
|
-
json = hqmf_model.to_json
|
|
43
|
-
json.convert_keys_to_strings
|
|
44
|
-
|
|
45
|
-
# Set the code list ids of data criteria and source data criteria that use direct reference codes to GUIDS.
|
|
46
|
-
json['source_data_criteria'], json['data_criteria'] = set_data_criteria_code_list_ids(json, cql_artifacts)
|
|
63
|
+
# Verifies contents of the given measure are valid (works for regular, composite and component measures)
|
|
64
|
+
def self.valid_measure_contents?(measure_dir, check_components = false)
|
|
65
|
+
# If composite measure given, check components
|
|
66
|
+
if check_components
|
|
67
|
+
Dir.glob("#{measure_dir}/*").each do |file|
|
|
68
|
+
if File.directory?(file)
|
|
69
|
+
if !valid_measure_contents?(file)
|
|
70
|
+
return false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
47
75
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
measure['type'] = measure_details['type']
|
|
53
|
-
measure['calculate_sdes'] = measure_details['calculate_sdes']
|
|
76
|
+
# Grab all cql, elm & human readable docs from measure
|
|
77
|
+
cql_entry = Dir.glob(File.join(measure_dir,'**.cql')).select {|x| !File.basename(x).starts_with?('__MACOSX') }.first
|
|
78
|
+
elm_json = Dir.glob(File.join(measure_dir,'**.json')).select {|x| !File.basename(x).starts_with?('__MACOSX') }.first
|
|
79
|
+
human_readable_entry = Dir.glob(File.join(measure_dir,'**.html')).select {|x| !File.basename(x).starts_with?('__MACOSX') }.first
|
|
54
80
|
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
measure.package.save
|
|
81
|
+
# Grab all xml files in the measure.
|
|
82
|
+
xml_files = Dir.glob(File.join(measure_dir,'**.xml')).select {|x| !File.basename(x).starts_with?('__MACOSX') }
|
|
58
83
|
|
|
59
|
-
|
|
84
|
+
# Find key value pair for HQMF and ELM xml files.
|
|
85
|
+
if xml_files.count > 0
|
|
86
|
+
xml_files_hash = retrieve_elm_and_hqmf(xml_files)
|
|
87
|
+
!cql_entry.nil? && !elm_json.nil? && !human_readable_entry.nil? && !xml_files_hash[:HQMF_XML].nil? && !xml_files_hash[:ELM_XML].nil?
|
|
88
|
+
else
|
|
89
|
+
false
|
|
90
|
+
end
|
|
60
91
|
end
|
|
61
92
|
|
|
62
93
|
def self.set_data_criteria_code_list_ids(json, cql_artifacts)
|
|
@@ -84,28 +115,175 @@ module Measures
|
|
|
84
115
|
return json['source_data_criteria'], json['data_criteria']
|
|
85
116
|
end
|
|
86
117
|
|
|
87
|
-
|
|
118
|
+
# Returns an array of measures
|
|
119
|
+
# Single measure returned into the array if it is a non-composite measure
|
|
120
|
+
def self.extract_measures(measure_zip, current_user, measure_details, vsac_options, vsac_ticket_granting_ticket)
|
|
88
121
|
measure = nil
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
component_measures = []
|
|
123
|
+
# Unzip measure contents while retaining the directory structure
|
|
124
|
+
Dir.mktmpdir do |tmp_dir|
|
|
125
|
+
current_directory = unzip_measure_contents(measure_zip, tmp_dir)
|
|
126
|
+
if !valid_measure_contents?(current_directory, true)
|
|
127
|
+
raise MeasureLoadingException.new("Zip file was not a MAT package.")
|
|
128
|
+
end
|
|
129
|
+
component_elms = {}
|
|
130
|
+
component_elms[:ELM_JSON] = []
|
|
131
|
+
|
|
132
|
+
# If it is a composite measure, load in each of the components
|
|
133
|
+
# Components must be loaded first so their elms can be passed onto the composite
|
|
134
|
+
if composite_measure?(current_directory)
|
|
135
|
+
component_measures = create_component_measures(current_directory, current_user, measure_details, vsac_options, vsac_ticket_granting_ticket)
|
|
136
|
+
component_measures.each do |component_measure|
|
|
137
|
+
component_elms[:ELM_JSON].push(*component_measure.elm)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Load in regular/composite measure measure
|
|
142
|
+
begin
|
|
143
|
+
measure = create_measure(current_directory, current_user, measure_details, vsac_options, vsac_ticket_granting_ticket, component_elms)
|
|
144
|
+
rescue => e
|
|
145
|
+
component_measures.each { |component| component.delete }
|
|
146
|
+
raise e
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Create, associate and save the measure package.
|
|
150
|
+
measure_package = CqlMeasurePackage.new(file: BSON::Binary.new(measure_zip.read()))
|
|
151
|
+
measure.package = measure_package
|
|
152
|
+
measure.package.save
|
|
153
|
+
|
|
154
|
+
component_measures.each do |component_measure|
|
|
155
|
+
# Update the components' hqmf_set_id, formatted as follows:
|
|
156
|
+
# <composite_hqmf_set_id>&<component_hqmf_set_id>
|
|
157
|
+
component_measure.hqmf_set_id = measure.hqmf_set_id + '&' + component_measure.hqmf_set_id
|
|
158
|
+
component_measure.component = true;
|
|
159
|
+
# Associate the component with the composite
|
|
160
|
+
measure.component_hqmf_set_ids.push(component_measure.hqmf_set_id)
|
|
161
|
+
end
|
|
162
|
+
end # End of temporary directory usage
|
|
163
|
+
|
|
164
|
+
# Put measure (and component measures) into an array to return
|
|
165
|
+
measures = component_measures << measure
|
|
166
|
+
return measures
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Creates a composite's component measures
|
|
170
|
+
def self.create_component_measures(current_directory, current_user, measure_details, vsac_options, vsac_ticket_granting_ticket)
|
|
171
|
+
component_measures = []
|
|
172
|
+
Dir.glob("#{current_directory}/*").sort.each do |file|
|
|
173
|
+
if File.directory?(file)
|
|
174
|
+
component_measures << create_measure(file, current_user, measure_details, vsac_options, vsac_ticket_granting_ticket)
|
|
175
|
+
end
|
|
91
176
|
end
|
|
177
|
+
component_measures
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Creates and returns a measure
|
|
181
|
+
def self.create_measure(measure_dir, user, measure_details, vsac_options, vsac_ticket_granting_ticket, component_elms=nil)
|
|
182
|
+
measure = nil
|
|
183
|
+
|
|
184
|
+
# Grabs the cql file contents, the elm_xml contents, elm_json contents and the hqmf file path
|
|
185
|
+
files = get_files_from_directory(measure_dir)
|
|
186
|
+
|
|
187
|
+
# Load hqmf into HQMF Parser
|
|
188
|
+
hqmf_model = Measures::Loader.parse_hqmf_model(files[:HQMF_XML_PATH])
|
|
189
|
+
|
|
190
|
+
# Get main measure from hqmf parser
|
|
191
|
+
main_cql_library = hqmf_model.cql_measure_library
|
|
192
|
+
|
|
193
|
+
cql_artifacts = process_cql(files, main_cql_library, user, vsac_options, vsac_ticket_granting_ticket, hqmf_model.hqmf_set_id, component_elms)
|
|
194
|
+
|
|
195
|
+
# Create CQL Measure
|
|
196
|
+
hqmf_model.backfill_patient_characteristics_with_codes(cql_artifacts[:all_codes_and_code_names])
|
|
197
|
+
json = hqmf_model.to_json
|
|
198
|
+
json.convert_keys_to_strings
|
|
199
|
+
|
|
200
|
+
# Set the code list ids of data criteria and source data criteria that use direct reference codes to GUIDS.
|
|
201
|
+
json['source_data_criteria'], json['data_criteria'] = set_data_criteria_code_list_ids(json, cql_artifacts)
|
|
202
|
+
|
|
203
|
+
# Create CQL Measure
|
|
204
|
+
measure_details["composite"] = composite_measure?(measure_dir)
|
|
205
|
+
measure = Measures::Loader.load_hqmf_cql_model_json(json, user, cql_artifacts[:all_value_set_oids], main_cql_library, cql_artifacts[:cql_definition_dependency_structure],
|
|
206
|
+
cql_artifacts[:elms], cql_artifacts[:elm_annotations], files[:CQL], measure_details, cql_artifacts[:value_set_oid_version_objects])
|
|
92
207
|
measure
|
|
93
208
|
end
|
|
94
209
|
|
|
210
|
+
def self.get_files_from_directory(dir)
|
|
211
|
+
cql_paths = Dir.glob(File.join("#{dir}/**.cql")).sort
|
|
212
|
+
xml_paths = Dir.glob(File.join("#{dir}/**.xml")).sort
|
|
213
|
+
elm_json_paths = Dir.glob(File.join("#{dir}/**.json")).sort
|
|
214
|
+
|
|
215
|
+
begin
|
|
216
|
+
cql_contents = []
|
|
217
|
+
cql_paths.each do |cql_path|
|
|
218
|
+
cql_contents << open(cql_path).read
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
elm_json = []
|
|
222
|
+
elm_json_paths.each do |elm_json_path|
|
|
223
|
+
elm_json << open(elm_json_path).read
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
xml_file_hash = retrieve_elm_and_hqmf(xml_paths)
|
|
227
|
+
elm_xml_paths = xml_file_hash[:ELM_XML]
|
|
228
|
+
elm_xml = []
|
|
229
|
+
elm_xml_paths.each do |elm_xml_path|
|
|
230
|
+
elm_xml << open(elm_xml_path).read
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
files = { :HQMF_XML_PATH => xml_file_hash[:HQMF_XML],
|
|
234
|
+
:ELM_JSON => elm_json,
|
|
235
|
+
:CQL => cql_contents,
|
|
236
|
+
:ELM_XML => elm_xml }
|
|
237
|
+
return files
|
|
238
|
+
rescue Exception => e
|
|
239
|
+
raise MeasureLoadingException.new "Error Parsing Measure Logic: #{e.message}"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Takes in array of xml files and returns hash with keys HQMF_XML and ELM_XML
|
|
244
|
+
def self.retrieve_elm_and_hqmf(files)
|
|
245
|
+
file_paths_hash = {}
|
|
246
|
+
file_paths_hash[:ELM_XML] = []
|
|
247
|
+
begin
|
|
248
|
+
files.each do |xml_file_path|
|
|
249
|
+
if xml_file_path && xml_file_path.size > 0
|
|
250
|
+
# Open up xml file and read contents.
|
|
251
|
+
doc = Nokogiri::XML.parse(File.read(xml_file_path))
|
|
252
|
+
# Check if root node in xml file matches either the HQMF file or ELM file.
|
|
253
|
+
if doc.root.name == 'QualityMeasureDocument' # Root node for HQMF XML
|
|
254
|
+
file_paths_hash[:HQMF_XML] = xml_file_path
|
|
255
|
+
elsif doc.root.name == 'library' # Root node for ELM XML
|
|
256
|
+
file_paths_hash[:ELM_XML] << xml_file_path
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
rescue Exception => e
|
|
261
|
+
raise MeasureLoadingException.new "Error Checking MAT Export: #{e.message}"
|
|
262
|
+
end
|
|
263
|
+
file_paths_hash
|
|
264
|
+
end
|
|
265
|
+
|
|
95
266
|
# Manages all of the CQL processing that is not related to the HQMF.
|
|
96
|
-
def self.process_cql(files, main_cql_library, user, vsac_options, vsac_ticket_granting_ticket, measure_id=nil)
|
|
267
|
+
def self.process_cql(files, main_cql_library, user, vsac_options, vsac_ticket_granting_ticket, measure_id=nil, component_elms=nil)
|
|
97
268
|
elm_strings = files[:ELM_JSON]
|
|
98
269
|
# Removes 'urn:oid:' from ELM for Bonnie and Parse the JSON
|
|
99
270
|
elm_strings.each { |elm_string| elm_string.gsub! 'urn:oid:', '' }
|
|
100
271
|
elms = elm_strings.map{ |elm| JSON.parse(elm, :max_nesting=>1000)}
|
|
101
272
|
elm_annotations = parse_elm_annotations(files[:ELM_XML])
|
|
102
273
|
|
|
274
|
+
if (!component_elms.nil?)
|
|
275
|
+
elms.push(*component_elms[:ELM_JSON])
|
|
276
|
+
end
|
|
103
277
|
# Hash of define statements to which define statements they use.
|
|
104
278
|
cql_definition_dependency_structure = populate_cql_definition_dependency_structure(main_cql_library, elms)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
279
|
+
begin
|
|
280
|
+
# Go back for the library statements
|
|
281
|
+
cql_definition_dependency_structure = populate_used_library_dependencies(cql_definition_dependency_structure, main_cql_library, elms)
|
|
282
|
+
# Add unused libraries to structure and set the value to empty hash
|
|
283
|
+
cql_definition_dependency_structure = populate_unused_included_libraries(cql_definition_dependency_structure, elms)
|
|
284
|
+
rescue => e
|
|
285
|
+
raise MeasureLoadingException.new("Measure package missing a library or component.")
|
|
286
|
+
end
|
|
109
287
|
|
|
110
288
|
# fix up statement names in cql_statement_dependencies to not use periods <<WRAP 1>>
|
|
111
289
|
# this is matched with an UNWRAP in MeasuresController in the bonnie project
|
|
@@ -177,7 +355,7 @@ module Measures
|
|
|
177
355
|
:all_codes_and_code_names => all_codes_and_code_names}
|
|
178
356
|
end
|
|
179
357
|
|
|
180
|
-
#
|
|
358
|
+
# Returns a list of objects that include the valueset oids and their versions
|
|
181
359
|
def self.get_value_set_oid_version_objects(value_sets, single_code_references)
|
|
182
360
|
# [LDC] need to make this an array of objects instead of a hash because Mongo is
|
|
183
361
|
# dumb and *let's you* have dots in keys on object creation but *doesn't let you*
|
|
@@ -268,50 +446,6 @@ module Measures
|
|
|
268
446
|
return single_code_references, all_codes_and_code_names
|
|
269
447
|
end
|
|
270
448
|
|
|
271
|
-
# Opens the zip and grabs the cql file contents, the ELM contents (XML and JSON) and hqmf_path.
|
|
272
|
-
def self.get_files_from_zip(zip_file, out_dir)
|
|
273
|
-
Zip::ZipFile.open(zip_file.path) do |file|
|
|
274
|
-
cql_entries = file.glob(File.join('**','**.cql')).select {|x| !x.name.starts_with?('__MACOSX') }
|
|
275
|
-
zip_xml_files = file.glob(File.join('**','**.xml')).select {|x| !x.name.starts_with?('__MACOSX') }
|
|
276
|
-
elm_json_entries = file.glob(File.join('**','**.json')).select {|x| !x.name.starts_with?('__MACOSX') }
|
|
277
|
-
|
|
278
|
-
begin
|
|
279
|
-
cql_paths = []
|
|
280
|
-
cql_entries.each do |cql_file|
|
|
281
|
-
cql_paths << extract(file, cql_file, out_dir) if cql_file.size > 0
|
|
282
|
-
end
|
|
283
|
-
cql_contents = []
|
|
284
|
-
cql_paths.each do |cql_path|
|
|
285
|
-
cql_contents << open(cql_path).read
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
elm_json_paths = []
|
|
289
|
-
elm_json_entries.each do |json_file|
|
|
290
|
-
elm_json_paths << extract(file, json_file, out_dir) if json_file.size > 0
|
|
291
|
-
end
|
|
292
|
-
elm_json = []
|
|
293
|
-
elm_json_paths.each do |elm_json_path|
|
|
294
|
-
elm_json << open(elm_json_path).read
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
xml_file_paths = extract_xml_files(file, zip_xml_files, out_dir)
|
|
298
|
-
elm_xml_paths = xml_file_paths[:ELM_XML]
|
|
299
|
-
elm_xml = []
|
|
300
|
-
elm_xml_paths.each do |elm_xml_path|
|
|
301
|
-
elm_xml << open(elm_xml_path).read
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
files = { :HQMF_XML_PATH => xml_file_paths[:HQMF_XML],
|
|
305
|
-
:ELM_JSON => elm_json,
|
|
306
|
-
:CQL => cql_contents,
|
|
307
|
-
:ELM_XML => elm_xml }
|
|
308
|
-
return files
|
|
309
|
-
rescue Exception => e
|
|
310
|
-
raise MeasureLoadingException.new "Error Parsing Measure Logic: #{e.message}"
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
|
|
315
449
|
private
|
|
316
450
|
def self.parse_elm_annotations(xmls)
|
|
317
451
|
elm_annotations = {}
|