quality-measure-engine 0.8.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 1.0.1
@@ -116,9 +116,9 @@ function() {
116
116
  root.map = function(record, population, denominator, numerator, exclusion) {
117
117
  var value = {population: false, denominator: false, numerator: false,
118
118
  exclusions: false, antinumerator: false, patient_id: record._id,
119
+ medical_record_id: record.patient_id,
119
120
  first: record.first, last: record.last, gender: record.gender,
120
- birthdate: record.birthdate};
121
- var patient = record._id;
121
+ birthdate: record.birthdate, test_id: record.test_id};
122
122
  if (population()) {
123
123
  value.population = true;
124
124
  if (denominator()) {
@@ -1,6 +1,5 @@
1
1
  module QME
2
2
  module Importer
3
-
4
3
  # General helpers for working with codes and code systems
5
4
  class CodeSystemHelper
6
5
  CODE_SYSTEMS = {
@@ -20,9 +19,27 @@ module QME
20
19
  # @param [String] oid of a code system
21
20
  # @return [String] the name of the code system as described in the measure definition JSON
22
21
  def self.code_system_for(oid)
23
- CODE_SYSTEMS[oid]
22
+ CODE_SYSTEMS[oid] || "Unknown"
23
+ end
24
+
25
+ @@oid_map = nil
26
+
27
+ # Returns the oid for a code system given a codesystem name
28
+ # @param [String] the name of the code system
29
+ # @return [String] the oid of the code system
30
+ def self.oid_for_code_system(codesystem)
31
+ if(!@@oid_map)
32
+ @@oid_map = {}
33
+ CODE_SYSTEMS.each_pair do |oid, codesystem|
34
+ # STDERR.puts "Adding #{oid}, #{codesystem}"
35
+ @@oid_map[codesystem] = oid
36
+ end
37
+ end
38
+ # STDERR.puts "@@oid_map[#{codesystem}] = #{@@oid_map[codesystem]}"
39
+ return @@oid_map[codesystem]
24
40
  end
25
41
 
26
42
  end
27
43
  end
28
- end
44
+ end
45
+
@@ -2,19 +2,27 @@ module QME
2
2
  module Importer
3
3
  # Object that represents a CDA Entry (or act, observation, etc.)
4
4
  class Entry
5
- attr_accessor :start_time, :end_time, :time, :status
5
+ attr_accessor :start_time, :end_time, :time, :status, :description
6
6
  attr_reader :codes, :value
7
7
 
8
- def initialize
8
+ def initialize
9
9
  @codes = {}
10
10
  @value = {}
11
11
  end
12
12
 
13
- def Entry.from_event_hash(event)
13
+ def self.from_event_hash(event)
14
14
  entry = Entry.new
15
- entry.add_code(event['code'], event['code_set'])
15
+ if event['code']
16
+ entry.add_code(event['code'], event['code_set'])
17
+ end
16
18
  entry.time = event['time']
17
- entry.set_value(event['value'], event['unit'])
19
+ if event['value']
20
+ entry.set_value(event['value'], event['unit'])
21
+ end
22
+ if event['description']
23
+ entry.description = event['description']
24
+ end
25
+
18
26
  entry
19
27
  end
20
28
 
@@ -74,6 +82,33 @@ module QME
74
82
  def usable?
75
83
  (! @codes.empty?) && ((! @start_time.nil?) || (! @end_time.nil?) || (! @time.nil?))
76
84
  end
85
+
86
+ # Creates a Hash for this Entry
87
+ # @return [Hash] a Hash representing the Entry
88
+ def to_hash
89
+ entry_hash = {}
90
+ entry_hash['codes'] = @codes
91
+ unless @value.empty?
92
+ entry_hash['value'] = @value
93
+ end
94
+
95
+ if is_date_range?
96
+ entry_hash['start_time'] = @start_time
97
+ entry_hash['end_time'] = @end_time
98
+ else
99
+ entry_hash['time'] = as_point_in_time
100
+ end
101
+
102
+ if @status
103
+ entry_hash['status'] = @status
104
+ end
105
+
106
+ if @description
107
+ entry_hash['description'] = @description
108
+ end
109
+
110
+ entry_hash
111
+ end
77
112
  end
78
113
  end
79
- end
114
+ end
@@ -50,26 +50,51 @@ module QME
50
50
  #
51
51
  # Codes for immunizations are found in the substanceAdministration with the following relative XPath
52
52
  # ./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code
53
- def initialize
53
+ def initialize (check_usable = true)
54
54
  @measure_importers = {}
55
55
  @section_importers = {}
56
+ @id_map = {}
56
57
  @section_importers[:encounters] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.127']/cda:entry/cda:encounter")
57
58
  @section_importers[:procedures] = SectionImporter.new("//cda:procedure[cda:templateId/@root='2.16.840.1.113883.10.20.1.29']")
58
59
  @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']")
59
60
  @section_importers[:vital_signs] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.14']")
60
61
  @section_importers[:medications] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.112']/cda:entry/cda:substanceAdministration",
61
- "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code")
62
+ "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code",
63
+ nil,
64
+ "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code/cda:originalText/cda:reference[@value]")
62
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",
63
- "./cda:value",
64
- "./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.1 13883.10.20.1.50']/cda:value")
66
+ "./cda:value",
67
+ "./cda:entryRelationship/cda:observation[cda:templateId/@root='2.16.840.1.1 13883.10.20.1.50']/cda:value",
68
+ "./cda:text/cda:reference[@value]")
65
69
  @section_importers[:social_history] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.19']")
