Class: MPI::Responses::ProfileParser

Inherits:
ParserBase show all
Includes:
Identity::Parsers::GCIds, SentryLogging
Defined in:
lib/mpi/responses/profile_parser.rb

Overview

Parses a MVI response and returns a MviProfile

Constant Summary collapse

BODY_XPATH =
'env:Envelope/env:Body/idm:PRPA_IN201306UV02'
CODE_XPATH =
'acknowledgement/typeCode/@code'
QUERY_XPATH =
'controlActProcess/queryByParameter'
SSN_ROOT_ID =
'2.16.840.1.113883.4.1'
SUBJECT_XPATH =
'controlActProcess/subject'
PATIENT_XPATH =
'registrationEvent/subject1/patient'
STATUS_XPATH =
'statusCode/@code'
CONFIDENTIALITY_CODE_XPATH =
'confidentialityCode/@code'
ID_THEFT_INDICATOR =
'ID_THEFT^TRUE'
PATIENT_PERSON_PREFIX =
'patientPerson/'
RELATIONSHIP_PREFIX =
'relationshipHolder1/'
GENDER_XPATH =
'administrativeGenderCode/@code'
DOB_XPATH =
'birthTime/@value'
SSN_XPATH =
'asOtherIDs'
NAME_XPATH =
'name'
'L'
NAME_PREFERRED_INDICATOR =
'ASGN'
ADDRESS_XPATH =
'addr'
DECEASED_XPATH =
'deceasedTime/@value'
PHONE =
'telecom'
PERSON_TYPE =
'PERSON_TYPE'
RELATIONSHIP_PERSON_TYPE =
'RoleCode'
PERSON_TYPE_SEPARATOR =
'~'
PERSON_TYPE_VALUE_XPATH =
'value/@code'
PERSON_TYPE_CODE_XPATH =
'code/@code'
RELATIONSHIP_PERSON_TYPE_VALUE_XPATH =
'code/@code'
RELATIONSHIP_PERSON_TYPE_CODE_XPATH =
'code/@codeSystemName'
ADMIN_OBSERVATION_XPATH =
'*/administrativeObservation'
ACKNOWLEDGEMENT_DETAIL_XPATH =
'acknowledgement/acknowledgementDetail/text'
ACKNOWLEDGEMENT_TARGET_MESSAGE_ID_EXTENSION_XPATH =
'acknowledgement/targetMessage/id/@extension'
MULTIPLE_MATCHES_FOUND =
'Multiple Matches Found'
PATIENT_RELATIONSHIP_XPATH =
'patientPerson/personalRelationship'

Constants included from Identity::Parsers::GCIdsConstants

Identity::Parsers::GCIdsConstants::ACTIVE_MHV_IDS_REGEX, Identity::Parsers::GCIdsConstants::BIRLS_IDS_REGEX, Identity::Parsers::GCIdsConstants::CERNER_FACILITY_IDS_REGEX, Identity::Parsers::GCIdsConstants::CERNER_ID_REGEX, Identity::Parsers::GCIdsConstants::DOD_ROOT_OID, Identity::Parsers::GCIdsConstants::EDIPI_REGEX, Identity::Parsers::GCIdsConstants::ICN_ASSIGNING_AUTHORITY_ID, Identity::Parsers::GCIdsConstants::ICN_REGEX, Identity::Parsers::GCIdsConstants::IDENTIFIERS_SPLIT_TOKEN, Identity::Parsers::GCIdsConstants::IDME_ID_REGEX, Identity::Parsers::GCIdsConstants::IDS_SPLIT_TOKEN, Identity::Parsers::GCIdsConstants::ID_MAPPINGS, Identity::Parsers::GCIdsConstants::LOGINGOV_ID_REGEX, Identity::Parsers::GCIdsConstants::MHV_IDS_REGEX, Identity::Parsers::GCIdsConstants::MHV_IEN_REGEX, Identity::Parsers::GCIdsConstants::PERMANENT_ICN_REGEX, Identity::Parsers::GCIdsConstants::SEC_ID_REGEX, Identity::Parsers::GCIdsConstants::VA_ROOT_OID, Identity::Parsers::GCIdsConstants::VBA_CORP_ID_REGEX, Identity::Parsers::GCIdsConstants::VET360_ID_REGEX, Identity::Parsers::GCIdsConstants::VHA_FACILITY_IDS_REGEX

