Skip to content
Browse files

merged down develop

  • Loading branch information...
2 parents 0be12a9 + 204a425 commit 3d6125183920115a8eba91f45bf809ae7bcfae8b @andrequina andrequina committed Dec 16, 2011
View
4 .gitignore
@@ -8,4 +8,6 @@ pkg
doc
.yardoc
coverage*
-tmp*
+tmp*
+dump.rdb
+
View
3 Gemfile
@@ -2,7 +2,8 @@ source "http://rubygems.org"
gemspec :development_group => :test
-gem 'bson_ext', :platforms => :mri
+gem 'mongo', '1.5.1'
+gem 'bson_ext', '1.5.1', :platforms => :mri
gem 'rake'
gem 'pry', :require => true
View
2 VERSION
@@ -1 +1 @@
-1.0.3
+1.0.4
View
13 fixtures/measures/sample_single/patients/manual_exclusion.json
@@ -0,0 +1,13 @@
+{
+ "first": "Frederick", // personal data
+ "last": "Smith",
+ "gender": "M",
+ "patient_id": "1234567890",
+ "test_id": 1,
+ "birthdate": 182908800, // Time.gm(1975, 10, 19).to_i
+ "measures": { // bag of measures, one entry per measure
+ "test1": {
+ "eyes": "blue"
+ }
+ }
+}
View
2 fixtures/measures/sample_single/result.json
@@ -2,5 +2,5 @@
"initialPopulation": 4,
"numerator": 1,
"denominator": 2,
- "exclusions": 1
+ "exclusions": 2
}
View
2 js/map_reduce_utils.js
@@ -124,7 +124,7 @@ function() {
first: record.first, last: record.last, gender: record.gender,
birthdate: record.birthdate, test_id: record.test_id,
provider_performances: record.provider_performances,
- race: record.race, ethnicity: record.ethnicity};
+ race: record.race, ethnicity: record.ethnicity, languages: record.languages};
if (population()) {
value.population = true;
if (denominator()) {
View
7 lib/qme/importer/patient_importer.rb
@@ -64,7 +64,7 @@ def initialize (check_usable = true)
"./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code/cda:originalText/cda:reference[@value]")
@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",
"./cda:value",
- "./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.1 13883.10.20.1.50']/cda:value",
+ "./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.50']/cda:value",
"./cda:text/cda:reference[@value]")
@section_importers[:social_history] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.19']")
@section_importers[:care_goals] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.25']")
@@ -123,6 +123,7 @@ def parse_hash(patient_hash)
patient_record['birthdate'] = patient_hash['birthdate']
patient_record['race'] = patient_hash['race']
patient_record['ethnicity'] = patient_hash['ethnicity']
+ patient_record['languages'] = patient_hash['languages']
patient_record['addresses'] = patient_hash['addresses']
event_hash = {}
patient_hash['events'].each do |key, value|
@@ -215,6 +216,10 @@ def get_demographics(patient, doc)
patient['race'] = race_node['code'] if race_node
ethnicity_node = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:patient/cda:ethnicGroupCode')
patient['ethnicity'] = ethnicity_node['code'] if ethnicity_node
+
+ languages = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:patient').search('languageCommunication').map {|lc| lc.at_xpath('cda:languageCode')['code'] }
+ patient['languages'] = languages unless languages.empty?
+
id_node = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:id')
patient['patient_id'] = id_node['extension']
end
View
10 lib/qme/map/map_reduce_builder.rb
@@ -93,6 +93,16 @@ def finalize_function
end
reduce += "patient.effective_date = #{@params['effective_date']};
+ if (patient.provider_performances) {
+ var tmp = [];
+ for(var i=0; i<patient.provider_performances.length; i++) {
+ var value = patient.provider_performances[i];
+ if (value['start_date'] <= #{@params['effective_date']} && (value['end_date'] >= #{@params['effective_date']} || value['end_date'] == null))
+ tmp.push(value);
+ }
+ if (tmp.length == 0) tmp = null;
+ patient.provider_performances = tmp;
+ }
return patient;}"
reduce
View
79 lib/qme/map/map_reduce_executor.rb
@@ -25,30 +25,42 @@ def initialize(measure_id, sub_id, parameter_values)
# @return [Hash] measure groups (like numerator) as keys, counts as values
def count_records_in_measure_groups
patient_cache = get_db.collection('patient_cache')
- query = {'value.measure_id' => @measure_id, 'value.sub_id' => @sub_id,
- 'value.effective_date' => @parameter_values['effective_date'],
- 'value.test_id' => @parameter_values['test_id']}
+ base_query = {'value.measure_id' => @measure_id, 'value.sub_id' => @sub_id,
+ 'value.effective_date' => @parameter_values['effective_date'],
+ 'value.test_id' => @parameter_values['test_id']}
+
+ base_query.merge!(filter_parameters)
+
+ query = base_query.clone
- query.merge!(filter_parameters)
+ query.merge!({'value.manual_exclusion'=>{'$ne'=>true}})
result = {:measure_id => @measure_id, :sub_id => @sub_id,
:effective_date => @parameter_values['effective_date'],
:test_id => @parameter_values['test_id'], :filters => @parameter_values['filters']}
aggregate = patient_cache.group({cond: query,
- initial: {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0, considered: 0},
- reduce: "function(record,sums) { for (var key in sums) { sums[key] += (record['value'][key] || key == 'considered') ? 1 : 0 } }"}).first
-
- aggregate ||= {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0}
- aggregate.each {|key, value| aggregate[key] = value.to_i}
- result.merge!(aggregate)
-
-# need to time the old way agains the single query to verify that the single query is more performant
-# %w(population denominator numerator antinumerator exclusions).each do |measure_group|
-# patient_cache.find(query.merge("value.#{measure_group}" => true)) do |cursor|
-# result[measure_group] = cursor.count
-# end
-# end
+ initial: {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0, considered: 0},
+ reduce: "function(record,sums) {
+ for (var key in sums) {
+ sums[key] += (record['value'][key] || key == 'considered') ? 1 : 0
+ }
+ }"}).first
+
+ aggregate ||= {"population"=>0, "denominator"=>0, "numerator"=>0, "antinumerator"=>0, "exclusions"=>0}
+ aggregate.each {|key, value| aggregate[key] = value.to_i}
+ aggregate['exclusions'] += patient_cache.find(base_query.merge({'value.manual_exclusion'=>true})).count
+ result.merge!(aggregate)
+
+ # # need to time the old way agains the single query to verify that the single query is more performant
+ # aggregate = {population: 0, denominator: 0, numerator: 0, antinumerator: 0, exclusions: 0}
+ # %w(population denominator numerator antinumerator exclusions).each do |measure_group|
+ # patient_cache.find(query.merge("value.#{measure_group}" => true)) do |cursor|
+ # aggregate[measure_group] = cursor.count
+ # end
+ # end
+ # aggregate[:considered] = patient_cache.find(query).count
+ # result.merge!(aggregate)
result.merge!(execution_time: (Time.now.to_i - @parameter_values['start_time'].to_i)) if @parameter_values['start_time']
@@ -67,6 +79,7 @@ def map_records_into_measure_groups
:out => {:reduce => 'patient_cache'},
:finalize => measure.finalize_function,
:query => {:test_id => @parameter_values['test_id']})
+ apply_manual_exclusions
end
# This method runs the MapReduce job for the measure and a specific patient.
@@ -80,15 +93,29 @@ def map_record_into_measure_groups(patient_id)
:out => {:reduce => 'patient_cache'},
:finalize => measure.finalize_function,
:query => {:patient_id => patient_id, :test_id => @parameter_values['test_id']})
+ apply_manual_exclusions
+ end
+
+ # This records collects the set of manual exclusions from the manual_exclusions collections
+ # and sets a flag in each cached patient result for patients that have been excluded from the
+ # current measure
+ def apply_manual_exclusions
+ exclusions = get_db.collection('manual_exclusions').find({'measure_id'=>@measure_id, 'sub_id'=>@sub_id}).to_a.map do |exclusion|
+ exclusion['medical_record_id']
+ end
+ get_db.collection('patient_cache').update(
+ {'value.measure_id'=>@measure_id, 'value.sub_id'=>@sub_id, 'value.medical_record_id'=>{'$in'=>exclusions} },
+ {'$set'=>{'value.manual_exclusion'=>true}}, :multi=>true)
end
def filter_parameters
results = {}
conditions = []
if(filters = @parameter_values['filters'])
if (filters['providers'] && filters['providers'].size > 0)
- providers = filters['providers'].map {|provider_id| BSON::ObjectId(provider_id) if provider_id }
- conditions << provider_queries(providers, @parameter_values['effective_date'])
+ providers = filters['providers'].map {|provider_id| BSON::ObjectId(provider_id) if (provider_id and provider_id != 'null') }
+ # provider_performances have already been filtered by start and end date in map_reduce_builder as part of the finalize
+ conditions << {'value.provider_performances.provider_id' => {'$in' => providers}}
end
if (filters['races'] && filters['races'].size > 0)
conditions << {'value.race.code' => {'$in' => filters['races']}}
@@ -99,16 +126,18 @@ def filter_parameters
if (filters['genders'] && filters['genders'].size > 0)
conditions << {'value.gender' => {'$in' => filters['genders']}}
end
+ if (filters['languages'] && filters['languages'].size > 0)
+ languages = filters['languages'].clone
+ has_unspecified = languages.delete('null')
+ or_clauses = []
+ or_clauses << {'value.languages'=>{'$regex'=>Regexp.new("(#{languages.join("|")})-..")}} if languages.length > 0
+ or_clauses << {'value.languages'=>nil} if (has_unspecified)
+ conditions << {'$or'=>or_clauses}
+ end
end
results.merge!({'$and'=>conditions}) if conditions.length > 0
results
end
- def provider_queries(provider_ids, effective_date)
- {'$or' => [provider_query(provider_ids, effective_date,effective_date), provider_query(provider_ids, nil,effective_date), provider_query(provider_ids, effective_date,nil)]}
- end
- def provider_query(provider_ids, start_before, end_after)
- {'value.provider_performances' => {'$elemMatch' => {'provider_id' => {'$in' => provider_ids}, 'start_date'=> {'$lt'=>start_before}, 'end_date'=> {'$gt'=>end_after} } }}
- end
end
end
end
View
78 lib/qme/randomizer/patient_randomizer.rb
@@ -80,6 +80,84 @@ def race_and_ethnicity
{race: '2131-1', ethnicity: '2186-5'}
end
end
+
+ # Picks spoken language based on 2010 census estamates
+ # 80.3% english
+ # 12.3% spanish
+ # 00.9% chinese
+ # 00.7% french
+ # 00.4% german
+ # 00.4% korean
+ # 00.4% vietnamese
+ # 00.3% italian
+ # 00.3% portuguese
+ # 00.3% russian
+ # 00.2% japanese
+ # 00.2% polish
+ # 00.1% greek
+ # 00.1% persian
+ # 00.1% us sign
+ # 03.0% other
+ #
+ def language
+ language_percent = rand(999)
+ case language_percent
+ when 0..802
+ # english
+ 'en-US'
+ when 802..925
+ # spanish
+ 'es-US'
+ when 926..932
+ # french
+ 'fr-US'
+ when 933..935
+ # italian
+ 'it-US'
+ when 936..938
+ # portuguese
+ 'pt-US'
+ when 939..942
+ # german
+ 'de-US'
+ when 943..943
+ # greek
+ 'el-US'
+ when 944..946
+ # russian
+ 'ru-US'
+ when 947..948
+ # polish
+ 'pl-US'
+ when 949..949
+ # persian
+ 'fa-US'
+ when 950..958
+ # chinese
+ 'zh-US'
+ when 959..960
+ # japanese
+ 'ja-US'
+ when 961..964
+ # korean
+ 'ko-US'
+ when 965..968
+ # vietnamese
+ 'vi-US'
+ when 969..969
+ # us sign
+ 'sgn-US'
+ when 970..999
+ # other
+ 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",
+ "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",
+ "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",
+ "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",
+ "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",
+ "uz","ve","vo","wa","wo","xh","yi","yo","za","zu"].sample
+ "#{other}-US"
+ end
+ end
# Pick a forename at random appropriate for the supplied gender
# @param [String] gender the gender 'M' or 'F'
View
2 quality-measure-engine.gemspec
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
s.email = "talk@projectpophealth.org"
s.homepage = "http://github.com/pophealth/quality-measure-engine"
s.authors = ["Marc Hadley", "Andy Gregorowicz", "Rob Dingwell"]
- s.version = '1.0.3'
+ s.version = '1.0.4'
s.add_dependency 'mongo', '~> 1.3'
s.add_dependency 'rubyzip', '~> 0.9.4'
View
3 spec/spec_helper.rb
@@ -16,13 +16,16 @@ def reload_bundle(bundle_dir='.', measure_dir=ENV['MEASURE_DIR'] || 'measures')
loader = QME::Database::Loader.new
loader.drop_collection('bundles')
loader.drop_collection('measures')
+ loader.drop_collection('manual_exclusions')
loader.save_bundle(bundle_dir, measure_dir)
loader
end
def validate_measures(measure_dirs, loader)
reload_bundle
+
+ loader.get_db.collection('manual_exclusions').save({'measure_id'=>'test1', 'medical_record_id'=>'1234567890'})
measure_dirs.each do |dir|
# check for sample data

0 comments on commit 3d61251

Please sign in to comment.
Something went wrong with that request. Please try again.