quality-measure-engine 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -2,7 +2,8 @@ source "http://rubygems.org"
2
2
 
3
3
  gemspec :development_group => :test
4
4
 
5
- gem 'bson_ext', :platforms => :mri
5
+ gem 'mongo', '1.5.1'
6
+ gem 'bson_ext', '1.5.1', :platforms => :mri
6
7
  gem 'rake'
7
8
  gem 'pry', :require => true
8
9
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.3
1
+ 1.0.4
@@ -124,7 +124,7 @@ function() {
124
124
  first: record.first, last: record.last, gender: record.gender,
125
125
  birthdate: record.birthdate, test_id: record.test_id,
126
126
  provider_performances: record.provider_performances,
127
- race: record.race, ethnicity: record.ethnicity};
127
+ race: record.race, ethnicity: record.ethnicity, languages: record.languages};
128
128
  if (population()) {
129
129
  value.population = true;
130
130
  if (denominator()) {
@@ -64,7 +64,7 @@ module QME
64
64
  "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code/cda:originalText/cda:reference[@value]")
65
65
  @section_importers[:conditions] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.103']/cda:entry/cda:act/cda:entryRelationship/cda:observation",
66
66
  "./cda:value",
67
- "./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.1 13883.10.20.1.50']/cda:value",
67
+ "./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.50']/cda:value",
68
68
  "./cda:text/cda:reference[@value]")
69
69
  @section_importers[:social_history] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.19']")
70
70
  @section_importers[:care_goals] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.25']")
@@ -123,6 +123,7 @@ module QME
123
123
  patient_record['birthdate'] = patient_hash['birthdate']
124
124
  patient_record['race'] = patient_hash['race']
125
125
  patient_record['ethnicity'] = patient_hash['ethnicity']
126
+ patient_record['languages'] = patient_hash['languages']
126
127
  patient_record['addresses'] = patient_hash['addresses']
127
128
  event_hash = {}
128
129
  patient_hash['events'].each do |key, value|
@@ -215,6 +216,10 @@ module QME
215
216
  patient['race'] = race_node['code'] if race_node
216
217
  ethnicity_node = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:patient/cda:ethnicGroupCode')
217
218
  patient['ethnicity'] = ethnicity_node['code'] if ethnicity_node
219
+
220
+ languages = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:patient').search('languageCommunication').map {|lc| lc.at_xpath('cda:languageCode')['code'] }
221
+ patient['languages'] = languages unless languages.empty?
222
+
218
223
  id_node = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:id')
219
224
  patient['patient_id'] = id_node['extension']
220
225
  end
@@ -93,6 +93,16 @@ module QME
93
93
  end
94
94
 
95
95
  reduce += "patient.effective_date = #{@params['effective_date']};
96
+ if (patient.provider_performances) {
97
+ var tmp = [];
98
+ for(var i=0; i<patient.provider_performances.length; i++) {
99
+ var value = patient.provider_performances[i];
100
+ if (value['start_date'] <= #{@params['effective_date']} && (value['end_date'] >= #{@params['effective_date']} || value['end_date'] == null))
101
+ tmp.push(value);
102
+ }
103
+ if (tmp.length == 0) tmp = null;
104
+ patient.provider_performances = tmp;
105
+ }
96
106
  return patient;}"
97
107
 
98
108
  reduce
@@ -25,30 +25,42 @@ module QME
25
25
  # @return [Hash] measure groups (like numerator) as keys, counts as values
26
26
  def count_records_in_measure_groups
27
27
  patient_cache = get_db.collection('patient_cache')
28
- query = {'value.measure_id' => @measure_id, 'value.sub_id' => @sub_id,
29
- 'value.effective_date' => @parameter_values['effective_date'],
30
- 'value.test_id' => @parameter_values['test_id']}
28
+ base_query = {'value.measure_id' => @measure_id, 'value.sub_id' => @sub_id,
29
+ 'value.effective_date' => @parameter_values['effective_date'],
30
+ 'value.test_id' => @parameter_values['test_id']}
31
+
32
+ base_query.merge!(filter_parameters)
33
+
34
+ query = base_query.clone
31
35
 
32
- query.merge!(filter_parameters)
36
+ query.merge!({'value.manual_exclusion'=>{'$ne'=>true}})
33
37
 
34
38
  result = {:measure_id => @measure_id, :sub_id => @sub_id,
35
39
  :effective_date => @parameter_values['effective_date'],
36
40
  :test_id => @parameter_values['test_id'], :filters => @parameter_values['filters']}
37
41
 