Constants inherited from ParserBase

MPI::Responses::ParserBase::EXTERNAL_RESPONSE_CODES

Instance Method Summary collapse

Methods included from Identity::Parsers::GCIds

#build_hash, #parse_string_gcids, #parse_xml_gcids, #select, #select_extension, #select_icn_with_aaid, #select_token_position

Methods included from Identity::Parsers::GCIdsHelper

#sanitize_edipi, #sanitize_id, #sanitize_id_array

Methods included from SentryLogging

#log_exception_to_sentry, #log_message_to_sentry, #non_nil_hash?, #normalize_level, #rails_logger, #set_sentry_metadata

Methods inherited from ParserBase

#failed_or_invalid?, #failed_request?, #invalid_request?, #locate_element, #locate_elements, #unknown_error?

Constructor Details

#initialize(response) ⇒ ProfileParser

Returns a new instance of ProfileParser.



54
55
56
57
58
# File 'lib/mpi/responses/profile_parser.rb', line 54

def initialize(response)
  @transaction_id = response.response_headers['x-global-transaction-id']
  @original_body = locate_element(response.body, BODY_XPATH)
  @code = locate_element(@original_body, CODE_XPATH)
end

Instance Method Details

#build_mpi_profile(patient) ⇒ Object (private)



102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/mpi/responses/profile_parser.rb', line 102

def build_mpi_profile(patient)
  profile_identity_hash = create_mpi_profile_identity(patient)
  profile_ids_hash = create_mpi_profile_ids(patient)
  misc_hash = {
    search_token: locate_element(@original_body, 'id').attributes[:extension],
    relationships: parse_relationships(patient.locate(PATIENT_RELATIONSHIP_XPATH)),
    id_theft_flag: parse_id_theft_flag(patient),
    transaction_id: @transaction_id
  }
  mpi_attribute_validations(profile_identity_hash, profile_ids_hash)
  log_mpi_relationships(misc_hash[:relationships]) if misc_hash[:relationships].present?

  MPI::Models::MviProfile.new(profile_identity_hash.merge(profile_ids_hash).merge(misc_hash))
end

#build_relationship_mpi_profile(relationship) ⇒ Object (private)



121
122
123
124
125
# File 'lib/mpi/responses/profile_parser.rb', line 121

def build_relationship_mpi_profile(relationship)
  relationship_identity_hash = create_mpi_profile_relationship_identity(relationship)
  relationship_ids_hash = create_mpi_profile_ids(locate_element(relationship, RELATIONSHIP_PREFIX))
  MPI::Models::MviProfileRelationship.new(relationship_identity_hash.merge(relationship_ids_hash))
end

#create_ids_obj(full_mvi_ids, parsed_mvi_ids) ⇒ Object (private)



169
170
171
172
173
# File 'lib/mpi/responses/profile_parser.rb', line 169

def create_ids_obj(full_mvi_ids, parsed_mvi_ids)
  {
    full_mvi_ids:
  }.merge(parse_single_ids(parsed_mvi_ids).merge(parse_multiple_ids(parsed_mvi_ids)))
end

#create_mpi_profile_identity(person) ⇒ Object (private)



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/mpi/responses/profile_parser.rb', line 143

def create_mpi_profile_identity(person)
  person_component = locate_element(person, PATIENT_PERSON_PREFIX)
  person_types = parse_person_type(person)
  name = parse_name(locate_elements(person_component, NAME_XPATH))
  preferred_names = parse_name(locate_elements(person_component, NAME_XPATH), indicator: NAME_PREFERRED_INDICATOR)
  {
    given_names: name[:given],
    family_name: name[:family],
    suffix: name[:suffix],
    preferred_names: preferred_names[:given],
    gender: locate_element(person_component, GENDER_XPATH),
    birth_date: locate_element(person_component, DOB_XPATH),
    deceased_date: locate_element(person_component, DECEASED_XPATH),
    ssn: parse_ssn(locate_element(person_component, SSN_XPATH)),
    address: parse_address(person_component),
    home_phone: parse_phone(person, PATIENT_PERSON_PREFIX),
    person_types:
  }
