davinci_us_drug_formulary_test_kit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/conformance_support_test.rb +41 -0
  4. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/fhir_version_test.rb +15 -0
  5. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/instantiate_test.rb +19 -0
  6. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/json_support_test.rb +40 -0
  7. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/capability_statement/profile_support_test.rb +39 -0
  8. data/lib/davinci_us_drug_formulary_test_kit/custom_groups/v2.0.1/capability_statement_group.rb +78 -0
  9. data/lib/davinci_us_drug_formulary_test_kit/date_search_validation.rb +121 -0
  10. data/lib/davinci_us_drug_formulary_test_kit/fhir_resource_navigation.rb +155 -0
  11. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_code_search_test.rb +54 -0
  12. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_drug_tier_search_test.rb +43 -0
  13. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_formulary_include_search_test.rb +40 -0
  14. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_formulary_search_test.rb +43 -0
  15. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_id_search_test.rb +42 -0
  16. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_lastupdated_search_test.rb +42 -0
  17. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_must_support_test.rb +46 -0
  18. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_period_search_test.rb +42 -0
  19. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_pharmacy_benefit_type_search_test.rb +43 -0
  20. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_read_test.rb +26 -0
  21. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_reference_resolution_test.rb +40 -0
  22. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_status_search_test.rb +42 -0
  23. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_subject_include_search_test.rb +40 -0
  24. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_subject_search_test.rb +43 -0
  25. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/basic_validation_test.rb +39 -0
  26. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic/metadata.yml +292 -0
  27. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/basic_group.rb +107 -0
  28. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_coverage_area_search_test.rb +45 -0
  29. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_coverage_type_search_test.rb +45 -0
  30. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_formulary_coverage_search_test.rb +45 -0
  31. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_id_search_test.rb +42 -0
  32. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_identifier_search_test.rb +43 -0
  33. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_lastupdated_search_test.rb +42 -0
  34. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_must_support_test.rb +42 -0
  35. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_name_search_test.rb +42 -0
  36. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_period_search_test.rb +42 -0
  37. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_read_test.rb +26 -0
  38. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_status_search_test.rb +53 -0
  39. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_type_search_test.rb +43 -0
  40. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/formulary_validation_test.rb +39 -0
  41. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary/metadata.yml +278 -0
  42. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/formulary_group.rb +104 -0
  43. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_city_search_test.rb +44 -0
  44. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_postalcode_search_test.rb +44 -0
  45. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_search_test.rb +42 -0
  46. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_address_state_search_test.rb +44 -0
  47. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_id_search_test.rb +56 -0
  48. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_lastupdated_search_test.rb +44 -0
  49. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_must_support_test.rb +37 -0
  50. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_read_test.rb +26 -0
  51. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/location_validation_test.rb +39 -0
  52. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location/metadata.yml +177 -0
  53. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/location_group.rb +92 -0
  54. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_code_search_test.rb +43 -0
  55. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_doseform_search_test.rb +45 -0
  56. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_drug_name_search_test.rb +44 -0
  57. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_id_search_test.rb +42 -0
  58. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_lastupdated_search_test.rb +42 -0
  59. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_must_support_test.rb +47 -0
  60. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_read_test.rb +26 -0
  61. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_status_search_test.rb +53 -0
  62. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/medication_knowledge_validation_test.rb +39 -0
  63. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge/metadata.yml +214 -0
  64. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/medication_knowledge_group.rb +91 -0
  65. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/metadata.yml +1192 -0
  66. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/metadata.yml +371 -0
  67. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_coverage_area_search_test.rb +43 -0
  68. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_coverage_type_search_test.rb +43 -0
  69. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_formulary_coverage_include_search_test.rb +40 -0
  70. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_formulary_coverage_search_test.rb +43 -0
  71. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_id_search_test.rb +42 -0
  72. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_identifier_search_test.rb +43 -0
  73. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_lastupdated_search_test.rb +42 -0
  74. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_must_support_test.rb +66 -0
  75. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_name_search_test.rb +42 -0
  76. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_period_search_test.rb +42 -0
  77. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_read_test.rb +26 -0
  78. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_reference_resolution_test.rb +40 -0
  79. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_status_search_test.rb +53 -0
  80. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_type_search_test.rb +43 -0
  81. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan/payer_insurance_plan_validation_test.rb +39 -0
  82. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/payer_insurance_plan_group.rb +108 -0
  83. data/lib/davinci_us_drug_formulary_test_kit/generated/v2.0.1/usdf_test_suite.rb +114 -0
  84. data/lib/davinci_us_drug_formulary_test_kit/generator/group_generator.rb +181 -0
  85. data/lib/davinci_us_drug_formulary_test_kit/generator/group_metadata.rb +79 -0
  86. data/lib/davinci_us_drug_formulary_test_kit/generator/group_metadata_extractor.rb +329 -0
  87. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_loader.rb +77 -0
  88. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_metadata.rb +33 -0
  89. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_metadata_extractor.rb +40 -0
  90. data/lib/davinci_us_drug_formulary_test_kit/generator/ig_resources.rb +60 -0
  91. data/lib/davinci_us_drug_formulary_test_kit/generator/include_search_test_generator.rb +68 -0
  92. data/lib/davinci_us_drug_formulary_test_kit/generator/must_support_metadata_extractor.rb +384 -0
  93. data/lib/davinci_us_drug_formulary_test_kit/generator/must_support_test_generator.rb +117 -0
  94. data/lib/davinci_us_drug_formulary_test_kit/generator/naming.rb +28 -0
  95. data/lib/davinci_us_drug_formulary_test_kit/generator/read_test_generator.rb +92 -0
  96. data/lib/davinci_us_drug_formulary_test_kit/generator/reference_resolution_test_generator.rb +91 -0
  97. data/lib/davinci_us_drug_formulary_test_kit/generator/search_definition_metadata_extractor.rb +187 -0
  98. data/lib/davinci_us_drug_formulary_test_kit/generator/search_metadata_extractor.rb +59 -0
  99. data/lib/davinci_us_drug_formulary_test_kit/generator/search_test_generator.rb +270 -0
  100. data/lib/davinci_us_drug_formulary_test_kit/generator/suite_generator.rb +94 -0
  101. data/lib/davinci_us_drug_formulary_test_kit/generator/terminology_binding_metadata_extractor.rb +116 -0
  102. data/lib/davinci_us_drug_formulary_test_kit/generator/validation_test_generator.rb +102 -0
  103. data/lib/davinci_us_drug_formulary_test_kit/generator/value_extractor.rb +113 -0
  104. data/lib/davinci_us_drug_formulary_test_kit/generator.rb +94 -0
  105. data/lib/davinci_us_drug_formulary_test_kit/igs/package/r4_search-parameters.json +65408 -0
  106. data/lib/davinci_us_drug_formulary_test_kit/igs/package.tgz +0 -0
  107. data/lib/davinci_us_drug_formulary_test_kit/must_support_test.rb +224 -0
  108. data/lib/davinci_us_drug_formulary_test_kit/read_test.rb +62 -0
  109. data/lib/davinci_us_drug_formulary_test_kit/reference_resolution_test.rb +174 -0
  110. data/lib/davinci_us_drug_formulary_test_kit/request_logger.rb +46 -0
  111. data/lib/davinci_us_drug_formulary_test_kit/search_test.rb +767 -0
  112. data/lib/davinci_us_drug_formulary_test_kit/search_test_properties.rb +58 -0
  113. data/lib/davinci_us_drug_formulary_test_kit/validation_test.rb +56 -0
  114. data/lib/davinci_us_drug_formulary_test_kit/version.rb +5 -0
  115. data/lib/davinci_us_drug_formulary_test_kit.rb +1 -0
  116. metadata +245 -0
