quality-measure-engine 0.2.0 → 0.8.0

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.
@@ -5,65 +5,124 @@ module QME
5
5
  # it can then be passed a C32 document and will return a Hash with all of the information needed to calculate the measure.
6
6
  class GenericImporter
7
7
 
8
- @@warnings = {}
8
+ class << self
9
+ attr_accessor :warnings
10
+ end
11
+
12
+ @warnings = []
9
13
 
10
14
  # Creates a generic importer for any quality measure.
11
15
  #
12
16
  # @param [Hash] definition A measure definition described in JSON
13
17
  def initialize(definition)
14
18
  @definition = definition
19
+ @warnings = []
15
20
  end
16
-
21
+
17
22
  # Parses a HITSP C32 document and returns a Hash of information related to the measure
18
23
  #
19
24
  # @param [Hash] patient_hash representation of a patient
20
25
  # @return [Hash] measure information
21
26
  def parse(patient_hash)
22
27
  measure_info = {}
23
-
24
28
  @definition['measure'].each_pair do |property, description|
25
29
  raise "No standard_category for #{property}" if !description['standard_category']
26
30
  matcher = PropertyMatcher.new(description)
27
- entry_list = symbols_for_category(description['standard_category']).map do |section|
31
+ enrty_filter = filter_for_property(description['standard_category'], description['qds_data_type'])
32
+ entry_list = enrty_filter.call(patient_hash)
33
+ if ! entry_list.empty?
34
+ matched_list = matcher.match(entry_list)
35
+ measure_info[property] = matched_list if matched_list.length > 0
36
+ end
37
+ end
38
+ measure_info
39
+ end
40
+
41
+ private
42
+
43
+ def create_section_filter(*sections)
44
+ Proc.new do |patient_hash|
45
+ sections.map do |section|
28
46
  if patient_hash[section]
29
47
  patient_hash[section]
30
48
  else
31
49
  []
32
50
  end
33
51
  end.flatten
34
- if ! entry_list.empty?
35
- matched_list = matcher.match(entry_list)
36
- measure_info[property]=matched_list if matched_list.length>0
52
+ end
53
+ end
54
+
55
+ def create_section_and_status_filter(status, *sections)
56
+ section_filter = create_section_filter(*sections)
57
+ Proc.new do |patient_hash|
58
+ entry_list = section_filter.call(patient_hash)
59
+ entry_list.select do |entry|
60
+ entry.status.nil? || entry.status == status
37
61
  end
38
62
  end
39
-
40
- measure_info
41
63
  end
42
-
43
- private
44
-
45
- def symbols_for_category(standard_category)
46
- # Currently unsupported categories:
47
- # characteristic, substance_allergy, medication_allergy, negation_rationale,
48
- # diagnostic_study
64
+
65
+ def filter_for_property(standard_category, qds_data_type)
66
+ # Currently unsupported categories: negation_rationale, risk_category_assessment
49
67
  case standard_category
