quality-measure-engine 2.2.0 → 2.3.0
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.
- data/Gemfile.lock +59 -55
- data/lib/qme/map/cv_aggregator.rb +40 -0
- data/lib/qme/map/map_reduce_executor.rb +35 -6
- data/lib/qme/version.rb +1 -1
- data/lib/quality-measure-engine.rb +2 -2
- data/quality-measure-engine.gemspec +3 -3
- data/test/fixtures/bundles/bundle.json +10 -0
- data/test/fixtures/bundles/measures_0003_0002.zip +0 -0
- data/test/fixtures/library_functions/hqmf_utils.js +5146 -0
- data/test/fixtures/library_functions/map_reduce_utils.js +45 -0
- data/test/fixtures/library_functions/underscore_min.js +32 -0
- data/test/fixtures/measures/0002.json +1415 -0
- data/test/fixtures/measures/0142.json +1 -0
- data/test/fixtures/measures/0495a.json +1 -0
- data/test/fixtures/measures/0495b.json +1 -0
- data/test/fixtures/measures/measure_metadata.json +1 -0
- data/test/test_helper.rb +16 -0
- data/test/unit/qme/map/map_reduce_builder_test.rb +1 -0
- data/test/unit/qme/map/map_reduce_executor_test.rb +5 -2
- data/test/unit/qme/map/measure_calculation_job_test.rb +5 -3
- data/test/unit/qme/quality_measure_test.rb +6 -3
- data/test/unit/qme/quality_report_test.rb +2 -0
- metadata +20 -9
- data/lib/qme/bundle/bundle.rb +0 -51
- data/lib/qme/bundle/importer.rb +0 -63
- data/lib/qme/tasks/bundle.rake +0 -95
data/test/test_helper.rb
CHANGED
@@ -9,6 +9,22 @@ Mongoid.configure do |config|
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class MiniTest::Unit::TestCase
|
12
|
+
|
13
|
+
def load_system_js
|
14
|
+
Mongoid.default_session['system.js'].drop
|
15
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'fixtures', "library_functions", '*.js')).each do |json_fixture_file|
|
16
|
+
name = File.basename(json_fixture_file,".*")
|
17
|
+
fn = "function () {\n #{File.read(json_fixture_file)} \n }"
|
18
|
+
Mongoid.default_session['system.js'].find('_id' => name).upsert(
|
19
|
+
{
|
20
|
+
"_id" => name,
|
21
|
+
"value" => Moped::BSON::Code.new(fn)
|
22
|
+
}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
12
28
|
# Add more helper methods to be used by all tests here...
|
13
29
|
|
14
30
|
def collection_fixtures(db, collection, *id_attributes)
|
@@ -6,6 +6,7 @@ class MapReduceBuilderTest < MiniTest::Unit::TestCase
|
|
6
6
|
def setup
|
7
7
|
raw_measure_json = File.read(File.join('test', 'fixtures', 'measures', 'measure_metadata.json'))
|
8
8
|
@measure_json = JSON.parse(raw_measure_json)
|
9
|
+
load_system_js
|
9
10
|
end
|
10
11
|
|
11
12
|
def test_extracting_measure_metadata
|
@@ -4,10 +4,13 @@ class MapReduceExecutorTest < MiniTest::Unit::TestCase
|
|
4
4
|
include QME::DatabaseAccess
|
5
5
|
|
6
6
|
def setup
|
7
|
-
importer = QME::Bundle::Importer.new
|
8
|
-
importer.import(File.new('test/fixtures/bundles/just_measure_0002.zip'), true, nil)
|
9
7
|
|
8
|
+
get_db['query_cache'].drop()
|
9
|
+
get_db['patient_cache'].drop()
|
10
|
+
collection_fixtures(get_db(), 'measures')
|
10
11
|
collection_fixtures(get_db(), 'records', '_id')
|
12
|
+
collection_fixtures(get_db(), 'bundles')
|
13
|
+
load_system_js
|
11
14
|
end
|
12
15
|
|
13
16
|
def test_map_records_into_measure_groups
|
@@ -4,10 +4,12 @@ class MapCalculationJobTest < MiniTest::Unit::TestCase
|
|
4
4
|
include QME::DatabaseAccess
|
5
5
|
|
6
6
|
def setup
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
get_db['query_cache'].drop()
|
8
|
+
get_db['patient_cache'].drop()
|
9
|
+
collection_fixtures(get_db(), 'measures')
|
10
10
|
collection_fixtures(get_db(), 'records', '_id')
|
11
|
+
collection_fixtures(get_db(), 'bundles')
|
12
|
+
load_system_js
|
11
13
|
|
12
14
|
Delayed::Worker.delay_jobs = false
|
13
15
|
end
|
@@ -1,14 +1,17 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class QualityMeasureTest < MiniTest::Unit::TestCase
|
4
|
+
include QME::DatabaseAccess
|
4
5
|
def setup
|
5
|
-
|
6
|
-
|
6
|
+
|
7
|
+
collection_fixtures(get_db(), 'measures')
|
8
|
+
collection_fixtures(get_db(), 'bundles')
|
9
|
+
load_system_js
|
7
10
|
end
|
8
11
|
|
9
12
|
def test_getting_all_measures
|
10
13
|
all_measures = QME::QualityMeasure.all
|
11
|
-
assert_equal
|
14
|
+
assert_equal 5, all_measures.size
|
12
15
|
assert all_measures["2E679CD2-3FEC-4A75-A75A-61403E5EFEE8.json"]
|
13
16
|
end
|
14
17
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quality-measure-engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,10 +9,11 @@ authors:
|
|
9
9
|
- Andy Gregorowicz
|
10
10
|
- Rob Dingwell
|
11
11
|
- Adam Goldstein
|
12
|
+
- Andre Quina
|
12
13
|
autorequire:
|
13
14
|
bindir: bin
|
14
15
|
cert_chain: []
|
15
|
-
date:
|
16
|
+
date: 2013-02-27 00:00:00.000000000 Z
|
16
17
|
dependencies:
|
17
18
|
- !ruby/object:Gem::Dependency
|
18
19
|
name: moped
|
@@ -21,7 +22,7 @@ dependencies:
|
|
21
22
|
requirements:
|
22
23
|
- - ~>
|
23
24
|
- !ruby/object:Gem::Version
|
24
|
-
version: 1.2
|
25
|
+
version: 1.4.2
|
25
26
|
type: :runtime
|
26
27
|
prerelease: false
|
27
28
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +30,7 @@ dependencies:
|
|
29
30
|
requirements:
|
30
31
|
- - ~>
|
31
32
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.2
|
33
|
+
version: 1.4.2
|
33
34
|
- !ruby/object:Gem::Dependency
|
34
35
|
name: mongoid
|
35
36
|
requirement: !ruby/object:Gem::Requirement
|
@@ -37,7 +38,7 @@ dependencies:
|
|
37
38
|
requirements:
|
38
39
|
- - ~>
|
39
40
|
- !ruby/object:Gem::Version
|
40
|
-
version: 3.0
|
41
|
+
version: '3.0'
|
41
42
|
type: :runtime
|
42
43
|
prerelease: false
|
43
44
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -45,7 +46,7 @@ dependencies:
|
|
45
46
|
requirements:
|
46
47
|
- - ~>
|
47
48
|
- !ruby/object:Gem::Version
|
48
|
-
version: 3.0
|
49
|
+
version: '3.0'
|
49
50
|
- !ruby/object:Gem::Dependency
|
50
51
|
name: rubyzip
|
51
52
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,24 +173,28 @@ files:
|
|
172
173
|
- LICENSE.txt
|
173
174
|
- README.md
|
174
175
|
- Rakefile
|
175
|
-
- lib/qme/bundle/bundle.rb
|
176
176
|
- lib/qme/bundle/eh_measure_sheet.rb
|
177
177
|
- lib/qme/bundle/eh_patient_importer.rb
|
178
|
-
- lib/qme/bundle/importer.rb
|
179
178
|
- lib/qme/database_access.rb
|
179
|
+
- lib/qme/map/cv_aggregator.rb
|
180
180
|
- lib/qme/map/map_reduce_builder.rb
|
181
181
|
- lib/qme/map/map_reduce_executor.rb
|
182
182
|
- lib/qme/map/measure_calculation_job.rb
|
183
183
|
- lib/qme/quality_measure.rb
|
184
184
|
- lib/qme/quality_report.rb
|
185
185
|
- lib/qme/railtie.rb
|
186
|
-
- lib/qme/tasks/bundle.rake
|
187
186
|
- lib/qme/version.rb
|
188
187
|
- lib/quality-measure-engine.rb
|
189
188
|
- quality-measure-engine.gemspec
|
189
|
+
- test/fixtures/bundles/bundle.json
|
190
190
|
- test/fixtures/bundles/just_measure_0002.zip
|
191
|
+
- test/fixtures/bundles/measures_0003_0002.zip
|
191
192
|
- test/fixtures/delayed_backend_mongoid_jobs/queued_job.json
|
192
193
|
- test/fixtures/eh_patient_sheets/results_matrix_eh.xlsx
|
194
|
+
- test/fixtures/library_functions/hqmf_utils.js
|
195
|
+
- test/fixtures/library_functions/map_reduce_utils.js
|
196
|
+
- test/fixtures/library_functions/underscore_min.js
|
197
|
+
- test/fixtures/measures/0002.json
|
193
198
|
- test/fixtures/measures/0142.json
|
194
199
|
- test/fixtures/measures/0495a.json
|
195
200
|
- test/fixtures/measures/0495b.json
|
@@ -233,9 +238,15 @@ specification_version: 3
|
|
233
238
|
summary: This library can run JavaScript based clinical quality measures on a repository
|
234
239
|
of patients stored in MongoDB
|
235
240
|
test_files:
|
241
|
+
- test/fixtures/bundles/bundle.json
|
236
242
|
- test/fixtures/bundles/just_measure_0002.zip
|
243
|
+
- test/fixtures/bundles/measures_0003_0002.zip
|
237
244
|
- test/fixtures/delayed_backend_mongoid_jobs/queued_job.json
|
238
245
|
- test/fixtures/eh_patient_sheets/results_matrix_eh.xlsx
|
246
|
+
- test/fixtures/library_functions/hqmf_utils.js
|
247
|
+
- test/fixtures/library_functions/map_reduce_utils.js
|
248
|
+
- test/fixtures/library_functions/underscore_min.js
|
249
|
+
- test/fixtures/measures/0002.json
|
239
250
|
- test/fixtures/measures/0142.json
|
240
251
|
- test/fixtures/measures/0495a.json
|
241
252
|
- test/fixtures/measures/0495b.json
|
data/lib/qme/bundle/bundle.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
module QME
|
2
|
-
module Bundle
|
3
|
-
# Delete a list of collections. By default, this function drops all of collections related to measures and patients.
|
4
|
-
#
|
5
|
-
# @param [Array] collection_names Optionally, an array of collection names to be dropped.
|
6
|
-
def self.drop_collections(db, collection_names=[])
|
7
|
-
collection_names = ["bundles", "records", "measures", "selected_measures", "patient_cache", "query_cache", "system.js"] if collection_names.empty?
|
8
|
-
collection_names.each {|collection| db[collection].drop}
|
9
|
-
end
|
10
|
-
|
11
|
-
# Save a javascript function into Mongo's system.js collection for measure execution.
|
12
|
-
#
|
13
|
-
# @param [String] name The name by which the function will be referred.
|
14
|
-
# @param [String] fn The body of the function being saved.
|
15
|
-
def self.save_system_js_fn(db, name, fn)
|
16
|
-
fn = "function () {\n #{fn} \n }"
|
17
|
-
db['system.js'].find('_id' => name).upsert(
|
18
|
-
{
|
19
|
-
"_id" => name,
|
20
|
-
"value" => Moped::BSON::Code.new(fn)
|
21
|
-
}
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
|
-
# A utility function for finding files in a bundle. Strip a file path of it's extension and just give the filename.
|
26
|
-
#
|
27
|
-
# @param [String] original A file path.
|
28
|
-
# @param [String] extension A file extension.
|
29
|
-
# @return The filename at the end of the original String path with the extension removed. e.g. "/boo/urns.html" -> "urns"
|
30
|
-
def self.entry_key(original, extension)
|
31
|
-
original.split('/').last.gsub(".#{extension}", '')
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.unpack_bundle_contents(zip, type = nil)
|
35
|
-
bundle_contents = { bundle: nil, measures: {}, patients: {}, extensions: {}, results: {} }
|
36
|
-
Zip::ZipFile.open(zip.path) do |zipfile|
|
37
|
-
zipfile.entries.each do |entry|
|
38
|
-
bundle_contents[:bundle] = zipfile.read(entry.name) if entry.name.include? "bundle"
|
39
|
-
if type.nil? || entry.name.match(Regexp.new("/#{type}/"))
|
40
|
-
bundle_contents[:measures][Bundle.entry_key(entry.name, "json")] = zipfile.read(entry.name) if entry.name.match /^measures.*\.json$/
|
41
|
-
bundle_contents[:patients][Bundle.entry_key(entry.name, "json")] = zipfile.read(entry.name) if entry.name.match /^patients.*\.json$/ # Only need to import one of the formats
|
42
|
-
bundle_contents[:results][Bundle.entry_key(entry.name,"json")] = zipfile.read(entry.name) if entry.name.match /^results.*\.json/
|
43
|
-
end
|
44
|
-
bundle_contents[:extensions][Bundle.entry_key(entry.name,"js")] = zipfile.read(entry.name) if entry.name.match /^library_functions.*\.js/
|
45
|
-
|
46
|
-
end
|
47
|
-
end
|
48
|
-
bundle_contents
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
data/lib/qme/bundle/importer.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
module QME
|
2
|
-
module Bundle
|
3
|
-
class Importer
|
4
|
-
include QME::DatabaseAccess
|
5
|
-
|
6
|
-
# Create a new Importer.
|
7
|
-
# @param [String] db_name the name of the database to use
|
8
|
-
def initialize(db_name = nil)
|
9
|
-
determine_connection_information(db_name)
|
10
|
-
@db = get_db
|
11
|
-
end
|
12
|
-
|
13
|
-
# Import a quality bundle into the database. This includes metadata, measures, test patients, supporting JS libraries, and expected results.
|
14
|
-
#
|
15
|
-
# @param [File] zip The bundle zip file.
|
16
|
-
# @param [String] Type of measures to import, either 'ep', 'eh' or nil for all
|
17
|
-
# @param [Boolean] keep_existing If true, delete all current collections related to patients and measures.
|
18
|
-
def import(zip, delete_existing, type=nil)
|
19
|
-
Bundle.drop_collections(@db) if delete_existing
|
20
|
-
|
21
|
-
# Unpack content from the bundle.
|
22
|
-
bundle_contents = QME::Bundle.unpack_bundle_contents(zip, type)
|
23
|
-
|
24
|
-
# Store all JS libraries.
|
25
|
-
bundle_contents[:extensions].each do |key, contents|
|
26
|
-
Bundle.save_system_js_fn(@db, key, contents)
|
27
|
-
end
|
28
|
-
|
29
|
-
# Store the bundle metadata.
|
30
|
-
bundle_id = Moped::BSON::ObjectId.new()
|
31
|
-
bundle = JSON.parse(bundle_contents[:bundle])
|
32
|
-
bundle["_id"] = bundle_id
|
33
|
-
@db['bundles'].insert(bundle)
|
34
|
-
|
35
|
-
# Store all measures.
|
36
|
-
bundle_contents[:measures].each do |key, contents|
|
37
|
-
measure_id = Moped::BSON::ObjectId.new()
|
38
|
-
measure = JSON.parse(contents, {:max_nesting => 100})
|
39
|
-
measure['_id'] = measure_id
|
40
|
-
measure['bundle'] = bundle_id
|
41
|
-
@db['measures'].insert(measure)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Store all patients.
|
45
|
-
bundle_id = Moped::BSON::ObjectId(bundle_id.to_s)
|
46
|
-
bundle_contents[:patients].each do |key, contents|
|
47
|
-
patient = JSON.parse(contents, {:max_nesting => 100})
|
48
|
-
patient['bundle'] = bundle_id
|
49
|
-
Record.new(patient).save
|
50
|
-
end
|
51
|
-
|
52
|
-
# Store the expected results into the query and patient caches.
|
53
|
-
bundle_contents[:results].each do |name, contents|
|
54
|
-
collection = name == "by_patient" ? "patient_cache" : "query_cache"
|
55
|
-
contents = JSON.parse(contents, {:max_nesting => 100})
|
56
|
-
contents.each {|document| @db[collection].insert(document)}
|
57
|
-
end
|
58
|
-
|
59
|
-
bundle_contents
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
data/lib/qme/tasks/bundle.rake
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
require 'quality-measure-engine'
|
2
|
-
|
3
|
-
db_name = ENV['DB_NAME'] || 'test'
|
4
|
-
|
5
|
-
namespace :bundle do
|
6
|
-
desc 'Import a quality bundle into the database.'
|
7
|
-
task :import, [:bundle_path, :delete_existing, :type] => [:environment] do |task, args|
|
8
|
-
raise "The path to the measures zip file must be specified" unless args.bundle_path
|
9
|
-
|
10
|
-
bundle = File.open(args.bundle_path)
|
11
|
-
importer = QME::Bundle::Importer.new(db_name)
|
12
|
-
bundle_contents = importer.import(bundle, args.delete_existing == "true", args.type)
|
13
|
-
|
14
|
-
puts "Successfully imported bundle at: #{args.bundle_path}"
|
15
|
-
puts "\t Imported into environment: #{Rails.env.upcase}" if defined? Rails
|
16
|
-
puts "\t Loaded #{args.type || "all"} measures"
|
17
|
-
puts "\t Measures Loaded: #{bundle_contents[:measures].count}"
|
18
|
-
puts "\t Test Patients Loaded: #{bundle_contents[:patients].count}"
|
19
|
-
puts "\t Extensions Loaded: #{bundle_contents[:extensions].count}"
|
20
|
-
end
|
21
|
-
|
22
|
-
# this task is most likely temporary. Once Bonnie can handle both EP and EH measures together, this would no longer be required.
|
23
|
-
desc 'Merge two bundles into one.'
|
24
|
-
task :merge, [:bundle_one,:bundle_two] do |t, args|
|
25
|
-
raise "Two bundle zip file paths to be merged must be specified" unless args.bundle_one && args.bundle_two
|
26
|
-
|
27
|
-
tmpdir = Dir.mktmpdir
|
28
|
-
['measures','patients','library_functions','results', 'sources'].each do |dir|
|
29
|
-
|
30
|
-
FileUtils.mkdir_p(File.join(tmpdir, 'output', dir))
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
begin
|
35
|
-
|
36
|
-
({'one'=>args.bundle_one,'two'=>args.bundle_two}).each do |key, source|
|
37
|
-
Zip::ZipFile.open(source) do |zip_file|
|
38
|
-
zip_file.each do |f|
|
39
|
-
f_path=File.join(tmpdir, key, f.name)
|
40
|
-
FileUtils.mkdir_p(File.dirname(f_path))
|
41
|
-
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
['measures','patients','library_functions', 'sources'].each do |dir|
|
48
|
-
['one','two'].each do |key|
|
49
|
-
FileUtils.mv(Dir.glob(File.join(tmpdir,key,dir,'*')), File.join(tmpdir,'output',dir))
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
Dir.glob(File.join(tmpdir,'one','results','*.json')).each do |result_path_one|
|
54
|
-
json_one = JSON.parse(File.new(result_path_one).read)
|
55
|
-
result_filename = Pathname.new(result_path_one).basename.to_s
|
56
|
-
json_two = JSON.parse(File.new(File.join(tmpdir,'two','results',result_filename)).read)
|
57
|
-
File.open(File.join(tmpdir,'output','results',result_filename), 'w') {|f| f.write(JSON.pretty_generate(json_one + json_two)) }
|
58
|
-
end
|
59
|
-
|
60
|
-
json_one = JSON.parse(File.new(File.join(tmpdir,'one','bundle.json')).read)
|
61
|
-
json_two = JSON.parse(File.new(File.join(tmpdir,'two','bundle.json')).read)
|
62
|
-
json_out = {}
|
63
|
-
|
64
|
-
['title','effective_date','version','license','exported'].each do |key|
|
65
|
-
json_out[key] = json_one[key]
|
66
|
-
end
|
67
|
-
|
68
|
-
['measures','patients','extensions'].each do |key|
|
69
|
-
json_out[key] = (json_one[key] + json_two[key]).uniq
|
70
|
-
end
|
71
|
-
|
72
|
-
version = json_out['version']
|
73
|
-
|
74
|
-
File.open(File.join(tmpdir,'output','bundle.json'), 'w') {|f| f.write(JSON.pretty_generate(json_out)) }
|
75
|
-
date_string = Time.now.strftime("%Y-%m-%d")
|
76
|
-
|
77
|
-
out_zip = File.join('tmp','bundles',"bundle-merged-#{date_string}-#{version}.zip")
|
78
|
-
FileUtils.remove_entry_secure out_zip if File.exists?(out_zip)
|
79
|
-
Zip::ZipFile.open(out_zip, 'w') do |zipfile|
|
80
|
-
path = File.join(tmpdir,'output')
|
81
|
-
Dir[File.join(path,'**','**')].each do |file|
|
82
|
-
zipfile.add(file.sub(path+'/',''),file)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
puts "wrote merged bundle to: #{out_zip}"
|
87
|
-
|
88
|
-
ensure
|
89
|
-
FileUtils.remove_entry_secure tmpdir
|
90
|
-
end
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|