onc_certification_g10_test_kit 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/terminology/expected_manifest.yml +242 -29
  3. data/lib/inferno/terminology/fhir_package_manager.rb +27 -0
  4. data/lib/inferno/terminology/loader.rb +22 -1
  5. data/lib/inferno/terminology/tasks/create_value_set_validators.rb +1 -1
  6. data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +5 -0
  7. data/lib/inferno/terminology/value_set.rb +51 -5
  8. data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +5 -4
  9. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_stu1.rb +5 -0
  10. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_stu2.rb +2 -0
  11. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +206 -28
  12. data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +25 -40
  13. data/lib/onc_certification_g10_test_kit/encounter_context_test.rb +30 -0
  14. data/lib/onc_certification_g10_test_kit/feature.rb +5 -8
  15. data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +18 -5
  16. data/lib/onc_certification_g10_test_kit/profile_selector.rb +175 -0
  17. data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +54 -4
  18. data/lib/onc_certification_g10_test_kit/single_patient_us_core_5_api_group.rb +93 -0
  19. data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +50 -5
  20. data/lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group.rb +94 -0
  21. data/lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group_stu2.rb +94 -0
  22. data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +197 -13
  23. data/lib/onc_certification_g10_test_kit/smart_invalid_pkce_group.rb +310 -0
  24. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group_stu2.rb +211 -0
  25. data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +135 -9
  26. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +16 -4
  27. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group_stu2.rb +130 -0
  28. data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +134 -67
  29. data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +166 -11
  30. data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +119 -135
  31. data/lib/onc_certification_g10_test_kit/version.rb +1 -1
  32. data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +19 -0
  33. data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +7 -1
  34. data/lib/onc_certification_g10_test_kit.rb +115 -74
  35. metadata +19 -11
  36. data/lib/onc_certification_g10_test_kit/profile_guesser.rb +0 -72
@@ -1,22 +1,25 @@
1
- require_relative 'onc_certification_g10_test_kit/feature'
2
-
3
1
  require 'smart_app_launch/smart_stu1_suite'
4
- require 'us_core_test_kit/generated/v3.1.1/us_core_test_suite'
5
- require 'us_core_test_kit/generated/v4.0.0/us_core_test_suite' if ONCCertificationG10TestKit::Feature.us_core_v4?
2
+ require 'smart_app_launch/smart_stu2_suite'
3
+ require 'us_core_test_kit'
6
4
 
7
5
  require_relative 'onc_certification_g10_test_kit/configuration_checker'
8
6
  require_relative 'onc_certification_g10_test_kit/version'
9
7
 
8
+ require_relative 'onc_certification_g10_test_kit/feature'
10
9
  require_relative 'onc_certification_g10_test_kit/single_patient_api_group'
11
- if ONCCertificationG10TestKit::Feature.us_core_v4?
12
- require_relative 'onc_certification_g10_test_kit/single_patient_us_core_4_api_group'
13
- end
10
+ require_relative 'onc_certification_g10_test_kit/single_patient_us_core_4_api_group'
11
+ require_relative 'onc_certification_g10_test_kit/single_patient_us_core_5_api_group'
14
12
  require_relative 'onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group'
15
13
  require_relative 'onc_certification_g10_test_kit/smart_invalid_token_group'
14
+ require_relative 'onc_certification_g10_test_kit/smart_invalid_token_group_stu2'
15
+ require_relative 'onc_certification_g10_test_kit/smart_invalid_pkce_group'
16
16
  require_relative 'onc_certification_g10_test_kit/smart_limited_app_group'
17
17
  require_relative 'onc_certification_g10_test_kit/smart_standalone_patient_app_group'
18
18
  require_relative 'onc_certification_g10_test_kit/smart_ehr_practitioner_app_group'
19
19
  require_relative 'onc_certification_g10_test_kit/smart_public_standalone_launch_group'
20
+ require_relative 'onc_certification_g10_test_kit/smart_public_standalone_launch_group_stu2'
21
+ require_relative 'onc_certification_g10_test_kit/smart_ehr_patient_launch_group'
22
+ require_relative 'onc_certification_g10_test_kit/smart_ehr_patient_launch_group_stu2'
20
23
  require_relative 'onc_certification_g10_test_kit/multi_patient_api_stu1'
