quality-measure-engine 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/Gemfile +9 -9
  2. data/README.md +39 -2
  3. data/Rakefile +25 -44
  4. data/js/map-reduce-utils.js +174 -0
  5. data/js/underscore-min.js +24 -0
  6. data/lib/qme/importer/code_system_helper.rb +26 -0
  7. data/lib/qme/importer/entry.rb +89 -0
  8. data/lib/qme/importer/generic_importer.rb +71 -0
  9. data/lib/qme/importer/hl7_helper.rb +27 -0
  10. data/lib/qme/importer/patient_importer.rb +150 -0
  11. data/lib/qme/importer/property_matcher.rb +103 -0
  12. data/lib/qme/importer/section_importer.rb +82 -0
  13. data/lib/qme/map/map_reduce_builder.rb +77 -147
  14. data/lib/qme/map/map_reduce_executor.rb +97 -13
  15. data/lib/qme/measure/database_loader.rb +90 -0
  16. data/lib/qme/measure/measure_loader.rb +141 -0
  17. data/lib/qme/mongo_helpers.rb +15 -0
  18. data/lib/qme/randomizer/patient_randomizer.rb +95 -0
  19. data/lib/qme_test.rb +13 -0
  20. data/lib/quality-measure-engine.rb +20 -4
  21. data/lib/tasks/measure.rake +76 -0
  22. data/lib/tasks/mongo.rake +74 -0
  23. data/lib/tasks/patient_random.rake +46 -0
  24. metadata +110 -156
  25. data/.gitignore +0 -6
  26. data/Gemfile.lock +0 -44
  27. data/fixtures/complex_measure.json +0 -36
  28. data/fixtures/result_example.json +0 -6
  29. data/lib/patches/v8.rb +0 -20
  30. data/lib/qme/query/json_document_builder.rb +0 -130
  31. data/lib/qme/query/json_query_executor.rb +0 -44
  32. data/measures/0032/0032_NQF_Cervical_Cancer_Screening.json +0 -171
  33. data/measures/0032/patients/denominator1.json +0 -10
  34. data/measures/0032/patients/denominator2.json +0 -10
  35. data/measures/0032/patients/numerator1.json +0 -11
  36. data/measures/0032/patients/population1.json +0 -9
  37. data/measures/0032/patients/population2.json +0 -11
  38. data/measures/0032/result/result.json +0 -6
  39. data/measures/0043/0043_NQF_PneumoniaVaccinationStatusForOlderAdults.json +0 -71
  40. data/measures/0043/patients/denominator.json +0 -11
  41. data/measures/0043/patients/numerator.json +0 -11
  42. data/measures/0043/patients/population.json +0 -10
  43. data/measures/0043/result/result.json +0 -6
  44. data/quality-measure-engine.gemspec +0 -97
  45. data/schema/result.json +0 -28
  46. data/schema/schema.json +0 -143
  47. data/spec/qme/map/map_reduce_builder_spec.rb +0 -64
  48. data/spec/qme/measures_spec.rb +0 -50
  49. data/spec/qme/query/json_document_builder_spec.rb +0 -56
  50. data/spec/schema_spec.rb +0 -21
  51. data/spec/spec_helper.rb +0 -7
  52. data/spec/validate_measures_spec.rb +0 -21
