Module: Inferno::DSL::FHIRResourceNavigation

Included in:
Inferno::DSL::FHIREvaluation::Rules::AllMustSupportsPresent
Defined in:
lib/inferno/dsl/fhir_resource_navigation.rb

Overview

The FHIRResourceNavigation module is used to pick values from a FHIR resource, based on a profile. Originally intended for use for verifying the presence of Must Support elements on a resource. This module expects pre-processed metadata defining the elements of the profile to be present in the attribute metadata in the including class.

Constant Summary collapse

DAR_EXTENSION_URL =
'http://hl7.org/fhir/StructureDefinition/data-absent-reason'.freeze
PRIMITIVE_DATA_TYPES =
FHIR::PRIMITIVES.keys

Instance Method Summary collapse

Instance Method Details

#current_and_child_values_match?(el_found, value_definitions_for_path) ⇒ Boolean

Returns:

  • (Boolean)


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 197

def current_and_child_values_match?(el_found, value_definitions_for_path)
  child_element_value_definitions, current_element_value_definitions =
    value_definitions_for_path.partition { |value_definition| value_definition[:path].present? }

  current_element_values_match =
    current_element_value_definitions
      .all? { |value_definition| value_definition[:value] == el_found }

  child_element_values_match =
    if child_element_value_definitions.present?
      verify_slice_by_values(el_found, child_element_value_definitions)
    else
      true
    end
  current_element_values_match && child_element_values_match
end

#find_a_value_at(given_element, path, include_dar: false, &block) ⇒ Array<FHIR::Model>

Get a value from the given FHIR element(s), by navigating through the resource to the given path. Fields with a DataAbsentReason extension present may be selected if include_dar is true. To filter the resulting elements, a block may be passed in.

Parameters:

Returns:



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 39

def find_a_value_at(given_element, path, include_dar: false, &block)
  return nil if given_element.nil?

  elements = Array.wrap(given_element)
  return find_in_elements(elements, include_dar:, &block) if path.empty?

  path_segments = path.split(/(?<!hl7)\./)

  segment = path_segments.shift.delete_suffix('[x]').gsub(/^class$/, 'local_class').gsub('[x]:', ':').to_sym

  remaining_path = path_segments.join('.')
  elements.each do |element|
    child = get_next_value(element, segment)
    element_found = find_a_value_at(child, remaining_path, include_dar:, &block)
    return element_found if element_found.present? || element_found == false
  end

  nil
end

#find_in_elements(elements, include_dar: false) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 59

def find_in_elements(elements, include_dar: false, &)
  unless include_dar
    elements = elements.reject do |el|
      el.respond_to?(:extension) && el.extension.any? { |ext| ext.url == DAR_EXTENSION_URL }
    end
  end

  return elements.find(&) if block_given?

  elements.first
end

#find_slice_via_discriminator(element, property) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 108

def find_slice_via_discriminator(element, property)
  return unless .present?

  element_name = local_field_name(property.to_s.split(':')[0])
  slice_name = local_field_name(property.to_s.split(':')[1])

  slice_by_name = .must_supports[:slices].find { |slice| slice[:slice_name] == slice_name }
  discriminator = slice_by_name[:discriminator]
  slices = Array.wrap(element.send(element_name))
  slices.find { |slice| matching_slice?(slice, discriminator) }
end

#flatten_bundles(resources) ⇒ Object



214
215
216
217
218
219
220
221
222
223
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 214

def flatten_bundles(resources)
  resources.flat_map do |resource|
    if resource&.resourceType == 'Bundle'
      # Recursive to consider that Bundles may contain Bundles
      flatten_bundles(resource.entry.map(&:resource))
    else
      resource
    end
  end
end

#get_next_value(element, property) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 71

def get_next_value(element, property)
  extension_url = property[/(?<=where\(url=').*(?='\))/]
  if extension_url.present?
    element.url == extension_url ? element : nil
  elsif property.to_s.include?(':') && !property.to_s.include?('url')
    find_slice_via_discriminator(element, property)

  else
    local_name = local_field_name(property)
    value = element.send(local_name)
    primitive_value = get_primitive_type_value(element, property, value)
    primitive_value.present? ? primitive_value : value
  end
rescue NoMethodError
  nil
end

#get_primitive_type_value(element, property, value) ⇒ Object



88
89
90
91
92
93
94
95
96
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 88

def get_primitive_type_value(element, property, value)
  source_value = element.source_hash["_#{property}"]

  return nil unless source_value.present?

  primitive_value = PrimitiveType.new(source_value)
  primitive_value.value = value
  primitive_value