50
- when 'encounter'; [:encounters]
51
- when 'procedure'; [:procedures]
52
- when 'communication'; [:procedures]
53
- when 'laboratory_test'; [:results, :vital_signs]
54
- when 'physical_exam'; [:vital_signs]
55
- when 'medication'; [:medications]
56
- when 'diagnosis_condition_problem'; [:conditions, :social_history]
57
- when 'characteristic'; [:conditions, :social_history]
58
- when 'device'; [:conditions, :procedures, :care_goals, :medical_equipment]
59
- when 'care_goal'; [:care_goals]
60
- when 'diagnostic_study'; [:procedures]
68
+ when 'encounter'
69
+ create_section_filter(:encounters)
70
+ when 'procedure'
71
+ case qds_data_type
72
+ when 'procedure_performed'
73
+ create_section_filter(:procedures)
74
+ when 'procedure_adverse_event', 'procedure_intolerance'
75
+ create_section_filter(:allergies)
76
+ when 'procedure_result'
77
+ create_section_filter(:procedures, :results, :vital_signs)
78
+ end
79
+ when 'risk_category_assessment'
80
+ create_section_filter(:procedures)
81
+ when 'communication'
82
+ create_section_filter(:procedures)
83
+ when 'laboratory_test'
84
+ create_section_filter(:results, :vital_signs)
85
+ when 'physical_exam'
86
+ create_section_filter(:vital_signs)
87
+ when 'medication'
88
+ case qds_data_type
89
+ when 'medication_dispensed', 'medication_order', 'medication_active', 'medication_administered'
90
+ create_section_filter(:medications, :immunizations)
91
+ when 'medication_allergy', 'medication_intolerance', 'medication_adverse_event'
92
+ create_section_filter(:allergies)
93
+ end
94
+ when 'diagnosis_condition_problem'
95
+ case qds_data_type
96
+ when 'diagnosis_active'
97
+ create_section_and_status_filter(:active, :conditions, :social_history)
98
+ when 'diagnosis_inactive'
99
+ create_section_and_status_filter(:inactive, :conditions, :social_history)
100
+ when 'diagnosis_resolved'
101
+ create_section_and_status_filter(:resolved, :conditions, :social_history)
102
+ end
103
+ when 'symptom'
104
+ create_section_filter(:conditions, :social_history)
105
+ when 'individual_characteristic'
106
+ create_section_filter(:conditions, :social_history)
107
+ when 'device'
108
+ case qds_data_type
109
+ when 'device_applied'
110
+ create_section_filter(:conditions, :procedures, :care_goals, :medical_equipment)
111
+ when 'device_allergy'
112
+ create_section_filter(:allergies)
113
+ end
114
+ when 'care_goal'
115
+ create_section_filter(:care_goals)
116
+ when 'diagnostic_study'
117
+ create_section_filter(:procedures)
118
+ when 'substance'
119
+ create_section_filter(:allergies)
61
120
  else
62
- if !@@warnings[standard_category]
121
+ unless self.class.warnings.include?(standard_category)
63
122
  puts "Warning: Unsupported standard_category (#{standard_category})"
64
- @@warnings[standard_category]=true
123
+ self.class.warnings << standard_category
65
124
  end
66
- []
125
+ Proc.new {[]} # A proc that returns an empty array
67
126
  end
68
127
  end
69
128
  end
@@ -1,14 +1,15 @@
1
1
  module QME
2
2
  module Importer
3
-
3
+
4
4
  # This class is the central location for taking a HITSP C32 XML document and converting it
5
5
  # into the processed form we store in MongoDB. The class does this by running each measure
6
6
  # independently on the XML document
7
7
  #
8
8
  # This class is a Singleton. It should be accessed by calling PatientImporter.instance
9
9
  class PatientImporter
10
+
10
11
  include Singleton
11
-
12
+
12
13
  # Creates a new PatientImporter with the following XPath expressions used to find content in
13
14
  # a HITSP C32:
14
15
  #
@@ -18,8 +19,8 @@ module QME
18
19
  # Procedure entries
19
20
  # //cda:procedure[cda:templateId/@root='2.16.840.1.113883.10.20.1.29']
20
21
  #
21
- # Result entries
22
- # //cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.15']
22
+ # Result entries - There seems to be some confusion around the correct templateId, so the code checks for both
23
+ # //cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.15.1'] | //cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.15']
23
24
  #
24
25
  # Vital sign entries
25
26
  # //cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.14']
@@ -33,33 +34,44 @@ module QME
33
34
  # Condition entries
34
35
  # //cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.103']/cda:entry/cda:act/cda:entryRelationship/cda:observation
35
36
  #
37
+ # Codes for conditions are determined by examining the value child element as opposed to the code child element
38
+ #
36
39
  # Social History entries (non-C32 section, specified in the HL7 CCD)
37
40
  # //cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.19']
38
41
  #
39
42
  # Care Goal entries(non-C32 section, specified in the HL7 CCD)
40
43
  # //cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.25']
41
44
  #
42
- # Codes for conditions are determined by examining the value child element as opposed to the code child element
45
+ # Allergy entries
46
+ # //cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.18']
47
+ #
48
+ # Immunization entries
49
+ # //cda:substanceAdministration[cda:templateId/@root='2.16.840.1.113883.10.20.1.24']
50
+ #
51
+ # Codes for immunizations are found in the substanceAdministration with the following relative XPath
52
+ # ./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code
43
53
  def initialize
