davinci_pas_test_kit 0.12.1 → 0.13.0

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/pas_server_subscription_creation_against_pas_client.json +32 -0
  3. data/lib/davinci_pas_test_kit/certs/InfernoCA.key +52 -0
  4. data/lib/davinci_pas_test_kit/certs/InfernoCA.pem +35 -0
  5. data/lib/davinci_pas_test_kit/certs/TestKit.pem +32 -0
  6. data/lib/davinci_pas_test_kit/certs/TestKitPrivateKey.key +28 -0
  7. data/lib/davinci_pas_test_kit/client_suite.rb +141 -100
  8. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/configuration_other_display_test.rb +46 -0
  9. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/configuration_smart_display_test.rb +37 -0
  10. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/configuration_udap_display_test.rb +37 -0
  11. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_registration/other_auth_attest_test.rb +36 -0
  12. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_approval_submit_test.rb +24 -10
  13. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_denial_submit_test.rb +23 -10
  14. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_must_support_test.rb +21 -9
  15. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_inquire_request_bundle_validation_test.rb +4 -0
  16. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_pended_submit_test.rb +30 -14
  17. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_request_bundle_validation_test.rb +6 -0
  18. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_response_attest.rb +4 -9
  19. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_submit_must_support_test.rb +24 -9
  20. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_create_test.rb +26 -13
  21. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_subscription_pas_conformance_test.rb +6 -24
  22. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_inquiry_error_test.rb +1 -0
  23. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/error_tests/pas_submission_error_test.rb +3 -0
  24. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/notification/pas_subscription_notification_test.rb +24 -20
  25. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_approval_group.rb +6 -4
  26. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_auth_smart_group.rb +32 -0
  27. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_auth_udap_group.rb +31 -0
  28. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_denial_group.rb +10 -4
  29. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_must_support_group.rb +29 -0
  30. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_options.rb +25 -0
  31. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_pended_group.rb +11 -4
  32. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_registration_group.rb +63 -0
  33. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_subscription_setup_group.rb +23 -0
  34. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_workflows_group.rb +21 -0
  35. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_server_subscription_input_conformance.rb +35 -0
  36. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_server_subscription_setup.rb +43 -0
  37. data/lib/davinci_pas_test_kit/descriptions.rb +10 -0
  38. data/lib/davinci_pas_test_kit/docs/client_suite_description_v201.md +203 -79
  39. data/lib/davinci_pas_test_kit/docs/demo/PAS Client Suite Demonstration.postman_collection.json +246 -0
  40. data/lib/davinci_pas_test_kit/docs/server_suite_description_v201.md +21 -10
  41. data/lib/davinci_pas_test_kit/endpoints/claim_endpoint.rb +13 -1
  42. data/lib/davinci_pas_test_kit/endpoints/subscription_create_endpoint.rb +13 -1
  43. data/lib/davinci_pas_test_kit/endpoints/subscription_status_endpoint.rb +10 -1
  44. data/lib/davinci_pas_test_kit/endpoints/token_endpoint.rb +29 -15
  45. data/lib/davinci_pas_test_kit/generated/v2.0.1/beneficiary/metadata.yml +2 -1
  46. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/claim_operation_test.rb +1 -1
  47. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim/metadata.yml +28 -17
  48. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/claim_inquiry_operation_test.rb +1 -2
  49. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/client_inquire_request_claim_inquiry_must_support_test.rb +4 -1
  50. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/metadata.yml +7 -1
  51. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_inquiry/server_inquire_request_claim_inquiry_must_support_test.rb +3 -1
  52. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/client_submit_request_claim_update_must_support_test.rb +9 -3
  53. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/metadata.yml +28 -17
  54. data/lib/davinci_pas_test_kit/generated/v2.0.1/claim_update/server_submit_request_claim_update_must_support_test.rb +9 -3
  55. data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/metadata.yml +6 -0
  56. data/lib/davinci_pas_test_kit/generated/v2.0.1/claiminquiryresponse/server_inquire_response_claiminquiryresponse_must_support_test.rb +1 -0
  57. data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/metadata.yml +8 -0
  58. data/lib/davinci_pas_test_kit/generated/v2.0.1/claimresponse/server_submit_response_claimresponse_must_support_test.rb +1 -0
  59. data/lib/davinci_pas_test_kit/generated/v2.0.1/communication_request/metadata.yml +4 -0
  60. data/lib/davinci_pas_test_kit/generated/v2.0.1/coverage/metadata.yml +11 -5
  61. data/lib/davinci_pas_test_kit/generated/v2.0.1/device_request/metadata.yml +2 -0
  62. data/lib/davinci_pas_test_kit/generated/v2.0.1/encounter/metadata.yml +6 -0
  63. data/lib/davinci_pas_test_kit/generated/v2.0.1/insurer/metadata.yml +4 -2
  64. data/lib/davinci_pas_test_kit/generated/v2.0.1/medication_request/metadata.yml +2 -0
  65. data/lib/davinci_pas_test_kit/generated/v2.0.1/metadata.yml +153 -53
  66. data/lib/davinci_pas_test_kit/generated/v2.0.1/nutrition_order/metadata.yml +2 -0
  67. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +2 -1
  68. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_request_bundle/server_pas_inquiry_request_bundle_validation_test.rb +0 -2
  69. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/metadata.yml +2 -1
  70. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_inquiry_response_bundle/server_pas_inquiry_response_bundle_validation_test.rb +2 -3
  71. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/metadata.yml +2 -1
  72. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_request_bundle/server_pas_request_bundle_validation_test.rb +0 -2
  73. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/metadata.yml +2 -1
  74. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_response_bundle/server_pas_response_bundle_validation_test.rb +2 -3
  75. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_approval_use_case_group.rb +0 -1
  76. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_denial_use_case_group.rb +0 -1
  77. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_must_support_use_case_group.rb +3 -0
  78. data/lib/davinci_pas_test_kit/generated/v2.0.1/pas_server_pended_use_case_group.rb +10 -3
  79. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner/metadata.yml +2 -1
  80. data/lib/davinci_pas_test_kit/generated/v2.0.1/practitioner_role/metadata.yml +4 -0
  81. data/lib/davinci_pas_test_kit/generated/v2.0.1/requestor/metadata.yml +4 -2
  82. data/lib/davinci_pas_test_kit/generated/v2.0.1/server_suite.rb +8 -5
  83. data/lib/davinci_pas_test_kit/generated/v2.0.1/service_request/metadata.yml +2 -0
  84. data/lib/davinci_pas_test_kit/generated/v2.0.1/task/metadata.yml +4 -0
  85. data/lib/davinci_pas_test_kit/generator/group_generator.rb +20 -4
  86. data/lib/davinci_pas_test_kit/generator/group_metadata_extractor.rb +2 -2
  87. data/lib/davinci_pas_test_kit/generator/ig_resources.rb +4 -0
  88. data/lib/davinci_pas_test_kit/generator/must_support_test_generator.rb +14 -3
  89. data/lib/davinci_pas_test_kit/generator/operation_test_generator.rb +16 -3
  90. data/lib/davinci_pas_test_kit/generator/templates/group.rb.erb +10 -3
  91. data/lib/davinci_pas_test_kit/generator/templates/must_support.rb.erb +3 -0
  92. data/lib/davinci_pas_test_kit/generator/templates/operation.rb.erb +4 -1
  93. data/lib/davinci_pas_test_kit/generator/templates/suite.rb.erb +8 -5
  94. data/lib/davinci_pas_test_kit/generator/templates/validation.rb.erb +5 -4
  95. data/lib/davinci_pas_test_kit/generator/validation_test_generator.rb +12 -1
  96. data/lib/davinci_pas_test_kit/must_support_test.rb +2 -202
  97. data/lib/davinci_pas_test_kit/pas_bundle_validation.rb +4 -4
  98. data/lib/davinci_pas_test_kit/pas_subscription_verification.rb +30 -0
  99. data/lib/davinci_pas_test_kit/requirements/davinci-pas-test-kit_out_of_scope_requirements.csv +11 -0
  100. data/lib/davinci_pas_test_kit/requirements/davinci-pas-test-kit_requirements.csv +214 -0
  101. data/lib/davinci_pas_test_kit/requirements/generated/davinci-pas-test-kit_requirements_coverage.csv +214 -0
  102. data/lib/davinci_pas_test_kit/session_identification.rb +45 -0
  103. data/lib/davinci_pas_test_kit/tags.rb +1 -0
  104. data/lib/davinci_pas_test_kit/urls.rb +61 -9
  105. data/lib/davinci_pas_test_kit/version.rb +2 -2
  106. data/lib/davinci_pas_test_kit.rb +1 -0
  107. data/lib/inferno_requirements_tools/ext/inferno_core/runnable.rb +22 -0
  108. data/lib/inferno_requirements_tools/tasks/requirements_coverage.rb +284 -0
  109. data/lib/requirements_config.yaml +17 -0
  110. metadata +62 -10
  111. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_request_test.rb +0 -31
  112. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/client_tests/pas_client_token_validation_test.rb +0 -18
  113. data/lib/davinci_pas_test_kit/custom_groups/v2.0.1/pas_client_authentication_group.rb +0 -49
  114. data/lib/davinci_pas_test_kit/generator/must_support_metadata_extractor.rb +0 -327
