pacio_inferno_core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/pacio_inferno_core/custom_groups/capability_statement/conformance_support_test.rb +41 -0
  4. data/lib/pacio_inferno_core/custom_groups/capability_statement/fhir_version_test.rb +15 -0
  5. data/lib/pacio_inferno_core/custom_groups/capability_statement/instantiate_test.rb +23 -0
  6. data/lib/pacio_inferno_core/custom_groups/capability_statement/json_support_test.rb +40 -0
  7. data/lib/pacio_inferno_core/date_search_validation.rb +112 -0
  8. data/lib/pacio_inferno_core/fhir_resource_navigation.rb +7 -0
  9. data/lib/pacio_inferno_core/generator/group_generator.rb +161 -0
  10. data/lib/pacio_inferno_core/generator/group_metadata.rb +114 -0
  11. data/lib/pacio_inferno_core/generator/group_metadata_extractor.rb +301 -0
  12. data/lib/pacio_inferno_core/generator/ig_loader.rb +118 -0
  13. data/lib/pacio_inferno_core/generator/ig_metadata.rb +60 -0
  14. data/lib/pacio_inferno_core/generator/ig_metadata_extractor.rb +88 -0
  15. data/lib/pacio_inferno_core/generator/ig_resources.rb +88 -0
  16. data/lib/pacio_inferno_core/generator/must_support_metadata_extractor.rb +8 -0
  17. data/lib/pacio_inferno_core/generator/must_support_test_generator.rb +138 -0
  18. data/lib/pacio_inferno_core/generator/naming.rb +50 -0
  19. data/lib/pacio_inferno_core/generator/read_test_generator.rb +102 -0
  20. data/lib/pacio_inferno_core/generator/reference_resolution_test_generator.rb +96 -0
  21. data/lib/pacio_inferno_core/generator/search_definition_metadata_extractor.rb +228 -0
  22. data/lib/pacio_inferno_core/generator/search_metadata_extractor.rb +78 -0
  23. data/lib/pacio_inferno_core/generator/search_test_generator.rb +298 -0
  24. data/lib/pacio_inferno_core/generator/special_cases.rb +61 -0
  25. data/lib/pacio_inferno_core/generator/suite_generator.rb +105 -0
  26. data/lib/pacio_inferno_core/generator/templates/group.rb.erb +27 -0
  27. data/lib/pacio_inferno_core/generator/templates/must_support.rb.erb +36 -0
  28. data/lib/pacio_inferno_core/generator/templates/read.rb.erb +34 -0
  29. data/lib/pacio_inferno_core/generator/templates/reference_resolution.rb.erb +40 -0
  30. data/lib/pacio_inferno_core/generator/templates/search.rb.erb +40 -0
  31. data/lib/pacio_inferno_core/generator/templates/validation.rb.erb +36 -0
  32. data/lib/pacio_inferno_core/generator/terminology_binding_metadata_extractor.rb +116 -0
  33. data/lib/pacio_inferno_core/generator/validation_test_generator.rb +146 -0
  34. data/lib/pacio_inferno_core/generator/value_extractor.rb +152 -0
  35. data/lib/pacio_inferno_core/generator.rb +130 -0
  36. data/lib/pacio_inferno_core/must_support_test.rb +20 -0
  37. data/lib/pacio_inferno_core/primitive_type.rb +5 -0
  38. data/lib/pacio_inferno_core/read_test.rb +103 -0
  39. data/lib/pacio_inferno_core/reference_resolution_test.rb +181 -0
  40. data/lib/pacio_inferno_core/request_logger.rb +46 -0
  41. data/lib/pacio_inferno_core/resource_search_param_checker.rb +136 -0
  42. data/lib/pacio_inferno_core/search_test.rb +859 -0
  43. data/lib/pacio_inferno_core/search_test_properties.rb +56 -0
  44. data/lib/pacio_inferno_core/validation_test.rb +55 -0
  45. data/lib/pacio_inferno_core/version.rb +4 -0
  46. data/lib/pacio_inferno_core/well_known_code_systems.rb +21 -0
  47. metadata +165 -0