44
54
  @measure_importers = {}
45
-
46
55
  @section_importers = {}
47
56
  @section_importers[:encounters] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.127']/cda:entry/cda:encounter")
48
57
  @section_importers[:procedures] = SectionImporter.new("//cda:procedure[cda:templateId/@root='2.16.840.1.113883.10.20.1.29']")
49
- @section_importers[:results] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.15']")
58
+ @section_importers[:results] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.15.1'] | //cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.15']")
50
59
  @section_importers[:vital_signs] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.14']")
51
60
  @section_importers[:medications] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.112']/cda:entry/cda:substanceAdministration",
52
61
  "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code")
53
-
54
62
  @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",
55
- "./cda:value")
56
-
63
+ "./cda:value",
64
+ "./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.1 13883.10.20.1.50']/cda:value")
57
65
  @section_importers[:social_history] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.19']")
58
66
  @section_importers[:care_goals] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.25']")
59
67
  @section_importers[:medical_equipment] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.128']/cda:entry/cda:supply",
60
68
  "./cda:participant/cda:participantRole/cda:playingDevice/cda:code")
69
+ @section_importers[:allergies] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.18']",
70
+ "./cda:participant/cda:participantRole/cda:playingEntity/cda:code")
71
+ @section_importers[:immunizations] = SectionImporter.new("//cda:substanceAdministration[cda:templateId/@root='2.16.840.1.113883.10.20.1.24']",
72
+ "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code")
61
73
  end
62
-
74
+
63
75
  # Parses a HITSP C32 document and returns a Hash of of the patient.
64
76
  #
65
77
  # @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
@@ -71,7 +83,7 @@ module QME
71
83
  get_demographics(patient_record, doc)
72
84
  process_events(patient_record, c32_patient)
73
85
  end
74
-
86
+
75
87
  # Parses a patient hash containing demongraphic and event information
76
88
  #
77
89
  # @param [Hash] patient_hash patient data
@@ -88,27 +100,32 @@ module QME
88
100
  end
89
101
  process_events(patient_record, event_hash)
90
102
  end
91
-
103
+
92
104
  def process_events(patient_record, event_hash)
93
105
  patient_record['measures'] = {}
94
106
  @measure_importers.each_pair do |measure_id, importer|
95
107
  patient_record['measures'][measure_id] = importer.parse(event_hash)
96
108
  end
97
-
98
109
  patient_record
99
110
  end
100
-
111
+
101
112
  # Parses a list of event hashes into an array of Entry objects
102
113
  #
103
114
  # @param [Array] event_list list of event hashes
104
115
  # @return [Array] array of Entry objects
105
116
  def parse_events(event_list)
106
117
  event_list.collect do |event|
107
- nil if event.class==String.class # skip
108
- QME::Importer::Entry.from_event_hash(event)
118
+ if event.class==String.class
119
+ # skip String elements in the event list, patient randomization templates
120
+ # introduce String elements to simplify tailing-comma handling when generating
121
+ # JSON using ERb
122
+ nil
123
+ else
124
+ QME::Importer::Entry.from_event_hash(event)
125
+ end
109
126
  end.compact
110
127
  end
111
-
128
+
112
129
  # Adds a measure to run on a C32 that is passed in
113
130
  #
114
131
  # @param [MeasureBase] measure an Class that can extract information from a C32 that is necessary
@@ -116,7 +133,7 @@ module QME
116
133
  def add_measure(measure_id, importer)
117
134
  @measure_importers[measure_id] = importer
118
135
  end
119
-
136
+
120
137
  # Create a simple representation of the patient from a HITSP C32
121
138
  #
122
139
  # @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
@@ -127,10 +144,9 @@ module QME
127
144
  @section_importers.each_pair do |section, importer|
128
145
  c32_patient[section] = importer.create_entries(doc)
129
146
  end
130
-
131
147
  c32_patient
132
148
  end
133
-
149
+
134
150
  # Inspects a C32 document and populates the patient Hash with first name, last name
135
151
  # birth date and gender.
136
152
  #
@@ -42,8 +42,13 @@ module QME
42
42
  # Hashes will have a "value" and "date" property containing the respective data
