Module: DaVinciPASTestKit::DaVinciPASV221::ClaimUpdateValidationUtils

Overview

Shared helpers for the Claim Update verification tests. These tests reload the requests received during the Claim Update wait tests (by their unique tags) and inspect the submitted PAS Claim Update Bundles against the relevant conformance requirements. No live state is used - everything is reloaded from the tagged, persisted requests.

Constant Summary collapse

CERTIFICATION_TYPE_EXTENSION_URL =
'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-certificationType'.freeze
INFO_CHANGED_EXTENSION_URL =
'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/extension-infoChanged'.freeze
INFO_CANCELLED_EXTENSION_URL =
'http://hl7.org/fhir/us/davinci-pas/StructureDefinition/modifierextension-infoCancelled'.freeze
CERTIFICATION_TYPE_CANCEL_CODE =
'3'.freeze
INFO_CHANGE_MODE_CODES =

The PAS Information Change Mode value set (CodeSystem PASTempCodes) defines exactly these two codes: 'added' (new information) and 'changed' (previously sent info changed).

%w[added changed].freeze

Instance Method Summary collapse

Instance Method Details

#bundle_entry_for_reference(bundle, reference) ⇒ Object

Finds the bundle entry that a reference points to, accommodating absolute fullUrls, urn:uuid fullUrls, and relative "ResourceType/id" references. NOTE: purposefully more permissive than strict FHIR Bundle reference resolution logic to allow for evaluation. Other steps will catch issues like missing fullUrl values.



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 84

def bundle_entry_for_reference(bundle, reference)
  return nil if bundle.nil? || reference.blank?

  bundle.entry.find do |entry|
    next false if entry.nil?

    resource = entry.resource
    full_url = entry.fullUrl.to_s
    full_url == reference ||
      (resource && "#{resource.resourceType}/#{resource.id}" == reference) ||
      (full_url.present? && full_url.end_with?("/#{reference}"))
  end
end

#cancellation_marked?(element) ⇒ Boolean

An entry is treated as "canceled" when it carries either change marker.

Returns:

  • (Boolean)


138
139
140
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 138

def cancellation_marked?(element)
  certification_type_cancel?(element) || info_cancelled_extension(element).present?
end

#certification_type_cancel?(element) ⇒ Boolean

Returns true if the element (an item or the Claim itself) carries a certificationType extension with code 3 (Cancel).

Returns:

  • (Boolean)


116
117
118
119
120
121
122
123
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 116

def certification_type_cancel?(element)
  Array(element&.extension).any? do |extension|
    extension.url == CERTIFICATION_TYPE_EXTENSION_URL &&
      Array(extension.valueCodeableConcept&.coding).any? do |coding|
        coding.code == CERTIFICATION_TYPE_CANCEL_CODE
      end
  end
end

#claim_update_bundle(tag) ⇒ Object

Reloads the most recent request stored under tag and parses it as a FHIR::Bundle. Returns nil if no such request was received or it does not parse to a Bundle.



47
48
49
50
51
52
53
54
55
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 47

def claim_update_bundle(tag)
  request = load_tagged_requests(tag).last
  return nil if request.nil? || request.request_body.blank?

  resource = FHIR.from_contents(request.request_body)
  resource.is_a?(FHIR::Bundle) ? resource : nil
rescue StandardError
  nil
end

#claim_update_detail_chainObject

The submissions that carry item/supportingInfo detail and can be compared against the immediately prior version. The cancel-entire-request update is excluded because it needs no items.



41
42
43
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 41

def claim_update_detail_chain
  claim_update_steps.take(3)
end

#claim_update_detail_comparisonsObject

Builds ordered prior/current Claim comparisons across the detail chain, skipping any link where a request was not received.



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 66

def claim_update_detail_comparisons
  claims = claim_update_detail_chain.map do |step|
    bundle = claim_update_bundle(step[:tag])
    { label: step[:label], claim: first_claim(bundle) }
  end

  claims.each_cons(2).filter_map do |prior, current|
    next if prior[:claim].nil? || current[:claim].nil?

    { prior: prior[:claim], current: current[:claim],
      prior_label: prior[:label], current_label: current[:label] }
  end
end

#claim_update_stepsObject

The ordered Claim Update submissions, paired with the unique tag each was stored under.



23
24
25
26
27
28
29
30
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 23

