Class: DaVinciPASTestKit::PASMustSupportMetadataExtractor

Inherits:
Inferno::DSL::MustSupportMetadataExtractor
  • Object
show all
Defined in:
lib/davinci_pas_test_kit/generator/pas_must_support_metadata_extractor.rb

Overview

The MustSupportMetadataExtractor takes a StructureDefinition and parses it into a hash-based metadata that simplifies checking for MustSupport elements. MustSupport elements may be either plain elements, extensions, or slices. This logic was originally developed for the US Core Test Kit and has been migrated into Inferno core.

Instance Method Summary collapse

Instance Method Details

#find_element_by_discriminator_path(current_element, discriminator_path) ⇒ Object



31
32
33
34
35
36
37
38
39
40
# File 'lib/davinci_pas_test_kit/generator/pas_must_support_metadata_extractor.rb', line 31

def find_element_by_discriminator_path(current_element, discriminator_path)
  target_element = current_element
  remaining_path = discriminator_path

  while remaining_path.present?
    target_element, remaining_path = take_discriminator_step(target_element, remaining_path)
  end

  target_element
end

#save_pattern_slice(pattern_element, discriminator_path, metadata) ⇒ Object

Override save_pattern_slice to handle patternIdentifier slices that discriminate on identifier.type.coding (e.g. Organization.identifier:PI) rather than identifier.system. inferno_core only extracts .system from patternIdentifier, leaving it nil for type-coded slices, which causes find_pattern_identifier_slice to never match at runtime. When patternIdentifier.type.coding is present, emit a patternCodeableConcept discriminator on path 'type' instead, which inferno_core CAN navigate at runtime.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/davinci_pas_test_kit/generator/pas_must_support_metadata_extractor.rb', line 15

def save_pattern_slice(pattern_element, discriminator_path, )
  pi_type = pattern_element.patternIdentifier&.type
  coding = pi_type&.coding&.first
  if coding
    runtime_path = navigation_compatible_discriminator_path(discriminator_path)
    path = runtime_path.present? ? "#{runtime_path}.type" : 'type'
    return {
      type: 'patternCodeableConcept',
      path: path,
      code: coding.code,
      system: coding.system
    }
  end
  super
end

#take_discriminator_step(current_element, path) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/davinci_pas_test_kit/generator/pas_must_support_metadata_extractor.rb', line 43

def take_discriminator_step(current_element, path)
  if path.start_with?('extension(')
    ext_url, remaining_path = path.delete_prefix("extension('").split("')", 2)
    next_element = profile_elements.find do |element|
      element.path == "#{current_element.path}.extension" &&
        element.id.start_with?("#{current_element.id}.extension:") &&
        element.type.any? { |type| type.code == 'Extension' && type.profile.any? { |p| p.start_with?(ext_url) } }
    end
  else
    step_path, remaining_path = path.split('.', 2)
    if remaining_path&.start_with?('ofType(')
      target_element = "#{step_path}[x]"
      target_type, remaining_path = remaining_path.delete_prefix('ofType(').split(')', 2)
      next_element = profile_elements.find do |element|
        element.id == "#{current_element.id}.#{target_element}" &&
          element.type.any? { |type| type.code == target_type }
      end
    elsif step_path.include?(' as ')
      # Handle FHIR FHIRPath "value as boolean" cast syntax (equivalent to ofType())
      base_step, cast_type = step_path.split(' as ', 2)
      target_element_path = "#{base_step.strip}[x]"
      next_element = profile_elements.find do |element|
        element.id == "#{current_element.id}.#{target_element_path}" &&
          element.type.any? { |type| type.code == cast_type.strip }
      end
    else
      next_element =
        profile_elements.find { |element| element.id == "#{current_element.id}.#{step_path}" } ||
        profile_elements.find { |element| element.id == "#{current_element.path}.#{step_path}" }
    end
  end

  [next_element, remaining_path&.delete_prefix('.')]
end

#value_slicesObject

rubocop:enable Metrics/CyclomaticComplexity



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/davinci_pas_test_kit/generator/pas_must_support_metadata_extractor.rb', line 79

def value_slices
  must_support_value_slice_elements.map do |current_element|
    {
      slice_id: current_element.id,
      slice_name: current_element.sliceName,
      path: current_element.path.gsub("#{resource}.", '')
    }.tap do ||
      fixed_values = []
      pattern_value = {}

      element_discriminators = discriminators(sliced_element(current_element))

      element_discriminators.each do |discriminator|
        discriminator_path = discriminator_path(discriminator)
        pattern_element = find_element_by_discriminator_path(current_element, discriminator_path)

        # This is a workaround for a known version of a profile that has a bad discriminator:
        # the discriminator refers to a nested field within a CodeableConcept,
        # but the profile doesn't contain an element definition for it, so there's no way to
        # define a fixed value on the element to define the slice.
        # In this instance the element has a second (good) discriminator on the CodeableConcept field itself,
        # and in subsequent versions of the profile, the bad discriminator was removed.
        next if pattern_element.nil? && element_discriminators.length > 1

        if !pattern_element.fixed.nil?
          fixed_values << {
            path: navigation_compatible_discriminator_path(discriminator_path),
            value: pattern_element.fixed
          }
        elsif pattern_value.present?
          raise StandardError, "Found more than one pattern slices for the same element #{pattern_element}."
        else
          pattern_value = save_pattern_slice(pattern_element, discriminator_path, )
        end
      end

      if fixed_values.present?
        [:discriminator] = {
          type: 'value',
          values: fixed_values
        }
      elsif pattern_value.present?
        [:discriminator] = pattern_value
      end

      [:by_requirement_extension_only] = true if by_requirement_extension_only?(current_element)
    end
  end
end