Class: Inferno::DSL::MustSupportAssessment::InternalMustSupportLogic

Inherits:
Object
  • Object
show all
Includes:
FHIRResourceNavigation
Defined in:
lib/inferno/dsl/must_support_assessment.rb

Constant Summary

Constants included from FHIRResourceNavigation

FHIRResourceNavigation::DAR_EXTENSION_URL, FHIRResourceNavigation::PRIMITIVE_DATA_TYPES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from FHIRResourceNavigation

#current_and_child_values_match?, #find_a_value_at, #find_in_elements, #find_slice_via_discriminator, #flatten_bundles, #get_next_value, #get_primitive_type_value, #local_field_name, #matching_pattern_codeable_concept_slice?, #matching_pattern_coding_slice?, #matching_pattern_identifier_slice?, #matching_required_binding_slice?, #matching_slice?, #matching_type_slice?, #matching_value_slice?, #resolve_path, #verify_slice_by_values

Constructor Details

#initialize(metadata = nil) ⇒ InternalMustSupportLogic

Returns a new instance of InternalMustSupportLogic.



68
69
70
# File 'lib/inferno/dsl/must_support_assessment.rb', line 68

def initialize( = nil)
   = 
end

Instance Attribute Details

#metadataObject

Returns the value of attribute metadata.



66
67
68
# File 'lib/inferno/dsl/must_support_assessment.rb', line 66

def 
  
end

Instance Method Details

#any_choice_supported?(choices) ⇒ Boolean

Returns:

  • (Boolean)


145
146
147
148
149
150
151
152
# File 'lib/inferno/dsl/must_support_assessment.rb', line 145

def any_choice_supported?(choices)
  return false unless choices.present?

  any_path_choice_supported?(choices) ||
    any_extension_ids_choice_supported?(choices) ||
    any_slice_names_choice_supported?(choices) ||
    any_elements_choice_supported?(choices)
end

#any_elements_choice_supported?(choices) ⇒ Boolean

Returns:

  • (Boolean)


174
175
176
177
178
179
180
181
182
# File 'lib/inferno/dsl/must_support_assessment.rb', line 174

def any_elements_choice_supported?(choices)
  return false unless choices[:elements].present?

  choices[:elements].any? do |choice|
    missing_elements.none? do |element|
      element[:path] == choice[:path] && element[:fixed_value] == choice[:fixed_value]
    end
  end
end

#any_extension_ids_choice_supported?(choices) ⇒ Boolean

Returns:

  • (Boolean)


160
161
162
163
164
165
166
# File 'lib/inferno/dsl/must_support_assessment.rb', line 160

def any_extension_ids_choice_supported?(choices)
  return false unless choices[:extension_ids].present?

  choices[:extension_ids].any? do |extension_id|
    missing_extensions.none? { |extension| extension[:id] == extension_id }
  end
end

#any_path_choice_supported?(choices) ⇒ Boolean

Returns:

  • (Boolean)


154
155
156
157
158
# File 'lib/inferno/dsl/must_support_assessment.rb', line 154

def any_path_choice_supported?(choices)
  return false unless choices[:paths].present?

  choices[:paths].any? { |path| missing_elements.none? { |element| element[:path] == path } }
end

#any_slice_names_choice_supported?(choices) ⇒ Boolean

Returns:

  • (Boolean)


168
169
170
171
172
# File 'lib/inferno/dsl/must_support_assessment.rb', line 168

def any_slice_names_choice_supported?(choices)
  return false unless choices[:slice_names].present?

  choices[:slice_names].any? { |slice_name| missing_slices.none? { |slice| slice[:name] == slice_name } }
end

#extract_metadata(profile, ig, requirement_extension: nil) ⇒ Object



89
90
91
# File 'lib/inferno/dsl/must_support_assessment.rb', line 89

def (profile, ig, requirement_extension: nil)
  MustSupportMetadataExtractor.new(profile.snapshot.element, profile, profile.type, ig, requirement_extension)
end

#find_missing_elements(resources, must_support_elements) ⇒ Object



229
230
231
232
233
# File 'lib/inferno/dsl/must_support_assessment.rb', line 229

