davinci_pas_test_kit 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/g33_br_provider.json +13 -0
  3. data/config/presets/pas_client_example.json +2 -2
  4. data/config/presets/pas_client_v201_run_against_pas_server.json +2 -2
  5. data/config/presets/pas_client_v221_run_against_pas_server.json +3 -3
  6. data/config/presets/pas_ri.json +6 -6
  7. data/config/presets/pas_server_v201_run_against_pas_client.json.erb +6 -6
  8. data/config/presets/pas_server_v221_run_against_pas_client.json.erb +10 -10
  9. data/lib/davinci_pas_test_kit/client/abstract_approval_submit_test.rb +28 -17
  10. data/lib/davinci_pas_test_kit/client/abstract_denial_submit_test.rb +26 -16
  11. data/lib/davinci_pas_test_kit/client/abstract_gather_must_support_test.rb +45 -74
  12. data/lib/davinci_pas_test_kit/client/abstract_modification_submit_test.rb +29 -19
  13. data/lib/davinci_pas_test_kit/client/abstract_response_attest.rb +75 -6
  14. data/lib/davinci_pas_test_kit/client/abstract_subscription_create_test.rb +26 -9
  15. data/lib/davinci_pas_test_kit/client/abstract_subscription_update_delete_test.rb +91 -0
  16. data/lib/davinci_pas_test_kit/client/client_bundle_validation_helper.rb +245 -0
  17. data/lib/davinci_pas_test_kit/client/client_input_descriptions.rb +96 -1
  18. data/lib/davinci_pas_test_kit/client/endpoints/claim_endpoint.rb +128 -72
  19. data/lib/davinci_pas_test_kit/client/endpoints/endpoint_session_identification.rb +24 -0
  20. data/lib/davinci_pas_test_kit/client/endpoints/expired_token.rb +40 -0
  21. data/lib/davinci_pas_test_kit/client/endpoints/subscription_create_endpoint.rb +7 -12
  22. data/lib/davinci_pas_test_kit/client/endpoints/subscription_delete_endpoint.rb +13 -0
  23. data/lib/davinci_pas_test_kit/client/endpoints/subscription_read_endpoint.rb +13 -0
  24. data/lib/davinci_pas_test_kit/client/endpoints/subscription_status_endpoint.rb +15 -20
  25. data/lib/davinci_pas_test_kit/client/endpoints/subscription_update_endpoint.rb +13 -0
  26. data/lib/davinci_pas_test_kit/client/endpoints/token_endpoint.rb +16 -0
  27. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/beneficiary/client_inquire_request_must_support_beneficiary_test.rb +3 -3
  28. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/beneficiary/client_inquire_response_must_support_beneficiary_test.rb +3 -3
  29. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/beneficiary/client_submit_request_must_support_beneficiary_test.rb +3 -3
  30. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/beneficiary/client_submit_response_must_support_beneficiary_test.rb +3 -3
  31. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/claim_inquiry/client_inquire_request_must_support_claim_inquiry_test.rb +3 -3
  32. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/claim_update/client_submit_request_must_support_claim_update_test.rb +3 -3
  33. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/claiminquiryresponse/client_inquire_response_must_support_claiminquiryresponse_test.rb +3 -3
  34. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/claimresponse/client_submit_response_must_support_claimresponse_test.rb +3 -3
  35. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/communication_request/client_submit_response_must_support_communication_request_test.rb +3 -3
  36. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/coverage/client_inquire_request_must_support_coverage_test.rb +3 -3
  37. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/coverage/client_submit_request_must_support_coverage_test.rb +3 -3
  38. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/encounter/client_submit_request_must_support_encounter_test.rb +3 -3
  39. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/insurer/client_inquire_request_must_support_insurer_test.rb +3 -3
  40. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/insurer/client_inquire_response_must_support_insurer_test.rb +3 -3
  41. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/insurer/client_submit_request_must_support_insurer_test.rb +3 -3
  42. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/insurer/client_submit_response_must_support_insurer_test.rb +3 -3
  43. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_client_inquire_must_support_group.rb +1 -1
  44. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_client_inquire_response_must_support_group.rb +9 -31
  45. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_client_submit_must_support_group.rb +1 -1
  46. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_client_submit_response_must_support_group.rb +10 -32
  47. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_inquiry_request_bundle/client_inquire_request_must_support_pas_inquiry_request_bundle_test.rb +3 -3
  48. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_inquiry_response_bundle/client_inquire_response_must_support_pas_inquiry_response_bundle_test.rb +3 -3
  49. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_request_bundle/client_submit_request_must_support_pas_request_bundle_test.rb +3 -3
  50. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/pas_response_bundle/client_submit_response_must_support_pas_response_bundle_test.rb +3 -3
  51. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner/client_inquire_request_must_support_practitioner_test.rb +3 -3
  52. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner/client_inquire_response_must_support_practitioner_test.rb +3 -3
  53. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner/client_submit_request_must_support_practitioner_test.rb +3 -3
  54. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner/client_submit_response_must_support_practitioner_test.rb +3 -3
  55. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner_role/client_inquire_request_must_support_practitioner_role_test.rb +3 -3
  56. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner_role/client_inquire_response_must_support_practitioner_role_test.rb +3 -3
  57. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner_role/client_submit_request_must_support_practitioner_role_test.rb +3 -3
  58. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/practitioner_role/client_submit_response_must_support_practitioner_role_test.rb +3 -3
  59. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/requestor/client_inquire_request_must_support_requestor_test.rb +3 -3
  60. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/requestor/client_inquire_response_must_support_requestor_test.rb +3 -3
  61. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/requestor/client_submit_request_must_support_requestor_test.rb +3 -3
  62. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/requestor/client_submit_response_must_support_requestor_test.rb +3 -3
  63. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/subscriber/client_inquire_request_must_support_subscriber_test.rb +3 -3
  64. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/subscriber/client_submit_request_must_support_subscriber_test.rb +3 -3
  65. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/beneficiary/client_inquire_response_must_support_beneficiary_test.rb +4 -3
  66. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/beneficiary/client_submit_response_must_support_beneficiary_test.rb +4 -3
  67. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/claim_inquiry/client_inquire_request_must_support_claim_inquiry_test.rb +3 -3
  68. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/claim_update/client_submit_request_must_support_claim_update_test.rb +3 -3
  69. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/claiminquiryresponse/client_inquire_response_must_support_claiminquiryresponse_test.rb +4 -3
  70. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/claimresponse/client_submit_response_must_support_claimresponse_test.rb +4 -3
  71. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/communication_request/client_submit_response_must_support_communication_request_test.rb +4 -3
  72. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/insurer/client_inquire_response_must_support_insurer_test.rb +4 -3
  73. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/insurer/client_submit_response_must_support_insurer_test.rb +4 -3
  74. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/pas_client_inquire_must_support_group.rb +8 -6
  75. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/pas_client_inquire_response_must_support_group.rb +9 -31
  76. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/pas_client_submit_must_support_group.rb +13 -9
  77. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/pas_client_submit_response_must_support_group.rb +10 -32
  78. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/pas_inquiry_response_bundle/client_inquire_response_must_support_pas_inquiry_response_bundle_test.rb +4 -3
  79. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/pas_response_bundle/client_submit_response_must_support_pas_response_bundle_test.rb +4 -3
  80. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/practitioner/client_inquire_response_must_support_practitioner_test.rb +4 -3
  81. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/practitioner/client_submit_response_must_support_practitioner_test.rb +4 -3
  82. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/practitioner_role/client_inquire_response_must_support_practitioner_role_test.rb +4 -3
  83. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/practitioner_role/client_submit_response_must_support_practitioner_role_test.rb +4 -3
  84. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/requestor/client_inquire_response_must_support_requestor_test.rb +4 -3
  85. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/requestor/client_submit_response_must_support_requestor_test.rb +4 -3
  86. data/lib/davinci_pas_test_kit/client/jobs/send_pas_subscription_notification.rb +64 -3
  87. data/lib/davinci_pas_test_kit/client/pas_client_subscription_pas_conformance_test.rb +4 -3
  88. data/lib/davinci_pas_test_kit/client/registration/abstract_configuration_other_display_test.rb +6 -4
  89. data/lib/davinci_pas_test_kit/client/registration/abstract_configuration_smart_display_test.rb +8 -4
  90. data/lib/davinci_pas_test_kit/client/registration/abstract_configuration_udap_display_test.rb +9 -4
  91. data/lib/davinci_pas_test_kit/client/registration/abstract_other_auth_attest_test.rb +5 -4
  92. data/lib/davinci_pas_test_kit/client/request_count_gating.rb +14 -0
  93. data/lib/davinci_pas_test_kit/client/response_generator.rb +50 -9
  94. data/lib/davinci_pas_test_kit/client/session_identification.rb +26 -7
  95. data/lib/davinci_pas_test_kit/client/subscription_notification_trigger.rb +80 -0
  96. data/lib/davinci_pas_test_kit/client/subscription_setup_requirement.rb +59 -0
  97. data/lib/davinci_pas_test_kit/client/user_input_response.rb +50 -10
  98. data/lib/davinci_pas_test_kit/client/v2.0.1/client_suite.rb +30 -15
  99. data/lib/davinci_pas_test_kit/client/v2.0.1/must_support/pas_client_must_support_request_profiles_test.rb +2 -2
  100. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_approval_group.rb +8 -7
  101. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_auth_smart_group.rb +4 -4
  102. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_auth_udap_group.rb +4 -4
  103. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_denial_group.rb +8 -7
  104. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_must_support_group.rb +38 -52
  105. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_pended_group.rb +28 -22
  106. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_registration_group.rb +2 -2
  107. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_subscription_setup_group.rb +2 -2
  108. data/lib/davinci_pas_test_kit/client/v2.0.1/pas_client_workflows_group.rb +2 -2
  109. data/lib/davinci_pas_test_kit/client/v2.0.1/workflows/pas_client_inquire_request_bundle_validation_test.rb +15 -21
  110. data/lib/davinci_pas_test_kit/client/v2.0.1/workflows/pas_client_inquire_response_bundle_validation_test.rb +29 -29
  111. data/lib/davinci_pas_test_kit/client/v2.0.1/workflows/pas_client_pended_submit_test.rb +49 -40
  112. data/lib/davinci_pas_test_kit/client/v2.0.1/workflows/pas_client_request_bundle_validation_test.rb +15 -21
  113. data/lib/davinci_pas_test_kit/client/v2.0.1/workflows/pas_client_response_bundle_validation_test.rb +29 -29
  114. data/lib/davinci_pas_test_kit/client/v2.2.1/attestations/attestation_instructions.rb +10 -0
  115. data/lib/davinci_pas_test_kit/client/v2.2.1/attestations/client_data_element_expectations_attestation_test.rb +44 -0
  116. data/lib/davinci_pas_test_kit/client/v2.2.1/attestations/client_provider_review_attestation_test.rb +45 -0
  117. data/lib/davinci_pas_test_kit/client/v2.2.1/attestations/client_security_attestation_test.rb +42 -0
  118. data/lib/davinci_pas_test_kit/client/v2.2.1/attestations/client_subscription_error_recovery_attestation_test.rb +42 -0
  119. data/lib/davinci_pas_test_kit/client/v2.2.1/client_suite.rb +64 -12
  120. data/lib/davinci_pas_test_kit/client/v2.2.1/error_handling/pas_client_operation_failure_submit_test.rb +20 -10
  121. data/lib/davinci_pas_test_kit/client/v2.2.1/error_handling/pas_client_operation_outcome_validation_test.rb +11 -5
  122. data/lib/davinci_pas_test_kit/client/v2.2.1/error_handling/pas_client_processing_error_response_validation_test.rb +21 -12
  123. data/lib/davinci_pas_test_kit/client/v2.2.1/error_handling/pas_client_processing_error_submit_test.rb +19 -8
  124. data/lib/davinci_pas_test_kit/client/v2.2.1/must_support/must_support_with_attestation_option.rb +90 -40
  125. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_approval_group.rb +12 -11
  126. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_attestations_group.rb +32 -0
  127. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_auth_smart_group.rb +4 -4
  128. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_auth_udap_group.rb +4 -4
  129. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_claim_updates_group.rb +24 -6
  130. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_denial_group.rb +12 -10
  131. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_modification_group.rb +13 -14
  132. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_must_support_group.rb +55 -59
  133. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_operation_failure_group.rb +8 -5
  134. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_pended_group.rb +53 -41
  135. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_processing_error_group.rb +9 -7
  136. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_registration_group.rb +2 -2
  137. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_subscription_setup_group.rb +9 -4
  138. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_subscription_update_delete_group.rb +57 -0
  139. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_workflows_group.rb +22 -2
  140. data/lib/davinci_pas_test_kit/client/v2.2.1/subscription/pas_client_subscription_create_test.rb +2 -1
  141. data/lib/davinci_pas_test_kit/client/v2.2.1/subscription/pas_client_subscription_put_update_pas_conformance_test.rb +49 -0
  142. data/lib/davinci_pas_test_kit/client/v2.2.1/subscription/pas_client_subscription_update_delete_test.rb +12 -0
  143. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_referenced_resources_test.rb +5 -6
  144. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_claim_update_submit_tests.rb +82 -56
  145. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_inquire_request_bundle_validation_test.rb +17 -20
  146. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_inquire_response_bundle_validation_test.rb +59 -27
  147. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_modification_verification_test.rb +0 -3
  148. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_pended_send_notification_test.rb +138 -0
  149. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_pended_submit_test.rb +32 -85
  150. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_request_bundle_validation_test.rb +16 -21
  151. data/lib/davinci_pas_test_kit/client/v2.2.1/workflows/pas_client_response_bundle_validation_test.rb +27 -27
  152. data/lib/davinci_pas_test_kit/cross_suite/fhirpath_utils.rb +13 -3
  153. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/beneficiary/metadata.yml +4 -0
  154. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/claim_inquiry/metadata.yml +15 -0
  155. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/claim_update/metadata.yml +17 -0
  156. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/claiminquiryresponse/metadata.yml +15 -0
  157. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/claimresponse/metadata.yml +15 -0
  158. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/communication_request/metadata.yml +4 -0
  159. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/coverage/metadata.yml +1 -0
  160. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/device_request/metadata.yml +2 -0
  161. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/encounter/metadata.yml +3 -0
  162. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/insurer/metadata.yml +1 -0
  163. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/medication_request/metadata.yml +2 -0
  164. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/metadata.yml +143 -0
  165. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/nutrition_order/metadata.yml +2 -0
  166. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/pas_inquiry_request_bundle/metadata.yml +2 -0
  167. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/pas_inquiry_response_bundle/metadata.yml +2 -0
  168. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/pas_request_bundle/metadata.yml +2 -0
  169. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/pas_response_bundle/metadata.yml +2 -0
  170. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/practitioner/metadata.yml +1 -0
  171. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/practitioner_role/metadata.yml +1 -0
  172. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/requestor/metadata.yml +1 -0
  173. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/service_request/metadata.yml +3 -0
  174. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/subscriber/metadata.yml +4 -0
  175. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.0.1/task/metadata.yml +1 -4
  176. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/beneficiary/metadata.yml +1 -0
  177. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/claim_inquiry/metadata.yml +23 -0
  178. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/claim_update/metadata.yml +31 -0
  179. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/claiminquiryresponse/metadata.yml +42 -0
  180. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/claimresponse/metadata.yml +42 -0
  181. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/communication_request/metadata.yml +4 -0
  182. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/coverage/metadata.yml +1 -0
  183. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/device_request/metadata.yml +2 -0
  184. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/encounter/metadata.yml +3 -0
  185. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/insurer/metadata.yml +1 -0
  186. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/medication_request/metadata.yml +2 -0
  187. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/metadata.yml +265 -1
  188. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/nutrition_order/metadata.yml +2 -0
  189. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/pas_inquiry_request_bundle/metadata.yml +2 -0
  190. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/pas_inquiry_response_bundle/metadata.yml +2 -0
  191. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/pas_request_bundle/metadata.yml +2 -0
  192. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/pas_response_bundle/metadata.yml +2 -0
  193. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/practitioner/metadata.yml +1 -0
  194. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/practitioner_role/metadata.yml +1 -0
  195. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/requestor/metadata.yml +1 -0
  196. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/service_request/metadata.yml +3 -0
  197. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/subscriber/metadata.yml +1 -0
  198. data/lib/davinci_pas_test_kit/cross_suite/generated/v2.2.1/task/metadata.yml +4 -4
  199. data/lib/davinci_pas_test_kit/cross_suite/pas_bundle_validation.rb +570 -312
  200. data/lib/davinci_pas_test_kit/cross_suite/pas_constants.rb +13 -0
  201. data/lib/davinci_pas_test_kit/cross_suite/pas_datatype_constraints.rb +166 -0
  202. data/lib/davinci_pas_test_kit/cross_suite/pas_subscription_verification.rb +8 -4
  203. data/lib/davinci_pas_test_kit/cross_suite/referenced_resource_presence_validation.rb +141 -0
  204. data/lib/davinci_pas_test_kit/cross_suite/response_selection_utils.rb +39 -21
  205. data/lib/davinci_pas_test_kit/cross_suite/tags.rb +11 -6
  206. data/lib/davinci_pas_test_kit/cross_suite/validator_configuration.rb +16 -0
  207. data/lib/davinci_pas_test_kit/cross_suite/validator_suppressions.rb +210 -146
  208. data/lib/davinci_pas_test_kit/docs/demo/PAS Client Suite Demonstration.postman_collection.json +7 -7
  209. data/lib/davinci_pas_test_kit/ext/inferno_core/runnable.rb +1 -0
  210. data/lib/davinci_pas_test_kit/generator/client_must_support_group_generator.rb +47 -16
  211. data/lib/davinci_pas_test_kit/generator/must_support_target_profiles.rb +17 -0
  212. data/lib/davinci_pas_test_kit/generator/must_support_test_generator.rb +14 -2
  213. data/lib/davinci_pas_test_kit/generator/pas_must_support_metadata_extractor.rb +1 -2
  214. data/lib/davinci_pas_test_kit/generator/profile_metadata.rb +1 -1
  215. data/lib/davinci_pas_test_kit/generator/server_must_support_group_generator.rb +23 -0
  216. data/lib/davinci_pas_test_kit/generator/server_suite_generator.rb +8 -7
  217. data/lib/davinci_pas_test_kit/generator/templates/client_must_support_group.rb.erb +22 -3
  218. data/lib/davinci_pas_test_kit/generator/templates/client_must_support_response_group.rb.erb +7 -16
  219. data/lib/davinci_pas_test_kit/generator/templates/must_support.rb.erb +11 -10
  220. data/lib/davinci_pas_test_kit/generator/templates/server_must_support_group.rb.erb +22 -0
  221. data/lib/davinci_pas_test_kit/generator/templates/server_suite.rb.erb +9 -11
  222. data/lib/davinci_pas_test_kit/generator/templates/use_case_group.rb.erb +3 -0
  223. data/lib/davinci_pas_test_kit/generator/use_case_group_generator.rb +19 -13
  224. data/lib/davinci_pas_test_kit/parameters_helper.rb +13 -5
  225. data/lib/davinci_pas_test_kit/pas_notification_conformance_test.rb +2 -2
  226. data/lib/davinci_pas_test_kit/requirements/davinci_pas_test_kit_requirements.csv +14 -7
  227. data/lib/davinci_pas_test_kit/requirements/generated/davinci_pas_client_suite_v201_requirements_coverage.csv +21 -21
  228. data/lib/davinci_pas_test_kit/requirements/generated/davinci_pas_client_suite_v221_requirements_coverage.csv +30 -26
  229. data/lib/davinci_pas_test_kit/requirements/generated/davinci_pas_server_suite_v221_requirements_coverage.csv +39 -33
  230. data/lib/davinci_pas_test_kit/requirements/hl7.fhir.us.davinci-pas_2.2.1_requirements.xlsx +0 -0
  231. data/lib/davinci_pas_test_kit/server/claim_operation_logic.rb +1 -1
  232. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/beneficiary/server_inquire_request_must_support_beneficiary_test.rb +1 -1
  233. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/beneficiary/server_inquire_response_must_support_beneficiary_test.rb +1 -1
  234. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/beneficiary/server_submit_request_must_support_beneficiary_test.rb +1 -1
  235. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/beneficiary/server_submit_response_must_support_beneficiary_test.rb +1 -1
  236. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/claim_inquiry/server_inquire_request_must_support_claim_inquiry_test.rb +1 -1
  237. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/claim_update/server_submit_request_must_support_claim_update_test.rb +1 -1
  238. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/claiminquiryresponse/server_inquire_response_must_support_claiminquiryresponse_test.rb +1 -1
  239. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/claimresponse/server_submit_response_must_support_claimresponse_test.rb +1 -1
  240. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/communication_request/server_submit_response_must_support_communication_request_test.rb +1 -1
  241. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/coverage/server_inquire_request_must_support_coverage_test.rb +1 -1
  242. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/coverage/server_submit_request_must_support_coverage_test.rb +1 -1
  243. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/encounter/server_submit_request_must_support_encounter_test.rb +1 -1
  244. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/insurer/server_inquire_request_must_support_insurer_test.rb +1 -1
  245. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/insurer/server_inquire_response_must_support_insurer_test.rb +1 -1
  246. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/insurer/server_submit_request_must_support_insurer_test.rb +1 -1
  247. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/insurer/server_submit_response_must_support_insurer_test.rb +1 -1
  248. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/pas_inquiry_request_bundle/server_inquire_request_must_support_pas_inquiry_request_bundle_test.rb +1 -1
  249. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/pas_inquiry_response_bundle/server_inquire_response_must_support_pas_inquiry_response_bundle_test.rb +1 -1
  250. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/pas_request_bundle/server_submit_request_must_support_pas_request_bundle_test.rb +1 -1
  251. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/pas_response_bundle/server_submit_response_must_support_pas_response_bundle_test.rb +1 -1
  252. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/pas_server_approval_use_case_group.rb +4 -3
  253. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/pas_server_denial_use_case_group.rb +4 -3
  254. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/pas_server_pended_use_case_group.rb +7 -6
  255. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner/server_inquire_request_must_support_practitioner_test.rb +1 -1
  256. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner/server_inquire_response_must_support_practitioner_test.rb +1 -1
  257. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner/server_submit_request_must_support_practitioner_test.rb +1 -1
  258. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner/server_submit_response_must_support_practitioner_test.rb +1 -1
  259. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner_role/server_inquire_request_must_support_practitioner_role_test.rb +1 -1
  260. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner_role/server_inquire_response_must_support_practitioner_role_test.rb +1 -1
  261. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner_role/server_submit_request_must_support_practitioner_role_test.rb +1 -1
  262. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/practitioner_role/server_submit_response_must_support_practitioner_role_test.rb +1 -1
  263. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/requestor/server_inquire_request_must_support_requestor_test.rb +1 -1
  264. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/requestor/server_inquire_response_must_support_requestor_test.rb +1 -1
  265. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/requestor/server_submit_request_must_support_requestor_test.rb +1 -1
  266. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/requestor/server_submit_response_must_support_requestor_test.rb +1 -1
  267. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/server_suite.rb +9 -11
  268. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/subscriber/server_inquire_request_must_support_subscriber_test.rb +1 -1
  269. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/subscriber/server_submit_request_must_support_subscriber_test.rb +1 -1
  270. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/task/server_inquire_response_must_support_task_test.rb +1 -1
  271. data/lib/davinci_pas_test_kit/server/generated/v2.0.1/task/server_submit_response_must_support_task_test.rb +1 -1
  272. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/beneficiary/server_inquire_request_must_support_beneficiary_test.rb +1 -1
  273. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/beneficiary/server_inquire_response_must_support_beneficiary_test.rb +1 -1
  274. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/beneficiary/server_submit_request_must_support_beneficiary_test.rb +1 -1
  275. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/beneficiary/server_submit_response_must_support_beneficiary_test.rb +1 -1
  276. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/claim_inquiry/server_inquire_request_must_support_claim_inquiry_test.rb +1 -1
  277. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/claim_update/server_submit_request_must_support_claim_update_test.rb +1 -1
  278. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/claiminquiryresponse/server_inquire_response_must_support_claiminquiryresponse_test.rb +3 -1
  279. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/claimresponse/server_submit_response_must_support_claimresponse_test.rb +3 -1
  280. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/communication_request/server_submit_response_must_support_communication_request_test.rb +1 -1
  281. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/coverage/server_inquire_request_must_support_coverage_test.rb +1 -1
  282. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/coverage/server_submit_request_must_support_coverage_test.rb +1 -1
  283. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/encounter/server_submit_request_must_support_encounter_test.rb +1 -1
  284. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/insurer/server_inquire_request_must_support_insurer_test.rb +1 -1
  285. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/insurer/server_inquire_response_must_support_insurer_test.rb +1 -1
  286. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/insurer/server_submit_request_must_support_insurer_test.rb +1 -1
  287. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/insurer/server_submit_response_must_support_insurer_test.rb +1 -1
  288. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_inquiry_request_bundle/server_inquire_request_must_support_pas_inquiry_request_bundle_test.rb +1 -1
  289. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_inquiry_response_bundle/server_inquire_response_must_support_pas_inquiry_response_bundle_test.rb +1 -1
  290. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_request_bundle/server_submit_request_must_support_pas_request_bundle_test.rb +1 -1
  291. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_response_bundle/server_submit_response_must_support_pas_response_bundle_test.rb +1 -1
  292. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_server_approval_use_case_group.rb +5 -3
  293. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_server_denial_use_case_group.rb +5 -3
  294. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_server_must_support_group.rb +12 -0
  295. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/pas_server_pended_use_case_group.rb +9 -4
  296. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner/server_inquire_request_must_support_practitioner_test.rb +1 -1
  297. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner/server_inquire_response_must_support_practitioner_test.rb +1 -1
  298. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner/server_submit_request_must_support_practitioner_test.rb +1 -1
  299. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner/server_submit_response_must_support_practitioner_test.rb +1 -1
  300. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner_role/server_inquire_request_must_support_practitioner_role_test.rb +1 -1
  301. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner_role/server_inquire_response_must_support_practitioner_role_test.rb +1 -1
  302. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner_role/server_submit_request_must_support_practitioner_role_test.rb +1 -1
  303. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/practitioner_role/server_submit_response_must_support_practitioner_role_test.rb +1 -1
  304. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/requestor/server_inquire_request_must_support_requestor_test.rb +1 -1
  305. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/requestor/server_inquire_response_must_support_requestor_test.rb +1 -1
  306. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/requestor/server_submit_request_must_support_requestor_test.rb +1 -1
  307. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/requestor/server_submit_response_must_support_requestor_test.rb +1 -1
  308. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/server_suite.rb +9 -11
  309. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/subscriber/server_inquire_request_must_support_subscriber_test.rb +1 -1
  310. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/subscriber/server_submit_request_must_support_subscriber_test.rb +1 -1
  311. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/task/server_inquire_response_must_support_task_test.rb +1 -1
  312. data/lib/davinci_pas_test_kit/server/generated/v2.2.1/task/server_submit_response_must_support_task_test.rb +1 -1
  313. data/lib/davinci_pas_test_kit/server/pas_claim_response_decision_test.rb +2 -2
  314. data/lib/davinci_pas_test_kit/server/server_request_bundle_validation_test.rb +4 -6
  315. data/lib/davinci_pas_test_kit/server/server_response_bundle_validation_test.rb +17 -10
  316. data/lib/davinci_pas_test_kit/server/v2.0.1/must_support/pas_server_must_support_request_profiles_test.rb +1 -1
  317. data/lib/davinci_pas_test_kit/server/v2.0.1/pas_server_subscription_setup.rb +1 -1
  318. data/lib/davinci_pas_test_kit/server/v2.2.1/claim_inquiry/server_claim_inquiry_response_tests.rb +89 -0
  319. data/lib/davinci_pas_test_kit/server/v2.2.1/must_support/pas_server_must_support_request_profiles_test.rb +1 -1
  320. data/lib/davinci_pas_test_kit/server/v2.2.1/pas_error_group.rb +2 -0
  321. data/lib/davinci_pas_test_kit/server/v2.2.1/pas_server_claim_inquiry_responses_group.rb +17 -0
  322. data/lib/davinci_pas_test_kit/server/v2.2.1/pas_server_claim_updates_group.rb +1 -1
  323. data/lib/davinci_pas_test_kit/server/v2.2.1/pas_server_no_custom_extensions_test.rb +241 -0
  324. data/lib/davinci_pas_test_kit/server/v2.2.1/pas_server_subscription_capability_statement_test.rb +40 -0
  325. data/lib/davinci_pas_test_kit/server/v2.2.1/pas_server_subscription_setup.rb +15 -10
  326. data/lib/davinci_pas_test_kit/server/v2.2.1/server_replaced_pa_inquire_reference_input_test.rb +63 -0
  327. data/lib/davinci_pas_test_kit/server/v2.2.1/server_replaced_pa_inquire_reference_test.rb +109 -0
  328. data/lib/davinci_pas_test_kit/version.rb +2 -2
  329. metadata +38 -13
  330. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/task/client_inquire_response_must_support_task_test.rb +0 -45
  331. data/lib/davinci_pas_test_kit/client/generated/v2.0.1/task/client_submit_response_must_support_task_test.rb +0 -45
  332. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/task/client_inquire_response_must_support_task_test.rb +0 -55
  333. data/lib/davinci_pas_test_kit/client/generated/v2.2.1/task/client_submit_response_must_support_task_test.rb +0 -55
  334. data/lib/davinci_pas_test_kit/client/v2.2.1/pas_client_error_handling_group.rb +0 -20