@@ -0,0 +1,114 @@
1
+ require_relative 'special_cases'
2
+
3
+ module PacioInfernoCore
4
+ class Generator
5
+ class GroupMetadata
6
+ ATTRIBUTES = %i[
7
+ name
8
+ class_name
9
+ version
10
+ reformatted_version
11
+ resource
12
+ profile_url
13
+ profile_name
14
+ profile_version
15
+ title
16
+ short_description
17
+ is_delayed
18
+ interactions
19
+ operations
20
+ searches
21
+ search_definitions
22
+ include_params
23
+ revincludes
24
+ required_concepts
25
+ must_supports
26
+ mandatory_elements
27
+ bindings
28
+ references
29
+ tests
30
+ granular_scope_tests
31
+ id
32
+ file_name
33
+ delayed_references
34
+ resource_conformance_expectation
35
+ ].freeze
36
+
37
+ ATTRIBUTES.each { |name| attr_accessor name }
38
+
39
+ def initialize(metadata)
40
+ metadata.each do |key, value|
41
+ raise "Unknown attribute #{key}" unless ATTRIBUTES.include? key
42
+
43
+ instance_variable_set(:"@#{key}", value)
44
+ end
45
+ end
46
+
47
+ def delayed?
48
+ @is_delayed ||= if resource == 'Patient'
49
+ false
50
+ else
51
+ no_patient_searches?
52
+ end
53
+ end
54
+
55
+ def exclude_search_tests?
56
+ delayed? && !searchable_delayed_resource?
57
+ end
58
+
59
+ def no_patient_searches?
60
+ searches.none? { |search| search[:names].include?('patient') && search[:expectation] == 'SHALL' }
61
+ end
62
+
63
+ def optional_profile?
64
+ SpecialCases::OPTIONAL_PROFILES.key?(profile_url) && SpecialCases::OPTIONAL_PROFILES[profile_url].include?(reformatted_version)
65
+ end
66
+
67
+ def searchable_delayed_resource?
68
+ SpecialCases::SEARCHABLE_DELAYED_RESOURCES.key?(resource) && SpecialCases::SEARCHABLE_DELAYED_RESOURCES[resource].include?(reformatted_version)
69
+ end
70
+
71
+ def add_test(id:, file_name:)
72
+ self.tests ||= []
73
+
74
+ test_metadata = {
75
+ id: id,
76
+ file_name: file_name
77
+ }
78
+
79
+ if delayed? && id.include?('read')
80
+ self.tests.unshift(test_metadata)
81
+ else
82
+ self.tests << test_metadata
83
+ end
84
+ end
85
+
86
+ def add_granular_scope_test(id:, file_name:)
87
+ self.granular_scope_tests ||= []
88
+
89
+ self.granular_scope_tests << {
90
+ id:,
91
+ file_name:
92
+ }
93
+ end
94
+
95
+ def to_hash
96
+ ATTRIBUTES.each_with_object({}) { |key, hash| hash[key] = send(key) unless send(key).nil? }
97
+ end
98
+
99
+ def add_delayed_references(delayed_profiles, ig_resources)
100
+ self.delayed_references =
101
+ references
102
+ .select { |reference| (reference[:profiles] & delayed_profiles).present? }
103
+ .map do |reference|
104
+ profile_urls = (reference[:profiles] & delayed_profiles)
105
+ delayed_resources = profile_urls.map { |url| ig_resources.resource_for_profile(url) }
106
+ {
107
+ path: reference[:path].gsub("#{resource}.", ''),
108
+ resources: delayed_resources
109
+ }
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,301 @@
1
+ require_relative 'group_metadata'
2
+ require_relative 'ig_metadata'
3
+ require_relative 'must_support_metadata_extractor'
4
+ require_relative 'search_metadata_extractor'
5
+ require_relative 'terminology_binding_metadata_extractor'
6
+ require_relative 'naming'
7
+
8
+ module PacioInfernoCore
9
+ class Generator
10
+ class GroupMetadataExtractor
11
+ attr_accessor :resource_capabilities, :profile_url, :ig_metadata, :ig_resources
12
+
13
+ def initialize(resource_capabilities, profile_url, ig_metadata, ig_resources)
14
+ self.resource_capabilities = resource_capabilities
15
+ self.profile_url = profile_url
16
+ self.ig_metadata = ig_metadata
17
+ self.ig_resources = ig_resources
18
+ end
19
+
20
+ def group_metadata
21
+ @group_metadata ||=
22
+ GroupMetadata.new(group_metadata_hash)
23
+ end
24
+
25
+ def group_metadata_hash
26
+ @group_metadata_hash ||=
27
+ {
28
+ name: name,
29
+ class_name: class_name,
30
+ version: version,
31
+ reformatted_version: reformatted_version,
32
+ # test_id_prefix: test_id_prefix,
33
+ resource: resource,
34
+ profile_url: profile_url,
35
+ profile_name: profile_name,
36
+ profile_version: profile_version,
37
+ title: title,
38
+ short_description: short_description,
39
+ interactions: interactions,
40
+ operations: operations,
41
+ searches: searches,
42
+ search_definitions: search_definitions,
43
+ include_params: include_params,
44
+ revincludes: revincludes,
45
+ required_concepts: required_concepts,
46
+ must_supports: must_supports,
47
+ mandatory_elements: mandatory_elements,
48
+ bindings: bindings,
49
+ references: references,
50
+ resource_conformance_expectation: resource_conformance_expectation
51
+ # tests: []
52
+ }
53
+
54
+ mark_mandatory_and_must_support_searches
55
+ handle_special_cases
56
+
57
+ @group_metadata_hash
58
+ end
59
+
60
+ def mark_mandatory_and_must_support_searches
61
+ searches.each do |search|
62
+ search[:names_not_must_support_or_mandatory] = search[:names].reject do |name|
63
+ full_paths = search_definitions[name.to_sym][:full_paths]
64
+ any_must_support_elements = must_supports[:elements].any? do |element|
65
+ full_must_support_paths = ["#{resource}.#{element[:original_path]}", "#{resource}.#{element[:path]}"]
66
+
67
+ full_paths.any? do |path|
68
+ # allow for non-choice, choice types, and _id
69
+ name == '_id' || full_must_support_paths.include?(path) || full_must_support_paths.include?("#{path}[x]")
70
+ end
71
+ end
72
+
73
+ any_must_support_slices = must_supports[:slices].any? do |slice|
74
+ # only handle type slices because that is all we need for now
75
+ # for a slice like Observation.effective[x]:effectiveDateTime, the search parameter's expression could be
76
+ # either Observation.effective or Observation.effectiveDateTime.
77
+ if slice[:discriminator] && slice[:discriminator][:type] == 'type'
78
+ full_must_support_path = "#{resource}.#{slice[:path].sub('[x]', slice[:discriminator][:code])}"
79
+ base_must_support_path = "#{resource}.#{slice[:path].sub('[x]', '')}"
80
+
81
+ full_paths.intersection([full_must_support_path, base_must_support_path]).present?
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ any_mandatory_elements = mandatory_elements.any? do |element|
88
+ full_paths.include?(element)
89
+ end
90
+
91
+ any_must_support_elements || any_must_support_slices || any_mandatory_elements
92
+ end
93
+
94
+ search[:must_support_or_mandatory] = search[:names_not_must_support_or_mandatory].empty?
95
+ end
96
+ end
97
+
98
+ ### BEGIN SPECIAL CASES ###
99
+ def category_first_profile?
100
+ SpecialCases::ALL_VERSION_CATEGORY_FIRST_PROFILES.include?(profile_url) ||
101
+ SpecialCases::VERSION_SPECIFIC_CATEGORY_FIRST_PROFILES[profile_url]&.include?(reformatted_version)
102
+ end
103
+
104
+ def first_search_params
105
+ @first_search_params ||=
106
+ if category_first_profile?
107
+ %w[patient category]
108
+ elsif resource == 'Observation'
109
+ %w[patient code]
110
+ elsif resource == 'MedicationRequest'
111
+ %w[patient intent]
112
+ elsif resource == 'CareTeam'
113
+ %w[patient status]
114
+ else
115
+ ['patient']
116
+ end
117
+ end
118
+
119
+ def handle_special_cases
120
+ set_first_search
121
+ end
122
+
123
+ def set_first_search
124
+ search = searches.find { |param| param[:names] == first_search_params }
125
+ return if search.nil?
126
+
127
+ searches.delete(search)
128
+ searches.unshift(search)
129
+ end
130
+
131
+ ### END SPECIAL CASES ###
132
+
133
+ def profile
134
+ @profile ||= ig_resources.profile_by_url(profile_url)
135
+ end
136
+
137
+ def profile_elements
138
+ @profile_elements ||= profile.snapshot.element
139
+ end
140
+
141
+ def base_name
142
+ profile_url.split('StructureDefinition/').last
143
+ end
144
+
145
+ def name
146
+ base_name.tr('-', '_')
147
+ end
148
+
149
+ def class_name
150
+ base_name
151
+ .split('-')
152
+ .map(&:capitalize)
153
+ .join
154
+ .gsub('UsCore', "USCore#{ig_metadata.reformatted_version}")
155
+ .concat('Sequence')
156
+ end
157
+
158
+ def version
159
+ ig_metadata.ig_version
160
+ end
161
+
162
+ def reformatted_version
163
+ ig_metadata.reformatted_version
164
+ end
165
+
166
+ def resource
167
+ resource_capabilities.type
168
+ end
169
+
170
+ def profile_name
171
+ profile.title.gsub(' ', ' ')
172
+ end
173
+
174
+ def profile_version
175
+ profile.version
176
+ end
177
+
178
+ def title
179
+ title = profile.title.gsub(/US\s*Core\s*/, '').gsub(/\s*Profile/, '').strip
180
+
181
+ if Naming.resources_with_multiple_profiles.include?(resource) && !title.start_with?(resource) && version != 'v3.1.1'
182
+ title = resource + ' ' + title.split(resource).map(&:strip).join(' ')
183
+ end
184
+
185
+ title
186
+ end
187
+
188
+ def short_description
189
+ "Verify support for the server capabilities required by the #{profile_name}."
190
+ end
191
+
192
+ def interactions
193
+ @interactions ||=
194
+ resource_capabilities.interaction.map do |interaction|
195
+ {
196
+ code: interaction.code,
197
+ expectation: interaction.extension.first.valueCode # TODO: fix expectation extension finding
198
+ }
199
+ end
200
+ end
201
+
202
+ def operations
203
+ @operations ||=
204
+ resource_capabilities.operation.map do |operation|
205
+ {
206
+ code: operation.name,
207
+ expectation: operation.extension.first.valueCode # TODO: fix expectation extension finding
208
+ }
209
+ end
210
+ end
211
+
212
+ def search_metadata_extractor
213
+ @search_metadata_extractor ||= SearchMetadataExtractor.new(
214
+ resource_capabilities,
215
+ ig_resources,
216
+ profile_elements,
217
+ {
218
+ resource: resource,
219
+ profile_url: profile_url,
220
+ must_supports: must_supports
221
+ }
222
+ )
223
+ end
224
+
225
+ def searches
226
+ @searches ||= search_metadata_extractor.searches
227
+ end
228
+
229
+ def search_definitions
230
+ @search_definitions ||= search_metadata_extractor.search_definitions
231
+ end
232
+
233
+ def include_params
234
+ resource_capabilities.searchInclude || []
235
+ end
236
+
237
+ def revincludes
238
+ resource_capabilities.searchRevInclude || []
239
+ end
240
+
241
+ def required_concepts
242
+ # The base FHIR vital signs profile has a required binding that isn't
243
+ # relevant for any of its child profiles
244
+ return [] if resource == 'Observation'
245
+
246
+ profile_elements
247
+ .select { |element| element.type&.any? { |type| type.code == 'CodeableConcept' } }
248
+ .select { |element| element.binding&.strength == 'required' }
249
+ .map { |element| element.path.gsub("#{resource}.", '').gsub('[x]', 'CodeableConcept') }
250
+ .uniq
251
+ end
252
+
253
+ def terminology_binding_metadata_extractor
254
+ @terminology_binding_metadata_extractor ||=
255
+ TerminologyBindingMetadataExtractor.new(profile_elements, ig_resources, resource)
256
+ end
257
+
258
+ def bindings
259
+ @bindings ||=
260
+ terminology_binding_metadata_extractor.terminology_bindings
261
+ end
262
+
263
+ def must_support_metadata_extractor
264
+ @must_support_metadata_extractor ||=
265
+ MustSupportMetadataExtractor.new(profile_elements, profile, resource, ig_resources)
266
+ end
267
+
268
+ def must_supports
269
+ @must_supports ||=
270
+ must_support_metadata_extractor.must_supports
271
+ end
272
+
273
+ def mandatory_elements
274
+ @mandatory_elements ||=
275
+ profile_elements
276
+ .select { |element| element.min.positive? }
277
+ .map { |element| element.path }
278
+ .uniq
279
+ end
280
+
281
+ def references
282
+ @references ||=
283
+ profile_elements
284
+ .select { |element| element.type&.first&.code == 'Reference' }
285
+ .map do |reference_definition|
286
+ {
287
+ path: reference_definition.path,
288
+ profiles: reference_definition.type.first.targetProfile
289
+ }
290
+ end
291
+ end
292
+
293
+ def resource_conformance_expectation
294
+ @resource_conformance_expectation ||=
295
+ resource_capabilities.extension.find do |extension|
296
+ extension.url == 'http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation'
297
+ end&.valueCode
298
+ end
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,118 @@
1
+ require 'active_support/all'
2
+ require 'fhir_models'
3
+ require 'pathname'
4
+ require 'rubygems/package'
5
+ require 'zlib'
6
+ require_relative 'ig_resources'
7
+
8
+ module PacioInfernoCore
9
+ class Generator
10
+ class IGLoader
11
+ attr_accessor :ig_file_name
12
+
13
+ def initialize(ig_file_name)
14
+ self.ig_file_name = ig_file_name
15
+ end
16
+
17
+ def ig_resources
18
+ @ig_resources ||= IGResources.new
19
+ end
20
+
21
+ def load
22
+ load_ig
23
+ load_standalone_resources
24
+ end
25
+
26
+ def load_ig
27
+ tar = Gem::Package::TarReader.new(
28
+ Zlib::GzipReader.open(ig_file_name)
29
+ )
30
+
31
+ tar.each do |entry|
32
+ next if entry.directory?
33
+
34
+ file_name = entry.full_name.split('/').last
35
+
36
+ next if file_name.end_with? 'openapi.json'
37
+
38
+ next unless file_name.end_with? '.json'
39
+
40
+ next unless entry.full_name.start_with? 'package/'
41
+
42
+ begin
43
+ resource = FHIR.from_contents(entry.read)
44
+ next if resource.nil?
45
+ rescue StandardError
46
+ puts "#{file_name} does not appear to be a FHIR resource."
47
+ next
48
+ end
49
+
50
+ ig_resources.add(resource)
51
+ end
52
+
53
+ ig_resources
54
+ end
55
+
56
+ def load_standalone_resources
57
+ ig_directory = ig_file_name.chomp('.tgz')
58
+
59
+ return ig_resources unless File.exist? ig_directory
60
+
61
+ Dir.glob(File.join(ig_directory, '*.{json,tgz}')).each do |file_path|
62
+ if file_path.end_with? '.tgz'
63
+ load_tgz_resources(file_path)
64
+ else
65
+ load_json_resource(file_path)
66
+ end
67
+ end
68
+
69
+ ig_resources
70
+ end
71
+
72
+ private
73
+
74
+ def load_tgz_resources(file_path)
75
+ tar = Gem::Package::TarReader.new(
76
+ Zlib::GzipReader.open(file_path)
77
+ )
78
+
79
+ tar.each do |entry|
80
+ next if entry.directory?
81
+
82
+ file_name = entry.full_name.split('/').last
83
+
84
+ next if file_name.end_with? 'openapi.json'
85
+
86
+ next if file_name.start_with? 'CapabilityStatement'
87
+
88
+ next unless file_name.end_with? '.json'
89
+
90
+ next unless entry.full_name.start_with? 'package/'
91
+
92
+ # exclude examples from FHIR R4
93
+ next if file_name.end_with? 'example.json'
94
+ next if file_name.end_with? 'example-reference.json'
95
+ next if file_name.end_with? 'example-extension.json'
96
+
97
+ load_resource(entry.read, file_name)
98
+ end
99
+ end
100
+
101
+ def load_json_resource(file_path)
102
+ load_resource(File.read(file_path), file_path.split('/').last)
103
+ end
104
+
105
+ def load_resource(contents, file_name)
106
+ begin
107
+ resource = FHIR.from_contents(contents)
108
+ return if resource.nil?
109
+ rescue StandardError
110
+ puts "#{file_name} does not appear to be a FHIR resource."
111
+ return
112
+ end
113
+
114
+ ig_resources.add(resource)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,60 @@
1
+ module PacioInfernoCore
2
+ class Generator
3
+ class IGMetadata
4
+ attr_accessor :ig_version, :groups, :ig_package_id
5
+
6
+ def reformatted_version
7
+ @reformatted_version ||= ig_version.delete('.').gsub('-', '_')
8
+ end
9
+
10
+ def ordered_groups
11
+ @ordered_groups ||=
12
+ [patient_group] + non_delayed_groups + delayed_groups
13
+ end
14
+
15
+ def patient_group
16
+ @patient_group ||=
17
+ groups.find { |group| group.resource == 'Patient' }
18
+ end
19
+
20
+ def delayed_groups
21
+ @delayed_groups ||=
22
+ groups.select { |group| group.delayed? }
23
+ end
24
+
25
+ def non_delayed_groups
26
+ @non_delayed_groups ||=
27
+ groups.reject { |group| group.delayed? } - [patient_group]
28
+ end
29
+
30
+ def delayed_profiles
31
+ @delayed_profiles ||=
32
+ delayed_groups.map(&:profile_url)
33
+ end
34
+
35
+ def postprocess_groups(ig_resources)
36
+ groups.each do |group|
37
+ group.add_delayed_references(delayed_profiles, ig_resources)
38
+ end
39
+ end
40
+
41
+ def granular_scope_resource_type_groups
42
+ @granular_scope_resource_type_groups ||=
43
+ Hash.new { |hash, key| hash[key] = [] }
44
+ end
45
+
46
+ def granular_scope_groups
47
+ @granular_scope_groups ||= []
48
+ end
49
+
50
+ def to_hash
51
+ {
52
+ ig_version:,
53
+ groups: groups.map(&:to_hash),
54
+ granular_scope_resource_type_groups:,
55
+ granular_scope_groups:
56
+ }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,88 @@
1
+ require_relative 'ig_metadata'
2
+ require_relative 'group_metadata_extractor'
3
+
4
+ module PacioInfernoCore
5
+ class Generator
6
+ class IGMetadataExtractor
7
+ attr_accessor :ig_resources, :metadata
8
+
9
+ def initialize(ig_resources)
10
+ self.ig_resources = ig_resources
11
+ add_missing_supported_profiles
12
+ remove_version_from_supported_profiles
13
+ remove_extra_supported_profiles
14
+ self.metadata = IGMetadata.new
15
+ end
16
+
17
+ def extract
18
+ add_metadata_from_ig
19
+ add_metadata_from_resources
20
+ metadata
21
+ end
22
+
23
+ def add_metadata_from_ig
24
+ metadata.ig_version = "v#{ig_resources.ig.version}".delete('-ballot')
25
+ metadata.ig_package_id = ig_resources.ig.packageId
26
+ end
27
+
28
+ def resources_in_capability_statement
29
+ ig_resources.capability_statement.rest.first.resource
30
+ end
31
+
32
+ def add_missing_supported_profiles
33
+ case ig_resources.ig.version.delete('-ballot')
34
+ when '3.1.1'
35
+ # TODO: Remove these after v8.0.0 IG is fixed
36
+
37
+ # The US Core v3.1.1 Server Capability Statement does not list support for the
38
+ # required vital signs profiles, so they need to be added
39
+ ig_resources.capability_statement.rest.first.resource
40
+ .find { |resource| resource.type == 'Observation' }
41
+ .supportedProfile.concat [
42
+ 'http://hl7.org/fhir/StructureDefinition/bodyheight',
43
+ 'http://hl7.org/fhir/StructureDefinition/bodytemp',
44
+ 'http://hl7.org/fhir/StructureDefinition/bp',
45
+ 'http://hl7.org/fhir/StructureDefinition/bodyweight',
46
+ 'http://hl7.org/fhir/StructureDefinition/heartrate',
47
+ 'http://hl7.org/fhir/StructureDefinition/resprate'
48
+ ]
49
+ when '5.0.1'
50
+ # The US Core v5.0.1 Server Capability Statement does not have supported-profile for Encounter
51
+ ig_resources.capability_statement.rest.first.resource
52
+ .find { |resource| resource.type == 'Encounter' }
53
+ .supportedProfile.concat [
54
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-encounter'
55
+ ]
56
+ end
57
+ end
58
+
59
+ def remove_version_from_supported_profiles
60
+ resources_in_capability_statement.each do |resource|
61
+ resource.supportedProfile.map! { |profile_url| profile_url.split('|').first }
62
+ end
63
+ end
64
+
65
+ def remove_extra_supported_profiles
66
+ ig_resources.capability_statement.rest.first.resource
67
+ .find { |resource| resource.type == 'Observation' }
68
+ .supportedProfile.delete_if do |profile_url|
69
+ SpecialCases::PROFILES_TO_EXCLUDE.include?(profile_url)
70
+ end
71
+ end
72
+
73
+ def add_metadata_from_resources
74
+ metadata.groups =
75
+ resources_in_capability_statement.flat_map do |resource|
76
+ resource.supportedProfile&.map do |supported_profile|
77
+ # supported_profile = supported_profile.split('|').first
78
+ next if supported_profile == 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire'
79
+
80
+ GroupMetadataExtractor.new(resource, supported_profile, metadata, ig_resources).group_metadata
81
+ end
82
+ end.compact
83
+
84
+ metadata.postprocess_groups(ig_resources)
85
+ end
86
+ end
87
+ end
88
+ end