@@ -1,327 +0,0 @@
1
- require 'pry'
2
- require_relative 'value_extractor'
3
-
4
- module DaVinciPASTestKit
5
- class Generator
6
- class MustSupportMetadataExtractor
7
- attr_accessor :profile_elements, :profile, :resource, :ig_resources
8
-
9
- def initialize(profile_elements, profile, resource, ig_resources)
10
- self.profile_elements = profile_elements
11
- self.profile = profile
12
- self.resource = resource
13
- self.ig_resources = ig_resources
14
- end
15
-
16
- def must_supports
17
- @must_supports = {
18
- extensions: must_support_extensions,
19
- slices: must_support_slices,
20
- elements: must_support_elements
21
- }
22
-
23
- @must_supports
24
- end
25
-
26
- def all_must_support_elements
27
- profile_elements.select(&:mustSupport)
28
- end
29
-
30
- def must_support_extension_elements
31
- all_must_support_elements.select { |element| element.path.end_with? 'extension' }
32
- end
33
-
34
- def must_support_extensions
35
- must_support_extension_elements.map do |element|
36
- {
37
- id: element.id,
38
- path: element.path.gsub("#{resource}.", ''),
39
- url: element.type.first.profile.first
40
- }
41
- end
42
- end
43
-
44
- def must_support_slice_elements
45
- all_must_support_elements.select do |element|
46
- !element.path.end_with?('extension') && element.sliceName.present?
47
- end
48
- end
49
-
50
- def sliced_element(slice)
51
- profile_elements.find { |element| element.id == slice.path }
52
- end
53
-
54
- def discriminators(slice)
55
- slice.slicing.discriminator
56
- end
57
-
58
- def must_support_pattern_slice_elements
59
- must_support_slice_elements.select do |element|
60
- discriminators(sliced_element(element)).first.type == 'pattern' unless sliced_element(element).slicing.nil?
61
- end
62
- end
63
-
64
- def pattern_slices
65
- must_support_pattern_slice_elements.map do |current_element|
66
- {
67
- name: current_element.id,
68
- path: current_element.path.gsub("#{resource}.", '')
69
- }.tap do |metadata|
70
- discriminator = discriminators(sliced_element(current_element)).first
71
- discriminator_path = discriminator.path
72
- discriminator_path = '' if discriminator_path == '$this'
73
- pattern_element =
74
- if discriminator_path.present?
75
- profile_elements.find { |element| element.id == "#{current_element.id}.#{discriminator_path}" }
76
- else
77
- current_element
78
- end
79
-
80
- metadata[:discriminator] =
81
- if pattern_element.patternCodeableConcept.present?
82
- {
83
- type: 'patternCodeableConcept',
84
- path: discriminator_path,
85
- code: pattern_element.patternCodeableConcept.coding.first.code,
86
- system: pattern_element.patternCodeableConcept.coding.first.system
87
- }
88
- elsif pattern_element.patternCoding.present?
89
- {
90
- type: 'patternCoding',
91
- path: discriminator_path,
92
- code: pattern_element.patternCoding.code,
93
- system: pattern_element.patternCoding.system
94
- }
95
- elsif pattern_element.patternIdentifier.present?
96
- {
97
- type: 'patternIdentifier',
98
- path: discriminator_path,
99
- system: pattern_element.patternIdentifier.system
100
- }
101
- elsif pattern_element.binding&.strength == 'required' &&
102
- pattern_element.binding&.valueSet.present?
103
-
104
- value_extractor = ValueExactor.new(ig_resources, resource)
105
-
106
- values = value_extractor.values_from_value_set_binding(pattern_element).presence ||
107
- value_extractor.values_from_resource_metadata([metadata[:path]]).presence || []
108
-
109
- {
110
- type: 'requiredBinding',
111
- path: discriminator_path,
112
- values:
113
- }
114
- else
115
- raise StandardError, 'Unsupported discriminator pattern type'
116
- end
117
- end
118
- end
119
- end
120
-
121
- def must_support_type_slice_elements
122
- must_support_slice_elements.select do |element|
123
- discriminators(sliced_element(element)).first.type == 'type' unless sliced_element(element).slicing.nil?
124
- end
125
- end
126
-
127
- def type_slices
128
- must_support_type_slice_elements.map do |current_element|
129
- discriminator = discriminators(sliced_element(current_element)).first
130
- type_path = discriminator.path
131
- type_path = '' if type_path == '$this'
132
- type_element =
133
- if type_path.present?
134
- profile_elements.find { |element| element.id == "#{current_element.id}.#{type_path}" }
135
- else
136
- current_element
137
- end
138
-
139
- type_code = type_element.type.first.code
140
-
141
- {
142
- name: current_element.id,
143
- path: current_element.path.gsub("#{resource}.", ''),
144
- discriminator: {
145
- type: 'type',
146
- code: type_code.upcase_first
147
- }
148
- }
149
- end
150
- end
151
-
152
- def must_support_value_slice_elements
153
- must_support_slice_elements.select do |element|
154
- discriminators(sliced_element(element)).first.type == 'value' unless sliced_element(element).slicing.nil?
155
- end
156
- end
157
-
158
- def value_slices
159
- must_support_value_slice_elements.map do |current_element|
160
- {
161
- name: current_element.id,
162
- path: current_element.path.gsub("#{resource}.", ''),
163
- discriminator: {
164
- type: 'value'
165
- }
166
- }.tap do |metadata|
167
- metadata[:discriminator][:values] = discriminators(sliced_element(current_element)).map do |discriminator|
168
- fixed_element = profile_elements.find do |element|
169
- element.id.starts_with?(current_element.id) &&
170
- element.path == "#{current_element.path}.#{discriminator.path}"
171
- end
172
-
173
- {
174
- path: discriminator.path,
175
- value: fixed_element&.fixedUri || fixed_element&.fixedCode
176
- }
177
- end
178
- end
179
- end
180
- end
181
-
182
- def must_support_slices
183
- pattern_slices + type_slices + value_slices
184
- end
185
-
186
- def plain_must_support_elements
187
- all_must_support_elements - must_support_extension_elements - must_support_slice_elements
188
- end
189
-
190
- def handle_fixed_values(metadata, element)
191
- if element.fixedUri.present?
192
- metadata[:fixed_value] = element.fixedUri
193
- elsif element.patternCodeableConcept.present?
194
- metadata[:fixed_value] = element.patternCodeableConcept.coding.first.code
195
- metadata[:path] += '.coding.code'
196
- elsif element.fixedCode.present?
197
- metadata[:fixed_value] = element.fixedCode
198
- elsif element.patternIdentifier.present?
199
- metadata[:fixed_value] = element.patternIdentifier.system
200
- metadata[:path] += '.system'
201
- end
202
- end
203
-
204
- def type_must_support_extension?(extensions)
205
- extensions&.any? do |extension|
206
- extension.url == 'http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support' &&
207
- extension.valueBoolean
208
- end
209
- end
210
-
211
- def save_type_code?(type)
212
- type.code == 'Reference'
213
- end
214
-
215
- def get_type_must_support_metadata(current_metadata, current_element)
216
- current_element.type.map do |type|
217
- next unless type_must_support_extension?(type.extension)
218
-
219
- metadata =
220
- {
221
- path: "#{current_metadata[:path].delete_suffix('[x]')}#{type.code.upcase_first}",
222
- original_path: current_metadata[:path]
223
- }
224
- metadata[:type] = [type.code] if save_type_code?(type)
225
- handle_type_must_support_target_profiles(type, metadata) if type.code == 'Reference'
226
-
227
- metadata
228
- end.compact
229
- end
230
-
231
- def handle_type_must_support_target_profiles(type, metadata)
232
- index = 0
233
- target_profiles = []
234
-
235
- type.source_hash['_targetProfile']&.each do |hash|
236
- if hash.present?
237
- element = FHIR::Element.new(hash)
238
- target_profiles << type.targetProfile[index] if type_must_support_extension?(element.extension)
239
- end
240
- index += 1
241
- end
242
-
243
- metadata[:target_profiles] = target_profiles if target_profiles.present?
244
- end
245
-
246
- def handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
247
- choice_element_metadata = must_support_elements_metadata.find do |metadata|
248
- metadata[:original_path].present? &&
249
- current_metadata[:path].include?(metadata[:original_path])
250
- end
251
-
252
- return unless choice_element_metadata.present?
253
-
254
- current_metadata[:original_path] = current_metadata[:path]
255
- current_metadata[:path] =
256
- current_metadata[:path].sub(choice_element_metadata[:original_path], choice_element_metadata[:path])
257
- end
258
-
259
- def must_support_elements
260
- plain_must_support_elements.each_with_object([]) do |current_element, must_support_elements_metadata|
261
- {
262
- path: current_element.path.gsub("#{resource}.", '')
263
- }.tap do |current_metadata|
264
- type_must_support_metadata = get_type_must_support_metadata(current_metadata, current_element)
265
-
266
- if type_must_support_metadata.any?
267
- must_support_elements_metadata.concat(type_must_support_metadata)
268
- else
269
- handle_choice_type_in_sliced_element(current_metadata, must_support_elements_metadata)
270
-
271
- supported_types = current_element.type.select { |type| save_type_code?(type) }.map(&:code)
272
- current_metadata[:types] = supported_types if supported_types.present?
273
-
274
- if current_element.type.first&.code == 'Reference'
275
- handle_type_must_support_target_profiles(current_element.type.first,
276
- current_metadata)
277
- end
278
-
279
- handle_fixed_values(current_metadata, current_element)
280
-
281
- must_support_elements_metadata.delete_if do |metadata|
282
- metadata[:path] == current_metadata[:path] && metadata[:fixed_value].blank?
283
- end
284
-
285
- must_support_elements_metadata << current_metadata
286
- end
287
- end
288
- end.uniq
289
- end
290
-
291
- #### SPECIAL CASE ####
292
-
293
- def vital_sign?
294
- [
295
- 'http://hl7.org/fhir/StructureDefinition/vitalsigns',
296
- 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs'
297
- ].include?(profile.baseDefinition)
298
- end
299
-
300
- def blood_pressure?
301
- ['observation-bp', 'USCoreBloodPressureProfile'].include?(profile.name)
302
- end
303
-
304
- # ONC and US Core 4.0.0 both clarified that health IT developers that always provide HL7 FHIR "observation" values
305
- # are not required to demonstrate Health IT Module support for "dataAbsentReason" elements.
306
- # Remove MS check for dataAbsentReason and component.dataAbsentReason from vital sign profiles and observation lab
307
- # profile.
308
- # Smoking status profile does not have MS on dataAbsentReason. It is safe to use profile.type == 'Observation'
309
-
310
- def remove_device_carrier
311
- return unless profile.type == 'Device'
312
-
313
- @must_supports[:elements].delete_if do |element|
314
- ['udiCarrier.carrierAIDC', 'udiCarrier.carrierHRF'].include?(element[:path])
315
- end
316
- end
317
-
318
- def remove_document_reference_attachment_data_url
319
- return unless profile.type == 'DocumentReference'
320
-
321
- @must_supports[:elements].delete_if do |element|
322
- ['content.attachment.data', 'content.attachment.url'].include?(element[:path])
323
- end
324
- end
325
- end
326
- end
327
- end