@@ -3,151 +3,297 @@
3
3
  require_relative '../parameters_helper'
4
4
  require_relative 'validation_test'
5
5
  require_relative 'pas_constants'
6
+ require_relative 'pas_datatype_constraints'
7
+ require_relative 'referenced_resource_presence_validation'
8
+ require_relative 'validator_configuration'
6
9
 
7
10
  module DaVinciPASTestKit
8
11
  module PasBundleValidation
9
12
  include DaVinciPASTestKit::ValidationTest
13
+ include DaVinciPASTestKit::PasDatatypeConstraints
14
+ include DaVinciPASTestKit::ReferencedResourcePresenceValidation
10
15
  include ParametersHelper
11
16
 
12
- US_CORE_VERSION = '6.1.0'
13
- US_CORE_PROFILE_BASE = 'http://hl7.org/fhir/us/core/StructureDefinition'
14
- BASE_R4_PROFILE = :base_r4
15
- CLAIM_ENCOUNTER_EXTENSION_URL = 'http://hl7.org/fhir/5.0/StructureDefinition/extension-Claim.encounter'
16
- LOINC_SYSTEM = 'http://loinc.org'
17
- TERMINOLOGY_CONDITION_CATEGORY_SYSTEM = 'http://terminology.hl7.org/CodeSystem/condition-category'
18
- OBSERVATION_CATEGORY_SYSTEM = 'http://terminology.hl7.org/CodeSystem/observation-category'
19
- DIAGNOSTIC_REPORT_CATEGORY_SYSTEM = 'http://terminology.hl7.org/CodeSystem/v2-0074'
20
-
21
- US_CORE_SINGLE_PROFILE_IDS_BY_RESOURCE = {
22
- 'AllergyIntolerance' => 'us-core-allergyintolerance',
23
- 'CarePlan' => 'us-core-careplan',
24
- 'CareTeam' => 'us-core-careteam',
25
- 'Coverage' => 'us-core-coverage',
26
- 'Device' => 'us-core-implantable-device',
27
- 'DocumentReference' => 'us-core-documentreference',
28
- 'Encounter' => 'us-core-encounter',
29
- 'Goal' => 'us-core-goal',
30
- 'Immunization' => 'us-core-immunization',
31
- 'Location' => 'us-core-location',
32
- 'Medication' => 'us-core-medication',
33
- 'MedicationDispense' => 'us-core-medicationdispense',
34
- 'MedicationRequest' => 'us-core-medicationrequest',
35
- 'Organization' => 'us-core-organization',
36
- 'Patient' => 'us-core-patient',
37
- 'Practitioner' => 'us-core-practitioner',
38
- 'PractitionerRole' => 'us-core-practitionerrole',
39
- 'Procedure' => 'us-core-procedure',
40
- 'Provenance' => 'us-core-provenance',
41
- 'QuestionnaireResponse' => 'us-core-questionnaireresponse',
42
- 'RelatedPerson' => 'us-core-relatedperson',
43
- 'ServiceRequest' => 'us-core-servicerequest',
44
- 'Specimen' => 'us-core-specimen'
45
- }.freeze
46
-
47
- US_CORE_CONDITION_ENCOUNTER_DIAGNOSIS_PROFILE_ID = 'us-core-condition-encounter-diagnosis'
48
- US_CORE_CONDITION_PROBLEMS_HEALTH_CONCERNS_PROFILE_ID = 'us-core-condition-problems-health-concerns'
49
- US_CORE_DIAGNOSTIC_REPORT_LAB_PROFILE_ID = 'us-core-diagnosticreport-lab'
50
- US_CORE_DIAGNOSTIC_REPORT_NOTE_PROFILE_ID = 'us-core-diagnosticreport-note'
51
- US_CORE_OBSERVATION_CLINICAL_RESULT_PROFILE_ID = 'us-core-observation-clinical-result'
52
- US_CORE_OBSERVATION_LAB_PROFILE_ID = 'us-core-observation-lab'
53
- US_CORE_OBSERVATION_SCREENING_ASSESSMENT_PROFILE_ID = 'us-core-observation-screening-assessment'
54
- US_CORE_SIMPLE_OBSERVATION_PROFILE_ID = 'us-core-simple-observation'
55
- US_CORE_SMOKING_STATUS_PROFILE_ID = 'us-core-smokingstatus'
56
- US_CORE_VITAL_SIGNS_PROFILE_ID = 'us-core-vital-signs'
57
-
58
- US_CORE_OBSERVATION_CODE_PROFILE_IDS = {
59
- '11341-5' => 'us-core-observation-occupation',
60
- '86645-9' => 'us-core-observation-pregnancyintent',
61
- '82810-3' => 'us-core-observation-pregnancystatus',
62
- '76690-7' => 'us-core-observation-sexual-orientation',
63
- '8289-1' => 'head-occipital-frontal-circumference-percentile',
64
- '59576-9' => 'pediatric-bmi-for-age',
65
- '77606-2' => 'pediatric-weight-for-height',
66
- '85354-9' => 'us-core-blood-pressure',
67
- '39156-5' => 'us-core-bmi',
68
- '8302-2' => 'us-core-body-height',
69
- '8310-5' => 'us-core-body-temperature',
70
- '29463-7' => 'us-core-body-weight',
71
- '9843-4' => 'us-core-head-circumference',
72
- '8867-4' => 'us-core-heart-rate',
73
- '59408-5' => 'us-core-pulse-oximetry',
74
- '2708-6' => 'us-core-pulse-oximetry',
75
- '9279-1' => 'us-core-respiratory-rate',
76
- '72166-2' => 'us-core-smokingstatus'
77
- }.freeze
78
-
79
- def validation_error_messages
80
- @validation_error_messages ||= []
81
- end
82
-
83
- def perform_bundle_validation(bundle, operation, type, ig_version)
17
+ ###########################################################################
18
+ # Public API
19
+ ###########################################################################
20
+
21
+ # @return [Array<Hash>] The messages (of every severity) found while validating this
22
+ # bundle, each a { type:, message: } hash. Callers that only care about failure should
23
+ # check for a message with type: 'error' rather than just whether this is present -
24
+ # see validation_messages.
25
+ # @param validator [Symbol] the name of the FHIR resource validator to use for profile
26
+ # validation. Use perform_no_custom_extensions_validation for Bundles Inferno sends.
27
+ # @param echoed_from [FHIR::Bundle, String, nil] the request Bundle the response answers. When
28
+ # given, entry resources echoed back from it that carry no extensions beyond those they had in
29
+ # it are validated with the :default validator rather than `validator`, since the extensions
30
+ # came from the system under test.
31
+ def perform_bundle_validation(bundle, operation, type, ig_version, request_bundle = nil, validator: :default,
32
+ echoed_from: nil)
33
+ @validation_messages = []
34
+ @profile_validator = validator
35
+ @echoed_from_bundle = parse_bundle(echoed_from)
84
36
  target_profile = PASConstants.bundle_profile_url_for_operation_and_type(operation, type)
