davinci_crd_test_kit 0.14.1 → 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 (121) 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} +0 -61
  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 +63 -38
  16. data/lib/davinci_crd_test_kit/client/endpoints/mock_service_response.rb +26 -7
  17. data/lib/davinci_crd_test_kit/client/endpoints/scenario_service_response.rb +54 -0
  18. data/lib/davinci_crd_test_kit/client/tagged_request_load_helper.rb +8 -27
  19. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/retrieve_jwks_test.rb +35 -23
  20. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_payload_test.rb +34 -21
  21. data/lib/davinci_crd_test_kit/client/v2.0.1/cds-services-v201.json +20 -20
  22. data/lib/davinci_crd_test_kit/client/v2.0.1/client_appointment_book_group.rb +12 -10
  23. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_discharge_group.rb +12 -10
  24. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_start_group.rb +12 -10
  25. data/lib/davinci_crd_test_kit/client/v2.0.1/client_hooks_group.rb +5 -5
  26. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_dispatch_group.rb +12 -10
  27. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_select_group.rb +12 -10
  28. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_sign_group.rb +12 -10
  29. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_coverage_information.rb +3 -36
  30. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_external_reference.rb +3 -36
  31. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_instructions.rb +3 -36
  32. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_fetched_data_test.rb +4 -13
  33. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_prefetch_equals_queried_test.rb +3 -13
  34. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/client_display_cards_attest.rb +3 -13
  35. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/inferno_response_validation.rb +3 -13
  36. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_coverage_info_update_test.rb +1 -1
  37. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/attestation_instructions.rb +10 -0
  38. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_authorized_scope_attestation_test.rb +45 -0
  39. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_coverage_based_invocation_attestation_test.rb +44 -0
  40. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_data_element_expectations_attestation_test.rb +44 -0
  41. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_hook_invocation_logging_attestation_test.rb +43 -0
  42. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_exposure_attestation_test.rb +43 -0
  43. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_use_attestation_test.rb +41 -0
  44. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_order_sign_support_attestation_test.rb +42 -0
  45. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_prefetch_key_omission_attestation_test.rb +42 -0
  46. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_resource_identifiers_attestation_test.rb +43 -0
  47. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_response_case_distinction_attestation_test.rb +43 -0
  48. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_security_and_privacy_attestation_test.rb +43 -0
  49. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_workflow_integration_attestation_test.rb +44 -0
  50. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/decode_auth_token_test.rb +1 -1
  51. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/retrieve_jwks_test.rb +29 -18
  52. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_payload_test.rb +35 -22
  53. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-prefetch-subset-v221.json +47 -47
  54. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-v221.json +50 -50
  55. data/lib/davinci_crd_test_kit/client/v2.2.1/client_appointment_book_group.rb +16 -14
  56. data/lib/davinci_crd_test_kit/client/v2.2.1/client_attestations_group.rb +49 -0
  57. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_group.rb +22 -6
  58. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_interaction_group.rb +98 -0
  59. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_discharge_group.rb +12 -10
  60. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_start_group.rb +12 -10
  61. data/lib/davinci_crd_test_kit/client/v2.2.1/client_fhir_api_group.rb +14 -12
  62. data/lib/davinci_crd_test_kit/client/v2.2.1/client_hooks_group.rb +9 -9
  63. data/lib/davinci_crd_test_kit/client/v2.2.1/client_long_running_hook_group.rb +2 -1
  64. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_dispatch_group.rb +16 -14
  65. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_select_group.rb +12 -10
  66. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_sign_group.rb +16 -14
  67. data/lib/davinci_crd_test_kit/client/v2.2.1/client_scenarios_group.rb +26 -0
  68. data/lib/davinci_crd_test_kit/client/v2.2.1/client_self_pay_hook_group.rb +36 -0
  69. data/lib/davinci_crd_test_kit/client/v2.2.1/client_unknown_content_hook_group.rb +34 -0
  70. data/lib/davinci_crd_test_kit/client/v2.2.1/crd_client_suite.rb +19 -3
  71. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_card_must_support_coverage_information_test.rb +1 -1
  72. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_fhirpath_collection_as_comma_delimited_string_test.rb +4 -11
  73. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_hook_instances_unique_test.rb +1 -9
  74. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_location_address_propagation_test.rb +6 -2
  75. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_prefetch_complete_and_subset_test.rb +3 -12
  76. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/appointment_book_receive_request_test.rb +8 -89
  77. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/base_hook_invocation_receive_request_test.rb +163 -0
  78. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/cross_hooks_receive_request_test.rb +97 -0
  79. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_discharge_receive_request_test.rb +8 -90
  80. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_start_receive_request_test.rb +8 -90
  81. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_dispatch_receive_request_test.rb +8 -98
  82. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_select_receive_request_test.rb +8 -98
  83. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_sign_receive_request_test.rb +8 -98
  84. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_long_running_receive_request_test.rb +10 -2
  85. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_skip_long_running_attestation_test.rb +1 -1
  86. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_no_request_test.rb +30 -0
  87. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_workflow_test.rb +55 -0
  88. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_attestation_test.rb +99 -0
  89. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_receive_request_test.rb +62 -0
  90. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_conformance_test.rb +1 -1
  91. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_coverage_verification_test.rb +10 -2
  92. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_data_fetch_verification_test.rb +1 -1
  93. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_granted_scopes_test.rb +1 -1
  94. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_complete_test.rb +1 -1
  95. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_profiles_test.rb +1 -1
  96. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_requested_version_test.rb +1 -1
  97. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_secured_transport_test.rb +1 -1
  98. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/client_display_cards_attest.rb +52 -20
  99. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/hook_response_support_coverage_information_test.rb +1 -1
  100. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/inferno_response_validation.rb +6 -3
  101. data/lib/davinci_crd_test_kit/cross_suite/cards_identification.rb +9 -0
  102. data/lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb +30 -13
  103. data/lib/davinci_crd_test_kit/cross_suite/prefetch_profile_validation.rb +2 -0
  104. data/lib/davinci_crd_test_kit/cross_suite/requests_logical_model_validation.rb +26 -27
  105. data/lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb +48 -41
  106. data/lib/davinci_crd_test_kit/cross_suite/tags.rb +3 -0
  107. data/lib/davinci_crd_test_kit/requirements/cds-hooks_3.0.0-ballot_requirements.xlsx +0 -0
  108. data/lib/davinci_crd_test_kit/requirements/davinci_crd_test_kit_requirements.csv +6 -2
  109. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +107 -107
  110. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_v221_requirements_coverage.csv +101 -97
  111. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_requirements_coverage.csv +18 -18
  112. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_v221_requirements_coverage.csv +36 -32
  113. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.2.1_requirements.xlsx +0 -0
  114. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_server_suite.rb +1 -1
  115. data/lib/davinci_crd_test_kit/server/v2.0.1/server_hooks_group.rb +5 -5
  116. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_standard_prefetch_expressions_test.rb +1 -2
  117. data/lib/davinci_crd_test_kit/server/v2.2.1/server_demonstrate_hook_response_group.rb +2 -2
  118. data/lib/davinci_crd_test_kit/server/v2.2.1/server_hooks_group.rb +5 -5
  119. data/lib/davinci_crd_test_kit/version.rb +2 -2
  120. metadata +40 -12
  121. data/config/presets/inferno_crd_server_suite_v221.json.erb +0 -94