end

#create_mpi_profile_ids(patient) ⇒ Object (private)



163
164
165
166
167
# File 'lib/mpi/responses/profile_parser.rb', line 163

def create_mpi_profile_ids(patient)
  full_mvi_ids = get_extensions(patient.locate('id'))
  parsed_mvi_ids = parse_xml_gcids(patient.locate('id'))
  create_ids_obj(full_mvi_ids, parsed_mvi_ids)
end

#create_mpi_profile_relationship_identity(relationship) ⇒ Object (private)



132
133
134
135
136
137
138
139
140
141
# File 'lib/mpi/responses/profile_parser.rb', line 132

def create_mpi_profile_relationship_identity(relationship)
  person_component = locate_element(relationship, RELATIONSHIP_PREFIX)
  person_types = parse_relationship_person_type(relationship)
  name = parse_relationship_name(locate_elements(person_component, NAME_XPATH))
  {
    given_names: name[:given],
    family_name: name[:family],
    person_types:
  }
end

#error_detailsObject



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/mpi/responses/profile_parser.rb', line 81

def error_details
  error_details = {
    ack_detail_code: @code,
    id_extension: locate_element(@original_body, ACKNOWLEDGEMENT_TARGET_MESSAGE_ID_EXTENSION_XPATH),
    transaction_id: @transaction_id,
    error_texts: []
  }
  error_text_nodes = locate_elements(@original_body, ACKNOWLEDGEMENT_DETAIL_XPATH)
  if error_text_nodes.nil?
    error_details[:error_texts] = error_text_nodes
  else
    error_text_nodes.each do |node|
      error_text = node.text || node&.nodes&.first&.value
      error_details[:error_texts].append(error_text) unless error_details[:error_texts].include?(error_text)
    end
  end
  { error_details: }
end

#get_extensions(id_array) ⇒ Object (private)



204
205
206
207
208
# File 'lib/mpi/responses/profile_parser.rb', line 204

def get_extensions(id_array)
  id_array.map do |id_object|
    id_object.attributes[:extension]
  end
end

#log_inactive_mhv_ids(mhv_ids, active_mhv_ids) ⇒ Object (private)



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/mpi/responses/profile_parser.rb', line 220

def log_inactive_mhv_ids(mhv_ids, active_mhv_ids)
  return if mhv_ids.blank?

  if (mhv_ids - active_mhv_ids).present?
    log_message_to_sentry('Inactive MHV correlation IDs present', :info,
                          ids: mhv_ids)
  end
  unless active_mhv_ids.include?(mhv_ids.first)
    log_message_to_sentry('Returning inactive MHV correlation ID as first identifier', :warn,
                          ids: mhv_ids)
  end
  if active_mhv_ids.uniq.size > 1
    log_message_to_sentry('Multiple active MHV correlation IDs present', :info,
                          ids: active_mhv_ids)
  end
end

#log_mpi_relationships(relationships) ⇒ Object (private)



215
216
217
218
# File 'lib/mpi/responses/profile_parser.rb', line 215

def log_mpi_relationships(relationships)
  Rails.logger.info('[MPI][Responses][ProfileParser] Relationships detected',
                    { person_types: relationships.map(&:person_types) })
end

#mpi_attribute_validations(identity_hash, ids_hash) ⇒ Object (private)



210
211
212
213
# File 'lib/mpi/responses/profile_parser.rb', line 210

def mpi_attribute_validations(identity_hash, ids_hash)
  log_inactive_mhv_ids(ids_hash[:mhv_ids].to_a, ids_hash[:active_mhv_ids].to_a)
  validate_dob(identity_hash[:birth_date], ids_hash[:icn])
end

#multiple_match?Boolean

Returns:

  • (Boolean)


60
61
62
63
64
65
# File 'lib/mpi/responses/profile_parser.rb', line 60

def multiple_match?
  acknowledgement_detail = locate_element(@original_body, ACKNOWLEDGEMENT_DETAIL_XPATH)
  return false unless acknowledgement_detail

  acknowledgement_detail.nodes.first == MULTIPLE_MATCHES_FOUND