85
37
  request_type = "#{operation}_#{type}"
86
38
  if type == 'request'
87
39
  perform_request_validation(bundle, target_profile, ig_version.delete_prefix('v'), request_type)
88
40
  else
89
- perform_response_validation(bundle, target_profile, ig_version.delete_prefix('v'), request_type)
41
+ perform_response_validation(bundle, target_profile, ig_version.delete_prefix('v'), request_type, request_bundle)
90
42
  end
43
+ validation_messages
91
44
  end
92
45
 
93
- def perform_request_validation(bundle, profile_url, version, request_type)
94
- validate_pa_request_payload_structure(bundle, request_type)
95
- validate_resources_conformance_against_profile(bundle, profile_url, version, request_type)
46
+ # Validates a Bundle that Inferno sends to the system under test, which may be tester-provided,
47
+ # using NO_CUSTOM_EXTENSIONS_VALIDATOR so that custom extensions aren't allowed. Resources echoed
48
+ # back from the system under test's Bundle may keep the extensions they had in it.
49
+ # @param type [String] 'request' or 'response' - see perform_bundle_validation.
50
+ # @param echoed_from [FHIR::Bundle, String, nil] the Bundle from the system under test that this
51
+ # one answers, e.g. the request for a response, or nil if there is none - see
52
+ # perform_bundle_validation.
53
+ # @return [Array<Hash>] see perform_bundle_validation.
54
+ def perform_no_custom_extensions_validation(bundle, operation, type, ig_version, echoed_from:)
55
+ perform_bundle_validation(bundle, operation, type, ig_version,
56
+ validator: NO_CUSTOM_EXTENSIONS_VALIDATOR, echoed_from:)
96
57
  end