43
43
  def extract_value_date_list(entry_list)
44
44
  basic_extractor(entry_list) do |entry, matching_values|
45
- if entry.value[:scalar]
46
- matching_values << {'date' => entry.as_point_in_time, 'value' => entry.value[:scalar]}
45
+ value = entry.value[:scalar]
46
+ if value
47
+ if @property_description['items']['properties']['value']['type'] == 'number'
48
+ value = value.to_f
49
+ end
50
+
51
+ matching_values << {'date' => entry.as_point_in_time, 'value' => value}
47
52
  end
48
53
  end
49
54
  end
@@ -8,11 +8,15 @@ module QME
8
8
  # @param [String] entry_xpath An XPath expression that can be used to find the desired entries
9
9
  # @param [String] code_xpath XPath expression to find the code element as a child of the desired CDA entry.
10
10
  # Defaults to "./cda:code"
11
- def initialize(entry_xpath, code_xpath="./cda:code")
11
+ # @param [String] status_xpath XPath expression to find the status element as a child of the desired CDA
12
+ # entry. Defaults to nil. If not provided, a status will not be checked for since it is not applicable
13
+ # to all enrty types
14
+ def initialize(entry_xpath, code_xpath="./cda:code", status_xpath=nil)
12
15
  @entry_xpath = entry_xpath
13
16
  @code_xpath = code_xpath
17
+ @status_xpath = status_xpath
14
18
  end
15
-
19
+
16
20
  # Traverses that HITSP C32 document passed in using XPath and creates an Array of Entry
17
21
  # objects based on what it finds
18
22
  # @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
@@ -27,34 +31,49 @@ module QME
27
31
  extract_codes(entry_element, entry)
28
32
  extract_dates(entry_element, entry)
29
33
  extract_value(entry_element, entry)
34
+ if @status_xpath
35
+ extract_status(entry_element, entry)
36
+ end
30
37
  if entry.usable?
31
38
  entry_list << entry
32
39
  end
33
40
  end
34
-
35
41
  entry_list
36
42
  end
37
-
43
+
38
44
  private
39
-
45
+
46
+ def extract_status(parent_element, entry)
47
+ status_element = parent_element.at_xpath(@status_xpath)
48
+ if status_element
49
+ case status_element['code']
50
+ when '55561003'
51
+ entry.status = :active
52
+ when '73425007'
53
+ entry.status = :inactive
54
+ when '413322009'
55
+ entry.status = :resolved
56
+ end
57
+ end
58
+ end
59
+
40
60
  def extract_codes(parent_element, entry)
41
61
  code_elements = parent_element.xpath(@code_xpath)
42
62
  code_elements.each do |code_element|
43
63
  add_code_if_present(code_element, entry)
44
-
45
64
  translations = code_element.xpath('cda:translation')
46
65
  translations.each do |translation|
47
66
  add_code_if_present(translation, entry)
48
67
  end
49
68
  end
50
69
  end
51
-
70
+
52
71
  def add_code_if_present(code_element, entry)
53
72
  if code_element['codeSystem'] && code_element['code']
54
73
  entry.add_code(code_element['code'], CodeSystemHelper.code_system_for(code_element['codeSystem']))
55
74
  end
56
75
  end
57
-
76
+
58
77
  def extract_dates(parent_element, entry)
59
78
  if parent_element.at_xpath('cda:effectiveTime')
60
79
  entry.time = HL7Helper.timestamp_to_integer(parent_element.at_xpath('cda:effectiveTime')['value'])
@@ -65,8 +84,11 @@ module QME
65
84
  if parent_element.at_xpath('cda:effectiveTime/cda:high')
66
85
  entry.end_time = HL7Helper.timestamp_to_integer(parent_element.at_xpath('cda:effectiveTime/cda:high')['value'])
67
86
  end
87
+ if parent_element.at_xpath('cda:effectiveTime/cda:center')
88
+ entry.time = HL7Helper.timestamp_to_integer(parent_element.at_xpath('cda:effectiveTime/cda:center')['value'])
89
+ end
68
90
  end
69
-
91
+
70
92
  def extract_value(parent_element, entry)
