Class: USCoreTestKit::Generator::MustSupportMetadataExtractor

Inherits:
Object
  • Object
show all
Defined in:
lib/us_core_test_kit/generator/must_support_metadata_extractor.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(profile_elements, profile, resource, ig_resources) ⇒ MustSupportMetadataExtractor

Returns a new instance of MustSupportMetadataExtractor.



13
14
15
16
17
18
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 13

def initialize(profile_elements, profile, resource, ig_resources)
  self.profile_elements = profile_elements
  self.profile = profile
  self.resource = resource
  self.ig_resources = ig_resources
end

Instance Attribute Details

#ig_resourcesObject

Returns the value of attribute ig_resources.



11
12
13
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 11

def ig_resources
  @ig_resources
end

#profileObject

Returns the value of attribute profile.



11
12
13
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 11

def profile
  @profile
end

#profile_elementsObject

Returns the value of attribute profile_elements.



11
12
13
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 11

def profile_elements
  @profile_elements
end

#resourceObject

Returns the value of attribute resource.



11
12
13
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 11

def resource
  @resource
end

Instance Method Details

#all_must_support_elementsObject



39
40
41
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 39

def all_must_support_elements
  profile_elements.select { |element| (element.mustSupport || is_uscdi_requirement_element?(element))}
end

#discriminators(slice) ⇒ Object



69
70
71
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 69

def discriminators(slice)
  slice.slicing.discriminator
end

#element_part_of_slice_discrimination?(element) ⇒ Boolean

Returns:

  • (Boolean)


219
220
221
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 219

def element_part_of_slice_discrimination?(element)
  must_support_slice_elements.any? { |ms_slice| element.id.include?(ms_slice.id) }
end

#get_type_must_support_metadata(current_metadata, current_element) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 249

def (, current_element)
  current_element.type.map do |type|
    if type_must_support_extension?(type.extension)
       =
      {
        path: "#{[:path].delete_suffix('[x]')}#{type.code.upcase_first}",
        original_path: [:path]
      }
      [:types] = [type.code] if save_type_code?(type)
      handle_type_must_support_target_profiles(type, ) if type.code == 'Reference'

      
    end
  end.compact
end

#handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 288

def handle_choice_type_in_sliced_element(, )
   = .find do ||
    [:original_path].present? &&
    [:path].include?( [:original_path] )
  end

  if .present?
    [:original_path] = [:path]
    [:path] = [:path].sub([:original_path], [:path])
  end
end

#handle_fixed_values(metadata, element) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 224

def handle_fixed_values(, element)
  if element.fixedUri.present?
    [:fixed_value] = element.fixedUri
  elsif element.patternCodeableConcept.present? && !element_part_of_slice_discrimination?(element)
    [:fixed_value] = element.patternCodeableConcept.coding.first.code
    [:path] += '.coding.code'
  elsif element.fixedCode.present?
    [:fixed_value] = element.fixedCode
  elsif element.patternIdentifier.present? && !element_part_of_slice_discrimination?(element)
    [:fixed_value] = element.patternIdentifier.system
    [:path] += '.system'
  end
end

#handle_special_casesObject

SPECIAL CASE ####



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 335

def handle_special_cases
  remove_vital_sign_component
  remove_blood_pressure_value_data_absent_reason
  remove_observation_data_absent_reason

  case profile.version
  when '3.1.1'
    MustSupportMetadataExtractorUsCore3.new(profile, @must_supports).handle_special_cases
  when '4.0.0'
    MustSupportMetadataExtractorUsCore4.new(profile, @must_supports).handle_special_cases
  when '5.0.1'
    MustSupportMetadataExtractorUsCore5.new(profile, @must_supports).handle_special_cases
  when '6.1.0'
    MustSupportMetadataExtractorUsCore6.new(profile, @must_supports).handle_special_cases
  when '7.0.0'
    MustSupportMetadataExtractorUsCore7.new(profile, @must_supports).handle_special_cases
  end
end

#handle_type_must_support_target_profiles(type, metadata) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 265

def handle_type_must_support_target_profiles(type, )
  # US Core 3.1.1 profiles do not have US Core target profiles.
  # Vital Sign proifles from FHIR R4 (version 4.0.1) do not have US Core target profiles either.
  return if ['3.1.1', '4.0.1'].include?(profile.version)

  target_profiles = []

  if type.targetProfile&.length == 1
    target_profiles << type.targetProfile.first
  else
    type.source_hash['_targetProfile']&.each_with_index do |hash, index|
      if hash.present?
        element = FHIR::Element.new(hash)
        target_profiles << type.targetProfile[index] if type_must_support_extension?(element.extension)
      end
    end
  end

  # remove target_profile for FHIR Base resource type.
  target_profiles.delete_if { |reference| reference.start_with?('http://hl7.org/fhir/StructureDefinition')}
  [:target_profiles] = target_profiles if target_profiles.present?