66
70
  @section_importers[:care_goals] = SectionImporter.new("//cda:observation[cda:templateId/@root='2.16.840.1.113883.10.20.1.25']")
67
71
  @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",
68
- "./cda:participant/cda:participantRole/cda:playingDevice/cda:code")
72
+ "./cda:participant/cda:participantRole/cda:playingDevice/cda:code")
69
73
  @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")
74
+ "./cda:participant/cda:participantRole/cda:playingEntity/cda:code")
75
+ @section_importers[:immunizations] = SectionImporter.new("//cda:section[cda:templateId/@root='2.16.840.1.113883.3.88.11.83.117']/cda:entry/cda:substanceAdministration",
76
+ "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code",
77
+ nil,
78
+ "./cda:consumable/cda:manufacturedProduct/cda:manufacturedMaterial/cda:code/cda:originalText/cda:reference[@value]" )
79
+ end
80
+
81
+ def build_id_map(doc)
82
+ id_map = {}
83
+ path = "//*[@ID]"
84
+ ids = doc.xpath(path)
85
+ ids.each do |id|
86
+ tag = id['ID']
87
+ value = id.content
88
+ id_map[tag] = value
89
+ end
90
+ return id_map
91
+ end
92
+
93
+ # @param [boolean] value for check_usable_entries...importer uses true, stats uses false
94
+ def check_usable(check_usable_entries)
95
+ @section_importers.each_pair do |section, importer|
96
+ importer.check_for_usable = check_usable_entries
97
+ end
73
98
  end
74
99
 
75
100
  # Parses a HITSP C32 document and returns a Hash of of the patient.
@@ -78,21 +103,23 @@ module QME
78
103
  # will have the "cda" namespace registered to "urn:hl7-org:v3"
79
104
  # @return [Hash] a representation of the patient that can be inserted into MongoDB
80
105
  def parse_c32(doc)
81
- patient_record = {}
82
- c32_patient = create_c32_hash(doc)
83
- get_demographics(patient_record, doc)
84
- process_events(patient_record, c32_patient)
106
+ c32_patient = {}
107
+ entries = create_c32_hash(doc)
108
+ get_demographics(c32_patient, doc)
109
+ process_events(c32_patient, entries)
85
110
  end
86
111
 
87
- # Parses a patient hash containing demongraphic and event information
112
+ # Parses a patient hash containing demographic and event information
88
113
  #
89
114
  # @param [Hash] patient_hash patient data
90
115
  # @return [Hash] a representation of the patient that can be inserted into MongoDB
91
116
  def parse_hash(patient_hash)
92
117
  patient_record = {}
93
118
  patient_record['first'] = patient_hash['first']
119
+ patient_record['patient_id'] = patient_hash['patient_id']
94
120
  patient_record['last'] = patient_hash['last']
95
121
  patient_record['gender'] = patient_hash['gender']
122
+ patient_record['patient_id'] = patient_hash['patient_id']
96
123
  patient_record['birthdate'] = patient_hash['birthdate']
97
124
  event_hash = {}
98
125
  patient_hash['events'].each do |key, value|
@@ -101,11 +128,30 @@ module QME
101
128
  process_events(patient_record, event_hash)
102
129
  end
103
130
 
104
- def process_events(patient_record, event_hash)
131
+ # Adds the entries and denormalized measure information to the patient_record.
132
+ # Each Entry will be converted to a Hash and stored in an Array under the appropriate
133
+ # section key, such as medications. Measure information is listed under the measures
134
+ # key which has a Hash value. The Hash has the measure id as a key, and the denormalized
135
+ # measure information as a value
136
+ #
137
+ # @param patient_record - Hash with basic patient demographic information
138
+ # @entries - Hash of entries with section names a keys and an Array of Entry values
139
+ def process_events(patient_record, entries)
105
140
  patient_record['measures'] = {}
106
141
  @measure_importers.each_pair do |measure_id, importer|
107
- patient_record['measures'][measure_id] = importer.parse(event_hash)
142
+ patient_record['measures'][measure_id] = importer.parse(entries)
108
143
  end
144
+
145
+ entries.each_pair do |key, value|
146
+ patient_record[key] = value.map do |e|
147
+ if e.usable?
148
+ e.to_hash
149
+ else
150
+ nil
151
+ end
152
+ end.compact
153
+ end
154
+
109
155
  patient_record
110
156
  end
111
157
 
@@ -139,10 +185,12 @@ module QME
139
185
  # @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
140
186
  # will have the "cda" namespace registered to "urn:hl7-org:v3"
141
187
  # @return [Hash] a represnetation of the patient with symbols as keys for each section
142
- def create_c32_hash(doc)
188
+ def create_c32_hash(doc, check_usable_entries = true)
143
189
  c32_patient = {}
190
+ id_map = build_id_map(doc)
144
191
  @section_importers.each_pair do |section, importer|
145
- c32_patient[section] = importer.create_entries(doc)
192
+ importer.check_for_usable = check_usable_entries
193
+ c32_patient[section] = importer.create_entries(doc,id_map)
146
194
  end
147
195
  c32_patient
148
196
  end
