davinci_pdex_test_kit 0.9.1

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_pdex_test_kit/docs/payer_client_suite_description_v200.md +91 -0
  4. data/lib/davinci_pdex_test_kit/docs/payer_server_suite_description_v200.md +119 -0
  5. data/lib/davinci_pdex_test_kit/ext/inferno_core/record_response_route.rb +98 -0
  6. data/lib/davinci_pdex_test_kit/ext/inferno_core/request.rb +19 -0
  7. data/lib/davinci_pdex_test_kit/ext/inferno_core/runnable.rb +18 -0
  8. data/lib/davinci_pdex_test_kit/fhir_resource_navigation.rb +154 -0
  9. data/lib/davinci_pdex_test_kit/group_metadata.rb +109 -0
  10. data/lib/davinci_pdex_test_kit/metadata/mock_capability_statement.json +1052 -0
  11. data/lib/davinci_pdex_test_kit/metadata/mock_operation_outcome_resource.json +16 -0
  12. data/lib/davinci_pdex_test_kit/mock_server.rb +247 -0
  13. data/lib/davinci_pdex_test_kit/must_support_test.rb +252 -0
  14. data/lib/davinci_pdex_test_kit/pdex_payer_client/client_member_match_tests/client_member_match_submit_test.rb +24 -0
  15. data/lib/davinci_pdex_test_kit/pdex_payer_client/client_member_match_tests/client_member_match_validation_test.rb +23 -0
  16. data/lib/davinci_pdex_test_kit/pdex_payer_client/client_must_support_tests/client_member_match_must_support_submit_test.rb +26 -0
  17. data/lib/davinci_pdex_test_kit/pdex_payer_client/client_must_support_tests/client_member_match_must_support_validation_test.rb +32 -0
  18. data/lib/davinci_pdex_test_kit/pdex_payer_client/client_validation_test.rb +94 -0
  19. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/allergyintolerance_clinical_data_request_test.rb +23 -0
  20. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/careplan_clinical_data_request_test.rb +23 -0
  21. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/careteam_clinical_data_request_test.rb +23 -0
  22. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/condition_clinical_data_request_test.rb +23 -0
  23. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/device_clinical_data_request_test.rb +23 -0
  24. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/diagnosticreport_clinical_data_request_test.rb +23 -0
  25. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/documentreference_clinical_data_request_test.rb +23 -0
  26. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/encounter_clinical_data_request_test.rb +23 -0
  27. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/explanationofbenefit_clinical_data_request_test.rb +23 -0
  28. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/goal_clinical_data_request_test.rb +23 -0
  29. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/immunization_clinical_data_request_test.rb +23 -0
  30. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/initial_scratch_storing.rb +34 -0
  31. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/initial_wait_test.rb +28 -0
  32. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/location_clinical_data_request_test.rb +23 -0
  33. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/medicationdispense_clinical_data_request_test.rb +23 -0
  34. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/medicationrequest_clinical_data_request_test.rb +23 -0
  35. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/observation_clinical_data_request_test.rb +23 -0
  36. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/organization_clinical_data_request_test.rb +23 -0
  37. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/patient_clinical_data_request_test.rb +23 -0
  38. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/practitioner_clinical_data_request_test.rb +23 -0
  39. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/practitionerrole_clinical_data_request_test.rb +23 -0
  40. data/lib/davinci_pdex_test_kit/pdex_payer_client/clinical_data_request_tests/procedure_clinical_data_request_test.rb +23 -0
  41. data/lib/davinci_pdex_test_kit/pdex_payer_client/collection.rb +46 -0
  42. data/lib/davinci_pdex_test_kit/pdex_payer_client_suite.rb +152 -0
  43. data/lib/davinci_pdex_test_kit/pdex_payer_server/abstract_member_match_request_conformance_test.rb +33 -0
  44. data/lib/davinci_pdex_test_kit/pdex_payer_server/abstract_member_match_request_local_references_test.rb +35 -0
  45. data/lib/davinci_pdex_test_kit/pdex_payer_server/coverage_to_link_has_minimal_data_test.rb +52 -0
  46. data/lib/davinci_pdex_test_kit/pdex_payer_server/coverage_to_link_must_support_test.rb +28 -0
  47. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_id_search_test.rb +58 -0
  48. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_identifier_search_test.rb +58 -0
  49. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_must_support_test.rb +40 -0
  50. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_last_updated_search_test.rb +63 -0
  51. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_service_date_search_test.rb +63 -0
  52. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_type_search_test.rb +63 -0
  53. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_patient_use_search_test.rb +68 -0
  54. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_provenance_revinclude_search_test.rb +52 -0
  55. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_read_test.rb +26 -0
  56. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_reference_resolution_test.rb +43 -0
  57. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit/explanation_of_benefit_validation_test.rb +40 -0
  58. data/lib/davinci_pdex_test_kit/pdex_payer_server/explanation_of_benefit_group.rb +105 -0
  59. data/lib/davinci_pdex_test_kit/pdex_payer_server/export_patient_group.rb +103 -0
  60. data/lib/davinci_pdex_test_kit/pdex_payer_server/export_validation_group.rb +59 -0
  61. data/lib/davinci_pdex_test_kit/pdex_payer_server/multiple_member_matches_group.rb +66 -0
  62. data/lib/davinci_pdex_test_kit/pdex_payer_server/no_member_matches_group.rb +69 -0
  63. data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_clinical_data.rb +66 -0
  64. data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_everything.rb +184 -0
  65. data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_export.rb +67 -0
  66. data/lib/davinci_pdex_test_kit/pdex_payer_server/workflow_member_match.rb +171 -0
  67. data/lib/davinci_pdex_test_kit/pdex_payer_server_suite.rb +158 -0
  68. data/lib/davinci_pdex_test_kit/pdex_provider_client_suite.rb +36 -0
  69. data/lib/davinci_pdex_test_kit/tags.rb +9 -0
  70. data/lib/davinci_pdex_test_kit/urls.rb +67 -0
  71. data/lib/davinci_pdex_test_kit/user_input_response.rb +32 -0
  72. data/lib/davinci_pdex_test_kit/version.rb +5 -0
  73. data/lib/davinci_pdex_test_kit.rb +8 -0
  74. 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