us_core_test_kit 0.11.0 → 0.11.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/lib/us_core_test_kit/generated/v6.1.0/metadata.yml +0 -4
- data/lib/us_core_test_kit/generated/v6.1.0/patient/metadata.yml +0 -4
- data/lib/us_core_test_kit/generated/v6.1.0/patient/patient_must_support_test.rb +0 -1
- data/lib/us_core_test_kit/generated/v7.0.0/metadata.yml +0 -4
- data/lib/us_core_test_kit/generated/v7.0.0/patient/metadata.yml +0 -4
- data/lib/us_core_test_kit/generated/v7.0.0/patient/patient_must_support_test.rb +0 -1
- data/lib/us_core_test_kit/generator/group_generator.rb +1 -2
- data/lib/us_core_test_kit/generator/group_metadata.rb +4 -0
- data/lib/us_core_test_kit/generator/must_support_metadata_extractor_us_core_6.rb +8 -0
- data/lib/us_core_test_kit/generator/must_support_metadata_extractor_us_core_7.rb +1 -0
- data/lib/us_core_test_kit/generator/special_cases.rb +3 -3
- data/lib/us_core_test_kit/granular_scope_read_test.rb +11 -4
- data/lib/us_core_test_kit/must_support_test.rb +1 -215
- data/lib/us_core_test_kit/practitioner_address_test.rb +1 -1
- data/lib/us_core_test_kit/version.rb +2 -2
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a024a9b164018f0dab07cef2db848b746cd08daf78b76555ddc76697e10be68
|
4
|
+
data.tar.gz: cffeb336d69befe4f7af954f0d1967c424132413a8a66cce740ebcba390effcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4dc45be569bb789b1e88db5e5ee7b84f90438363dcaeb690a64359a84d2e77bd9e6981482d95ef13dfef7fa451f6c0e6b827e8a6c66511a16e728be9d79a70e
|
7
|
+
data.tar.gz: 6fdcff078c88cf5f340b0e06e0901bb9d7797b0f79c7826cf37f0b650b676d8e6c39e0ba6c12808e501247d6018d8648ea16334858f862b0532ee1acd25f18cd
|
@@ -10419,10 +10419,6 @@
|
|
10419
10419
|
:path: extension
|
10420
10420
|
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-sex
|
10421
10421
|
:uscdi_only: true
|
10422
|
-
- :id: Patient.extension:genderIdentity
|
10423
|
-
:path: extension
|
10424
|
-
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity
|
10425
|
-
:uscdi_only: true
|
10426
10422
|
:slices: []
|
10427
10423
|
:elements:
|
10428
10424
|
- :path: identifier
|
@@ -188,10 +188,6 @@
|
|
188
188
|
:path: extension
|
189
189
|
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-sex
|
190
190
|
:uscdi_only: true
|
191
|
-
- :id: Patient.extension:genderIdentity
|
192
|
-
:path: extension
|
193
|
-
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity
|
194
|
-
:uscdi_only: true
|
195
191
|
:slices: []
|
196
192
|
:elements:
|
197
193
|
- :path: identifier
|
@@ -12229,10 +12229,6 @@
|
|
12229
12229
|
:path: extension
|
12230
12230
|
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-sex
|
12231
12231
|
:uscdi_only: true
|
12232
|
-
- :id: Patient.extension:genderIdentity
|
12233
|
-
:path: extension
|
12234
|
-
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity
|
12235
|
-
:uscdi_only: true
|
12236
12232
|
:slices: []
|
12237
12233
|
:elements:
|
12238
12234
|
- :path: identifier
|
@@ -188,10 +188,6 @@
|
|
188
188
|
:path: extension
|
189
189
|
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-sex
|
190
190
|
:uscdi_only: true
|
191
|
-
- :id: Patient.extension:genderIdentity
|
192
|
-
:path: extension
|
193
|
-
:url: http://hl7.org/fhir/us/core/StructureDefinition/us-core-genderIdentity
|
194
|
-
:uscdi_only: true
|
195
191
|
:slices: []
|
196
192
|
:elements:
|
197
193
|
- :path: identifier
|
@@ -95,8 +95,7 @@ module USCoreTestKit
|
|
95
95
|
|
96
96
|
|
97
97
|
def optional?
|
98
|
-
SpecialCases::OPTIONAL_RESOURCES.include?(resource_type) ||
|
99
|
-
SpecialCases::OPTIONAL_PROFILES.include?(profile_url)
|
98
|
+
SpecialCases::OPTIONAL_RESOURCES.include?(resource_type) || group_metadata.optional_profile?
|
100
99
|
end
|
101
100
|
|
102
101
|
def generate
|
@@ -63,6 +63,10 @@ module USCoreTestKit
|
|
63
63
|
SpecialCases::NON_USCDI_RESOURCES.key?(resource) && SpecialCases::NON_USCDI_RESOURCES[resource].include?(reformatted_version)
|
64
64
|
end
|
65
65
|
|
66
|
+
def optional_profile?
|
67
|
+
SpecialCases::OPTIONAL_PROFILES.key?(profile_url) && SpecialCases::OPTIONAL_PROFILES[profile_url].include?(reformatted_version)
|
68
|
+
end
|
69
|
+
|
66
70
|
def searchable_delayed_resource?
|
67
71
|
SpecialCases::SEARCHABLE_DELAYED_RESOURCES.key?(resource) && SpecialCases::SEARCHABLE_DELAYED_RESOURCES[resource].include?(reformatted_version)
|
68
72
|
end
|
@@ -30,6 +30,7 @@ module USCoreTestKit
|
|
30
30
|
update_smoking_status_effective
|
31
31
|
remove_practitioner_address
|
32
32
|
remove_diagnosticreport_media
|
33
|
+
remove_patient_gender_identity
|
33
34
|
end
|
34
35
|
|
35
36
|
def add_must_support_choices
|
@@ -106,6 +107,13 @@ module USCoreTestKit
|
|
106
107
|
return unless profile.id == 'us-core-diagnosticreport-note'
|
107
108
|
must_supports[:elements].delete_if { |element| element[:path].start_with?('media') }
|
108
109
|
end
|
110
|
+
|
111
|
+
# genderIdentify is removed as directed by ASTP/ONC enforcement discretion issued on March 21, 2025:
|
112
|
+
# https://www.healthit.gov/topic/certification-ehrs/enforcement-discretion
|
113
|
+
def remove_patient_gender_identity
|
114
|
+
return unless profile.type == 'Patient'
|
115
|
+
must_supports[:extensions].delete_if { |extension| extension[:id] == 'Patient.extension:genderIdentity' }
|
116
|
+
end
|
109
117
|
end
|
110
118
|
end
|
111
119
|
end
|
@@ -17,9 +17,9 @@ module USCoreTestKit
|
|
17
17
|
'QuestionnaireResponse'
|
18
18
|
].freeze
|
19
19
|
|
20
|
-
OPTIONAL_PROFILES =
|
21
|
-
'http://hl7.org/fhir/us/core/StructureDefinition/us-core-simple-observation'
|
22
|
-
|
20
|
+
OPTIONAL_PROFILES = {
|
21
|
+
'http://hl7.org/fhir/us/core/StructureDefinition/us-core-simple-observation' => ['v610', 'v700']
|
22
|
+
}.freeze
|
23
23
|
|
24
24
|
NON_USCDI_RESOURCES = {
|
25
25
|
'Encounter' => ['v311', 'v400'],
|
@@ -22,7 +22,7 @@ module USCoreTestKit
|
|
22
22
|
"No #{resource_type} reads found"
|
23
23
|
previous_resources_for_reads = previous_request_resources.values.flatten
|
24
24
|
|
25
|
-
resource_specific_granular_scope_search_params.each do |scope|
|
25
|
+
resource_specific_granular_scope_search_params.each do |scope|
|
26
26
|
current_scope = granular_scopes.find {|granular| granular.include?(scope[:name]) && granular.include?(scope[:value])}
|
27
27
|
|
28
28
|
resource_matching_scope = previous_resources_for_reads.find do |prev_resource|
|
@@ -37,19 +37,26 @@ module USCoreTestKit
|
|
37
37
|
end
|
38
38
|
|
39
39
|
nonmatching_resource = previous_resources_for_reads.find do |prev_resource|
|
40
|
-
resource_specific_granular_scope_search_params.none? do |scope|
|
40
|
+
resource_specific_granular_scope_search_params.none? do |scope|
|
41
41
|
resource_matches_param?(prev_resource, scope[:name], scope[:value])
|
42
42
|
end
|
43
43
|
end
|
44
44
|
if nonmatching_resource
|
45
45
|
fhir_read resource_type, nonmatching_resource.id
|
46
46
|
assert (response && response[:status]) != 200, "Server incorrectly responded with a successful status, read should fail due to scopes."
|
47
|
-
|
47
|
+
|
48
|
+
begin
|
49
|
+
res = resource
|
50
|
+
rescue NoMethodError
|
51
|
+
res = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
assert (!res || res.resourceType == 'OperationOutcome'), "Read shall fail due to scopes. Server shall return 4xx status code with an optional OperationOutcome payload."
|
48
55
|
else
|
49
56
|
info "Unable to find a resource that does not match scopes."
|
50
57
|
end
|
51
58
|
end
|
52
|
-
|
59
|
+
|
53
60
|
def load_previous_requests
|
54
61
|
params = metadata.searches.first[:names]
|
55
62
|
search_params_as_hash = params.each_with_object({}) do |name, hash|
|
@@ -14,221 +14,7 @@ module USCoreTestKit
|
|
14
14
|
def perform_must_support_test(resources)
|
15
15
|
skip_if resources.blank?, "No #{resource_type} resources were found"
|
16
16
|
|
17
|
-
|
18
|
-
missing_slices(resources)
|
19
|
-
missing_extensions(resources)
|
20
|
-
|
21
|
-
handle_must_support_choices if metadata.must_supports[:choices].present?
|
22
|
-
|
23
|
-
if (missing_elements + missing_slices + missing_extensions).length.zero?
|
24
|
-
pass
|
25
|
-
end
|
26
|
-
skip "Could not find #{missing_must_support_strings.join(', ')} in the #{resources.length} " \
|
27
|
-
"provided #{resource_type} resource(s)"
|
28
|
-
end
|
29
|
-
|
30
|
-
def handle_must_support_choices
|
31
|
-
missing_elements.delete_if do |element|
|
32
|
-
choices = metadata.must_supports[:choices].find { |choice| choice[:paths]&.include?(element[:path]) }
|
33
|
-
is_any_choice_supported?(choices)
|
34
|
-
end
|
35
|
-
|
36
|
-
missing_extensions.delete_if do |extension|
|
37
|
-
choices = metadata.must_supports[:choices].find { |choice| choice[:extension_ids]&.include?(extension[:id]) }
|
38
|
-
is_any_choice_supported?(choices)
|
39
|
-
end
|
40
|
-
|
41
|
-
missing_slices.delete_if do |slice|
|
42
|
-
choices = metadata.must_supports[:choices].find { |choice| choice[:slice_names]&.include?(slice[:name]) }
|
43
|
-
is_any_choice_supported?(choices)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def is_any_choice_supported? (choices)
|
48
|
-
choices.present? &&
|
49
|
-
(
|
50
|
-
choices[:paths]&.any? { |path| missing_elements.none? { |element| element[:path] == path } } ||
|
51
|
-
choices[:extension_ids]&.any? { |extension_id| missing_extensions.none? { |extension| extension[:id] == extension_id} } ||
|
52
|
-
choices[:slice_names]&.any? { |slice_name| missing_slices.none? { |slice| slice[:name] == slice_name} }
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
def missing_must_support_strings
|
57
|
-
missing_elements.map { |element_definition| missing_element_string(element_definition) } +
|
58
|
-
missing_slices.map { |slice_definition| slice_definition[:slice_id] } +
|
59
|
-
missing_extensions.map { |extension_definition| extension_definition[:id] }
|
60
|
-
end
|
61
|
-
|
62
|
-
def missing_element_string(element_definition)
|
63
|
-
if element_definition[:fixed_value].present?
|
64
|
-
"#{element_definition[:path]}:#{element_definition[:fixed_value]}"
|
65
|
-
else
|
66
|
-
element_definition[:path]
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def exclude_uscdi_only_test?
|
71
|
-
config.options[:exclude_uscdi_only_test] == true
|
72
|
-
end
|
73
|
-
|
74
|
-
def must_support_extensions
|
75
|
-
if exclude_uscdi_only_test?
|
76
|
-
metadata.must_supports[:extensions].reject{ |extension| extension[:uscdi_only] }
|
77
|
-
else
|
78
|
-
metadata.must_supports[:extensions]
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def missing_extensions(resources = [])
|
83
|
-
@missing_extensions ||=
|
84
|
-
must_support_extensions.select do |extension_definition|
|
85
|
-
resources.none? do |resource|
|
86
|
-
path = extension_definition[:path]
|
87
|
-
|
88
|
-
if path == 'extension'
|
89
|
-
resource.extension.any? { |extension| extension.url == extension_definition[:url] }
|
90
|
-
else
|
91
|
-
extension = find_a_value_at(resource, path) do |el|
|
92
|
-
el.url == extension_definition[:url]
|
93
|
-
end
|
94
|
-
|
95
|
-
extension.present?
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def must_support_elements
|
102
|
-
if exclude_uscdi_only_test?
|
103
|
-
metadata.must_supports[:elements].reject{ |element| element[:uscdi_only] }
|
104
|
-
else
|
105
|
-
metadata.must_supports[:elements]
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def missing_elements(resources = [])
|
110
|
-
@missing_elements ||= find_missing_elements(resources, must_support_elements)
|
111
|
-
@missing_elements
|
112
|
-
end
|
113
|
-
|
114
|
-
def find_missing_elements(resources, must_support_elements)
|
115
|
-
must_support_elements.select do |element_definition|
|
116
|
-
resources.none? do |resource|
|
117
|
-
path = element_definition[:path]
|
118
|
-
ms_extension_urls = must_support_extensions.select { |ex| ex[:path] == "#{path}.extension" }
|
119
|
-
.map { |ex| ex[:url] }
|
120
|
-
|
121
|
-
value_found = find_a_value_at(resource, path) do |value|
|
122
|
-
if value.instance_of?(USCoreTestKit::PrimitiveType) && ms_extension_urls.present?
|
123
|
-
urls = value.extension&.map(&:url)
|
124
|
-
has_ms_extension = (urls & ms_extension_urls).present?
|
125
|
-
end
|
126
|
-
|
127
|
-
unless has_ms_extension
|
128
|
-
value = value.value if value.instance_of?(USCoreTestKit::PrimitiveType)
|
129
|
-
value_without_extensions =
|
130
|
-
value.respond_to?(:to_hash) ? value.to_hash.reject { |key, _| key == 'extension' } : value
|
131
|
-
end
|
132
|
-
|
133
|
-
(has_ms_extension || value_without_extensions.present? || value_without_extensions == false) &&
|
134
|
-
(element_definition[:fixed_value].blank? || value == element_definition[:fixed_value])
|
135
|
-
end
|
136
|
-
# Note that false.present? => false, which is why we need to add this extra check
|
137
|
-
value_found.present? || value_found == false
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def must_support_slices
|
143
|
-
if exclude_uscdi_only_test?
|
144
|
-
metadata.must_supports[:slices].reject{ |slice| slice[:uscdi_only] }
|
145
|
-
else
|
146
|
-
metadata.must_supports[:slices]
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def missing_slices(resources = [])
|
151
|
-
@missing_slices ||=
|
152
|
-
must_support_slices.select do |slice|
|
153
|
-
resources.none? do |resource|
|
154
|
-
path = slice[:path] # .delete_suffix('[x]')
|
155
|
-
find_slice(resource, path, slice[:discriminator]).present?
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def find_slice(resource, path, discriminator)
|
161
|
-
find_a_value_at(resource, path) do |element|
|
162
|
-
case discriminator[:type]
|
163
|
-
when 'patternCodeableConcept'
|
164
|
-
coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
|
165
|
-
find_a_value_at(element, coding_path) do |coding|
|
166
|
-
coding.code == discriminator[:code] && coding.system == discriminator[:system]
|
167
|
-
end
|
168
|
-
when 'patternCoding'
|
169
|
-
coding_path = discriminator[:path].present? ? discriminator[:path] : ''
|
170
|
-
find_a_value_at(element, coding_path) do |coding|
|
171
|
-
coding.code == discriminator[:code] && coding.system == discriminator[:system]
|
172
|
-
end
|
173
|
-
when 'patternIdentifier'
|
174
|
-
find_a_value_at(element, discriminator[:path]) { |identifier| identifier.system == discriminator[:system] }
|
175
|
-
when 'value'
|
176
|
-
values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
|
177
|
-
find_slice_by_values(element, values)
|
178
|
-
when 'type'
|
179
|
-
case discriminator[:code]
|
180
|
-
when 'Date'
|
181
|
-
begin
|
182
|
-
Date.parse(element)
|
183
|
-
rescue ArgumentError
|
184
|
-
false
|
185
|
-
end
|
186
|
-
when 'DateTime'
|
187
|
-
begin
|
188
|
-
DateTime.parse(element)
|
189
|
-
rescue ArgumentError
|
190
|
-
false
|
191
|
-
end
|
192
|
-
when 'String'
|
193
|
-
element.is_a? String
|
194
|
-
else
|
195
|
-
element.is_a? FHIR.const_get(discriminator[:code])
|
196
|
-
end
|
197
|
-
when 'requiredBinding'
|
198
|
-
coding_path = discriminator[:path].present? ? "#{discriminator[:path]}.coding" : 'coding'
|
199
|
-
|
200
|
-
find_a_value_at(element, coding_path) do |coding|
|
201
|
-
discriminator[:values].any? { |value| value[:system] == coding.system && value[:code] == coding.code }
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def find_slice_by_values(element, value_definitions)
|
208
|
-
path_prefixes = value_definitions.map { |value_definition| value_definition[:path].first }.uniq
|
209
|
-
Array.wrap(element).find do |el|
|
210
|
-
path_prefixes.all? do |path_prefix|
|
211
|
-
value_definitions_for_path =
|
212
|
-
value_definitions
|
213
|
-
.select { |value_definition| value_definition[:path].first == path_prefix }
|
214
|
-
.each { |value_definition| value_definition[:path].shift }
|
215
|
-
|
216
|
-
find_a_value_at(el, path_prefix) do |el_found|
|
217
|
-
child_element_value_definitions, current_element_value_definitions =
|
218
|
-
value_definitions_for_path.partition { |value_definition| value_definition[:path].present? }
|
219
|
-
|
220
|
-
current_element_values_match =
|
221
|
-
current_element_value_definitions
|
222
|
-
.all? { |value_definition| value_definition[:value] == el_found }
|
223
|
-
|
224
|
-
child_element_values_match =
|
225
|
-
child_element_value_definitions.present? ?
|
226
|
-
find_slice_by_values(el_found, child_element_value_definitions) : true
|
227
|
-
|
228
|
-
current_element_values_match && child_element_values_match
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
17
|
+
skip { assert_must_support_elements_present(resources, nil, metadata:) }
|
232
18
|
end
|
233
19
|
end
|
234
20
|
end
|
@@ -25,7 +25,7 @@ module USCoreTestKit
|
|
25
25
|
|
26
26
|
def verify_practitioner_address
|
27
27
|
references = scratch.dig(:references, 'Practitioner')
|
28
|
-
assert references.any?, 'No
|
28
|
+
assert references.any?, 'No Practitioner references found.'
|
29
29
|
|
30
30
|
@missing_elements = MUST_SUPPORT_ELEMENTS
|
31
31
|
practitioners = []
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: us_core_test_kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen MacVicar
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inferno_core
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.6.
|
19
|
+
version: 0.6.8
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.6.
|
26
|
+
version: 0.6.8
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: smart_app_launch_test_kit
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.6.
|
33
|
+
version: 0.6.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.6.
|
40
|
+
version: 0.6.1
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: tls_test_kit
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|