us_quality_core_test_kit 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/NOTICE.md +12 -0
- data/config/presets/{localhost_preset.json → inferno_reference_server_preset.json.erb} +2 -2
- data/lib/us_quality_core_test_kit/client/generator/example_client_generator.rb +0 -1
- data/lib/us_quality_core_test_kit/client/generator/tags_generator.rb +0 -1
- data/lib/us_quality_core_test_kit/client/server_proxy.rb +2 -6
- data/lib/us_quality_core_test_kit/date_search_validation.rb +122 -0
- data/lib/us_quality_core_test_kit/fhir_resource_navigation.rb +9 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/devicenotrequested/devicenotrequested_validation_test.rb +3 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/devicerequest/devicerequest_validation_test.rb +3 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/patient/patient_validation_test.rb +4 -0
- data/lib/us_quality_core_test_kit/generated/v0.5.0/us_quality_core_test_suite.rb +1 -2
- data/lib/us_quality_core_test_kit/generator/group_generator.rb +69 -3
- data/lib/us_quality_core_test_kit/generator/group_metadata.rb +100 -4
- data/lib/us_quality_core_test_kit/generator/group_metadata_extractor.rb +184 -8
- data/lib/us_quality_core_test_kit/generator/ig_loader.rb +47 -3
- data/lib/us_quality_core_test_kit/generator/ig_metadata.rb +51 -2
- data/lib/us_quality_core_test_kit/generator/ig_metadata_extractor.rb +25 -6
- data/lib/us_quality_core_test_kit/generator/ig_resources.rb +27 -3
- data/lib/us_quality_core_test_kit/generator/must_support_metadata_extractor.rb +366 -9
- data/lib/us_quality_core_test_kit/generator/must_support_test_generator.rb +54 -3
- data/lib/us_quality_core_test_kit/generator/provenance_revinclude_search_test_generator.rb +149 -3
- data/lib/us_quality_core_test_kit/generator/read_test_generator.rb +54 -3
- data/lib/us_quality_core_test_kit/generator/reference_resolution_test_generator.rb +57 -3
- data/lib/us_quality_core_test_kit/generator/search_definition_metadata_extractor.rb +181 -5
- data/lib/us_quality_core_test_kit/generator/search_metadata_extractor.rb +61 -3
- data/lib/us_quality_core_test_kit/generator/search_test_generator.rb +186 -3
- data/lib/us_quality_core_test_kit/generator/suite_generator.rb +35 -8
- data/lib/us_quality_core_test_kit/generator/terminology_binding_metadata_extractor.rb +107 -3
- data/lib/us_quality_core_test_kit/generator/validation_test_generator.rb +83 -4
- data/lib/us_quality_core_test_kit/generator/value_extractor.rb +147 -3
- data/lib/us_quality_core_test_kit/must_support_test.rb +15 -3
- data/lib/us_quality_core_test_kit/primitive_type.rb +7 -0
- data/lib/us_quality_core_test_kit/read_test.rb +99 -4
- data/lib/us_quality_core_test_kit/reference_resolution_test.rb +164 -3
- data/lib/us_quality_core_test_kit/resource_search_param_checker.rb +138 -0
- data/lib/us_quality_core_test_kit/search_test.rb +681 -46
- data/lib/us_quality_core_test_kit/search_test_properties.rb +53 -3
- data/lib/us_quality_core_test_kit/validation_test.rb +51 -4
- data/lib/us_quality_core_test_kit/version.rb +2 -2
- data/lib/us_quality_core_test_kit/well_known_code_systems.rb +23 -0
- metadata +8 -17
|
@@ -2,11 +2,330 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'value_extractor'
|
|
4
4
|
|
|
5
|
-
require 'us_core_test_kit/generator/must_support_metadata_extractor'
|
|
6
|
-
|
|
7
5
|
module USQualityCoreTestKit
|
|
8
6
|
class Generator
|
|
9
|
-
class MustSupportMetadataExtractor
|
|
7
|
+
class MustSupportMetadataExtractor
|
|
8
|
+
attr_accessor :profile_elements, :profile, :resource, :ig_resources
|
|
9
|
+
|
|
10
|
+
def initialize(profile_elements, profile, resource, ig_resources)
|
|
11
|
+
self.profile_elements = profile_elements
|
|
12
|
+
self.profile = profile
|
|
13
|
+
self.resource = resource
|
|
14
|
+
self.ig_resources = ig_resources
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def must_supports
|
|
18
|
+
@must_supports = {
|
|
19
|
+
extensions: must_support_extensions,
|
|
20
|
+
slices: must_support_slices,
|
|
21
|
+
elements: must_support_elements
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
handle_special_cases
|
|
25
|
+
|
|
26
|
+
@must_supports
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def uscdi_requirement_element?(element)
|
|
30
|
+
element.extension.any? do |extension|
|
|
31
|
+
extension.url == 'http://hl7.org/fhir/us/core/StructureDefinition/uscdi-requirement' &&
|
|
32
|
+
extension.valueBoolean
|
|
33
|
+
end && !element.mustSupport
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def must_support_extension_elements
|
|
37
|
+
all_must_support_elements.select { |element| element.path.end_with? 'extension' }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def must_support_slice_elements
|
|
41
|
+
all_must_support_elements.select { |element| !element.path.end_with?('extension') && element.sliceName.present? }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sliced_element(slice)
|
|
45
|
+
profile_elements.find { |element| element.id == slice.path || element.id == slice.id.sub(":#{slice.sliceName}", '') }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def discriminators(slice)
|
|
49
|
+
slice.slicing.discriminator
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def must_support_type_slice_elements
|
|
53
|
+
must_support_slice_elements.select do |element|
|
|
54
|
+
discriminators(sliced_element(element)).first.type == 'type'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def type_slices
|
|
59
|
+
must_support_type_slice_elements.map do |current_element|
|
|
60
|
+
discriminator = discriminators(sliced_element(current_element)).first
|
|
61
|
+
type_path = discriminator.path
|
|
62
|
+
type_path = '' if type_path == '$this'
|
|
63
|
+
type_element =
|
|
64
|
+
if type_path.present?
|
|
65
|
+
profile_elements.find { |element| element.id == "#{current_element.id}.#{type_path}" }
|
|
66
|
+
else
|
|
67
|
+
current_element
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
type_code = type_element.type.first.code
|
|
71
|
+
|
|
72
|
+
discriminator = {
|
|
73
|
+
type: 'type',
|
|
74
|
+
code: type_code.upcase_first
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
discriminator[:path] = type_path unless type_path.empty?
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
slice_id: current_element.id,
|
|
81
|
+
slice_name: current_element.sliceName,
|
|
82
|
+
path: current_element.path.gsub("#{resource}.", ''),
|
|
83
|
+
discriminator: discriminator
|
|
84
|
+
}.tap do |metadata|
|
|
85
|
+
metadata[:uscdi_only] = true if uscdi_requirement_element?(current_element)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def must_support_value_slice_elements
|
|
91
|
+
must_support_slice_elements.select do |element|
|
|
92
|
+
# 'pattern' is deparected in FHIR R5
|
|
93
|
+
# 'pattern' type is used in US Core v7 and all earlier versions.
|
|
94
|
+
# Since US Core v8, all 'patten' types are moved to 'value' type
|
|
95
|
+
%w[value pattern].include?(discriminators(sliced_element(element)).first.type)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def value_slices
|
|
100
|
+
must_support_value_slice_elements.map do |current_element|
|
|
101
|
+
{
|
|
102
|
+
slice_id: current_element.id,
|
|
103
|
+
slice_name: current_element.sliceName,
|
|
104
|
+
path: current_element.path.gsub("#{resource}.", '')
|
|
105
|
+
}.tap do |metadata|
|
|
106
|
+
fixed_values = []
|
|
107
|
+
pattern_value = {}
|
|
108
|
+
|
|
109
|
+
discriminators(sliced_element(current_element)).each do |discriminator|
|
|
110
|
+
discriminator_path = discriminator.path
|
|
111
|
+
discriminator_path = '' if discriminator_path == '$this'
|
|
112
|
+
pattern_element =
|
|
113
|
+
if discriminator_path.present?
|
|
114
|
+
sliced_target_path = "#{current_element.path}.#{discriminator_path}"
|
|
115
|
+
|
|
116
|
+
profile_elements.find do |element|
|
|
117
|
+
element.id.starts_with?(current_element.id) &&
|
|
118
|
+
element.path == sliced_target_path
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
current_element
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
if pattern_element.fixedCode.present? || pattern_element.fixedUri.present?
|
|
125
|
+
fixed_values << {
|
|
126
|
+
path: discriminator.path,
|
|
127
|
+
value: pattern_element.fixedUri || pattern_element.fixedCode
|
|
128
|
+
}
|
|
129
|
+
elsif pattern_value.present?
|
|
130
|
+
raise StandardError, "Found more than one pattern slices for the same element #{pattern_element}."
|
|
131
|
+
else
|
|
132
|
+
pattern_value = save_pattern_slice(pattern_element, discriminator_path, metadata)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if !fixed_values.empty?
|
|
137
|
+
metadata[:discriminator] = {
|
|
138
|
+
type: 'value',
|
|
139
|
+
values: fixed_values
|
|
140
|
+
}
|
|
141
|
+
elsif pattern_value.present?
|
|
142
|
+
metadata[:discriminator] = pattern_value
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
metadata[:uscdi_only] = true if uscdi_requirement_element?(current_element)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def save_pattern_slice(pattern_element, discriminator_path, metadata)
|
|
151
|
+
if pattern_element.patternCodeableConcept.present?
|
|
152
|
+
{
|
|
153
|
+
type: 'patternCodeableConcept',
|
|
154
|
+
path: discriminator_path,
|
|
155
|
+
code: pattern_element.patternCodeableConcept.coding.first.code,
|
|
156
|
+
system: pattern_element.patternCodeableConcept.coding.first.system
|
|
157
|
+
}
|
|
158
|
+
elsif pattern_element.patternCoding.present?
|
|
159
|
+
{
|
|
160
|
+
type: 'patternCoding',
|
|
161
|
+
path: discriminator_path,
|
|
162
|
+
code: pattern_element.patternCoding.code,
|
|
163
|
+
system: pattern_element.patternCoding.system
|
|
164
|
+
}
|
|
165
|
+
elsif pattern_element.patternIdentifier.present?
|
|
166
|
+
{
|
|
167
|
+
type: 'patternIdentifier',
|
|
168
|
+
path: discriminator_path,
|
|
169
|
+
system: pattern_element.patternIdentifier.system
|
|
170
|
+
}
|
|
171
|
+
elsif pattern_element.binding&.strength == 'required' &&
|
|
172
|
+
pattern_element.binding&.valueSet.present?
|
|
173
|
+
|
|
174
|
+
value_extractor = ValueExactor.new(ig_resources, resource, profile_elements)
|
|
175
|
+
|
|
176
|
+
values = value_extractor.codings_from_value_set_binding(pattern_element).presence ||
|
|
177
|
+
value_extractor.values_from_resource_metadata([metadata[:path]]).presence || []
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
type: 'requiredBinding',
|
|
181
|
+
path: discriminator_path,
|
|
182
|
+
values: values
|
|
183
|
+
}
|
|
184
|
+
else
|
|
185
|
+
raise StandardError, 'Unsupported discriminator pattern type'
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def plain_must_support_elements
|
|
190
|
+
all_must_support_elements - must_support_extension_elements - must_support_slice_elements
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def element_part_of_slice_discrimination?(element)
|
|
194
|
+
must_support_slice_elements.any? { |ms_slice| element.id.include?(ms_slice.id) }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def handle_fixed_values(metadata, element)
|
|
198
|
+
if element.fixedUri.present?
|
|
199
|
+
metadata[:fixed_value] = element.fixedUri
|
|
200
|
+
elsif element.patternCodeableConcept.present? && !element_part_of_slice_discrimination?(element)
|
|
201
|
+
metadata[:fixed_value] = element.patternCodeableConcept.coding.first.code
|
|
202
|
+
metadata[:path] += '.coding.code'
|
|
203
|
+
elsif element.fixedCode.present?
|
|
204
|
+
metadata[:fixed_value] = element.fixedCode
|
|
205
|
+
elsif element.patternIdentifier.present? && !element_part_of_slice_discrimination?(element)
|
|
206
|
+
metadata[:fixed_value] = element.patternIdentifier.system
|
|
207
|
+
metadata[:path] += '.system'
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def type_must_support_extension?(extensions)
|
|
212
|
+
extensions&.any? do |extension|
|
|
213
|
+
extension.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support' &&
|
|
214
|
+
extension.valueBoolean
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def save_type_code?(type)
|
|
219
|
+
type.code == 'Reference'
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def get_type_must_support_metadata(current_metadata, current_element)
|
|
223
|
+
current_element.type.map do |type|
|
|
224
|
+
next unless type_must_support_extension?(type.extension)
|
|
225
|
+
|
|
226
|
+
metadata =
|
|
227
|
+
{
|
|
228
|
+
path: "#{current_metadata[:path].delete_suffix('[x]')}#{type.code.upcase_first}",
|
|
229
|
+
original_path: current_metadata[:path]
|
|
230
|
+
}
|
|
231
|
+
metadata[:types] = [type.code] if save_type_code?(type)
|
|
232
|
+
handle_type_must_support_target_profiles(type, metadata) if type.code == 'Reference'
|
|
233
|
+
|
|
234
|
+
metadata
|
|
235
|
+
end.compact
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def handle_type_must_support_target_profiles(type, metadata)
|
|
239
|
+
target_profiles = []
|
|
240
|
+
|
|
241
|
+
if type.targetProfile&.length == 1
|
|
242
|
+
target_profiles << type.targetProfile.first
|
|
243
|
+
else
|
|
244
|
+
type.source_hash['_targetProfile']&.each_with_index do |hash, index|
|
|
245
|
+
if hash.present?
|
|
246
|
+
element = FHIR::Element.new(hash)
|
|
247
|
+
target_profiles << type.targetProfile[index] if type_must_support_extension?(element.extension)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# remove target_profile for FHIR Base resource type.
|
|
253
|
+
target_profiles.delete_if { |reference| reference.start_with?('http://hl7.org/fhir/StructureDefinition') }
|
|
254
|
+
metadata[:target_profiles] = target_profiles if target_profiles.present?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
|
|
258
|
+
choice_element_metadata = must_support_elements_metadata.find do |metadata|
|
|
259
|
+
metadata[:original_path].present? &&
|
|
260
|
+
current_metadata[:path].include?(metadata[:original_path])
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
return unless choice_element_metadata.present?
|
|
264
|
+
|
|
265
|
+
current_metadata[:original_path] = current_metadata[:path]
|
|
266
|
+
current_metadata[:path] = current_metadata[:path].sub(choice_element_metadata[:original_path], choice_element_metadata[:path])
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
#### SPECIAL CASE ####
|
|
270
|
+
|
|
271
|
+
def vital_sign?
|
|
272
|
+
[
|
|
273
|
+
'http://hl7.org/fhir/StructureDefinition/vitalsigns',
|
|
274
|
+
'http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs'
|
|
275
|
+
].include?(profile.baseDefinition)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def blood_pressure?
|
|
279
|
+
%w[bp us-core-blood-pressure us-core-average-blood-pressure].include?(profile.id)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Exclude Observation.component from vital sign profiles except observation-bp and observation-pulse-ox
|
|
283
|
+
|
|
284
|
+
def remove_vital_sign_component
|
|
285
|
+
return if blood_pressure? || profile.name == 'USCorePulseOximetryProfile'
|
|
286
|
+
|
|
287
|
+
return unless vital_sign?
|
|
288
|
+
|
|
289
|
+
@must_supports[:elements].delete_if do |element|
|
|
290
|
+
element[:path].start_with?('component')
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Exclude Observation.value[x] from observation-bp
|
|
295
|
+
|
|
296
|
+
def remove_blood_pressure_value_data_absent_reason
|
|
297
|
+
return unless blood_pressure?
|
|
298
|
+
|
|
299
|
+
@must_supports[:elements].delete_if do |element|
|
|
300
|
+
element[:path].start_with?('value[x]') ||
|
|
301
|
+
element[:original_path]&.start_with?('value[x]') ||
|
|
302
|
+
element[:path] == ('dataAbsentReason')
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
@must_supports[:slices].delete_if do |slice|
|
|
306
|
+
slice[:path].start_with?('value[x]')
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# ONC and US Core 4.0.0 both clarified that health IT developers that always provide HL7 FHIR "observation" values
|
|
311
|
+
# are not required to demonstrate Health IT Module support for "dataAbsentReason" elements.
|
|
312
|
+
# Remove MS check for dataAbsentReason and component.dataAbsentReason from vital sign profiles and observation lab profile
|
|
313
|
+
# Smoking status profile does not have MS on dataAbsentReason. It is safe to use profile.type == 'Observation'
|
|
314
|
+
# Since US Core 5.0.1, Blood Pressure profile restores component.dataAbsentReason as MustSupport.
|
|
315
|
+
# US Quality Core Test Kit has to keep this as there are still profiles either are us-core or inherit from us-core
|
|
316
|
+
|
|
317
|
+
def remove_observation_data_absent_reason
|
|
318
|
+
return if blood_pressure?
|
|
319
|
+
|
|
320
|
+
pattern = /(component(:[^.]+)?\.)?dataAbsentReason/
|
|
321
|
+
|
|
322
|
+
return unless profile.type == 'Observation'
|
|
323
|
+
|
|
324
|
+
@must_supports[:elements].delete_if do |element|
|
|
325
|
+
pattern.match?(element[:path])
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
10
329
|
def uscdi_plus_quality_element?(element)
|
|
11
330
|
!element.mustSupport && element.extension.any? do |extension|
|
|
12
331
|
extension.url.downcase.ends_with? 'uscdiplusquality'
|
|
@@ -23,20 +342,26 @@ module USQualityCoreTestKit
|
|
|
23
342
|
|
|
24
343
|
def all_must_support_elements
|
|
25
344
|
profile_elements.select do |element|
|
|
26
|
-
element.mustSupport || uscdi_plus_quality_element?(element) ||
|
|
345
|
+
element.mustSupport || uscdi_plus_quality_element?(element) || uscdi_requirement_element?(element)
|
|
27
346
|
end
|
|
28
347
|
end
|
|
29
348
|
|
|
30
349
|
def must_support_extensions
|
|
31
|
-
|
|
32
|
-
|
|
350
|
+
must_support_extension_elements.map do |element|
|
|
351
|
+
metadata = {
|
|
352
|
+
id: element.id,
|
|
353
|
+
path: element.path.gsub("#{resource}.", ''),
|
|
354
|
+
url: element.type.first.profile.first
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
metadata[:uscdi_only] = true if uscdi_requirement_element?(element)
|
|
33
358
|
|
|
34
|
-
add_us_quality_coreuscdi_plus_quality_flag(
|
|
359
|
+
add_us_quality_coreuscdi_plus_quality_flag(element, metadata)
|
|
35
360
|
end
|
|
36
361
|
end
|
|
37
362
|
|
|
38
363
|
def must_support_slices
|
|
39
|
-
|
|
364
|
+
(type_slices + value_slices).map do |slice|
|
|
40
365
|
profile_element = must_support_slice_elements.find { |e| e.id == slice[:slice_id] }
|
|
41
366
|
|
|
42
367
|
add_us_quality_coreuscdi_plus_quality_flag(profile_element, slice)
|
|
@@ -44,7 +369,38 @@ module USQualityCoreTestKit
|
|
|
44
369
|
end
|
|
45
370
|
|
|
46
371
|
def must_support_elements
|
|
47
|
-
ms_elements =
|
|
372
|
+
ms_elements =
|
|
373
|
+
plain_must_support_elements.each_with_object([]) do |current_element, must_support_elements_metadata|
|
|
374
|
+
{
|
|
375
|
+
path: current_element.id.gsub("#{resource}.", '')
|
|
376
|
+
}.tap do |current_metadata|
|
|
377
|
+
if current_element.id.include?(':') && !current_element.id.include?('extension')
|
|
378
|
+
slice_name = current_element.id.match(/:(\w+)/)[1]
|
|
379
|
+
|
|
380
|
+
next if must_support_slice_elements.none? { |slice| slice.sliceName == slice_name }
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
current_metadata[:uscdi_only] = true if uscdi_requirement_element?(current_element)
|
|
384
|
+
|
|
385
|
+
type_must_support_metadata = get_type_must_support_metadata(current_metadata, current_element)
|
|
386
|
+
|
|
387
|
+
if type_must_support_metadata.any?
|
|
388
|
+
must_support_elements_metadata.concat(type_must_support_metadata)
|
|
389
|
+
else
|
|
390
|
+
handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
|
|
391
|
+
|
|
392
|
+
supported_types = current_element.type.select { |type| save_type_code?(type) }.map(&:code)
|
|
393
|
+
current_metadata[:types] = supported_types if supported_types.present?
|
|
394
|
+
|
|
395
|
+
handle_type_must_support_target_profiles(current_element.type.first, current_metadata) if current_element.type.first&.code == 'Reference'
|
|
396
|
+
|
|
397
|
+
handle_fixed_values(current_metadata, current_element)
|
|
398
|
+
|
|
399
|
+
must_support_elements_metadata << current_metadata
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end.uniq
|
|
403
|
+
|
|
48
404
|
return ms_elements unless profile.url.include?('us-quality-core')
|
|
49
405
|
|
|
50
406
|
ms_elements.map do |element|
|
|
@@ -58,6 +414,7 @@ module USQualityCoreTestKit
|
|
|
58
414
|
end
|
|
59
415
|
|
|
60
416
|
# Aligns US Core v6.1.0 must support requirements to [USCDI v3.1](https://isp.healthit.gov/united-states-core-data-interoperability-uscdi#uscdi-v3-1)
|
|
417
|
+
|
|
61
418
|
def remove_patient_gender_identity
|
|
62
419
|
return unless profile.type == 'Patient'
|
|
63
420
|
return unless @must_supports && @must_supports[:extensions]
|
|
@@ -1,13 +1,64 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/must_support_test_generator'
|
|
4
|
-
|
|
5
3
|
require_relative 'naming'
|
|
6
4
|
require_relative 'special_cases'
|
|
7
5
|
|
|
8
6
|
module USQualityCoreTestKit
|
|
9
7
|
class Generator
|
|
10
|
-
class MustSupportTestGenerator
|
|
8
|
+
class MustSupportTestGenerator
|
|
9
|
+
attr_accessor :group_metadata, :base_output_dir
|
|
10
|
+
|
|
11
|
+
def initialize(group_metadata, base_output_dir)
|
|
12
|
+
self.group_metadata = group_metadata
|
|
13
|
+
self.base_output_dir = base_output_dir
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def output
|
|
17
|
+
@output ||= ERB.new(template, trim_mode: '-').result(binding)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def base_output_file_name
|
|
21
|
+
"#{class_name.underscore}.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def output_file_directory
|
|
25
|
+
File.join(base_output_dir, profile_identifier)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def output_file_name
|
|
29
|
+
File.join(output_file_directory, base_output_file_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def read_interaction
|
|
33
|
+
self.class.read_interaction(group_metadata)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def resource_type
|
|
37
|
+
group_metadata.resource
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resource_collection_string
|
|
41
|
+
'all_scratch_resources'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def must_support_list_string
|
|
45
|
+
build_must_support_list_string(false)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def uscdi_list_string
|
|
49
|
+
build_must_support_list_string(true)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def generate
|
|
53
|
+
FileUtils.mkdir_p(output_file_directory)
|
|
54
|
+
File.write(output_file_name, output)
|
|
55
|
+
|
|
56
|
+
group_metadata.add_test(
|
|
57
|
+
id: test_id,
|
|
58
|
+
file_name: base_output_file_name
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
11
62
|
class << self
|
|
12
63
|
def generate(ig_metadata, base_output_dir)
|
|
13
64
|
ig_metadata.groups
|
|
@@ -1,13 +1,159 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'us_core_test_kit/generator/provenance_revinclude_search_test_generator'
|
|
4
|
-
|
|
5
3
|
require_relative 'naming'
|
|
6
4
|
require_relative 'special_cases'
|
|
7
5
|
|
|
8
6
|
module USQualityCoreTestKit
|
|
9
7
|
class Generator
|
|
10
|
-
class ProvenanceRevincludeSearchTestGenerator
|
|
8
|
+
class ProvenanceRevincludeSearchTestGenerator
|
|
9
|
+
class << self
|
|
10
|
+
def generate(ig_metadata, base_output_dir)
|
|
11
|
+
ig_metadata.groups
|
|
12
|
+
.reject { |group| SpecialCases.exclude_group? group }
|
|
13
|
+
.select { |group| group.revincludes.include? 'Provenance:target' }
|
|
14
|
+
.each { |group| new(group, group.searches.first, base_output_dir).generate }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_accessor :group_metadata, :search_metadata, :base_output_dir
|
|
19
|
+
|
|
20
|
+
def initialize(group_metadata, search_metadata, base_output_dir)
|
|
21
|
+
self.group_metadata = group_metadata
|
|
22
|
+
self.search_metadata = search_metadata
|
|
23
|
+
self.base_output_dir = base_output_dir
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def output
|
|
27
|
+
@output ||= ERB.new(template, trim_mode: '-').result(binding)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def base_output_file_name
|
|
31
|
+
"#{class_name.underscore}.rb"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def output_file_directory
|
|
35
|
+
File.join(base_output_dir, profile_identifier)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def output_file_name
|
|
39
|
+
File.join(output_file_directory, base_output_file_name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def search_identifier
|
|
43
|
+
'provenance_revinclude'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def search_title
|
|
47
|
+
search_identifier.camelize
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def resource_type
|
|
51
|
+
group_metadata.resource
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def search_params
|
|
55
|
+
@search_params ||=
|
|
56
|
+
search_metadata[:names].map do |name|
|
|
57
|
+
{
|
|
58
|
+
name: name,
|
|
59
|
+
path: search_definition(name)[:path]
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def first_search?
|
|
65
|
+
group_metadata.searches.first == search_metadata
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def fixed_value_search_param_name
|
|
69
|
+
(search_metadata[:names] - [:patient]).first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def search_param_name_string
|
|
73
|
+
"#{search_metadata[:names].join(' + ')} + revInclude:Provenance:target"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def search_param_names
|
|
77
|
+
search_params.map { |param| param[:name] }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def search_param_names_array
|
|
81
|
+
array_of_strings(search_param_names)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def path_for_value(path)
|
|
85
|
+
path == 'class' ? 'local_class' : path
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def required_comparators_for_param(name)
|
|
89
|
+
search_definition(name)[:comparators].select { |_comparator, expectation| expectation == 'SHALL' }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def required_comparators
|
|
93
|
+
@required_comparators ||=
|
|
94
|
+
search_param_names.each_with_object({}) do |name, comparators|
|
|
95
|
+
required_comparators = required_comparators_for_param(name)
|
|
96
|
+
comparators[name] = required_comparators if required_comparators.present?
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def search_definition(name)
|
|
101
|
+
group_metadata.search_definitions[name.to_sym]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def saves_delayed_references?
|
|
105
|
+
first_search? && group_metadata.delayed_references.present?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def possible_status_search?
|
|
109
|
+
!search_metadata[:names].include?('status') && group_metadata.search_definitions.key?(:status)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def token_search_params
|
|
113
|
+
@token_search_params ||=
|
|
114
|
+
search_param_names.select do |name|
|
|
115
|
+
%w[Identifier CodeableConcept Coding].include? group_metadata.search_definitions[name.to_sym][:type]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def token_search_params_string
|
|
120
|
+
array_of_strings(token_search_params)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def required_comparators_string
|
|
124
|
+
array_of_strings(required_comparators.keys)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def array_of_strings(array)
|
|
128
|
+
quoted_strings = array.map { |element| "'#{element}'" }
|
|
129
|
+
"[#{quoted_strings.join(', ')}]"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def search_properties
|
|
133
|
+
{}.tap do |properties|
|
|
134
|
+
properties[:fixed_value_search] = 'true' if fixed_value_search?
|
|
135
|
+
properties[:resource_type] = "'#{resource_type}'"
|
|
136
|
+
properties[:search_param_names] = search_param_names_array
|
|
137
|
+
properties[:possible_status_search] = 'true' if possible_status_search?
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def search_test_properties_string
|
|
142
|
+
search_properties
|
|
143
|
+
.map { |key, value| "#{' ' * 8}#{key}: #{value}" }
|
|
144
|
+
.join(",\n")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def generate
|
|
148
|
+
FileUtils.mkdir_p(output_file_directory)
|
|
149
|
+
File.write(output_file_name, output)
|
|
150
|
+
|
|
151
|
+
group_metadata.add_test(
|
|
152
|
+
id: test_id,
|
|
153
|
+
file_name: base_output_file_name
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
11
157
|
def template
|
|
12
158
|
@template ||= File.read(File.join(__dir__, 'templates', 'provenance_revinclude_search.rb.erb'))
|
|
13
159
|
end
|