class CQM::Converter::QDMPatient

CQM Converter class for QDM based patients.

Public Class Methods

new() click to toggle source

Initialize a new QDMPatient converter. NOTE: This should be done once, and then used for every QDM Patient you want to convert, since it takes a few seconds to initialize the conversion environment.

# File lib/cqm/converter/qdm_patient.rb, line 8
def initialize
  @qdm_model_attrs = Utils.gather_qdm_model_attrs
  @qdm_to_hds_mappings = Utils.gather_qdm_to_hds_mappings(@qdm_model_attrs)
end

Public Instance Methods

to_hds(patient) click to toggle source

Given a QDM patient, return a corresponding HDS record.

# File lib/cqm/converter/qdm_patient.rb, line 14
def to_hds(patient)
  # Start with a new HDS record.
  record = Record.new

  # Loop over the QDM Patient's data elements, and create the corresponding
  # HDS Record Entry models on the newly created record.
  patient.dataElements.each do |data_element|
    category = data_element.qdmCategory if data_element.fields.include? 'qdmCategory'
    next unless category
    # Handle patient characteristics seperately.
    next if data_element.qdmCategory == 'patient_characteristic'

    # Grab the QDM datatype name of this data element.
    qdm_model_name = data_element.class.name.demodulize

    # Handle mismatched HDS names for QDM things.
    category = Utils.qdm_to_hds_class_type(category)

    # Grab the corresponding HDS model we will cast this QDM model into.
    hds_model = category.camelize.constantize

    # Start with a new HDS entry.
    hds_entry = hds_model.new

    # Populate codes.
    hds_entry.codes = Utils.qdm_codes_to_hds_codes(data_element.dataElementCodes)

    # Populate OID.
    hds_entry.oid = data_element.hqmfOid

    # Populate description.
    hds_entry.description = data_element.description

    # Set type.
    hds_entry.set(_type: hds_model.to_s)

    # Set status_code.
    status = data_element.fields.include?('qdmStatus') ? data_element[:qdmStatus] : nil
    status = 'ordered' if status == 'order'
    hds_entry.status_code = { 'HL7 ActStatus': [status] }

    # Grab the QDM attribute mappings for this data element, and construct the
    # corresponding HDS attributes.
    hds_attrs = {}
    @qdm_to_hds_mappings[qdm_model_name].each do |qdm_attr, hds_attr|
      next if data_element[qdm_attr].nil?

      extracted_value = extractor(data_element[qdm_attr].as_json)
      if hds_attr.is_a?(Hash) && hds_attr[:low]
        # Handle something that has multiple parts.
        hds_attrs[hds_attr[:low]] = extracted_value.first
        hds_attrs[hds_attr[:high]] = extracted_value.last if hds_attr[:high]
      elsif extracted_value.is_a?(Array) && extracted_value.any? && extracted_value.first.is_a?(Hash) && extracted_value.first[:code] && hds_attr != 'facility' && hds_attr != 'components'
        # Handle a result that is returning multiple codes.
        hds_attrs[hds_attr] = [CodedResultValue.new(codes: Utils.qdm_codes_to_hds_codes(extracted_value), description: extracted_value.first[:title])]
      elsif extracted_value.is_a?(Array)
        # Handle simple Arrays.
        hds_attrs[hds_attr] = { values: extracted_value } if extracted_value.any?
      elsif hds_attr == 'values' && extracted_value.key?(:scalar)
        # Handle a Quantity.
        hds_attrs[hds_attr] = [PhysicalQuantityResultValue.new(extracted_value)]
      elsif hds_attr == 'values' && extracted_value.key?(:code)
        # Handle a Code result.
        hds_attrs[hds_attr] = [CodedResultValue.new(codes: Utils.qdm_codes_to_hds_codes([extracted_value]), description: extracted_value[:title])]
      elsif hds_attr == 'dose'
        # Handle a dosage.
        hds_attrs[hds_attr] = dose_extractor(extracted_value)
      else
        # Nothing special.
        hds_attrs[hds_attr] = extracted_value
      end
    end

    # If there is an actual negationReason, set negationInd to true.
    if hds_attrs.key?('negationReason') && !hds_attrs['negationReason'].nil?
      hds_attrs['negationInd'] = true
    end

    # Unpack references.
    unpack_references(hds_attrs)

    # Unpack facility.
    unpack_facility(hds_attrs)

    # Unpack diagnosis.
    unpack_diagnosis(hds_attrs)

    # Unpack components.
    unpack_components(hds_attrs)

    # Communication entries need direction, which we can get from the QDM model name.
    if hds_entry._type == 'Communication'
      hds_attrs['direction'] = qdm_model_name.underscore
    end

    # Ignore infinity dates.
    Utils.fix_infinity_dates(hds_attrs)

    # Apply the attributes to the entry.
    hds_entry.set(hds_attrs)

    # Add entry to HDS record, make sure to handle tricky plural types.
    plural_category = ['medical_equipment'].include?(category) ? category : category.pluralize
    record.send(plural_category) << hds_entry unless hds_entry.codes.empty?
  end

  # Unpack patient characteristics.
  unpack_patient_characteristics(patient, record)

  # Unpack extended_data.
  unpack_extended_data(patient, record)

  record