21
24
  require_relative 'onc_certification_g10_test_kit/multi_patient_api_stu2'
22
25
  require_relative 'onc_certification_g10_test_kit/terminology_binding_validator'
@@ -67,7 +70,8 @@ module ONCCertificationG10TestKit
67
70
  (
68
71
  message.type == 'error' && (
69
72
  message.message.match?(/\A\S+: \S+: Unknown Code/) ||
70
- message.message.match?(/\A\S+: \S+: None of the codings provided are in the value set/)
73
+ message.message.match?(/\A\S+: \S+: None of the codings provided are in the value set/) ||
74
+ message.message.match?(/\A\S+: \S+: The Coding provided \(\S*\) is not in the value set/)
71
75
  )
72
76
  )
73
77
  true
@@ -114,35 +118,54 @@ module ONCCertificationG10TestKit
114
118
  well_known_route_handler
115
119
  )
116
120
 
117
- if Feature.us_core_v4?
118
- suite_option :us_core_version,
119
- title: 'US Core Version',
120
- list_options: [
121
- {
122
- label: 'US Core 3.1.1',
123
- value: 'us_core_3'
124
- },
125
- {
126
- label: 'US Core 4.0.0',
127
- value: 'us_core_4'
128
- }
129
- ]
130
- end
131
-
132
- if Feature.bulk_data_v2?
133
- suite_option :multi_patient_version,
134
- title: 'Multi-Patient Authorization and API Version',
135
- list_options: [
136
- {
137
- label: 'Multi-Patient Authorization and API STU1',
138
- value: 'multi_patient_api_stu1'
139
- },
140
- {
141
- label: 'Multi-Patient Authorization and API STU2',
142
- value: 'multi_patient_api_stu2'
143
- }
144
- ]
145
- end
121
+ suite_option :us_core_version,
122
+ title: 'US Core Version',
123
+ list_options: [
124
+ {
125
+ label: 'US Core 3.1.1 / USCDI v1',
126
+ value: 'us_core_3'
127
+ },
128
+ {
129
+ label: 'US Core 4.0.0 / USCDI v1',
130
+ value: 'us_core_4'
131
+ },
132
+ {
133
+ label: 'US Core 5.0.1 / USCDI v2',
134
+ value: 'us_core_5'
135
+ }
136
+ ]
137
+
138
+ suite_option :smart_app_launch_version,
139
+ title: 'SMART App Launch Version',
140
+ list_options: [
141
+ {
142
+ label: 'SMART App Launch 1.0.0',
143
+ value: 'smart_app_launch_1'
144
+ },
145
+ {
146
+ label: 'SMART App Launch 2.0.0',
147
+ value: 'smart_app_launch_2'
148
+ }
149
+ ]
150
+
151
+ suite_option :multi_patient_version,
152
+ title: 'Bulk Data Version',
153
+ list_options: [
154
+ {
155
+ label: 'Bulk Data 1.0.1',
156
+ value: 'multi_patient_api_stu1'
157
+ },
158
+ {
159
+ label: 'Bulk Data 2.0.0',
160
+ value: 'multi_patient_api_stu2'
161
+ }
162
+ ]
163
+
164
+ config(
165
+ options: {
166
+ post_authorization_uri: "#{Inferno::Application['base_url']}/custom/smart_stu2/post_auth"
167
+ }
168
+ )
146
169
 