def find_missing_elements(resources, must_support_elements)
  must_support_elements.select do |element_definition|
    resources.none? { |resource| resource_populates_element?(resource, element_definition) }
  end
end

#find_pattern_codeable_concept_slice(element, discriminator) ⇒ Object



328
329
330
331
332
333
# File 'lib/inferno/dsl/must_support_assessment.rb', line 328

def find_pattern_codeable_concept_slice(element, discriminator)
  coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
  find_a_value_at(element, coding_path) do |coding|
    coding.code == discriminator[:code] && coding.system == discriminator[:system]
  end
end

#find_pattern_coding_slice(element, discriminator) ⇒ Object



335
336
337
338
339
340
# File 'lib/inferno/dsl/must_support_assessment.rb', line 335

def find_pattern_coding_slice(element, discriminator)
  coding_path = discriminator[:path].present? ? discriminator[:path] : ''
  find_a_value_at(element, coding_path) do |coding|
    coding.code == discriminator[:code] && coding.system == discriminator[:system]
  end
end

#find_pattern_identifier_slice(element, discriminator) ⇒ Object



342
343
344
345
346
# File 'lib/inferno/dsl/must_support_assessment.rb', line 342

def find_pattern_identifier_slice(element, discriminator)
  find_a_value_at(element, discriminator[:path]) do |identifier|
    identifier.system == discriminator[:system]
  end
end

#find_required_binding_slice(element, discriminator) ⇒ Object



379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/inferno/dsl/must_support_assessment.rb', line 379

def find_required_binding_slice(element, discriminator)
  coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'

  find_a_value_at(element, coding_path) do |coding|
    discriminator[:values].any? do |value|
      case value
      when String
        value == coding.code
      when Hash
        value[:system] == coding.system && value[:code] == coding.code
      end
    end
  end
end

#find_slice(resource, path, discriminator) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/inferno/dsl/must_support_assessment.rb', line 306

def find_slice(resource, path, discriminator)
  # TODO: there is a lot of similarity
  # between this and FHIRResourceNavigation.matching_slice?
  # Can these be combined?
  find_a_value_at(resource, path) do |element|
    case discriminator[:type]
    when 'patternCodeableConcept'
      find_pattern_codeable_concept_slice(element, discriminator)
    when 'patternCoding'
      find_pattern_coding_slice(element, discriminator)
    when 'patternIdentifier'
      find_pattern_identifier_slice(element, discriminator)
    when 'value'
      find_value_slice(element, discriminator)
    when 'type'
      find_type_slice(element, discriminator)
    when 'requiredBinding'
      find_required_binding_slice(element, discriminator)
    end
  end
end

#find_slice_by_values(element, value_definitions) ⇒ Object



394
395
396
# File 'lib/inferno/dsl/must_support_assessment.rb', line 394

def find_slice_by_values(element, value_definitions)
  Array.wrap(element).find { |el| verify_slice_by_values(el, value_definitions) }
end

#find_type_slice(element, discriminator) ⇒ Object



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/inferno/dsl/must_support_assessment.rb', line 353

def find_type_slice(element, discriminator)
  case discriminator[:code]
  when 'Date'
    begin
      Date.parse(element)
    rescue ArgumentError, TypeError
      false
    end
  when 'DateTime'
    begin
      DateTime.parse(element)
    rescue ArgumentError, TypeError
      false
    end
  when 'String'
    element.is_a? String
  else
    if element.is_a? FHIR::Bundle::Entry
      # Special case for type slicing in a Bundle - look at the resource not the entry
      element = element.resource
    end

    element.is_a? FHIR.const_get(discriminator[:code])
  end
end

#find_value_slice(element, discriminator) ⇒ Object



348
349
350
351
# File 'lib/inferno/dsl/must_support_assessment.rb', line 348

def find_value_slice(element, discriminator)
  values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
  find_slice_by_values(element, values)
end

#handle_must_support_choicesObject



113
114
115
116
117
# File 'lib/inferno/dsl/must_support_assessment.rb', line 113

def handle_must_support_choices
  handle_must_support_element_choices
  handle_must_support_extension_choices
  handle_must_support_slice_choices