end

Private Instance Methods

code_extractor(code) click to toggle source

Extract a QDM::Code to something usable in HDS.

# File lib/cqm/converter/qdm_patient.rb, line 174
def code_extractor(code)
  { code: code[:code], code_system: code[:codeSystem], title: code[:descriptor] }
end
component_extractor(component) click to toggle source
# File lib/cqm/converter/qdm_patient.rb, line 169
def component_extractor(component)
  { code: code_extractor(component[:code].symbolize_keys), result: code_extractor(component[:result].symbolize_keys) }
end
date_time?(date_time) click to toggle source

Check if the given string is a DateTime.

# File lib/cqm/converter/qdm_patient.rb, line 222
def date_time?(date_time)
  true if DateTime.parse(date_time)
rescue ArgumentError
  false
end
date_time_converter(date_time) click to toggle source

Convert a DateTime to something usable in HDS.

# File lib/cqm/converter/qdm_patient.rb, line 196
def date_time_converter(date_time)
  date_time = DateTime.parse(date_time) if date_time.class.to_s == 'String'
  date_time.to_i
end
dose_extractor(dose) click to toggle source

Extract a Dose to something usable in HDS.

# File lib/cqm/converter/qdm_patient.rb, line 191
def dose_extractor(dose)
  { unit: dose[:units], value: dose[:scalar] }
end
extractor(qdm_thing) click to toggle source

Given something QDM model based, return a corresponding HDS representation. This will operate recursively.

# File lib/cqm/converter/qdm_patient.rb, line 133
def extractor(qdm_thing)
  keys = qdm_thing.symbolize_keys.keys if qdm_thing.class.to_s == 'Hash'
  if qdm_thing.nil? # Is nothing.
    nil
  elsif ['Time', 'DateTime', 'Date'].include? qdm_thing.class.to_s # Is a DateTime.
    date_time_converter(qdm_thing)
  elsif qdm_thing.is_a?(String) && date_time?(qdm_thing) # Is a DateTime.
    date_time_converter(qdm_thing)
  elsif qdm_thing.is_a?(Hash) && keys.include?(:low) # Is a QDM::Interval.
    interval_extractor(qdm_thing.symbolize_keys)
  elsif qdm_thing.is_a?(Hash) && keys.include?(:locationPeriod)
    location_extractor(qdm_thing.symbolize_keys)
  elsif qdm_thing.is_a?(Hash) && keys.include?(:result) # is a QDM::CodedResultValue
    component_extractor(qdm_thing.symbolize_keys)
  elsif qdm_thing.is_a?(Hash) && keys.include?(:code) # Is a QDM::Code.
    code_extractor(qdm_thing.symbolize_keys)
  elsif qdm_thing.is_a?(Hash) && keys.include?(:unit) # Is a QDM::Quantity.
    quantity_extractor(qdm_thing.symbolize_keys)
  elsif qdm_thing.is_a?(Array) # Is an Array.
    qdm_thing.collect { |item| extractor(item) }
  elsif qdm_thing.is_a?(Numeric) # Is a Number.
    { units: '', scalar: qdm_thing.to_s }
  elsif qdm_thing.is_a?(String) # Is a String.
    qdm_thing
  elsif qdm_thing.is_a?(Hash)
    qdm_thing.each { |k, v| qdm_thing[k] = extractor(v) }
  else
    raise 'Unsupported type! Found: ' + qdm_thing.class.to_s
  end
end
get_data_elements(patient, category, status = nil) click to toggle source

Grab the data elements on the patient. This should only be used when there is no active Mongo connection.

