davinci_crd_test_kit 0.14.0 → 0.14.2

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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/{inferno_crd_server_suite.json.erb → client_against_inferno_crd_server_suite.json.erb} +0 -33
  3. data/config/presets/{ri_crd_request_generator.json.erb → client_against_ri_crd_request_generator.json.erb} +1 -14
  4. data/config/presets/client_v221_against_inferno_crd_server_suite.json.erb +84 -0
  5. data/config/presets/client_v221_against_ri_br_provider.json.erb +31 -0
  6. data/config/presets/{ri_crd_request_generator.json_v221.json.erb → client_v221_against_ri_crd_request_generator.json.erb} +1 -1
  7. data/config/presets/{inferno_crd_client_suite.json.erb → server_against_inferno_crd_client_suite.json.erb} +0 -43
  8. data/config/presets/{ri_crd_server.json.erb → server_against_ri_crd_server.json.erb} +1 -44
  9. data/config/presets/{inferno_crd_client_suite_v221.json.erb → server_v221_against_inferno_crd_client_suite.json.erb} +48 -45
  10. data/config/presets/{inferno_crd_client_suite_prefetch_subset_v221.json.erb → server_v221_against_inferno_crd_client_suite_prefetch_subset.json.erb} +0 -46
  11. data/config/presets/server_v221_against_inferno_g31_suite.json.erb +49 -0
  12. data/lib/davinci_crd_test_kit/client/cross_hook_helper.rb +26 -0
  13. data/lib/davinci_crd_test_kit/client/endpoints/custom_service_response.rb +7 -7
  14. data/lib/davinci_crd_test_kit/client/endpoints/gather_response_generation_data.rb +4 -1
  15. data/lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb +70 -39
  16. data/lib/davinci_crd_test_kit/client/endpoints/mock_service_response.rb +26 -7
  17. data/lib/davinci_crd_test_kit/client/endpoints/mocked_card_responses/external_reference.json +1 -1
  18. data/lib/davinci_crd_test_kit/client/endpoints/mocked_card_responses/launch_smart_app.json +0 -7
  19. data/lib/davinci_crd_test_kit/client/endpoints/scenario_service_response.rb +54 -0
  20. data/lib/davinci_crd_test_kit/client/tagged_request_load_helper.rb +8 -27
  21. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/retrieve_jwks_test.rb +35 -23
  22. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_payload_test.rb +34 -21
  23. data/lib/davinci_crd_test_kit/client/v2.0.1/cds-services-v201.json +20 -20
  24. data/lib/davinci_crd_test_kit/client/v2.0.1/client_appointment_book_group.rb +12 -10
  25. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_discharge_group.rb +12 -10
  26. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_start_group.rb +12 -10
  27. data/lib/davinci_crd_test_kit/client/v2.0.1/client_hooks_group.rb +5 -5
  28. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_dispatch_group.rb +12 -10
  29. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_select_group.rb +12 -10
  30. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_sign_group.rb +12 -10
  31. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_coverage_information.rb +3 -36
  32. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_external_reference.rb +3 -36
  33. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_instructions.rb +3 -36
  34. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_fetched_data_test.rb +4 -13
  35. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_prefetch_equals_queried_test.rb +3 -13
  36. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/client_display_cards_attest.rb +3 -13
  37. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/inferno_response_validation.rb +3 -13
  38. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_coverage_info_update_test.rb +1 -1
  39. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/attestation_instructions.rb +10 -0
  40. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_authorized_scope_attestation_test.rb +45 -0
  41. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_coverage_based_invocation_attestation_test.rb +44 -0
  42. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_data_element_expectations_attestation_test.rb +44 -0
  43. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_hook_invocation_logging_attestation_test.rb +43 -0
  44. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_exposure_attestation_test.rb +43 -0
  45. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_use_attestation_test.rb +41 -0
  46. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_order_sign_support_attestation_test.rb +42 -0
  47. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_prefetch_key_omission_attestation_test.rb +42 -0
  48. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_resource_identifiers_attestation_test.rb +43 -0
  49. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_response_case_distinction_attestation_test.rb +43 -0
  50. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_security_and_privacy_attestation_test.rb +43 -0
  51. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_workflow_integration_attestation_test.rb +44 -0
  52. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/decode_auth_token_test.rb +1 -1
  53. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/retrieve_jwks_test.rb +29 -18
  54. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_payload_test.rb +35 -22
  55. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-prefetch-subset-v221.json +47 -47
  56. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-v221.json +50 -50
  57. data/lib/davinci_crd_test_kit/client/v2.2.1/client_appointment_book_group.rb +24 -15
  58. data/lib/davinci_crd_test_kit/client/v2.2.1/client_attestations_group.rb +49 -0
  59. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_group.rb +22 -6
  60. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_interaction_group.rb +98 -0
  61. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_discharge_group.rb +19 -11
  62. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_start_group.rb +19 -11
  63. data/lib/davinci_crd_test_kit/client/v2.2.1/client_fhir_api_group.rb +14 -12
  64. data/lib/davinci_crd_test_kit/client/v2.2.1/client_hooks_group.rb +25 -5
  65. data/lib/davinci_crd_test_kit/client/v2.2.1/client_long_running_hook_group.rb +3 -2
  66. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_dispatch_group.rb +23 -15
  67. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_select_group.rb +19 -11
  68. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_sign_group.rb +23 -15
  69. data/lib/davinci_crd_test_kit/client/v2.2.1/client_registration_group.rb +6 -0
  70. data/lib/davinci_crd_test_kit/client/v2.2.1/client_scenarios_group.rb +26 -0
  71. data/lib/davinci_crd_test_kit/client/v2.2.1/client_self_pay_hook_group.rb +36 -0
  72. data/lib/davinci_crd_test_kit/client/v2.2.1/client_unknown_content_hook_group.rb +34 -0
  73. data/lib/davinci_crd_test_kit/client/v2.2.1/crd_client_suite.rb +24 -3
  74. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_card_must_support_coverage_information_test.rb +1 -1
  75. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_fhirpath_collection_as_comma_delimited_string_test.rb +4 -11
  76. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_hook_instances_unique_test.rb +1 -9
  77. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_location_address_propagation_test.rb +6 -2
  78. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_prefetch_complete_and_subset_test.rb +3 -12
  79. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/appointment_book_receive_request_test.rb +8 -89
  80. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/base_hook_invocation_receive_request_test.rb +163 -0
  81. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/cross_hooks_receive_request_test.rb +97 -0
  82. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_discharge_receive_request_test.rb +8 -90
  83. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_start_receive_request_test.rb +8 -90
  84. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_dispatch_receive_request_test.rb +8 -98
  85. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_select_receive_request_test.rb +8 -98
  86. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_sign_receive_request_test.rb +8 -98
  87. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_long_running_receive_request_test.rb +10 -2
  88. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_skip_long_running_attestation_test.rb +11 -6
  89. data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_service_registration_attestation_test.rb +2 -1
  90. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_no_request_test.rb +30 -0
  91. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_workflow_test.rb +55 -0
  92. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_attestation_test.rb +99 -0
  93. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_receive_request_test.rb +62 -0
  94. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_conformance_test.rb +1 -1
  95. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_coverage_verification_test.rb +10 -2
  96. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_data_fetch_verification_test.rb +1 -1
  97. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_granted_scopes_test.rb +1 -1
  98. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_complete_test.rb +7 -1
  99. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_profiles_test.rb +1 -1
  100. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_requested_version_test.rb +1 -1
  101. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_secured_transport_test.rb +1 -1
  102. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/client_display_cards_attest.rb +54 -21
  103. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/hook_response_support_coverage_information_test.rb +1 -1
  104. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/inferno_response_validation.rb +6 -3
  105. data/lib/davinci_crd_test_kit/cross_suite/cards_identification.rb +18 -0
  106. data/lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb +30 -13
  107. data/lib/davinci_crd_test_kit/cross_suite/prefetch_completeness_checker.rb +25 -11
  108. data/lib/davinci_crd_test_kit/cross_suite/prefetch_profile_validation.rb +2 -0
  109. data/lib/davinci_crd_test_kit/cross_suite/requests_logical_model_validation.rb +28 -28
  110. data/lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb +211 -43
  111. data/lib/davinci_crd_test_kit/cross_suite/tags.rb +7 -0
  112. data/lib/davinci_crd_test_kit/requirements/cds-hooks_3.0.0-ballot_requirements.xlsx +0 -0
  113. data/lib/davinci_crd_test_kit/requirements/davinci_crd_test_kit_requirements.csv +6 -2
  114. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +107 -107
  115. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_v221_requirements_coverage.csv +101 -97
  116. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_requirements_coverage.csv +18 -18
  117. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_v221_requirements_coverage.csv +43 -39
  118. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.2.1_requirements.xlsx +0 -0
  119. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook.rb +3 -3
  120. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook_no_special_requests.rb +17 -0
  121. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook_with_bad_auth.rb +14 -0
  122. data/lib/davinci_crd_test_kit/server/server_abstract_invoke_hook_test.rb +24 -6
  123. data/lib/davinci_crd_test_kit/server/server_hook_helper.rb +2 -2
  124. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_server_suite.rb +1 -1
  125. data/lib/davinci_crd_test_kit/server/v2.0.1/server_hooks_group.rb +5 -5
  126. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_server_suite.rb +3 -0
  127. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_standard_prefetch_expressions_test.rb +165 -0
  128. data/lib/davinci_crd_test_kit/server/v2.2.1/interaction/server_invoke_hook_single_request_test.rb +22 -0
  129. data/lib/davinci_crd_test_kit/server/v2.2.1/server_appointment_book_group.rb +10 -102
  130. data/lib/davinci_crd_test_kit/server/v2.2.1/server_demonstrate_hook_response_group.rb +3 -2
  131. data/lib/davinci_crd_test_kit/server/v2.2.1/server_discovery_group.rb +2 -0
  132. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_discharge_group.rb +10 -97
  133. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_start_group.rb +10 -97
  134. data/lib/davinci_crd_test_kit/server/v2.2.1/server_hooks_group.rb +5 -5
  135. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_dispatch_group.rb +10 -103
  136. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_select_group.rb +12 -116
  137. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_sign_group.rb +12 -121
  138. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_response_group.rb +23 -0
  139. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/coverage_info_reason_test.rb +108 -0
  140. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/coverage_not_found_group.rb +116 -0
  141. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/no_active_coverage_group.rb +115 -0
  142. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/no_member_found_group.rb +117 -0
  143. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/technical_issues_group.rb +121 -0
  144. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/technical_issues_invoke_test.rb +22 -0
  145. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_context_validation_test.rb +1 -0
  146. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_optional_fields_validation_test.rb +1 -0
  147. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_required_fields_validation_test.rb +1 -0
  148. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/additional_orders_validation_test.rb +78 -35
  149. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_card_absence_test.rb +76 -0
  150. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_validation_test.rb +70 -124
  151. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +83 -39
  152. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/external_reference_card_validation_test.rb +59 -17
  153. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/form_completion_response_validation_test.rb +80 -45
  154. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/hook_request_resource_resolution.rb +37 -0
  155. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/instructions_card_received_test.rb +57 -6
  156. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/launch_smart_app_card_validation_test.rb +56 -17
  157. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/propose_alternate_request_card_validation_test.rb +52 -25
  158. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/service_response_validation_test.rb +17 -51
  159. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_cds_hooks_elements_test.rb +1 -1
  160. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_configuration_test.rb +1 -1
  161. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_context_test.rb +1 -1
  162. data/lib/davinci_crd_test_kit/version.rb +2 -2
  163. metadata +54 -16
  164. data/config/presets/inferno_crd_server_suite_v221.json.erb +0 -94
  165. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/card_optional_fields_validation_test.rb +0 -57
  166. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_received_test.rb +0 -65
