bonnie_bundler 2.2.3 → 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/Gemfile.lock +39 -39
  4. data/bonnie-bundler.gemspec +3 -3
  5. data/lib/bonnie_bundler.rb +0 -1
  6. data/lib/measures/loading/cql_loader.rb +234 -100
  7. data/lib/measures/loading/loader.rb +18 -24
  8. data/lib/models/cql_measure.rb +9 -0
  9. data/test/fixtures/CMSAWA_v5_6_Artifacts.zip +0 -0
  10. data/test/fixtures/CMSAWA_v5_6_Artifacts_missing_component.zip +0 -0
  11. data/test/fixtures/CMSAWA_v5_6_Artifacts_missing_composite_files.zip +0 -0
  12. data/test/fixtures/CMSAWA_v5_6_Artifacts_missing_file.zip +0 -0
  13. data/test/fixtures/not_mat_export.zip +0 -0
  14. data/test/fixtures/vcr_cassettes/load_composite_measure.yml +7305 -0
  15. data/test/fixtures/vcr_cassettes/load_composite_measure_with_missing_component.yml +6923 -0
  16. data/test/fixtures/vcr_cassettes/load_composite_measure_with_missing_composite_files.yml +57 -0
  17. data/test/fixtures/vcr_cassettes/load_composite_measure_with_missing_file.yml +5938 -0
  18. data/test/fixtures/vcr_cassettes/multi_library_webcalls.yml +156 -167
  19. data/test/fixtures/vcr_cassettes/valid_vsac_response.yml +216 -191
  20. data/test/fixtures/vcr_cassettes/valid_vsac_response_158.yml +112 -116
  21. data/test/fixtures/vcr_cassettes/valid_vsac_response_158_update.yml +121 -125
  22. data/test/fixtures/vcr_cassettes/valid_vsac_response_hospice.yml +523 -447
  23. data/test/fixtures/vcr_cassettes/valid_vsac_response_includes_draft.yml +388 -356
  24. data/test/fixtures/vcr_cassettes/valid_vsac_response_pvc_unused_libraries.yml +969 -2079
  25. data/test/fixtures/vcr_cassettes/valid_vsac_response_special_characters.yml +1715 -1715
  26. data/test/fixtures/vcr_cassettes/vs_loading_500_response.yml +26 -510
  27. data/test/fixtures/vcr_cassettes/vs_loading_empty_concept_list.yml +26 -26
  28. data/test/fixtures/vcr_cassettes/vs_loading_release.yml +106 -106
  29. data/test/fixtures/vcr_cassettes/vs_loading_version.yml +106 -106
  30. data/test/unit/composite_cql_loader_test.rb +102 -0
  31. data/test/unit/cql_loader_test.rb +18 -7
  32. data/test/unit/get_value_sets_from_measure_model_test.rb +1 -1
  33. data/test/unit/load_mat_export_test.rb +15 -9
  34. data/test/unit/measure_complexity_test.rb +1 -1
  35. data/test/unit/measure_diff_test.rb +4 -4
  36. data/test/unit/storing_mat_export_package_test.rb +1 -2
  37. data/test/unit/value_set_loading_test.rb +16 -16
  38. data/test/vcr_setup.rb +1 -1
  39. metadata +23 -8
  40. 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: 4ba2b327df38138686e837f9156bb3b0c63398d9
4
- data.tar.gz: '08581626e3b497890a99ef1c170b1450ea928121'
3
+ metadata.gz: 973eb1a1c06145e1a7d06a4f1101bf2e35e179e9
4
+ data.tar.gz: 445d5d2057bfb716ce5cbc1e30baa45e58439ab3
5
5
  SHA512:
6
- metadata.gz: 3dbd38b0b9e248e20c68c5ae68eab09e9f1f94b98647d5a87ea53cd6cb2d6b9190382ed544b3a08ca1df6132e76881fb63ceed851c255066dc6c30bfc453b344
7
- data.tar.gz: fe7ff9b1b9c1c156bbbeb9f8fe402e59fb96181c818ea17a16485434d4535c5962941e7ee760cf64026942192fcd9709f4fae50a9c3ef6e36a61d49ac255ba00
6
+ metadata.gz: 3f26b65b570b975bcb4e804e6083f32ef8b0702fcf52c8da48e2b6083dd8de896605d4b5b0f4dd7cd6861b8612d1e9eca10949e8076927390883173884e49cd6
7
+ data.tar.gz: 70c68df147debef6e370d1e8366d338a0e030e1056775111e8224601f06b171b33e1eeceeb2568b8e3740dfda26fecac2c6187e077523e14fdc2a2332969ca34
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "2.3.5"
3
+ - "2.3.7"
4
4
  services: mongodb
5
5
  script:
6
6
  - bundle exec bundle-audit check --update
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bonnie_bundler (2.2.3)
4
+ bonnie_bundler (2.2.4)
5
5
  diffy (~> 3.0.0)