@@ -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" : [
@@ -60,7 +60,9 @@ module DaVinciCRDTestKit
60
60
  crd_jwks_keys_json: { name: :appointment_book_crd_jwks_keys_json }
61
61
  },
62
62
  options: {
63
- hook_name: 'appointment-book'
63
+ hook_name: 'appointment-book',
64
+ crd_interaction_group: 'hooks-appointment-book',
65
+ include_in_cross_hook_analysis: true
64
66
  }
65
67
  )
66
68
 
@@ -70,11 +72,14 @@ module DaVinciCRDTestKit
70
72
  end
71
73
 
72
74
  group do
73
- title 'Authorization'
74
- test from: :crd_v221_decode_auth_token
75
- test from: :crd_v221_retrieve_jwks
76
- test from: :crd_v221_token_header
77
- test from: :crd_v221_token_payload
75
+ title 'Response Handling'
76
+
77
+ test from: :crd_v221_card_display_attest_test
78
+ test from: :crd_v221_inferno_response_validation
79
+ test from: :crd_v221_client_hook_response_support_coverage_information do
80
+ title 'Client supports the Coverage Information response type on the appointment-book hook'
81
+ verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@hook-26'
82
+ end
78
83
  end
79
84
 
80
85
  group do
@@ -95,14 +100,11 @@ module DaVinciCRDTestKit
95
100
  end