end

#no_match?Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/mpi/responses/profile_parser.rb', line 67

def no_match?
  locate_element(@original_body, SUBJECT_XPATH).blank?
end

#parseObject



71
72
73
74
75
76
77
78
79
# File 'lib/mpi/responses/profile_parser.rb', line 71

def parse
  subject = locate_element(@original_body, SUBJECT_XPATH)
  return MPI::Models::MviProfile.new({ transaction_id: @transaction_id }) unless subject

  patient = locate_element(subject, PATIENT_XPATH)
  return MPI::Models::MviProfile.new({ transaction_id: @transaction_id }) unless patient

  build_mpi_profile(patient)
end

#parse_address(person) ⇒ Object (private)



281
282
283
284
285
286
287
288
# File 'lib/mpi/responses/profile_parser.rb', line 281

def parse_address(person)
  el = locate_element(person, ADDRESS_XPATH)
  return nil unless el

  address_hash = el.nodes.map { |n| { n.value.snakecase.to_sym => n.nodes.first } }.reduce({}, :merge)
  address_hash[:street] = address_hash.delete :street_address_line
  MPI::Models::MviProfileAddress.new(address_hash)
end

#parse_id_theft_flag(patient) ⇒ Object (private)



127
128
129
130
# File 'lib/mpi/responses/profile_parser.rb', line 127

def parse_id_theft_flag(patient)
  code = locate_element(patient, CONFIDENTIALITY_CODE_XPATH)
  code == ID_THEFT_INDICATOR
end

#parse_multiple_ids(parsed_mvi_ids) ⇒ Object (private)



189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/mpi/responses/profile_parser.rb', line 189

def parse_multiple_ids(parsed_mvi_ids)
  {
    mhv_ids: parsed_mvi_ids[:mhv_ids],
    active_mhv_ids: parsed_mvi_ids[:active_mhv_ids],
    edipis: sanitize_id_array(parsed_mvi_ids[:edipis]),
    participant_ids: sanitize_id_array(parsed_mvi_ids[:vba_corp_ids]),
    mhv_iens: sanitize_id_array(parsed_mvi_ids[:mhv_iens]),
    sec_ids: parsed_mvi_ids[:sec_ids],
    vha_facility_ids: parsed_mvi_ids[:vha_facility_ids],
    vha_facility_hash: parsed_mvi_ids[:vha_facility_hash],
    birls_ids: sanitize_id_array(parsed_mvi_ids[:birls_ids]),
    cerner_facility_ids: parsed_mvi_ids[:cerner_facility_ids]
  }
end

#parse_name(name, indicator: NAME_LEGAL_INDICATOR) ⇒ Object (private)



253
254
255
256
257
258
259
260
261
262
263
# File 'lib/mpi/responses/profile_parser.rb', line 253

def parse_name(name, indicator: NAME_LEGAL_INDICATOR)
  name_element = parse_name_node(name, indicator:)

  given = [*name_element.locate('given')].map { |el| el.nodes.first.capitalize }
  family = name_element.locate('family')&.first&.nodes&.first&.capitalize
  suffix = name_element.locate('suffix')&.first&.nodes&.first&.capitalize
  { given:, family:, suffix: }
rescue
  Rails.logger.warn 'MPI::Response.parse_name failed' if indicator == NAME_LEGAL_INDICATOR
  { given: nil, family: nil }
end

#parse_name_node(name_array, indicator: NAME_LEGAL_INDICATOR) ⇒ Object (private)



265
266
267
# File 'lib/mpi/responses/profile_parser.rb', line 265

def parse_name_node(name_array, indicator: NAME_LEGAL_INDICATOR)
  name_array.find { |name_element| name_element if name_element.attributes[:use] == indicator }
end

#parse_person_type(person) ⇒ Object (private)



311
312
313
314
315
316
317
318
# File 'lib/mpi/responses/profile_parser.rb', line 311

