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:



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/tpg/generation/generator.rb', line 154

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



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/tpg/generation/generator.rb', line 215

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.



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

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:



95
96
97
98
99
100
101
102
103
# File 'lib/tpg/generation/generator.rb', line 95

def self.create_oid_dictionary(oids)
  value_sets = []
  HealthDataStandards::SVS::ValueSet.any_in(oid: oids).each do |value_set|
    code_sets = value_set.concepts.map { |concept| {"code_set" => concept.code_system_name, "codes" => [concept.code]} }
    value_sets << {"code_sets" => code_sets, "oid" => value_set.oid, "concept" => value_set.display_name}
  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.



179
180
181
182
183
184
185
186
# File 'lib/tpg/generation/generator.rb', line 179

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.



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

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

Returns:

  • (Hash)

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



8
9
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
# File 'lib/tpg/generation/generator.rb', line 8

def self.generate_qrda_patients(measure_needs)      
  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)
    
    # 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



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/tpg/generation/generator.rb', line 192

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



78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/tpg/generation/generator.rb', line 78

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|
    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.field_values == data_criteria.field_values && dc.status == data_criteria.status}
    unique_data_criteria << data_criteria if index.nil?
  end

  unique_data_criteria
end

.select_unique_oids(all_data_criteria) ⇒ Object

Parameters:

  • all_data_criteria (Array)

Returns:



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/tpg/generation/generator.rb', line 109

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.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/tpg/generation/generator.rb', line 132

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