davinci_pdex_test_kit 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/davinci_pdex_test_kit/docs/payer_client_suite_description_v200.md +91 -0
- data/lib/davinci_pdex_test_kit/docs/payer_server_suite_description_v200.md +119 -0
- data/lib/davinci_pdex_test_kit/ext/inferno_core/record_response_route.rb +98 -0
- data/lib/davinci_pdex_test_kit/ext/inferno_core/request.rb +19 -0
- data/lib/davinci_pdex_test_kit/ext/inferno_core/runnable.rb +18 -0
- data/lib/davinci_pdex_test_kit/fhir_resource_navigation.rb +154 -0
- data/lib/davinci_pdex_test_kit/group_metadata.rb +109 -0
- data/lib/davinci_pdex_test_kit/metadata/mock_capability_statement.json +1052 -0
- data/lib/davinci_pdex_test_kit/metadata/mock_operation_outcome_resource.json +16 -0
- data/lib/davinci_pdex_test_kit/mock_server.rb +247 -0
- data/lib/davinci_pdex_test_kit/must_support_test.rb +252 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/client_member_match_tests/client_member_match_submit_test.rb +24 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/client_member_match_tests/client_member_match_validation_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/client_must_support_tests/client_member_match_must_support_submit_test.rb +26 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/client_must_support_tests/client_member_match_must_support_validation_test.rb +32 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/client_validation_test.rb +94 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/allergyintolerance_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/careplan_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/careteam_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/condition_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/device_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/diagnosticreport_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/documentreference_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/encounter_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/explanationofbenefit_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/goal_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/immunization_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/initial_scratch_storing.rb +34 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/initial_wait_test.rb +28 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/location_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/medicationdispense_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/medicationrequest_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/observation_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/organization_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/patient_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/practitioner_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/practitionerrole_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/procedure_clinical_data_request_test.rb +23 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client/collection.rb +46 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_client_suite.rb +152 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/abstract_member_match_request_conformance_test.rb +33 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/abstract_member_match_request_local_references_test.rb +35 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/coverage_to_link_has_minimal_data_test.rb +52 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/coverage_to_link_must_support_test.rb +28 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_id_search_test.rb +58 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_identifier_search_test.rb +58 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_must_support_test.rb +40 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_last_updated_search_test.rb +63 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_service_date_search_test.rb +63 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_type_search_test.rb +63 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_use_search_test.rb +68 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_provenance_revinclude_search_test.rb +52 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_read_test.rb +26 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_reference_resolution_test.rb +43 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_validation_test.rb +40 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit_group.rb +105 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/export_patient_group.rb +103 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/export_validation_group.rb +59 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/multiple_member_matches_group.rb +66 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/no_member_matches_group.rb +69 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_clinical_data.rb +66 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_everything.rb +184 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_export.rb +67 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_member_match.rb +171 -0
- data/lib/davinci_pdex_test_kit/pdex_payer_server_suite.rb +158 -0
- data/lib/davinci_pdex_test_kit/pdex_provider_client_suite.rb +36 -0
- data/lib/davinci_pdex_test_kit/tags.rb +9 -0
- data/lib/davinci_pdex_test_kit/urls.rb +67 -0
- data/lib/davinci_pdex_test_kit/user_input_response.rb +32 -0
- data/lib/davinci_pdex_test_kit/version.rb +5 -0
- data/lib/davinci_pdex_test_kit.rb +8 -0
- metadata +218 -0
@@ -0,0 +1,154 @@
|
|
1
|
+
module DaVinciPDexTestKit
|
2
|
+
module FHIRResourceNavigation
|
3
|
+
DAR_EXTENSION_URL = 'http://hl7.org/fhir/StructureDefinition/data-absent-reason'.freeze
|
4
|
+
PRIMITIVE_DATA_TYPES = FHIR::PRIMITIVES.keys
|
5
|
+
|
6
|
+
def resolve_path(elements, path)
|
7
|
+
elements = Array.wrap(elements)
|
8
|
+
return elements if path.blank?
|
9
|
+
|
10
|
+
paths = path.split(/(?<!hl7)\./)
|
11
|
+
segment = paths.first
|
12
|
+
remaining_path = paths.drop(1).join('.')
|
13
|
+
|
14
|
+
elements.flat_map do |element|
|
15
|
+
child = get_next_value(element, segment)
|
16
|
+
resolve_path(child, remaining_path)
|
17
|
+
end.compact
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_a_value_at(element, path, include_dar: false)
|
21
|
+
return nil if element.nil?
|
22
|
+
|
23
|
+
elements = Array.wrap(element)
|
24
|
+
if path.empty?
|
25
|
+
unless include_dar
|
26
|
+
elements = elements.reject do |el|
|
27
|
+
el.respond_to?(:extension) && el.extension.any? { |ext| ext.url == DAR_EXTENSION_URL}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
return elements.find { |el| yield(el) } if block_given?
|
32
|
+
return elements.first
|
33
|
+
end
|
34
|
+
|
35
|
+
path_segments = path.split(/(?<!hl7)\./)
|
36
|
+
|
37
|
+
segment = path_segments.shift.delete_suffix('[x]').gsub(/^class$/, 'local_class').gsub(/\[x\]:/, ':').to_sym
|
38
|
+
no_elements_present =
|
39
|
+
elements.none? do |element|
|
40
|
+
child = get_next_value(element, segment)
|
41
|
+
child.present? || child == false
|
42
|
+
end
|
43
|
+
return nil if no_elements_present
|
44
|
+
|
45
|
+
remaining_path = path_segments.join('.')
|
46
|
+
elements.each do |element|
|
47
|
+
child = get_next_value(element, segment)
|
48
|
+
element_found =
|
49
|
+
if block_given?
|
50
|
+
find_a_value_at(child, remaining_path, include_dar: include_dar) { |value_found| yield(value_found) }
|
51
|
+
else
|
52
|
+
find_a_value_at(child, remaining_path, include_dar: include_dar)
|
53
|
+
end
|
54
|
+
return element_found if element_found.present? || element_found == false
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_next_value(element, property)
|
60
|
+
extension_url = property[/(?<=where\(url=').*(?='\))/]
|
61
|
+
if extension_url.present?
|
62
|
+
element.url == extension_url ? element : nil
|
63
|
+
elsif property.to_s.include?(':') && !property.to_s.include?('url')
|
64
|
+
slice = find_slice_via_discriminator(element, property)
|
65
|
+
slice
|
66
|
+
else
|
67
|
+
result = element.send(property)
|
68
|
+
result = find_primitive_extension(element, property) if result.nil?
|
69
|
+
result
|
70
|
+
end
|
71
|
+
rescue NoMethodError
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_primitive_extension(element, property)
|
76
|
+
type = element.class::METADATA[property.to_s]['type']
|
77
|
+
source_value = element.source_hash["_#{property}"]
|
78
|
+
|
79
|
+
PRIMITIVE_DATA_TYPES.include?(type) && source_value.present? ? FHIR::Element.new(source_value) : nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def find_slice_via_discriminator(element, property)
|
83
|
+
element_name = property.to_s.split(':')[0].gsub(/^class$/, 'local_class')
|
84
|
+
slice_name = property.to_s.split(':')[1].gsub(/^class$/, 'local_class')
|
85
|
+
if metadata.present?
|
86
|
+
slice_by_name = metadata.must_supports[:slices].find{ |slice| slice[:slice_name] == slice_name }
|
87
|
+
discriminator = slice_by_name[:discriminator]
|
88
|
+
slices = Array.wrap(element.send(element_name))
|
89
|
+
slices.find do |slice|
|
90
|
+
case discriminator[:type]
|
91
|
+
when 'patternCodeableConcept'
|
92
|
+
slice_value = discriminator[:path].present? ? slice.send("#{discriminator[:path]}")&.coding : slice.coding
|
93
|
+
slice_value&.any? { |coding| coding.code == discriminator[:code] && coding.system == discriminator[:system] }
|
94
|
+
when 'patternCoding'
|
95
|
+
slice_value = discriminator[:path].present? ? slice.send(discriminator[:path]) : slice
|
96
|
+
slice_value&.code == discriminator[:code] && slice_value&.system == discriminator[:system]
|
97
|
+
when 'patternIdentifier'
|
98
|
+
slice.identifier.system == discriminator[:system]
|
99
|
+
when 'value'
|
100
|
+
values = discriminator[:values].map { |value| value.merge(path: value[:path].split('.')) }
|
101
|
+
verify_slice_by_values(slice, values)
|
102
|
+
when 'type'
|
103
|
+
case discriminator[:code]
|
104
|
+
when 'Date'
|
105
|
+
begin
|
106
|
+
Date.parse(slice)
|
107
|
+
rescue ArgumentError
|
108
|
+
false
|
109
|
+
end
|
110
|
+
when 'DateTime'
|
111
|
+
begin
|
112
|
+
DateTime.parse(slice)
|
113
|
+
rescue ArgumentError
|
114
|
+
false
|
115
|
+
end
|
116
|
+
when 'String'
|
117
|
+
slice.is_a? String
|
118
|
+
else
|
119
|
+
slice.is_a? FHIR.const_get(discriminator[:code])
|
120
|
+
end
|
121
|
+
when 'requiredBinding'
|
122
|
+
slice_value = discriminator[:path].present? ? slice.send("#{discriminator[:path]}").coding : slice.coding
|
123
|
+
slice_value {|coding| discriminator[:values].include?(coding.code) }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
else
|
127
|
+
#TODO: Error handling for if this file doesn't have access to metadata for some reason (begin/rescue with StandardError?)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def verify_slice_by_values(element, value_definitions)
|
132
|
+
path_prefixes = value_definitions.map { |value_definition| value_definition[:path].first }.uniq
|
133
|
+
path_prefixes.all? do |path_prefix|
|
134
|
+
value_definitions_for_path =
|
135
|
+
value_definitions
|
136
|
+
.select { |value_definition| value_definition[:path].first == path_prefix }
|
137
|
+
.each { |value_definition| value_definition[:path].shift }
|
138
|
+
find_a_value_at(element, path_prefix) do |el_found|
|
139
|
+
child_element_value_definitions, current_element_value_definitions =
|
140
|
+
value_definitions_for_path.partition { |value_definition| value_definition[:path].present? }
|
141
|
+
|
142
|
+
current_element_values_match =
|
143
|
+
current_element_value_definitions
|
144
|
+
.all? { |value_definition| value_definition[:value] == el_found }
|
145
|
+
|
146
|
+
child_element_values_match =
|
147
|
+
child_element_value_definitions.present? ?
|
148
|
+
verify_slice_by_values(el_found, child_element_value_definitions) : true
|
149
|
+
current_element_values_match && child_element_values_match
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module DaVinciPDexTestKit
|
2
|
+
class GroupMetadata
|
3
|
+
ATTRIBUTES = [
|
4
|
+
:name,
|
5
|
+
:class_name,
|
6
|
+
:version,
|
7
|
+
:reformatted_version,
|
8
|
+
:resource,
|
9
|
+
:profile_url,
|
10
|
+
:profile_name,
|
11
|
+
:profile_version,
|
12
|
+
:title,
|
13
|
+
:short_description,
|
14
|
+
:is_delayed,
|
15
|
+
:interactions,
|
16
|
+
:operations,
|
17
|
+
:searches,
|
18
|
+
:search_definitions,
|
19
|
+
:include_params,
|
20
|
+
:revincludes,
|
21
|
+
:required_concepts,
|
22
|
+
:must_supports,
|
23
|
+
:mandatory_elements,
|
24
|
+
:bindings,
|
25
|
+
:references,
|
26
|
+
:tests,
|
27
|
+
:granular_scope_tests,
|
28
|
+
:id,
|
29
|
+
:file_name,
|
30
|
+
:delayed_references
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
ATTRIBUTES.each { |name| attr_accessor name }
|
34
|
+
|
35
|
+
def initialize(metadata)
|
36
|
+
metadata.each do |key, value|
|
37
|
+
raise "Unknown attribute #{key}" unless ATTRIBUTES.include? key
|
38
|
+
|
39
|
+
instance_variable_set(:"@#{key}", value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def delayed?
|
44
|
+
@is_delayed ||= if resource == 'Patient'
|
45
|
+
false
|
46
|
+
else
|
47
|
+
no_patient_searches? || non_uscdi_resource?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def exclude_search_tests?
|
52
|
+
delayed? && !searchable_delayed_resource?
|
53
|
+
end
|
54
|
+
|
55
|
+
def no_patient_searches?
|
56
|
+
searches.none? { |search| search[:names].include? 'patient' }
|
57
|
+
end
|
58
|
+
|
59
|
+
def non_uscdi_resource?
|
60
|
+
SpecialCases::NON_USCDI_RESOURCES.key?(resource) && SpecialCases::NON_USCDI_RESOURCES[resource].include?(reformatted_version)
|
61
|
+
end
|
62
|
+
|
63
|
+
def searchable_delayed_resource?
|
64
|
+
SpecialCases::SEARCHABLE_DELAYED_RESOURCES.key?(resource) && SpecialCases::SEARCHABLE_DELAYED_RESOURCES[resource].include?(reformatted_version)
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_test(id:, file_name:)
|
68
|
+
self.tests ||= []
|
69
|
+
|
70
|
+
test_metadata = {
|
71
|
+
id: id,
|
72
|
+
file_name: file_name
|
73
|
+
}
|
74
|
+
|
75
|
+
if delayed? && id.include?('read')
|
76
|
+
self.tests.unshift(test_metadata)
|
77
|
+
else
|
78
|
+
self.tests << test_metadata
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_granular_scope_test(id:, file_name:)
|
83
|
+
self.granular_scope_tests ||= []
|
84
|
+
|
85
|
+
self.granular_scope_tests << {
|
86
|
+
id:,
|
87
|
+
file_name:
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_hash
|
92
|
+
ATTRIBUTES.each_with_object({}) { |key, hash| hash[key] = send(key) unless send(key).nil? }
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_delayed_references(delayed_profiles, ig_resources)
|
96
|
+
self.delayed_references =
|
97
|
+
references
|
98
|
+
.select { |reference| (reference[:profiles] & delayed_profiles).present? }
|
99
|
+
.map do |reference|
|
100
|
+
profile_urls = (reference[:profiles] & delayed_profiles)
|
101
|
+
delayed_resources = profile_urls.map { |url| ig_resources.resource_for_profile(url) }
|
102
|
+
{
|
103
|
+
path: reference[:path].gsub("#{resource}.", ''),
|
104
|
+
resources: delayed_resources
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|