def parse_person_type(person)
  person.locate(ADMIN_OBSERVATION_XPATH).each do |element|
    if element.locate(PERSON_TYPE_CODE_XPATH).first == PERSON_TYPE
      person_type_string = element.locate(PERSON_TYPE_VALUE_XPATH).first
      return person_type_string&.split(PERSON_TYPE_SEPARATOR) || []
    end
  end
end

#parse_phone(person, person_prefix) ⇒ Object (private)



290
291
292
293
294
295
# File 'lib/mpi/responses/profile_parser.rb', line 290

def parse_phone(person, person_prefix)
  el = locate_element(person, PHONE) || locate_element(person, person_prefix + PHONE)
  return nil unless el

  el.attributes[:value]
end

#parse_relationship_name(name) ⇒ Object (private)



243
244
245
246
247
248
249
250
251
# File 'lib/mpi/responses/profile_parser.rb', line 243

def parse_relationship_name(name)
  name_element = parse_name_node(name, indicator: NAME_PREFERRED_INDICATOR) ||
                 parse_name_node(name, indicator: NAME_LEGAL_INDICATOR)
  return { given: nil, family: nil } if name_element.blank?

  given = [*name_element.locate('given')].map { |el| el.nodes.first.capitalize }
  family = name_element.locate('family').first.nodes.first.capitalize
  { given:, family: }
end

#parse_relationship_person_type(element) ⇒ Object (private)



304
305
306
307
308
309
# File 'lib/mpi/responses/profile_parser.rb', line 304

def parse_relationship_person_type(element)
  if element.locate(RELATIONSHIP_PERSON_TYPE_CODE_XPATH).first == RELATIONSHIP_PERSON_TYPE
    person_type_string = element.locate(RELATIONSHIP_PERSON_TYPE_VALUE_XPATH).first
    person_type_string&.split(PERSON_TYPE_SEPARATOR) || []
  end
end

#parse_relationships(relationships_array) ⇒ Object (private)



117
118
119
# File 'lib/mpi/responses/profile_parser.rb', line 117

def parse_relationships(relationships_array)
  relationships_array.map { |relationship| build_relationship_mpi_profile(relationship) }
end

#parse_single_ids(parsed_mvi_ids) ⇒ Object (private)



175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/mpi/responses/profile_parser.rb', line 175

def parse_single_ids(parsed_mvi_ids)
  {
    icn: parsed_mvi_ids[:icn],
    edipi: sanitize_edipi(parsed_mvi_ids[:edipi]),
    participant_id: sanitize_id(parsed_mvi_ids[:vba_corp_id]),
    mhv_ien: sanitize_id(parsed_mvi_ids[:mhv_ien]),
    sec_id: parsed_mvi_ids[:sec_id],
    birls_id: sanitize_id(parsed_mvi_ids[:birls_id]),
    vet360_id: parsed_mvi_ids[:vet360_id],
    icn_with_aaid: parsed_mvi_ids[:icn_with_aaid],
    cerner_id: parsed_mvi_ids[:cerner_id]
  }
end

#parse_ssn(other_ids) ⇒ Object (private)

other_ids can be hash or array of hashes



270
271
272
273
274
275
276
277
278
279
# File 'lib/mpi/responses/profile_parser.rb', line 270

def parse_ssn(other_ids)
  other_ids = [other_ids] unless other_ids.is_a? Array
  ssn_element = select_ssn_element(other_ids)
  return nil unless ssn_element

  ssn_element.attributes[:extension]
rescue
  Rails.logger.warn 'MPI::Response.parse_ssn failed'
  nil
end

#select_ssn_element(other_ids) ⇒ Object (private)



297
298
299
300
301
302
# File 'lib/mpi/responses/profile_parser.rb', line 297

def select_ssn_element(other_ids)
  other_ids.each do |oi|
    node = oi.nodes.select { |n| n.attributes[:root] == SSN_ROOT_ID }
    return node.first unless node.empty?
  end
end

#validate_dob(dob, icn) ⇒ Object (private)



237
238
239
240
241
# File 'lib/mpi/responses/profile_parser.rb', line 237

def validate_dob(dob, icn)
  Date.iso8601(dob)
rescue Date::Error
  Rails.logger.warn 'MPI::Response.parse_dob failed', { dob:, icn: }
end