quality-measure-engine 1.0.3 → 1.0.4

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.
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