@@ -160,7 +208,9 @@ module QME
160
208
  patient['birthdate'] = HL7Helper.timestamp_to_integer(birthdate_in_hl7ts)
161
209
  gender_node = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:patient/cda:administrativeGenderCode')
162
210
  patient['gender'] = gender_node['code']
211
+ id_node = doc.at_xpath('/cda:ClinicalDocument/cda:recordTarget/cda:patientRole/cda:id')
212
+ patient['patient_id'] = id_node['extension']
163
213
  end
164
214
  end
165
215
  end
166
- end
216
+ end
@@ -3,27 +3,47 @@ module QME
3
3
  # Class that can be used to create an importer for a section of a HITSP C32 document. It usually
4
4
  # operates by selecting all CDA entries in a section and then creates entries for them.
5
5
  class SectionImporter
6
-
6
+ attr_accessor :check_for_usable
7
7
  # Creates a new SectionImporter
8
+ # @param [Hash] id_map A hash of all ID tags to values for the enclosing document. Used to look up descriptions.
8
9
  # @param [String] entry_xpath An XPath expression that can be used to find the desired entries
9
10
  # @param [String] code_xpath XPath expression to find the code element as a child of the desired CDA entry.
10
11
  # Defaults to "./cda:code"
11
12
  # @param [String] status_xpath XPath expression to find the status element as a child of the desired CDA
12
13
  # entry. Defaults to nil. If not provided, a status will not be checked for since it is not applicable
13
14
  # to all enrty types
14
- def initialize(entry_xpath, code_xpath="./cda:code", status_xpath=nil)
15
+ def initialize(entry_xpath, code_xpath="./cda:code", status_xpath=nil, description_xpath="./cda:code/cda:originalText/cda:reference[@value] | ./cda:text/cda:reference[@value] ")
15
16
  @entry_xpath = entry_xpath
16
17
  @code_xpath = code_xpath
17
18
  @status_xpath = status_xpath
19
+ @description_xpath = description_xpath
20
+ @check_for_usable = true # Pilot tools will set this to false
21
+ @id_map = {}
18
22
  end
19
23
 
24
+
25
+ # @param [String] tag
26
+ # @return [String] text description of tag
27
+ def lookup_tag(tag)
28
+ value = @id_map[tag]
29
+ # Not sure why, but sometimes the reference is #<Reference> and the ID value is <Reference>, and
30
+ # sometimes it is #<Reference>. We look for both.
31
+ if !value and tag[0] == '#'
32
+ tag = tag[1,tag.length]
33
+ value = @id_map[tag]
34
+ end
35
+
36
+ value
37
+ end
38
+
20
39
  # Traverses that HITSP C32 document passed in using XPath and creates an Array of Entry
21
- # objects based on what it finds
40
+ # objects based on what it finds
22
41
  # @param [Nokogiri::XML::Document] doc It is expected that the root node of this document
23
42
  # will have the "cda" namespace registered to "urn:hl7-org:v3"
24
43
  # measure definition
25
44
  # @return [Array] will be a list of Entry objects
26
- def create_entries(doc)
45
+ def create_entries(doc,id_map = {})
46
+ @id_map = id_map
27
47
  entry_list = []
28
48
  entry_elements = doc.xpath(@entry_xpath)
29
49
  entry_elements.each do |entry_element|
@@ -34,7 +54,12 @@ module QME
34
54
  if @status_xpath
35
55
  extract_status(entry_element, entry)
36
56
  end
37
- if entry.usable?
57
+ if @description_xpath
58
+ extract_description(entry_element, entry, id_map)
59
+ end
60
+ if @check_for_usable
61
+ entry_list << entry if entry.usable?
62
+ else
38
63
  entry_list << entry
39
64
  end
40
65
  end
@@ -51,12 +76,20 @@ module QME
51
76
  entry.status = :active
52
77
  when '73425007'
53
78
  entry.status = :inactive
54
- when '413322009'
79
+ when '413322009'
55
80
  entry.status = :resolved
56
81
  end
57
82
  end
58
83
  end
59
84
 
85
+ def extract_description(parent_element, entry, id_map)
86
+ code_elements = parent_element.xpath(@description_xpath)
87
+ code_elements.each do |code_element|
88
+ tag = code_element['value']
89
+ entry.description = lookup_tag(tag)
90
+ end
91
+ end
92
+
60
93
  def extract_codes(parent_element, entry)
61
94
  code_elements = parent_element.xpath(@code_xpath)
62
95
  code_elements.each do |code_element|
@@ -101,4 +134,5 @@ module QME
101
134
  end
102
135
  end
103
136
  end
104
- end
137
+ end
138
+
@@ -85,6 +85,9 @@ module QME
85
85
  "function (key, value) {
86
86
  var patient = value;
87
87
  patient.measure_id = \"#{@measure_def['id']}\";\n"
88
+ if @params['test_id'] && @params['test_id'].class==BSON::ObjectId
89
+ reduce += " patient.test_id = new ObjectId(\"#{@params['test_id']}\");\n"
90
+ end
88
91
  if @measure_def['sub_id']
89
92
  reduce += " patient.sub_id = \"#{@measure_def['sub_id']}\";\n"
90
93
  end
@@ -1,11 +1,17 @@
1
1
  module QME
2
+
2
3
  module MapReduce
3
4
 
4
5
  # Computes the value of quality measures based on the current set of patient