@@ -0,0 +1,43 @@
1
+ require_relative 'attestation_instructions'
2
+
3
+ module DaVinciCRDTestKit
4
+ module V221
5
+ class SecurityAndPrivacyAttestationTest < Inferno::Test
6
+ id :crd_v221_security_and_privacy_attestation
7
+ ATTESTATION_TITLE = 'Health IT module adheres to security and privacy requirements'.freeze
8
+ title ATTESTATION_TITLE
9
+ description %(
10
+ During this test, the tester will confirm that the Health IT module adheres to the security and privacy
11
+ rules that CRD inherits from the specifications it builds on.
12
+ To see the specifics of the attested requirements, click the "View Specification Requirements" link for this
13
+ test.
14
+ )
15
+ attestation
16
+ verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@sec-1'
17
+ input_instructions ATTESTATION_INPUT_INSTRUCTIONS
18
+
19
+ input :security_and_privacy_attestation,
20
+ title: ATTESTATION_TITLE,
21
+ description: %(
22
+ I attest that the Health IT module adheres to the security and privacy rules that CRD inherits from
23
+ the specifications it builds on.
24
+ ),
25
+ type: 'radio',
26
+ default: 'false',
27
+ options: {
28
+ list_options: [
29
+ { label: 'Yes', value: 'true' },
30
+ { label: 'No', value: 'false' }
31
+ ]
32
+ }
33
+ input :security_and_privacy_attestation_note,
34
+ title: 'Notes, if applicable:',
35
+ type: 'textarea',
36
+ optional: true
37
+
38
+ run do
39
+ assert security_and_privacy_attestation == 'true'
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'attestation_instructions'
2
+
3
+ module DaVinciCRDTestKit
4
+ module V221
5
+ class WorkflowIntegrationAttestationTest < Inferno::Test
6
+ id :crd_v221_workflow_integration_attestation
7
+ ATTESTATION_TITLE = 'Health IT module integrates hook invocations transparently into user workflows'.freeze
8
+ title ATTESTATION_TITLE
9
+ description %(
10
+ During this test, the tester will confirm that the Health IT module integrates CRD hook invocations
11
+ transparently into the regular user workflow.
12
+ To see the specifics of the attested requirements, click the "View Specification Requirements" link for this
13
+ test.
14
+ )
15
+ attestation
16
+ verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@hook-7', 'hl7.fhir.us.davinci-crd_2.2.1@hook-8',
17
+ 'hl7.fhir.us.davinci-crd_2.2.1@resp-48'
18
+ input_instructions ATTESTATION_INPUT_INSTRUCTIONS
19
+
20
+ input :workflow_integration_attestation,
21
+ title: ATTESTATION_TITLE,
22
+ description: %(
23
+ I attest that the Health IT module integrates CRD hook invocations transparently into the regular
24
+ user workflow.
25
+ ),
26
+ type: 'radio',
27
+ default: 'false',
28
+ options: {
29
+ list_options: [
30
+ { label: 'Yes', value: 'true' },
31
+ { label: 'No', value: 'false' }
32
+ ]
33
+ }
34
+ input :workflow_integration_attestation_note,
35
+ title: 'Notes, if applicable:',
36
+ type: 'textarea',
37
+ optional: true
38
+
39
+ run do
40
+ assert workflow_integration_attestation == 'true'
41
+ end
42
+ end
43
+ end
44
+ end
@@ -21,7 +21,7 @@ module DaVinciCRDTestKit
21
21
  output :auth_tokens, :auth_token_payloads_json, :auth_token_headers_json
