quality-measure-engine 2.3.0 → 2.4.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/lib/qme/map/map_reduce_builder.rb +14 -2
- data/lib/qme/map/map_reduce_executor.rb +43 -1
- data/lib/qme/quality_report.rb +10 -1
- data/lib/qme/version.rb +1 -1
- data/lib/quality-measure-engine.rb +2 -1
- data/test/fixtures/records/jane_jones_numerator.json +3 -0
- data/test/fixtures/records/jill_jones_denominator.json +6 -0
- data/test/unit/qme/map/map_reduce_builder_test.rb +9 -0
- data/test/unit/qme/map/map_reduce_executor_test.rb +39 -0
- metadata +3 -4
|
@@ -11,9 +11,10 @@ module QME
|
|
|
11
11
|
# Utility class used to supply a binding to Erb
|
|
12
12
|
class Context < OpenStruct
|
|
13
13
|
# Create a new context
|
|
14
|
-
# @param [Hash] vars a hash of parameter names (String) and values (Object). Each
|
|
14
|
+
# @param [Hash] vars a hash of parameter names (String) and values (Object). Each
|
|
15
|
+
# entry is added as an accessor of the new Context
|
|
15
16
|
def initialize(db, vars)
|
|
16
|
-
super(vars)
|
|
17
|
+
super(Context.add_defaults(vars))
|
|
17
18
|
@db = db
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -23,6 +24,17 @@ module QME
|
|
|
23
24
|
binding
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
# Add default parameter values if not specified
|
|
28
|
+
def self.add_defaults(vars)
|
|
29
|
+
if !vars.has_key?('enable_logging')
|
|
30
|
+
vars['enable_logging'] = false
|
|
31
|
+
end
|
|
32
|
+
if !vars.has_key?('enable_rationale')
|
|
33
|
+
vars['enable_rationale'] = false
|
|
34
|
+
end
|
|
35
|
+
vars
|
|
36
|
+
end
|
|
37
|
+
|
|
26
38
|
# Inserts any library code into the measure JS. JS library code is loaded from
|
|
27
39
|
# three locations: the js directory of the quality-measure-engine project, the
|
|
28
40
|
# js sub-directory of the current directory (e.g. measures/js), and the bundles
|
|
@@ -7,7 +7,10 @@ module QME
|
|
|
7
7
|
class Executor
|
|
8
8
|
|
|
9
9
|
include DatabaseAccess
|
|
10
|
-
|
|
10
|
+
SUPPLEMENTAL_DATA_ELEMENTS = {QME::QualityReport::RACE => "$value.race.code",
|
|
11
|
+
QME::QualityReport::ETHNICITY => "$value.ethnicity.code",
|
|
12
|
+
QME::QualityReport::SEX => "$value.gender",
|
|
13
|
+
QME::QualityReport::PAYER => "$value.payer"}
|
|
11
14
|
# Create a new Executor for a specific measure, effective date and patient population.
|
|
12
15
|
# @param [String] measure_id the measure identifier
|
|
13
16
|
# @param [String] sub_id the measure sub-identifier or null if the measure is single numerator
|
|
@@ -64,6 +67,44 @@ module QME
|
|
|
64
67
|
pipeline
|
|
65
68
|
end
|
|
66
69
|
|
|
70
|
+
|
|
71
|
+
#Calculate all of the supoplemental data elements
|
|
72
|
+
def calculate_supplemental_data_elements
|
|
73
|
+
|
|
74
|
+
match = {'value.measure_id' => @measure_id,
|
|
75
|
+
'value.sub_id' => @sub_id,
|
|
76
|
+
'value.effective_date' => @parameter_values['effective_date'],
|
|
77
|
+
'value.test_id' => @parameter_values['test_id'],
|
|
78
|
+
'value.manual_exclusion' => {'$in' => [nil, false]}}
|
|
79
|
+
|
|
80
|
+
keys = @measure_def["population_ids"].keys - [QME::QualityReport::OBSERVATION, "stratification"]
|
|
81
|
+
supplemental_data = Hash[*keys.map{|k| [k,{QME::QualityReport::RACE => {},
|
|
82
|
+
QME::QualityReport::ETHNICITY => {},
|
|
83
|
+
QME::QualityReport::SEX => {},
|
|
84
|
+
QME::QualityReport::PAYER => {}}]}.flatten]
|
|
85
|
+
|
|
86
|
+
keys.each do |pop_id|
|
|
87
|
+
_match = match.clone
|
|
88
|
+
_match["value.#{pop_id}"] = {"$gt" => 0}
|
|
89
|
+
SUPPLEMENTAL_DATA_ELEMENTS.each_pair do |supp_element,location|
|
|
90
|
+
group1 = {"$group" => { "_id" => { "id" => "$_id", "val" => location}}}
|
|
91
|
+
group2 = {"$group" => {"_id" => "$_id.val", "val" =>{"$sum" => 1} }}
|
|
92
|
+
pipeline = [{"$match" =>_match},group1,group2]
|
|
93
|
+
aggregate = get_db.command(:aggregate => 'patient_cache', :pipeline => pipeline)
|
|
94
|
+
|
|
95
|
+
v = {}
|
|
96
|
+
(aggregate["result"] || []).each do |entry|
|
|
97
|
+
code = entry["_id"].nil? ? "UNK" : entry["_id"]
|
|
98
|
+
v[code] = entry["val"]
|
|
99
|
+
end
|
|
100
|
+
supplemental_data[pop_id] ||= {}
|
|
101
|
+
supplemental_data[pop_id][supp_element] = v
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
supplemental_data
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
67
108
|
# Examines the patient_cache collection and generates a total of all groups
|
|
68
109
|
# for the measure. The totals are placed in a document in the query_cache
|
|
69
110
|
# collection.
|
|
@@ -104,6 +145,7 @@ module QME
|
|
|
104
145
|
result.reject! {|k, v| k == '_id'} # get rid of the group id the Mongo forced us to use
|
|
105
146
|
# result['exclusions'] += get_db['patient_cache'].find(base_query.merge({'value.manual_exclusion'=>true})).count
|
|
106
147
|
result.merge!(execution_time: (Time.now.to_i - @parameter_values['start_time'].to_i)) if @parameter_values['start_time']
|
|
148
|
+
result[:supplemental_data] = self.calculate_supplemental_data_elements
|
|
107
149
|
get_db()["query_cache"].insert(result)
|
|
108
150
|
get_db().command({:getLastError => 1}) # make sure last insert finished before we continue
|
|
109
151
|
result
|
data/lib/qme/quality_report.rb
CHANGED
|
@@ -18,6 +18,12 @@ module QME
|
|
|
18
18
|
ANTINUMERATOR = 'antinumerator'
|
|
19
19
|
CONSIDERED = 'considered'
|
|
20
20
|
|
|
21
|
+
RACE = 'RACE'
|
|
22
|
+
ETHNICITY = 'ETHNICITY'
|
|
23
|
+
SEX ='SEX'
|
|
24
|
+
POSTAL_CODE = 'POSTAL_CODE'
|
|
25
|
+
PAYER = 'PAYER'
|
|
26
|
+
|
|
21
27
|
# Gets rid of all calculated QualityReports by dropping the patient_cache
|
|
22
28
|
# and query_cache collections
|
|
23
29
|
def self.destroy_all
|
|
@@ -144,11 +150,14 @@ module QME
|
|
|
144
150
|
filters.each {|key, value| value.sort_by! {|v| (v.is_a? Hash) ? "#{v}" : v} if value.is_a? Array} unless filters.nil?
|
|
145
151
|
end
|
|
146
152
|
|
|
147
|
-
def patient_result
|
|
153
|
+
def patient_result(patient_id = nil)
|
|
148
154
|
cache = get_db()["patient_cache"]
|
|
149
155
|
query = {'value.measure_id' => @measure_id, 'value.sub_id' => @sub_id,
|
|
150
156
|
'value.effective_date' => @parameter_values['effective_date'],
|
|
151
157
|
'value.test_id' => @parameter_values['test_id']}
|
|
158
|
+
if patient_id
|
|
159
|
+
query['value.medical_record_id'] = patient_id
|
|
160
|
+
end
|
|
152
161
|
cache.find(query).first()
|
|
153
162
|
end
|
|
154
163
|
|
data/lib/qme/version.rb
CHANGED
|
@@ -8,13 +8,14 @@ require 'rubyXL'
|
|
|
8
8
|
require "qme/version"
|
|
9
9
|
require 'qme/database_access'
|
|
10
10
|
require 'qme/quality_measure'
|
|
11
|
+
require 'qme/quality_report'
|
|
11
12
|
|
|
12
13
|
require 'qme/map/map_reduce_builder'
|
|
13
14
|
require 'qme/map/map_reduce_executor'
|
|
14
15
|
require 'qme/map/measure_calculation_job'
|
|
15
16
|
require 'qme/map/cv_aggregator'
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
|
|
19
20
|
require 'qme/bundle/eh_measure_sheet'
|
|
20
21
|
require 'qme/bundle/eh_patient_importer'
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
"_id": "507ec1407042f9362c000017",
|
|
3
3
|
"birthdate": 946715400,
|
|
4
4
|
"medical_record_number" : "1236",
|
|
5
|
+
"race" : { "code" : "1002-5",
|
|
6
|
+
"name" : "American Indian or Alaska Native",
|
|
7
|
+
"codeSystem" : "CDC Race" },
|
|
8
|
+
"ethnicity" : { "code" : "2186-5",
|
|
9
|
+
"name" : "Not Hispanic or Latino",
|
|
10
|
+
"codeSystem" : "CDC Race" },
|
|
5
11
|
"conditions": [
|
|
6
12
|
{
|
|
7
13
|
"codes": {
|
|
@@ -35,5 +35,14 @@ class MapReduceBuilderTest < MiniTest::Unit::TestCase
|
|
|
35
35
|
binding = context.get_binding
|
|
36
36
|
assert_equal 10, eval("a",binding)
|
|
37
37
|
assert_equal 20, eval("b",binding)
|
|
38
|
+
assert_equal false, eval("enable_logging",binding)
|
|
39
|
+
vars = {'enable_logging'=>true}
|
|
40
|
+
context = QME::MapReduce::Builder::Context.new(get_db(), vars)
|
|
41
|
+
binding = context.get_binding
|
|
42
|
+
assert_equal true, eval("enable_logging",binding)
|
|
43
|
+
vars = {'enable_logging'=>false}
|
|
44
|
+
context = QME::MapReduce::Builder::Context.new(get_db(), vars)
|
|
45
|
+
binding = context.get_binding
|
|
46
|
+
assert_equal false, eval("enable_logging",binding)
|
|
38
47
|
end
|
|
39
48
|
end
|
|
@@ -18,6 +18,7 @@ class MapReduceExecutorTest < MiniTest::Unit::TestCase
|
|
|
18
18
|
'effective_date' => Time.gm(2011, 1, 15).to_i)
|
|
19
19
|
|
|
20
20
|
executor.map_records_into_measure_groups
|
|
21
|
+
|
|
21
22
|
|
|
22
23
|
assert_equal 4, get_db['patient_cache'].find().count
|
|
23
24
|
assert_equal 3, get_db['patient_cache'].find("value.#{QME::QualityReport::POPULATION}" => 1).count
|
|
@@ -26,6 +27,44 @@ class MapReduceExecutorTest < MiniTest::Unit::TestCase
|
|
|
26
27
|
assert_equal 1, get_db['patient_cache'].find("value.#{QME::QualityReport::NUMERATOR}" => 1).count
|
|
27
28
|
end
|
|
28
29
|
|
|
30
|
+
|
|
31
|
+
def test_calculate_supplemental_data_elements
|
|
32
|
+
executor = QME::MapReduce::Executor.new("2E679CD2-3FEC-4A75-A75A-61403E5EFEE8", nil,
|
|
33
|
+
'effective_date' => Time.gm(2011, 1, 15).to_i)
|
|
34
|
+
|
|
35
|
+
executor.map_records_into_measure_groups
|
|
36
|
+
executor.count_records_in_measure_groups
|
|
37
|
+
assert_equal 1, get_db['query_cache'].find().count
|
|
38
|
+
doc = get_db['query_cache'].find().first
|
|
39
|
+
suppl = doc["supplemental_data"]
|
|
40
|
+
assert !suppl.empty?, "should contain supplemental data entries"
|
|
41
|
+
ipp = {QME::QualityReport::RACE =>{"UNK"=>2, "1002-5"=>1},
|
|
42
|
+
QME::QualityReport::ETHNICITY => {"UNK"=>1, "2186-5"=>2},
|
|
43
|
+
QME::QualityReport::PAYER => {"UNK"=>3},
|
|
44
|
+
QME::QualityReport::SEX => {"F"=>2,"M"=>1}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
denom = {QME::QualityReport::RACE =>{"UNK"=>1, "1002-5"=>1},
|
|
48
|
+
QME::QualityReport::ETHNICITY => { "2186-5"=>2},
|
|
49
|
+
QME::QualityReport::PAYER => {"UNK"=>2},
|
|
50
|
+
QME::QualityReport::SEX => {"F"=>2}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
numer = {QME::QualityReport::RACE =>{"UNK"=>1},
|
|
54
|
+
QME::QualityReport::ETHNICITY => {"2186-5"=>1},
|
|
55
|
+
QME::QualityReport::PAYER => {"UNK"=>1},
|
|
56
|
+
QME::QualityReport::SEX => {"F"=>1}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
assert_equal ipp, suppl[QME::QualityReport::POPULATION]
|
|
62
|
+
assert_equal denom, suppl[QME::QualityReport::DENOMINATOR]
|
|
63
|
+
assert_equal numer, suppl[QME::QualityReport::NUMERATOR]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
29
68
|
def test_count_records_in_measure_groups
|
|
30
69
|
executor = QME::MapReduce::Executor.new("2E679CD2-3FEC-4A75-A75A-61403E5EFEE8", nil,
|
|
31
70
|
'effective_date' => Time.gm(2011, 1, 15).to_i)
|
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.4.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -13,7 +13,7 @@ authors:
|
|
|
13
13
|
autorequire:
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
|
-
date: 2013-
|
|
16
|
+
date: 2013-04-22 00:00:00.000000000 Z
|
|
17
17
|
dependencies:
|
|
18
18
|
- !ruby/object:Gem::Dependency
|
|
19
19
|
name: moped
|
|
@@ -232,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
232
232
|
version: '0'
|
|
233
233
|
requirements: []
|
|
234
234
|
rubyforge_project:
|
|
235
|
-
rubygems_version: 1.8.
|
|
235
|
+
rubygems_version: 1.8.25
|
|
236
236
|
signing_key:
|
|
237
237
|
specification_version: 3
|
|
238
238
|
summary: This library can run JavaScript based clinical quality measures on a repository
|
|
@@ -264,4 +264,3 @@ test_files:
|
|
|
264
264
|
- test/unit/qme/map/measure_calculation_job_test.rb
|
|
265
265
|
- test/unit/qme/quality_measure_test.rb
|
|
266
266
|
- test/unit/qme/quality_report_test.rb
|
|
267
|
-
has_rdoc:
|