96
101
 
97
102
  group do
98
- title 'Response Handling'
99
-
100
- test from: :crd_v221_inferno_response_validation
101
- test from: :crd_v221_card_display_attest_test
102
- test from: :crd_v221_client_hook_response_support_coverage_information do
103
- title 'Client supports the Coverage Information response type on the appointment-book hook'
104
- verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@hook-26'
105
- end
103
+ title 'Authorization'
104
+ test from: :crd_v221_decode_auth_token
105
+ test from: :crd_v221_retrieve_jwks
106
+ test from: :crd_v221_token_header
107
+ test from: :crd_v221_token_payload
106
108
  end
107
109
  end
108
110
  end
@@ -0,0 +1,49 @@
1
+ require_relative 'attestations/attestation_instructions'
2
+ require_relative 'attestations/client_authorized_scope_attestation_test'
3
+ require_relative 'attestations/client_coverage_based_invocation_attestation_test'
4
+ require_relative 'attestations/client_data_element_expectations_attestation_test'
5
+ require_relative 'attestations/client_hook_invocation_logging_attestation_test'
6
+ require_relative 'attestations/client_must_support_exposure_attestation_test'
7
+ require_relative 'attestations/client_must_support_use_attestation_test'
8
+ require_relative 'attestations/client_order_sign_support_attestation_test'
9
+ require_relative 'attestations/client_prefetch_key_omission_attestation_test'
10
+ require_relative 'attestations/client_resource_identifiers_attestation_test'
11
+ require_relative 'attestations/client_response_case_distinction_attestation_test'
12
+ require_relative 'attestations/client_security_and_privacy_attestation_test'
13
+ require_relative 'attestations/client_workflow_integration_attestation_test'
14
+
15
+ module DaVinciCRDTestKit
16
+ module V221
17
+ class CRDClientAttestationsGroup < Inferno::TestGroup
18
+ id :crd_v221_client_attestations
19
+ title 'Visual Inspection and Attestation'
20
+ description %(
21
+ Each test in this group asks the tester to confirm system conformance to one or more
22
+ **SHALL** requirements from the [CRD v2.2.1 Implementation Guide](https://hl7.org/fhir/us/davinci-crd/2.2.1)
23
+ or the [CDS Hooks specification](https://cds-hooks.hl7.org/2026Jan/en/).
24
+ The tester attests that the client system under test meets the statement by selecting "Yes",
25
+ and may record supporting details in the accompanying notes field. Selecting "No" fails the
26
+ test. Notes provided are recorded in the test result.
27
+
28
+ Testers are responsible for confirming that their system meets all requirements associated with a test
29
+ before selecting "Yes" on the attestation input with the same name as the test. The text of the
30
+ attested requirement(s) for each input can be reviewed by clicking the "View Specification Requirements" link
31
+ in the "About" tab of the test with the same name.
32
+ )
33
+ input_instructions ATTESTATION_INPUT_INSTRUCTIONS
34
+
35
+ test from: :crd_v221_security_and_privacy_attestation
36
+ test from: :crd_v221_response_case_distinction_attestation
37
+ test from: :crd_v221_data_element_expectations_attestation
38
+ test from: :crd_v221_resource_identifiers_attestation
39
+ test from: :crd_v221_must_support_exposure_attestation
40
+ test from: :crd_v221_workflow_integration_attestation
41
+ test from: :crd_v221_coverage_based_invocation_attestation
42
+ test from: :crd_v221_prefetch_key_omission_attestation
43
+ test from: :crd_v221_hook_invocation_logging_attestation
44
+ test from: :crd_v221_order_sign_support_attestation
45
+ test from: :crd_v221_authorized_scope_attestation
46
+ test from: :crd_v221_must_support_use_attestation
47
+ end
48
+ end
49
+ end