97
58
 
98
- def perform_response_validation(response_bundle, profile_url, version, request_type, request_bundle = nil)
99
- validate_pa_response_body_structure(response_bundle, request_bundle) if request_type == 'submit'
100
- validate_resources_conformance_against_profile(response_bundle, profile_url, version, request_type)
59
+ # Validates a Parameters resource that Inferno sends to wrap Bundles (e.g., a v2.2.1 $inquire
60
+ # response) against the base Parameters definition, since PAS doesn't profile it, using
61
+ # NO_CUSTOM_EXTENSIONS_VALIDATOR. Issues within the wrapped resources are dropped, since those are
62
+ # validated separately - see perform_no_custom_extensions_validation.
63
+ # @param parameters [FHIR::Parameters]
64
+ # @return [Array<Hash>] the messages found, each a { type:, message: } hash.
65
+ def perform_no_custom_extensions_parameters_validation(parameters)
66
+ response_details = []
67
+ resource_is_valid?(resource: parameters, validator: NO_CUSTOM_EXTENSIONS_VALIDATOR,
68
+ add_messages_to_runnable: false,
69
+ validator_response_details: response_details)
70
+ validator_issues_to_messages(response_details.reject do |issue|
71
+ issue.filtered || issue.location&.match?(/\AParameters\.parameter\[\d+\](\.part\[\d+\])*\.resource/)
72
+ end)
101
73
  end
102
74
 
103
- def validate_pas_bundle_json(json, profile_url, version, request_type, bundle_type, skips: false, message: '')
104
- assert_valid_json(json)
105
- resource = FHIR.from_contents(json)
106
- assert resource.present?, 'Not a FHIR resource'
75
+ ###########################################################################
76
+ # Internal Validation Methods
77
+ ###########################################################################
78
+
79
+ # Every message (of every severity, not just errors) collected while validating the
80
+ # current bundle. Passing-profile messages and per-issue profile-conformance messages
81
+ # (see validate_bundle_entries_against_profiles) may be warnings or info, not just
82
+ # errors, which is why this isn't error-only despite most entries being added via
83
+ # add_validation_error.
84
+ def validation_messages
85
+ @validation_messages ||= []
86
+ end
107
87
 
108
- # For v2.2.1 inquire responses, expect Parameters resource
109
- if version == '2.2.1' && request_type == 'inquire' && bundle_type == 'response_bundle'
110
- if resource.resourceType == 'Parameters'
111
- # Extract and validate each Bundle in the Parameters
112
- bundles = extract_bundles_from_pas_inquiry_response_parameters(resource)
88
+ # Appends a plain error string to validation_messages as a { type:, message: } hash,
89
+ # for the many checks in this file that only ever produce errors (as opposed to
90
+ # validate_bundle_entries_against_profiles, which already has typed messages from the
91
+ # validator and concats them directly).
92
+ def add_validation_error(message)
93
+ validation_messages << { type: 'error', message: }
94
+ end
113
95
 
114
- bundles.each do |bundle|
115
- perform_response_validation(bundle, profile_url, version, request_type)
116
- end
117
- elsif resource.is_a?(FHIR::Bundle)
118
- # Bundle received instead of Parameters - validate it but log an error
119
- validation_error_messages << 'Expected Parameters resource for v2.2.1 inquire response, but received ' \
120
- 'Bundle. The response Bundle should be wrapped in a ' \
121
- 'Parameters resource with a return parameter.'
122
- perform_response_validation(resource, profile_url, version, request_type)
123
- else
124
- assert false,
125
- "Expected Parameters resource for v2.2.1 inquire response, but received #{resource.resourceType}"
96
+ # The name of the FHIR resource validator used for profile validation of the current
97
+ # bundle - see perform_bundle_validation.
98
+ def profile_validator
99
+ @profile_validator || :default
100
+ end
101
+
102
+ # How to validate one entry resource in the current bundle. Resources echoed back from the request
103
+ # (see perform_bundle_validation's echoed_from) may keep the extensions they had in it: those that
104
+ # add no extensions of their own use the :default validator, and the others use the specified
105
+ # validator with the issues it reports about their extensions explained or dropped - see
106
+ # echoed_extension_issue_messages.
107
+ # @param full_url [String] the fullUrl of the entry containing the resource.
108
+ # @param resource [FHIR::Model] the entry resource.
109
+ # @return [Array(Symbol, Array, FHIR::Bundle::Entry)] the name of the validator to use, the
110
+ # [element path, extension] pairs (see extensions_in) of the request resource the resource echoes,
111
+ # and the request entry it echoes. The last two are nil if it doesn't echo one (or the validator is
112
+ # :default, which allows any extension).
113
+ def entry_validation_settings(full_url, resource)
114
+ return [profile_validator, nil, nil] if profile_validator == :default
115
+
116
+ request_entry = echoed_request_entry(full_url, resource)
117
+ return [profile_validator, nil, nil] if request_entry.blank?
118
+
119
+ echoed_extensions = extensions_in(validated_hash(request_entry.resource))
120
+ all_echoed = extensions_in(validated_hash(resource)).all? { |extension| echoed_extensions.include?(extension) }
121
+ [all_echoed ? :default : profile_validator, echoed_extensions, request_entry]
122
+ end
123
+
124
+ def echoed_request_entry(full_url, resource)
125
+ return if @echoed_from_bundle.blank?
126
+
127
+ response_entry = FHIR::Bundle::Entry.new(fullUrl: full_url, resource:)
128
+ @echoed_from_bundle.entry.find { |entry| echoed_resource?(entry, response_entry) }
129
+ end
130
+
131
+ # The resource as the validator receives it (Inferno sends its source_contents), as a hash. Unlike
132
+ # to_hash, this keeps the extensions on primitive elements (e.g., `_birthDate`).
133
+ def validated_hash(resource)
134
+ JSON.parse(resource.source_contents)
135
+ rescue StandardError
136
+ resource.to_hash
137
+ end
138
+
139
+ # An extension is echoed if it appears, with the same content and at the same element path, in the
140
+ # request resource. Paths leave out array indexes so reordered repeating elements still match.
141
+ # Reference values are ignored because Inferno rewrites them when it echoes resources in responses
142
+ # it generates.
143
+ # @return [Array<Array(String, Hash)>] [element path, extension] pairs for every extension (at any
144
+ # depth, including modifierExtensions) in the value.
145
+ def extensions_in(value, path = '')
146
+ case value
147
+ when Hash
148
+ value.flat_map do |key, child|
149
+ child_path = path.empty? ? key : "#{path}.#{key}"
150
+ own = if %w[extension modifierExtension].include?(key)
151
+ Array(child).map { |extension| [child_path, without_references(extension)] }
152
+ else
153
+ []
154
+ end
155
+ own + extensions_in(child, child_path)
126
156
  end
