quality-measure-engine 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/lib/qme/ext/record.rb +13 -19
- data/lib/qme/importer/generic_importer.rb +4 -2
- data/lib/qme/quality_measure.rb +4 -0
- data/lib/tasks/measure.rake +16 -1
- data/spec/qme/bundle_spec.rb +37 -0
- data/spec/qme/importer/generic_importer_spec.rb +73 -0
- data/spec/qme/importer/measure_properties_generator_spec.rb +15 -0
- data/spec/qme/importer/property_matcher_spec.rb +174 -0
- data/spec/qme/map/map_reduce_builder_spec.rb +38 -0
- data/spec/qme/map/measures_spec.rb +38 -0
- data/spec/qme/map/patient_mapper_spec.rb +11 -0
- data/spec/qme/measure_loader_spec.rb +12 -0
- data/spec/qme/properties_builder_spec.rb +61 -0
- data/spec/qme/quality_report_spec.rb +74 -0
- data/spec/spec_helper.rb +120 -0
- data/spec/validate_measures_spec.rb +21 -0
- metadata +40 -29
data/Gemfile
CHANGED
@@ -7,7 +7,7 @@ gem 'bson_ext', '1.5.1', :platforms => :mri
|
|
7
7
|
gem 'rake'
|
8
8
|
#gem 'pry', :require => true
|
9
9
|
#gem 'health-data-standards', :git => 'https://github.com/projectcypress/health-data-standards.git', :branch => 'master'
|
10
|
-
gem 'health-data-standards', '0.
|
10
|
+
gem 'health-data-standards', '0.8.0'
|
11
11
|
|
12
12
|
group :test do
|
13
13
|
gem 'cover_me', '>= 1.0.0.rc5', :platforms => :ruby_19
|
data/lib/qme/ext/record.rb
CHANGED
@@ -1,49 +1,43 @@
|
|
1
1
|
# Extensions to the Record model in health-data-standards to support
|
2
2
|
# quality measure calculation
|
3
3
|
class Record
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
def procedures_performed
|
6
|
+
@procedures_performed = procedures.to_a + immunizations.to_a
|
7
|
+
end
|
8
|
+
|
6
9
|
def procedure_results
|
7
|
-
results.to_a + vital_signs.to_a + procedures.to_a
|
10
|
+
@procedure_results ||= results.to_a + vital_signs.to_a + procedures.to_a
|
8
11
|
end
|
9
12
|
|
10
13
|
def laboratory_tests
|
11
|
-
results.to_a + vital_signs.to_a
|
14
|
+
@laboratory_tests ||= results.to_a + vital_signs.to_a
|
12
15
|
end
|
13
16
|
|
14
17
|
def all_meds
|
15
|
-
medications.to_a + immunizations.to_a
|
18
|
+
@all_meds ||= medications.to_a + immunizations.to_a
|
16
19
|
end
|
17
20
|
|
18
21
|
def active_diagnosis
|
19
|
-
conditions.any_of({:status => 'active'}, {:status => nil}).to_a +
|
22
|
+
@active_diagnosis ||= conditions.any_of({:status => 'active'}, {:status => nil}).to_a +
|
20
23
|
social_history.any_of({:status => 'active'}, {:status => nil}).to_a
|
21
24
|
end
|
22
25
|
|
23
26
|
def inactive_diagnosis
|
24
|
-
conditions.any_of({:status => 'inactive'}, {:status => nil}).to_a +
|
27
|
+
@inactive_diagnosis ||= conditions.any_of({:status => 'inactive'}, {:status => nil}).to_a +
|
25
28
|
social_history.any_of({:status => 'inactive'}, {:status => nil}).to_a
|
26
29
|
end
|
27
30
|
|
28
31
|
def resolved_diagnosis
|
29
|
-
conditions.any_of({:status => 'resolved'}, {:status => nil}).to_a +
|
32
|
+
@resolved_diagnosis ||= conditions.any_of({:status => 'resolved'}, {:status => nil}).to_a +
|
30
33
|
social_history.any_of({:status => 'resolved'}, {:status => nil}).to_a
|
31
34
|
end
|
32
35
|
|
33
36
|
def all_problems
|
34
|
-
conditions.to_a + social_history.to_a
|
37
|
+
@all_problems ||= conditions.to_a + social_history.to_a
|
35
38
|
end
|
36
39
|
|
37
40
|
def all_devices
|
38
|
-
conditions.to_a + procedures.to_a + care_goals.to_a + medical_equipment.to_a
|
41
|
+
@all_devices ||= conditions.to_a + procedures.to_a + care_goals.to_a + medical_equipment.to_a
|
39
42
|
end
|
40
|
-
|
41
|
-
memoize :procedure_results
|
42
|
-
memoize :laboratory_tests
|
43
|
-
memoize :all_meds
|
44
|
-
memoize :active_diagnosis
|
45
|
-
memoize :inactive_diagnosis
|
46
|
-
memoize :resolved_diagnosis
|
47
|
-
memoize :all_problems
|
48
|
-
memoize :all_devices
|
49
43
|
end
|
@@ -44,10 +44,12 @@ module QME
|
|
44
44
|
case standard_category
|
45
45
|
when 'encounter'
|
46
46
|
patient.encounters
|
47
|
+
when 'immunization'
|
48
|
+
patient.immunizations
|
47
49
|
when 'procedure'
|
48
50
|
case qds_data_type
|
49
51
|
when 'procedure_performed'
|
50
|
-
patient.
|
52
|
+
patient.procedures_performed
|
51
53
|
when 'procedure_adverse_event', 'procedure_intolerance'
|
52
54
|
patient.allergies
|
53
55
|
when 'procedure_result'
|
@@ -60,7 +62,7 @@ module QME
|
|
60
62
|
when 'laboratory_test'
|
61
63
|
patient.laboratory_tests
|
62
64
|
when 'physical_exam'
|
63
|
-
patient.
|
65
|
+
patient.procedure_results
|
64
66
|
when 'medication'
|
65
67
|
case qds_data_type
|
66
68
|
when 'medication_dispensed', 'medication_order', 'medication_active', 'medication_administered'
|
data/lib/qme/quality_measure.rb
CHANGED
@@ -18,6 +18,10 @@ module QME
|
|
18
18
|
result
|
19
19
|
end
|
20
20
|
|
21
|
+
def self.get_measures(measure_ids)
|
22
|
+
get_db.collection('measures').find('id' => {"$in" => measure_ids})
|
23
|
+
end
|
24
|
+
|
21
25
|
# Creates a new QualityMeasure
|
22
26
|
# @param [String] measure_id value of the measure's id field
|
23
27
|
# @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
|
data/lib/tasks/measure.rake
CHANGED
@@ -6,7 +6,6 @@ gem 'rubyzip'
|
|
6
6
|
require 'zip/zip'
|
7
7
|
require 'zip/zipfilesystem'
|
8
8
|
require File.join(path,'../quality-measure-engine')
|
9
|
-
|
10
9
|
measures_dir = ENV['MEASURE_DIR'] || 'measures'
|
11
10
|
bundle_dir = ENV['BUNDLE_DIR'] || './'
|
12
11
|
xls_dir = ENV['XLS_DIR'] || 'xls'
|
@@ -91,5 +90,21 @@ EOF
|
|
91
90
|
file.close
|
92
91
|
end
|
93
92
|
end
|
93
|
+
|
94
|
+
desc "export results of measures (expects date in YYYY-MM-DD format)"
|
95
|
+
task :export_results, :effective_date do |t, args|
|
96
|
+
measure_ids = ENV["MEASURES"].split(",")
|
97
|
+
measures = QME::QualityMeasure.get_measures(measure_ids)
|
98
|
+
effective_date_s = args[:effective_date] || "2010-12-31"
|
99
|
+
effective_date = Time.parse(effective_date_s).to_i
|
100
|
+
measures.each do |measure|
|
101
|
+
qr = QME::QualityReport.new(measure['id'], measure['sub_id'], 'effective_date' => effective_date)
|
102
|
+
qr.calculate(false) unless qr.calculated?
|
103
|
+
r = qr.result
|
104
|
+
|
105
|
+
puts "#{measure['id']}#{measure['sub_id']}: #{r['numerator']}/#{r['denominator']}/#{r['population']} (#{r['exclusions']})"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
94
109
|
|
95
110
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
describe QME::MapReduce::Executor do
|
2
|
+
|
3
|
+
before :all do
|
4
|
+
@bundle_dir = File.join(File.dirname(__FILE__),'../../fixtures/bundle')
|
5
|
+
@measure_dir = 'measures'
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
@loader = QME::Database::Loader.new('test')
|
10
|
+
@loader.get_db.drop_collection('measures')
|
11
|
+
@loader.get_db.drop_collection('bundles')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'Should be able to load a bundle' do
|
15
|
+
bundle = @loader.save_bundle(@bundle_dir, @measure_dir)
|
16
|
+
bundle[:measures].length.should == 1
|
17
|
+
bundle[:bundle_data][:extensions].length.should == 3
|
18
|
+
bundle[:bundle_data]['name'].should == "test_bundle"
|
19
|
+
@loader.get_db['bundles'].count.should == 1
|
20
|
+
@loader.get_db['bundles'].find_one['name'].should == 'test_bundle'
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
it 'should be able to remove a bundle' do
|
25
|
+
bundle = @loader.save_bundle(@bundle_dir, @measure_dir)
|
26
|
+
bundle_measures_count = bundle[:measures].length
|
27
|
+
@loader.get_db['bundles'].count.should == 1
|
28
|
+
measures = @loader.get_db['measures'].count
|
29
|
+
|
30
|
+
@loader.remove_bundle(bundle[:bundle_data]['_id'])
|
31
|
+
@loader.get_db['bundles'].count.should == 0
|
32
|
+
measures = @loader.get_db['measures'].count.should == (measures - bundle_measures_count)
|
33
|
+
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
describe QME::Importer::GenericImporter do
|
2
|
+
|
3
|
+
it "should properly handle devices" do
|
4
|
+
measure_def = {'measure' => {"cardiac_pacer" => {
|
5
|
+
"standard_category" => "device",
|
6
|
+
"qds_data_type" => "device_applied",
|
7
|
+
"type" => "array",
|
8
|
+
"items" => {
|
9
|
+
"type" => "number",
|
10
|
+
"format" => "utc-sec"
|
11
|
+
},
|
12
|
+
"codes" => [
|
13
|
+
{
|
14
|
+
"set" => "SNOMED-CT",
|
15
|
+
"values" => [
|
16
|
+
"14106009",
|
17
|
+
"56961003"
|
18
|
+
]
|
19
|
+
}
|
20
|
+
]
|
21
|
+
}}}
|
22
|
+
|
23
|
+
entry = Entry.new
|
24
|
+
entry.add_code('14106009', 'SNOMED-CT')
|
25
|
+
entry.start_time = 1026777600
|
26
|
+
|
27
|
+
patient = Record.new
|
28
|
+
patient.medical_equipment = [entry]
|
29
|
+
|
30
|
+
gi = QME::Importer::GenericImporter.new(measure_def)
|
31
|
+
measure_info = gi.parse(patient)
|
32
|
+
measure_info['cardiac_pacer'].should include(1026777600)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should handle active conditions" do
|
36
|
+
measure_def = {'measure' => {"silliness" => {
|
37
|
+
"standard_category" => "diagnosis_condition_problem",
|
38
|
+
"qds_data_type" => "diagnosis_active",
|
39
|
+
"type" => "array",
|
40
|
+
"items" => {
|
41
|
+
"type" => "number",
|
42
|
+
"format" => "utc-sec"
|
43
|
+
},
|
44
|
+
"codes" => [
|
45
|
+
{
|
46
|
+
"set" => "SNOMED-CT",
|
47
|
+
"values" => [
|
48
|
+
"14106009",
|
49
|
+
"56961003"
|
50
|
+
]
|
51
|
+
}
|
52
|
+
]
|
53
|
+
}}}
|
54
|
+
|
55
|
+
entry1 = Entry.new
|
56
|
+
entry1.add_code('14106009', 'SNOMED-CT')
|
57
|
+
entry1.start_time = 1026777600
|
58
|
+
entry1.status = 'active'
|
59
|
+
|
60
|
+
entry2 = Entry.new
|
61
|
+
entry2.add_code('14106009', 'SNOMED-CT')
|
62
|
+
entry2.start_time = 1026777601
|
63
|
+
entry2.status = 'inactive'
|
64
|
+
|
65
|
+
patient = Record.new
|
66
|
+
patient.conditions = [entry1, entry2]
|
67
|
+
|
68
|
+
gi = QME::Importer::GenericImporter.new(measure_def)
|
69
|
+
measure_info = gi.parse(patient)
|
70
|
+
measure_info['silliness'].should include(1026777600)
|
71
|
+
measure_info['silliness'].should_not include(1026777601)
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
describe QME::Importer::MeasurePropertiesGenerator do
|
2
|
+
it 'should generate measure properties' do
|
3
|
+
doc = Nokogiri::XML(File.new('fixtures/c32_fragments/0032/numerator.xml'))
|
4
|
+
doc.root.add_namespace_definition('cda', 'urn:hl7-org:v3')
|
5
|
+
|
6
|
+
measure_json = JSON.parse(File.read(File.join('fixtures', 'entry', 'sample.json')))
|
7
|
+
QME::Importer::MeasurePropertiesGenerator.instance.add_measure('0043', QME::Importer::GenericImporter.new(measure_json))
|
8
|
+
|
9
|
+
patient = HealthDataStandards::Import::C32::PatientImporter.instance.parse_c32(doc)
|
10
|
+
|
11
|
+
measure_properties = QME::Importer::MeasurePropertiesGenerator.instance.generate_properties(patient)
|
12
|
+
|
13
|
+
measure_properties['0043']['encounter'].should include(1270598400)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
describe QME::Importer::PropertyMatcher do
|
2
|
+
|
3
|
+
it "should raise an error when it can't determine the property schema" do
|
4
|
+
property_description = {
|
5
|
+
"type" => "cheese",
|
6
|
+
"description" => "A cheesey example"
|
7
|
+
}
|
8
|
+
|
9
|
+
pm = QME::Importer::PropertyMatcher.new(property_description)
|
10
|
+
expect {pm.match([])}.to raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should be able to extract a date list property" do
|
14
|
+
property_description = {
|
15
|
+
"type" => "array",
|
16
|
+
"items" => {
|
17
|
+
"type" => "number"
|
18
|
+
},
|
19
|
+
"codes" => [
|
20
|
+
{
|
21
|
+
"set" => "SNOMED-CT",
|
22
|
+
"values" => ["314443004"]
|
23
|
+
}
|
24
|
+
]
|
25
|
+
}
|
26
|
+
|
27
|
+
pm = QME::Importer::PropertyMatcher.new(property_description)
|
28
|
+
|
29
|
+
entry = Entry.new
|
30
|
+
entry.add_code('314443004', 'SNOMED-CT')
|
31
|
+
entry.start_time = 1026777600
|
32
|
+
|
33
|
+
result = pm.match([entry])
|
34
|
+
result.should include(1026777600)
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
describe 'when extracting a date/value list property' do
|
39
|
+
it "should be able to deal with number values" do
|
40
|
+
property_description = {
|
41
|
+
"type" => "array",
|
42
|
+
"items" => {
|
43
|
+
"type" => "object",
|
44
|
+
"properties" => {
|
45
|
+
"value" => {
|
46
|
+
"type" => "number"
|
47
|
+
},
|
48
|
+
"date" => {
|
49
|
+
"type" => "number"
|
50
|
+
}
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"codes" => [
|
54
|
+
{
|
55
|
+
"set" => "SNOMED-CT",
|
56
|
+
"values" => ["314443004"]
|
57
|
+
}
|
58
|
+
]
|
59
|
+
}
|
60
|
+
|
61
|
+
pm = QME::Importer::PropertyMatcher.new(property_description)
|
62
|
+
|
63
|
+
entry = Entry.new
|
64
|
+
entry.add_code('314443004', 'SNOMED-CT')
|
65
|
+
entry.set_value('11.45')
|
66
|
+
entry.start_time = 1026777600
|
67
|
+
|
68
|
+
result = pm.match([entry])
|
69
|
+
result.should include({'date' => 1026777600, 'value' => 11.45})
|
70
|
+
result.length.should == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be able to deal with boolean values" do
|
74
|
+
property_description = {
|
75
|
+
"type" => "array",
|
76
|
+
"items" => {
|
77
|
+
"type" => "object",
|
78
|
+
"properties" => {
|
79
|
+
"value" => {
|
80
|
+
"type" => "boolean"
|
81
|
+
},
|
82
|
+
"date" => {
|
83
|
+
"type" => "number"
|
84
|
+
}
|
85
|
+
}
|
86
|
+
},
|
87
|
+
"codes" => [
|
88
|
+
{
|
89
|
+
"set" => "SNOMED-CT",
|
90
|
+
"values" => ["314443004"]
|
91
|
+
}
|
92
|
+
]
|
93
|
+
}
|
94
|
+
|
95
|
+
pm = QME::Importer::PropertyMatcher.new(property_description)
|
96
|
+
|
97
|
+
entry = Entry.new
|
98
|
+
entry.add_code('314443004', 'SNOMED-CT')
|
99
|
+
entry.set_value('true')
|
100
|
+
entry.start_time = 1026777600
|
101
|
+
|
102
|
+
result = pm.match([entry])
|
103
|
+
result.should include({'date' => 1026777600, 'value' => true})
|
104
|
+
result.length.should == 1
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should be able to deal with string" do
|
108
|
+
property_description = {
|
109
|
+
"type" => "array",
|
110
|
+
"items" => {
|
111
|
+
"type" => "object",
|
112
|
+
"properties" => {
|
113
|
+
"value" => {
|
114
|
+
"type" => "string"
|
115
|
+
},
|
116
|
+
"date" => {
|
117
|
+
"type" => "number"
|
118
|
+
}
|
119
|
+
}
|
120
|
+
},
|
121
|
+
"codes" => [
|
122
|
+
{
|
123
|
+
"set" => "SNOMED-CT",
|
124
|
+
"values" => ["314443004"]
|
125
|
+
}
|
126
|
+
]
|
127
|
+
}
|
128
|
+
|
129
|
+
pm = QME::Importer::PropertyMatcher.new(property_description)
|
130
|
+
|
131
|
+
entry = Entry.new
|
132
|
+
entry.add_code('314443004', 'SNOMED-CT')
|
133
|
+
entry.set_value('super critical')
|
134
|
+
entry.start_time = 1026777600
|
135
|
+
|
136
|
+
result = pm.match([entry])
|
137
|
+
result.should include({'date' => 1026777600, 'value' => 'super critical'})
|
138
|
+
result.length.should == 1
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should be able to extract a date range property" do
|
143
|
+
property_description = {
|
144
|
+
"type" => "array",
|
145
|
+
"items" => {
|
146
|
+
"type" => "object",
|
147
|
+
"properties" => {
|
148
|
+
"start" => {
|
149
|
+
"type" => "number"
|
150
|
+
},
|
151
|
+
"end" => {
|
152
|
+
"type" => "number"
|
153
|
+
}
|
154
|
+
}
|
155
|
+
},
|
156
|
+
"codes" => [
|
157
|
+
{
|
158
|
+
"set" => "SNOMED-CT",
|
159
|
+
"values" => ["194774006"]
|
160
|
+
}
|
161
|
+
]
|
162
|
+
}
|
163
|
+
|
164
|
+
pm = QME::Importer::PropertyMatcher.new(property_description)
|
165
|
+
|
166
|
+
entry = Entry.new
|
167
|
+
entry.add_code('194774006', 'SNOMED-CT')
|
168
|
+
entry.start_time = 1026777600
|
169
|
+
entry.end_time = 1189814400
|
170
|
+
|
171
|
+
result = pm.match([entry])
|
172
|
+
result.should include({'start' => 1026777600, 'end' => 1189814400})
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
describe QME::MapReduce::Builder do
|
2
|
+
|
3
|
+
before do
|
4
|
+
@loader = QME::Database::Loader.new('test')
|
5
|
+
raw_measure_json = File.read(File.join('fixtures', 'entry', 'sample.json'))
|
6
|
+
@measure_json = JSON.parse(raw_measure_json)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should extract the measure metadata' do
|
10
|
+
measure = QME::MapReduce::Builder.new(@loader.get_db, @measure_json, 'effective_date'=>Time.gm(2010, 9, 19).to_i)
|
11
|
+
measure.id.should eql('0043')
|
12
|
+
end
|
13
|
+
it 'should extract one parameter for measure 0043' do
|
14
|
+
time = Time.gm(2010, 9, 19).to_i
|
15
|
+
measure = QME::MapReduce::Builder.new(@loader.get_db, @measure_json, 'effective_date'=>time)
|
16
|
+
measure.params.size.should eql(1)
|
17
|
+
measure.params.should have_key('effective_date')
|
18
|
+
measure.params['effective_date'].should eql(time)
|
19
|
+
end
|
20
|
+
it 'should raise a RuntimeError if not passed all the parameters' do
|
21
|
+
lambda { QME::MapReduce::Builder.new(@measure_json) }.should
|
22
|
+
raise_error(RuntimeError, 'No value supplied for measure parameter: effective_date')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe QME::MapReduce::Builder::Context do
|
27
|
+
before do
|
28
|
+
@loader = QME::Database::Loader.new('test')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should set instance methods from the supplied hash' do
|
32
|
+
vars = {'a'=>10, 'b'=>20}
|
33
|
+
context = QME::MapReduce::Builder::Context.new(@loader.get_db, vars)
|
34
|
+
binding = context.get_binding
|
35
|
+
eval("a",binding).should eql(10)
|
36
|
+
eval("b",binding).should eql(20)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
describe QME::MapReduce::Executor do
|
2
|
+
|
3
|
+
before do
|
4
|
+
@loader = QME::Database::Loader.new('test')
|
5
|
+
if ENV['MEASURE_DIR']
|
6
|
+
@measures = Dir.glob(File.join(ENV['MEASURE_DIR'], '*'))
|
7
|
+
else
|
8
|
+
@measures = Dir.glob(File.join('measures', '*'))
|
9
|
+
end
|
10
|
+
|
11
|
+
# define custom matchers
|
12
|
+
RSpec::Matchers.define :match_population do |population|
|
13
|
+
match do |value|
|
14
|
+
value == population
|
15
|
+
end
|
16
|
+
end
|
17
|
+
RSpec::Matchers.define :match_denominator do |denominator|
|
18
|
+
match do |value|
|
19
|
+
value == denominator
|
20
|
+
end
|
21
|
+
end
|
22
|
+
RSpec::Matchers.define :match_numerator do |numerator|
|
23
|
+
match do |value|
|
24
|
+
value == numerator
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RSpec::Matchers.define :match_exclusions do |exclusions|
|
28
|
+
match do |value|
|
29
|
+
value == exclusions
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should produce the expected results for each measure' do
|
35
|
+
validate_measures(@measures,@loader)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
describe QME::Measure::Loader do
|
2
|
+
before do
|
3
|
+
@measure_def_dir = File.join('fixtures', 'measure_defs', 'sample_single_from_multi_xls')
|
4
|
+
end
|
5
|
+
|
6
|
+
it 'Should load the sample measure correctly' do
|
7
|
+
measure = QME::Measure::Loader.load_measure(@measure_def_dir)
|
8
|
+
measure = measure[0]
|
9
|
+
measure['measure'].should have_key('eyes')
|
10
|
+
measure['measure'].should have_key('esrd_diagnosis_active')
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'ap'
|
2
|
+
|
3
|
+
describe QME::Measure::PropertiesBuilder do
|
4
|
+
before do
|
5
|
+
@json_file = File.join('fixtures', 'measure_props', 'props2.xlsx.json')
|
6
|
+
@properties = JSON.parse(File.read(@json_file))
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'Should patch the definition' do
|
10
|
+
@properties['N_1810']['value'].should be_nil
|
11
|
+
patched_properties = QME::Measure::PropertiesBuilder.patch_properties(@properties, @json_file)
|
12
|
+
patched_properties['N_1810']['value'].should have_key('type')
|
13
|
+
patched_properties['N_1472']['group_type'].should eql('abstract')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'Should group properties' do
|
17
|
+
@properties.values.select { |value| value['standard_concept_id']=='N_c102' }.should_not be_empty
|
18
|
+
patched_properties = QME::Measure::PropertiesBuilder.patch_properties(@properties, @json_file)
|
19
|
+
grouped_properties = QME::Measure::PropertiesBuilder.build_groups(patched_properties)
|
20
|
+
grouped_properties.select { |key| key['standard_concept_id']=='N_c102' }.should be_empty
|
21
|
+
grouped_properties.select { |key| key['standard_concept_id']=='N_c190' }.should_not be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'Should create the expected properties' do
|
25
|
+
result = QME::Measure::PropertiesBuilder.build_properties(@properties, @json_file)
|
26
|
+
result['measure'].should have_key('diastolic_blood_pressure_physical_exam_finding')
|
27
|
+
result['measure']['diastolic_blood_pressure_physical_exam_finding'].should have_key('standard_concept')
|
28
|
+
result['measure']['diastolic_blood_pressure_physical_exam_finding']['standard_concept'].should eql('diastolic_blood_pressure')
|
29
|
+
result['measure']['diastolic_blood_pressure_physical_exam_finding'].should have_key('standard_category')
|
30
|
+
result['measure']['diastolic_blood_pressure_physical_exam_finding']['standard_category'].should eql('physical_exam')
|
31
|
+
result['measure']['diastolic_blood_pressure_physical_exam_finding']['items']['type'].should eql('object')
|
32
|
+
result['measure']['diastolic_blood_pressure_physical_exam_finding'].should have_key('codes')
|
33
|
+
result['measure']['diastolic_blood_pressure_physical_exam_finding']['codes'].length.should eql(1)
|
34
|
+
result['measure'].should have_key('encounter_outpatient_encounter')
|
35
|
+
result['measure']['encounter_outpatient_encounter']['items']['type'].should eql('number')
|
36
|
+
result['measure'].should have_key('esrd_diagnosis_active')
|
37
|
+
result['measure']['esrd_diagnosis_active']['standard_category'].should eql('diagnosis_condition_problem')
|
38
|
+
result['measure']['esrd_diagnosis_active']['codes'].length.should eql(3)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'Should not create the excluded properties' do
|
42
|
+
result = QME::Measure::PropertiesBuilder.build_properties(@properties, @json_file)
|
43
|
+
result['measure'].select { |key| QME::Measure::PropertiesBuilder::PROPERTIES_TO_IGNORE.include?(key['standard_concept']) }.should be_empty
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'Should expand code ranges for CPT codes' do
|
47
|
+
result = QME::Measure::PropertiesBuilder.extract_code_values("1, 2-5, 6 ", "SNOMED-CT")
|
48
|
+
result.length.should eql(3)
|
49
|
+
result.should include("1")
|
50
|
+
result.should include("2-5")
|
51
|
+
result.should include("6")
|
52
|
+
result = QME::Measure::PropertiesBuilder.extract_code_values("1, 2-5, 6 ", "CPT")
|
53
|
+
result.length.should eql(6)
|
54
|
+
result.should include("1")
|
55
|
+
result.should include("2")
|
56
|
+
result.should include("3")
|
57
|
+
result.should include("4")
|
58
|
+
result.should include("5")
|
59
|
+
result.should include("6")
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
describe QME::QualityReport do
|
2
|
+
before do
|
3
|
+
loader = QME::Database::Loader.new
|
4
|
+
loader.drop_collection('query_cache')
|
5
|
+
loader.drop_collection('patient_cache')
|
6
|
+
loader.get_db['query_cache'].save(
|
7
|
+
"measure_id" => "test2",
|
8
|
+
"sub_id" => "b",
|
9
|
+
"initialPopulation" => 4,
|
10
|
+
"numerator" => 1,
|
11
|
+
"denominator" => 2,
|
12
|
+
"exclusions" => 1,
|
13
|
+
"effective_date" => Time.gm(2010, 9, 19).to_i
|
14
|
+
)
|
15
|
+
loader.get_db['patient_cache'].save(
|
16
|
+
"value" => {
|
17
|
+
"population" => false,
|
18
|
+
"denominator" => false,
|
19
|
+
"numerator" => false,
|
20
|
+
"exclusions" => false,
|
21
|
+
"antinumerator" => false,
|
22
|
+
"medical_record_id" => "0616911582",
|
23
|
+
"first" => "Mary",
|
24
|
+
"last" => "Edwards",
|
25
|
+
"gender" => "F",
|
26
|
+
"birthdate" => Time.gm(1940, 9, 19).to_i,
|
27
|
+
"test_id" => nil,
|
28
|
+
"provider_performances" => nil,
|
29
|
+
"race" => {
|
30
|
+
"code" => "2106-3",
|
31
|
+
"code_set" => "CDC-RE"
|
32
|
+
},
|
33
|
+
"ethnicity" => {
|
34
|
+
"code" => "2135-2",
|
35
|
+
"code_set" => "CDC-RE"
|
36
|
+
},
|
37
|
+
"measure_id" => "test2",
|
38
|
+
"sub_id" => "b",
|
39
|
+
"effective_date" => Time.gm(2010, 9, 19).to_i
|
40
|
+
}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be able to determine if it has been calculated" do
|
45
|
+
qr = QME::QualityReport.new('test2', 'b', "effective_date" => Time.gm(2010, 9, 19).to_i)
|
46
|
+
qr.calculated?.should be_true
|
47
|
+
|
48
|
+
qr = QME::QualityReport.new('test2', 'b', "effective_date" => Time.gm(2010, 9, 20).to_i)
|
49
|
+
qr.calculated?.should be_false
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the result of a calculated quality measure" do
|
53
|
+
qr = QME::QualityReport.new('test2', 'b', "effective_date" => Time.gm(2010, 9, 19).to_i)
|
54
|
+
result = qr.result
|
55
|
+
|
56
|
+
result['numerator'].should == 1
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be able to clear all of the quality reports" do
|
60
|
+
QME::QualityReport.destroy_all
|
61
|
+
|
62
|
+
qr = QME::QualityReport.new('test2', 'b', "effective_date" => Time.gm(2010, 9, 19).to_i)
|
63
|
+
qr.calculated?.should be_false
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be remove results for updated patients" do
|
67
|
+
qr = QME::QualityReport.new('test2', 'b', "effective_date" => Time.gm(2010, 9, 19).to_i)
|
68
|
+
qr.calculated?.should be_true
|
69
|
+
qr.patients_cached?.should be_true
|
70
|
+
QME::QualityReport.update_patient_results("0616911582")
|
71
|
+
qr.calculated?.should be_false
|
72
|
+
qr.patients_cached?.should be_false
|
73
|
+
end
|
74
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
begin
|
2
|
+
require 'cover_me'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'cover_me unavailable, running without code coverage measurement'
|
5
|
+
end
|
6
|
+
require 'bundler/setup'
|
7
|
+
|
8
|
+
#require 'pry'
|
9
|
+
|
10
|
+
PROJECT_ROOT = File.dirname(__FILE__) + '/../'
|
11
|
+
|
12
|
+
require PROJECT_ROOT + 'lib/quality-measure-engine'
|
13
|
+
|
14
|
+
Bundler.require(:test)
|
15
|
+
ENV['DB_NAME'] = 'test'
|
16
|
+
|
17
|
+
def reload_bundle(bundle_dir='.', measure_dir=ENV['MEASURE_DIR'] || 'measures')
|
18
|
+
loader = QME::Database::Loader.new
|
19
|
+
loader.drop_collection('bundles')
|
20
|
+
loader.drop_collection('measures')
|
21
|
+
loader.drop_collection('manual_exclusions')
|
22
|
+
loader.save_bundle(bundle_dir, measure_dir)
|
23
|
+
loader
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_measures(measure_dirs, loader)
|
27
|
+
|
28
|
+
reload_bundle
|
29
|
+
|
30
|
+
loader.get_db.collection('manual_exclusions').save({'measure_id'=>'test1', 'medical_record_id'=>'1234567890'})
|
31
|
+
|
32
|
+
measure_dirs.each do |dir|
|
33
|
+
# check for sample data
|
34
|
+
fixture_dir = File.join('fixtures', 'measures', File.basename(dir))
|
35
|
+
patient_files = Dir.glob(File.join(fixture_dir, 'patients', '*.json'))
|
36
|
+
if patient_files.length==0
|
37
|
+
puts "Skipping #{dir}, no sample data in #{fixture_dir}"
|
38
|
+
next
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "Parsing #{dir}"
|
42
|
+
|
43
|
+
loader.drop_collection('records')
|
44
|
+
loader.drop_collection('query_cache')
|
45
|
+
loader.drop_collection('patient_cache')
|
46
|
+
|
47
|
+
# load measure from file system
|
48
|
+
# this is innefficient, could just load it from DB as its already stored there
|
49
|
+
measures = QME::Measure::Loader.load_measure(dir)
|
50
|
+
|
51
|
+
# patients can include an optional test_id population identifier, extract this if present
|
52
|
+
test_id = nil
|
53
|
+
|
54
|
+
# load db with sample patient records
|
55
|
+
patient_files.each do |patient_file|
|
56
|
+
patient = JSON.parse(File.read(patient_file))
|
57
|
+
test_id ||= patient['test_id']
|
58
|
+
loader.save('records', patient)
|
59
|
+
end
|
60
|
+
|
61
|
+
# load expected results
|
62
|
+
result_file = File.join('fixtures', 'measures', File.basename(dir), 'result.json')
|
63
|
+
expected = JSON.parse(File.read(result_file))
|
64
|
+
|
65
|
+
# evaulate measure using Map/Reduce and validate results
|
66
|
+
measures.each do |measure|
|
67
|
+
measure_id = measure['id']
|
68
|
+
sub_id = measure['sub_id']
|
69
|
+
puts "Validating measure #{measure_id}#{sub_id}"
|
70
|
+
executor = QME::MapReduce::Executor.new(measure_id, sub_id,
|
71
|
+
'effective_date'=>Time.gm(2010, 9, 19).to_i,
|
72
|
+
'test_id'=>test_id)
|
73
|
+
executor.map_records_into_measure_groups
|
74
|
+
result = executor.count_records_in_measure_groups
|
75
|
+
if expected['initialPopulation'] == nil
|
76
|
+
# multiple results for multi numerator/denominator measure
|
77
|
+
# loop through list of results to find the matching one
|
78
|
+
expected['results'].each do |expect|
|
79
|
+
if expect['id'].eql?(measure_id) && (sub_id==nil || expect['sub_id'].eql?(sub_id))
|
80
|
+
result['population'].should match_population(expect['initialPopulation'])
|
81
|
+
result['numerator'].should match_numerator(expect['numerator'])
|
82
|
+
result['denominator'].should match_denominator(expect['denominator'])
|
83
|
+
result['exclusions'].should match_exclusions(expect['exclusions'])
|
84
|
+
(result['numerator']+result['antinumerator']).should eql(expect['denominator'])
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
else
|
89
|
+
result['population'].should match_population(expected['initialPopulation'])
|
90
|
+
result['numerator'].should match_numerator(expected['numerator'])
|
91
|
+
result['denominator'].should match_denominator(expected['denominator'])
|
92
|
+
result['exclusions'].should match_exclusions(expected['exclusions'])
|
93
|
+
(result['numerator']+result['antinumerator']).should eql(expected['denominator'])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
puts ' - done'
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
def validate_patient_mapping(loader)
|
102
|
+
reload_bundle
|
103
|
+
loader.drop_collection('records')
|
104
|
+
loader.drop_collection('query_cache')
|
105
|
+
loader.drop_collection('patient_cache')
|
106
|
+
|
107
|
+
patient_file = File.join('fixtures', 'mapping', 'test1_numerator.json')
|
108
|
+
patient = JSON.parse(File.read(patient_file))
|
109
|
+
loader.save('records', patient)
|
110
|
+
|
111
|
+
executor = QME::MapReduce::Executor.new('test1', nil,
|
112
|
+
'effective_date'=>Time.gm(2010, 9, 19).to_i)
|
113
|
+
result = executor.get_patient_result('patient1')
|
114
|
+
|
115
|
+
result['population'].should be(true)
|
116
|
+
result['numerator'].should be(true)
|
117
|
+
result['denominator'].should be(true)
|
118
|
+
result['exclusions'].should be(false)
|
119
|
+
result['antinumerator'].should be(false)
|
120
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Validate the measure specifications and patient samples
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
describe JSON, 'All measure specifications' do
|
6
|
+
it 'should be valid JSON' do
|
7
|
+
Dir.glob(File.join('measures', '*', '*.json')).each do |measure_file|
|
8
|
+
measure = File.read(measure_file)
|
9
|
+
json = JSON.parse(measure)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe JSON, 'All patient samples' do
|
15
|
+
it 'should be valid JSON' do
|
16
|
+
Dir.glob(File.join('fixtures', 'measures', '*', 'patients', '*.json')).each do |measure_file|
|
17
|
+
measure = File.read(measure_file)
|
18
|
+
json = JSON.parse(measure)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,12 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
15
|
-
default_executable:
|
14
|
+
date: 2012-05-10 00:00:00.000000000Z
|
16
15
|
dependencies:
|
17
16
|
- !ruby/object:Gem::Dependency
|
18
17
|
name: mongo
|
19
|
-
requirement: &
|
18
|
+
requirement: &2158244120 !ruby/object:Gem::Requirement
|
20
19
|
none: false
|
21
20
|
requirements:
|
22
21
|
- - ~>
|
@@ -24,10 +23,10 @@ dependencies:
|
|
24
23
|
version: '1.3'
|
25
24
|
type: :runtime
|
26
25
|
prerelease: false
|
27
|
-
version_requirements: *
|
26
|
+
version_requirements: *2158244120
|
28
27
|
- !ruby/object:Gem::Dependency
|
29
28
|
name: rubyzip
|
30
|
-
requirement: &
|
29
|
+
requirement: &2158243320 !ruby/object:Gem::Requirement
|
31
30
|
none: false
|
32
31
|
requirements:
|
33
32
|
- - ~>
|
@@ -35,10 +34,10 @@ dependencies:
|
|
35
34
|
version: 0.9.4
|
36
35
|
type: :runtime
|
37
36
|
prerelease: false
|
38
|
-
version_requirements: *
|
37
|
+
version_requirements: *2158243320
|
39
38
|
- !ruby/object:Gem::Dependency
|
40
39
|
name: nokogiri
|
41
|
-
requirement: &
|
40
|
+
requirement: &2158242720 !ruby/object:Gem::Requirement
|
42
41
|
none: false
|
43
42
|
requirements:
|
44
43
|
- - ~>
|
@@ -46,10 +45,10 @@ dependencies:
|
|
46
45
|
version: 1.4.4
|
47
46
|
type: :runtime
|
48
47
|
prerelease: false
|
49
|
-
version_requirements: *
|
48
|
+
version_requirements: *2158242720
|
50
49
|
- !ruby/object:Gem::Dependency
|
51
50
|
name: resque
|
52
|
-
requirement: &
|
51
|
+
requirement: &2158242260 !ruby/object:Gem::Requirement
|
53
52
|
none: false
|
54
53
|
requirements:
|
55
54
|
- - ~>
|
@@ -57,10 +56,10 @@ dependencies:
|
|
57
56
|
version: 1.15.0
|
58
57
|
type: :runtime
|
59
58
|
prerelease: false
|
60
|
-
version_requirements: *
|
59
|
+
version_requirements: *2158242260
|
61
60
|
- !ruby/object:Gem::Dependency
|
62
61
|
name: resque-status
|
63
|
-
requirement: &
|
62
|
+
requirement: &2158235580 !ruby/object:Gem::Requirement
|
64
63
|
none: false
|
65
64
|
requirements:
|
66
65
|
- - ~>
|
@@ -68,10 +67,10 @@ dependencies:
|
|
68
67
|
version: 0.2.3
|
69
68
|
type: :runtime
|
70
69
|
prerelease: false
|
71
|
-
version_requirements: *
|
70
|
+
version_requirements: *2158235580
|
72
71
|
- !ruby/object:Gem::Dependency
|
73
72
|
name: jsonschema
|
74
|
-
requirement: &
|
73
|
+
requirement: &2158235040 !ruby/object:Gem::Requirement
|
75
74
|
none: false
|
76
75
|
requirements:
|
77
76
|
- - ~>
|
@@ -79,10 +78,10 @@ dependencies:
|
|
79
78
|
version: 2.0.0
|
80
79
|
type: :development
|
81
80
|
prerelease: false
|
82
|
-
version_requirements: *
|
81
|
+
version_requirements: *2158235040
|
83
82
|
- !ruby/object:Gem::Dependency
|
84
83
|
name: rspec
|
85
|
-
requirement: &
|
84
|
+
requirement: &2158234480 !ruby/object:Gem::Requirement
|
86
85
|
none: false
|
87
86
|
requirements:
|
88
87
|
- - ~>
|
@@ -90,10 +89,10 @@ dependencies:
|
|
90
89
|
version: 2.5.0
|
91
90
|
type: :development
|
92
91
|
prerelease: false
|
93
|
-
version_requirements: *
|
92
|
+
version_requirements: *2158234480
|
94
93
|
- !ruby/object:Gem::Dependency
|
95
94
|
name: awesome_print
|
96
|
-
requirement: &
|
95
|
+
requirement: &2158233760 !ruby/object:Gem::Requirement
|
97
96
|
none: false
|
98
97
|
requirements:
|
99
98
|
- - ~>
|
@@ -101,10 +100,10 @@ dependencies:
|
|
101
100
|
version: '0.3'
|
102
101
|
type: :development
|
103
102
|
prerelease: false
|
104
|
-
version_requirements: *
|
103
|
+
version_requirements: *2158233760
|
105
104
|
- !ruby/object:Gem::Dependency
|
106
105
|
name: roo
|
107
|
-
requirement: &
|
106
|
+
requirement: &2158232780 !ruby/object:Gem::Requirement
|
108
107
|
none: false
|
109
108
|
requirements:
|
110
109
|
- - ~>
|
@@ -112,10 +111,10 @@ dependencies:
|
|
112
111
|
version: 1.9.3
|
113
112
|
type: :development
|
114
113
|
prerelease: false
|
115
|
-
version_requirements: *
|
114
|
+
version_requirements: *2158232780
|
116
115
|
- !ruby/object:Gem::Dependency
|
117
116
|
name: builder
|
118
|
-
requirement: &
|
117
|
+
requirement: &2158231380 !ruby/object:Gem::Requirement
|
119
118
|
none: false
|
120
119
|
requirements:
|
121
120
|
- - ~>
|
@@ -123,10 +122,10 @@ dependencies:
|
|
123
122
|
version: 3.0.0
|
124
123
|
type: :development
|
125
124
|
prerelease: false
|
126
|
-
version_requirements: *
|
125
|
+
version_requirements: *2158231380
|
127
126
|
- !ruby/object:Gem::Dependency
|
128
127
|
name: spreadsheet
|
129
|
-
requirement: &
|
128
|
+
requirement: &2158230480 !ruby/object:Gem::Requirement
|
130
129
|
none: false
|
131
130
|
requirements:
|
132
131
|
- - ~>
|
@@ -134,10 +133,10 @@ dependencies:
|
|
134
133
|
version: 0.6.5.2
|
135
134
|
type: :development
|
136
135
|
prerelease: false
|
137
|
-
version_requirements: *
|
136
|
+
version_requirements: *2158230480
|
138
137
|
- !ruby/object:Gem::Dependency
|
139
138
|
name: google-spreadsheet-ruby
|
140
|
-
requirement: &
|
139
|
+
requirement: &2158229620 !ruby/object:Gem::Requirement
|
141
140
|
none: false
|
142
141
|
requirements:
|
143
142
|
- - ~>
|
@@ -145,7 +144,7 @@ dependencies:
|
|
145
144
|
version: 0.1.2
|
146
145
|
type: :development
|
147
146
|
prerelease: false
|
148
|
-
version_requirements: *
|
147
|
+
version_requirements: *2158229620
|
149
148
|
description: A library for extracting quality measure information from HITSP C32's
|
150
149
|
and ASTM CCR's
|
151
150
|
email: talk@projectpophealth.org
|
@@ -179,11 +178,22 @@ files:
|
|
179
178
|
- lib/tasks/patient_random.rake
|
180
179
|
- js/map_reduce_utils.js
|
181
180
|
- js/underscore_min.js
|
181
|
+
- spec/qme/bundle_spec.rb
|
182
|
+
- spec/qme/importer/generic_importer_spec.rb
|
183
|
+
- spec/qme/importer/measure_properties_generator_spec.rb
|
184
|
+
- spec/qme/importer/property_matcher_spec.rb
|
185
|
+
- spec/qme/map/map_reduce_builder_spec.rb
|
186
|
+
- spec/qme/map/measures_spec.rb
|
187
|
+
- spec/qme/map/patient_mapper_spec.rb
|
188
|
+
- spec/qme/measure_loader_spec.rb
|
189
|
+
- spec/qme/properties_builder_spec.rb
|
190
|
+
- spec/qme/quality_report_spec.rb
|
191
|
+
- spec/spec_helper.rb
|
192
|
+
- spec/validate_measures_spec.rb
|
182
193
|
- Gemfile
|
183
194
|
- README.md
|
184
195
|
- Rakefile
|
185
196
|
- VERSION
|
186
|
-
has_rdoc: true
|
187
197
|
homepage: http://github.com/pophealth/quality-measure-engine
|
188
198
|
licenses: []
|
189
199
|
post_install_message:
|
@@ -204,9 +214,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
214
|
version: '0'
|
205
215
|
requirements: []
|
206
216
|
rubyforge_project:
|
207
|
-
rubygems_version: 1.
|
217
|
+
rubygems_version: 1.8.10
|
208
218
|
signing_key:
|
209
219
|
specification_version: 3
|
210
220
|
summary: A library for extracting quality measure information from HITSP C32's and
|
211
221
|
ASTM CCR's
|
212
222
|
test_files: []
|
223
|
+
has_rdoc:
|