end

#handle_must_support_element_choicesObject



119
120
121
122
123
124
125
126
127
# File 'lib/inferno/dsl/must_support_assessment.rb', line 119

def handle_must_support_element_choices
  missing_elements.delete_if do |element|
    choices = .must_supports[:choices].find do |choice|
      choice[:paths]&.include?(element[:path]) ||
        choice[:elements]&.any? { |ms_element| ms_element[:path] == element[:path] }
    end
    any_choice_supported?(choices)
  end
end

#handle_must_support_extension_choicesObject



129
130
131
132
133
134
135
136
# File 'lib/inferno/dsl/must_support_assessment.rb', line 129

def handle_must_support_extension_choices
  missing_extensions.delete_if do |extension|
    choices = .must_supports[:choices].find do |choice|
      choice[:extension_ids]&.include?(extension[:id])
    end
    any_choice_supported?(choices)
  end
end

#handle_must_support_slice_choicesObject



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

def handle_must_support_slice_choices
  missing_slices.delete_if do |slice|
    choices = .must_supports[:choices].find { |choice| choice[:slice_names]&.include?(slice[:name]) }
    any_choice_supported?(choices)
  end
end

#matches_fixed_value?(value, fixed_value) ⇒ Boolean

Returns:

  • (Boolean)


283
284
285
# File 'lib/inferno/dsl/must_support_assessment.rb', line 283

def matches_fixed_value?(value, fixed_value)
  fixed_value.blank? || value == fixed_value
end

#matching_without_extensions?(value, ms_extension_urls, fixed_value) ⇒ Boolean

Returns:

  • (Boolean)


271
272
273
274
275
276
277
278
279
280
281
# File 'lib/inferno/dsl/must_support_assessment.rb', line 271

def matching_without_extensions?(value, ms_extension_urls, fixed_value)
  if value.instance_of?(Inferno::DSL::PrimitiveType)
    urls = value.extension&.map(&:url)
    has_ms_extension = (urls & ms_extension_urls).present?
    value = value.value
  end

  return false unless has_ms_extension || value_without_extensions?(value)

  matches_fixed_value?(value, fixed_value)
end

#missing_element_string(element_definition) ⇒ Object



190
191
192
193
194
195
196
# File 'lib/inferno/dsl/must_support_assessment.rb', line 190

def missing_element_string(element_definition)
  if element_definition[:fixed_value].present?
    "#{element_definition[:path]}:#{element_definition[:fixed_value]}"
  else
    element_definition[:path]
  end
end

#missing_elements(resources = []) ⇒ Object



225
226
227
# File 'lib/inferno/dsl/must_support_assessment.rb', line 225

def missing_elements(resources = [])
  @missing_elements ||= find_missing_elements(resources, must_support_elements)
end

#missing_extensions(resources = []) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/inferno/dsl/must_support_assessment.rb', line 202

def missing_extensions(resources = [])
  @missing_extensions ||=
    must_support_extensions.select do |extension_definition|
      resources.none? do |resource|
        path = extension_definition[:path]

        if path == 'extension'
          resource.extension.any? { |extension| extension.url == extension_definition[:url] }
        else
          extension = find_a_value_at(resource, path) do |el|
            el.url == extension_definition[:url]
          end

          extension.present?
        end
      end
    end
end

#missing_must_support_stringsObject



184
185
186
187
188
# File 'lib/inferno/dsl/must_support_assessment.rb', line 184

def missing_must_support_strings
  missing_elements.map { |element_definition| missing_element_string(element_definition) } +
    missing_slices.map { |slice_definition| slice_definition[:slice_id] } +
    missing_extensions.map { |extension_definition| extension_definition[:id] }
end

#missing_slices(resources = []) ⇒ Object



296
297
298
299
300
301
302
303
304
# File 'lib/inferno/dsl/must_support_assessment.rb', line 296

def missing_slices(resources = [])
  @missing_slices ||=
    must_support_slices.select do |slice|
      resources.none? do |resource|
        path = slice[:path]
        find_slice(resource, path, slice[:discriminator]).present?
      end
    end