5
6
  # records in the database
6
7
  class Executor
8
+
7
9
  include DatabaseAccess
8
-
10
+
11
+ # Create a new Executor for a specific measure, effective date and patient population.
12
+ # @param [String] measure_id the measure identifier
13
+ # @param [String] sub_id the measure sub-identifier or null if the measure is single numerator
14
+ # @param [Hash] parameter_values a hash that may contain the following keys: 'effective_date' the measurement period end date, 'test_id' an identifier for a specific set of patients
9
15
  def initialize(measure_id, sub_id, parameter_values)
10
16
  @measure_id = measure_id
11
17
  @sub_id = sub_id
@@ -20,18 +26,17 @@ module QME
20
26
  def count_records_in_measure_groups
21
27
  patient_cache = get_db.collection('patient_cache')
22
28
  query = {'value.measure_id' => @measure_id, 'value.sub_id' => @sub_id,
23
- 'value.effective_date' => @parameter_values['effective_date']}
29
+ 'value.effective_date' => @parameter_values['effective_date'],
30
+ 'value.test_id' => @parameter_values['test_id']}
24
31
  result = {:measure_id => @measure_id, :sub_id => @sub_id,
25
- :effective_date => @parameter_values['effective_date']}
26
-
32
+ :effective_date => @parameter_values['effective_date'],
33
+ :test_id => @parameter_values['test_id']}
27
34
  %w(population denominator numerator antinumerator exclusions).each do |measure_group|
28
35
  patient_cache.find(query.merge("value.#{measure_group}" => true)) do |cursor|
29
36
  result[measure_group] = cursor.count
30
37
  end
31
38
  end
32
-
33
39
  get_db.collection("query_cache").save(result)
34
-
35
40
  result
36
41
  end
37
42
 
@@ -44,7 +49,8 @@ module QME
44
49
  records = get_db.collection('records')
45
50
  records.map_reduce(measure.map_function, "function(key, values){return values;}",
46
51
  :out => {:reduce => 'patient_cache'},
47
- :finalize => measure.finalize_function)
52
+ :finalize => measure.finalize_function,
53
+ :query => {:test_id => @parameter_values['test_id']})
48
54
  end
49
55
  end
50
56
  end
@@ -2,7 +2,7 @@ module QME
2
2
  module MapReduce
3
3
  # A Resque job that allows for measure calculation by a Resque worker. Can be created as follows:
4
4
  #
5
- # MapReduce::MeasureCalculationJob.create(:measure_id => '0221', :sub_id => 'a', :effective_date => 1291352400)
5
+ # MapReduce::MeasureCalculationJob.create(:measure_id => '0221', :sub_id => 'a', :effective_date => 1291352400, :test_id => xyzzy)
6
6
  #
7
7
  # This will return a uuid which can be used to check in on the status of a job. More details on this can be found
8
8
  # at the {Resque Stats project page}[https://github.com/quirkey/resque-status].
@@ -15,11 +15,12 @@ module QME
15
15
  # the report.
16
16
  class MeasureCalculationJob < Resque::JobWithStatus
17
17
  def perform
18
- qr = QualityReport.new(options['measure_id'], options['sub_id'], 'effective_date' => options['effective_date'])
18
+ test_id = options['test_id'] ? BSON::ObjectId(options['test_id']) : nil
19
+ qr = QualityReport.new(options['measure_id'], options['sub_id'], 'effective_date' => options['effective_date'], 'test_id' => test_id)
19
20
  if qr.calculated?
20
21
  completed("#{options['measure_id']}#{options['sub_id']} has already been calculated")
21
22
  else
22
- map = QME::MapReduce::Executor.new(options['measure_id'], options['sub_id'], 'effective_date' => options['effective_date'])
23
+ map = QME::MapReduce::Executor.new(options['measure_id'], options['sub_id'], 'effective_date' => options['effective_date'], 'test_id' => test_id)
23
24
  tick('Starting MapReduce')
24
25
  map.map_records_into_measure_groups
25
26
  tick('MapReduce complete')
@@ -19,7 +19,7 @@ module QME
19
19
  # @param [String] sub_id value of the measure's sub_id field, may be nil
20
20
  # for measures with only a single numerator and denominator
21
21
  # @param [Hash] parameter_values slots in the measure definition that need to
22
- # be filled in.
22
+ # be filled in and an optional test_id to identify a sub-population.
23
23
  def initialize(measure_id, sub_id, parameter_values)
24
24
  @measure_id = measure_id
25
25
  @sub_id = sub_id
@@ -38,7 +38,8 @@ module QME
38
38
  # @return a unique id for the measure calculation job
39
39
  def calculate
40
40
  MapReduce::MeasureCalculationJob.create(:measure_id => @measure_id, :sub_id => @sub_id,
41
- :effective_date => @parameter_values['effective_date'])
41
+ :effective_date => @parameter_values['effective_date'],
42
+ :test_id => @parameter_values['test_id'])
42
43
  end
43
44
 
44
45
  # Returns the status of a measure calculation job
@@ -54,7 +55,8 @@ module QME
54
55
  def result
55
56
  cache = get_db.collection("query_cache")
56
57
  query = {:measure_id => @measure_id, :sub_id => @sub_id,
57
- :effective_date => @parameter_values['effective_date']}
58
+ :effective_date => @parameter_values['effective_date'],
59
+ :test_id => @parameter_values['test_id']}
58
60
  cache.find_one(query)