22
22
 
23
23
  run do
24
- load_hook_requests
24
+ load_interaction_group_requests
25
25
  skip_if requests.empty?, "No #{hook_name} requests were made in a previous test as expected."
26
26
  auth_tokens = []
27
27
  auth_token_payloads_json = []
@@ -11,7 +11,8 @@ module DaVinciCRDTestKit
11
11
  During this test, Inferno will verify that for each request the JWKS can be retrieved from the JWKS uri if
12
12
  it is present in the `jku` field within the JWT token header. Additionally, keys will be extracted and
13
13
  outputted for use in subsequent tests. If the client does not provide a uri in the `jku` field,
14
- Inferno will extract keys from the raw JWKS JSON provided out of band as a part of the "Registration" group.
14
+ Inferno will use the JWK Set provided out of band as a part of the "Registration" group, either
15
+ retrieving it from a url or, if provided directly, extracting keys from the raw JWKS JSON.
15
16
  )
16
17
 
17
18
  verifies_requirements 'cds-hooks_3.0.0-ballot@183', 'cds-hooks_3.0.0-ballot@185', 'cds-hooks_3.0.0-ballot@197',
@@ -22,12 +23,10 @@ module DaVinciCRDTestKit
22
23
  title: 'CRD JSON Web Key Set (JWKS)',
23
24
  type: 'textarea',
