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,40 @@
1
+ require_relative 'ig_metadata'
2
+ require_relative 'group_metadata_extractor'
3
+
4
+ module DaVinciUSDrugFormularyTestKit
5
+ class Generator
6
+ class IGMetadataExtractor
7
+ attr_accessor :ig_resources, :metadata
8
+
9
+ def initialize(ig_resources)
10
+ self.ig_resources = ig_resources
11
+ self.metadata = IGMetadata.new
12
+ end
13
+
14
+ def extract
15
+ add_metadata_from_ig
16
+ add_metadata_from_resources
17
+ metadata
18
+ end
19
+
20
+ def add_metadata_from_ig
21
+ metadata.ig_version = "v#{ig_resources.ig.version}"
22
+ end
23
+
24
+ def resources_in_capability_statement
25
+ ig_resources.capability_statement.rest.first.resource
26
+ end
27
+
28
+ def add_metadata_from_resources
29
+ metadata.groups =
30
+ resources_in_capability_statement.flat_map do |resource|
31
+ resource.supportedProfile&.map do |supported_profile|
32
+ GroupMetadataExtractor.new(resource, supported_profile, metadata, ig_resources).group_metadata
33
+ end
34
+ end.compact
35
+
36
+ # metadata.postprocess_groups(ig_resources)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,60 @@
1
+ require 'fhir_models'
2
+
3
+ module DaVinciUSDrugFormularyTestKit
4
+ class Generator
5
+ class IGResources
6
+ def add(resource)
7
+ resources_by_type[resource.resourceType] << resource
8
+ end
9
+
10
+ def capability_statement(mode = 'server')
11
+ resources_by_type['CapabilityStatement'].find do |capability_statement_resource|
12
+ capability_statement_resource.rest.any? { |r| r.mode == mode }
13
+ end
14
+ end
15
+
16
+ def ig
17
+ resources_by_type['ImplementationGuide'].first
18
+ end
19
+
20
+ def inspect
21
+ 'IGResources'
22
+ end
23
+
24
+ def profile_by_url(url)
25
+ resources_by_type['StructureDefinition'].find { |profile| profile.url == url }
26
+ end
27
+
28
+ def resource_for_profile(url)
29
+ resources_by_type['StructureDefinition'].find { |profile| profile.url == url }.type
30
+ end
31
+
32
+ def value_set_by_url(url)
33
+ resources_by_type['ValueSet'].find { |profile| profile.url == url }
34
+ end
35
+
36
+ def code_system_by_url(url)
37
+ resources_by_type['CodeSystem'].find { |system| system.url == url }
38
+ end
39
+
40
+ def search_param_by_resource_and_name(resource, name)
41
+ # remove '_' from search parameter name, such as _id or _tag
42
+ normalized_name = name.to_s.delete_prefix('_')
43
+
44
+ res = resources_by_type['SearchParameter']
45
+ .find { |param| param.id == "#{resource}-#{normalized_name}" }
46
+ if res.nil?
47
+ res = resources_by_type['SearchParameter']
48
+ .find { |param| param.id == "Resource-#{normalized_name}" }
49
+ end
50
+ res
51
+ end
52
+
53
+ private
54
+
55
+ def resources_by_type
56
+ @resources_by_type ||= Hash.new { |hash, key| hash[key] = [] }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,68 @@
1
+ require_relative 'naming'
2
+
3
+ module DaVinciUSDrugFormularyTestKit
4
+ class Generator
5
+ class IncludeSearchTestGenerator < SearchTestGenerator
6
+ INCLUDE_SEARCH_PARAMS = { 'Payer Insurance Plan' => 'coverage-type', 'Formulary Item' => 'code' }.freeze
7
+
8
+ class << self
9
+ def generate(ig_metadata, base_output_dir)
10
+ # The IG defines the formulary-coverage include param for both InsuancePlan profiles,
11
+ # even though it only pertains to Payer Insurance Plan, so we explicitly exclude Formulary
12
+ ig_metadata.groups
13
+ .reject { |group| group.profile_name == 'Formulary' }
14
+ .reject { |group| group.include_params.empty? }
15
+ .each do |group|
16
+ group.include_params.each do |include_param|
17
+ search_meta = group.searches.find do |search|
18
+ search[:names].include? INCLUDE_SEARCH_PARAMS[group.profile_name]
19
+ end
20
+ new(group, search_meta, base_output_dir, include_param).generate
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ attr_accessor :include_param
27
+
28
+ def initialize(group_metadata, search_metadata, base_output_dir, include_param)
29
+ super(group_metadata, search_metadata, base_output_dir)
30
+ self.include_param = include_param
31
+ end
32
+
33
+ def template
34
+ @template ||= File.read(File.join(__dir__, 'templates', 'include_search.rb.erb'))
35
+ end
36
+
37
+ def test_id
38
+ "usdf_#{group_metadata.reformatted_version}_#{profile_identifier}_#{search_identifier}_include_search_test"
39
+ end
40
+
41
+ def search_identifier
42
+ include_param.gsub(/[-:]/, '_').underscore
43
+ end
44
+
45
+ def class_name
46
+ "#{Naming.upper_camel_case_for_profile(group_metadata)}#{search_title}IncludeSearchTest"
47
+ end
48
+
49
+ def search_properties
50
+ {}.tap do |properties|
51
+ properties[:resource_type] = "'#{resource_type}'"
52
+ properties[:search_param_names] = search_param_names
53
+ properties[:include_param] = "'#{include_param}'"
54
+ end
55
+ end
56
+
57
+ def include_param_string
58
+ "_include=#{include_param}"
59
+ end
60
+
61
+ def include_param_resource
62
+ res_type = group_metadata.search_definitions[:"#{include_param}"][:type]
63
+ res_type = group_metadata.search_definitions[:"#{include_param}"][:target] if res_type == 'Reference'
64
+ res_type
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,384 @@
1
+ require_relative 'value_extractor'
2
+
3
+ module DaVinciUSDrugFormularyTestKit
4
+ class Generator
5
+ class MustSupportMetadataExtractor
6
+ attr_accessor :profile_elements, :profile, :resource, :ig_resources
7
+
8
+ def initialize(profile_elements, profile, resource, ig_resources)
9
+ self.profile_elements = profile_elements
10
+ self.profile = profile
11
+ self.resource = resource
12
+ self.ig_resources = ig_resources
13
+ end
14
+
15
+ def must_supports
16
+ @must_supports = {
17
+ extensions: must_support_extensions,
18
+ slices: must_support_slices,
19
+ elements: must_support_elements,
20
+ references: must_support_references
21
+ }
22
+ # handle_special_cases
23
+
24
+ @must_supports
25
+ end
26
+
27
+ def all_must_support_elements
28
+ profile_elements.select(&:mustSupport)
29
+ end
30
+
31
+ def must_support_extension_elements
32
+ all_must_support_elements.select do |element|
33
+ (element.path.end_with? 'extension') && !element&.type&.first&.profile&.first.nil?
34
+ end
35
+ end
36
+
37
+ def must_support_extensions
38
+ # non_slice_extensions = must_support_extension_elements - slice_sub_elements
39
+ must_support_extension_elements.map do |element|
40
+ {
41
+ id: element.id,
42
+ url: element.type.first.profile.first,
43
+ path: element.path.gsub("#{resource}.", '')
44
+ }
45
+ end
46
+ end
47
+
48
+ def must_support_slice_elements
49
+ all_must_support_elements.select do |element|
50
+ !element.path.end_with?('extension') && element.sliceName.present?
51
+ end
52
+ end
53
+
54
+ # def slice_sub_elements
55
+ # must_support_slice_elements.flat_map do |slice|
56
+ # profile_elements.select do |element|
57
+ # element.id != slice.id && element.id.start_with?(slice.id) && element.mustSupport
58
+ # end
59
+ # end
60
+ # end
61
+
62
+ def sliced_element(slice)
63
+ profile_elements.find do |element|
64
+ (element.id == slice.path && element.slicing.present?) || element.id == slice.id.sub(":#{slice.sliceName}",
65
+ '')
66
+ end
67
+ end
68
+
69
+ def discriminators(slice)
70
+ slice.slicing.discriminator
71
+ end
72
+
73
+ def must_support_pattern_slice_elements
74
+ must_support_slice_elements.select do |element|
75
+ discriminators(sliced_element(element)).first.type == 'pattern'
76
+ end
77
+ end
78
+
79
+ def pattern_slices
80
+ must_support_pattern_slice_elements.map do |current_element|
81
+ {
82
+ slice_id: current_element.id,
83
+ slice_name: current_element.sliceName,
84
+ path: current_element.path.gsub("#{resource}.", '')
85
+ }.tap do |metadata|
86
+ discriminator = discriminators(sliced_element(current_element)).first
87
+ discriminator_path = discriminator.path
88
+ discriminator_path = '' if discriminator_path == '$this'
89
+ pattern_element =
90
+ if discriminator_path.present?
91
+ profile_elements.find { |element| element.id == "#{current_element.id}.#{discriminator_path}" }
92
+ else
93
+ current_element
94
+ end
95
+
96
+ metadata[:discriminator] =
97
+ if pattern_element.patternCodeableConcept.present?
98
+ {
99
+ type: 'patternCodeableConcept',
100
+ path: discriminator_path,
101
+ code: pattern_element.patternCodeableConcept.coding.first.code,
102
+ system: pattern_element.patternCodeableConcept.coding.first.system
103
+ }
104
+ elsif pattern_element.patternCoding.present?
105
+ {
106
+ type: 'patternCoding',
107
+ path: discriminator_path,
108
+ code: pattern_element.patternCoding.code,
109
+ system: pattern_element.patternCoding.system
110
+ }
111
+ elsif pattern_element.patternIdentifier.present?
112
+ {
113
+ type: 'patternIdentifier',
114
+ path: discriminator_path,
115
+ system: pattern_element.patternIdentifier.system
116
+ }
117
+ elsif pattern_element.binding&.strength == 'required' &&
118
+ pattern_element.binding&.valueSet.present?
119
+
120
+ value_extractor = ValueExactor.new(ig_resources, resource, profile_elements)
121
+
122
+ values = value_extractor.values_from_value_set_binding(pattern_element).presence ||
123
+ value_extractor.values_from_resource_metadata([metadata[:path]]).presence || []
124
+
125
+ {
126
+ type: 'requiredBinding',
127
+ path: discriminator_path,
128
+ values:
129
+ }
130
+ else
131
+ raise StandardError, 'Unsupported discriminator pattern type'
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ def must_support_type_slice_elements
138
+ must_support_slice_elements.select do |element|
139
+ discriminators(sliced_element(element)).first.type == 'type'
140
+ end
141
+ end
142
+
143
+ def type_slices
144
+ must_support_type_slice_elements.map do |current_element|
145
+ discriminator = discriminators(sliced_element(current_element)).first
146
+ type_path = discriminator.path
147
+ type_path = '' if type_path == '$this'
148
+ type_element =
149
+ if type_path.present?
150
+ elements.find { |element| element.id == "#{current_element.id}.#{type_path}" }
151
+ else
152
+ current_element
153
+ end
154
+
155
+ type_code = type_element.type.first.code
156
+
157
+ {
158
+ slice_id: current_element.id,
159
+ slice_name: current_element.sliceName,
160
+ path: current_element.path.gsub("#{resource}.", ''),
161
+ discriminator: {
162
+ type: 'type',
163
+ code: type_code.upcase_first
164
+ }
165
+ }
166
+ end
167
+ end
168
+
169
+ def must_support_value_slice_elements
170
+ must_support_slice_elements.select do |element|
171
+ discriminators(sliced_element(element)).first.type == 'value'
172
+ end
173
+ end
174
+
175
+ def value_slices
176
+ must_support_value_slice_elements.map do |current_element|
177
+ {
178
+ slice_id: current_element.id,
179
+ slice_name: current_element.sliceName,
180
+ path: current_element.path.gsub("#{resource}.", ''),
181
+ discriminator: {
182
+ type: 'value'
183
+ }
184
+ }.tap do |metadata|
185
+ metadata[:discriminator][:values] = discriminators(sliced_element(current_element)).map do |discriminator|
186
+ if discriminator.path == '$this'
187
+ fixed_element = profile_elements.find do |element|
188
+ element.id.starts_with?(current_element.id) &&
189
+ element.path == current_element.path.to_s
190
+ end
191
+ {
192
+ path: fixed_element.path.gsub("#{resource}.", ''),
193
+ value: fixed_element.binding.valueSet
194
+ }
195
+ else
196
+ fixed_element = profile_elements.find do |element|
197
+ element.id.starts_with?(current_element.id) &&
198
+ element.path == "#{current_element.path}.#{discriminator.path}"
199
+ end
200
+ {
201
+ path: discriminator.path,
202
+ value: fixed_element.fixedUri || fixed_element.fixedCode
203
+ }
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ def must_support_slices
211
+ pattern_slices + type_slices + value_slices
212
+ end
213
+
214
+ def plain_must_support_elements
215
+ all_must_support_elements - must_support_extension_elements - must_support_slice_elements
216
+ end
217
+
218
+ def element_part_of_slice_discrimination?(element)
219
+ must_support_slice_elements.any? { |ms_slice| element.id.include?(ms_slice.id) }
220
+ end
221
+
222
+ def handle_fixed_values(metadata, element)
223
+ if element.fixedUri.present?
224
+ metadata[:fixed_value] = element.fixedUri
225
+ elsif element.patternCodeableConcept.present? && !element_part_of_slice_discrimination?(element)
226
+ metadata[:fixed_value] = element.patternCodeableConcept.coding.first.code
227
+ metadata[:path] += '.coding.code'
228
+ elsif element.fixedCode.present?
229
+ metadata[:fixed_value] = element.fixedCode
230
+ elsif element.patternIdentifier.present? && !element_part_of_slice_discrimination?(element)
231
+ metadata[:fixed_value] = element.patternIdentifier.system
232
+ metadata[:path] += '.system'
233
+ end
234
+ end
235
+
236
+ def type_must_support_extension?(extensions)
237
+ extensions&.any? do |extension|
238
+ extension.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support' &&
239
+ extension.valueBoolean
240
+ end
241
+ end
242
+
243
+ def save_type_code?(type)
244
+ type.code == 'Reference'
245
+ end
246
+
247
+ def get_type_must_support_metadata(current_metadata, current_element)
248
+ current_element.type.map do |type|
249
+ next unless type_must_support_extension?(type.extension)
250
+
251
+ metadata =
252
+ {
253
+ path: "#{current_metadata[:path].delete_suffix('[x]')}#{type.code.upcase_first}",
254
+ original_path: current_metadata[:path]
255
+ }
256
+ metadata[:types] = [type.code] if save_type_code?(type)
257
+ handle_type_must_support_target_profiles(type, metadata) if type.code == 'Reference'
258
+
259
+ metadata
260
+ end.compact
261
+ end
262
+
263
+ def handle_type_must_support_target_profiles(type, metadata)
264
+ index = 0
265
+ target_profiles = []
266
+
267
+ type.source_hash['_targetProfile']&.each do |hash|
268
+ if hash.present?
269
+ element = FHIR::Element.new(hash)
270
+ target_profiles << type.targetProfile[index] if type_must_support_extension?(element.extension)
271
+ end
272
+ index += 1
273
+ end
274
+
275
+ metadata[:target_profiles] = target_profiles if target_profiles.present?
276
+ end
277
+
278
+ def handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
279
+ choice_element_metadata = must_support_elements_metadata.find do |metadata|
280
+ metadata[:original_path].present? &&
281
+ current_metadata[:path].include?(metadata[:original_path])
282
+ end
283
+
284
+ return unless choice_element_metadata.present?
285
+
286
+ current_metadata[:original_path] = current_metadata[:path]
287
+ current_metadata[:path] =
288
+ current_metadata[:path].sub(choice_element_metadata[:original_path], choice_element_metadata[:path])
289
+ end
290
+
291
+ def must_support_elements
292
+ plain_must_support_elements.each_with_object([]) do |current_element, must_support_elements_metadata|
293
+ {
294
+ path: current_element.id.gsub("#{resource}.", '')
295
+ }.tap do |current_metadata|
296
+ type_must_support_metadata = get_type_must_support_metadata(current_metadata, current_element)
297
+
298
+ if type_must_support_metadata.any?
299
+ must_support_elements_metadata.concat(type_must_support_metadata)
300
+ else
301
+ handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
302
+
303
+ supported_types = current_element.type.select { |type| save_type_code?(type) }.map(&:code)
304
+ current_metadata[:types] = supported_types if supported_types.present?
305
+
306
+ if current_element.type.first&.code == 'Reference'
307
+ handle_type_must_support_target_profiles(current_element.type.first,
308
+ current_metadata)
309
+ end
310
+
311
+ handle_fixed_values(current_metadata, current_element)
312
+
313
+ must_support_elements_metadata.delete_if do |metadata|
314
+ metadata[:path] == current_metadata[:path] && metadata[:fixed_value].blank?
315
+ end
316
+
317
+ must_support_elements_metadata << current_metadata
318
+ end
319
+ end
320
+ end.uniq
321
+ end
322
+
323
+ def must_support_references
324
+ (must_support_reference_elements + must_support_reference_extensions)
325
+ end
326
+
327
+ def must_support_reference_elements
328
+ must_support_elements
329
+ .select { |element_definition| element_definition[:types]&.include?('Reference') }
330
+ .flat_map do |element_definition|
331
+ profile_element = profile_elements.find do |element|
332
+ element.path == "#{resource}.#{element_definition[:path]}"
333
+ end
334
+ (
335
+ element_definition[:target_profiles] ||
336
+ profile_element&.type&.first&.targetProfile ||
337
+ []
338
+ ).map do |target_profile|
339
+ {
340
+ path: element_definition[:path],
341
+ target_profile:
342
+ }
343
+ end
344
+ end
345
+ end
346
+
347
+ def must_support_reference_extensions
348
+ profile_elements
349
+ .select { |element| element.type.any? { |type| type.code == 'Extension' && type.profile.present? } }
350
+ .flat_map do |element|
351
+ extension_url = element.type.first.profile.first
352
+
353
+ next unless must_support_extensions.any? { |extension| extension[:url] == extension_url }
354
+
355
+ profiles =
356
+ ig_resources
357
+ .profile_by_url(extension_url)
358
+ &.snapshot
359
+ &.element
360
+ &.find { |profile_element| profile_element.id == 'Extension.value[x]:valueReference' }
361
+ &.type
362
+ &.first
363
+ &.targetProfile
364
+
365
+ next if profiles.blank?
366
+
367
+ path = element.path.delete_prefix("#{resource}.")
368
+
369
+ profiles.map do |target_profile|
370
+ {
371
+ path: "#{path}.where(url='#{extension_url}').valueReference",
372
+ target_profile:
373
+ }
374
+ end
375
+ end.compact
376
+ end
377
+
378
+ #### SPECIAL CASES ####
379
+
380
+ # def handle_special_cases
381
+ # end
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,117 @@
1
+ require_relative 'naming'
2
+
3
+ module DaVinciUSDrugFormularyTestKit
4
+ class Generator
5
+ class MustSupportTestGenerator
6
+ class << self
7
+ def generate(ig_metadata, base_output_dir)
8
+ ig_metadata.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', 'must_support.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 output_file_directory
33
+ File.join(base_output_dir, profile_identifier)
34
+ end
35
+
36
+ def output_file_name
37
+ File.join(output_file_directory, base_output_file_name)
38
+ end
39
+
40
+ def read_interaction
41
+ self.class.read_interaction(group_metadata)
42
+ end
43
+
44
+ def profile_identifier
45
+ Naming.snake_case_for_profile(group_metadata)
46
+ end
47
+
48
+ def test_id
49
+ "usdf_#{group_metadata.reformatted_version}_#{profile_identifier}_must_support_test"
50
+ end
51
+
52
+ def class_name
53
+ "#{Naming.upper_camel_case_for_profile(group_metadata)}MustSupportTest"
54
+ end
55
+
56
+ def module_name
57
+ "DaVinciUSDrugFormulary#{group_metadata.reformatted_version.upcase}"
58
+ end
59
+
60
+ def resource_type
61
+ group_metadata.resource
62
+ end
63
+
64
+ def resource_collection_string
65
+ 'all_scratch_resources'
66
+ end
67
+
68
+ def must_support_list_string
69
+ build_must_support_list_string(false)
70
+ end
71
+
72
+ def uscdi_list_string
73
+ build_must_support_list_string(true)
74
+ end
75
+
76
+ def build_must_support_list_string(uscdi_only)
77
+ slice_names = group_metadata.must_supports[:slices]
78
+ .select { |slice| slice[:uscdi_only].presence == uscdi_only.presence }
79
+ .map { |slice| slice[:slice_id] }
80
+ element_names = group_metadata.must_supports[:elements]
81
+ .select { |element| element[:uscdi_only].presence == uscdi_only.presence }
82
+ .map { |element| "#{resource_type}.#{element[:path]}" }
83
+
84
+ extension_names = group_metadata.must_supports[:extensions]
85
+ .select { |extension| extension[:uscdi_only].presence == uscdi_only.presence }
86
+ .map { |extension| extension[:id] }
87
+
88
+ group_metadata.must_supports[:choices]&.each do |choice|
89
+ next unless choice[:uscdi_only].presence == uscdi_only.presence && choice.key?(:paths)
90
+
91
+ choice[:paths].each { |path| element_names.delete("#{resource_type}.#{path}") }
92
+ choice[:extension_ids].each { |id| extension_names.delete(id.to_s) } if choice[:extension_ids].present?
93
+
94
+ element_paths = choice[:paths].map { |path| "#{resource_type}.#{path}" }.join(' or ')
95
+ extension_ids = choice[:extension_ids].map(&:to_s).join(' or ')
96
+
97
+ element_names << ("#{element_paths} or #{extension_ids}")
98
+ end
99
+ (slice_names + element_names + extension_names)
100
+ .uniq
101
+ .sort
102
+ .map { |name| "#{' ' * 8}* #{name}" }
103
+ .join("\n")
104
+ end
105
+
106
+ def generate
107
+ FileUtils.mkdir_p(output_file_directory)
108
+ File.write(output_file_name, output)
109
+
110
+ group_metadata.add_test(
111
+ id: test_id,
112
+ file_name: base_output_file_name
113
+ )
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,28 @@
1
+ module DaVinciUSDrugFormularyTestKit
2
+ class Generator
3
+ module Naming
4
+ class << self
5
+ def resources_with_multiple_profiles
6
+ ['InsurancePlan']
7
+ end
8
+
9
+ def resource_has_multiple_profiles?(resource)
10
+ resources_with_multiple_profiles.include? resource
11
+ end
12
+
13
+ def snake_case_for_profile(group_metadata)
14
+ resource = group_metadata.resource
15
+ return resource.underscore unless resource_has_multiple_profiles?(resource)
16
+
17
+ group_metadata.name
18
+ .delete_prefix('usdf_')
19
+ .underscore
20
+ end
21
+
22
+ def upper_camel_case_for_profile(group_metadata)
23
+ snake_case_for_profile(group_metadata).camelize
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end