59
61
  end
60
62
  end
@@ -0,0 +1,48 @@
1
+ module QME
2
+ module Randomizer
3
+ # A Resque job that allows for generation of randomized patients by a Resque worker. Can be created as follows:
4
+ #
5
+ # QME::Randomizer::PatientRandomizationJob.create(:template_dir => '/xx/yy', :count => 100, [:test_id => ObjectId])
6
+ #
7
+ # This will return a uuid which can be used to check in on the status of a job. More details on this can be found
8
+ # at the {Resque Stats project page}[https://github.com/quirkey/resque-status].
9
+ #
10
+ class PatientRandomizationJob < Resque::JobWithStatus
11
+ def perform
12
+ test_id = options['test_id'] ? BSON::ObjectId(options['test_id']) : nil
13
+ template_dir = options['template_dir']
14
+ count = options['count']
15
+
16
+ tick('Reading templates')
17
+ templates = []
18
+ Dir.glob(File.join(template_dir, '*.json.erb')).each do |file|
19
+ templates << File.read(file)
20
+ end
21
+
22
+ tick('Initializing parser')
23
+ processed_measures = {}
24
+ QME::QualityMeasure.all.each_value do |measure_def|
25
+ measure_id = measure_def['id']
26
+ if !processed_measures[measure_id]
27
+ QME::Importer::PatientImporter.instance.add_measure(measure_id, QME::Importer::GenericImporter.new(measure_def))
28
+ processed_measures[measure_id]=true
29
+ end
30
+ end
31
+
32
+ loader = QME::Database::Loader.new()
33
+ tick('Generating patients')
34
+ count.times do |i|
35
+ at(i, count, "Generating patient #{i} of #{count}")
36
+ template = templates[rand(templates.length)]
37
+ generator = QME::Randomizer::Patient.new(template)
38
+ json = JSON.parse(generator.get())
39
+ patient_record_hash = QME::Importer::PatientImporter.instance.parse_hash(json)
40
+ patient_record_hash['test_id'] = test_id
41
+ loader.save('records', patient_record_hash)
42
+ end
43
+
44
+ completed
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,13 +1,15 @@
1
1
  require 'erb'
2
2
 
3
3
  module QME
4
+
4
5
  module Randomizer
5
-
6
+
6
7
  # Provides functionality for randomizing patient records based on erb templates
7
8
  class Patient
8
9
 
9
10
  # Utility class used to supply a binding to Erb
10
11
  class Context
12
+
11
13
  # Create a new context
12
14
  def initialize()
13
15
  @genders = ['M', 'F']
@@ -19,26 +21,32 @@ module QME
19
21
  # 500 most popular surnames according to US census 1990
20
22
  @surnames = %w{Smith Johnson Williams Jones Brown Davis Miller Wilson Moore Taylor Anderson Thomas Jackson White Harris Martin Thompson Garcia Martinez Robinson Clark Rodriguez Lewis Lee Walker Hall Allen Young Hernandez King Wright Lopez Hill Scott Green Adams Baker Gonzalez Nelson Carter Mitchell Perez Roberts Turner Phillips Campbell Parker Evans Edwards Collins Stewart Sanchez Morris Rogers Reed Cook Morgan Bell Murphy Bailey Rivera Cooper Richardson Cox Howard Ward Torres Peterson Gray Ramirez James Watson Brooks Kelly Sanders Price Bennett Wood Barnes Ross Henderson Coleman Jenkins Perry Powell Long Patterson Hughes Flores Washington Butler Simmons Foster Gonzales Bryant Alexander Russell Griffin Diaz Hayes Myers Ford Hamilton Graham Sullivan Wallace Woods Cole West Jordan Owens Reynolds Fisher Ellis Harrison Gibson Mcdonald Cruz Marshall Ortiz Gomez Murray Freeman Wells Webb Simpson Stevens Tucker Porter Hunter Hicks Crawford Henry Boyd Mason Morales Kennedy Warren Dixon Ramos Reyes Burns Gordon Shaw Holmes Rice Robertson Hunt Black Daniels Palmer Mills Nichols Grant Knight Ferguson Rose Stone Hawkins Dunn Perkins Hudson Spencer Gardner Stephens Payne Pierce Berry Matthews Arnold Wagner Willis Ray Watkins Olson Carroll Duncan Snyder Hart Cunningham Bradley Lane Andrews Ruiz Harper Fox Riley Armstrong Carpenter Weaver Greene Lawrence Elliott Chavez Sims Austin Peters Kelley Franklin Lawson Fields Gutierrez Ryan Schmidt Carr Vasquez Castillo Wheeler Chapman Oliver Montgomery Richards Williamson Johnston Banks Meyer Bishop Mccoy Howell Alvarez Morrison Hansen Fernandez Garza Harvey Little Burton Stanley Nguyen George Jacobs Reid Kim Fuller Lynch Dean Gilbert Garrett Romero Welch Larson Frazier Burke Hanson Day Mendoza Moreno Bowman Medina Fowler Brewer Hoffman Carlson Silva Pearson Holland Douglas Fleming Jensen Vargas Byrd Davidson Hopkins May Terry Herrera Wade Soto Walters Curtis Neal Caldwell Lowe Jennings Barnett Graves Jimenez Horton Shelton Barrett Obrien Castro Sutton Gregory Mckinney Lucas Miles Craig Rodriquez Chambers Holt Lambert Fletcher Watts Bates Hale Rhodes Pena Beck Newman Haynes Mcdaniel Mendez Bush Vaughn Parks Dawson Santiago Norris Hardy Love Steele Curry Powers Schultz Barker Guzman Page Munoz Ball Keller Chandler Weber Leonard Walsh Lyons Ramsey Wolfe Schneider Mullins Benson Sharp Bowen Daniel Barber Cummings Hines Baldwin Griffith Valdez Hubbard Salazar Reeves Warner Stevenson Burgess Santos Tate Cross Garner Mann Mack Moss Thornton Dennis Mcgee Farmer Delgado Aguilar Vega Glover Manning Cohen Harmon Rodgers Robbins Newton Todd Blair Higgins Ingram Reese Cannon Strickland Townsend Potter Goodwin Walton Rowe Hampton Ortega Patton Swanson Joseph Francis Goodman Maldonado Yates Becker Erickson Hodges Rios Conner Adkins Webster Norman Malone Hammond Flowers Cobb Moody Quinn Blake Maxwell Pope Floyd Osborne Paul Mccarthy Guerrero Lindsey Estrada Sandoval Gibbs Tyler Gross Fitzgerald Stokes Doyle Sherman Saunders Wise Colon Gill Alvarado Greer Padilla Simon Waters Nunez Ballard Schwartz Mcbride Houston Christensen Klein Pratt Briggs Parsons Mclaughlin Zimmerman French Buchanan Moran Copeland Roy Pittman Brady Mccormick Holloway Brock Poole Frank Logan Owen Bass Marsh Drake Wong Jefferson Park Morton Abbott Sparks Patrick Norton Huff Clayton Massey Lloyd Figueroa Carson Bowers Roberson Barton Tran Lamb Harrington Casey Boone Cortez Clarke Mathis Singleton Wilkins Cain Bryan Underwood Hogan Mckenzie Collier Luna Phelps Mcguire Allison Bridges Wilkerson Nash Summers Atkins}