# File lib/cqm/converter/qdm_patient.rb, line 203
def get_data_elements(patient, category, status = nil)
  matches = []
  patient.dataElements.each do |data_element|
    matches << data_element if data_element[:qdmCategory] == category && (data_element[:qdmStatus] == status || status.nil?)
  end
  matches
end
get_data_elements_by_type(patient, type) click to toggle source

Grab the data elements on the patient by _type. This should only be used when there is no active Mongo connection.

# File lib/cqm/converter/qdm_patient.rb, line 213
def get_data_elements_by_type(patient, type)
  matches = []
  patient.dataElements.each do |data_element|
    matches << data_element if data_element[:_type] == type
  end
  matches
end
interval_extractor(interval) click to toggle source

Extract a QDM::Interval to something usable in HDS.

# File lib/cqm/converter/qdm_patient.rb, line 179
def interval_extractor(interval)
  # If this interval has a high of inifinity, nil it out.
  interval[:high] = nil if interval[:high] == '9999-12-31T23:59:59.99+0000'
  [extractor(interval[:low]), extractor(interval[:high])]
end
location_extractor(facility) click to toggle source
# File lib/cqm/converter/qdm_patient.rb, line 164
def location_extractor(facility)
  interval = interval_extractor(facility[:locationPeriod].as_json.symbolize_keys)
  { 'code' => code_extractor(facility[:code].as_json.symbolize_keys), 'locationPeriod' => interval }
end
quantity_extractor(quantity) click to toggle source

Extract a QDM::Quantity to something usable in HDS.

# File lib/cqm/converter/qdm_patient.rb, line 186
def quantity_extractor(quantity)
  { units: quantity[:unit], scalar: quantity[:value].to_s }
end
unpack_components(hds_attrs) click to toggle source

Unpack components.

# File lib/cqm/converter/qdm_patient.rb, line 229
def unpack_components(hds_attrs)
  return unless hds_attrs.key?('components') && !hds_attrs['components'].nil?

  hds_attrs['components']['type'] = 'COL'
  hds_attrs['components'][:values]&.collect do |code_value|
    code_value[:code] = code_value.delete('Code') if code_value.key?('Code')
    code_value[:result] = if code_value.key?('Result')
                            { code: code_value.delete('Result'), title: code_value['code'][:title] }
                          else
                            { code: code_value.delete(:result), title: code_value[:code][:title] }
                          end
    code_value[:code].delete(:title)
    code_value[:result][:code].delete(:title)
    { code: code_value }
  end
end
unpack_diagnosis(hds_attrs) click to toggle source

Unpack diagnosis.

# File lib/cqm/converter/qdm_patient.rb, line 247
def unpack_diagnosis(hds_attrs)
  if hds_attrs.key?('diagnosis') && !hds_attrs['diagnosis'].empty?
    unpacked = {}
    unpacked['type'] = 'COL'
    unpacked['values'] = hds_attrs['diagnosis'].collect do |diag|
      code = Utils.hds_codes_to_qdm_codes(diag.codes).first
      {
        code_system: code[:codeSystem],
        code: code[:code],
        title: diag.description
      }
    end
    hds_attrs['diagnosis'] = unpacked
  end
  # Remove diagnosis if principalDiagnosis is equivalent.
  return unless hds_attrs.key?('diagnosis') && hds_attrs.key?('principalDiagnosis')
  return unless hds_attrs['diagnosis']['values'] && Hash[hds_attrs['diagnosis']['values'].first.sort] == Hash[hds_attrs['principalDiagnosis'].sort]

  hds_attrs.delete('diagnosis')
end
unpack_extended_data(patient, record) click to toggle source

Unpack extended data.

# File lib/cqm/converter/qdm_patient.rb, line 328
def unpack_extended_data(patient, record)
  record['type'] = patient.extendedData['type'] if patient.extendedData['type']
  record['measure_ids'] = patient.extendedData['measure_ids'] if patient.extendedData['measure_ids']
  record['source_data_criteria'] = patient.extendedData['source_data_criteria'] if patient.extendedData['source_data_criteria']
  record['expected_values'] = patient.extendedData['expected_values'] if patient.extendedData['expected_values'].is_a?(Array)
  record['notes'] = patient.extendedData['notes'] if patient.extendedData['notes']
  record['is_shared'] = patient.extendedData['is_shared'] if patient.extendedData['is_shared']
  record['origin_data'] = patient.extendedData['origin_data'] if patient.extendedData['origin_data']
  record['test_id'] = patient.extendedData['test_id'] if patient.extendedData['test_id']
  record['medical_record_number'] = patient.extendedData['medical_record_number'] if patient.extendedData['medical_record_number']
  record['medical_record_assigner'] = patient.extendedData['medical_record_assigner'] if patient.extendedData['medical_record_assigner']
  record['description'] = patient.extendedData['description'] if patient.extendedData['description']
  record['description_category'] = patient.extendedData['description_category'] if patient.extendedData['description_category']
  if patient.extendedData['provider_performances']
    providers = JSON.parse(patient.extendedData['provider_performances']).collect do |provider|
      ProviderPerformance.new.from_json(provider.to_json)
    end
    record['provider_performances'] = providers
  end
  insurance_providers = JSON.parse(patient.extendedData['insurance_providers']).collect do |insurance_provider|
    InsuranceProvider.new.from_json(insurance_provider.to_json)
  end
  record['insurance_providers'] = insurance_providers
