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,228 @@
1
+ require_relative 'value_extractor'
2
+
3
+ module PacioInfernoCore
4
+ class Generator
5
+ class SearchDefinitionMetadataExtractor
6
+ attr_accessor :ig_resources, :name, :profile_elements, :group_metadata
7
+
8
+ def initialize(name, ig_resources, profile_elements, group_metadata)
9
+ self.name = name
10
+ self.ig_resources = ig_resources
11
+ self.profile_elements = profile_elements
12
+ self.group_metadata = group_metadata
13
+ end
14
+
15
+ def search_definition
16
+ @search_definition ||=
17
+ {
18
+ paths: paths,
19
+ full_paths: full_paths,
20
+ comparators: comparators,
21
+ values: values,
22
+ type: type,
23
+ contains_multiple: contains_multiple?,
24
+ multiple_or: multiple_or_expectation,
25
+ chain: chain
26
+ }.compact
27
+ end
28
+
29
+ def resource
30
+ group_metadata[:resource]
31
+ end
32
+
33
+ def param
34
+ @param ||= ig_resources.search_param_by_resource_and_name(resource, name)
35
+ end
36
+
37
+ def param_hash
38
+ param.source_hash
39
+ end
40
+
41
+ def full_paths
42
+ @full_paths ||=
43
+ begin
44
+ path = param.expression.gsub(/.where\(resolve\((.*)/, '').gsub('url = \'', 'url=\'')
45
+ path = path[1..-2] if path.start_with?('(') && path.end_with?(')')
46
+ path.scan(/[. ]as[( ]([^)]*)[)]?/).flatten.map do |as_type|
47
+ path.gsub!(/[. ]as[( ](#{as_type}[^)]*)[)]?/, as_type.upcase_first) if as_type.present?
48
+ end
49
+
50
+ path.gsub!('Resource.', "#{resource}.") if path.start_with?('Resource.')
51
+
52
+ full_paths = path.split('|')
53
+
54
+ # There is a bug in US Core 5 asserted-date search parameter. See FHIR-40573
55
+ if param.respond_to?(:version) && param.version == '5.0.1' && name == 'asserted-date'
56
+ remove_additional_extension_from_asserted_date(full_paths)
57
+ end
58
+
59
+ full_paths
60
+ end
61
+ end
62
+
63
+ def remove_additional_extension_from_asserted_date(full_paths)
64
+ full_paths.each do |full_path|
65
+ next unless full_path.include?('http://hl7.org/fhir/StructureDefinition/condition-assertedDate')
66
+
67
+ full_path.gsub!(/\).extension./, ').')
68
+ end
69
+ end
70
+
71
+ def paths
72
+ @paths ||= full_paths.map { |a_path| a_path.gsub("#{resource}.", '') }
73
+ end
74
+
75
+ def extensions
76
+ @extensions ||= full_paths.select { |a_path| a_path.include?('extension.where') }
77
+ .map { |a_path| { url: a_path[/(?<=extension.where\(url=').*(?='\))/] } }
78
+ .presence
79
+ end
80
+
81
+ def profile_element
82
+ @profile_element ||=
83
+ profile_elements.find { |element| full_paths.include?(element.id) } ||
84
+ extension_definition&.differential&.element&.find { |element| element.id == 'Extension.value[x]' }
85
+ end
86
+
87
+ def extension_definition
88
+ @extension_definition ||=
89
+ begin
90
+ ext_definition = nil
91
+ extensions&.each do |ext_metadata|
92
+ ext_definition = ig_resources.profile_by_url(ext_metadata[:url])
93
+ break if ext_definition.present?
94
+ end
95
+ ext_definition
96
+ end
97
+ end
98
+
99
+ def comparator_expectation_extensions
100
+ @comparator_expectation_extensions ||= param_hash['_comparator'] || []
101
+ end
102
+
103
+ def support_expectation(extension)
104
+ extension['extension'].first['valueCode']
105
+ end
106
+
107
+ def comparator_expectation(extension)
108
+ if extension.nil?
109
+ 'MAY'
110
+ else
111
+ support_expectation(extension)
112
+ end
113
+ end
114
+
115
+ def comparators
116
+ {}.tap do |comparators|
117
+ param.comparator&.each_with_index do |comparator, index|
118
+ comparators[comparator.to_sym] = comparator_expectation(comparator_expectation_extensions[index])
119
+ end
120
+ end
121
+ end
122
+
123
+ def type
124
+ if profile_element.present?
125
+ profile_element.type.first.code
126
+ else
127
+ # search is a variable type, eg. Condition.onsetDateTime - element
128
+ # in profile def is Condition.onset[x]
129
+ param.type
130
+ end
131
+ end
132
+
133
+ def contains_multiple?
134
+ if profile_element.present?
135
+ if profile_element.id.start_with?('Extension') && extension_definition.present?
136
+ # Find the extension instance in a US Core profile
137
+ target_element = profile_elements.find do |element|
138
+ element.type.any? { |type| type.code == 'Extension' && type.profile.include?(extension_definition.url) }
139
+ end
140
+ target_element&.max == '*'
141
+ else
142
+ profile_element.max == '*'
143
+ end
144
+ else
145
+ false
146
+ end
147
+ end
148
+
149
+ def chain_extensions
150
+ param_hash['_chain']
151
+ end
152
+
153
+ def chain_expectations
154
+ chain_extensions.map { |extension| support_expectation(extension) }
155
+ end
156
+
157
+ def chain
158
+ return nil if param.chain.blank?
159
+
160
+ param.chain
161
+ .zip(chain_expectations)
162
+ .map { |chain, expectation| { chain: chain, expectation: expectation } }
163
+ end
164
+
165
+ def multiple_or_expectation
166
+ param_hash['_multipleOr'] ? param_hash['_multipleOr']['extension'].first['valueCode'] : 'MAY'
167
+ end
168
+
169
+ def values
170
+ values_from_must_supports(profile_element).presence ||
171
+ value_extractor.values_from_fixed_codes(profile_element, type).presence ||
172
+ value_extractor.codes_from_value_set_binding(profile_element).presence ||
173
+ values_from_resource_metadata(paths).presence ||
174
+ []
175
+ end
176
+
177
+ def values_from_must_supports(profile_element)
178
+ return if profile_element.nil?
179
+
180
+ short_path = profile_element.path.split('.', 2)[1]
181
+
182
+ values_from_must_support_slices(profile_element, short_path, true).presence ||
183
+ values_from_must_support_slices(profile_element, short_path, false).presence ||
184
+ values_from_must_support_elements(short_path).presence ||
185
+ []
186
+ end
187
+
188
+ def values_from_must_support_slices(profile_element, short_path, mandatory_slice_only)
189
+ group_metadata[:must_supports][:slices]
190
+ .select { |slice| [short_path, "#{short_path}.coding"].include?(slice[:path]) }
191
+ .map do |slice|
192
+ slice_element = profile_elements.find { |element| slice[:slice_id] == element.id }
193
+ next if profile_element.min.positive? && slice_element.min.zero? && mandatory_slice_only
194
+
195
+ case slice[:discriminator][:type]
196
+ when 'patternCoding', 'patternCodeableConcept'
197
+ slice[:discriminator][:code]
198
+ when 'requiredBinding'
199
+ value_extractor.codes_from_system_code_pair(slice[:discriminator][:values])
200
+ when 'value'
201
+ slice[:discriminator][:values]
202
+ .select { |value| value[:path] == 'coding.code' }
203
+ .map { |value| value[:value] }
204
+ end
205
+ end
206
+ .compact.flatten
207
+ end
208
+
209
+ def values_from_must_support_elements(short_path)
210
+ group_metadata[:must_supports][:elements]
211
+ .select { |element| element[:path] == "#{short_path}.coding.code" }
212
+ .map { |element| element[:fixed_value] }
213
+ end
214
+
215
+ def values_from_resource_metadata(paths)
216
+ if multiple_or_expectation == 'SHALL' || paths.any? { |path| path.downcase.include?('status') }
217
+ value_extractor.codes_from_system_code_pair(value_extractor.values_from_resource_metadata(paths))
218
+ else
219
+ []
220
+ end
221
+ end
222
+
223
+ def value_extractor
224
+ @value_extractor ||= ValueExactor.new(ig_resources, resource, profile_elements)
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'search_definition_metadata_extractor'
2
+
3
+ module PacioInfernoCore
4
+ class Generator
5
+ class SearchMetadataExtractor
6
+ COMBO_EXTENSION_URL =
7
+ 'http://hl7.org/fhir/StructureDefinition/capabilitystatement-search-parameter-combination'.freeze
8
+
9
+ attr_accessor :resource_capabilities, :ig_resources, :profile_elements, :group_metadata
10
+
11
+ def initialize(resource_capabilities, ig_resources, profile_elements, group_metadata)
12
+ self.resource_capabilities = resource_capabilities
13
+ self.ig_resources = ig_resources
14
+ self.profile_elements = profile_elements
15
+ self.group_metadata = group_metadata
16
+ end
17
+
18
+ def searches
19
+ @searches ||= basic_searches + combo_searches
20
+ end
21
+
22
+ def conformance_expectation(search_param)
23
+ search_param.extension.first.valueCode # TODO: fix expectation extension finding
24
+ end
25
+
26
+ def no_search_params?
27
+ resource_capabilities.searchParam.blank?
28
+ end
29
+
30
+ def basic_searches
31
+ return [] if no_search_params?
32
+
33
+ resource_capabilities.searchParam
34
+ .select do |search_param|
35
+ %w[SHALL
36
+ SHOULD].include? conformance_expectation(search_param)
37
+ end
38
+ .map do |search_param|
39
+ {
40
+ names: [search_param.name],
41
+ expectation: conformance_expectation(search_param)
42
+ }
43
+ end
44
+ end
45
+
46
+ def search_extensions
47
+ resource_capabilities.extension
48
+ end
49
+
50
+ def combo_searches
51
+ return [] if search_extensions.blank?
52
+
53
+ search_extensions
54
+ .select { |extension| extension.url == COMBO_EXTENSION_URL }
55
+ .select { |extension| %w[SHALL SHOULD].include? conformance_expectation(extension) }
56
+ .map do |extension|
57
+ names = extension.extension.select { |param| param.valueString.present? }.map(&:valueString)
58
+ {
59
+ expectation: conformance_expectation(extension),
60
+ names: names
61
+ }
62
+ end
63
+ end
64
+
65
+ def search_param_names
66
+ searches.flat_map { |search| search[:names] }.uniq
67
+ end
68
+
69
+ def search_definitions
70
+ search_param_names.each_with_object({}) do |name, definitions|
71
+ definitions[name.to_sym] =
72
+ SearchDefinitionMetadataExtractor.new(name, ig_resources, profile_elements,
73
+ group_metadata).search_definition
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,298 @@
1
+ require_relative 'naming'
2
+ require_relative 'special_cases'
3
+
4
+ module PacioInfernoCore
5
+ class Generator
6
+ class SearchTestGenerator
7
+ class << self
8
+ def generate(ig_metadata, base_output_dir)
9
+ ig_metadata.groups
10
+ .reject { |group| SpecialCases.exclude_group? group }
11
+ .select { |group| group.searches.present? }
12
+ .each do |group|
13
+ group.searches.each do |search|
14
+ new(group, search, base_output_dir).generate
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ attr_accessor :group_metadata, :search_metadata, :base_output_dir
21
+
22
+ def initialize(group_metadata, search_metadata, base_output_dir)
23
+ self.group_metadata = group_metadata
24
+ self.search_metadata = search_metadata
25
+ self.base_output_dir = base_output_dir
26
+ end
27
+
28
+ def template
29
+ @template ||= File.read(File.join(__dir__, 'templates', 'search.rb.erb'))
30
+ end
31
+
32
+ def output
33
+ @output ||= ERB.new(template, trim_mode: '-').result(binding)
34
+ end
35
+
36
+ def base_output_file_name
37
+ "#{class_name.underscore}.rb"
38
+ end
39
+
40
+ def output_file_directory
41
+ File.join(base_output_dir, profile_identifier)
42
+ end
43
+
44
+ def output_file_name
45
+ File.join(output_file_directory, base_output_file_name)
46
+ end
47
+
48
+ def naming
49
+ self.class.module_parent::Naming
50
+ end
51
+
52
+ def profile_identifier
53
+ naming.snake_case_for_profile(group_metadata)
54
+ end
55
+
56
+ def test_id
57
+ "#{naming.prefix}_#{group_metadata.reformatted_version}_#{profile_identifier}_#{search_identifier}_search_test"
58
+ end
59
+
60
+ def search_identifier
61
+ search_metadata[:names].join('_').tr('-', '_')
62
+ end
63
+
64
+ def search_title
65
+ search_identifier.camelize
66
+ end
67
+
68
+ def class_name
69
+ "#{naming.upper_camel_case_for_profile(group_metadata)}#{search_title}SearchTest"
70
+ end
71
+
72
+ def module_name_with_version
73
+ "#{naming.module_name}#{group_metadata.reformatted_version.upcase}"
74
+ end
75
+
76
+ def resource_type
77
+ group_metadata.resource
78
+ end
79
+
80
+ def conformance_expectation
81
+ search_metadata[:expectation]
82
+ end
83
+
84
+ def search_params
85
+ @search_params ||=
86
+ search_metadata[:names].map do |name|
87
+ {
88
+ name: name,
89
+ path: search_definition(name)[:path]
90
+ }
91
+ end
92
+ end
93
+
94
+ def first_search?
95
+ group_metadata.searches.first == search_metadata
96
+ end
97
+
98
+ def fixed_value_search?
99
+ first_search? && search_metadata[:names] != ['patient'] &&
100
+ !group_metadata.delayed? && resource_type != 'Patient'
101
+ end
102
+
103
+ def fixed_value_search_param_name
104
+ (search_metadata[:names] - [:patient]).first
105
+ end
106
+
107
+ def search_param_name_string
108
+ search_metadata[:names].join(' + ')
109
+ end
110
+
111
+ def needs_patient_id?
112
+ search_metadata[:names].include?('patient') ||
113
+ (resource_type == 'Patient' && search_metadata[:names].include?('_id'))
114
+ end
115
+
116
+ def search_param_names
117
+ search_params.map { |param| param[:name] }
118
+ end
119
+
120
+ def search_param_names_array
121
+ array_of_strings(search_param_names)
122
+ end
123
+
124
+ def path_for_value(path)
125
+ path == 'class' ? 'local_class' : path
126
+ end
127
+
128
+ def required_comparators_for_param(name)
129
+ search_definition(name)[:comparators].select { |_comparator, expectation| expectation == 'SHALL' }
130
+ end
131
+
132
+ def required_comparators
133
+ @required_comparators ||=
134
+ search_param_names.each_with_object({}) do |name, comparators|
135
+ required_comparators = required_comparators_for_param(name)
136
+ comparators[name] = required_comparators if required_comparators.present?
137
+ end
138
+ end
139
+
140
+ def optional?
141
+ conformance_expectation != 'SHALL' || !search_metadata[:must_support_or_mandatory]
142
+ end
143
+
144
+ def search_definition(name)
145
+ group_metadata.search_definitions[name.to_sym]
146
+ end
147
+
148
+ def saves_delayed_references?
149
+ first_search? && group_metadata.delayed_references.present?
150
+ end
151
+
152
+ def possible_status_search?
153
+ search_metadata[:names].none? { |name| name.include? 'status' } &&
154
+ group_metadata.search_definitions.keys.any? { |key| key.to_s.include? 'status' }
155
+ end
156
+
157
+ def token_search_params
158
+ @token_search_params ||=
159
+ search_param_names.select do |name|
160
+ %w[Identifier CodeableConcept Coding].include? group_metadata.search_definitions[name.to_sym][:type]
161
+ end
162
+ end
163
+
164
+ def token_search_params_string
165
+ array_of_strings(token_search_params)
166
+ end
167
+
168
+ def required_multiple_or_search_params
169
+ @multiple_or_search_params ||=
170
+ search_param_names.select do |name|
171
+ search_definition(name)[:multiple_or] == 'SHALL'
172
+ end
173
+ end
174
+
175
+ def required_multiple_or_search_params_string
176
+ array_of_strings(required_multiple_or_search_params)
177
+ end
178
+
179
+ def required_comparators_string
180
+ array_of_strings(required_comparators.keys)
181
+ end
182
+
183
+ def array_of_strings(array)
184
+ quoted_strings = array.map { |element| "'#{element}'" }
185
+ "[#{quoted_strings.join(', ')}]"
186
+ end
187
+
188
+ def test_reference_variants?
189
+ first_search? && search_param_names.include?('patient')
190
+ end
191
+
192
+ def test_medication_inclusion?
193
+ %w[MedicationRequest MedicationDispense].include?(resource_type)
194
+ end
195
+
196
+ def test_post_search?
197
+ first_search?
198
+ end
199
+
200
+ def search_properties
201
+ {}.tap do |properties|
202
+ properties[:first_search] = 'true' if first_search?
203
+ properties[:fixed_value_search] = 'true' if fixed_value_search?
204
+ properties[:resource_type] = "'#{resource_type}'"
205
+ properties[:search_param_names] = search_param_names_array
206
+ properties[:saves_delayed_references] = 'true' if saves_delayed_references?
207
+ properties[:possible_status_search] = 'true' if possible_status_search?
208
+ properties[:test_medication_inclusion] = 'true' if test_medication_inclusion?
209
+ properties[:token_search_params] = token_search_params_string if token_search_params.present?
210
+ properties[:test_reference_variants] = 'true' if test_reference_variants?
211
+ properties[:params_with_comparators] = required_comparators_string if required_comparators.present?
212
+ if required_multiple_or_search_params.present?
213
+ properties[:multiple_or_search_params] =
214
+ required_multiple_or_search_params_string
215
+ end
216
+ properties[:test_post_search] = 'true' if first_search?
217
+ end
218
+ end
219
+
220
+ def ig_link
221
+ Naming.ig_link(group_metadata.version)
222
+ end
223
+
224
+ def search_test_properties_string
225
+ search_properties
226
+ .map { |key, value| "#{' ' * 8}#{key}: #{value}" }
227
+ .join(",\n")
228
+ end
229
+
230
+ def generate
231
+ FileUtils.mkdir_p(output_file_directory)
232
+ File.write(output_file_name, output)
233
+
234
+ group_metadata.add_test(
235
+ id: test_id,
236
+ file_name: base_output_file_name
237
+ )
238
+ end
239
+
240
+ def reference_search_description
241
+ return '' unless test_reference_variants?
242
+
243
+ <<~REFERENCE_SEARCH_DESCRIPTION
244
+ This test verifies that the server supports searching by reference using
245
+ the form `patient=[id]` as well as `patient=Patient/[id]`. The two
246
+ different forms are expected to return the same number of results. US
247
+ Core requires that both forms are supported by US Core responders.
248
+ REFERENCE_SEARCH_DESCRIPTION
249
+ end
250
+
251
+ def first_search_description
252
+ return '' unless first_search?
253
+
254
+ <<~FIRST_SEARCH_DESCRIPTION
255
+ Because this is the first search of the sequence, resources in the
256
+ response will be used for subsequent tests.
257
+ FIRST_SEARCH_DESCRIPTION
258
+ end
259
+
260
+ def medication_inclusion_description
261
+ return '' unless test_medication_inclusion?
262
+
263
+ <<~MEDICATION_INCLUSION_DESCRIPTION
264
+ If any #{resource_type} resources use external references to
265
+ Medications, the search will be repeated with
266
+ `_include=#{resource_type}:medication`.
267
+ MEDICATION_INCLUSION_DESCRIPTION
268
+ end
269
+
270
+ def post_search_description
271
+ return '' unless test_post_search?
272
+
273
+ <<~POST_SEARCH_DESCRIPTION
274
+ Additionally, this test will check that GET and POST search methods
275
+ return the same number of results. Search by POST is required by the
276
+ FHIR R4 specification, and these tests interpret search by GET as a
277
+ requirement of US Core #{group_metadata.version}.
278
+ POST_SEARCH_DESCRIPTION
279
+ end
280
+
281
+ def description
282
+ <<~DESCRIPTION.gsub(/\n{3,}/, "\n\n")
283
+ A server #{conformance_expectation} support searching by
284
+ #{search_param_name_string} on the #{resource_type} resource. This test
285
+ will pass if resources are returned and match the search criteria. If
286
+ none are returned, the test is skipped.
287
+
288
+ #{medication_inclusion_description}
289
+ #{reference_search_description}
290
+ #{first_search_description}
291
+ #{post_search_description}
292
+
293
+ [US Core Server CapabilityStatement](#{ig_link}/CapabilityStatement-us-core-server.html)
294
+ DESCRIPTION
295
+ end
296
+ end
297
+ end
298
+ end
@@ -0,0 +1,61 @@
1
+ module PacioInfernoCore
2
+ class Generator
3
+ module SpecialCases
4
+ # These resources are excluded from US Core Test Suite
5
+ RESOURCES_TO_EXCLUDE = {
6
+ 'Location' => %w[v311 v400 v501 v610],
7
+ 'Medication' => %w[v311 v400 v501 v610 v700 v800],
8
+ 'PractitionerRole' => %w[v311 v400]
9
+ }.freeze
10
+
11
+ # These profiles are excluded from US Core Test Suite
12
+ PROFILES_TO_EXCLUDE = [
13
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-survey',
14
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs'
15
+ ].freeze
16
+
17
+ OPTIONAL_RESOURCES = %w[
18
+ PractitionerRole
19
+ QuestionnaireResponse
20
+ ].freeze
21
+
22
+ OPTIONAL_PROFILES = {
23
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-simple-observation' => %w[v610 v700]
24
+ }.freeze
25
+
26
+ # These resources relies on references from other resources but they also have mandatory search tests.
27
+ SEARCHABLE_DELAYED_RESOURCES = {
28
+ 'Location' => %w[v700 v800]
29
+ }.freeze
30
+
31
+ ALL_VERSION_CATEGORY_FIRST_PROFILES = [
32
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-careplan',
33
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-lab',
34
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-diagnosticreport-note',
35
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-clinical-result',
36
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-clinical-test',
37
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-imaging',
38
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab',
39
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-screening-assessment',
40
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-sdoh-assessment',
41
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-social-history',
42
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-survey',
43
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-simple-observation'
44
+ ].freeze
45
+
46
+ VERSION_SPECIFIC_CATEGORY_FIRST_PROFILES = {
47
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition-encounter-diagnosis' => %w[v610 v700
48
+ v800],
49
+ 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition-problems-health-concerns' => %w[v610
50
+ v700 v800]
51
+ }.freeze
52
+
53
+ class << self
54
+ def exclude_group?(group)
55
+ RESOURCES_TO_EXCLUDE.key?(group.resource) &&
56
+ RESOURCES_TO_EXCLUDE[group.resource].include?(group.reformatted_version)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end