24
25
  description: %(
25
- The client's registered JWK Set containing it's public key. Used
26
- only when a request was received with a JWT without the `jku` header.
27
- Inferno assumes this input, provided during the "Registration"
28
- group, contains the raw JSON representation of a JWKS (if a URI was provided
29
- it would be populated in the `jku` header). Run or re-run the "Registration"
30
- group to set or change this value.
26
+ The client's registered JWK Set, provided during the "Registration" group, containing
27
+ it's public key. Used only when a request was received with a JWT without the `jku`
28
+ header. May be either a publicly accessible url containing the JWKS, or the raw JWKS
29
+ JSON. Run or re-run the "Registration" group to set or change this value.
31
30
  ),
32
31
  locked: true,
33
32
  optional: true
@@ -49,18 +48,9 @@ module DaVinciCRDTestKit
49
48
  jku = JSON.parse(token_header)['jku'] # NOTE: pre-verified json
50
49
  jwks =
51
50
  if jku.present?
52
- get(jku)
53
-
54
- if response[:status] == 200
55
- parse_json_request_entity(response[:body], 'Fetched jku url response', index)
56
- else
57
- add_request_message('error',
58
- "Unexpected response status: expected 200, but received #{response[:status]}",
59
- index)
60
- nil
61
- end
51
+ fetch_jwks(jku, 'Fetched jku url response', index)
62
52
  else
63
- parse_json_request_entity(cds_jwk_set, 'JWK Set input', index)
53
+ parse_or_fetch_jwks(cds_jwk_set, index)
64
54
  end
65
55
  if jwks.blank?
66
56
  crd_jwks_keys_json << nil
@@ -115,6 +105,27 @@ module DaVinciCRDTestKit
115
105
  token_header.present? && JSON.parse(token_header)['jku'].blank?
116
106
  end
117
107
  end
108
+
109
+ def fetch_jwks(uri, entity, index)
110
+ get(uri)
111
+
112
+ if response[:status] == 200
113
+ parse_json_request_entity(response[:body], entity, index)
114
+ else
115
+ add_request_message('error',
116
+ "Unexpected response status: expected 200, but received #{response[:status]}",
117
+ index)
118
+ nil
119
+ end
120
+ end
121
+
122
+ def parse_or_fetch_jwks(jwk_set_input, index)
123
+ if jwk_set_input.match?(%r{\Ahttps?://})
124
+ fetch_jwks(jwk_set_input, 'Fetched JWK Set input url response', index)
125
+ else
126
+ parse_json_request_entity(jwk_set_input, 'JWK Set input', index)
127
+ end
128
+ end
118
129
  end
119
130
  end
120
131
  end
@@ -45,7 +45,7 @@ module DaVinciCRDTestKit
45
45
  run do
46
46
  auth_tokens_list = JSON.parse(auth_tokens)
47
47
  auth_tokens_jwk = JSON.parse(auth_tokens_jwk_json)
48
- requests = load_hook_requests
48
+ requests = load_interaction_group_requests
49
49
  skip_if auth_tokens_list.compact.empty?, 'No Authorization tokens produced from the previous tests.'
50
50
  skip_if auth_tokens_jwk.compact.empty?, 'No Authorization token JWK produced from the previous test.'
51
51
 
@@ -56,34 +56,47 @@ module DaVinciCRDTestKit
56
56
 
57
57
  begin
58
58
  jwk = JSON.parse(auth_token_jwk).deep_symbolize_keys # NOTE: pre-verified json
59
-
60
- payload, =
61
- JWT.decode(
62
- auth_tokens_list[index],
63
- JWT::JWK.import(jwk).public_key,
64
- true,
65
- algorithms: [jwk[:alg]],
66
- exp_leeway: 60,
67
- iss: cds_jwt_iss,
68
- aud: public_hook_url(request),
69
- verify_not_before: false,
70
- verify_iat: false,
71
- verify_jti: true,
72
- verify_iss: true,
73
- verify_aud: true
74
- )
59
+ unverified_payload, jwt_header = JWT.decode(auth_tokens_list[index], nil, false)
75
60
  rescue StandardError => e
76
61
  add_request_message('error', "Token validation error: #{e.message}", index)
77
62
  next
78
63
  end
79
64
 
80
- missing_claims = required_claims - payload.keys
81
- missing_claims_string = missing_claims.map { |claim| "`#{claim}`" }.join(', ')
65
+ alg = jwk[:alg] || jwt_header['alg']
82
66
 
83
- unless missing_claims.empty?
84
- add_request_message('error', "JWT payload missing required claims: #{missing_claims_string}", index)
85
- next
67
+ # Continue checking the rest of the token even when one check fails, so the tester sees every
68
+ # issue at once rather than fixing them one error at a time.
69
+ # CDS Hooks prohibits the `none` algorithm and symmetric (HMAC) algorithms for the authentication JWT.
70
+ if alg.to_s.match?(/\A(none|hs\d+)\z/i)
71
+ add_request_message('error',
72
+ "Token signature algorithm #{alg.inspect} is not permitted; CDS Hooks prohibits " \
73
+ 'the `none` algorithm and symmetric (HMAC) algorithms.', index)
86
74
  end
75
+
76
+ begin
77
+ JWT.decode(
78
+ auth_tokens_list[index],
79
+ JWT::JWK.import(jwk).public_key,
80
+ true,
81
+ algorithms: [alg],
82
+ exp_leeway: 60,
83
+ iss: cds_jwt_iss,
84
+ aud: public_hook_url(request),
85
+ verify_not_before: false,
86
+ verify_iat: false,
87
+ verify_jti: true,
88
+ verify_iss: true,
89
+ verify_aud: true
90
+ )
91
+ rescue StandardError => e
92
+ add_request_message('error', "Token validation error: #{e.message}", index)
93
+ end
94
+
95
+ missing_claims = required_claims - unverified_payload.keys
96
+ next if missing_claims.empty?
97
+
98
+ missing_claims_string = missing_claims.map { |claim| "`#{claim}`" }.join(', ')
99
+ add_request_message('error', "JWT payload missing required claims: #{missing_claims_string}", index)
87
100
  end
88
101
  assert_no_error_messages("#{requests_with_errors_prefix}Token payload is missing required claims or " \
89
102
  'does not have a valid signature. See Messages for details.')
@@ -66,18 +66,19 @@
66
66
  }
67
67
  },
