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
@@ -0,0 +1,78 @@
1
+ {
2
+ "_id": "507eb5977042f9362c000001",
3
+ "birthdate": 1059723000,
4
+ "conditions": [
5
+ {
6
+ "codes": {
7
+ "ICD-9-CM": [
8
+ "034.0"
9
+ ],
10
+ "SNOMED-CT": [
11
+ "140004.0"
12
+ ]
13
+ },
14
+ "mood_code": "EVN",
15
+ "_type": "Condition",
16
+ "description": "Diagnosis, Active: pharyngitis (Code List: 2.16.840.1.113883.3.464.0001.369)",
17
+ "start_time": 1295067600,
18
+ "end_time": 1295067600,
19
+ "status_code": {
20
+ "SNOMED-CT": [
21
+ "55561003"
22
+ ],
23
+ "HL7 ActStatus": [
24
+ "active"
25
+ ]
26
+ }
27
+ }
28
+ ],
29
+ "description": "",
30
+ "description_category": "DENOM",
31
+ "elimination_population": null,
32
+ "elimination_reason": null,
33
+ "encounters": [
34
+ {
35
+ "codes": {
36
+ "CPT": [
37
+ "99201"
38
+ ],
39
+ "ICD-9-CM": [
40
+ "V70.0"
41
+ ]
42
+ },
43
+ "mood_code": "EVN",
44
+ "_type": "Encounter",
45
+ "description": "Encounter: Encounter ambulatory including pediatrics (Code List: 2.16.840.1.113883.3.464.0001.231)",
46
+ "start_time": 1295067600,
47
+ "end_time": 1295067600
48
+ }
49
+ ],
50
+ "expired": false,
51
+ "first": "Billy",
52
+ "gender": "M",
53
+ "last": "Jones",
54
+ "measure_period_end": 1325394000000,
55
+ "measure_period_start": 1293858000000,
56
+ "medications": [
57
+ {
58
+ "codes": {
59
+ "RxNorm": [
60
+ "197450"
61
+ ]
62
+ },
63
+ "mood_code": "EVN",
64
+ "_type": "Medication",
65
+ "description": "Medication, Active: pharyngitis antibiotics (Code List: 2.16.840.1.113883.3.464.0001.373)",
66
+ "start_time": 1295154000,
67
+ "end_time": 1295154000,
68
+ "status_code": {
69
+ "SNOMED-CT": [
70
+ "55561003"
71
+ ],
72
+ "HL7 ActStatus": [
73
+ "active"
74
+ ]
75
+ }
76
+ }
77
+ ]
78
+ }
@@ -0,0 +1,120 @@
1
+ {
2
+ "_id": "507ec2077042f9362c00001b",
3
+ "birthdate": 946715400,
4
+ "medical_record_number": "12345",
5
+ "conditions": [
6
+ {
7
+ "codes": {
8
+ "ICD-9-CM": [
9
+ "034.0"
10
+ ],
11
+ "SNOMED-CT": [
12
+ "140004.0"
13
+ ]
14
+ },
15
+ "mood_code": "EVN",
16
+ "_id": {
17
+ "$oid": "507ecc2b7042f9362c000061"
18
+ },
19
+ "_type": "Condition",
20
+ "description": "Diagnosis, Active: pharyngitis (Code List: 2.16.840.1.113883.3.464.0001.369)",
21
+ "start_time": 1293253200,
22
+ "end_time": 1293253200,
23
+ "status_code": {
24
+ "SNOMED-CT": [
25
+ "55561003"
26
+ ],
27
+ "HL7 ActStatus": [
28
+ "active"
29
+ ]
30
+ }
31
+ }
32
+ ],
33
+ "description": "",
34
+ "description_category": "NUMER",
35
+ "elimination_population": null,
36
+ "elimination_reason": null,
37
+ "encounters": [
38
+ {
39
+ "codes": {
40
+ "CPT": [
41
+ "99201"
42
+ ],
43
+ "ICD-9-CM": [
44
+ "V70.0"
45
+ ]
46
+ },
47
+ "mood_code": "EVN",
48
+ "_type": "Encounter",
49
+ "description": "Encounter: Encounter ambulatory including pediatrics (Code List: 2.16.840.1.113883.3.464.0001.231)",
50
+ "start_time": 1293253200,
51
+ "end_time": 1293253200
52
+ }
53
+ ],
54
+ "expired": false,
55
+ "first": "Jane",
56
+ "gender": "F",
57
+ "last": "Jones",
58
+ "measure_period_end": 1325307600000,
59
+ "measure_period_start": 1293858000000,
60
+ "medications": [
61
+ {
62
+ "codes": {
63
+ "RxNorm": [
64
+ "197450"
65
+ ]
66
+ },
67
+ "mood_code": "EVN",
68
+ "_type": "Medication",
69
+ "description": "Medication, Active: pharyngitis antibiotics (Code List: 2.16.840.1.113883.3.464.0001.373)",
70
+ "start_time": 1293339600,
71
+ "end_time": 1293339600,
72
+ "status_code": {
73
+ "SNOMED-CT": [
74
+ "55561003"
75
+ ],
76
+ "HL7 ActStatus": [
77
+ "active"
78
+ ]
79
+ }
80
+ }
81
+ ],
82
+ "vital_signs": [
83
+ {
84
+ "codes": {
85
+ "CPT": [
86
+ "87070"
87
+ ],
88
+ "LOINC": [
89
+ "11268-0"
90
+ ],
91
+ "SNOMED-CT": [
92
+ "89634005.0"
93
+ ]
94
+ },
95
+ "mood_code": "EVN",
96
+ "_type": "VitalSign",
97
+ "description": "Laboratory Test, Performed: Group A Streptococcus Test (Code List: 2.16.840.1.113883.3.464.0001.250)",
98
+ "start_time": 1293166800,
99
+ "end_time": 1293166800
100
+ },
101
+ {
102
+ "codes": {
103
+ "CPT": [
104
+ "87070"
105
+ ],
106
+ "LOINC": [
107
+ "11268-0"
108
+ ],
109
+ "SNOMED-CT": [
110
+ "89634005.0"
111
+ ]
112
+ },
113
+ "mood_code": "EVN",
114
+ "_type": "VitalSign",
115
+ "description": "Laboratory Test, Performed: Group A Streptococcus Test (Code List: 2.16.840.1.113883.3.464.0001.250)",
116
+ "start_time": 1293339600,
117
+ "end_time": 1293339600
118
+ }
119
+ ]
120
+ }
@@ -0,0 +1,78 @@
1
+ {
2
+ "_id": "507ec1407042f9362c000017",
3
+ "birthdate": 946715400,
4
+ "conditions": [
5
+ {
6
+ "codes": {
7
+ "ICD-9-CM": [
8
+ "034.0"
9
+ ],
10
+ "SNOMED-CT": [
11
+ "140004.0"
12
+ ]
13
+ },
14
+ "mood_code": "EVN",
15
+ "_type": "Condition",
16
+ "description": "Diagnosis, Active: pharyngitis (Code List: 2.16.840.1.113883.3.464.0001.369)",
17
+ "start_time": 1293858000,
18
+ "end_time": 1293858000,
19
+ "status_code": {
20
+ "SNOMED-CT": [
21
+ "55561003"
22
+ ],
23
+ "HL7 ActStatus": [
24
+ "active"
25
+ ]
26
+ }
27
+ }
28
+ ],
29
+ "description": "",
30
+ "description_category": "DENOM",
31
+ "elimination_population": null,
32
+ "elimination_reason": null,
33
+ "encounters": [
34
+ {
35
+ "codes": {
36
+ "CPT": [
37
+ "99201"
38
+ ],
39
+ "ICD-9-CM": [
40
+ "V70.0"
41
+ ]
42
+ },
43
+ "mood_code": "EVN",
44
+ "_type": "Encounter",
45
+ "description": "Encounter: Encounter ambulatory including pediatrics (Code List: 2.16.840.1.113883.3.464.0001.231)",
46
+ "start_time": 1293858000,
47
+ "end_time": 1293858000
48
+ }
49
+ ],
50
+ "expired": false,
51
+ "first": "Jill",
52
+ "gender": "F",
53
+ "last": "Jones",
54
+ "measure_period_end": 1325307600000,
55
+ "measure_period_start": 1293858000000,
56
+ "medications": [
57
+ {
58
+ "codes": {
59
+ "RxNorm": [
60
+ "197450"
61
+ ]
62
+ },
63
+ "mood_code": "EVN",
64
+ "_type": "Medication",
65
+ "description": "Medication, Active: pharyngitis antibiotics (Code List: 2.16.840.1.113883.3.464.0001.373)",
66
+ "start_time": 1293944400,
67
+ "end_time": 1293944400,
68
+ "status_code": {
69
+ "SNOMED-CT": [
70
+ "55561003"
71
+ ],
72
+ "HL7 ActStatus": [
73
+ "active"
74
+ ]
75
+ }
76
+ }
77
+ ]
78
+ }
@@ -0,0 +1,18 @@
1
+ require 'simplecov'
2
+ SimpleCov.command_name 'Unit Tests'
3
+ SimpleCov.start do
4
+ add_filter "test/"
5
+ add_group "Map Reduce", "lib/qme/map"
6
+ add_group "Bundles", "lib/qme/bundle"
7
+ end
8
+
9
+ class SimpleCov::Formatter::QualityFormatter
10
+ def format(result)
11
+ SimpleCov::Formatter::HTMLFormatter.new.format(result)
12
+ File.open("coverage/covered_percent", "w") do |f|
13
+ f.puts result.source_files.covered_percent.to_f
14
+ end
15
+ end
16
+ end
17
+
18
+ SimpleCov.formatter = SimpleCov::Formatter::QualityFormatter
@@ -0,0 +1,26 @@
1
+ require 'simplecov_setup'
2
+ require 'minitest/autorun'
3
+ require 'quality-measure-engine'
4
+
5
+ db_host = ENV['TEST_DB_HOST'] || 'localhost'
6
+
7
+ Mongoid.configure do |config|
8
+ config.sessions = { default: { hosts: [ "#{db_host}:27017" ], database: 'test' }}
9
+ end
10
+
11
+ class MiniTest::Unit::TestCase
12
+ # Add more helper methods to be used by all tests here...
13
+
14
+ def collection_fixtures(db, collection, *id_attributes)
15
+ db[collection].drop
16
+ Dir.glob(File.join(File.dirname(__FILE__), 'fixtures', collection, '*.json')).each do |json_fixture_file|
17
+ #puts "Loading #{json_fixture_file}"
18
+ fixture_json = JSON.parse(File.read(json_fixture_file))
19
+ id_attributes.each do |attr|
20
+ fixture_json[attr] = Moped::BSON::ObjectId.from_string(fixture_json[attr])
21
+ end
22
+
23
+ db[collection].insert(fixture_json)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+
3
+ class MapReduceBuilderTest < MiniTest::Unit::TestCase
4
+ include QME::DatabaseAccess
5
+
6
+ def setup
7
+ raw_measure_json = File.read(File.join('test', 'fixtures', 'measures', 'measure_metadata.json'))
8
+ @measure_json = JSON.parse(raw_measure_json)
9
+ end
10
+
11
+ def test_extracting_measure_metadata
12
+ measure = QME::MapReduce::Builder.new(get_db(), @measure_json, 'effective_date' => Time.gm(2010, 9, 19).to_i)
13
+ assert_equal '0043', measure.id
14
+ end
15
+
16
+ def test_extracting_parameters
17
+ time = Time.gm(2010, 9, 19).to_i
18
+ measure = QME::MapReduce::Builder.new(get_db(), @measure_json, 'effective_date'=>time)
19
+ assert_equal 1, measure.params.size
20
+ assert measure.params.keys.include?('effective_date')
21
+ assert_equal time, measure.params['effective_date']
22
+ end
23
+
24
+ def test_raise_error_when_no_params_provided
25
+ rte = assert_raises(RuntimeError) do
26
+ QME::MapReduce::Builder.new(get_db(), @measure_json, {})
27
+ end
28
+ assert_equal "No value supplied for measure parameter: effective_date", rte.message
29
+ end
30
+
31
+ def test_context_building
32
+ vars = {'a'=>10, 'b'=>20}
33
+ context = QME::MapReduce::Builder::Context.new(get_db(), vars)
34
+ binding = context.get_binding
35
+ assert_equal 10, eval("a",binding)
36
+ assert_equal 20, eval("b",binding)
37
+ end
38
+ end
@@ -0,0 +1,56 @@
1
+ require 'test_helper'
2
+
3
+ class MapReduceExecutorTest < MiniTest::Unit::TestCase
4
+ include QME::DatabaseAccess
5
+
6
+ def setup
7
+ importer = QME::Bundle::Importer.new
8
+ importer.import(File.new('test/fixtures/bundles/just_measure_0002.zip'), true)
9
+
10
+ collection_fixtures(get_db(), 'records', '_id')
11
+ end
12
+
13
+ def test_map_records_into_measure_groups
14
+ executor = QME::MapReduce::Executor.new("2E679CD2-3FEC-4A75-A75A-61403E5EFEE8", nil,
15
+ 'effective_date' => Time.gm(2011, 1, 15).to_i)
16
+ executor.map_records_into_measure_groups
17
+
18
+ assert_equal 4, get_db['patient_cache'].find().count
19
+ assert_equal 3, get_db['patient_cache'].find('value.population' => 1).count
20
+ assert_equal 1, get_db['patient_cache'].find('value.population' => 0).count
21
+ assert_equal 2, get_db['patient_cache'].find('value.denominator' => 1).count
22
+ assert_equal 1, get_db['patient_cache'].find('value.numerator' => 1).count
23
+ end
24
+
25
+ def test_count_records_in_measure_groups
26
+ executor = QME::MapReduce::Executor.new("2E679CD2-3FEC-4A75-A75A-61403E5EFEE8", nil,
27
+ 'effective_date' => Time.gm(2011, 1, 15).to_i)
28
+ executor.map_records_into_measure_groups
29
+ executor.count_records_in_measure_groups
30
+ assert_equal 1, get_db['query_cache'].find().count
31
+ doc = get_db['query_cache'].find().first
32
+ assert_equal 3, doc['population']
33
+ assert_equal 2, doc['denominator']
34
+ assert_equal 1, doc['numerator']
35
+ end
36
+
37
+ def test_map_record_into_measure_groups
38
+ executor = QME::MapReduce::Executor.new("2E679CD2-3FEC-4A75-A75A-61403E5EFEE8", nil,
39
+ 'effective_date' => Time.gm(2011, 1, 15).to_i)
40
+ executor.map_record_into_measure_groups("12345")
41
+
42
+ assert_equal 1, get_db['patient_cache'].find().count
43
+ assert_equal 1, get_db['patient_cache'].find('value.population' => 1).count
44
+ assert_equal 0, get_db['patient_cache'].find('value.population' => 0).count
45
+ assert_equal 1, get_db['patient_cache'].find('value.denominator' => 1).count
46
+ assert_equal 1, get_db['patient_cache'].find('value.numerator' => 1).count
47
+ end
48
+
49
+ def test_get_patient_result
50
+ executor = QME::MapReduce::Executor.new("2E679CD2-3FEC-4A75-A75A-61403E5EFEE8", nil,
51
+ 'effective_date' => Time.gm(2011, 1, 15).to_i)
52
+ result = executor.get_patient_result("12345")
53
+ assert_equal 0, get_db['patient_cache'].find().count
54
+ assert result['numerator']
55
+ end
56
+ end
@@ -0,0 +1,22 @@
1
+ require 'test_helper'
2
+
3
+ class MapCalculationJobTest < MiniTest::Unit::TestCase
4
+ include QME::DatabaseAccess
5
+
6
+ def setup
7
+ importer = QME::Bundle::Importer.new
8
+ importer.import(File.new('test/fixtures/bundles/just_measure_0002.zip'), true)
9
+
10
+ collection_fixtures(get_db(), 'records', '_id')
11
+
12
+ Delayed::Worker.delay_jobs = false
13
+ end
14
+
15
+ def test_perform
16
+ options = {'measure_id' => "2E679CD2-3FEC-4A75-A75A-61403E5EFEE8",
17
+ 'effective_date' => Time.gm(2011, 1, 15).to_i}
18
+
19
+ job = Delayed::Job.enqueue(QME::MapReduce::MeasureCalculationJob.new(options))
20
+ assert job
21
+ end
22
+ end