Class: HQMF::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/tpg/generation/generator.rb

Class Method Summary collapse

Class Method Details

.apply_field_defaults(data_criteria, time) ⇒ Object

Parameters:

Returns:



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/tpg/generation/generator.rb', line 165

def self.apply_field_defaults(data_criteria, time)
  return nil if data_criteria.field_values.nil?

  # Some fields come in with no value or marked as AnyValue (i.e. any value is acceptable, there just must be one). If that's the case, we pick a default here.
  data_criteria.field_values.each do |name, field|
    if field.is_a? HQMF::AnyValue
      if ["ADMISSION_DATETIME", "START_DATETIME", "INCISION_DATETIME"].include? name
        data_criteria.field_values[name] = time.low
      elsif ["DISCHARGE_DATETIME", "STOP_DATETIME", "REMOVAL_DATETIME"].include? name
        data_criteria.field_values[name] = time.high
      elsif name == "REASON"
        # If we're not explicitly given a code (e.g. HQMF dictates there must be a reason but any is ok), we assign a random one (birth)
        data_criteria.field_values[name] = Coded.for_code_list("2.16.840.1.113883.3.117.1.7.1.70", "birth")
      elsif name == "ORDINAL"
        # If we're not explicitly given a code (e.g. HQMF dictates there must be a reason but any is ok), we assign it to be not principle
        data_criteria.field_values[name] = Coded.for_code_list("2.16.840.1.113883.3.117.2.7.1.14", "principle")
      end
    end
  end
end

.classify_entry(type) ⇒ Object

Map all patient api coded entry types from HQMF data criteria to Record sections.

Parameters:

  • type (String)

    The type of the coded entry requried by a data criteria.

Returns:

  • The section type for the given patient api function type



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/tpg/generation/generator.rb', line 226

def self.classify_entry(type)
  # The possible matches per patientAPI function can be found in hqmf-parser's README
  case type
  when :allProcedures
    "procedures"
  when :proceduresPerformed
    "procedures"
  when :procedureResults
    "procedures"
  when :laboratoryTests
    "vital_signs"
  when :allMedications
    "medications"
  when :activeDiagnoses
    "conditions"
  when :inactiveDiagnoses
    "conditions"
  when :resolvedDiagnoses
    "conditions"
  when :allProblems
    "conditions"
  when :allDevices
    "medical_equipment"
  else
    type.to_s
  end
end

.create_base_patient(initial_attributes = nil) ⇒ Object

Create a patient with trivial demographic information and no coded entries.

Returns:

  • A Record with a blank slate.



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/tpg/generation/generator.rb', line 45

def self.create_base_patient(initial_attributes = nil)
  patient = Record.new
  
  if initial_attributes.nil?
    patient = Randomizer.randomize_demographics(patient)
  else
    initial_attributes.each {|attribute, value| patient.send("#{attribute}=", value)}
  end
  
  patient
end

.create_oid_dictionary(oids) ⇒ Object

Parameters:

  • oids (Array)

Returns:



107
108
109
110
111
112
113
114
# File 'lib/tpg/generation/generator.rb', line 107

def self.create_oid_dictionary(oids)
  value_sets = []
  HealthDataStandards::SVS::ValueSet.any_in(oid: oids).each do |value_set|
    code_sets = value_set.concepts
    value_sets << {"concepts" => code_sets, "oid" => value_set.oid}
  end
  value_sets
end

.determine_measure_needs(measures) ⇒ Object

Takes an Array of meassures and builds a Hash keyed by NQF ID with the values being an Array of data criteria.

Parameters:

  • measures (Array)

    A list of HQMF::Documents for which patients will be generated.

Returns:

  • A hash of measure IDs for which we’re generating patients, mapped to an array of HQMF::DataCriteria.



190
191
192
193
194
195
196
197
# File 'lib/tpg/generation/generator.rb', line 190

def self.determine_measure_needs(measures)
  measure_needs = {}
  measures.each do |measure|
    measure_needs[measure.id] = measure.all_data_criteria
  end

  measure_needs
end

.finalize_patient(patient) ⇒ Object

Fill in any missing details that should be filled in on a patient. These include: age, gender, and first name.

Parameters:

  • patient (Record)

    The patient for whom we are about to fill in remaining demographic information.

Returns:

  • A patient with guaranteed complete information necessary for standard formats.



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/tpg/generation/generator.rb', line 61

def self.finalize_patient(patient)
  if patient.birthdate.nil?
    patient.birthdate = Randomizer.randomize_birthdate(patient)
    patient.birthdate = Time.now.to_i
  end
  
  if patient.gender.nil?
    patient.gender = "F"
    patient.first ||= Randomizer.randomize_first_name(patient.gender)
  end
  
  patient
end

.generate_qrda_patients(measure_needs, value_sets = nil) ⇒ Hash

Generate patients from lists of DataCriteria. This is originally created for QRDA Category 1 validation testing, i.e. a single patient will be generated per measure with an entry for every data criteria involved in the measure.

Parameters:

  • measure_needs (Hash)

    A hash of measure IDs mapped to a list of all their data criteria in JSON.

  • Value (Hash)

    set hash to use for looking up codes for the data criteria passed in. If null will resort to original behavior of

Returns:

  • (Hash)

    A hash of measure IDs mapped to a Record that includes all the given data criteria (values and times are arbitrary).



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/tpg/generation/generator.rb', line 10

