Module: Inferno::DSL::FHIRResourceNavigation
- Included in:
- Inferno::DSL::FHIREvaluation::Rules::AllMustSupportsPresent, MustSupportAssessment::InternalMustSupportLogic
- 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 and finding values to use for search parameters. The methods in this module related to slices 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
- #append_path_character(state, char, depth_change:) ⇒ Object
- #choice_path?(property) ⇒ Boolean
- #current_and_child_values_match?(el_found, value_definitions_for_path) ⇒ Boolean
- #extension_filter_value(element, extension_url) ⇒ Object
- #field_value(element, field_name) ⇒ Object
-
#find_a_value_at(given_element, path, include_dar: false, &block) ⇒ Object
Get a value from the given FHIR element(s), by navigating through the resource to the given path.
- #find_in_elements(elements, include_dar: false) ⇒ Object
- #find_slice_via_discriminator(element, property) ⇒ Object
- #flatten_bundles(resources) ⇒ Object
- #get_next_value(element, property) ⇒ Object
- #get_primitive_type_value(element, property, value) ⇒ Object
- #local_field_name(field_name) ⇒ Object
- #matching_pattern_codeable_concept_slice?(slice, discriminator) ⇒ Boolean
- #matching_pattern_coding_slice?(slice, discriminator) ⇒ Boolean
- #matching_pattern_identifier_slice?(slice, discriminator) ⇒ Boolean
- #matching_required_binding_slice?(slice, discriminator) ⇒ Boolean
- #matching_slice?(slice, discriminator) ⇒ Boolean
- #matching_type_slice?(slice, discriminator) ⇒ Boolean
- #matching_value_slice?(slice, discriminator) ⇒ Boolean
- #path_segments(path) ⇒ Object
- #populated_choice_value(element, property) ⇒ Object
- #required_binding_codings(slice, discriminator) ⇒ Object
- #required_binding_value_match?(coding, values) ⇒ Boolean
-
#resolve_path(elements, path) ⇒ Array<FHIR::Model>
Get a value from the given FHIR element(s) by walking the given path through the element.
- #slice_path?(property) ⇒ Boolean
- #sliced_choice_path?(property) ⇒ Boolean
- #sliced_choice_value(element, property) ⇒ Object
- #split_path_segment_or_append(state, char) ⇒ Object
- #update_path_segment_state(state, char) ⇒ Object
- #value_at_path_matches?(element, path, include_dar: false) ⇒ Boolean
- #value_not_empty?(value) ⇒ Boolean
- #verify_slice_by_values(element, value_definitions) ⇒ Object
Instance Method Details
#append_path_character(state, char, depth_change:) ⇒ Object
195 196 197 198 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 195 def append_path_character(state, char, depth_change:) state[:current_segment] << char state[:parentheses_depth] += depth_change unless state[:in_quotes] end |
#choice_path?(property) ⇒ Boolean
104 105 106 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 104 def choice_path?(property) property.end_with?('[x]') end |
#current_and_child_values_match?(el_found, value_definitions_for_path) ⇒ Boolean
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 336 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 |
#extension_filter_value(element, extension_url) ⇒ Object
94 95 96 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 94 def extension_filter_value(element, extension_url) element.url == extension_url ? element : nil end |
#field_value(element, field_name) ⇒ Object
135 136 137 138 139 140 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 135 def field_value(element, field_name) local_name = local_field_name(field_name) value = element.send(local_name) primitive_value = get_primitive_type_value(element, field_name, value) primitive_value.present? ? primitive_value : value end |
#find_a_value_at(given_element, path, include_dar: false, &block) ⇒ Object
Get a value from the given FHIR element(s), by navigating through the resource to the given path. Fields with a DataAbsentReason extension present will be excluded unless include_dar is true. To filter the resulting elements, a block may be passed in.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 46 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_segments(path) segment = path_segments.shift 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 value_not_empty?(element_found) end nil end |
#find_in_elements(elements, include_dar: false) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 67 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
211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 211 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 } return nil if slice_by_name.blank? discriminator = slice_by_name[:discriminator] slices = Array.wrap(element.send(element_name)) slices.find { |slice| matching_slice?(slice, discriminator) } end |
#flatten_bundles(resources) ⇒ Object
365 366 367 368 369 370 371 372 373 374 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 365 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
80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 80 def get_next_value(element, property) property = property.to_s extension_url = property[/(?<=where\(url=').*(?='\))/] return extension_filter_value(element, extension_url) if extension_url.present? return sliced_choice_value(element, property) if sliced_choice_path?(property) return populated_choice_value(element, property) if choice_path?(property) return find_slice_via_discriminator(element, property) if slice_path?(property) field_value(element, property) rescue NoMethodError nil end |
#get_primitive_type_value(element, property, value) ⇒ Object
143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 143 def get_primitive_type_value(element, property, value) return nil unless element.respond_to?(:source_hash) source_hash = element.source_hash return nil unless source_hash.present? source_value = 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
159 160 161 162 163 164 165 166 167 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 159 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
244 245 246 247 248 249 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 244 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
252 253 254 255 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 252 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
258 259 260 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 258 def matching_pattern_identifier_slice?(slice, discriminator) slice.system == discriminator[:system] end |
#matching_required_binding_slice?(slice, discriminator) ⇒ Boolean
293 294 295 296 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 293 def matching_required_binding_slice?(slice, discriminator) slice_coding = required_binding_codings(slice, discriminator) slice_coding.any? { |coding| required_binding_value_match?(coding, discriminator[:values]) } end |
#matching_slice?(slice, discriminator) ⇒ Boolean
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 226 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
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 269 def matching_type_slice?(slice, discriminator) slice_value = resolve_path(slice, discriminator[:path]).first case discriminator[:code] when 'Date' begin Date.parse(slice_value) rescue ArgumentError false end when 'DateTime' begin DateTime.parse(slice_value) rescue ArgumentError false end when 'String' slice_value.is_a? String else slice_value.is_a? FHIR.const_get(discriminator[:code]) end end |
#matching_value_slice?(slice, discriminator) ⇒ Boolean
263 264 265 266 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 263 def matching_value_slice?(slice, discriminator) values = discriminator[:values].map { |value| value.merge(path: path_segments(value[:path])) } verify_slice_by_values(slice, values) end |
#path_segments(path) ⇒ Object
170 171 172 173 174 175 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 170 def path_segments(path) state = { current_segment: +'', segments: [], parentheses_depth: 0, in_quotes: false } path.each_char { |char| update_path_segment_state(state, char) } state[:segments] << state[:current_segment] unless state[:current_segment].empty? state[:segments] end |
#populated_choice_value(element, property) ⇒ Object
120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 120 def populated_choice_value(element, property) choice_prefix = property.delete_suffix('[x]') populated_field = Array.wrap(element.to_hash&.keys) .map(&:to_s) .find do |field_name| field_name.start_with?(choice_prefix) && value_not_empty?(field_value(element, field_name)) end return nil if populated_field.blank? field_value(element, populated_field) end |
#required_binding_codings(slice, discriminator) ⇒ Object
299 300 301 302 303 304 305 306 307 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 299 def required_binding_codings(slice, discriminator) if discriminator[:path].present? Array.wrap(resolve_path(slice, discriminator[:path])).flat_map { |value| Array.wrap(value&.coding) } elsif slice.is_a?(FHIR::Coding) [slice] else Array.wrap(slice.coding) end end |
#required_binding_value_match?(coding, values) ⇒ Boolean
310 311 312 313 314 315 316 317 318 319 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 310 def required_binding_value_match?(coding, values) values.any? do |value| case value when String value == coding.code when Hash value[:system] == coding.system && value[:code] == coding.code end end 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.
23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 23 def resolve_path(elements, path) elements = Array.wrap(elements) return elements if path.blank? paths = path_segments(path) 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 |
#slice_path?(property) ⇒ Boolean
109 110 111 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 109 def slice_path?(property) property.include?(':') && !property.include?('url') end |
#sliced_choice_path?(property) ⇒ Boolean
99 100 101 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 99 def sliced_choice_path?(property) property.include?('[x]:') end |
#sliced_choice_value(element, property) ⇒ Object
114 115 116 117 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 114 def sliced_choice_value(element, property) _choice_path, sliced_field = property.split(':', 2) field_value(element, sliced_field) end |
#split_path_segment_or_append(state, char) ⇒ Object
201 202 203 204 205 206 207 208 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 201 def split_path_segment_or_append(state, char) if state[:parentheses_depth].zero? && !state[:in_quotes] state[:segments] << state[:current_segment].dup state[:current_segment].clear else state[:current_segment] << char end end |
#update_path_segment_state(state, char) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 178 def update_path_segment_state(state, char) case char when "'" state[:current_segment] << char state[:in_quotes] = !state[:in_quotes] when '(' append_path_character(state, char, depth_change: 1) when ')' append_path_character(state, char, depth_change: -1) when '.' split_path_segment_or_append(state, char) else state[:current_segment] << char end end |
#value_at_path_matches?(element, path, include_dar: false) ⇒ Boolean
354 355 356 357 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 354 def value_at_path_matches?(element, path, include_dar: false, &) value_found = find_a_value_at(element, path, include_dar:, &) value_not_empty?(value_found) end |
#value_not_empty?(value) ⇒ Boolean
360 361 362 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 360 def value_not_empty?(value) value.present? || value == false end |
#verify_slice_by_values(element, value_definitions) ⇒ Object
322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/inferno/dsl/fhir_resource_navigation.rb', line 322 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 } value_at_path_matches?(element, path_prefix) do |el_found| current_and_child_values_match?(el_found, value_definitions_for_path) end end end |