def claim_update_steps
  [
    { label: 'initial submission', tag: CLAIM_UPDATE_INITIAL_TAG },
    { label: 'add-item update', tag: CLAIM_UPDATE_ADD_ITEM_TAG },
    { label: 'modify-and-cancel update', tag: CLAIM_UPDATE_MODIFY_CANCEL_TAG },
    { label: 'cancel-entire-request update', tag: CLAIM_UPDATE_CANCEL_ALL_TAG }
  ]
end

#claim_update_updatesObject

The submissions that are updates to a previously submitted Claim (everything except the initial submission).



34
35
36
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 34

def claim_update_updates
  claim_update_steps.drop(1)
end

#entry_kind(entry) ⇒ Object



110
111
112
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 110

def entry_kind(entry)
  entry.class.name.to_s.split('::').last == 'Item' ? 'item' : 'supportingInfo'
end

#entry_modified?(current_entry, prior_entry) ⇒ Boolean

Compares an entry against its prior version, ignoring the change-tracking extensions (infoChanged, certificationType) and modifier extension (infoCancelled), to determine whether its substantive content was modified.

Returns:

  • (Boolean)


149
150
151
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 149

def entry_modified?(current_entry, prior_entry)
  normalized_entry_hash(current_entry) != normalized_entry_hash(prior_entry)
end

#entry_sequences(entries) ⇒ Object



102
103
104
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 102

def entry_sequences(entries)
  Array(entries).map(&:sequence).compact
end

#first_claim(bundle) ⇒ Object

The Claim being submitted is the first Claim entry in a PAS Request Bundle.



58
59
60
61
62
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 58

def first_claim(bundle)
  return nil if bundle.nil?

  bundle.entry.map(&:resource).find { |resource| resource.is_a?(FHIR::Claim) }
end

#index_by_sequence(entries) ⇒ Object



106
107
108
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 106

def index_by_sequence(entries)
  Array(entries).to_h { |entry| [entry.sequence, entry] }
end

#info_cancelled?(element) ⇒ Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 129

def info_cancelled?(element)
  info_cancelled_extension(element)&.valueBoolean == true
end

#info_cancelled_extension(element) ⇒ Object



125
126
127
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 125

def info_cancelled_extension(element)
  Array(element&.modifierExtension).find { |extension| extension.url == INFO_CANCELLED_EXTENSION_URL }
end

#info_changed_extension(element) ⇒ Object



133
134
135
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 133

def info_changed_extension(element)
  Array(element&.extension).find { |extension| extension.url == INFO_CHANGED_EXTENSION_URL }
end

#newly_cancelled?(current_entry, prior_entry) ⇒ Boolean

Returns:

  • (Boolean)


142
143
144
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 142

def newly_cancelled?(current_entry, prior_entry)
  info_cancelled?(current_entry) && !info_cancelled?(prior_entry)
end

#normalized_entry_hash(entry) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 153

def normalized_entry_hash(entry)
  hash = entry.to_hash
  if hash['extension'].is_a?(Array)
    hash['extension'] = hash['extension'].reject do |extension|
      [INFO_CHANGED_EXTENSION_URL, CERTIFICATION_TYPE_EXTENSION_URL].include?(extension['url'])
    end
    hash.delete('extension') if hash['extension'].empty?
  end
  if hash['modifierExtension'].is_a?(Array)
    hash['modifierExtension'] = hash['modifierExtension'].reject do |extension|
      extension['url'] == INFO_CANCELLED_EXTENSION_URL
    end
    hash.delete('modifierExtension') if hash['modifierExtension'].empty?
  end
  hash
end

#received_claim_update_steps(steps = claim_update_steps) ⇒ Object

Collects the Claim Update steps for which a request was actually received, attaching the parsed Bundle and Claim. Used by tests that inspect each submission independently.



172
173
174
175
176
177
178
179
180
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 172

def received_claim_update_steps(steps = claim_update_steps)
  steps.filter_map do |step|
    bundle = claim_update_bundle(step[:tag])
    claim = first_claim(bundle)
    next if claim.nil?

    step.merge(bundle:, claim:)
  end
end


98
99
100
# File 'lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_validation_utils.rb', line 98

def related_claim_references(claim)
  Array(claim&.related).filter_map { |related| related.claim&.reference }
end