def self.generate_qrda_patients(measure_needs, value_sets=nil)      
  return {} if measure_needs.nil?
  
  measure_patients = {}
  measure_needs.each do |measure, all_data_criteria|
    # Define a list of unique data criteria and matching value sets to create a patient for this measure.
    unique_data_criteria = select_unique_data_criteria(all_data_criteria)
    oids = select_unique_oids(all_data_criteria)
    value_sets = create_oid_dictionary(oids) if value_sets.nil?
    
    # Create a patient that includes an entry for every data criteria included in this measure.
    patient = Generator.create_base_patient
    unique_data_criteria.each do |data_criteria|
      # Ignore data criteria that are really just containers.
      next if data_criteria.derivation_operator.present?

      # Prepare and apply our parameters for modifying the patient based on the data criteria.
      time = select_valid_time_range(patient, data_criteria)
      apply_field_defaults(data_criteria, time)
      data_criteria.modify_patient(patient, time, value_sets)
    end

    # Add final data for the patient, e.g. that they were designed for the measure, possibly a birthdate, etc.
    patient.measure_ids ||= []
    patient.measure_ids << measure
    patient.type = "qrda"
    measure_patients[measure] = Generator.finalize_patient(patient)
  end
  
  measure_patients
end

.parse_measure(measure_json) ⇒ Object

Parses a JSON representation of a measure from a Bonnie Bundle into an hqmf-parser ready format.

Parameters:

  • measure (Hash)

    JSON representation of a measure

Returns:

  • Tweaked JSON that has fields in the places hqmf-parser expects



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/tpg/generation/generator.rb', line 203

def self.parse_measure(measure_json)
  # HQMF Parser expects just a hash of ID => data_criteria, so translate to that format here.
  translated_data_criteria = {}
  measure_json["data_criteria"].each { |data_criteria| translated_data_criteria[data_criteria.keys.first] = data_criteria.values.first }
  measure_json["data_criteria"] = translated_data_criteria
  
  # HQMF::Documents have fields for hqmf_id and id, but not NQF ID. We'll store NQF_ID in ID.
  measure_json["id"] = measure_json["nqf_id"]
  measure_json["source_data_criteria"] = []

  measure = HQMF::Document.from_json(measure_json)
  measure.all_data_criteria.each do |data_criteria|
    data_criteria.values ||= []
    data_criteria.values << data_criteria.value if data_criteria.value && data_criteria.value.type != "ANYNonNull"
  end

  measure
end

.select_unique_data_criteria(all_data_criteria) ⇒ Object

Select all unique data criteria from a list. Category 1 validation is only checking for ability to access information so to minimize time we only want to include each kind of data once.

Parameters:

  • all_data_criteria (Array)

    A list of HQMF::DataCriteria to be sifted through.

Returns:

  • The unique list of data criteria extracted from all_data_criteria



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/tpg/generation/generator.rb', line 80

def self.select_unique_data_criteria(all_data_criteria)
  all_data_criteria.flatten!
  all_data_criteria.uniq!
  
  unique_data_criteria = []
  all_data_criteria.each do |data_criteria|

    fields1 = data_criteria.field_values || {}

    index = unique_data_criteria.index {|dc| dc.code_list_id == data_criteria.code_list_id && dc.negation_code_list_id == data_criteria.negation_code_list_id && dc.status == data_criteria.status && dc.definition == data_criteria.definition}
   
    if index
      crit = unique_data_criteria[index]
      crit.field_values ||= {}
      crit.field_values.merge! data_criteria.field_values || {} 
    else
     unique_data_criteria << data_criteria 
    end
  end

  unique_data_criteria
end

.select_unique_oids(all_data_criteria) ⇒ Object

Parameters:

  • all_data_criteria (Array)

Returns:



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/tpg/generation/generator.rb', line 120

def self.select_unique_oids(all_data_criteria)
  oids = []
  all_data_criteria.each do |dc|
    oids << dc.code_list_id if dc.code_list_id.present?
    oids << dc.negation_code_list_id if dc.negation_code_list_id.present?
    oids << dc.value.code_list_id if dc.value.present? && dc.value.type == "CD"

    dc.field_values.each {|name, field| oids << field.code_list_id if field.present? && field.type == "CD"} if dc.field_values.present?
  end

  oids << "2.16.840.1.113883.3.117.1.7.1.70"
  oids << "2.16.840.1.113883.3.117.2.7.1.14"

  oids.flatten!
  oids.uniq!
  oids.compact
end

.select_valid_time_range(patient, data_criteria) ⇒ Object

Create a random time range for an entry to occur. It is guaranteed to be within the lifespan of the patient and will last no longer than a day.

Parameters:

  • patient (Record)

    The patient for whom this range is being generated.

  • data_criteria (HQMF::DataCriteria)

    The data criteria for which we’re creating an entry.

Returns:

  • A time range that can be used to create an entry for this data criteria.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/tpg/generation/generator.rb', line 143

def self.select_valid_time_range(patient, data_criteria)
  earliest_time = patient.birthdate
  latest_time = patient.deathdate

  # Make sure all ranges occur within the bounds of birth and death. If this data criteria is deciding one of those two, place this range outside of our 35 year range for entries.
  if data_criteria.property.present?
    if data_criteria.property == :birthtime
      earliest_time = HQMF::Randomizer.randomize_birthdate(patient)
      latest_time = earliest_time.advance(days: 1)
    elsif data_criteria.property == :expired
      earliest_time = Time.now
      latest_time = earliest_time.advance(days: 1)
    end
  end

  time = Randomizer.randomize_range(earliest_time, latest_time, {days: 1})
end