Class: Inferno::DSL::MustSupportAssessment::InternalMustSupportLogic
- Inherits:
-
Object
- Object
- Inferno::DSL::MustSupportAssessment::InternalMustSupportLogic
show all
- Includes:
- FHIRResourceNavigation
- Defined in:
- lib/inferno/dsl/must_support_assessment.rb
Constant Summary
FHIRResourceNavigation::DAR_EXTENSION_URL, FHIRResourceNavigation::PRIMITIVE_DATA_TYPES
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#any_choice_supported?(choices) ⇒ Boolean
-
#any_elements_choice_supported?(choices) ⇒ Boolean
-
#any_extension_ids_choice_supported?(choices) ⇒ Boolean
-
#any_path_choice_supported?(choices) ⇒ Boolean
-
#any_slice_names_choice_supported?(choices) ⇒ Boolean
-
#extension_definition_candidates(path_matching_extensions) ⇒ Object
-
#extension_urls(value) ⇒ Object
-
#extract_metadata(profile, ig, requirement_extension: nil) ⇒ Object
-
#find_missing_elements(resources, must_support_elements) ⇒ Object
-
#find_pattern_codeable_concept_slice(element, discriminator) ⇒ Object
-
#find_pattern_coding_slice(element, discriminator) ⇒ Object
-
#find_pattern_identifier_slice(element, discriminator) ⇒ Object
-
#find_required_binding_slice(element, discriminator) ⇒ Object
-
#find_slice(resource, path, discriminator) ⇒ Object
-
#find_slice_by_values(element, value_definitions) ⇒ Object
-
#find_type_slice(element, discriminator) ⇒ Object
-
#find_value_slice(element, discriminator) ⇒ Object
-
#handle_must_support_choices ⇒ Object
-
#handle_must_support_element_choices ⇒ Object
-
#handle_must_support_extension_choices ⇒ Object
-
#handle_must_support_slice_choices ⇒ Object
-
#initialize(metadata = nil) ⇒ InternalMustSupportLogic
constructor
A new instance of InternalMustSupportLogic.
-
#matches_fixed_value?(value, fixed_value) ⇒ Boolean
-
#matching_without_extensions?(value, ms_extension_urls, fixed_value) ⇒ Boolean
-
#missing_element_string(element_definition) ⇒ Object
-
#missing_elements(resources = []) ⇒ Object
-
#missing_extensions(resources = []) ⇒ Object
-
#missing_must_support_strings ⇒ Object
-
#missing_slices(resources = []) ⇒ Object
-
#must_support_elements ⇒ Object
-
#must_support_extension_definition(extension_path, extension_type, extension_name) ⇒ Object
-
#must_support_extension_present?(value, ms_extension_urls) ⇒ Boolean
-
#must_support_extensions ⇒ Object
-
#must_support_slices ⇒ Object
-
#navigation_compatible_must_support_path(path) ⇒ Object
-
#normalized_extension_url(url) ⇒ Object
-
#normalized_extension_urls(urls) ⇒ Object
-
#normalized_must_support_path_segment(segment, logical_segments) ⇒ Object
-
#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.
-
#perform_test(resources) ⇒ Object
-
#process_must_support_element_in_extension(resource, path) ⇒ Object
-
#recursive_element_segments ⇒ Object
Names of path segments that are self-referential (eg 'item' in Questionnaire.item.item) and so should be searched at any depth of nesting, not just the literal depth of a path.
-
#required_binding_value_match?(coding, values) ⇒ Boolean
-
#resource_populates_element?(resource, element_definition) ⇒ Boolean
-
#value_without_extensions?(value) ⇒ Boolean
-
#write_metadata_for_debugging ⇒ Object
#append_path_character, #choice_path?, #current_and_child_values_match?, #extension_filter_value, #field_value, #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?, #path_segments, #populated_choice_value, #required_binding_codings, #resolve_path, #slice_path?, #sliced_choice_path?, #sliced_choice_value, #split_path_segment_or_append, #update_path_segment_state, #value_at_path_matches?, #value_not_empty?, #verify_slice_by_values
Constructor Details
Returns a new instance of InternalMustSupportLogic.
72
73
74
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 72
def initialize(metadata = nil)
@metadata = metadata
end
|
Instance Attribute Details
Returns the value of attribute metadata.
70
71
72
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 70
def metadata
@metadata
end
|
Instance Method Details
#any_choice_supported?(choices) ⇒ Boolean
149
150
151
152
153
154
155
156
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 149
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
178
179
180
181
182
183
184
185
186
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 178
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
164
165
166
167
168
169
170
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 164
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
158
159
160
161
162
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 158
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
172
173
174
175
176
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 172
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
|
#extension_definition_candidates(path_matching_extensions) ⇒ Object
298
299
300
301
302
303
304
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 298
def extension_definition_candidates(path_matching_extensions)
[
[path_matching_extensions, :end_with?],
[path_matching_extensions, :include?],
[must_support_extensions, :end_with?]
]
end
|
#extension_urls(value) ⇒ Object
344
345
346
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 344
def extension_urls(value)
Array.wrap(value.extension).map { |extension| normalized_extension_url(extension.url) }
end
|
93
94
95
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 93
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
243
244
245
246
247
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 243
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
401
402
403
404
405
406
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 401
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
408
409
410
411
412
413
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 408
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
415
416
417
418
419
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 415
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
452
453
454
455
456
457
458
459
460
461
462
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 452
def find_required_binding_slice(element, discriminator)
if element.is_a?(FHIR::Coding) && required_binding_value_match?(element, discriminator[:values])
return element
end
coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
find_a_value_at(element, coding_path) do |coding|
required_binding_value_match?(coding, discriminator[:values])
end
end
|
#find_slice(resource, path, discriminator) ⇒ Object
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 379
def find_slice(resource, path, discriminator)
find_a_value_at(resource, path, recursive_segments: recursive_element_segments) 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
464
465
466
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 464
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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 426
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
element = element.resource
end
element.is_a? FHIR.const_get(discriminator[:code])
end
end
|
#find_value_slice(element, discriminator) ⇒ Object
421
422
423
424
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 421
def find_value_slice(element, discriminator)
values = discriminator[:values].map { |value| value.merge(path: path_segments(value[:path])) }
find_slice_by_values(element, values)
end
|
#handle_must_support_choices ⇒ Object
117
118
119
120
121
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 117
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_choices ⇒ Object
123
124
125
126
127
128
129
130
131
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 123
def handle_must_support_element_choices
missing_elements.delete_if do |element|
choices = metadata.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_choices ⇒ Object
133
134
135
136
137
138
139
140
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 133
def handle_must_support_extension_choices
missing_extensions.delete_if do |extension|
choices = metadata.must_supports[:choices].find do |choice|
choice[:extension_ids]&.include?(extension[:id])
end
any_choice_supported?(choices)
end
end
|
#handle_must_support_slice_choices ⇒ Object
142
143
144
145
146
147
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 142
def handle_must_support_slice_choices
missing_slices.delete_if do |slice|
choices = metadata.must_supports[:choices].find { |choice| choice[:slice_names]&.include?(slice[:name]) }
any_choice_supported?(choices)
end
end
|
#matches_fixed_value?(value, fixed_value) ⇒ Boolean
356
357
358
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 356
def matches_fixed_value?(value, fixed_value)
fixed_value.blank? || value == fixed_value
end
|
#matching_without_extensions?(value, ms_extension_urls, fixed_value) ⇒ Boolean
328
329
330
331
332
333
334
335
336
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 328
def matching_without_extensions?(value, ms_extension_urls, fixed_value)
has_ms_extension = must_support_extension_present?(value, ms_extension_urls)
value = value.value if value.instance_of?(Inferno::DSL::PrimitiveType)
return false unless has_ms_extension || value_without_extensions?(value)
matches_fixed_value?(value, fixed_value)
end
|
#missing_element_string(element_definition) ⇒ Object
194
195
196
197
198
199
200
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 194
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
239
240
241
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 239
def missing_elements(resources = [])
@missing_elements ||= find_missing_elements(resources, must_support_elements)
end
|
#missing_extensions(resources = []) ⇒ Object
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 212
def missing_extensions(resources = [])
@missing_extensions ||=
must_support_extensions.select do |extension_definition|
expected_url = normalized_extension_url(extension_definition[:url])
resources.none? do |resource|
path = extension_definition[:path]
if path == 'extension'
Array.wrap(resource.extension).any? do |extension|
normalized_extension_url(extension.url) == expected_url
end
else
extension = find_a_value_at(resource, path, recursive_segments: recursive_element_segments) do |el|
normalized_extension_url(el.url) == expected_url
end
extension.present?
end
end
end
end
|
#missing_must_support_strings ⇒ Object
188
189
190
191
192
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 188
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
369
370
371
372
373
374
375
376
377
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 369
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_elements ⇒ Object
235
236
237
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 235
def must_support_elements
metadata.must_supports[:elements]
end
|
#must_support_extension_definition(extension_path, extension_type, extension_name) ⇒ Object
286
287
288
289
290
291
292
293
294
295
296
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 286
def must_support_extension_definition(extension_path, extension_type, extension_name)
suffix = "#{extension_type}:#{extension_name}"
path_matching_extensions = must_support_extensions.select { |definition| definition[:path] == extension_path }
extension_definition_candidates(path_matching_extensions).each do |definitions, matcher|
match = definitions.find { |definition| definition[:id].public_send(matcher, suffix) }
return match if match.present?
end
nil
end
|
#must_support_extension_present?(value, ms_extension_urls) ⇒ Boolean
338
339
340
341
342
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 338
def must_support_extension_present?(value, ms_extension_urls)
return false unless value.respond_to?(:extension)
(extension_urls(value) & normalized_extension_urls(ms_extension_urls)).present?
end
|
#must_support_extensions ⇒ Object
202
203
204
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 202
def must_support_extensions
metadata.must_supports[:extensions]
end
|
#must_support_slices ⇒ Object
365
366
367
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 365
def must_support_slices
metadata.must_supports[:slices]
end
|
#navigation_compatible_must_support_path(path) ⇒ Object
265
266
267
268
269
270
271
272
273
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 265
def navigation_compatible_must_support_path(path)
logical_segments = []
path_segments(path).map do |segment|
normalized_segment = normalized_must_support_path_segment(segment, logical_segments)
logical_segments << segment.split(':').first
normalized_segment
end.join('.')
end
|
#normalized_extension_url(url) ⇒ Object
352
353
354
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 352
def normalized_extension_url(url)
url&.split('|')&.first
end
|
#normalized_extension_urls(urls) ⇒ Object
348
349
350
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 348
def normalized_extension_urls(urls)
Array.wrap(urls).map { |url| normalized_extension_url(url) }
end
|
#normalized_must_support_path_segment(segment, logical_segments) ⇒ Object
275
276
277
278
279
280
281
282
283
284
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 275
def normalized_must_support_path_segment(segment, logical_segments)
extension_type, extension_name = segment.match(/\A(modifierExtension|extension):(.+)\z/)&.captures
return segment if extension_type.blank?
extension_path = [logical_segments.join('.'), extension_type].reject(&:blank?).join('.')
extension_definition = must_support_extension_definition(extension_path, extension_type, extension_name)
return segment if extension_definition.blank?
"#{extension_type}.where(url='#{normalized_extension_url(extension_definition[:url])}')"
end
|
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.
83
84
85
86
87
88
89
90
91
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 83
def perform_must_support_test_with_metadata(resources, profile_metadata, debug_metadata: false)
return if resources.blank?
@metadata = profile_metadata
write_metadata_for_debugging if debug_metadata
perform_test(resources)
end
|
107
108
109
110
111
112
113
114
115
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 107
def perform_test(resources)
missing_elements(resources)
missing_slices(resources)
missing_extensions(resources)
handle_must_support_choices if metadata.must_supports[:choices].present?
missing_must_support_strings
end
|
#process_must_support_element_in_extension(resource, path) ⇒ 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 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 =
normalized_extension_url(must_support_extensions.find { |ex| ex[:id].include?(extension_name) }[:url])
ms_element_extension = resource.extension.find do |extension|
normalized_extension_url(extension.url) == found_extension_url
end
if ms_element_extension.present?
resource = ms_element_extension
path = extension_path
end
[resource, path]
end
|
#recursive_element_segments ⇒ Object
Names of path segments that are self-referential (eg 'item' in Questionnaire.item.item)
and so should be searched at any depth of nesting, not just the literal depth of a path.
208
209
210
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 208
def recursive_element_segments
Array.wrap(metadata.must_supports[:recursive_elements])
end
|
#required_binding_value_match?(coding, values) ⇒ Boolean
468
469
470
471
472
473
474
475
476
477
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 468
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
|
#resource_populates_element?(resource, element_definition) ⇒ Boolean
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 249
def resource_populates_element?(resource, element_definition)
raw_path = element_definition[:path]
path = navigation_compatible_must_support_path(raw_path)
ms_extension_urls = must_support_extensions.select { |ex| ex[:path] == "#{raw_path}.extension" }
.map { |ex| ex[:url] }
value_found =
find_a_value_at(resource, path, recursive_segments: recursive_element_segments) do |potential_value|
matching_without_extensions?(potential_value, ms_extension_urls, element_definition[:fixed_value])
end
value_found.present? || value_found == false
end
|
#value_without_extensions?(value) ⇒ Boolean
360
361
362
363
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 360
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
|
97
98
99
100
101
102
103
104
105
|
# File 'lib/inferno/dsl/must_support_assessment.rb', line 97
def write_metadata_for_debugging
outfile = "#{metadata.profile&.id}-#{SecureRandom.uuid}.yml"
File.open(File.join(Dir.tmpdir, outfile), 'w') do |f|
writable_metadata = { must_supports: @metadata.must_supports.to_hash }
f.write(YAML.dump(writable_metadata))
puts "Wrote MustSupport metadata to #{f.path}"
end
end
|