38
42
  aggregate = patient_cache.group({cond: query,
39
- initial: {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0, considered: 0},
40
- reduce: "function(record,sums) { for (var key in sums) { sums[key] += (record['value'][key] || key == 'considered') ? 1 : 0 } }"}).first
41
-
42
- aggregate ||= {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0}
43
- aggregate.each {|key, value| aggregate[key] = value.to_i}
44
- result.merge!(aggregate)
45
-
46
- # need to time the old way agains the single query to verify that the single query is more performant
47
- # %w(population denominator numerator antinumerator exclusions).each do |measure_group|
48
- # patient_cache.find(query.merge("value.#{measure_group}" => true)) do |cursor|
49
- # result[measure_group] = cursor.count
50
- # end
51
- # end
43
+ initial: {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0, considered: 0},
44
+ reduce: "function(record,sums) {
45
+ for (var key in sums) {
46
+ sums[key] += (record['value'][key] || key == 'considered') ? 1 : 0
47
+ }
48
+ }"}).first
49
+
50
+ aggregate ||= {"population"=>0, "denominator"=>0, "numerator"=>0, "antinumerator"=>0, "exclusions"=>0}
51
+ aggregate.each {|key, value| aggregate[key] = value.to_i}
52
+ aggregate['exclusions'] += patient_cache.find(base_query.merge({'value.manual_exclusion'=>true})).count
53
+ result.merge!(aggregate)
54
+
55
+ # # need to time the old way agains the single query to verify that the single query is more performant
56
+ # aggregate = {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0}
57
+ # %w(population denominator numerator antinumerator exclusions).each do |measure_group|
58
+ # patient_cache.find(query.merge("value.#{measure_group}" => true)) do |cursor|
59
+ # aggregate[measure_group] = cursor.count
60
+ # end
61
+ # end
62
+ # aggregate[:considered] = patient_cache.find(query).count
63
+ # result.merge!(aggregate)
52
64
 
53
65
  result.merge!(execution_time: (Time.now.to_i - @parameter_values['start_time'].to_i)) if @parameter_values['start_time']
54
66
 
@@ -67,6 +79,7 @@ module QME
67
79
  :out => {:reduce => 'patient_cache'},
68
80
  :finalize => measure.finalize_function,
69
81
  :query => {:test_id => @parameter_values['test_id']})
82
+ apply_manual_exclusions
70
83
  end
71
84
 
72
85
  # This method runs the MapReduce job for the measure and a specific patient.
@@ -80,6 +93,19 @@ module QME
80
93
  :out => {:reduce => 'patient_cache'},
81
94
  :finalize => measure.finalize_function,
82
95
  :query => {:patient_id => patient_id, :test_id => @parameter_values['test_id']})
96
+ apply_manual_exclusions
97
+ end
98
+
99
+ # This records collects the set of manual exclusions from the manual_exclusions collections
100
+ # and sets a flag in each cached patient result for patients that have been excluded from the
101
+ # current measure
102
+ def apply_manual_exclusions
103
+ exclusions = get_db.collection('manual_exclusions').find({'measure_id'=>@measure_id, 'sub_id'=>@sub_id}).to_a.map do |exclusion|
104
+ exclusion['medical_record_id']
105
+ end
106
+ get_db.collection('patient_cache').update(
107
+ {'value.measure_id'=>@measure_id, 'value.sub_id'=>@sub_id, 'value.medical_record_id'=>{'$in'=>exclusions} },
108
+ {'$set'=>{'value.manual_exclusion'=>true}}, :multi=>true)
83
109
  end
84
110
 
85
111
  def filter_parameters
@@ -87,8 +113,9 @@ module QME
87
113
  conditions = []
88
114
  if(filters = @parameter_values['filters'])
89
115
  if (filters['providers'] && filters['providers'].size > 0)
90
- providers = filters['providers'].map {|provider_id| BSON::ObjectId(provider_id) if provider_id }
91
- conditions << provider_queries(providers, @parameter_values['effective_date'])
116
+ providers = filters['providers'].map {|provider_id| BSON::ObjectId(provider_id) if (provider_id and provider_id != 'null') }
117
+ # provider_performances have already been filtered by start and end date in map_reduce_builder as part of the finalize
118
+ conditions << {'value.provider_performances.provider_id' => {'$in' => providers}}
92
119
  end
93
120
  if (filters['races'] && filters['races'].size > 0)
94
121
  conditions << {'value.race.code' => {'$in' => filters['races']}}
@@ -99,16 +126,18 @@ module QME
99
126
  if (filters['genders'] && filters['genders'].size > 0)
100
127
  conditions << {'value.gender' => {'$in' => filters['genders']}}
101
128
  end
129
+ if (filters['languages'] && filters['languages'].size > 0)
130
+ languages = filters['languages'].clone
131
+ has_unspecified = languages.delete('null')
132
+ or_clauses = []
133
+ or_clauses << {'value.languages'=>{'$regex'=>Regexp.new("(#{languages.join("|")})-..")}} if languages.length > 0
134
+ or_clauses << {'value.languages'=>nil} if (has_unspecified)
135
+ conditions << {'$or'=>or_clauses}
136
+ end
102
137
  end