68
68
  {
69
- "hook": "encounter-discharge",
70
- "title": "Encounter Disharge CDS Service Example",
71
- "description": "A simulated CDS Service requesting a subset of the standard prefetches that is invoked when the user is performing the discharge process for an encounter - typically an inpatient encounter.",
72
- "id": "encounter-discharge-subset",
73
- "prefetch" : {
69
+ "hook": "order-select",
70
+ "title": "Order Select CDS Service",
71
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a clinician selects one or more orders to place for a patient",
72
+ "id": "order-select-subset",
73
+ "prefetch": {
74
74
  "pat": "Patient/{{context.patientId}}",
75
75
  "enc": "Encounter/{{context.encounterId}}",
76
- "cov": "Coverage?patient={{context.patientId}}&status=active",
77
- "roles" : "PractitionerRole?_id={{%enc.participant.individual.resolve().ofType(PractitionerRole).id}}",
78
- "pracs" : "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%enc.participant.individual.resolve().ofType(Practitioner).id}}",
79
- "orgs" : "Organization?_id={{%roles.entry.resource.organization.resolve().id}}",
80
- "locs" : "Location?_id={{%roles.entry.resource.location.resolve().id}}"
76
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
77
+ "devs": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
78
+ "meds": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
79
+ "roles": "PractitionerRole?_id={{context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
80
+ "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id}}",
81
+ "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
81
82
  },
82
83
  "extension" : {
83
84
  "davinci-crd.version" : [
@@ -96,25 +97,19 @@
96
97
  }
97
98
  },
98
99
  {
99
- "hook": "order-dispatch",
100
- "title": "Order Dispatch CDS Service Example",
101
- "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a practitioner is selecting a candidate performer for a pre-existing order that was not tied to a specific performer",
102
- "id": "order-dispatch-subset",
100
+ "hook": "order-sign",
101
+ "title": "Order Sign CDS Service",
102
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a clinician is ready to sign one or more orders for a patient",
103
+ "id": "order-sign-subset",
103
104
  "prefetch": {
104
105
  "pat": "Patient/{{context.patientId}}",
105
106
  "enc": "Encounter/{{context.encounterId}}",
106
- "cov": "Coverage?patient={{context.patientId}}&status=active",
107
- "comReqs": "CommunicationRequest?_id={{context.dispatchedOrders.resolve().ofType(CommunicationRequest).id}}",
108
- "devReqs": "DeviceRequest?_id={{context.dispatchedOrders.resolve().ofType(DeviceRequest).id}}",
109
- "medReqs": "MedicationRequest?_id={{context.dispatchedOrders.resolve().ofType(MedicationRequest).id}}",
110
- "nutOrds": "NutritionOrder?_id={{context.dispatchedOrders.resolve().ofType(NutritionOrder).id}}",
111
- "servReqs": "ServiceRequest?_id={{context.dispatchedOrders.resolve().ofType(ServiceRequest).id}}",
112
- "visRxs": "VisionPrescription?_id={{context.dispatchedOrders.resolve().ofType(VisionPrescription).id}}",
113
- "devs": "Device?_id={{%devReqs.entry.resource.code.resolve().id}}",
114
- "meds": "Medication?_id={{%medReqs.entry.resource.medication.resolve().id}}",
115
- "roles": "PractitionerRole?_id={{%comReqs.entry.resource.sender.resolve().ofType(PractitionerRole).id|%comReqs.entry.resource.recipient.resolve().ofType(PractitionerRole).id|%devReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%medReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%servReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
116
- "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%comReqs.entry.resource.sender.resolve().ofType(Practitioner).id|%comReqs.entry.resource.recipient.resolve().ofType(Practitioner).id|%devReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%medReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%servReqs.entry.resource.performer.resolve().ofType(Practitioner).id}}",
117
- "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|%servReqs.entry.resource.locationReference.resolve().id}}"
107
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
108
+ "devs": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
109
+ "meds": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
110
+ "roles": "PractitionerRole?_id={{context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
111
+ "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id}}",
112
+ "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
118
113
  },
119
114
  "extension" : {
120
115
  "davinci-crd.version" : [
@@ -133,19 +128,25 @@
133
128
  }
134
129
  },
135
130
  {
136
- "hook": "order-select",
137
- "title": "Order Select CDS Service",
138
- "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a clinician selects one or more orders to place for a patient",
139
- "id": "order-select-subset",
131
+ "hook": "order-dispatch",
132
+ "title": "Order Dispatch CDS Service Example",
133
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a practitioner is selecting a candidate performer for a pre-existing order that was not tied to a specific performer",
134
+ "id": "order-dispatch-subset",
140
135
  "prefetch": {
141
136
  "pat": "Patient/{{context.patientId}}",
142
137
  "enc": "Encounter/{{context.encounterId}}",
143
- "cov": "Coverage?patient={{context.patientId}}&status=active",
144
- "devs": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
145
- "meds": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
146
- "roles": "PractitionerRole?_id={{context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
147
- "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id}}",
148
- "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
138
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
139
+ "comReqs": "CommunicationRequest?_id={{context.dispatchedOrders.resolve().ofType(CommunicationRequest).id}}",
140
+ "devReqs": "DeviceRequest?_id={{context.dispatchedOrders.resolve().ofType(DeviceRequest).id}}",
141
+ "medReqs": "MedicationRequest?_id={{context.dispatchedOrders.resolve().ofType(MedicationRequest).id}}",
142
+ "nutOrds": "NutritionOrder?_id={{context.dispatchedOrders.resolve().ofType(NutritionOrder).id}}",
143
+ "servReqs": "ServiceRequest?_id={{context.dispatchedOrders.resolve().ofType(ServiceRequest).id}}",
144
+ "visRxs": "VisionPrescription?_id={{context.dispatchedOrders.resolve().ofType(VisionPrescription).id}}",
145
+ "devs": "Device?_id={{%devReqs.entry.resource.code.resolve().id}}",
146
+ "meds": "Medication?_id={{%medReqs.entry.resource.medication.resolve().id}}",
147
+ "roles": "PractitionerRole?_id={{%comReqs.entry.resource.sender.resolve().ofType(PractitionerRole).id|%comReqs.entry.resource.recipient.resolve().ofType(PractitionerRole).id|%devReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%medReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id|%servReqs.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
148
+ "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%comReqs.entry.resource.sender.resolve().ofType(Practitioner).id|%comReqs.entry.resource.recipient.resolve().ofType(Practitioner).id|%devReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%medReqs.entry.resource.performer.resolve().ofType(Practitioner).id|%servReqs.entry.resource.performer.resolve().ofType(Practitioner).id}}",
149
+ "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|%servReqs.entry.resource.locationReference.resolve().id}}"
149
150
  },
150
151
  "extension" : {
151
152
  "davinci-crd.version" : [
@@ -164,19 +165,18 @@
164
165
  }
165
166
  },
166
167
  {
167
- "hook": "order-sign",
168
- "title": "Order Sign CDS Service",
169
- "description": "A simulated CDS Service requesting a subset of the standard prefetches that fires when a clinician is ready to sign one or more orders for a patient",
170
- "id": "order-sign-subset",
171
- "prefetch": {
168
+ "hook": "encounter-discharge",
169
+ "title": "Encounter Disharge CDS Service Example",
170
+ "description": "A simulated CDS Service requesting a subset of the standard prefetches that is invoked when the user is performing the discharge process for an encounter - typically an inpatient encounter.",
171
+ "id": "encounter-discharge-subset",
172
+ "prefetch" : {
172
173
  "pat": "Patient/{{context.patientId}}",
173
174
  "enc": "Encounter/{{context.encounterId}}",
174
- "cov": "Coverage?patient={{context.patientId}}&status=active",
175
- "devs": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
176
- "meds": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
177
- "roles": "PractitionerRole?_id={{context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id}}",
178
- "pracs": "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id}}",
179
- "locs": "Location?_id={{%roles.entry.resource.location.resolve().id|%enc.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
175
+ "cov": "Coverage?patient={{context.patientId}}&status=active",
176
+ "roles" : "PractitionerRole?_id={{%enc.participant.individual.resolve().ofType(PractitionerRole).id}}",
177
+ "pracs" : "Practitioner?_id={{%roles.entry.resource.practitioner.resolve().id|%enc.participant.individual.resolve().ofType(Practitioner).id}}",
178
+ "orgs" : "Organization?_id={{%roles.entry.resource.organization.resolve().id}}",
179
+ "locs" : "Location?_id={{%roles.entry.resource.location.resolve().id}}"
180
180
  },
181
181
  "extension" : {
182
182
  "davinci-crd.version" : [
@@ -67,18 +67,20 @@
67
67
  }
68
68
  },
69
69
  {
70
- "hook": "encounter-discharge",
71
- "title": "Encounter Disharge CDS Service Example",
72
- "description": "A simulated CDS Service that is invoked when the user is performing the discharge process for an encounter - typically an inpatient encounter.",
73
- "id": "encounter-discharge-service",
74
- "prefetch" : {
70
+ "hook": "order-select",
71
+ "title": "Order Select CDS Service",
72
+ "description": "A simulated CDS Service that fires when a clinician selects one or more orders to place for a patient",
73
+ "id": "order-select-service",
74
+ "prefetch": {
75
75
  "patient": "Patient/{{context.patientId}}",
76
76
  "encounter": "Encounter/{{context.encounterId}}",
77
- "coverage": "Coverage?patient={{context.patientId}}&status=active",
78
- "practitionerRoles" : "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id}}",
79
- "practitioners" : "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id}}",
80
- "organizations" : "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id}}",
81
- "locations" : "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id}}"
77
+ "coverage": "Coverage?patient={{context.patientId}}&status=active",
78
+ "devices": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
79
+ "medications": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
80
+ "practitionerRoles": "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.requester.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.orderer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(PractitionerRole).id}}",
81
+ "practitioners": "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.requester.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.orderer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(Practitioner).id}}",
82
+ "organizations": "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id|context.draftOrders.entry.resource.dispenseRequest.performer.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Organization).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Organization).id|context.draftOrders.entry.resource.performer.resolve().ofType(Organization).id}}",
83
+ "locations": "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
82
84
  },
83
85
  "extension" : {
84
86
  "davinci-crd.version" : [
@@ -97,26 +99,20 @@
97
99
  }
98
100
  },
99
101
  {
100
- "hook": "order-dispatch",
101
- "title": "Order Dispatch CDS Service Example",
102
- "description": "A simulated CDS Service that fires when a practitioner is selecting a candidate performer for a pre-existing order that was not tied to a specific performer",
103
- "id": "order-dispatch-service",
102
+ "hook": "order-sign",
103
+ "title": "Order Sign CDS Service",
104
+ "description": "A simulated CDS Service that fires when a clinician is ready to sign one or more orders for a patient",
105
+ "id": "order-sign-service",
104
106
  "prefetch": {
105
107
  "patient": "Patient/{{context.patientId}}",
106
108
  "encounter": "Encounter/{{context.encounterId}}",
107
- "coverage": "Coverage?patient={{context.patientId}}&status=active",
108
- "communicationRequests": "CommunicationRequest?_id={{context.dispatchedOrders.resolve().ofType(CommunicationRequest).id}}",
109
- "deviceRequests": "DeviceRequest?_id={{context.dispatchedOrders.resolve().ofType(DeviceRequest).id}}",
110
- "medicationRequests": "MedicationRequest?_id={{context.dispatchedOrders.resolve().ofType(MedicationRequest).id}}",
111
- "nutritionOrders": "NutritionOrder?_id={{context.dispatchedOrders.resolve().ofType(NutritionOrder).id}}",
112
- "serviceRequests": "ServiceRequest?_id={{context.dispatchedOrders.resolve().ofType(ServiceRequest).id}}",
113
- "visionPrescriptions": "VisionPrescription?_id={{context.dispatchedOrders.resolve().ofType(VisionPrescription).id}}",
114
- "devices": "Device?_id={{%deviceRequests.entry.resource.code.resolve().id}}",
115
- "medications": "Medication?_id={{%medicationRequests.entry.resource.medication.resolve().id}}",
116
- "practitionerRoles": "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id|%communicationRequests.entry.resource.sender.resolve().ofType(PractitionerRole).id|%communicationRequests.entry.resource.recipient.resolve().ofType(PractitionerRole).id|%communicationRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%deviceRequests.entry.resource.performer.resolve().ofType(PractitionerRole).id|%deviceRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%medicationRequests.entry.resource.performer.resolve().ofType(PractitionerRole).id|%medicationRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%serviceRequests.entry.resource.performer.resolve().ofType(PractitionerRole).id|%serviceRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%nutritionOrders.entry.resource.orderer.resolve().ofType(PractitionerRole).id|%visionPrescriptions.entry.resource.prescriber.resolve().ofType(PractitionerRole).id}}",
117
- "practitioners": "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id|%communicationRequests.entry.resource.sender.resolve().ofType(Practitioner).id|%communicationRequests.entry.resource.recipient.resolve().ofType(Practitioner).id|%communicationRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%deviceRequests.entry.resource.performer.resolve().ofType(Practitioner).id|%deviceRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%medicationRequests.entry.resource.performer.resolve().ofType(Practitioner).id|%medicationRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%serviceRequests.entry.resource.performer.resolve().ofType(Practitioner).id|%serviceRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%nutritionOrders.entry.resource.orderer.resolve().ofType(Practitioner).id|%visionPrescriptions.entry.resource.prescriber.resolve().ofType(Practitioner).id}}",
118
- "organizations": "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id|%communicationRequests.entry.resource.recipient.resolve().ofType(Organization).id|%communicationRequests.entry.resource.sender.resolve().ofType(Organization).id|%medicationRequests.entry.resource.dispenseRequest.performer.resolve().id|%serviceRequests.entry.resource.performer.resolve().ofType(Organization).id}}",
119
- "locations": "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id|%serviceRequests.entry.resource.locationReference.resolve().id}}"
109
+ "coverage": "Coverage?patient={{context.patientId}}&status=active",
110
+ "devices": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
111
+ "medications": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
112
+ "practitionerRoles": "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.requester.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.orderer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(PractitionerRole).id}}",
113
+ "practitioners": "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.requester.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.orderer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(Practitioner).id}}",
114
+ "organizations": "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id|context.draftOrders.entry.resource.dispenseRequest.performer.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Organization).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Organization).id|context.draftOrders.entry.resource.performer.resolve().ofType(Organization).id}}",
115
+ "locations": "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
120
116
  },
121
117
  "extension" : {
122
118
  "davinci-crd.version" : [
@@ -135,20 +131,26 @@
135
131
  }
136
132
  },
137
133
  {
138
- "hook": "order-select",
139
- "title": "Order Select CDS Service",
140
- "description": "A simulated CDS Service that fires when a clinician selects one or more orders to place for a patient",
141
- "id": "order-select-service",
134
+ "hook": "order-dispatch",
135
+ "title": "Order Dispatch CDS Service Example",
136
+ "description": "A simulated CDS Service that fires when a practitioner is selecting a candidate performer for a pre-existing order that was not tied to a specific performer",
137
+ "id": "order-dispatch-service",
142
138
  "prefetch": {
143
139
  "patient": "Patient/{{context.patientId}}",
144
140
  "encounter": "Encounter/{{context.encounterId}}",
145
- "coverage": "Coverage?patient={{context.patientId}}&status=active",
146
- "devices": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
147
- "medications": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
148
- "practitionerRoles": "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.requester.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.orderer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(PractitionerRole).id}}",
149
- "practitioners": "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.requester.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.orderer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(Practitioner).id}}",
150
- "organizations": "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id|context.draftOrders.entry.resource.dispenseRequest.performer.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Organization).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Organization).id|context.draftOrders.entry.resource.performer.resolve().ofType(Organization).id}}",
151
- "locations": "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
141
+ "coverage": "Coverage?patient={{context.patientId}}&status=active",
142
+ "communicationRequests": "CommunicationRequest?_id={{context.dispatchedOrders.resolve().ofType(CommunicationRequest).id}}",
143
+ "deviceRequests": "DeviceRequest?_id={{context.dispatchedOrders.resolve().ofType(DeviceRequest).id}}",
144
+ "medicationRequests": "MedicationRequest?_id={{context.dispatchedOrders.resolve().ofType(MedicationRequest).id}}",
145
+ "nutritionOrders": "NutritionOrder?_id={{context.dispatchedOrders.resolve().ofType(NutritionOrder).id}}",
146
+ "serviceRequests": "ServiceRequest?_id={{context.dispatchedOrders.resolve().ofType(ServiceRequest).id}}",
147
+ "visionPrescriptions": "VisionPrescription?_id={{context.dispatchedOrders.resolve().ofType(VisionPrescription).id}}",
148
+ "devices": "Device?_id={{%deviceRequests.entry.resource.code.resolve().id}}",
149
+ "medications": "Medication?_id={{%medicationRequests.entry.resource.medication.resolve().id}}",
150
+ "practitionerRoles": "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id|%communicationRequests.entry.resource.sender.resolve().ofType(PractitionerRole).id|%communicationRequests.entry.resource.recipient.resolve().ofType(PractitionerRole).id|%communicationRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%deviceRequests.entry.resource.performer.resolve().ofType(PractitionerRole).id|%deviceRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%medicationRequests.entry.resource.performer.resolve().ofType(PractitionerRole).id|%medicationRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%serviceRequests.entry.resource.performer.resolve().ofType(PractitionerRole).id|%serviceRequests.entry.resource.requester.resolve().ofType(PractitionerRole).id|%nutritionOrders.entry.resource.orderer.resolve().ofType(PractitionerRole).id|%visionPrescriptions.entry.resource.prescriber.resolve().ofType(PractitionerRole).id}}",
151
+ "practitioners": "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id|%communicationRequests.entry.resource.sender.resolve().ofType(Practitioner).id|%communicationRequests.entry.resource.recipient.resolve().ofType(Practitioner).id|%communicationRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%deviceRequests.entry.resource.performer.resolve().ofType(Practitioner).id|%deviceRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%medicationRequests.entry.resource.performer.resolve().ofType(Practitioner).id|%medicationRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%serviceRequests.entry.resource.performer.resolve().ofType(Practitioner).id|%serviceRequests.entry.resource.requester.resolve().ofType(Practitioner).id|%nutritionOrders.entry.resource.orderer.resolve().ofType(Practitioner).id|%visionPrescriptions.entry.resource.prescriber.resolve().ofType(Practitioner).id}}",
152
+ "organizations": "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id|%communicationRequests.entry.resource.recipient.resolve().ofType(Organization).id|%communicationRequests.entry.resource.sender.resolve().ofType(Organization).id|%medicationRequests.entry.resource.dispenseRequest.performer.resolve().id|%serviceRequests.entry.resource.performer.resolve().ofType(Organization).id}}",
153
+ "locations": "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id|%serviceRequests.entry.resource.locationReference.resolve().id}}"
152
154
  },
153
155
  "extension" : {
154
156
  "davinci-crd.version" : [
@@ -167,20 +169,18 @@
167
169
  }
168
170
  },
169
171
  {
170
- "hook": "order-sign",
171
- "title": "Order Sign CDS Service",
172
- "description": "A simulated CDS Service that fires when a clinician is ready to sign one or more orders for a patient",
173
- "id": "order-sign-service",
174
- "prefetch": {
172
+ "hook": "encounter-discharge",
173
+ "title": "Encounter Disharge CDS Service Example",
174
+ "description": "A simulated CDS Service that is invoked when the user is performing the discharge process for an encounter - typically an inpatient encounter.",
175
+ "id": "encounter-discharge-service",
176
+ "prefetch" : {
175
177
  "patient": "Patient/{{context.patientId}}",
176
178
  "encounter": "Encounter/{{context.encounterId}}",
177
- "coverage": "Coverage?patient={{context.patientId}}&status=active",
178
- "devices": "Device?_id={{context.draftOrders.entry.resource.ofType(DeviceRequest).code.resolve().id}}",
179
- "medications": "Medication?_id={{context.draftOrders.entry.resource.ofType(MedicationRequest).medication.resolve().id}}",
180
- "practitionerRoles": "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.sender.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.recipient.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.requester.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.performer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.orderer.resolve().ofType(PractitionerRole).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(PractitionerRole).id}}",
181
- "practitioners": "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.sender.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.requester.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.performer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.orderer.resolve().ofType(Practitioner).id|context.draftOrders.entry.resource.prescriber.resolve().ofType(Practitioner).id}}",
182
- "organizations": "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id|context.draftOrders.entry.resource.dispenseRequest.performer.resolve().id|context.draftOrders.entry.resource.sender.resolve().ofType(Organization).id|context.draftOrders.entry.resource.recipient.resolve().ofType(Organization).id|context.draftOrders.entry.resource.performer.resolve().ofType(Organization).id}}",
183
- "locations": "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id|context.draftOrders.entry.resource.locationReference.resolve().id}}"
179
+ "coverage": "Coverage?patient={{context.patientId}}&status=active",
180
+ "practitionerRoles" : "PractitionerRole?_id={{%encounter.participant.individual.resolve().ofType(PractitionerRole).id}}",
181
+ "practitioners" : "Practitioner?_id={{%practitionerRoles.entry.resource.practitioner.resolve().id|%encounter.participant.individual.resolve().ofType(Practitioner).id}}",
182
+ "organizations" : "Organization?_id={{%practitionerRoles.entry.resource.organization.resolve().id|%encounter.serviceProvider.resolve().ofType(Organization).id}}",
183
+ "locations" : "Location?_id={{%practitionerRoles.entry.resource.location.resolve().id|%encounter.location.location.resolve().id}}"
184
184
  },
185
185
  "extension" : {
186
186
  "davinci-crd.version" : [