21
23
  end
22
-
24
+
23
25
  # Pick a gender at random
24
26
  # @return 'M' or 'F'
25
27
  def gender
26
28
  @genders[rand(@genders.length)]
27
29
  end
28
-
30
+
29
31
  # Pick a forename at random appropriate for the supplied gender
30
32
  # @param [String] gender the gender 'M' or 'F'
31
33
  # @return a suitable forename
32
34
  def forename(gender)
33
35
  @forenames[gender][rand(@forenames[gender].length)]
34
36
  end
35
-
37
+
36
38
  # Pick a surname at random
37
39
  # @return a surname
38
40
  def surname
39
41
  @surnames[rand(@surnames.length)]
40
42
  end
41
-
43
+
44
+ # Pick a patient id, which will be a 10 character string, limited to numeric
45
+ # values for the string
46
+ def patient_id
47
+ (0...10).map{ ('0'..'9').to_a[rand(10)] }.join.to_s
48
+ end
49
+
42
50
  # Return a set of randomly selected numbers between two bounds
43
51
  # @param [int] min the lower inclusive bound
44
52
  # @param [int] max the upper inclusive bound
@@ -53,7 +61,7 @@ module QME
53
61
  end
54
62
  result.to_json
55
63
  end
56
-
64
+
57
65
  # Return a randomly selected number between two bounds
58
66
  # @param [int] min the lower inclusive bound
59
67
  # @param [int] max the upper inclusive bound
@@ -62,14 +70,14 @@ module QME
62
70
  span = max.to_i - min.to_i + 1
63
71
  min.to_i+rand(span)
64
72
  end
65
-
73
+
66
74
  # Pick true or false according to the supplied probability
67
75
  # @param [int] probability the probability of getting true as a percentage
68
76
  # @return [boolean] true or false
69
77
  def percent(probability)
70
78
  return rand(100)<probability
71
79
  end
72
-
80
+
73
81
  # Get a binding that for the current instance
74
82
  # @return [Binding]
75
83
  def get_binding
@@ -82,7 +90,7 @@ module QME
82
90
  def initialize(patient)
83
91
  @template = ERB.new(patient)
84
92
  end
85
-
93
+
86
94
  # Get a randomized record based on the stored template
87
95
  # @return [String] a randomized patient
88
96
  def get
@@ -91,5 +99,7 @@ module QME
91
99
  end
92
100
 
93
101
  end
102
+
94
103
  end
104
+
95
105
  end
@@ -1,4 +1,4 @@
1
- Bundler.require(:default)
1
+ require "bundler/setup"
2
2
 
3
3
  require 'resque/job_with_status'
4
4
 
@@ -12,6 +12,7 @@ require_relative 'qme/map/measure_calculation_job'
12
12
  require_relative 'qme/quality_report'
13
13
 
14
14
  require_relative 'qme/randomizer/patient_randomizer'
15
+ require_relative 'qme/randomizer/patient_randomization_job'
15
16
 
16
17
  require 'singleton'
17
18
 
@@ -12,7 +12,7 @@ namespace :patient do
12
12
 
13
13
  desc 'Generate n (default 10) random patient records and save them in the database'
14
14
  task :random, [:n] => ['mongo:drop_records'] do |t, args|