103
138
  results.merge!({'$and'=>conditions}) if conditions.length > 0
104
139
  results
105
140
  end
106
- def provider_queries(provider_ids, effective_date)
107
- {'$or' => [provider_query(provider_ids, effective_date,effective_date), provider_query(provider_ids, nil,effective_date), provider_query(provider_ids, effective_date,nil)]}
108
- end
109
- def provider_query(provider_ids, start_before, end_after)
110
- {'value.provider_performances' => {'$elemMatch' => {'provider_id' => {'$in' => provider_ids}, 'start_date'=> {'$lt'=>start_before}, 'end_date'=> {'$gt'=>end_after} } }}
111
- end
112
141
  end
113
142
  end
114
143
  end
@@ -80,6 +80,84 @@ module QME
80
80
  {race: '2131-1', ethnicity: '2186-5'}
81
81
  end
82
82
  end
83
+
84
+ # Picks spoken language based on 2010 census estamates
85
+ # 80.3% english
86
+ # 12.3% spanish
87
+ # 00.9% chinese
88
+ # 00.7% french
89
+ # 00.4% german
90
+ # 00.4% korean
91
+ # 00.4% vietnamese
92
+ # 00.3% italian
93
+ # 00.3% portuguese
94
+ # 00.3% russian
95
+ # 00.2% japanese
96
+ # 00.2% polish
97
+ # 00.1% greek
98
+ # 00.1% persian
99
+ # 00.1% us sign
100
+ # 03.0% other
101
+ #
102
+ def language
103
+ language_percent = rand(999)
104
+ case language_percent
105
+ when 0..802
106
+ # english
107
+ 'en-US'
108
+ when 802..925
109
+ # spanish
110
+ 'es-US'
111
+ when 926..932
112
+ # french
113
+ 'fr-US'
114
+ when 933..935
115
+ # italian
116
+ 'it-US'
117
+ when 936..938
118
+ # portuguese
119
+ 'pt-US'
120
+ when 939..942
121
+ # german
122
+ 'de-US'
123
+ when 943..943
124
+ # greek
125
+ 'el-US'
126
+ when 944..946
127
+ # russian
128
+ 'ru-US'
129
+ when 947..948
130
+ # polish
131
+ 'pl-US'
132
+ when 949..949
133
+ # persian
134
+ 'fa-US'
135
+ when 950..958
136
+ # chinese
137
+ 'zh-US'
138
+ when 959..960
139
+ # japanese
140
+ 'ja-US'
141
+ when 961..964
142
+ # korean
143
+ 'ko-US'
144
+ when 965..968
145
+ # vietnamese
146
+ 'vi-US'
147
+ when 969..969
148
+ # us sign
149
+ 'sgn-US'
150
+ when 970..999
151
+ # other
152
+ other = ["aa","ab","ae","af","ak","am","an","ar","as","av","ay","az","ba","be","bg","bh","bi","bm","bn","bo","br","bs","ca","ce","ch","co","cr","cs","cu","cv","cy","da",
153
+ "dv","dz","ee","eo","et","eu","ff","fi","fj","fo","fy","ga","gd","gl","gn","gu","gv","ha","he","hi","ho","hr","ht","hu","hy","hz","ia","id","ie","ig","ii","ik",
154
+ "io","is","iu","jv","ka","kg","ki","kj","kk","kl","km","kn","kr","ks","ku","kv","kw","ky","la","lb","lg","li","ln","lo","lt","lu","lv","mg","mh","mi","mk","ml",
155
+ "mn","mr","ms","mt","my","na","nb","nd","ne","ng","nl","nn","no","nr","nv","ny","oc","oj","om","or","os","pa","pi","ps","qu","rm","rn","ro","rw","sa","sc","sd",
156
+ "se","sg","si","sk","sl","sm","sn","so","sq","sr","ss","st","su","sv","sw","ta","te","tg","th","ti","tk","tl","tn","to","tr","ts","tt","tw","ty","ug","uk","ur",
157
+ "uz","ve","vo","wa","wo","xh","yi","yo","za","zu"].sample
158
+ "#{other}-US"
159
+ end
160
+ end
83
161
 
84
162
  # Pick a forename at random appropriate for the supplied gender
85
163
  # @param [String] gender the gender 'M' or 'F'
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: quality-measure-engine
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.0.3
5
+ version: 1.0.4
6
6
  platform: ruby
7
7
  authors:
8
8
  - Marc Hadley
@@ -12,7 +12,7 @@ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
14
 
15
- date: 2011-12-08 00:00:00 -05:00
15
+ date: 2011-12-16 00:00:00 -05:00
16
16
  default_executable:
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency