quality-measure-engine 1.1.1 → 1.1.2
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 +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:
|