71
93
  value_element = parent_element.at_xpath('cda:value')
72
94
  if value_element
@@ -31,18 +31,9 @@ module QME
31
31
  result = ''
32
32
  result << 'if (typeof(map)=="undefined") {'
33
33
  result << "\n"
34
- Dir.glob(File.join(File.dirname(__FILE__), '../../../js/*.js')).each do |js_file|
35
- result << File.read(js_file)
36
- result << "\n"
37
- end
38
- Dir.glob(File.join('./js/*.js')).each do |js_file|
39
- result << File.read(js_file)
40
- result << "\n"
41
- end
42
34
  @db['bundles'].find.each do |bundle|
43
35
  (bundle['extensions'] || []).each do |ext|
44
- result << ext
45
- result << "\n"
36
+ result << "#{ext}();\n"
46
37
  end
47
38
  end
48
39
  result << "}\n"
@@ -89,8 +80,19 @@ module QME
89
80
  # wrapper for the reduce utility function specified in
90
81
  # map-reduce-utils.js
91
82
  # @return [String] the reduce function
92
- def reduce_function
93
- 'function (key, values) { return reduce(key, values);};'
83
+ def finalize_function
84
+ reduce =
85
+ "function (key, value) {
86
+ var patient = value;
87
+ patient.measure_id = \"#{@measure_def['id']}\";\n"
88
+ if @measure_def['sub_id']
89
+ reduce += " patient.sub_id = \"#{@measure_def['sub_id']}\";\n"
90
+ end
91
+
92
+ reduce += "patient.effective_date = #{@params['effective_date']};
93
+ return patient;}"
94
+
95
+ reduce
94
96
  end
95
97
 
96
98
 
@@ -4,112 +4,48 @@ module QME
4
4
  # Computes the value of quality measures based on the current set of patient
5
5
  # records in the database
6
6
  class Executor
7
-
8
- # Create a new Executor for the supplied database connection
9
- def initialize(db)
10
- @db = db
7
+ include DatabaseAccess
8
+
9
+ def initialize(measure_id, sub_id, parameter_values)
10
+ @measure_id = measure_id
11
+ @sub_id = sub_id
12
+ @parameter_values = parameter_values
13
+ determine_connection_information
11
14
  end
12
15
 
13
- # Retrieve a measure definition from the database
14
- # @param [String] measure_id value of the measure's id field
15
- # @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
16
- # @return [Hash] a JSON hash of the encoded measure
17
- def measure_def(measure_id, sub_id=nil)
18
- measures = @db.collection('measures')
19
- if sub_id
20
- measures.find_one({'id'=>measure_id, 'sub_id'=>sub_id})
21
- else
22
- measures.find_one({'id'=>measure_id})
16
+ # Examines the patient_cache collection and generates a total of all groups
17
+ # for the measure. The totals are placed in a document in the query_cache
18
+ # collection.
19
+ # @return [Hash] measure groups (like numerator) as keys, counts as values
20
+ def count_records_in_measure_groups
21
+ patient_cache = get_db.collection('patient_cache')
22
+ query = {'value.measure_id' => @measure_id, 'value.sub_id' => @sub_id,
23
+ 'value.effective_date' => @parameter_values['effective_date']}
24
+ result = {:measure_id => @measure_id, :sub_id => @sub_id,
25
+ :effective_date => @parameter_values['effective_date']}
26
+
27
+ %w(population denominator numerator antinumerator exclusions).each do |measure_group|
28
+ patient_cache.find(query.merge("value.#{measure_group}" => true)) do |cursor|
29
+ result[measure_group] = cursor.count
30
+ end
23
31
  end
24
- end
25
-
26
- # Compute the specified measure
27
- # @param [String] measure_id value of the measure's id field
28
- # @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
29
- # @param [Hash] parameter_values a hash of the measure parameter values. Keys may be either a String or Symbol
30
- # @return [Hash] a hash of the measure result with Symbol keys: population, denominator, numerator, antinumerator and exclusions whose values are the count of patient records that meet the criteria for each component of the measure. Also included are keys: population_members, denominator_members, numerator_members, antinumerator_members and exclusions_members whose values are arrays of the patient record identifiers that meet the criteria for each component of the measure.
31
- def measure_result(measure_id, sub_id, parameter_values)
32
32
 