end

#must_support_elementsObject



221
222
223
# File 'lib/inferno/dsl/must_support_assessment.rb', line 221

def must_support_elements
  .must_supports[:elements]
end

#must_support_extensionsObject



198
199
200
# File 'lib/inferno/dsl/must_support_assessment.rb', line 198

def must_support_extensions
  .must_supports[:extensions]
end

#must_support_slicesObject



292
293
294
# File 'lib/inferno/dsl/must_support_assessment.rb', line 292

def must_support_slices
  .must_supports[:slices]
end

#perform_must_support_test_with_metadata(resources, profile_metadata, debug_metadata: false) ⇒ Array<String>

perform_must_support_test_with_metadata is invoked from check and perform_must_support_test, with the metadata to be used as the basis for the test. It may also be invoked directly from a test if you want to completely overwrite the metadata.

Parameters:

  • resources (Array<FHIR::Model>)
  • profile_metadata (Metadata)

    Metadata object with must_supports field

  • debug_metadata (Boolean) (defaults to: false)

    if true, write out the final metadata used to a temporary file

Returns:

  • (Array<String>)

    list of elements that were not found in the provided resources



79
80
81
82
83
84
85
86
87
# File 'lib/inferno/dsl/must_support_assessment.rb', line 79

def (resources, , debug_metadata: false)
  return if resources.blank?

   = 

   if 

  perform_test(resources)
end

#perform_test(resources) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/inferno/dsl/must_support_assessment.rb', line 103

def perform_test(resources)
  missing_elements(resources)
  missing_slices(resources)
  missing_extensions(resources)

  handle_must_support_choices if .must_supports[:choices].present?

  missing_must_support_strings
end

#process_must_support_element_in_extension(resource, path) ⇒ Object



252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/inferno/dsl/must_support_assessment.rb', line 252

def process_must_support_element_in_extension(resource, path)
  return [resource, path] unless path.start_with?('extension:')

  path_without_prefix = path.delete_prefix('extension:')
  extension_split = path_without_prefix.split('.')
  extension_name = extension_split.first
  extension_path = extension_split.last

  found_extension_url = must_support_extensions.find { |ex| ex[:id].include?(extension_name) }[:url]
  ms_element_extension = resource.extension.find { |ex| ex.url == found_extension_url }

  if ms_element_extension.present?
    resource = ms_element_extension
    path = extension_path
  end

  [resource, path]
end

#resource_populates_element?(resource, element_definition) ⇒ Boolean

Returns:

  • (Boolean)


235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/inferno/dsl/must_support_assessment.rb', line 235

def resource_populates_element?(resource, element_definition)
  path = element_definition[:path]

  # handle MustSupport element under extension: Ex: extension:supporting-info.value[x]
  resource, path = process_must_support_element_in_extension(resource, path) if path.start_with?('extension:')

  ms_extension_urls = must_support_extensions.select { |ex| ex[:path] == "#{path}.extension" }
    .map { |ex| ex[:url] }

  value_found = find_a_value_at(resource, path) do |potential_value|
    matching_without_extensions?(potential_value, ms_extension_urls, element_definition[:fixed_value])
  end

  # Note that false.present? => false, which is why we need to add this extra check
  value_found.present? || value_found == false
end

#value_without_extensions?(value) ⇒ Boolean

Returns:

  • (Boolean)


287
288
289
290
# File 'lib/inferno/dsl/must_support_assessment.rb', line 287

def value_without_extensions?(value)
  value_without_extensions = value.respond_to?(:to_hash) ? value.to_hash.except('extension') : value
  value_without_extensions.present? || value_without_extensions == false
end

#write_metadata_for_debuggingObject



93
94
95
96
97
98
99
100
101
# File 'lib/inferno/dsl/must_support_assessment.rb', line 93

def 
  outfile = "#{metadata.profile&.id}-#{SecureRandom.uuid}.yml"

  File.open(File.join(Dir.tmpdir, outfile), 'w') do |f|
     = { must_supports: .must_supports.to_hash }
    f.write(YAML.dump())
    puts "Wrote MustSupport metadata to #{f.path}"
  end
end