157
+ when Array
158
+ value.flat_map { |child| extensions_in(child, path) }
127
159
  else
128
- # For v2.0.1 or non-inquire operations, expect Bundle resource
129
- assert_resource_type(:bundle, resource: resource)
130
- bundle = resource
160
+ []
161
+ end
162
+ end
131
163
 
132
- if bundle_type == 'request_bundle'
133
- perform_request_validation(bundle, profile_url, version, request_type)
134
- else
135
- perform_response_validation(bundle, profile_url, version, request_type)
136
- end
164
+ def without_references(value)
165
+ case value
166
+ when Hash
167
+ value.except('reference').transform_values { |child| without_references(child) }
168
+ when Array
169
+ value.map { |child| without_references(child) }
170
+ else
171
+ value
172
+ end
173
+ end
174
+
175
+ # An info message identifying an echoed resource, which of its identifying fields matched the request
176
+ # entry it echoes (see echoed_resource?), and the extensions it is allowed to keep because they are
177
+ # unchanged from the request - see entry_validation_settings.
178
+ def echoed_resource_message(full_url, resource, echoed_extensions, request_entry)
179
+ kept_urls = extensions_in(validated_hash(resource))
180
+ .select { |extension| echoed_extensions.include?(extension) }
181
+ .map { |_path, extension| extension['url'] }.uniq
182
+ message = "#{resource.resourceType} #{full_url} echoes the #{resource.resourceType} in the request " \
183
+ "(#{echo_match_details(full_url, resource, request_entry)})"
184
+ message += if kept_urls.present?
185
+ ", so these extensions unchanged from the request are allowed in it: #{kept_urls.join(', ')}."
186
+ else
187
+ '.'
188
+ end
189
+ { type: 'info', message: }
190
+ end
191
+
192
+ # Which identifying fields of an echoed resource match the request entry it echoes and which differ,
193
+ # e.g., "matched by id; fullUrl differs". Fields blank in both are left out.
194
+ def echo_match_details(full_url, resource, request_entry)
195
+ fields = {
196
+ 'fullUrl' => [request_entry.fullUrl, full_url],
197
+ 'id' => [request_entry.resource.id, resource.id],
198
+ 'identifiers' => [identifiers_of(request_entry.resource), identifiers_of(resource)]
199
+ }.reject { |_name, values| values.all?(&:blank?) }
200
+ matched, differing = fields.keys.partition do |name|
201
+ request_value, response_value = fields[name]
202
+ request_value == response_value
203
+ end
204
+ details = "matched by #{matched.to_sentence}"
205
+ details += "; #{differing.to_sentence} #{differing.one? ? 'differs' : 'differ'}" if differing.present?
206
+ details
207
+ end
208
+
209
+ # Messages for the validator issues about an echoed resource. An issue that an extension isn't allowed
210
+ # (see unknown_extension_issue?) is:
211
+ # - dropped if the extension is echoed, i.e., in echoed_extensions.
212
+ # - reworded otherwise, to say whether the request resource has an extension with that url at the
213
+ # same element, but with different content, or doesn't.
214
+ # Other issues are kept as is.
215
+ # @param resource_hash [Hash] the validated resource, as a hash.
216
+ def echoed_extension_issue_messages(issues, resource_hash, echoed_extensions)
217
+ issues.filter_map do |issue|
218
+ path, extension = extension_at_location(resource_hash, issue.location)
219
+ url = extension&.dig('url')
220
+ next validator_issue_message(issue) unless unknown_extension_issue?(issue, url)
221
+ next if echoed_extensions.include?([path, without_references(extension)])
222
+
223
+ details =
224
+ if echoed_extensions.any? { |echoed_path, echoed| echoed_path == path && echoed['url'] == url }
225
+ "echoed extension #{url} contained a different value than in the source resource."
226
+ else
227
+ "extension #{url} in an echoed resource is not in the source resource at this element."
228
+ end
229
+ validator_issue_message(issue, details:)
137
230
  end
231
+ end
232
+
233
+ # True if the issue is the validator's error for an extension it doesn't know, so doesn't allow
234
+ # (Extension_EXT_Unknown_NotHere), as the strict validator reports for custom extensions. Other
235
+ # issues about extensions, e.g., known extensions with invalid values, are reported as is.
236
+ def unknown_extension_issue?(issue, url)
237
+ url.present? && issue.raw_issue['message'] == "The extension #{url} could not be found so is not allowed here"
238
+ end
138
239
 
139
- validation_error_messages.each do |msg|
140
- messages << { type: 'error', message: msg }
240
+ # True if the issue is the :default validator's note that an echoed extension is unknown
241
+ # (Extension_EXT_Unknown) - see echoed_resource_message.
242
+ def echoed_unknown_extension_note?(issue, resource_hash, echoed_extensions)
243
+ path, extension = extension_at_location(resource_hash, issue.location)
244
+ url = extension&.dig('url')
245
+ url.present? && issue.raw_issue['message'] == "Unknown extension #{url}" &&
246
+ echoed_extensions.include?([path, without_references(extension)])
247
+ end
248
+
249
+ # Resolves a validator issue location such as `Patient.name[0].extension[1]` against the resource.
250
+ # Extensions on primitive elements are located by the element's name (e.g.,
251
+ # `Patient.birthDate.extension[0]`) but held in its `_` sibling (e.g., `_birthDate`) in JSON.
252
+ # @return [Array(String, Hash), nil] the extension's index-free element path (as in extensions_in)
253
+ # and the extension, or nil if the location isn't an extension in the resource.
254
+ def extension_at_location(resource_hash, location)
255
+ value = resource_hash
256
+ names = location.to_s.split('.').drop(1).map do |segment|
257
+ name, index = segment.match(/\A(\w+)(?:\[(\d+)\])?\z/)&.captures
258
+ return nil if name.nil? || !value.is_a?(Hash)
259
+
260
+ name = "_#{name}" if primitive_element?(value, name)
261
+ value = value[name]
262
+ value = value[index.to_i] if index.present? && value.is_a?(Array)
263
+ name
141
264
  end
142
- msg = 'Bundle and/or entry resources are not conformant. Check messages for issues found.'
143
- assert validation_error_messages.blank?, msg
144
- rescue Inferno::Exceptions::AssertionException => e
145
- msg = "#{message} #{e.message}".strip
146
- raise e.class, msg unless skips
265
+ return unless %w[extension modifierExtension].include?(names.last) && value.is_a?(Hash)
266
+
267
+ [names.join('.'), value]
268
+ end
269
+
270
+ # True if the element is a primitive (or repeating primitive) with a `_` sibling holding its
271
+ # extensions, in the JSON object `parent`.
272
+ def primitive_element?(parent, name)
273
+ parent.key?("_#{name}") && Array.wrap(parent[name]).none?(Hash)
274
+ end
275
+
276
+ def parse_bundle(bundle)
277
+ bundle = FHIR.from_contents(bundle) if bundle.is_a?(String)
278
+ bundle if bundle.is_a?(FHIR::Bundle)
279
+ rescue StandardError
280
+ nil
281
+ end
282
+
283
+ def perform_request_validation(bundle, profile_url, version, request_type)
284
+ validate_pa_request_payload_structure(bundle, request_type)
285
+ validate_resources_conformance_against_profile(bundle, profile_url, version, request_type)
286
+ end
147
287
 
148
- skip msg
288
+ def perform_response_validation(response_bundle, profile_url, version, request_type, request_bundle = nil)
289
+ validate_pa_response_body_structure(response_bundle, request_bundle) if request_type.start_with?('submit')
290
+ validate_resources_conformance_against_profile(response_bundle, profile_url, version, request_type)
149
291
  end
150
292
 
293
+ ###########################################################################
294
+ # Structure Validation
295
+ ###########################################################################
296
+
151
297
  # Validates the structure of a Prior Authorization (PA) request Bundle.
152
298
  #
153
299
  # @param bundle [FHIR::Bundle] The FHIR Bundle of the PA request.
@@ -165,18 +311,22 @@ module DaVinciPASTestKit
165
311
  base_url = extract_base_url(bundle.entry.first&.fullUrl)
166
312
 
167
313
  check_presence_of_referenced_resources(first_entry, base_url, bundle.entry)
314
+ .each { |msg| add_validation_error(msg) }
168
315
 
169
- if request_type == 'submit'
316
+ # request_type is the compound 'submit_request' / 'inquire_request' built by
317
+ # perform_bundle_validation - start_with? also accepts a bare 'submit', consistent with
318
+ # find_profile_url and validate_resources_conformance_against_profile below.
319
+ if request_type.start_with?('submit')
170
320
  unless first_entry.is_a?(FHIR::Claim)
171
- validation_error_messages << "[Bundle/#{bundle.id}]: The first bundle entry must be a Claim"
321
+ add_validation_error("[Bundle/#{bundle.id}]: The first bundle entry must be a Claim")
172
322
  end
173
323
 
174
324
  validate_uniqueness_of_supporting_info_sequences(first_entry)
175
325
  validate_bundle_entries_full_url(bundle)
176
326
  else
177
- claim_resource = bundle_entry_resources.find { |resource| resource.resourceType == 'Claim' }
327
+ claim_resource = bundle_entry_resources.find { |resource| resource&.resourceType == 'Claim' }
178
328
  if claim_resource.blank?
179
- validation_error_messages << "[Bundle/#{bundle.id}]: Claim must be present for inquiry request"
329
+ add_validation_error("[Bundle/#{bundle.id}]: Claim must be present for inquiry request")
180
330
  end
181
331
 
182
332
  # The inquiry operation must contain a requesting provider organization,
@@ -186,16 +336,15 @@ module DaVinciPASTestKit
186
336
  payer_reference = claim_resource&.insurer&.reference
187
337
 
188
338
  if patient_reference.blank?
189
- validation_error_messages <<
190
- "[Bundle/#{bundle.id}]: The Claim for inquiry operation must reference a patient."
339
+ add_validation_error("[Bundle/#{bundle.id}]: The Claim for inquiry operation must reference a patient.")
191
340
  end
192
341
  if provider_reference.blank?
193
- validation_error_messages << "[Bundle/#{bundle.id}]: The claim for inquiry operation must reference " \
194
- 'a requesting provider organization.'
342
+ add_validation_error("[Bundle/#{bundle.id}]: The claim for inquiry operation must reference " \
343
+ 'a requesting provider organization.')
195
344
  end
196
345
  if payer_reference.blank?
197
- validation_error_messages << "[Bundle/#{bundle.id}]: The Claim for inquiry operation must contain " \
198
- 'a payer organization.'
346
+ add_validation_error("[Bundle/#{bundle.id}]: The Claim for inquiry operation must contain " \
347
+ 'a payer organization.')
199
348
  end
200
349
  end
201
350
  end
@@ -203,7 +352,7 @@ module DaVinciPASTestKit
203
352
  # Validates the response body structure of a Prior Authorization (PA) response.
204
353
  #
205
354
  # @param pa_response_bundle [FHIR::Bundle] The FHIR bundle representing the PA response.