147
170
  description %(
148
171
  The ONC Certification (g)(10) Standardized API Test Kit is a testing tool for
@@ -151,11 +174,6 @@ module ONCCertificationG10TestKit
151
174
  Patient and Population Services criterion § 170.315(g)(10) in the 2015
152
175
  Edition Cures Update.
153
176
 
154
- This test kit is the successor to "Inferno Program Edition". Please [create an
155
- issue](https://github.com/inferno-framework/g10-certification-test-kit/issues)
156
- if there are discrepencies between these tests and the "Inferno Program
157
- Edition v1.9" tests.
158
-
159
177
  To get started, please first register the Inferno client as a SMART App
160
178
  with the following information:
161
179
 
@@ -170,6 +188,19 @@ module ONCCertificationG10TestKit
170
188
  Systems must pass all tests in order to qualify for ONC certification.
171
189
  )
172
190
 
191
+ suite_summary %(
192
+ The ONC Certification (g)(10) Standardized API Test Kit is a testing tool
193
+ for Health Level 7 (HL7®) Fast Healthcare Interoperability Resources
194
+ (FHIR®) services seeking to meet the requirements of the Standardized API
195
+ for Patient and Population Services criterion § 170.315(g)(10) in the 2015
196
+ Edition Cures Update rule.
197
+
198
+ Systems may adopt later versions of standards than those named in the rule
199
+ as approved by the ONC Standards Version Advancement Process (SVAP).
200
+ Please select which approved version of each standard to use, and click
201
+ ‘Select Options’ to begin testing.
202
+ )
203
+
173
204
  input_instructions %(
174
205
  Register Inferno as a SMART app using the following information:
175
206
 
@@ -188,23 +219,17 @@ module ONCCertificationG10TestKit
188
219
 
189
220
  group from: 'g10_smart_ehr_practitioner_app'
190
221
 
191
- group from: 'g10_single_patient_api' do
192
- required_suite_options us_core_version: 'us_core_3' if Feature.us_core_v4?
193
- end
194
-
195
- if Feature.us_core_v4?
196
- group from: 'g10_single_patient_us_core_4_api',
197
- required_suite_options: { us_core_version: 'us_core_4' }
198
- end
222
+ group from: 'g10_single_patient_api',
223
+ required_suite_options: { us_core_version: 'us_core_3' }
224
+ group from: 'g10_single_patient_us_core_4_api',
225
+ required_suite_options: { us_core_version: 'us_core_4' }
226
+ group from: 'g10_single_patient_us_core_5_api',
227
+ required_suite_options: { us_core_version: 'us_core_5' }
199
228
 
200
- group from: 'multi_patient_api' do
201
- required_suite_options multi_patient_version: 'multi_patient_api_stu1' if Feature.bulk_data_v2?
202
- end
203
-
204
- if Feature.bulk_data_v2?
205
- group from: 'multi_patient_api_stu2',
206
- required_suite_options: { multi_patient_version: 'multi_patient_api_stu2' }
207
- end
229
+ group from: 'multi_patient_api',
230
+ required_suite_options: { multi_patient_version: 'multi_patient_api_stu1' }
231
+ group from: 'multi_patient_api_stu2',
232
+ required_suite_options: { multi_patient_version: 'multi_patient_api_stu2' }
208
233
 
209
234
  group do
210
235
  title 'Additional Tests'
@@ -217,27 +242,43 @@ module ONCCertificationG10TestKit
217
242
  may require special setup on the part of the tester.
218
243
  )
219
244
 
220
- config(
221
- options: {
222
- redirect_message_proc: lambda do |auth_url|
223
- %(
224
- ### #{self.class.parent.title}
245
+ default_redirect_message_proc = lambda do |auth_url|
246
+ %(
247
+ ### #{self.class.parent.title}
225
248
 
226
- [Follow this link to authorize with the SMART
227
- server](#{auth_url}).
249
+ [Follow this link to authorize with the SMART server](#{auth_url}).
228
250
 
229
- Tests will resume once Inferno receives a request at
230
- `#{config.options[:redirect_uri]}` with a state of `#{state}`.
231
- )
232
- end
233
- }
234
- )
251
+ Tests will resume once Inferno receives a request at
252
+ `#{config.options[:redirect_uri]}` with a state of `#{state}`.
253
+ )
254
+ end
255
+
256
+ group from: :g10_public_standalone_launch,
257
+ required_suite_options: { smart_app_launch_version: 'smart_app_launch_1' },
258
+ config: { options: { redirect_message_proc: default_redirect_message_proc } }
259
+ group from: :g10_public_standalone_launch_stu2,
260
+ required_suite_options: { smart_app_launch_version: 'smart_app_launch_2' },
261
+ config: { options: { redirect_message_proc: default_redirect_message_proc } }
235
262
 
236
- group from: :g10_public_standalone_launch
237
263
  group from: :g10_token_revocation
238
264
 
239
- group from: :g10_smart_invalid_aud
240
- group from: :g10_smart_invalid_token_request
265
+ group from: :g10_smart_invalid_aud,
266
+ config: { options: { redirect_message_proc: default_redirect_message_proc } }
267
+
268
+ group from: :g10_smart_invalid_token_request,
269
+ required_suite_options: { smart_app_launch_version: 'smart_app_launch_1' },
270
+ config: { options: { redirect_message_proc: default_redirect_message_proc } }
271
+ group from: :g10_smart_invalid_token_request_stu2,
272
+ required_suite_options: { smart_app_launch_version: 'smart_app_launch_2' },
273
+ config: { options: { redirect_message_proc: default_redirect_message_proc } }
274
+
275
+ group from: :g10_smart_invalid_pkce_code_verifier_group,
276
+ required_suite_options: { smart_app_launch_version: 'smart_app_launch_2' }
277
+
278
+ group from: :g10_ehr_patient_launch,
279
+ required_suite_options: { smart_app_launch_version: 'smart_app_launch_1' }
280
+ group from: :g10_ehr_patient_launch_stu2,
281
+ required_suite_options: { smart_app_launch_version: 'smart_app_launch_2' }
241
282
 
242
283
  group from: :g10_visual_inspection_and_attestations
243
284
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: onc_certification_g10_test_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen MacVicar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-09 00:00:00.000000000 Z
11
+ date: 2022-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bloomer
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.3.7
47
+ version: 0.3.9
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 0.3.7
54
+ version: 0.3.9
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: json-jwt
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - '='
144
144
  - !ruby/object:Gem::Version
145
- version: 0.2.5
145
+ version: 0.3.0
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - '='
151
151
  - !ruby/object:Gem::Version
152
- version: 0.2.5
152
+ version: 0.3.0
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: database_cleaner-sequel
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -206,7 +206,8 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: '3.11'
209
- description: ONC Certification (g)(10) Test Kit for Inferno
209
+ description: ONC Certification (g)(10) Standardized API for Patient and Population
210
+ Services Test Kit
210
211
  email:
211
212
  - inferno@groups.mitre.org
212
213
  executables: []
@@ -255,6 +256,7 @@ files:
255
256
  - lib/onc_certification_g10_test_kit/bulk_data_jwks.json
256
257
  - lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb
257
258
  - lib/onc_certification_g10_test_kit/configuration_checker.rb
259
+ - lib/onc_certification_g10_test_kit/encounter_context_test.rb
258
260
  - lib/onc_certification_g10_test_kit/export_kick_off_performer.rb
259
261
  - lib/onc_certification_g10_test_kit/feature.rb
260
262
  - lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyheight.json
@@ -268,17 +270,23 @@ files:
268
270
  - lib/onc_certification_g10_test_kit/multi_patient_api_stu2.rb
269
271
  - lib/onc_certification_g10_test_kit/onc_program_procedure.yml
270
272
  - lib/onc_certification_g10_test_kit/patient_context_test.rb
271
- - lib/onc_certification_g10_test_kit/profile_guesser.rb
273
+ - lib/onc_certification_g10_test_kit/profile_selector.rb
272
274
  - lib/onc_certification_g10_test_kit/resource_access_test.rb
273
275
  - lib/onc_certification_g10_test_kit/restricted_access_test.rb
274
276
  - lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb
275
277
  - lib/onc_certification_g10_test_kit/single_patient_api_group.rb
276
278
  - lib/onc_certification_g10_test_kit/single_patient_us_core_4_api_group.rb
279
+ - lib/onc_certification_g10_test_kit/single_patient_us_core_5_api_group.rb
277
280
  - lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb
281
+ - lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group.rb
282
+ - lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group_stu2.rb
278
283
  - lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb
284
+ - lib/onc_certification_g10_test_kit/smart_invalid_pkce_group.rb
279
285
  - lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb
286
+ - lib/onc_certification_g10_test_kit/smart_invalid_token_group_stu2.rb
280
287
  - lib/onc_certification_g10_test_kit/smart_limited_app_group.rb
281
288
  - lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb
289
+ - lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group_stu2.rb
282
290
  - lib/onc_certification_g10_test_kit/smart_scopes_test.rb
283
291
  - lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb
284
292
  - lib/onc_certification_g10_test_kit/tasks/generate_matrix.rb
@@ -290,12 +298,12 @@ files:
290
298
  - lib/onc_certification_g10_test_kit/version.rb
291
299
  - lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb
292
300
  - lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb
293
- homepage: https://github.com/inferno_framework/onc-certification-g10-test-kit
301
+ homepage: https://github.com/onc-healthit/onc-certification-g10-test-kit
294
302
  licenses:
295
303
  - Apache-2.0
296
304
  metadata:
297
- homepage_uri: https://github.com/inferno_framework/onc-certification-g10-test-kit
298
- source_code_uri: https://github.com/inferno_framework/onc-certification-g10-test-kit
305
+ homepage_uri: https://github.com/onc-healthit/onc-certification-g10-test-kit
306
+ source_code_uri: https://github.com/onc-healthit/onc-certification-g10-test-kit
299
307
  post_install_message:
300
308
  rdoc_options: []
301
309
  require_paths:
@@ -1,72 +0,0 @@
1
- module ONCCertificationG10TestKit
2
- module ProfileGuesser
3
- def extract_profile(profile)
4
- case profile
5
- when 'Medication'
6
- return USCoreTestKit::USCoreV311::USCoreTestSuite.metadata.find do |meta|
7
- meta.resource == profile
8
- end.profile_url
9
- when 'Location'
10
- return 'http://hl7.org/fhir/StructureDefinition/Location'
11
- end
12
- "USCoreTestKit::USCoreV311::#{profile}Group".constantize.metadata.profile_url
13
- end
14
-
15
- def observation_contains_code(observation_resource, code)
16
- observation_resource&.code&.coding&.any? { |coding| coding&.code == code }
17
- end
18
-
19
- def resource_contains_category(resource, category_code, category_system = nil) # rubocop:disable Metrics/CyclomaticComplexity
20
- resource&.category&.any? do |category|
21
- category.coding&.any? do |coding|
22
- coding.code == category_code &&
23
- (category_system.blank? || coding.system.blank? || category_system == coding.system)
24
- end
25
- end
26
- end
27
-
28
- def guess_profile(resource) # rubocop:disable Metrics/CyclomaticComplexity
29
- case resource.resourceType
30
- when 'DiagnosticReport'
31
- return extract_profile('DiagnosticReportLab') if resource_contains_category(resource, 'LAB', 'http://terminology.hl7.org/CodeSystem/v2-0074')
32
-
33
- extract_profile('DiagnosticReportNote')
34
- when 'Observation'
35
- return extract_profile('Smokingstatus') if observation_contains_code(resource, '72166-2')
36
-
37
- return extract_profile('ObservationLab') if resource_contains_category(resource, 'laboratory', 'http://terminology.hl7.org/CodeSystem/observation-category')
38
-
39
- return extract_profile('PediatricBmiForAge') if observation_contains_code(resource, '59576-9')
40
-
41
- return extract_profile('PediatricWeightForHeight') if observation_contains_code(resource, '77606-2')
42
-
43
- return extract_profile('PulseOximetry') if observation_contains_code(resource, '59408-5')
44
-
45
- return extract_profile('HeadCircumference') if observation_contains_code(resource, '8289-1')
46
-
47
- # FHIR Vital Signs profiles: https://www.hl7.org/fhir/observation-vitalsigns.html
48
- # Vital Signs Panel, Oxygen Saturation are not required by USCDI
49
- # Body Mass Index is replaced by :pediatric_bmi_age Profile
50
- # Systolic Blood Pressure, Diastolic Blood Pressure are covered by :blood_pressure Profile
51
- # Head Circumference is replaced by US Core Head Occipital-frontal Circumference Percentile Profile
52
- return extract_profile('Bp') if observation_contains_code(resource, '85354-9')
53
-
54
- return extract_profile('Bodyheight') if observation_contains_code(resource, '8302-2')
55
-
56
- return extract_profile('Bodytemp') if observation_contains_code(resource, '8310-5')
57
-
58
- return extract_profile('Bodyweight') if observation_contains_code(resource, '29463-7')
59
-
60
- return extract_profile('Heartrate') if observation_contains_code(resource, '8867-4')
61
-
62
- return extract_profile('Resprate') if observation_contains_code(resource, '9279-1')
63
-
64
- nil
65
- else
66
- extract_profile(resource.resourceType)
67
- end
68
- rescue StandardError
69
- skip "Could not determine profile of \"#{resource.resourceType}\" resource."
70
- end
71
- end
72
- end