end

#is_blood_pressure?Boolean

Returns:

  • (Boolean)


361
362
363
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 361

def is_blood_pressure?
  ['bp', 'us-core-blood-pressure', 'us-core-average-blood-pressure'].include?(profile.id)
end

#is_uscdi_requirement_element?(element) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
35
36
37
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 32

def is_uscdi_requirement_element?(element)
  element.extension.any? do |extension|
    extension.url == 'http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement' &&
    extension.valueBoolean
  end && !element.mustSupport
end

#is_vital_sign?Boolean

Returns:

  • (Boolean)


354
355
356
357
358
359
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 354

def is_vital_sign?
  [
    'http://hl7.org/fhir/StructureDefinition/vitalsigns',
    'http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs'
  ].include?(profile.baseDefinition)
end

#must_support_elementsObject



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 300

def must_support_elements
  plain_must_support_elements.each_with_object([]) do |current_element, |
    {
      path: current_element.id.gsub("#{resource}.", '')
    }.tap do ||
      if is_uscdi_requirement_element?(current_element)
        [:uscdi_only] = true
      end

       = (, current_element)

      if .any?
        .concat()
      else
        handle_choice_type_in_sliced_element(, )

        supported_types = current_element.type.select { |type| save_type_code?(type) }.map { |type| type.code }
        [:types] = supported_types if supported_types.present?

        handle_type_must_support_target_profiles(current_element.type.first, ) if current_element.type.first&.code == 'Reference'

        handle_fixed_values(, current_element)

        .delete_if do ||
          [:path] == [:path] && [:fixed_value].blank?
        end

         << 
      end
    end
  end.uniq
end

#must_support_extension_elementsObject



43
44
45
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 43

def must_support_extension_elements
  all_must_support_elements.select { |element| element.path.end_with? 'extension' }
end

#must_support_extensionsObject



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 47

def must_support_extensions
  must_support_extension_elements.map do |element|
    {
      id: element.id,
      path: element.path.gsub("#{resource}.", ''),
      url: element.type.first.profile.first
    }.tap do ||
      if is_uscdi_requirement_element?(element)
        [:uscdi_only] = true
      end
    end
  end
end

#must_support_pattern_slice_elementsObject



73
74
75
76
77
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 73

def must_support_pattern_slice_elements
  must_support_slice_elements.select do |element|
    discriminators(sliced_element(element)).first.type == 'pattern'
  end
end

#must_support_slice_elementsObject



61
62
63
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 61

def must_support_slice_elements
  all_must_support_elements.select { |element| !element.path.end_with?('extension') && element.sliceName.present? }
end

#must_support_slicesObject



211
212
213
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 211

def must_support_slices
  pattern_slices + type_slices + value_slices
end

#must_support_type_slice_elementsObject



140
141
142
143
144
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 140

def must_support_type_slice_elements
  must_support_slice_elements.select do |element|
    discriminators(sliced_element(element)).first.type == 'type'
  end
end

#must_support_value_slice_elementsObject



176
177
178
179
180
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 176

def must_support_value_slice_elements
  must_support_slice_elements.select do |element|
    discriminators(sliced_element(element)).first.type == 'value'
  end
end

#must_supportsObject



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 20

def must_supports
  @must_supports = {
    extensions: must_support_extensions,
    slices: must_support_slices,
    elements: must_support_elements
  }

  handle_special_cases

  @must_supports
end

#pattern_slicesObject



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
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 79

def pattern_slices
  must_support_pattern_slice_elements.map do |current_element|
    {
      slice_id: current_element.id,
      slice_name: current_element.sliceName,
      path: current_element.path.gsub("#{resource}.", '')
    }.tap do ||
      discriminator = discriminators(sliced_element(current_element)).first
      discriminator_path = discriminator.path
      discriminator_path = '' if discriminator_path == '$this'
      pattern_element =
        if discriminator_path.present?
          profile_elements.find { |element| element.id == "#{current_element.id}.#{discriminator_path}" }
        else
          current_element
        end
      [:discriminator] =
        if pattern_element.patternCodeableConcept.present?
          {
            type: 'patternCodeableConcept',
            path: discriminator_path,
            code: pattern_element.patternCodeableConcept.coding.first.code,
            system: pattern_element.patternCodeableConcept.coding.first.system
          }
        elsif pattern_element.patternCoding.present?
          {
            type: 'patternCoding',
            path: discriminator_path,
            code: pattern_element.patternCoding.code,
            system: pattern_element.patternCoding.system
          }
        elsif pattern_element.patternIdentifier.present?
          {
            type: 'patternIdentifier',
            path: discriminator_path,
            system: pattern_element.patternIdentifier.system
          }
        elsif pattern_element.binding&.strength == 'required' &&
              pattern_element.binding&.valueSet.present?

          value_extractor = ValueExactor.new(ig_resources, resource, profile_elements)

          values = value_extractor.codings_from_value_set_binding(pattern_element).presence ||
                   value_extractor.([[:path]]).presence || []

          {
            type: 'requiredBinding',
            path: discriminator_path,
            values: values
          }
        else
          raise StandardError, 'Unsupported discriminator pattern type'
        end

      if is_uscdi_requirement_element?(current_element)
        [:uscdi_only] = true
      end
    end
  end
