quality-measure-engine 1.1.5 → 2.0.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.
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