206
- # @param pa_request_bundle [String] The JSON payload of the PA request bundle.
355
+ # @param pa_request_bundle [FHIR::Bundle, String, nil] The submitted PA request Bundle, either parsed or JSON.
207
356
  #
208
357
  # This method performs validation of the PA response bundle structure.
209
358
  # It follows the PAS IG requirement that the FHIR Bundle generated
@@ -214,34 +363,74 @@ module DaVinciPASTestKit
214
363
  # a given combination of content. Resources echoed back from the request are validated
215
364
  # to ensure the same fullUrl and resource identifiers as in the
216
365
  # request are used.
217
- def validate_pa_response_body_structure(pa_response_bundle, _pa_request_bundle)
218
- first_entry = pa_response_bundle.entry.first.resource
366
+ def validate_pa_response_body_structure(pa_response_bundle, pa_request_bundle)
367
+ first_entry = pa_response_bundle.entry.first&.resource
219
368
  unless first_entry.is_a?(FHIR::ClaimResponse)
220
- validation_error_messages <<
221
- "[Bundle/#{pa_response_bundle.id}]: The first bundle entry must be a ClaimResponse"
369
+ add_validation_error("[Bundle/#{pa_response_bundle.id}]: The first bundle entry must be a ClaimResponse")
222
370
  end
223
371
 
224
372
  base_url = extract_base_url(pa_response_bundle.entry.last&.fullUrl)
225
373
  check_presence_of_referenced_resources(first_entry, base_url, pa_response_bundle.entry)
374
+ .each { |msg| add_validation_error(msg) }
375
+
376
+ validate_echoed_response_resources(pa_response_bundle, pa_request_bundle)
377
+ end
378
+
379
+ def validate_echoed_response_resources(pa_response_bundle, pa_request_bundle)
380
+ return if pa_request_bundle.blank?
381
+
382
+ request_bundle = pa_request_bundle.is_a?(FHIR::Bundle) ? pa_request_bundle : FHIR.from_contents(pa_request_bundle)
383
+ return unless request_bundle.is_a?(FHIR::Bundle)
384
+
385
+ pa_response_bundle.entry.each do |response_entry|
386
+ response_resource = response_entry.resource
387
+ next if response_resource.blank?
388
+
389
+ request_entry = request_bundle.entry.find do |entry|
390
+ echoed_resource?(entry, response_entry)
391
+ end
392
+ next if request_entry.blank?
393
+
394
+ next if echoed_resource_identifiers_match?(request_entry, response_entry)
395
+
396
+ add_validation_error(resource_present_in_pa_request_and_response_msg(response_resource))
397
+ end
398
+ rescue StandardError
399
+ add_validation_error('Unable to compare PAS request and response Bundle resources for echoed identifiers.')
400
+ end
401
+
402
+ def echoed_resource?(request_entry, response_entry)
403
+ request_resource = request_entry.resource
404
+ response_resource = response_entry.resource
405
+ return false if request_resource.blank? || response_resource.blank?
406
+ return false unless request_resource.resourceType == response_resource.resourceType
407
+
408
+ # True if any identifying field is present on both sides and matches, so we can later report if any
409
+ # do not have all matching identifiers. Blank fields do not identify echoed resources.
410
+ (request_entry.fullUrl.present? && request_entry.fullUrl == response_entry.fullUrl) ||
411
+ (request_resource.id.present? && request_resource.id == response_resource.id) ||
412
+ (identifiers_of(request_resource).present? &&
413
+ identifiers_of(request_resource) == identifiers_of(response_resource))
414
+ end
226
415
 
227
- # Testing: When echoing back resources that are the same as were present in the prior authorization request,
228
- # the system SHALL ensure that the same fullUrl and resource identifiers are used in the response as appeared
229
- # in the request
230
- # pa_request_bundle = FHIR.from_contents(pa_request_bundle)
231
- # pa_response_bundle.entry.each do |entry|
232
- # res = entry.resource
233
- # request_entry = pa_request_bundle.entry.find do |ent|
234
- # ent.resource.resourceType == res.resourceType && ent.resource.id == res.id
235
- # end
236
- # next unless request_entry.present?
416
+ def echoed_resource_identifiers_match?(request_entry, response_entry)
417
+ request_resource = request_entry.resource
418
+ response_resource = response_entry.resource
237
419
 
238
- # assert(
239
- # request_entry.fullUrl == entry.fullUrl && request_entry.resource.identifier == res.identifier,
240
- # resource_present_in_pa_request_and_response_msg(res)
241
- # )
242
- # end
420
+ request_entry.fullUrl == response_entry.fullUrl &&
421
+ request_resource.id == response_resource.id &&
422
+ identifiers_of(request_resource) == identifiers_of(response_resource)
243
423
  end
244
424
 
425
+ # Nil for resource types without an identifier element (e.g., Binary, Parameters).
426
+ def identifiers_of(resource)
427
+ resource.identifier if resource.respond_to?(:identifier)
428
+ end
429
+
430
+ ###########################################################################
431
+ # Bundle entry profile validation
432
+ ###########################################################################
433
+
245
434
  # Profile conformance of Prior Authorization (PA) resources.
246
435
  #
247
436
  # @param bundle [FHIR::Bundle] The FHIR bundle representing the PA request/response.
@@ -259,7 +448,7 @@ module DaVinciPASTestKit
259
448
  bundle_entry = bundle.entry
260
449
 
261
450
  root_entry = bundle_entry.find do |entry|
262
- ['Claim', 'ClaimResponse'].include?(entry.resource.resourceType)
451
+ ['Claim', 'ClaimResponse'].include?(entry.resource&.resourceType)
263
452
  end
264
453
 
265
454
  if root_entry.present?
@@ -299,34 +488,63 @@ module DaVinciPASTestKit
299
488
  end
300
489
 
301
490
  # Validates bundle resource and each entry in the bundle against its target profiles.
302
- # It logs a message for each conformant entry
303
- # and collects error messages for non-conformant entries. Asserts that there are no validation errors.
491
+ # Validation messages are collected per profile rather than logged directly, so a
492
+ # resource with multiple candidate profiles only reports errors when it fails all of
493
+ # them. When a profile passes, only that profile's (non-error) messages are kept. Either
494
+ # way, messages go into validation_messages rather than straight to the runnable's
495
+ # `messages` - see perform_bundle_validation - so callers see every message this method
496
+ # finds, not just the summary line for a resource that conformed to nothing.
304
497
  # @param version [String] The version of the IG.
305
498
  def validate_bundle_entries_against_profiles(version)
306
499
  bundle_resources_target_profile_map.each do |key, item|
307
500
  resource = item[:resource]
308
501
  base_profile = FHIR::Definitions.resource_definition(resource.resourceType).url
502
+ unless key == 'bundle'
503
+ validator, echoed_extensions, request_entry = entry_validation_settings(key, resource)
504
+ if request_entry.present?
505
+ validation_messages << echoed_resource_message(key, resource, echoed_extensions, request_entry)
506
+ end
507
+ end
508
+ messages_by_profile = {}
309
509
 
310
510
  success_profile = item[:profile_urls].find do |url|
311
- if key == 'bundle'
312
- profile_to_validate = profile_url_for_validation(url, base_profile, version)
313
- bundle_profile_valid?(resource, profile_to_validate)
314
- elsif url == BASE_R4_PROFILE
315
- resource_is_valid?(resource:)
316
- else
317
- profile_to_validate = profile_url_for_validation(url, base_profile, version)
318
- resource_is_valid?(resource:, profile_url: profile_to_validate)
319
- end
511
+ profile_messages =
512
+ if key == 'bundle'
513
+ collect_bundle_profile_messages(resource, profile_url_for_validation(url, base_profile, version))
514
+ elsif url == BASE_R4_PROFILE
515
+ collect_resource_profile_messages(resource, validator:, echoed_extensions:)
516
+ else
517
+ collect_resource_profile_messages(resource, profile_url_for_validation(url, base_profile, version),
518
+ validator:, echoed_extensions:) +
519
+ datatype_constraint_messages(resource, profile_url_without_version(url), version)
520
+ end
521
+ messages_by_profile[url] = profile_messages
522
+ profile_messages.none? { |message| message[:type] == 'error' }
320
523
  end
321
524
 
322
- validation_error_messages << generate_non_conformance_message(item) unless success_profile
525
+ if success_profile
526
+ validation_messages.concat(messages_by_profile[success_profile])
527
+ else
528
+ messages_by_profile.each_value { |profile_messages| validation_messages.concat(profile_messages) }
529
+ add_validation_error(generate_non_conformance_message(item))
530
+ end
323
531
  end
324
532
  end
325
533
 
326
534
  def profile_url_for_validation(url, base_profile, version)
327
535
  url = url.to_s
328
536
  return url if url.include?('|') || profile_url_without_version(url) == base_profile
329
- return "#{url}|#{US_CORE_VERSION}" if us_core_profile_url?(url)
537
+
538
+ if us_core_profile_url?(url)
539
+ profile_id = url.delete_prefix("#{US_CORE_PROFILE_BASE}/")
540
+ # US Core 3.1.1's single Condition profile was split by category in later versions,
541
+ # so only 3.1.1 defines it.
542
+ return "#{url}|#{PASConstants::US_CORE_VERSION_3_1_1}" if profile_id == US_CORE_CONDITION_PROFILE_ID
543
+
544
+ # A declared US Core profile gets the selected version only when that version defines it;
545
+ # otherwise it stays unversioned so the validator resolves it to a version that does.
546
+ return us_core_profile_available?(profile_id) ? "#{url}|#{us_core_version}" : url
547
+ end
330
548
 
331
549
  "#{url}|#{version}"
332
550
  end
@@ -365,6 +583,12 @@ module DaVinciPASTestKit
365
583
  version.to_s.delete_prefix('v').match?(/\A2\.2(\.|\z)/)
366
584
  end
367
585
 
586
+ # The US Core version whose profiles the fallback validates against: the session's
587
+ # US Core Version suite option when the suite offers it, otherwise 6.1.0.
588
+ def us_core_version
589
+ suite_options[:us_core_version].presence || PASConstants::DEFAULT_US_CORE_VERSION
590
+ end
591
+
368
592
  def us_core_profile_urls_for_resource(resource)
369
593
  profile_ids =
370
594
  case resource.resourceType
@@ -378,10 +602,20 @@ module DaVinciPASTestKit
378
602
  Array(US_CORE_SINGLE_PROFILE_IDS_BY_RESOURCE[resource.resourceType])
379
603
  end
380
604
 
381
- us_core_profile_urls(profile_ids)
605
+ profile_urls = us_core_profile_urls(profile_ids.select { |profile_id| us_core_profile_available?(profile_id) })
606
+ # US Core 3.1.1 has no Simple Observation profile, so the base resource takes its place as the
607
+ # catch-all at the end of the Observation candidates.
608
+ if profile_ids.include?(US_CORE_SIMPLE_OBSERVATION_PROFILE_ID) &&
609
+ !us_core_profile_available?(US_CORE_SIMPLE_OBSERVATION_PROFILE_ID)
610
+ profile_urls << BASE_R4_PROFILE
611
+ end
612
+ profile_urls
382
613
  end
383
614
 
384
615
  def us_core_condition_profile_ids(resource)