end

#plain_must_support_elementsObject



215
216
217
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 215

def plain_must_support_elements
  plain_must_supports = all_must_support_elements - must_support_extension_elements - must_support_slice_elements
end

#remove_blood_pressure_value_data_absent_reasonObject

Exclude Observation.value[x] from observation-bp



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 377

def remove_blood_pressure_value_data_absent_reason
  return unless is_blood_pressure?

  pattern = /component(:[^.]+)?\.dataAbsentReason/

  @must_supports[:elements].delete_if do |element|
    element[:path].start_with?('value[x]') ||
    element[:original_path]&.start_with?('value[x]') ||
    element[:path] == ('dataAbsentReason') ||
    (
      pattern.match?(element[:path]) && ['3.1.1', '4.0.0'].include?(ig_resources.ig.version)
    )
  end

  @must_supports[:slices].delete_if do |slice|
    slice[:path].start_with?('value[x]')
  end
end

#remove_observation_data_absent_reasonObject

ONC and US Core 4.0.0 both clarified that health IT developers that always provide HL7 FHIR “observation” values are not required to demonstrate Health IT Module support for “dataAbsentReason” elements. Remove MS check for dataAbsentReason and component.dataAbsentReason from vital sign profiles and observation lab profile Smoking status profile does not have MS on dataAbsentReason. It is safe to use profile.type == ‘Observation’ Since US Core 5.0.1, Blood Pressure profile restores component.dataAbsentReason as MustSupport.



401
402
403
404
405
406
407
408
409
410
411
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 401

def remove_observation_data_absent_reason
  return if is_blood_pressure?

  pattern = /(component(:[^.]+)?\.)?dataAbsentReason/

  if profile.type == 'Observation'
    @must_supports[:elements].delete_if do |element|
      pattern.match?(element[:path])
    end
  end
end

#remove_vital_sign_componentObject

Exclude Observation.component from vital sign profiles except observation-bp and observation-pulse-ox



366
367
368
369
370
371
372
373
374
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 366

def remove_vital_sign_component
  return if is_blood_pressure? || profile.name == 'USCorePulseOximetryProfile'

  if is_vital_sign?
    @must_supports[:elements].delete_if do |element|
      element[:path].start_with?('component')
    end
  end
end

#save_type_code?(type) ⇒ Boolean

Returns:

  • (Boolean)


245
246
247
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 245

def save_type_code?(type)
  'Reference' == type.code
end

#sliced_element(slice) ⇒ Object



65
66
67
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 65

def sliced_element(slice)
  profile_elements.find { |element| element.id == slice.path || element.id == slice.id.sub(":#{slice.sliceName}", '') }
end

#type_must_support_extension?(extensions) ⇒ Boolean

Returns:

  • (Boolean)


238
239
240
241
242
243
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 238

def type_must_support_extension?(extensions)
  extensions&.any? do |extension|
    extension.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support' &&
    extension.valueBoolean
  end
end

#type_slicesObject



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 146

def type_slices
  must_support_type_slice_elements.map do |current_element|
    discriminator = discriminators(sliced_element(current_element)).first
    type_path = discriminator.path
    type_path = '' if type_path == '$this'
    type_element =
      if type_path.present?
        elements.find { |element| element.id == "#{current_element.id}.#{type_path}" }
      else
        current_element
      end

    type_code = type_element.type.first.code

    {
      slice_id: current_element.id,
      slice_name: current_element.sliceName,
      path: current_element.path.gsub("#{resource}.", ''),
      discriminator: {
        type: 'type',
        code: type_code.upcase_first
      }
    }.tap do ||
      if is_uscdi_requirement_element?(current_element)
        [:uscdi_only] = true
      end
    end
  end
end

#value_slicesObject



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/us_core_test_kit/generator/must_support_metadata_extractor.rb', line 182

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}.", ''),
      discriminator: {
        type: 'value'
      }
    }.tap do ||
      [:discriminator][:values] = discriminators(sliced_element(current_element)).map do |discriminator|
        fixed_element = profile_elements.find do |element|
          element.id.starts_with?(current_element.id) &&
            element.path == "#{current_element.path}.#{discriminator.path}"
        end

        {
          path: discriminator.path,
          value: fixed_element.fixedUri || fixed_element.fixedCode
        }
      end

      if is_uscdi_requirement_element?(current_element)
        [:uscdi_only] = true
      end
    end
  end
end