33
- cache = @db.collection("query_cache")
34
- cache_q = {:measure_id=>measure_id, :sub_id=>sub_id,:effective_date=>parameter_values[:effective_date]}
35
- result = cache.find_one(cache_q)
36
- unless result
37
- measure = Builder.new(@db, measure_def(measure_id, sub_id), parameter_values)
38
-
39
- records = @db.collection('records')
40
- results = records.map_reduce(measure.map_function, measure.reduce_function)
41
- result = results.find_one # only one key is used by map fn
42
- result['value']['cache_name'] = cache_name(measure_id,sub_id,parameter_values[:effective_date])
43
- cache_q["value"] =result['value']
44
- cache << cache_q
45
-
46
- cache_measure_patients(measure_id,sub_id,parameter_values[:effective_date],result['value'])
47
- end
33
+ get_db.collection("query_cache").save(result)
48
34
 
49
- value = result['value']
50
- summary = {}
51
- %w(population denominator numerator antinumerator exclusions).each do |field|
52
- summary[field.intern] = value[field].length
53
- summary[(field+'_members').intern] = value[field]
54
- end
55
- summary
56
-
57
- end
58
-
59
- # Return a list of the measures in the database
60
- # @return [Hash] an hash of measure definitions
61
- def all_measures
62
- result = {}
63
- measures = @db.collection('measures')
64
- measures.find().each do |measure|
65
- id = measure['id']
66
- sub_id = measure['sub_id']
67
- measure_id = "#{id}#{sub_id}.json"
68
- result[measure_id] ||= measure
69
- end
70
35
  result
71
36
  end
72
-
73
-
74
- private
75
-
76
-
77
- def cache_name(measure_id, sub_id, effective_date)
78
- "cached_measure_patients_#{measure_id}_#{sub_id}_#{effective_date}"
79
- end
80
-
81
- # Private method to cache the patient record for a particular measure and effective time. This also caches if they are part of the denominator, numeratore ..... which will be used for sorting.
82
- # @param [String] measure_id value of the measure's id field
83
- # @param [String] sub_id value of the measure's sub_id field, may be nil for measures with only a single numerator and denominator
84
- # @param [Integer] effective_date the effective date used by the map reduce function to calculate popultion, denominator .......
85
- def cache_measure_patients(measure_id, sub_id, effective_date, results,drop=true)
86
- col_name =cache_name(measure_id, sub_id, effective_date)
87
- @db.collection(col_name).drop if drop
88
- cached_patients = @db.collection(col_name)
89
- population = results['population']
90
- if population
91
- records = @db.collection("records").find('_id' => {'$in' => results['population']})
92
- records.each do |record|
93
- record_id = record['_id']
94
- new_record = {:first=>record['first'],
95
- :last=>record['last'],
96
- :birthdate=>record['birthdate'],
97
- :gender=>record['gender'],
98
- :measure_id => measure_id,
99
- :sub_id=>sub_id,
100
- :effective_date => effective_date,
101
- :measure_data => record[measure_id],
102
- :numerator=>!results['numerator'].index(record_id).nil?,
103
- :denominator=>!results['denominator'].index(record_id).nil?,
104
- :exclusion=>!results['exclusions'].index(record_id).nil?,
105
- :antinumerator=>!results['antinumerator'].index(record_id).nil?}
106
-
107
- cached_patients << new_record
108
-
109
- end
110
- end
37
+
38
+ # This method runs the MapReduce job for the measure which will create documents
39
+ # in the patient_cache collection. These documents will state the measure groups
40
+ # that the record belongs to, such as numerator, etc.
41
+ def map_records_into_measure_groups
42
+ qm = QualityMeasure.new(@measure_id, @sub_id)
43
+ measure = Builder.new(get_db, qm.definition, @parameter_values)
44
+ records = get_db.collection('records')
45
+ records.map_reduce(measure.map_function, "function(key, values){return values;}",
46
+ :out => {:reduce => 'patient_cache'},
47
+ :finalize => measure.finalize_function)
111
48
  end
112
-
113
49
  end
114
50
  end
115
51
  end