end

#local_field_name(field_name) ⇒ Object



98
99
100
101
102
103
104
105
106
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 98

def local_field_name(field_name)
  # fhir_models prepends fields whose names are reserved in ruby with "local_"
  # This should be used before `x.send(field_name)`
  if ['method', 'class'].include?(field_name.to_s)
    "local_#{field_name}"
  else
    field_name
  end
end

#matching_pattern_codeable_concept_slice?(slice, discriminator) ⇒ Boolean

Returns:

  • (Boolean)


137
138
139
140
141
142
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 137

def matching_pattern_codeable_concept_slice?(slice, discriminator)
  slice_value = discriminator[:path].present? ? slice.send((discriminator[:path]).to_s)&.coding : slice.coding
  slice_value&.any? do |coding|
    coding.code == discriminator[:code] && coding.system == discriminator[:system]
  end
end

#matching_pattern_coding_slice?(slice, discriminator) ⇒ Boolean

Returns:

  • (Boolean)


144
145
146
147
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 144

def matching_pattern_coding_slice?(slice, discriminator)
  slice_value = discriminator[:path].present? ? slice.send(discriminator[:path]) : slice
  slice_value&.code == discriminator[:code] && slice_value&.system == discriminator[:system]
end

#matching_pattern_identifier_slice?(slice, discriminator) ⇒ Boolean

Returns:

  • (Boolean)


149
150
151
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 149

def matching_pattern_identifier_slice?(slice, discriminator)
  slice.identifier.system == discriminator[:system]
end

#matching_required_binding_slice?(slice, discriminator) ⇒ Boolean

Returns:

  • (Boolean)


179
180
181
182
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 179

def matching_required_binding_slice?(slice, discriminator)
  discriminator[:path].present? ? slice.send((discriminator[:path]).to_s).coding : slice.coding
  slice_value { |coding| discriminator[:values].include?(coding.code) }
end

#matching_slice?(slice, discriminator) ⇒ Boolean

Returns:

  • (Boolean)


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 120

def matching_slice?(slice, discriminator)
  case discriminator[:type]
  when 'patternCodeableConcept'
    matching_pattern_codeable_concept_slice?(slice, discriminator)
  when 'patternCoding'
    matching_pattern_coding_slice?(slice, discriminator)
  when 'patternIdentifier'
    matching_pattern_identifier_slice?(slice, discriminator)
  when 'value'
    matching_value_slice?(slice, discriminator)
  when 'type'
    matching_type_slice?(slice, discriminator)
  when 'requiredBinding'
    matching_required_binding_slice?(slice, discriminator)
  end
end

#matching_type_slice?(slice, discriminator) ⇒ Boolean

Returns:

  • (Boolean)


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 158

def matching_type_slice?(slice, discriminator)
  case discriminator[:code]
  when 'Date'
    begin
      Date.parse(slice)
    rescue ArgumentError
      false
    end
  when 'DateTime'
    begin
      DateTime.parse(slice)
    rescue ArgumentError
      false
    end
  when 'String'
    slice.is_a? String
  else
    slice.is_a? FHIR.const_get(discriminator[:code])
  end
end

#matching_value_slice?(slice, discriminator) ⇒ Boolean

Returns:

  • (Boolean)


153
154
155
156
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 153

def matching_value_slice?(slice, discriminator)
  values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
  verify_slice_by_values(slice, values)
end

#resolve_path(elements, path) ⇒ Array<FHIR::Model>

Get a value from the given FHIR element(s) by walking the given path through the element.

Parameters:

Returns:



18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 18

def resolve_path(elements, path)
  elements = Array.wrap(elements)
  return elements if path.blank?

  paths = path.split(/(?<!hl7)\./)
  segment = paths.first
  remaining_path = paths.drop(1).join('.')

  elements.flat_map do |element|
    child = get_next_value(element, segment)
    resolve_path(child, remaining_path)
  end.compact
end

#verify_slice_by_values(element, value_definitions) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 184

def verify_slice_by_values(element, value_definitions)
  path_prefixes = value_definitions.map { |value_definition| value_definition[:path].first }.uniq
  path_prefixes.all? do |path_prefix|
    value_definitions_for_path =
      value_definitions
        .select { |value_definition| value_definition[:path].first == path_prefix }
        .each { |value_definition| value_definition[:path].shift }
    find_a_value_at(element, path_prefix) do |el_found|
      current_and_child_values_match?(el_found, value_definitions_for_path)
    end
  end
end