quality-measure-engine 1.1.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +12 -0
  2. data/.travis.yml +16 -0
  3. data/Gemfile +5 -21
  4. data/Gemfile.lock +126 -0
  5. data/LICENSE.txt +13 -0
  6. data/README.md +23 -44
  7. data/Rakefile +6 -29
  8. data/lib/qme/bundle/bundle.rb +34 -0
  9. data/lib/qme/bundle/importer.rb +69 -0
  10. data/lib/qme/database_access.rb +7 -11
  11. data/lib/qme/map/map_reduce_builder.rb +4 -1
  12. data/lib/qme/map/map_reduce_executor.rb +55 -43
  13. data/lib/qme/map/measure_calculation_job.rb +24 -23
  14. data/lib/qme/quality_measure.rb +5 -5
  15. data/lib/qme/quality_report.rb +37 -19
  16. data/lib/qme/railtie.rb +7 -0
  17. data/lib/qme/tasks/bundle.rake +14 -0
  18. data/lib/qme/version.rb +3 -0
  19. data/lib/quality-measure-engine.rb +13 -25
  20. data/quality-measure-engine.gemspec +28 -0
  21. data/test/fixtures/bundles/just_measure_0002.zip +0 -0
  22. data/test/fixtures/delayed_backend_mongoid_jobs/queued_job.json +9 -0
  23. data/test/fixtures/measures/measure_metadata.json +52 -0
  24. data/test/fixtures/records/barry_berry.json +471 -0
  25. data/test/fixtures/records/billy_jones_ipp.json +78 -0
  26. data/test/fixtures/records/jane_jones_numerator.json +120 -0
  27. data/test/fixtures/records/jill_jones_denominator.json +78 -0
  28. data/test/simplecov_setup.rb +18 -0
  29. data/test/test_helper.rb +26 -0
  30. data/test/unit/qme/map/map_reduce_builder_test.rb +38 -0
  31. data/test/unit/qme/map/map_reduce_executor_test.rb +56 -0
  32. data/test/unit/qme/map/measure_calculation_job_test.rb +22 -0
  33. data/test/unit/qme/quality_measure_test.rb +14 -0
  34. data/{spec/qme/quality_report_spec.rb → test/unit/qme/quality_report_test.rb} +32 -20
  35. metadata +91 -115
  36. data/VERSION +0 -1
  37. data/js/map_reduce_utils.js +0 -173
  38. data/js/underscore_min.js +0 -25
  39. data/lib/qme/ext/record.rb +0 -43
  40. data/lib/qme/importer/entry.rb +0 -126
  41. data/lib/qme/importer/generic_importer.rb +0 -117
  42. data/lib/qme/importer/measure_properties_generator.rb +0 -39
  43. data/lib/qme/importer/property_matcher.rb +0 -110
  44. data/lib/qme/measure/database_loader.rb +0 -83
  45. data/lib/qme/measure/measure_loader.rb +0 -174
  46. data/lib/qme/measure/properties_builder.rb +0 -184
  47. data/lib/qme/measure/properties_converter.rb +0 -27
  48. data/lib/qme/randomizer/patient_randomization_job.rb +0 -47
  49. data/lib/qme/randomizer/patient_randomizer.rb +0 -250
  50. data/lib/qme/randomizer/random_patient_creator.rb +0 -47
  51. data/lib/qme_test.rb +0 -13
  52. data/lib/tasks/fixtures.rake +0 -91
  53. data/lib/tasks/measure.rake +0 -110
  54. data/lib/tasks/mongo.rake +0 -68
  55. data/lib/tasks/patient_random.rake +0 -45
  56. data/spec/qme/bundle_spec.rb +0 -37
  57. data/spec/qme/importer/generic_importer_spec.rb +0 -73
  58. data/spec/qme/importer/measure_properties_generator_spec.rb +0 -15
  59. data/spec/qme/importer/property_matcher_spec.rb +0 -174
  60. data/spec/qme/map/map_reduce_builder_spec.rb +0 -38
  61. data/spec/qme/map/measures_spec.rb +0 -38
  62. data/spec/qme/map/patient_mapper_spec.rb +0 -11
  63. data/spec/qme/measure_loader_spec.rb +0 -12
  64. data/spec/qme/properties_builder_spec.rb +0 -61
  65. data/spec/spec_helper.rb +0 -120
  66. data/spec/validate_measures_spec.rb +0 -21
@@ -1,38 +0,0 @@
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
@@ -1,38 +0,0 @@
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
@@ -1,11 +0,0 @@
1
- describe QME::MapReduce::Executor do
2
-
3
- before do
4
- @loader = QME::Database::Loader.new('test')
5
- end
6
-
7
- it 'should map patients as expected' do
8
- validate_patient_mapping(@loader)
9
- end
10
-
11
- end
@@ -1,12 +0,0 @@
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
@@ -1,61 +0,0 @@
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
data/spec/spec_helper.rb DELETED
@@ -1,120 +0,0 @@
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
@@ -1,21 +0,0 @@
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