end
unpack_facility(hds_attrs) click to toggle source

Unpack facility.

# File lib/cqm/converter/qdm_patient.rb, line 269
def unpack_facility(hds_attrs)
  return unless hds_attrs.key?('facility') && !hds_attrs['facility'].empty?

  hds_attrs['facility']['type'] = 'COL'
  hds_attrs['facility'][:values]&.each do |value|
    value['code'] = value.delete('Code') if value['Code']
    value[:display] = value['code'].delete(:title) if value['code']
    value[:locationPeriodHigh] = Time.at(value['locationPeriod'].last).utc.strftime('%m/%d/%Y %l:%M %p').split.join(' ')
    value[:locationPeriodLow] = Time.at(value['locationPeriod'].first).utc.strftime('%m/%d/%Y %l:%M %p').split.join(' ')
    value.delete('locationPeriod')
  end
end
unpack_patient_characteristics(patient, record) click to toggle source

Unpack patient characteristics.

# File lib/cqm/converter/qdm_patient.rb, line 290
def unpack_patient_characteristics(patient, record)
  # Convert patient characteristic birthdate.
  birthdate = get_data_elements(patient, 'patient_characteristic', 'birthdate').first
  record.birthdate = birthdate.birthDatetime if birthdate

  # Convert patient characteristic clinical trial participant.
  # TODO, Adam 4/9: The Bonnie team is working on implementing this in HDS. When that work
  # is complete, this should be updated to reflect how that looks in HDS.
  # clinical_trial_participant = get_data_elements(patient, 'patient_characteristic', 'clinical_trial_participant').first

  # Convert patient characteristic ethnicity.
  ethnicity = get_data_elements(patient, 'patient_characteristic', 'ethnicity').first
  ethnicity_code = ethnicity.dataElementCodes.first.symbolize_keys if ethnicity.dataElementCodes.any?
  record.ethnicity = { code: ethnicity_code[:code], name: ethnicity_code[:descriptor], codeSystem: 'CDC Race' } if ethnicity_code

  # Convert patient characteristic expired.
  expired = get_data_elements_by_type(patient, 'QDM::PatientCharacteristicExpired').first
  record.deathdate = date_time_converter(expired.expiredDatetime) if expired
  record.expired = record.deathdate if record.deathdate

  # Convert patient characteristic race.
  race = get_data_elements(patient, 'patient_characteristic', 'race').first
  race_code = race.dataElementCodes.first.symbolize_keys if race.dataElementCodes.any?
  record.race = { code: race_code[:code], name: race_code[:descriptor], codeSystem: 'CDC Race' } if race_code

  # Convert patient characteristic sex.
  sex = get_data_elements_by_type(patient, 'QDM::PatientCharacteristicSex').first
  sex_code = sex.dataElementCodes.first.symbolize_keys if sex.dataElementCodes.any?
  record.gender = sex_code[:code] if sex

  # Convert remaining metadata.
  record.birthdate = date_time_converter(patient.birthDatetime) unless record.birthdate
  record.first = patient.givenNames.first if patient.givenNames.any?
  record.last = patient.familyName if patient.familyName
  record.bundle_id = patient.bundleId if patient.bundleId
end
unpack_references(hds_attrs) click to toggle source

Unpack references.

# File lib/cqm/converter/qdm_patient.rb, line 283
def unpack_references(hds_attrs)
  return unless hds_attrs.key?('references') && !hds_attrs['references'].empty?

  hds_attrs['references'] = hds_attrs['references'][:values].collect { |value| { referenced_id: value['value'], referenced_type: value['referencedType'], type: value['type'] } }
end