6
- health-data-standards (~> 4.3.1)
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 (~> 4.2)
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.10)
21
- actionpack (= 4.2.10)
22
- actionview (= 4.2.10)
23
- activejob (= 4.2.10)
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.10)
27
- actionview (= 4.2.10)
28
- activesupport (= 4.2.10)
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.10)
34
- activesupport (= 4.2.10)
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.10)
40
- activesupport (= 4.2.10)
39
+ activejob (4.2.11)
40
+ activesupport (= 4.2.11)
41
41
  globalid (>= 0.3.0)
42
- activemodel (4.2.10)
43
- activesupport (= 4.2.10)
42
+ activemodel (4.2.11)
43
+ activesupport (= 4.2.11)
44
44
  builder (~> 3.1)
45
- activerecord (4.2.10)
46
- activemodel (= 4.2.10)
47
- activesupport (= 4.2.10)
45
+ activerecord (4.2.11)
46
+ activemodel (= 4.2.11)
47
+ activesupport (= 4.2.11)
48
48
  arel (~> 6.0)
49
- activesupport (4.2.10)
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.0.5)
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.1)
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.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.12.2)
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.10)
167
+ rack (1.6.11)
168
168
  rack-test (0.6.3)
169
169
  rack (>= 1.0)
170
- rails (4.2.10)
171
- actionmailer (= 4.2.10)
172
- actionpack (= 4.2.10)
173
- actionview (= 4.2.10)
174
- activejob (= 4.2.10)
175
- activemodel (= 4.2.10)
176
- activerecord (= 4.2.10)
177
- activesupport (= 4.2.10)
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.10)
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.10)
190
- actionpack (= 4.2.10)
191
- activesupport (= 4.2.10)
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.0)
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.2
268
+ 1.16.6
@@ -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.3'
10
+ s.version = '2.2.4'
11
11
  s.license = 'Apache-2.0'
12
12
 
13
- s.add_dependency 'health-data-standards', '~> 4.3.1'
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', '~> 4.2'
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'
@@ -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 < BaseLoaderDefinition
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
- # Open the zip file and iterate over each of the files.
7
- Zip::ZipFile.open(zip_file.path) do |zip_file|
8
- # Check for CQL, HQMF, ELM and Human Readable
9
- cql_entry = zip_file.glob(File.join('**','**.cql')).select {|x| !x.name.starts_with?('__MACOSX') }.first
10
- elm_json = zip_file.glob(File.join('**','**.json')).select {|x| !x.name.starts_with?('__MACOSX') }.first
11
- human_readable_entry = zip_file.glob(File.join('**','**.html')).select { |x| !x.name.starts_with?('__MACOSX') }.first
12
-
13
- # Grab all xml files in the zip.
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
- if zip_xml_files.count > 0
17
- xml_files_hash = extract_xml_files(zip_file, zip_xml_files)
18
- !cql_entry.nil? && !elm_json.nil? && !human_readable_entry.nil? && !xml_files_hash[:HQMF_XML].nil? && !xml_files_hash[:ELM_XML].nil?
19
- else
20
- false
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
- def self.load_mat_cql_exports(user, zip_file, out_dir, measure_details, vsac_options, vsac_ticket_granting_ticket)
26
- measure = nil
27
- cql = nil
28
- hqmf_path = nil
29
- # Grabs the cql file contents, the elm_xml contents, elm_json contents and the hqmf file path
30
- files = get_files_from_zip(zip_file, out_dir)
31
-
32
- # Load hqmf into HQMF Parser
33
- hqmf_model = Measures::Loader.parse_hqmf_model(files[:HQMF_XML_PATH])
34
-
35
- # Get main measure from hqmf parser
36
- main_cql_library = hqmf_model.cql_measure_library
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
- # Create CQL Measure
49
- 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],
50
- cql_artifacts[:elms], cql_artifacts[:elm_annotations], files[:CQL], nil, cql_artifacts[:value_set_oid_version_objects])
51
- measure['episode_of_care'] = measure_details['episode_of_care']
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
- # Create, associate and save the measure package.
56
- measure.package = CqlMeasurePackage.new(file: BSON::Binary.new(zip_file.read()))
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
- measure
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
- def self.load(file, user, measure_details, vsac_options, vsac_ticket_granting_ticket)
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
- Dir.mktmpdir do |dir|
90
- measure = load_mat_cql_exports(user, file, dir, measure_details, vsac_options, vsac_ticket_granting_ticket)
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
- # Go back for the library statements
106
- cql_definition_dependency_structure = populate_used_library_dependencies(cql_definition_dependency_structure, main_cql_library, elms)
107
- # Add unused libraries to structure and set the value to empty hash
108
- cql_definition_dependency_structure = populate_unused_included_libraries(cql_definition_dependency_structure, elms)
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
- # returns a list of objects that include the valueset oids and their versions
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 = {}