data/.gitignore DELETED
@@ -1,6 +0,0 @@
1
- .bundle
2
- *.DS_Store
3
- vendor
4
- nbproject
5
- pkg
6
- .redcar
data/Gemfile.lock DELETED
@@ -1,44 +0,0 @@
1
- GEM
2
- remote: http://rubygems.org/
3
- specs:
4
- awesome_print (0.2.1)
5
- bson (1.1)
6
- bson_ext (1.1.1)
7
- diff-lcs (1.1.2)
8
- gemcutter (0.6.1)
9
- git (1.2.5)
10
- jeweler (1.4.0)
11
- gemcutter (>= 0.1.0)
12
- git (>= 1.2.5)
13
- rubyforge (>= 2.0.0)
14
- json_pure (1.4.6)
15
- jsonschema (2.0.0)
16
- mongo (1.1)
17
- bson (>= 1.0.5)
18
- rake (0.8.7)
19
- rspec (2.0.0)
20
- rspec-core (= 2.0.0)
21
- rspec-expectations (= 2.0.0)
22
- rspec-mocks (= 2.0.0)
23
- rspec-core (2.0.0)
24
- rspec-expectations (2.0.0)
25
- diff-lcs (>= 1.1.2)
26
- rspec-mocks (2.0.0)
27
- rspec-core (= 2.0.0)
28
- rspec-expectations (= 2.0.0)
29
- rubyforge (2.0.4)
30
- json_pure (>= 1.1.7)
31
- therubyracer (0.7.5)
32
-
33
- PLATFORMS
34
- ruby
35
-
36
- DEPENDENCIES
37
- awesome_print
38
- bson_ext
39
- jeweler
40
- jsonschema
41
- mongo
42
- rake
43
- rspec
44
- therubyracer
@@ -1,36 +0,0 @@
1
- {
2
- "id": "0043",
3
- "name": "Pneumonia Vaccination Status for Older Adults",
4
- "steward": "NCQA",
5
- "population": {
6
- "and": [
7
- {
8
- "category": "Patient Characteristic",
9
- "title": "Age > 17 before measure period",
10
- "query": {"age": {"_gt": 17}}
11
- },
12
- {
13
- "category": "Patient Characteristic",
14
- "title": "Age < 75 before measure period",
15
- "query": {"age": {"_lt": 75}}
16
- },
17
- {
18
- "or": [
19
- {
20
- "category": "Patient Characteristic",
21
- "title": "Male",
22
- "query": {"sex": "male"}
23
- },
24
- {
25
- "category": "Patient Characteristic",
26
- "title": "Female",
27
- "query": {"sex": "female"}
28
- }
29
- ]
30
- }
31
- ]
32
- },
33
- "denominator": {},
34
- "numerator": {},
35
- "exception": {}
36
- }
@@ -1,6 +0,0 @@
1
- {
2
- "initialPopulation": 50,
3
- "denominator": 45,
4
- "numerator": [35],
5
- "exclusions": [5]
6
- }
data/lib/patches/v8.rb DELETED
@@ -1,20 +0,0 @@
1
- # Patching V8::Object because it is the entry point for conversions
2
- # between Ruby and JavaScript types. We are using seconds since the
3
- # epoch to represent dates. On 32 bit architectures, for recent dates
4
- # this will be too large for Fixnum and become a Bignum. Ruby Racer
5
- # worn't properly convert a Bignum to JavaScript, but it will work
6
- # just fine for a Float. Because of this, we will convert all Bignums
7
- # passed into a JavaScript context to a Float
8
- module V8
9
- class Object
10
- alias :old_index_setter :'[]='
11
-
12
- def []=(key, value)
13
- if value.kind_of?(Bignum)
14
- old_index_setter(key, value.to_f)
15
- else
16
- old_index_setter(key, value)
17
- end
18
- end
19
- end
20
- end
@@ -1,130 +0,0 @@
1
- module QME
2
- module Query
3
- class JSONDocumentBuilder
4
- attr_accessor :parameters, :calculated_dates
5
-
6
- # Creates the JSONDocumentBuilder. Will calculate dates if parameters
7
- # are passed in.
8
- def initialize(measure_json, parameters={})
9
- @measure_json = measure_json
10
- @parameters = parameters
11
- @measure_id = measure_json['id']
12
-
13
- if ! parameters.empty?
14
- calculate_dates
15
- end
16
- end
17
-
18
- # Calculates all dates necessary to create a query for this measure
19
- # This will be run by the constructor if params were passed in
20
- def calculate_dates
21
- @calculated_dates = {}
22
-
23
- ctx = V8::Context.new
24
- @parameters.each_pair do |key, value|
25
- ctx[key] = value
26
- @calculated_dates[key] = value
27
- end
28
-
29
- ctx['year'] = 365 * 24 * 60 * 60 # TODO: Replace this with a js file that has all constants
30
-
31
- @measure_json["calculated_dates"].each_pair do |key, value|
32
- @calculated_dates[key] = ctx.eval(value)
33
- end
34
- end
35
-
36
- def numerator_query
37
- create_query(@measure_json['numerator']).merge(denominator_query)
38
- end
39
-
40
- def denominator_query
41
- create_query(@measure_json['denominator']).merge(initial_population_query)
42
- end
43
-
44
- def initial_population_query
45
- create_query(@measure_json['population'])
46
- end
47
-
48
- def exclusions_query
49
- create_query(@measure_json['exception'])
50
- end
51
-
52
- # Creates the appropriate JSON document to query MongoDB based on
53
- # a measure definition passed in. This method calls itself recursively
54
- # to walk the tree and get all possible logical operators available
55
- # in a measure definition
56
- def create_query(definition_json, args={})
57
- if definition_json.has_key?('and')
58
- definition_json['and'].each do |operand|
59
- create_query(operand, args)
60
- end
61
- elsif definition_json.has_key?('or')
62
- operands = []
63
- definition_json['or'].each do |operand|
64
- operands << create_query(operand)
65
- end
66
- if args['$or']
67
- args['$ne'] = {'$or' => operands}
68
- else
69
- args['$or'] = operands
70
- end
71
- elsif definition_json.has_key?('query')
72
- process_query(definition_json['query'], args)
73
- end
74
-
75
- args
76
- end
77
-
78
- # Called by create_query to process leaf nodes in a measure
79
- # definition
80
- def process_query(definition_json, args)
81
- if definition_json.size > 1
82
- raise 'A query should have only one property'
83
- end
84
-
85
- query_property = definition_json.keys.first
86
- document_key = transform_query_property(query_property)
87
- document_value = nil
88
- query_value = definition_json[query_property]
89
- if query_value.kind_of?(Hash)
90
- if query_value.size > 1
91
- raise 'A query value should only have one property'
92
- end
93
-
94
- document_value = {query_value.keys.first.gsub('_', '$') =>
95
- substitute_variables(query_value.values.first)}
96
- if args[document_key]
97
- args[document_key].merge!(document_value)
98
- else
99
- args[document_key] = document_value
100
- end
101
- else
102
- document_value = substitute_variables(query_value)
103
- args[document_key] = document_value
104
- end
105
- end
106
-
107
- # Takes a query property name and transforms it into
108
- # a name of a key in a MongoDB document
109
- def transform_query_property(property_name)
110
- #TODO What do we do with special case fields - the stuff we are keeping at the patient level?
111
- if ['birthdate'].include?(property_name)
112
- property_name
113
- else
114
- "measures.#{@measure_id}.#{property_name}"
115
- end
116
- end
117
-
118
- # Finds strings that start with "@" and replaces them
119
- # with the calculated date
120
- def substitute_variables(value)
121
- if value.kind_of?(String) && value[0] == '@'
122
- variable_name = value[1..-1]
123
- @calculated_dates[variable_name]
124
- else
125
- value
126
- end
127
- end
128
- end
129
- end
130
- end
@@ -1,44 +0,0 @@
1
- module QME
2
- module Query
3
- class JsonQueryExecutor
4
- def initialize(db)
5
- @db = db
6
- end
7
-
8
- def measure_def(measure_id)
9
- measures = @db.collection('measures')
10
- measures.find({'id'=> "#{measure_id}"}).to_a[0]
11
- end
12
-
13
- def measure_result(measure_id, parameter_values)
14
- jdb = JSONDocumentBuilder.new(measure_def(measure_id),
15
- parameter_values)
16
-
17
- collection = @db.collection('records')
18
- result = {}
19
-
20
- collection.find(jdb.numerator_query) do |cursor|
21
- result[:numerator] = cursor.count
22
- end
23
-
24
- collection.find(jdb.denominator_query) do |cursor|
25
- result[:denominator] = cursor.count
26
- end
27
- collection.find(jdb.initial_population_query) do |cursor|
28
- result[:population] = cursor.count
29
- end
30
-
31
- exclusions_query = jdb.exclusions_query
32
- if exclusions_query.empty?
33
- result[:exceptions] = 0
34
- else
35
- collection.find(exclusions_query) do |cursor|
36
- result[:exceptions] = cursor.count
37
- end
38
- end
39
-
40
- result
41
- end
42
- end
43
- end
44
- end
@@ -1,171 +0,0 @@
1
- {
2
- "id": "0032",
3
- "name": "Cervical Cancer Screening",
4
- "steward": "NCQA",
5
- "parameters": {
6
- "effective_date": {
7
- "name": "Effective end date for measure",
8
- "type": "long"
9
- }
10
- },
11
- "properties": {
12
- "birthdate": {
13
- "name": "Date of birth",
14
- "type": "long",
15
- "codes": [
16
- {
17
- "set": "HL7",
18
- "version": "3.0",
19
- "values": ["00110"]
20
- }
21
- ]
22
- },
23
- "encounter_outpatient": {
24
- "name": "Date of outpatient encounter",
25
- "type": "long",
26
- "codes": [
27
- {
28
- "set": "CPT",
29
- "version": "06/2009",
30
- "values": ["99201", "99202", "99203", "99204", "99205", "99211", "99212", "99213", "99214", "99215", "99217", "99218", "99219", "99220", "99241", "99242", "99243", "99244", "99245", "99341", "99342", "99343", "99344", "99345", "99347-99350", "99384", "99385", "99386", "99387", "99394", "99395", "99396", "99397", "99401", "99402", "99403", "99404", "99411", "99412", "99420", "99429", "99455", "99456"]
31
- },
32
- {
33
- "set": "ICD-9-CM",
34
- "version": "06/2009",
35
- "values": ["V70.0", "V70.3", "V70.5", "V70.6", "V70.8", "V70.9"]
36
- }
37
- ]
38
- },
39
- "encounter_obgyn": {
40
- "name": "Date of ObGyn encounter",
41
- "type": "long",
42
- "codes": [
43
- {
44
- "set": "ICD-9-CM",
45
- "version": "06/2009",
46
- "values": ["V24", "V25", "V26", "V27", "V28", "V45.5", "V61.5", "V61.6", "V61.7", "V69.2", "V72.3", "V72.4"]
47
- }
48
- ]
49
- },
50
- "hysterectomy": {
51
- "name": "Hysterectomy performed",
52
- "type": "long",
53
- "codes": [
54
- {
55
- "set": "CPT",
56
- "version": "06/2009",
57
- "values": ["51925", "56308", "58150", "58152", "58200", "58210", "58240", "58260", "58262", "58263", "58267", "58270", "58275", "58280", "58285", "58290", "58291", "58292", "58293", "58294", "58550", "58552", "58553", "58554", "58570", "58571", "58572", "58573", "58951", "58953", "58954", "58956", "59135"]
58
- },
59
- {
60
- "set": "ICD-9-CM",
61
- "version": "06/2009",
62
- "values": ["618.5", "68.4", "68.41", "68.49", "68.5", "68.51", "68.59", "68.6", "68.61", "68.69", "68.7", "68.71", "68.79", "68.8", "V67.01", "V76.47", "V88.01", "V88.03"]
63
- },
64
- {
65
- "set": "ICD-10-CM",
66
- "version": "06/2009",
67
- "values": ["N81.81", "Z12.72", "Z90.71", "Z90.710", "Z90.7112"]
68
- },
69
- {
70
- "set": "SNOMED-CT",
71
- "version": "07/2009",
72
- "values": ["116140006", "116142003", "116143008", "116144002", "236886002", "236888001", "236891001", "27950001", "307771009", "31545000", "35955002", "361222003", "361223008", "414575003", "59750000", "79095000", "86477000", "88144003"]
73
- }
74
- ]
75
- },
76
- "pap_test": {
77
- "name": "Pap test",
78
- "type": "long",
79
- "codes": [
80
- {
81
- "set": "CPT",
82
- "version": "06/2009",
83
- "values": ["88141", "88142", "88143", "88147", "88148", "88150", "88152", "88153", "88154", "88155", "88164", "88165", "88166", "88167", "88174", "88175"]
84
- },
85
- {
86
- "set": "HCPCS",
87
- "version": "06/2009",
88
- "values": ["G0123", "G0124", "G0141", "G0143", "G0144", "G0145", "G0147", "G0148", "P3000", "P3001", "Q0091"]
89
- },
90
- {
91
- "set": "ICD-10-CM",
92
- "version": "06/2009",
93
- "values": ["Z12.4", "Z12.72"]
94
- },
95
- {
96
- "set": "ICD-9-CM",
97
- "version": "06/2009",
98
- "values": ["91.46", "V72.32"]
99
- },
100
- {
101
- "set": "LOINC",
102
- "version": "06/2009",
103
- "values": ["10524-7", "18500-9", "19762-4", "19764-0", "19765-7", "19766-5", "19774-9", "33717-0", "47527-7", "47528-5"]
104
- },
105
- {
106
- "set": "SNOMED-CT",
107
- "version": "07/2009",
108
- "values": ["439958008", "440615002", "440623000"]
109
- }
110
- ]
111
- }
112
- },
113
- "calculated_dates": {
114
- "earliest_birthdate": "effective_date - 64*year",
115
- "latest_birthdate": "effective_date - 23*year",
116
- "earliest_encounter": "effective_date - 2*year",
117
- "earliest_pap": "effective_date - 3*year"
118
- },
119
- "population": {
120
- "and": [
121
- {
122
- "category": "Patient Characteristic",
123
- "title": "Age >= 23 before measure period",
124
- "query": {"birthdate": {"_lte": "@latest_birthdate"}}
125
- },
126
- {
127
- "category": "Patient Characteristic",
128
- "title": "Age <= 64 before measure period",
129
- "query": {"birthdate": {"_gte": "@earliest_birthdate"}}
130
- }
131
- ]
132
- },
133
- "denominator": {
134
- "and": [
135
- {
136
- "or": [
137
- {
138
- "category": "Outpatient Encounter",
139
- "title": "Outpatient encounter within last three years",
140
- "query": {"encounter_outpatient": {"_gte": "@earliest_encounter"}}
141
- },
142
- {
143
- "category": "ObGyn Encounter",
144
- "title": "ObGyn encounter within last three years",
145
- "query": {"encounter_obgyn": {"_gte": "@earliest_encounter"}}
146
- }
147
- ]
148
- },
149
- {
150
- "or": [
151
- {
152
- "category": "Procedure not performed",
153
- "title": "Hysterectomy",
154
- "query": {"hysterectomy": null}
155
- },
156
- {
157
- "category": "Procedure performed",
158
- "title": "Hysterectomy after effective date",
159
- "query": {"hysterectomy": {"_gte": "@effective_date"}}
160
- }
161
- ]
162
- }
163
- ]
164
- },
165
- "numerator": {
166
- "category": "Laboratory test result",
167
- "title": "Pap test",
168
- "query": {"pap_test": {"_gte": "@earliest_pap"}}
169
- },
170
- "exception": {}
171
- }