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.
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 = {}