@@ -0,0 +1,181 @@
1
+ require_relative 'naming'
2
+
3
+ module DaVinciUSDrugFormularyTestKit
4
+ class Generator
5
+ class GroupGenerator
6
+ class << self
7
+ def generate(ig_metadata, base_output_dir)
8
+ ig_metadata.ordered_groups
9
+ .each { |group| new(group, base_output_dir).generate }
10
+ end
11
+ end
12
+
13
+ attr_accessor :group_metadata, :base_output_dir
14
+
15
+ def initialize(group_metadata, base_output_dir)
16
+ self.group_metadata = group_metadata
17
+ self.base_output_dir = base_output_dir
18
+ end
19
+
20
+ def template
21
+ @template ||= File.read(File.join(__dir__, 'templates', 'group.rb.erb'))
22
+ end
23
+
24
+ def output
25
+ @output ||= ERB.new(template).result(binding)
26
+ end
27
+
28
+ def base_output_file_name
29
+ "#{class_name.underscore}.rb"
30
+ end
31
+
32
+ def base_metadata_file_name
33
+ 'metadata.yml'
34
+ end
35
+
36
+ def class_name
37
+ "#{Naming.upper_camel_case_for_profile(group_metadata)}Group"
38
+ end
39
+
40
+ def module_name
41
+ "USDF#{group_metadata.reformatted_version.upcase}"
42
+ end
43
+
44
+ def title
45
+ group_metadata.title
46
+ end
47
+
48
+ def short_description
49
+ group_metadata.short_description
50
+ end
51
+
52
+ def output_file_name
53
+ File.join(base_output_dir, base_output_file_name)
54
+ end
55
+
56
+ def metadata_file_name
57
+ File.join(base_output_dir, profile_identifier, base_metadata_file_name)
58
+ end
59
+
60
+ def profile_identifier
61
+ Naming.snake_case_for_profile(group_metadata)
62
+ end
63
+
64
+ def group_id
65
+ "usdf_#{group_metadata.reformatted_version}_#{profile_identifier}"
66
+ end
67
+
68
+ def resource_type
69
+ group_metadata.resource
70
+ end
71
+
72
+ def search_validation_resource_type
73
+ "#{resource_type} resources"
74
+ end
75
+
76
+ def profile_name
77
+ group_metadata.profile_name
78
+ end
79
+
80
+ def profile_url
81
+ group_metadata.profile_url
82
+ end
83
+
84
+ def optional?
85
+ false
86
+ end
87
+
88
+ def generate
89
+ File.write(output_file_name, output)
90
+ group_metadata.id = group_id
91
+ group_metadata.file_name = base_output_file_name
92
+ File.write(metadata_file_name, YAML.dump(group_metadata.to_hash))
93
+ end
94
+
95
+ def test_id_list
96
+ @test_id_list ||=
97
+ group_metadata.tests.map { |test| test[:id] }
98
+ end
99
+
100
+ def test_file_list
101
+ @test_file_list ||=
102
+ group_metadata.tests.map do |test|
103
+ name_without_suffix = test[:file_name].delete_suffix('.rb')
104
+ name_without_suffix.start_with?('..') ? name_without_suffix : "#{profile_identifier}/#{name_without_suffix}"
105
+ end
106
+ end
107
+
108
+ def required_searches
109
+ group_metadata.searches.select { |search| search[:expectation] == 'SHALL' }
110
+ end
111
+
112
+ def search_param_name_string
113
+ required_searches
114
+ .map { |search| search[:names].join(' + ') }
115
+ .map { |names| "* #{names}" }
116
+ .join("\n")
117
+ end
118
+
119
+ def search_description
120
+ return '' if required_searches.blank?
121
+
122
+ <<~SEARCH_DESCRIPTION
123
+ ## Searching
124
+ This test sequence will first perform each required search associated
125
+ with this resource. This sequence will perform searches with the
126
+ following parameters:
127
+
128
+ #{search_param_name_string}
129
+
130
+ ### Search Parameters
131
+ The first search uses the selected resources from the prior launch
132
+ sequence. Any subsequent searches will look for its parameter values
133
+ from the results of the first search. If a value cannot be found this way, the search is skipped.
134
+
135
+ ### Search Validation
136
+ Inferno will retrieve up to the first 20 bundle pages of the reply for
137
+ #{search_validation_resource_type} and save them for subsequent tests. Each of
138
+ these resources is then checked to see if it matches the searched
139
+ parameters in accordance with [FHIR search
140
+ guidelines](https://www.hl7.org/fhir/search.html).
141
+ SEARCH_DESCRIPTION
142
+ end
143
+
144
+ def description
145
+ <<~DESCRIPTION
146
+ # Background
147
+
148
+ The USDF #{title} sequence verifies that the system under test is
149
+ able to provide correct responses for #{resource_type} queries. These queries
150
+ must contain resources conforming to the #{profile_name} as
151
+ specified in the USDF #{group_metadata.version} Implementation Guide.
152
+
153
+ # Testing Methodology
154
+ #{search_description}
155
+
156
+ ## Must Support
157
+ Each profile contains elements marked as "must support". This test
158
+ sequence expects to see each of these elements at least once. If at
159
+ least one cannot be found, the test will fail. The test will look
160
+ through the #{resource_type} resources found in the first test for these
161
+ elements.
162
+
163
+ ## Profile Validation
164
+ Each resource returned from the first search is expected to conform to
165
+ the [#{profile_name}](#{profile_url}). Each element is checked against
166
+ teminology binding and cardinality requirements.
167
+
168
+ Elements with a required binding are validated against their bound
169
+ ValueSet. If the code/system in the element is not part of the ValueSet,
170
+ then the test will fail.
171
+
172
+ ## Reference Validation
173
+ At least one instance of each external reference in elements marked as
174
+ "must support" within the resources provided by the system must resolve.
175
+ The test will attempt to read each reference found and will fail if no
176
+ read succeeds.
177
+ DESCRIPTION
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,79 @@
1
+ module DaVinciUSDrugFormularyTestKit
2
+ class Generator
3
+ class GroupMetadata
4
+ ATTRIBUTES = [
5
+ :name,
6
+ :class_name,
7
+ :version,
8
+ :reformatted_version,
9
+ :resource,
10
+ :profile_url,
11
+ :profile_name,
12
+ :profile_version,
13
+ :title,
14
+ :short_description,
15
+ :interactions,
16
+ # :operations,
17
+ :searches,
18
+ :search_definitions,
19
+ :include_params,
20
+ # :revincludes,
21
+ :must_supports,
22
+ :mandatory_elements,
23
+ # :bindings,
24
+ :references,
25
+ :tests,
26
+ :id,
27
+ :file_name,
28
+ :delayed_references
29
+ ].freeze
30
+
31
+ ATTRIBUTES.each { |name| attr_accessor name }
32
+
33
+ def initialize(metadata)
34
+ metadata.each do |key, value|
35
+ raise "Unknown attribute #{key}" unless ATTRIBUTES.include? key
36
+
37
+ instance_variable_set(:"@#{key}", value)
38
+ end
39
+ end
40
+
41
+ def delayed?
42
+ false
43
+ end
44
+
45
+ def add_test(id:, file_name:)
46
+ self.tests ||= []
47
+
48
+ test_metadata = {
49
+ id:,
50
+ file_name:
51
+ }
52
+
53
+ if delayed? && id.include?('read')
54
+ self.tests.unshift(test_metadata)
55
+ else
56
+ self.tests << test_metadata
57
+ end
58
+ end
59
+
60
+ def to_hash
61
+ ATTRIBUTES.each_with_object({}) { |key, hash| hash[key] = send(key) unless send(key).nil? }
62
+ end
63
+
64
+ def add_delayed_references(delayed_profiles, ig_resources)
65
+ self.delayed_references =
66
+ references
67
+ .select { |reference| (reference[:profiles] & delayed_profiles).present? }
68
+ .map do |reference|
69
+ profile_urls = (reference[:profiles] & delayed_profiles)
70
+ delayed_resources = profile_urls.map { |url| ig_resources.resource_for_profile(url) }
71
+ {
72
+ path: reference[:path].gsub("#{resource}.", ''),
73
+ resources: delayed_resources
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,329 @@
1
+ require_relative 'group_metadata'
2
+ require_relative 'ig_metadata'
3
+ require_relative 'must_support_metadata_extractor'
4
+ require_relative 'search_metadata_extractor'
5
+ require_relative 'terminology_binding_metadata_extractor'
6
+
7
+ module DaVinciUSDrugFormularyTestKit
8
+ class Generator
9
+ class GroupMetadataExtractor
10
+ attr_accessor :resource_capabilities, :profile_url, :ig_metadata, :ig_resources
11
+
12
+ def initialize(resource_capabilities, profile_url, ig_metadata, ig_resources)
13
+ self.resource_capabilities = resource_capabilities
14
+ self.profile_url = profile_url
15
+ self.ig_metadata = ig_metadata
16
+ self.ig_resources = ig_resources
17
+ end
18
+
19
+ def group_metadata
20
+ @group_metadata ||=
21
+ GroupMetadata.new(group_metadata_hash)
22
+ end
23
+
24
+ def group_metadata_hash
25
+ @group_metadata_hash ||=
26
+ {
27
+ name:,
28
+ class_name:,
29
+ version:,
30
+ reformatted_version:,
31
+ resource:,
32
+ profile_url:,
33
+ profile_name:,
34
+ profile_version:,
35
+ title:,
36
+ short_description:,
37
+ interactions:,
38
+ # operations: operations,
39
+ searches:, # searches: searches,
40
+ search_definitions:, # search_definitions: search_definitions
41
+ include_params:,
42
+ # revincludes: revincludes,
43
+ must_supports:,
44
+ mandatory_elements:,
45
+ # bindings: bindings,
46
+ references:
47
+ }
48
+
49
+ mark_mandatory_and_must_support_searches
50
+ handle_special_cases
51
+
52
+ @group_metadata_hash
53
+ end
54
+
55
+ def mark_mandatory_and_must_support_searches
56
+ searches.each do |search|
57
+ search[:names_not_must_support_or_mandatory] = search[:names].reject do |name|
58
+ full_paths =
59
+ search_definitions[name.to_sym][:full_paths]
60
+ .map { |path| path.gsub(/\AResource\./, "#{resource}.") } # Fix for Resource.meta.*
61
+
62
+ any_must_support_elements = must_supports[:elements].any? do |element|
63
+ full_must_support_paths = ["#{resource}.#{element[:original_path]}", "#{resource}.#{element[:path]}"]
64
+
65
+ full_paths.any? do |path|
66
+ # allow for non-choice, choice types, and _id
67
+ name == '_id' ||
68
+ full_must_support_paths.include?(path) ||
69
+ full_must_support_paths.include?("#{path}[x]")
70
+ end
71
+ end
72
+
73
+ any_must_support_slices = must_supports[:slices].any? do |slice|
74
+ # only handle type slices because that is all we need for now
75
+ # for a slice like Observation.effective[x]:effectiveDateTime, the search parameter's expression could be
76
+ # either Observation.effective or Observation.effectiveDateTime.
77
+ if slice[:discriminator] && slice[:discriminator][:type] == 'type'
78
+ full_must_support_path = "#{resource}.#{slice[:path].sub('[x]', slice[:discriminator][:code])}"
79
+ base_must_support_path = "#{resource}.#{slice[:path].sub('[x]', '')}"
80
+
81
+ full_paths.intersection([full_must_support_path, base_must_support_path]).present?
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ any_must_support_extensions = must_supports[:extensions].any? do |extension|
88
+ full_must_support_paths = ["#{resource}.#{extension[:path]}.where(url='#{extension[:url]}').value"]
89
+
90
+ full_paths.any? { |path| full_must_support_paths.include?(path) }
91
+ end
92
+
93
+ any_mandatory_elements = mandatory_elements.any? do |element|
94
+ full_paths.include?(element)
95
+ end
96
+
97
+ any_must_support_elements ||
98
+ any_must_support_slices ||
99
+ any_must_support_extensions ||
100
+ any_mandatory_elements
101
+ end
102
+
103
+ search[:must_support_or_mandatory] = search[:names_not_must_support_or_mandatory].empty?
104
+ end
105
+ end
106
+
107
+ ### BEGIN SPECIAL CASES ###
108
+
109
+ def first_search_params
110
+ @first_search_params ||=
111
+ if resource == 'Location'
112
+ ['_id']
113
+ elsif resource == 'Basic'
114
+ ['code']
115
+ else
116
+ ['status']
117
+ end
118
+ end
119
+
120
+ def handle_special_cases
121
+ set_first_search
122
+ filter_drug_values
123
+ add_must_support_choices
124
+ end
125
+
126
+ def set_first_search
127
+ search = searches.find { |param| param[:names] == first_search_params }
128
+ return if search.nil?
129
+
130
+ searches.delete(search)
131
+ searches.unshift(search)
132
+ end
133
+
134
+ def filter_drug_values
135
+ search_definitions[:doseform][:values] = [] unless search_definitions[:doseform].nil?
136
+
137
+ return if search_definitions[:code].nil?
138
+ return unless search_definitions[:code][:full_paths] == ['MedicationKnowledge.code']
139
+
140
+ search_definitions[:code][:values] = []
141
+ end
142
+
143
+ def add_must_support_choices
144
+ choices = []
145
+ case profile.type
146
+ when 'Location'
147
+ choices << { paths: ['address'],
148
+ extension_ids: ['Location.extension:region'] }
149
+ end
150
+
151
+ must_supports[:choices] = choices if choices.present?
152
+ end
153
+
154
+ ### END SPECIAL CASES ###
155
+
156
+ def profile
157
+ @profile ||= ig_resources.profile_by_url(profile_url)
158
+ end
159
+
160
+ def profile_elements
161
+ @profile_elements ||= profile.snapshot.element
162
+ end
163
+
164
+ def base_name
165
+ profile_url.split('StructureDefinition/').last
166
+ end
167
+
168
+ def name
169
+ base_name.tr('-', '_')
170
+ end
171
+
172
+ def class_name
173
+ base_name
174
+ .split('-')
175
+ .map(&:capitalize)
176
+ .join
177
+ .gsub('USDF', "USDF#{ig_metadata.reformatted_version}")
178
+ .concat('Sequence')
179
+ end
180
+
181
+ def version
182
+ ig_metadata.ig_version
183
+ end
184
+
185
+ def reformatted_version
186
+ ig_metadata.reformatted_version
187
+ end
188
+
189
+ def resource
190
+ resource_capabilities.type
191
+ end
192
+
193
+ def profile_name
194
+ binding.pry if profile.nil?
195
+ profile.title.gsub(' ', ' ')
196
+ end
197
+
198
+ def profile_version
199
+ profile.version
200
+ end
201
+
202
+ def title
203
+ profile.title.gsub(/US\s*Core\s*/, '').gsub(/\s*Profile/, '').strip
204
+ end
205
+
206
+ def short_description
207
+ "Verify support for the server capabilities required by the #{profile_name}."
208
+ end
209
+
210
+ def interactions
211
+ @interactions ||=
212
+ resource_capabilities.interaction.map do |interaction|
213
+ {
214
+ code: interaction.code,
215
+ expectation: interaction.extension.first.valueCode # TODO: fix expectation extension finding
216
+ }
217
+ end
218
+ end
219
+
220
+ # def operations
221
+ # @operations ||=
222
+ # resource_capabilities.operation.map do |operation|
223
+ # {
224
+ # code: operation.name,
225
+ # expectation: operation.extension.first.valueCode # TODO: fix expectation extension finding
226
+ # }
227
+ # end
228
+ # end
229
+
230
+ def search_metadata_extractor
231
+ @search_metadata_extractor ||=
232
+ SearchMetadataExtractor.new(resource_capabilities, ig_resources, resource, profile_elements)
233
+ end
234
+
235
+ def searches
236
+ @searches ||= search_metadata_extractor.searches
237
+ end
238
+
239
+ def search_definitions
240
+ @search_definitions ||= search_metadata_extractor.search_definitions
241
+ end
242
+
243
+ def include_params
244
+ resource_capabilities.searchInclude || []
245
+ end
246
+
247
+ # def revincludes
248
+ # resource_capabilities.searchRevInclude || []
249
+ # end
250
+
251
+ # def terminology_binding_metadata_extractor
252
+ # @terminology_binding_metadata_extractor ||=
253
+ # TerminologyBindingMetadataExtractor.new(profile_elements, ig_resources, resource)
254
+ # end
255
+
256
+ # def bindings
257
+ # @bindings ||=
258
+ # terminology_binding_metadata_extractor.terminology_bindings
259
+ # end
260
+
261
+ def must_support_metadata_extractor
262
+ @must_support_metadata_extractor ||=
263
+ MustSupportMetadataExtractor.new(profile_elements, profile, resource, ig_resources)
264
+ end
265
+
266
+ def must_supports
267
+ @must_supports ||=
268
+ must_support_metadata_extractor.must_supports
269
+ end
270
+
271
+ def mandatory_elements
272
+ @mandatory_elements ||=
273
+ profile_elements
274
+ .select { |element| element.min.positive? && parents_are_mandatory?(element) }
275
+ .map(&:path)
276
+ .uniq
277
+ end
278
+
279
+ def parents_are_mandatory?(element)
280
+ return true if element.path.count('.') < 2
281
+
282
+ parent_path = element.path.split('.')[0..-2].join('.')
283
+ parent_element = profile_elements.find { |profile_element| profile_element.path == parent_path }
284
+
285
+ parent_element&.min&.positive? && parents_are_mandatory?(parent_element)
286
+ end
287
+
288
+ def references
289
+ @references ||= element_references + extension_references
290
+ end
291
+
292
+ def element_references
293
+ profile_elements
294
+ .select { |element| element.type&.first&.code == 'Reference' }
295
+ .map do |reference_definition|
296
+ {
297
+ path: reference_definition.path,
298
+ profiles: reference_definition.type.first.targetProfile
299
+ }
300
+ end
301
+ end
302
+
303
+ def extension_references
304
+ profile_elements
305
+ .select { |element| element.type.any? { |type| type.code == 'Extension' && type.profile.present? } }
306
+ .map do |element|
307
+ extension_url = element.type.first.profile.first
308
+
309
+ target_profiles =
310
+ ig_resources
311
+ .profile_by_url(extension_url)
312
+ &.snapshot
313
+ &.element
314
+ &.find { |profile_element| profile_element.id == 'Extension.value[x]:valueReference' }
315
+ &.type
316
+ &.first
317
+ &.targetProfile
318
+
319
+ next if target_profiles.blank?
320
+
321
+ {
322
+ path: "#{element.path}.where(url='#{extension_url}').valueReference",
323
+ target_profiles:
324
+ }
325
+ end.compact
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,77 @@
1
+ require 'active_support/all'
2
+ require 'fhir_models'
3
+ require 'pathname'
4
+ require 'rubygems/package'
5
+ require 'zlib'
6
+ require_relative 'ig_resources'
7
+
8
+ module DaVinciUSDrugFormularyTestKit
9
+ class Generator
10
+ class IGLoader
11
+ attr_accessor :ig_file_name
12
+
13
+ def initialize(ig_file_name)
14
+ self.ig_file_name = ig_file_name
15
+ end
16
+
17
+ def ig_resources
18
+ @ig_resources ||= IGResources.new
19
+ end
20
+
21
+ def load
22
+ load_ig
23
+ load_standalone_resources
24
+ end
25
+
26
+ def load_ig
27
+ tar = Gem::Package::TarReader.new(
28
+ Zlib::GzipReader.open(ig_file_name)
29
+ )
30
+
31
+ tar.each do |entry|
32
+ next if entry.directory?
33
+
34
+ file_name = entry.full_name.split('/').last
35
+
36
+ next unless file_name.end_with? '.json'
37
+
38
+ next unless entry.full_name.start_with? 'package/'
39
+
40
+ begin
41
+ resource = FHIR.from_contents(entry.read)
42
+ next if resource.nil?
43
+ rescue StandardError
44
+ puts "#{file_name} does not appear to be a FHIR resource."
45
+ next
46
+ end
47
+
48
+ ig_resources.add(resource)
49
+ end
50
+
51
+ ig_resources
52
+ end
53
+
54
+ def load_standalone_resources
55
+ ig_directory = ig_file_name.chomp('.tgz')
56
+
57
+ Dir.glob(File.join(ig_directory, '*.json')).each do |file_path|
58
+ resource = FHIR.from_contents(File.read(file_path))
59
+ next if resource.nil?
60
+
61
+ if resource.resourceType == 'Bundle' && !resource.entry.nil?
62
+ resource_arr = resource.entry
63
+ resource_arr.each do |entry|
64
+ ig_resources.add(entry.resource)
65
+ end
66
+ end
67
+ rescue StandardError
68
+ file_name = file_path.split('/').last
69
+ puts "#{file_name} does not appear to be a FHIR resource."
70
+ next
71
+ end
72
+
73
+ ig_resources
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,33 @@
1
+ module DaVinciUSDrugFormularyTestKit
2
+ class Generator
3
+ class IGMetadata
4
+ attr_accessor :ig_version, :groups
5
+
6
+ def reformatted_version
7
+ @reformatted_version ||= ig_version.delete('.').gsub('-', '_')
8
+ end
9
+
10
+ def ordered_groups
11
+ @ordered_groups ||= groups
12
+ end
13
+
14
+ def delayed_profiles
15
+ @delayed_profiles ||=
16
+ delayed_groups.map(&:profile_url)
17
+ end
18
+
19
+ def postprocess_groups(ig_resources)
20
+ groups.each do |group|
21
+ group.add_delayed_references(delayed_profiles, ig_resources)
22
+ end
23
+ end
24
+
25
+ def to_hash
26
+ {
27
+ ig_version:,
28
+ groups: groups.map(&:to_hash)
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end