616
+ # US Core 3.1.1 has a single Condition profile; later versions split it by category.
617
+ return [US_CORE_CONDITION_PROFILE_ID] if us_core_version == PASConstants::US_CORE_VERSION_3_1_1
618
+
385
619
  if category_code_present?(resource, 'encounter-diagnosis', system: TERMINOLOGY_CONDITION_CATEGORY_SYSTEM)
386
620
  [US_CORE_CONDITION_ENCOUNTER_DIAGNOSIS_PROFILE_ID]
387
621
  else
@@ -398,8 +632,9 @@ module DaVinciPASTestKit
398
632
  end
399
633
 
400
634
  def us_core_observation_profile_ids(resource)
401
- code_profile_id = US_CORE_OBSERVATION_CODE_PROFILE_IDS.find do |code, _profile_id|
402
- codeable_concept_has_code?(resource.code, code, system: LOINC_SYSTEM)
635
+ code_profile_id = US_CORE_OBSERVATION_CODE_PROFILE_IDS.find do |code, profile_id|
636
+ us_core_profile_available?(profile_id) &&
637
+ codeable_concept_has_code?(resource.code, code, system: LOINC_SYSTEM)
403
638
  end&.last
404
639
  return [code_profile_id] if code_profile_id.present?
405
640
 
@@ -422,7 +657,16 @@ module DaVinciPASTestKit
422
657
  end
423
658
 
424
659
  def us_core_profile_urls(profile_ids)
425
- Array(profile_ids).map { |profile_id| "#{US_CORE_PROFILE_BASE}/#{profile_id}|#{US_CORE_VERSION}" }
660
+ Array(profile_ids).map { |profile_id| "#{US_CORE_PROFILE_BASE}/#{profile_id}|#{us_core_version}" }
661
+ end
662
+
663
+ # Whether the selected US Core version defines the profile.
664
+ def us_core_profile_available?(profile_id)
665
+ # The only profile a later supported version dropped; see profile_url_for_validation.
666
+ return us_core_version == PASConstants::US_CORE_VERSION_3_1_1 if profile_id == US_CORE_CONDITION_PROFILE_ID
667
+
668
+ added_in = US_CORE_PROFILE_IDS_ADDED_IN_VERSION.find { |_, profile_ids| profile_ids.include?(profile_id) }&.first
669
+ added_in.nil? || Gem::Version.new(us_core_version) >= Gem::Version.new(added_in)
426
670
  end
427
671
 
428
672
  def profile_url_without_version(url)
@@ -446,19 +690,59 @@ module DaVinciPASTestKit
446
690
  end
447
691
  end
448
692
 
449
- # Validates a Bundle resource against a profile, filtering out entry-resource-level issues
450
- # that are covered by individual resource validation to avoid duplicate messages.
693
+ # Validates a resource against a profile without logging, returning the unfiltered
694
+ # issues as message hashes for the caller to log once profile selection is resolved.
695
+ # @param resource [FHIR::Model] The resource to validate.
696
+ # @param profile_url [String, nil] The profile URL (with version) to validate against,
697
+ # or nil to validate against the base resource definition.
698
+ # @param validator [Symbol] The name of the FHIR resource validator to use.
699
+ # @param echoed_extensions [Array, nil] Extensions of the request resource the resource echoes, or
700
+ # nil if it doesn't echo one - see entry_validation_settings and echoed_extension_issue_messages.
701
+ # @return [Array<Hash>] Message hashes for the issues found.
702
+ def collect_resource_profile_messages(resource, profile_url = nil, validator: profile_validator,
703
+ echoed_extensions: nil)
704
+ response_details = []
705
+ resource_is_valid?(resource:, profile_url:, validator:,
706
+ add_messages_to_runnable: false,
707
+ validator_response_details: response_details)
708
+ issues = response_details.reject(&:filtered)
709
+ return validator_issues_to_messages(issues) if echoed_extensions.nil?
710
+
711
+ resource_hash = validated_hash(resource)
712
+ if validator == :default
713
+ # The :default validator allows every extension, so its only issues to drop are its notes that
714
+ # echoed extensions are unknown, which the echoed resource message already covers.
715
+ return validator_issues_to_messages(issues.reject do |issue|
716
+ echoed_unknown_extension_note?(issue, resource_hash, echoed_extensions)
717
+ end)
718
+ end
719
+
720
+ echoed_extension_issue_messages(issues, resource_hash, echoed_extensions)
721
+ end
722
+
723
+ # Same as collect_resource_profile_messages for a Bundle, additionally dropping
724
+ # entry-resource-level issues that are covered by individual resource validation
725
+ # to avoid duplicate messages.
451
726
  # @param bundle [FHIR::Bundle] The Bundle resource to validate.
452
727
  # @param profile_url [String] The profile URL (with version) to validate against.
453
- # @return [Boolean] true if no unfiltered errors remain after removing entry-resource issues.
454
- def bundle_profile_valid?(bundle, profile_url)
728
+ # @return [Array<Hash>] Message hashes for the Bundle-structural issues found.
729
+ def collect_bundle_profile_messages(bundle, profile_url)
455
730
  response_details = []