15
- n = args.n.to_i>0 ? args.n.to_i : 1
15
+ n = args.n.to_i>0 ? args.n.to_i : 10
16
16
 
17
17
  templates = []
18
18
  Dir.glob(File.join(patient_template_dir, '*.json.erb')).each do |file|
metadata CHANGED
@@ -1,161 +1,158 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: quality-measure-engine
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
4
5
  prerelease:
5
- version: 0.8.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Marc Hadley
9
9
  - Andy Gregorowicz
10
10
  - Rob Dingwell
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
-
15
- date: 2011-05-09 00:00:00 -04:00
14
+ date: 2011-08-04 00:00:00.000000000 -04:00
16
15
  default_executable:
17
- dependencies:
18
- - !ruby/object:Gem::Dependency
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
19
18
  name: mongo
20
- prerelease: false
21
- requirement: &id001 !ruby/object:Gem::Requirement
19
+ requirement: &2152409780 !ruby/object:Gem::Requirement
22
20
  none: false
23
- requirements:
21
+ requirements:
24
22
  - - ~>
25
- - !ruby/object:Gem::Version
26
- version: "1.3"
23
+ - !ruby/object:Gem::Version
24
+ version: '1.3'
27
25
  type: :runtime
28
- version_requirements: *id001
29
- - !ruby/object:Gem::Dependency
30
- name: rubyzip
31
26
  prerelease: false
32
- requirement: &id002 !ruby/object:Gem::Requirement
27
+ version_requirements: *2152409780
28
+ - !ruby/object:Gem::Dependency
29
+ name: rubyzip
30
+ requirement: &2152409300 !ruby/object:Gem::Requirement
33
31
  none: false
34
- requirements:
32
+ requirements:
35
33
  - - ~>
36
- - !ruby/object:Gem::Version
34
+ - !ruby/object:Gem::Version
37
35
  version: 0.9.4
38
36
  type: :runtime
39
- version_requirements: *id002
40
- - !ruby/object:Gem::Dependency
41
- name: nokogiri
42
37
  prerelease: false
43
- requirement: &id003 !ruby/object:Gem::Requirement
38
+ version_requirements: *2152409300
39
+ - !ruby/object:Gem::Dependency
40
+ name: nokogiri
41
+ requirement: &2152408840 !ruby/object:Gem::Requirement
44
42
  none: false
45
- requirements:
43
+ requirements:
46
44
  - - ~>
47
- - !ruby/object:Gem::Version
45
+ - !ruby/object:Gem::Version
48
46
  version: 1.4.4
49
47
  type: :runtime
50
- version_requirements: *id003
51
- - !ruby/object:Gem::Dependency
52
- name: resque
53
48
  prerelease: false
54
- requirement: &id004 !ruby/object:Gem::Requirement
49
+ version_requirements: *2152408840
50
+ - !ruby/object:Gem::Dependency
51
+ name: resque
52
+ requirement: &2152408380 !ruby/object:Gem::Requirement
55
53
  none: false
56
- requirements:
54
+ requirements:
57
55
  - - ~>
58
- - !ruby/object:Gem::Version
56
+ - !ruby/object:Gem::Version
59
57
  version: 1.15.0
60
58
  type: :runtime
61
- version_requirements: *id004
62
- - !ruby/object:Gem::Dependency
63
- name: resque-status
64
59
  prerelease: false
65
- requirement: &id005 !ruby/object:Gem::Requirement
60
+ version_requirements: *2152408380
61
+ - !ruby/object:Gem::Dependency
62
+ name: resque-status
63
+ requirement: &2152407920 !ruby/object:Gem::Requirement
66
64
  none: false
67
- requirements:
65
+ requirements:
68
66
  - - ~>
69
- - !ruby/object:Gem::Version
67
+ - !ruby/object:Gem::Version
70
68
  version: 0.2.3
71
69
  type: :runtime
72
- version_requirements: *id005
73
- - !ruby/object:Gem::Dependency
74
- name: jsonschema
75
70
  prerelease: false
76
- requirement: &id006 !ruby/object:Gem::Requirement
71
+ version_requirements: *2152407920
72
+ - !ruby/object:Gem::Dependency
73
+ name: jsonschema
74
+ requirement: &2152407460 !ruby/object:Gem::Requirement
77
75
  none: false
78
- requirements:
76
+ requirements:
79
77
  - - ~>
80
- - !ruby/object:Gem::Version
78
+ - !ruby/object:Gem::Version
81
79
  version: 2.0.0
82
80
  type: :development
83
- version_requirements: *id006
84
- - !ruby/object:Gem::Dependency
85
- name: rspec
86
81
  prerelease: false
87
- requirement: &id007 !ruby/object:Gem::Requirement
82
+ version_requirements: *2152407460
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: &2152407000 !ruby/object:Gem::Requirement
88
86
  none: false
89
- requirements:
87
+ requirements:
90
88
  - - ~>
91
- - !ruby/object:Gem::Version
89
+ - !ruby/object:Gem::Version
92
90
  version: 2.5.0
93
91
  type: :development
94
- version_requirements: *id007
95
- - !ruby/object:Gem::Dependency
96
- name: awesome_print
97
92
  prerelease: false
98
- requirement: &id008 !ruby/object:Gem::Requirement
93
+ version_requirements: *2152407000
94
+ - !ruby/object:Gem::Dependency
95
+ name: awesome_print
96
+ requirement: &2152406540 !ruby/object:Gem::Requirement
99
97
  none: false