456
- resource_is_valid?(resource: bundle, profile_url:,
731
+ resource_is_valid?(resource: bundle, profile_url:, validator: profile_validator,
457
732
  add_messages_to_runnable: false,
458
733
  validator_response_details: response_details)
459
- bundle_level_issues = reject_entry_resource_issues(response_details)
460
- bundle_level_issues.each { |issue| messages << { type: issue.severity, message: issue.message } }
461
- bundle_level_issues.none? { |issue| issue.severity == 'error' }
734
+ validator_issues_to_messages(reject_entry_resource_issues(response_details))
735
+ end
736
+
737
+ def validator_issues_to_messages(issues)
738
+ issues.map { |issue| validator_issue_message(issue) }
739
+ end
740
+
741
+ # @param details [String, nil] replaces the validator's text, keeping the location the message starts with.
742
+ def validator_issue_message(issue, details: nil)
743
+ message = issue.message
744
+ message = message.delete_suffix(issue.raw_issue['message'].to_s) + details if details.present?
745
+ { type: issue.severity, message: }
462
746
  end
463
747
 
464
748
  # Rejects validator issues that are already filtered or are located at/below
@@ -652,29 +936,6 @@ module DaVinciPASTestKit
652
936
  bundle_map[key]
653
937
  end
654
938
 
655
- def absolute_url(reference, base_url)
656
- return if reference.blank?
657
- return reference if base_url.blank? || reference.starts_with?('urn:uuid:') || URI(reference).absolute?
658
-
659
- "#{base_url}/#{reference}"
660
- end
661
-
662
- # Extracts the base URL from an absolute URL by removing the resource type and ID.
663
- # @param absolute_url [String] The absolute URL.
664
- # @return [String] The base URL, or an empty string if the URL format is not as expected.
665
- def extract_base_url(absolute_url)
666
- return '' if absolute_url.blank?
667
-
668
- uri = URI(absolute_url)
669
- return '' unless uri.scheme && uri.host
670
-
671
- # Split the path segments and remove the last two segments (resource type and id)
672
- path_segments = uri.path.split('/')
673
- base_path = path_segments[0...-2].join('/')
674
-
675
- "#{uri.scheme}://#{uri.host}#{base_path}"
676
- end
677
-
678
939
  # Resource Types to validate in request/ response bundle
679
940
  def find_profile_url(request_type)
680
941
  {
@@ -693,7 +954,7 @@ module DaVinciPASTestKit
693
954
 
694
955
  # Determines the target profile URL for a Claim resource in a submit request bundle.
695
956
  #
696
- # In v2.2.1, profile-pas-request-bundle permits either profile-claim or profile-claim-update.
957
+ # In PAS v2.2.1, profile-pas-request-bundle permits either profile-claim or profile-claim-update.
697
958
  # The structural discriminator is Claim.related: profile-claim-update requires it (must-support)
698
959
  # while profile-claim disallows it (max: 0). For all other versions, profile-claim-update is
699
960
  # used unconditionally.
@@ -727,15 +988,15 @@ module DaVinciPASTestKit
727
988
  is_unique = sequences.uniq.length == sequences.length
728
989
  return if is_unique
729
990
 
730
- validation_error_messages << "[Claim/#{claim.id}]: The sequence element for each supportingInfo entry SHALL be " \
731
- 'unique within the Claim.'
991
+ add_validation_error("[Claim/#{claim.id}]: The sequence element for each supportingInfo entry SHALL be " \
992
+ 'unique within the Claim.')
732
993
  end
733
994
 
734
995
  def validate_bundle_entries_full_url(bundle)
735
996
  msg = "[Bundle/#{bundle.id}]: Bundle.entry.fullUrl values SHALL be a valid url or in the form " \
736
997
  "'urn:uuid:[some guid]'."
737
998
  bundle.entry.each do |entry|
738
- validation_error_messages << msg unless valid_url_or_urn_uuid?(entry.fullUrl)
999
+ add_validation_error(msg) unless valid_url_or_urn_uuid?(entry.fullUrl)
739
1000
  end
740
1001
  end
741
1002
 
@@ -749,119 +1010,6 @@ module DaVinciPASTestKit
749
1010
  string&.match?(url_regex) || string&.match?(urn_uuid_regex)
750
1011
  end
751
1012
 
752
- # This method traverses references within a FHIR resource, ensuring that referenced resources
753
- # are populated in the bundle. It also enforces that a referenced resource appears only once in the bundle,
754
- # as required by the PAS IG.
755
- # @param target_resource [FHIR::Model] The FHIR resource to traverse and validate.
756
- # @param base_url [String] The server base url.
757
- # @param resources_to_match [Array<FHIR:Bundle:Entry] The list of FHIR bundle entries to match references against.
758
- # @param skip_claim_related [Boolean] When true, a Claim's `related` element is not traversed. This is set
759
- # for any Claim reached by following a reference (i.e. a non-primary Claim in a Claim Update chain), whose
760
- # own Claim.related.claim (the grandparent) is deliberately omitted from the Bundle per spec-65/66. The
761
- # primary Claim passed in by the caller keeps `skip_claim_related: false`, so its parent reference - and the
762
- # parent's own referenced resources - are still checked.
763
- def check_presence_of_referenced_resources(target_resource, base_url, resources_to_match,
764
- skip_claim_related: false)
765
- return if target_resource.blank?
766
-
767
- if target_resource.is_a?(FHIR::Reference) && target_resource.reference.present?
768
- ref = target_resource.reference
769
- absolute_ref = absolute_url(ref, base_url)
770
- matching_resources = resources_to_match.find_all { |res| res.fullUrl == absolute_ref }
771
-
772
- if matching_resources.length != 1
773
- validation_error_messages << resource_shall_appear_once_message(absolute_ref,
774
- matching_resources.length)
775
- end
776
-
777
- if matching_resources.length.positive?
778
- # A resource reached by following a reference is an included resource, not the primary Claim
779
- # being validated; if it is itself a Claim Update, its referenced grandparent Claim is omitted.
780
- check_presence_of_referenced_resources(matching_resources.first.resource, base_url, resources_to_match,
781
- skip_claim_related: true)
782
- end
783
- else
784
- target_resource.source_hash.each_key do |attr|
785
- next if claim_response_request_attr?(target_resource, attr)
786
- next if skip_claim_related && claim_related_attr?(target_resource, attr)
787
-
788
- value = target_resource.send(attr.to_sym)
789
- if value.is_a?(FHIR::Model)
790
- check_presence_of_referenced_resources(value, base_url, resources_to_match, skip_claim_related:)
791
- elsif value.is_a?(Array) && value.all?(FHIR::Model)
792
- value.each do |elmt|
793
- check_presence_of_referenced_resources(elmt, base_url, resources_to_match, skip_claim_related:)
794
- end
795
- end
796
- end
797
- end
798
- end
799
-
800
- # ClaimResponse.request is a back-reference to the submitted Claim. The PAS IG response bundle
801
- # profile (profile-pas-response-bundle) has no required Claim entry slice, so the Claim need
802
- # not be present in the response bundle. Skipping here matches the identical guard in
803
- # ResponseGenerator#referenced_entities, which also skips ClaimResponse.request when
804
- # building mock response bundles.
805
- def claim_response_request_attr?(resource, attr)
806
- attr.to_s == 'request' &&
807
- resource.respond_to?(:resourceType) &&
808
- resource.resourceType == 'ClaimResponse'
809
- end
810
-
811
- # Claim.related.claim points to the Claim being updated. For a non-primary Claim in a Claim Update
812
- # chain (one reached by following a reference), that prior Claim is the grandparent, which spec-65/66
813
- # require to be omitted from the Bundle. Used with the `skip_claim_related` flag so the generic
814
- # reference-presence check does not flag the deliberately-omitted grandparent as missing.
815
- def claim_related_attr?(resource, attr)
816
- attr.to_s == 'related' &&
817
- resource.respond_to?(:resourceType) &&
818
- resource.resourceType == 'Claim'
819
- end
820
-
821
- # Extracts resources from a bundle while following "next" links.
822
- #
823
- # @param bundle [FHIR::Bundle] The initial FHIR bundle to extract resources from.
824
- # @param response [Object] The HTTP response object for the bundle retrieval.
825
- # @param reply_handler [Proc] A callback function to handle responses.
826
- # @param max_pages [Integer] The maximum number of pages to process.
827
- #
828
- # This method extracts resources from a FHIR bundle, following "next" links in the bundle
829
- # until the specified maximum number of pages is reached. It collects resources and
830
- # invokes the reply_handler for each response.
831
- def extract_resources_from_bundle(
832
- bundle: nil,
833
- response: nil,
834
- reply_handler: nil,
835
- max_pages: 20
836
- )
837
- page_count = 1
838
- resources = []
839
-
840
- until bundle.nil? || page_count == max_pages
841
- resources += bundle&.entry&.map { |entry| entry&.resource }
842
- next_bundle_link = bundle&.link&.find { |link| link.relation == 'next' }&.url
843
- reply_handler&.call(response)
844
-
845
- break if next_bundle_link.blank?
846
-
847
- page_count += 1
848
- end
849
-
850
- resources
851
- end
852
-
853
- # Generates a message for a resource that appears more than once in a bundle.
854
- #
855
- # @param reference_resource_type [String] The resource type being referenced.
856
- # @param reference_resource_id [String] The resource ID being referenced.
857
- # @param total_matches [Integer] The total number of matches found in the bundle.
858
- #
859
- # This method generates an error message when a referenced resource appears more than once
860
- # in a FHIR bundle, which is not allowed.
861
- def resource_shall_appear_once_message(absolute_ref, total_matches)
862
- " The referenced #{absolute_ref} resource SHALL appear exactly once in the Bundle, but found #{total_matches}."
863
- end
864
-
865
1013
  # Generates a message for a resource present in both the PA request and response bundles.
866
1014
  #
867
1015
  # @param resource [FHIR::Model] The resource present in both bundles.
@@ -872,5 +1020,115 @@ module DaVinciPASTestKit
872
1020
  "Resource #{resource.resourceType}/#{resource.id} is an entry in both the PA Request Bundle and the Response " \
873
1021
  'Bundle, but they do not have the same fullUrl or identifiers'
874
1022
  end
1023
+
1024
+ ###########################################################################
1025
+ # US Core Profile Constants
1026
+ ###########################################################################
1027
+
1028
+ US_CORE_PROFILE_BASE = 'http://hl7.org/fhir/us/core/StructureDefinition'
1029
+ BASE_R4_PROFILE = :base_r4
1030
+ CLAIM_ENCOUNTER_EXTENSION_URL = 'http://hl7.org/fhir/5.0/StructureDefinition/extension-Claim.encounter'
1031
+ LOINC_SYSTEM = 'http://loinc.org'
1032
+ TERMINOLOGY_CONDITION_CATEGORY_SYSTEM = 'http://terminology.hl7.org/CodeSystem/condition-category'
1033
+ OBSERVATION_CATEGORY_SYSTEM = 'http://terminology.hl7.org/CodeSystem/observation-category'
1034
+ DIAGNOSTIC_REPORT_CATEGORY_SYSTEM = 'http://terminology.hl7.org/CodeSystem/v2-0074'
1035
+
1036
+ US_CORE_SINGLE_PROFILE_IDS_BY_RESOURCE = {
1037
+ 'AllergyIntolerance' => 'us-core-allergyintolerance',
1038
+ 'CarePlan' => 'us-core-careplan',
1039
+ 'CareTeam' => 'us-core-careteam',
1040
+ 'Coverage' => 'us-core-coverage',
1041
+ 'Device' => 'us-core-implantable-device',
1042
+ 'DocumentReference' => 'us-core-documentreference',
1043
+ 'Encounter' => 'us-core-encounter',
1044
+ 'Goal' => 'us-core-goal',
1045
+ 'Immunization' => 'us-core-immunization',
1046
+ 'Location' => 'us-core-location',
1047
+ 'Medication' => 'us-core-medication',
1048
+ 'MedicationDispense' => 'us-core-medicationdispense',
1049
+ 'MedicationRequest' => 'us-core-medicationrequest',
1050
+ 'Organization' => 'us-core-organization',
1051
+ 'Patient' => 'us-core-patient',
1052
+ 'Practitioner' => 'us-core-practitioner',
1053
+ 'PractitionerRole' => 'us-core-practitionerrole',
1054
+ 'Procedure' => 'us-core-procedure',
1055
+ 'Provenance' => 'us-core-provenance',
1056
+ 'QuestionnaireResponse' => 'us-core-questionnaireresponse',
1057
+ 'RelatedPerson' => 'us-core-relatedperson',
1058
+ 'ServiceRequest' => 'us-core-servicerequest',
1059
+ 'Specimen' => 'us-core-specimen'
1060
+ }.freeze
1061
+
1062
+ US_CORE_CONDITION_PROFILE_ID = 'us-core-condition'
1063
+ US_CORE_CONDITION_ENCOUNTER_DIAGNOSIS_PROFILE_ID = 'us-core-condition-encounter-diagnosis'
1064
+ US_CORE_CONDITION_PROBLEMS_HEALTH_CONCERNS_PROFILE_ID = 'us-core-condition-problems-health-concerns'
1065
+ US_CORE_DIAGNOSTIC_REPORT_LAB_PROFILE_ID = 'us-core-diagnosticreport-lab'
1066
+ US_CORE_DIAGNOSTIC_REPORT_NOTE_PROFILE_ID = 'us-core-diagnosticreport-note'
1067
+ US_CORE_OBSERVATION_CLINICAL_RESULT_PROFILE_ID = 'us-core-observation-clinical-result'
1068
+ US_CORE_OBSERVATION_LAB_PROFILE_ID = 'us-core-observation-lab'
1069
+ US_CORE_OBSERVATION_SCREENING_ASSESSMENT_PROFILE_ID = 'us-core-observation-screening-assessment'
1070
+ US_CORE_SIMPLE_OBSERVATION_PROFILE_ID = 'us-core-simple-observation'
1071
+ US_CORE_SMOKING_STATUS_PROFILE_ID = 'us-core-smokingstatus'
1072
+ US_CORE_VITAL_SIGNS_PROFILE_ID = 'us-core-vital-signs'
1073
+
1074
+ US_CORE_OBSERVATION_CODE_PROFILE_IDS = {
1075
+ '11341-5' => 'us-core-observation-occupation',
1076
+ '86645-9' => 'us-core-observation-pregnancyintent',
1077
+ '82810-3' => 'us-core-observation-pregnancystatus',
1078
+ '76690-7' => 'us-core-observation-sexual-orientation',
1079
+ '95541-9' => 'us-core-care-experience-preference',
1080
+ '75773-2' => 'us-core-treatment-intervention-preference',
1081
+ '8289-1' => 'head-occipital-frontal-circumference-percentile',
1082
+ '59576-9' => 'pediatric-bmi-for-age',
1083
+ '77606-2' => 'pediatric-weight-for-height',
1084
+ '85354-9' => 'us-core-blood-pressure',
1085
+ '96607-7' => 'us-core-average-blood-pressure',
1086
+ '39156-5' => 'us-core-bmi',
1087
+ '8302-2' => 'us-core-body-height',
1088
+ '8310-5' => 'us-core-body-temperature',
1089
+ '29463-7' => 'us-core-body-weight',
1090
+ '9843-4' => 'us-core-head-circumference',
1091
+ '8867-4' => 'us-core-heart-rate',
1092
+ '59408-5' => 'us-core-pulse-oximetry',
1093
+ '2708-6' => 'us-core-pulse-oximetry',
1094
+ '9279-1' => 'us-core-respiratory-rate',
1095
+ '72166-2' => 'us-core-smokingstatus'
1096
+ }.freeze
1097
+
1098
+ # Fallback profiles that US Core 3.1.1 does not define, keyed by the earliest supported
1099
+ # version that does. Profiles not listed here exist in every supported version.
1100
+ US_CORE_PROFILE_IDS_ADDED_IN_VERSION = {
1101
+ PASConstants::US_CORE_VERSION_6_1_0 => [
1102
+ 'us-core-blood-pressure',
1103
+ 'us-core-bmi',
1104
+ 'us-core-body-height',
1105
+ 'us-core-body-temperature',
1106
+ 'us-core-body-weight',
1107
+ 'us-core-condition-encounter-diagnosis',
1108
+ 'us-core-condition-problems-health-concerns',
1109
+ 'us-core-coverage',
1110
+ 'us-core-head-circumference',
1111
+ 'us-core-heart-rate',
1112
+ 'us-core-medicationdispense',
1113
+ 'us-core-observation-clinical-result',
1114
+ 'us-core-observation-occupation',
1115
+ 'us-core-observation-pregnancyintent',
1116
+ 'us-core-observation-pregnancystatus',
1117
+ 'us-core-observation-screening-assessment',
1118
+ 'us-core-observation-sexual-orientation',
1119
+ 'us-core-questionnaireresponse',
1120
+ 'us-core-relatedperson',
1121
+ 'us-core-respiratory-rate',
1122
+ 'us-core-servicerequest',
1123
+ 'us-core-simple-observation',
1124
+ 'us-core-specimen',
1125
+ 'us-core-vital-signs'
1126
+ ],
1127
+ PASConstants::US_CORE_VERSION_7_0_0 => [
1128
+ 'us-core-average-blood-pressure',
1129
+ 'us-core-care-experience-preference',
1130
+ 'us-core-treatment-intervention-preference'
1131
+ ]
1132
+ }.freeze
875
1133
  end
876
1134
  end