100
- requirements:
98
+ requirements:
101
99
  - - ~>
102
- - !ruby/object:Gem::Version
103
- version: "0.3"
100
+ - !ruby/object:Gem::Version
101
+ version: '0.3'
104
102
  type: :development
105
- version_requirements: *id008
106
- - !ruby/object:Gem::Dependency
107
- name: roo
108
103
  prerelease: false
109
- requirement: &id009 !ruby/object:Gem::Requirement
104
+ version_requirements: *2152406540
105
+ - !ruby/object:Gem::Dependency
106
+ name: roo
107
+ requirement: &2152406080 !ruby/object:Gem::Requirement
110
108
  none: false
111
- requirements:
109
+ requirements:
112
110
  - - ~>
113
- - !ruby/object:Gem::Version
111
+ - !ruby/object:Gem::Version
114
112
  version: 1.9.3
115
113
  type: :development
116
- version_requirements: *id009
117
- - !ruby/object:Gem::Dependency
118
- name: builder
119
114
  prerelease: false
120
- requirement: &id010 !ruby/object:Gem::Requirement
115
+ version_requirements: *2152406080
116
+ - !ruby/object:Gem::Dependency
117
+ name: builder
118
+ requirement: &2152405620 !ruby/object:Gem::Requirement
121
119
  none: false
122
- requirements:
120
+ requirements:
123
121
  - - ~>
124
- - !ruby/object:Gem::Version
122
+ - !ruby/object:Gem::Version
125
123
  version: 3.0.0
126
124
  type: :development
127
- version_requirements: *id010
128
- - !ruby/object:Gem::Dependency
129
- name: spreadsheet
130
125
  prerelease: false
131
- requirement: &id011 !ruby/object:Gem::Requirement
126
+ version_requirements: *2152405620
127
+ - !ruby/object:Gem::Dependency
128
+ name: spreadsheet
129
+ requirement: &2152405160 !ruby/object:Gem::Requirement
132
130
  none: false
133
- requirements:
131
+ requirements:
134
132
  - - ~>
135
- - !ruby/object:Gem::Version
133
+ - !ruby/object:Gem::Version
136
134
  version: 0.6.5.2
137
135
  type: :development
138
- version_requirements: *id011
139
- - !ruby/object:Gem::Dependency
140
- name: google-spreadsheet-ruby
141
136
  prerelease: false
142
- requirement: &id012 !ruby/object:Gem::Requirement
137
+ version_requirements: *2152405160
138
+ - !ruby/object:Gem::Dependency
139
+ name: google-spreadsheet-ruby
140
+ requirement: &2152404700 !ruby/object:Gem::Requirement
143
141
  none: false
144
- requirements:
142
+ requirements:
145
143
  - - ~>
146
- - !ruby/object:Gem::Version
144
+ - !ruby/object:Gem::Version
147
145
  version: 0.1.2
148
146
  type: :development
149
- version_requirements: *id012
150
- description: A library for extracting quality measure information from HITSP C32's and ASTM CCR's
147
+ prerelease: false
148
+ version_requirements: *2152404700
149
+ description: A library for extracting quality measure information from HITSP C32's
150
+ and ASTM CCR's
151
151
  email: talk@projectpophealth.org
152
152
  executables: []
153
-
154
153
  extensions: []
155
-
156
154
  extra_rdoc_files: []
157
-
158
- files:
155
+ files:
159
156
  - lib/qme/database_access.rb
160
157
  - lib/qme/importer/code_system_helper.rb
161
158
  - lib/qme/importer/entry.rb
@@ -173,6 +170,7 @@ files:
173
170
  - lib/qme/measure/properties_converter.rb
174
171
  - lib/qme/quality_measure.rb
175
172
  - lib/qme/quality_report.rb
173
+ - lib/qme/randomizer/patient_randomization_job.rb
176
174
  - lib/qme/randomizer/patient_randomizer.rb
177
175
  - lib/qme_test.rb
178
176
  - lib/quality-measure-engine.rb
@@ -189,30 +187,27 @@ files:
189
187
  has_rdoc: true
190
188
  homepage: http://github.com/pophealth/quality-measure-engine
191
189
  licenses: []
192
-
193
190
  post_install_message:
194
191
  rdoc_options: []
195
-
196
- require_paths:
192
+ require_paths:
197
193
  - lib
198
- required_ruby_version: !ruby/object:Gem::Requirement
194
+ required_ruby_version: !ruby/object:Gem::Requirement
199
195
  none: false
200
- requirements:
201
- - - ">="
202
- - !ruby/object:Gem::Version
203
- version: "0"
204
- required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ! '>='
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ required_rubygems_version: !ruby/object:Gem::Requirement
205
201
  none: false
206
- requirements:
207
- - - ">="
208
- - !ruby/object:Gem::Version
209
- version: "0"
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
210
206
  requirements: []
211
-
212
207
  rubyforge_project:
213
- rubygems_version: 1.5.0
208
+ rubygems_version: 1.6.2
214
209
  signing_key:
215
210
  specification_version: 3
216
- summary: A library for extracting quality measure information from HITSP C32's and ASTM CCR's
211
+ summary: A library for extracting quality measure information from HITSP C32's and
212
+ ASTM CCR's
217
213
  test_files: []
218
-