cancer_registry_reporting_test_kit 0.9.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 (262) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/config/presets/hdea_report_preset.json +14 -0
  4. data/config/presets/inferno_reference_server_preset.json.erb +84 -0
  5. data/lib/cancer_registry_reporting_test_kit/bundle_parse.rb +161 -0
  6. data/lib/cancer_registry_reporting_test_kit/docs/ehr_suite_description.md +134 -0
  7. data/lib/cancer_registry_reporting_test_kit/docs/hdea_suite_description.md +108 -0
  8. data/lib/cancer_registry_reporting_test_kit/ehr_suite/ehr_capability_statement/mcode_capability_statement_profile_support.rb +40 -0
  9. data/lib/cancer_registry_reporting_test_kit/ehr_suite/ehr_data_access_group.rb +131 -0
  10. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_administration/medication_administration_must_support_test.rb +43 -0
  11. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_administration/medication_administration_search_test.rb +51 -0
  12. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_administration/medication_administration_validation_test.rb +41 -0
  13. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_administration/metadata.yml +72 -0
  14. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_administration_group.rb +72 -0
  15. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_request/medication_request_must_support_test.rb +55 -0
  16. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_request/medication_request_search_test.rb +55 -0
  17. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_request/medication_request_validation_test.rb +38 -0
  18. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_request/metadata.yml +94 -0
  19. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/medication_request_group.rb +73 -0
  20. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/primary_cancer_condition_group.rb +73 -0
  21. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/primary_condition/metadata.yml +100 -0
  22. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/primary_condition/primary_cancer_condition_must_support_test.rb +50 -0
  23. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/primary_condition/primary_cancer_condition_search_test.rb +70 -0
  24. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/primary_condition/primary_cancer_condition_validation_test.rb +39 -0
  25. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/radiotherapy_procedure/metadata.yml +99 -0
  26. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/radiotherapy_procedure/radiotherapy_procedure_must_support_test.rb +46 -0
  27. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/radiotherapy_procedure/radiotherapy_procedure_search_test.rb +51 -0
  28. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/radiotherapy_procedure/radiotherapy_procedure_validation_test.rb +37 -0
  29. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/radiotherapy_procedure_group.rb +83 -0
  30. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/secondary_cancer_condition_group.rb +68 -0
  31. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/secondary_condition/metadata.yml +102 -0
  32. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/secondary_condition/secondary_cancer_condition_must_support_test.rb +50 -0
  33. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/secondary_condition/secondary_cancer_condition_search_test.rb +70 -0
  34. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/secondary_condition/secondary_cancer_condition_validation_test.rb +39 -0
  35. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_distant_metastases_category/metadata.yml +65 -0
  36. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_distant_metastases_category/tnm_distant_metastases_category_must_support_test.rb +44 -0
  37. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_distant_metastases_category/tnm_distant_metastases_category_search_test.rb +69 -0
  38. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_distant_metastases_category/tnm_distant_metastases_category_validation_test.rb +39 -0
  39. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_distant_metastases_category_group.rb +74 -0
  40. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_primary_tumor_category/metadata.yml +64 -0
  41. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_primary_tumor_category/tnm_primary_tumor_category_must_support_test.rb +44 -0
  42. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_primary_tumor_category/tnm_primary_tumor_category_search_test.rb +69 -0
  43. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_primary_tumor_category/tnm_primary_tumor_category_validation_test.rb +39 -0
  44. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_primary_tumor_category_group.rb +73 -0
  45. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_regional_nodes_category/metadata.yml +64 -0
  46. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_regional_nodes_category/tnm_regional_nodes_category_must_support_test.rb +44 -0
  47. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_regional_nodes_category/tnm_regional_nodes_category_search_test.rb +69 -0
  48. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_regional_nodes_category/tnm_regional_nodes_category_validation_test.rb +39 -0
  49. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_regional_nodes_category_group.rb +73 -0
  50. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_stage_group/metadata.yml +68 -0
  51. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_stage_group/tnm_stage_group_must_support_test.rb +45 -0
  52. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_stage_group/tnm_stage_group_search_test.rb +69 -0
  53. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_stage_group/tnm_stage_group_validation_test.rb +39 -0
  54. data/lib/cancer_registry_reporting_test_kit/ehr_suite/mcode_data_access_resources/tnm_stage_group_group.rb +72 -0
  55. data/lib/cancer_registry_reporting_test_kit/ehr_suite.rb +69 -0
  56. data/lib/cancer_registry_reporting_test_kit/fhir_resource_navigation.rb +174 -0
  57. data/lib/cancer_registry_reporting_test_kit/hdea_generator/group_generator.rb +47 -0
  58. data/lib/cancer_registry_reporting_test_kit/hdea_generator/group_metadata.rb +86 -0
  59. data/lib/cancer_registry_reporting_test_kit/hdea_generator/group_metadata_extractor.rb +205 -0
  60. data/lib/cancer_registry_reporting_test_kit/hdea_generator/ig_loader.rb +78 -0
  61. data/lib/cancer_registry_reporting_test_kit/hdea_generator/ig_metadata.rb +26 -0
  62. data/lib/cancer_registry_reporting_test_kit/hdea_generator/ig_metadata_extractor.rb +78 -0
  63. data/lib/cancer_registry_reporting_test_kit/hdea_generator/ig_resources.rb +56 -0
  64. data/lib/cancer_registry_reporting_test_kit/hdea_generator/must_support_metadata_extractor.rb +399 -0
  65. data/lib/cancer_registry_reporting_test_kit/hdea_generator/must_support_test_generator.rb +134 -0
  66. data/lib/cancer_registry_reporting_test_kit/hdea_generator/naming.rb +168 -0
  67. data/lib/cancer_registry_reporting_test_kit/hdea_generator/special_cases.rb +43 -0
  68. data/lib/cancer_registry_reporting_test_kit/hdea_generator/templates/must_support.rb.erb +41 -0
  69. data/lib/cancer_registry_reporting_test_kit/hdea_generator/templates/validation.rb.erb +35 -0
  70. data/lib/cancer_registry_reporting_test_kit/hdea_generator/terminology_binding_metadata_extractor.rb +118 -0
  71. data/lib/cancer_registry_reporting_test_kit/hdea_generator/validation_test_generator.rb +125 -0
  72. data/lib/cancer_registry_reporting_test_kit/hdea_generator/value_extractor.rb +134 -0
  73. data/lib/cancer_registry_reporting_test_kit/hdea_generator.rb +66 -0
  74. data/lib/cancer_registry_reporting_test_kit/hdea_suite/author/author_validation_test.rb +61 -0
  75. data/lib/cancer_registry_reporting_test_kit/hdea_suite/author/organization_metadata.yml +47 -0
  76. data/lib/cancer_registry_reporting_test_kit/hdea_suite/author/organization_must_support_test.rb +73 -0
  77. data/lib/cancer_registry_reporting_test_kit/hdea_suite/author/practitioner_metadata.yml +51 -0
  78. data/lib/cancer_registry_reporting_test_kit/hdea_suite/author/practitioner_must_support_test.rb +73 -0
  79. data/lib/cancer_registry_reporting_test_kit/hdea_suite/author/practitioner_role_metadata.yml +48 -0
  80. data/lib/cancer_registry_reporting_test_kit/hdea_suite/author/practitioner_role_must_support_test.rb +66 -0
  81. data/lib/cancer_registry_reporting_test_kit/hdea_suite/bundle_resources_group.rb +213 -0
  82. data/lib/cancer_registry_reporting_test_kit/hdea_suite/ccrr_content_bundle_parse_and_validation_test.rb +67 -0
  83. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/allergy_intolerance/allergy_intolerance_must_support_test.rb +46 -0
  84. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/allergy_intolerance/allergy_intolerance_validation_test.rb +35 -0
  85. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/allergy_intolerance/metadata.yml +36 -0
  86. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/care_plan/care_plan_must_support_test.rb +48 -0
  87. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/care_plan/care_plan_validation_test.rb +35 -0
  88. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/care_plan/metadata.yml +50 -0
  89. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/ccrr_content_bundle/ccrr_content_bundle_must_support_test.rb +43 -0
  90. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/ccrr_content_bundle/metadata.yml +36 -0
  91. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/central_cancer_registry_primary_cancer_condition/central_cancer_registry_primary_cancer_condition_must_support_test.rb +59 -0
  92. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/central_cancer_registry_primary_cancer_condition/central_cancer_registry_primary_cancer_condition_validation_test.rb +35 -0
  93. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/central_cancer_registry_primary_cancer_condition/metadata.yml +86 -0
  94. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/composition/composition_must_support_test.rb +109 -0
  95. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/composition/composition_validation_test.rb +35 -0
  96. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/composition/metadata.yml +272 -0
  97. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/condition/condition_must_support_test.rb +45 -0
  98. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/condition/condition_validation_test.rb +35 -0
  99. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/condition/metadata.yml +33 -0
  100. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/diagnostic_report_lab/diagnostic_report_lab_must_support_test.rb +49 -0
  101. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/diagnostic_report_lab/diagnostic_report_lab_validation_test.rb +35 -0
  102. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/diagnostic_report_lab/metadata.yml +57 -0
  103. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/diagnostic_report_note/diagnostic_report_note_must_support_test.rb +53 -0
  104. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/diagnostic_report_note/diagnostic_report_note_validation_test.rb +35 -0
  105. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/diagnostic_report_note/metadata.yml +75 -0
  106. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/document_reference/document_reference_must_support_test.rb +57 -0
  107. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/document_reference/document_reference_validation_test.rb +35 -0
  108. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/document_reference/metadata.yml +68 -0
  109. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/encounter/encounter_must_support_test.rb +59 -0
  110. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/encounter/encounter_validation_test.rb +35 -0
  111. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/encounter/metadata.yml +73 -0
  112. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_radiotherapy_course_summary/mcode_radiotherapy_course_summary_must_support_test.rb +52 -0
  113. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_radiotherapy_course_summary/mcode_radiotherapy_course_summary_validation_test.rb +35 -0
  114. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_radiotherapy_course_summary/metadata.yml +59 -0
  115. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_secondary_cancer_condition/mcode_secondary_cancer_condition_must_support_test.rb +56 -0
  116. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_secondary_cancer_condition/mcode_secondary_cancer_condition_validation_test.rb +35 -0
  117. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_secondary_cancer_condition/metadata.yml +77 -0
  118. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_tnm_stage_group/mcode_tnm_stage_group_must_support_test.rb +48 -0
  119. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_tnm_stage_group/mcode_tnm_stage_group_validation_test.rb +35 -0
  120. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/mcode_tnm_stage_group/metadata.yml +46 -0
  121. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication/medication_must_support_test.rb +41 -0
  122. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication/medication_validation_test.rb +35 -0
  123. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication/metadata.yml +26 -0
  124. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_administration/medication_administration_must_support_test.rb +48 -0
  125. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_administration/medication_administration_validation_test.rb +35 -0
  126. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_administration/metadata.yml +48 -0
  127. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_request/medication_request_must_support_test.rb +57 -0
  128. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_request/medication_request_validation_test.rb +35 -0
  129. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_request/metadata.yml +86 -0
  130. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_statement/medication_statement_must_support_test.rb +41 -0
  131. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_statement/medication_statement_validation_test.rb +35 -0
  132. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/medication_statement/metadata.yml +26 -0
  133. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/observation/metadata.yml +26 -0
  134. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/observation/observation_must_support_test.rb +41 -0
  135. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/observation/observation_validation_test.rb +35 -0
  136. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/observation_lab/metadata.yml +53 -0
  137. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/observation_lab/observation_lab_must_support_test.rb +50 -0
  138. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/observation_lab/observation_lab_validation_test.rb +35 -0
  139. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/odh_usual_work/metadata.yml +81 -0
  140. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/odh_usual_work/odh_usual_work_must_support_test.rb +49 -0
  141. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/odh_usual_work/odh_usual_work_validation_test.rb +35 -0
  142. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/organization/metadata.yml +46 -0
  143. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/organization/organization_must_support_test.rb +55 -0
  144. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/organization/organization_validation_test.rb +35 -0
  145. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/patient/metadata.yml +51 -0
  146. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/patient/patient_must_support_test.rb +58 -0
  147. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/patient/patient_validation_test.rb +35 -0
  148. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/practitioner/metadata.yml +50 -0
  149. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/practitioner/practitioner_must_support_test.rb +55 -0
  150. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/practitioner/practitioner_validation_test.rb +35 -0
  151. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/practitioner_role/metadata.yml +47 -0
  152. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/practitioner_role/practitioner_role_must_support_test.rb +49 -0
  153. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/practitioner_role/practitioner_role_validation_test.rb +35 -0
  154. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/procedure/metadata.yml +37 -0
  155. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/procedure/procedure_must_support_test.rb +44 -0
  156. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/procedure/procedure_validation_test.rb +35 -0
  157. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/service_request/metadata.yml +26 -0
  158. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/service_request/service_request_must_support_test.rb +41 -0
  159. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/service_request/service_request_validation_test.rb +35 -0
  160. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/smokingstatus/metadata.yml +58 -0
  161. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/smokingstatus/smokingstatus_must_support_test.rb +47 -0
  162. data/lib/cancer_registry_reporting_test_kit/hdea_suite/generated/v1.0.0/smokingstatus/smokingstatus_validation_test.rb +35 -0
  163. data/lib/cancer_registry_reporting_test_kit/hdea_suite.rb +80 -0
  164. data/lib/cancer_registry_reporting_test_kit/igs/README.md +21 -0
  165. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-cancer-related-medication-administration.json +1905 -0
  166. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-cancer-related-medication-request.json +3611 -0
  167. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-primary-cancer-condition.json +2246 -0
  168. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-radiotherapy-course-summary.json +2700 -0
  169. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-secondary-cancer-condition.json +2419 -0
  170. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-tnm-distant-metastases-category.json +2320 -0
  171. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-tnm-primary-tumor-category.json +2320 -0
  172. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-tnm-regional-nodes-category.json +2320 -0
  173. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-mcode-tnm-stage-group.json +2414 -0
  174. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-medicationstatement.profile.json +1578 -0
  175. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-observation.profile.json +3510 -0
  176. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-odh-UsualWork.json +7248 -0
  177. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-servicerequest.profile.json +3048 -0
  178. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-allergyintolerance.json +1842 -0
  179. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-careplan.json +1 -0
  180. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-condition.json +2154 -0
  181. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-diagnosticreport-lab.json +2002 -0
  182. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-diagnosticreport-note.json +2049 -0
  183. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-documentreference.json +1 -0
  184. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-encounter.json +1 -0
  185. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-medication.json +1 -0
  186. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-observation-lab.json +3153 -0
  187. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-organization.json +1 -0
  188. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-patient.json +1 -0
  189. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-practitioner.json +1 -0
  190. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-practitionerrole.json +1 -0
  191. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-procedure.json +1 -0
  192. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100/StructureDefinition-us-core-smokingstatus.json +1 -0
  193. data/lib/cancer_registry_reporting_test_kit/igs/ccrr_v100.tgz +0 -0
  194. data/lib/cancer_registry_reporting_test_kit/igs/package/.index.db +0 -0
  195. data/lib/cancer_registry_reporting_test_kit/igs/package/.index.json +4 -0
  196. data/lib/cancer_registry_reporting_test_kit/igs/package/CapabilityStatement-central-cancer-registry-reporting-ehr.json +1 -0
  197. data/lib/cancer_registry_reporting_test_kit/igs/package/ImplementationGuide-hl7.fhir.us.central-cancer-registry-reporting.json +1 -0
  198. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-cancer-encounter.json +1 -0
  199. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-cancer-patient.json +1 -0
  200. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-ccrr-composition.json +1 -0
  201. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-ccrr-content-bundle.json +1 -0
  202. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-ccrr-plandefinition.json +1 -0
  203. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-ccrr-reporting-bundle.json +1 -0
  204. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-central-cancer-registry-primary-cancer-condition.json +1 -0
  205. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-central-cancer-registry-reporting-messageheader.json +1 -0
  206. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-us-ph-patient.json +1 -0
  207. data/lib/cancer_registry_reporting_test_kit/igs/package/StructureDefinition-us-ph-tribal-affiliation-extension.json +1 -0
  208. data/lib/cancer_registry_reporting_test_kit/igs/package/ValueSet-cancer-core-reportability-codes.json +1 -0
  209. data/lib/cancer_registry_reporting_test_kit/igs/package/example/AllergyIntolerance-example.json +1 -0
  210. data/lib/cancer_registry_reporting_test_kit/igs/package/example/BodyStructure-jenny-m-chest-wall-lymph-nodes-treatment-volume.json +1 -0
  211. data/lib/cancer_registry_reporting_test_kit/igs/package/example/BodyStructure-jenny-m-chest-wall-treatment-volume.json +1 -0
  212. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Bundle-ccrr-content-bundle-example.json +1 -0
  213. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Bundle-central-cancer-registry-reporting-specification-bundle-example.json +1 -0
  214. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Bundle-reporting-bundle-example.json +1 -0
  215. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Composition-ccrr-composition-example.json +1 -0
  216. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Condition-primary-cancer-condition-breast.json +1 -0
  217. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Encounter-encounter-cancer-example.json +1 -0
  218. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Endpoint-example-healthcare-endpoint.json +1 -0
  219. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Endpoint-example-ph-endpoint.json +1 -0
  220. data/lib/cancer_registry_reporting_test_kit/igs/package/example/MedicationAdministration-cancer-related-medication-administration-example.json +1 -0
  221. data/lib/cancer_registry_reporting_test_kit/igs/package/example/MessageHeader-messageheader-example-reportheader.json +1 -0
  222. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Observation-cancer-stage-group-example.json +1 -0
  223. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Observation-tnm-clinical-distant-metastases-category-cM0.json +1 -0
  224. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Observation-tnm-clinical-primary-tumor-category-cT3.json +1 -0
  225. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Observation-tnm-clinical-regional-nodes-category-cN3.json +1 -0
  226. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Organization-example-healthcare-org.json +1 -0
  227. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Organization-example-pha-org.json +1 -0
  228. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Patient-example.json +1 -0
  229. data/lib/cancer_registry_reporting_test_kit/igs/package/example/PlanDefinition-plandefinition-central-cancer-registry-reporting-example.json +1 -0
  230. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Practitioner-1.json +1 -0
  231. data/lib/cancer_registry_reporting_test_kit/igs/package/example/Procedure-radiotherapy-example.json +1 -0
  232. data/lib/cancer_registry_reporting_test_kit/igs/package/openapi/central-cancer-registry-reporting-ehr.openapi.json +364 -0
  233. data/lib/cancer_registry_reporting_test_kit/igs/package/other/ig-r4.jsonX +1 -0
  234. data/lib/cancer_registry_reporting_test_kit/igs/package/other/spec.internals +293 -0
  235. data/lib/cancer_registry_reporting_test_kit/igs/package/other/validation-oo.json +1 -0
  236. data/lib/cancer_registry_reporting_test_kit/igs/package/other/validation-summary.json +1 -0
  237. data/lib/cancer_registry_reporting_test_kit/igs/package/package.json +30 -0
  238. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-cancer-encounter.sch +19 -0
  239. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-cancer-patient.sch +175 -0
  240. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-ccrr-composition.sch +61 -0
  241. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-ccrr-content-bundle.sch +12 -0
  242. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-ccrr-plandefinition.sch +331 -0
  243. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-ccrr-reporting-bundle.sch +12 -0
  244. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-central-cancer-registry-primary-cancer-condition.sch +31 -0
  245. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-central-cancer-registry-reporting-messageheader.sch +27 -0
  246. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-us-ph-patient.sch +217 -0
  247. data/lib/cancer_registry_reporting_test_kit/igs/package/xml/StructureDefinition-us-ph-tribal-affiliation-extension.sch +36 -0
  248. data/lib/cancer_registry_reporting_test_kit/metadata.rb +71 -0
  249. data/lib/cancer_registry_reporting_test_kit/must_support_test.rb +263 -0
  250. data/lib/cancer_registry_reporting_test_kit/primitive_type.rb +7 -0
  251. data/lib/cancer_registry_reporting_test_kit/requirements/cancer-registry-reporting-test-kit_out_of_scope_requirements.csv +77 -0
  252. data/lib/cancer_registry_reporting_test_kit/requirements/cancer-registry-reporting-test-kit_requirements.csv +162 -0
  253. data/lib/cancer_registry_reporting_test_kit/requirements/generated/cancer-registry-reporting-test-kit_requirements_coverage.csv +161 -0
  254. data/lib/cancer_registry_reporting_test_kit/search_test.rb +897 -0
  255. data/lib/cancer_registry_reporting_test_kit/search_test_properties.rb +58 -0
  256. data/lib/cancer_registry_reporting_test_kit/validation_test.rb +68 -0
  257. data/lib/cancer_registry_reporting_test_kit/version.rb +6 -0
  258. data/lib/cancer_registry_reporting_test_kit.rb +6 -0
  259. data/lib/inferno_requirements_tools/ext/inferno_core/runnable.rb +22 -0
  260. data/lib/inferno_requirements_tools/tasks/requirements_coverage.rb +284 -0
  261. data/lib/requirements_config.yaml +22 -0
  262. metadata +341 -0
@@ -0,0 +1,897 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'fhir_resource_navigation'
4
+ require_relative 'search_test_properties'
5
+
6
+ module CancerRegistryReportingTestKit
7
+ module SearchTest
8
+ extend Forwardable
9
+ include FHIRResourceNavigation
10
+
11
+ def_delegators 'self.class', :metadata, :provenance_metadata, :properties
12
+ def_delegators 'properties',
13
+ :resource_type,
14
+ :search_param_names,
15
+ :saves_delayed_references?,
16
+ :first_search?,
17
+ :fixed_value_search?,
18
+ :manual_search_params?,
19
+ :possible_status_search?,
20
+ :test_medication_inclusion?,
21
+ :test_post_search?,
22
+ :token_search_params,
23
+ :test_reference_variants?,
24
+ :params_with_comparators,
25
+ :multiple_or_search_params
26
+
27
+ attr_accessor :manual_search_params
28
+
29
+ def all_search_params
30
+ @all_search_params ||=
31
+ patient_id_list.each_with_object({}) do |patient_id, params|
32
+ params[patient_id] ||= []
33
+ new_params =
34
+ if manual_search_params
35
+ manual_search_param_hash(patient_id)
36
+ elsif fixed_value_search?
37
+ fixed_value_search_param_values.map { |value| fixed_value_search_params(value, patient_id) }
38
+ else
39
+ [search_params_with_values(search_param_names, patient_id)]
40
+ end
41
+ new_params.reject! do |params|
42
+ params.any? { |_key, value| value.blank? }
43
+ end
44
+
45
+ params[patient_id].concat(new_params)
46
+ end
47
+ end
48
+
49
+ def all_provenance_revinclude_search_params
50
+ @all_provenance_revinclude_search_params ||=
51
+ all_search_params.transform_values! do |params_list|
52
+ params_list.map { |params| params.merge(_revinclude: 'Provenance:target') }
53
+ end
54
+ end
55
+
56
+ def any_valid_search_params?(search_params)
57
+ search_params.any? { |_patient_id, params| params.present? }
58
+ end
59
+
60
+ def run_provenance_revinclude_search_test
61
+ # TODO: skip if not supported?
62
+ skip_if !any_valid_search_params?(all_provenance_revinclude_search_params), unable_to_resolve_params_message
63
+
64
+ provenance_resources =
65
+ all_provenance_revinclude_search_params.flat_map do |_patient_id, params_list|
66
+ params_list.flat_map do |params|
67
+ fhir_search resource_type, params:, tags: tags(params)
68
+
69
+ perform_search_with_status(params, patient_id) if response[:status] == 400 && possible_status_search?
70
+
71
+ check_search_response
72
+
73
+ # TODO: check that only provenance resources for resources matching
74
+ # granular scopes returned
75
+ fetch_all_bundled_resources(additional_resource_types: ['Provenance'], params:)
76
+ .select { |resource| resource.resourceType == 'Provenance' }
77
+ end
78
+ end
79
+
80
+ scratch_provenance_resources[:all] ||= []
81
+ scratch_provenance_resources[:all].concat(provenance_resources)
82
+
83
+ save_delayed_references(provenance_resources, 'Provenance')
84
+
85
+ skip_if provenance_resources.empty?, no_resources_skip_message('Provenance')
86
+ end
87
+
88
+ def run_search_test
89
+ # TODO: skip if not supported?
90
+ skip_if !any_valid_search_params?(all_search_params), unable_to_resolve_params_message
91
+
92
+ resources_returned =
93
+ all_search_params.flat_map do |patient_id, params_list|
94
+ params_list.flat_map { |params| perform_search(params, patient_id) }
95
+ end
96
+
97
+ skip_if resources_returned.empty?, no_resources_skip_message
98
+
99
+ perform_multiple_or_search_test if multiple_or_search_params.present?
100
+ end
101
+
102
+ def perform_search(params, patient_id)
103
+ params.each do |key, value|
104
+ if key == "date" || key == "effective-time"
105
+ params[key] = ["ge#{value}", "le#{value}"]
106
+ end
107
+ end
108
+
109
+ fhir_search resource_type, params:, tags: tags(params)
110
+
111
+ perform_search_with_status(params, patient_id) if response[:status] == 400 && possible_status_search?
112
+
113
+ check_search_response
114
+
115
+ resources_returned =
116
+ fetch_all_bundled_resources(params:).select { |resource| resource.resourceType == resource_type }
117
+
118
+ return [] if resources_returned.blank?
119
+
120
+ perform_comparator_searches(params, patient_id) if params_with_comparators.present?
121
+
122
+ filter_conditions(resources_returned) if resource_type == 'Condition'
123
+ filter_devices(resources_returned) if resource_type == 'Device'
124
+
125
+ if first_search?
126
+ all_scratch_resources.concat(resources_returned).uniq!
127
+ scratch_resources_for_patient(patient_id).concat(resources_returned).uniq!
128
+ end
129
+
130
+ # resources_returned.each do |resource|
131
+ # check_resource_against_params(resource, params)
132
+ # end
133
+
134
+ save_delayed_references(resources_returned) if saves_delayed_references?
135
+
136
+ return resources_returned if all_search_variants_tested?
137
+
138
+ perform_post_search(resources_returned, params) if test_post_search?
139
+ test_medication_inclusion(resources_returned, params, patient_id) if test_medication_inclusion?
140
+ perform_reference_with_type_search(params, resources_returned.count) if test_reference_variants?
141
+ perform_search_with_system(params, patient_id) if token_search_params.present?
142
+
143
+ resources_returned
144
+ end
145
+
146
+ def perform_post_search(get_search_resources, params)
147
+ fhir_search resource_type, params:, search_method: :post
148
+
149
+ check_search_response
150
+
151
+ post_search_resources = fetch_all_bundled_resources.select { |resource| resource.resourceType == resource_type }
152
+
153
+ filter_conditions(post_search_resources) if resource_type == 'Condition'
154
+ filter_devices(post_search_resources) if resource_type == 'Device'
155
+
156
+ get_resource_count = get_search_resources.length
157
+ post_resource_count = post_search_resources.length
158
+
159
+ search_variant_test_records[:post_variant] = true
160
+
161
+ assert get_resource_count == post_resource_count,
162
+ 'Expected search by POST to return the same results as search by GET, ' \
163
+ "but GET search returned #{get_resource_count} resources, and POST search " \
164
+ "returned #{post_resource_count} resources."
165
+ end
166
+
167
+ def filter_devices(resources)
168
+ codes_to_include = implantable_device_codes&.split(',')&.map(&:strip)
169
+ return resources if codes_to_include.blank?
170
+
171
+ resources.select! do |resource|
172
+ resource&.type&.coding&.any? { |coding| codes_to_include.include?(coding.code) }
173
+ end
174
+ end
175
+
176
+ def filter_conditions(resources)
177
+ # HL7 JIRA FHIR-37917. US Core v5.0.1 does not required patient+category.
178
+ # In order to distinguish which resources matches the current profile, Inferno has to manually filter
179
+ # the result of first search, which is searching by patient.
180
+ selected_categories = []
181
+ all_search_params.each do |key, array_of_param_hashes|
182
+ categories = array_of_param_hashes.map { |param_hash| param_hash['category']}
183
+ selected_categories.concat categories
184
+ end
185
+ selected_categories.uniq!
186
+
187
+ resources.select! do |resource|
188
+ resource.category.any? do |category|
189
+ category.coding.any? do |coding|
190
+ selected_categories.include? coding.code
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ def search_and_check_response(params, resource_type = self.resource_type)
197
+ fhir_search resource_type, params:, tags: tags(params)
198
+
199
+ check_search_response
200
+ end
201
+
202
+ def check_search_response
203
+ assert_response_status(200)
204
+ assert_resource_type(:bundle)
205
+ # NOTE: how do we want to handle validating Bundles?
206
+ end
207
+
208
+ def search_variant_test_records
209
+ @search_variant_test_records ||= initial_search_variant_test_records
210
+ end
211
+
212
+ def initial_search_variant_test_records
213
+ {}.tap do |records|
214
+ records[:post_variant] = false if test_post_search?
215
+ records[:medication_inclusion] = false if test_medication_inclusion?
216
+ records[:reference_variants] = false if test_reference_variants?
217
+ records[:token_variants] = false if token_search_params.present?
218
+ records[:comparator_searches] = Set.new if params_with_comparators.present?
219
+ end
220
+ end
221
+
222
+ def all_search_variants_tested?
223
+ search_variant_test_records.all? { |_variant, tested| tested.present? } &&
224
+ all_comparator_searches_tested?
225
+ end
226
+
227
+ def all_comparator_searches_tested?
228
+ return true if params_with_comparators.blank?
229
+
230
+ Set.new(params_with_comparators) == search_variant_test_records[:comparator_searches]
231
+ end
232
+
233
+ def date_comparator_value(comparator, date)
234
+ date = date.start || date.end if date.is_a? FHIR::Period
235
+ case comparator
236
+ when 'lt', 'le'
237
+ comparator + (DateTime.xmlschema(date) + 1).xmlschema
238
+ when 'gt', 'ge'
239
+ comparator + (DateTime.xmlschema(date) - 1).xmlschema
240
+ else
241
+ # ''
242
+ raise "Unsupported comparator '#{comparator}'"
243
+ end
244
+ end
245
+
246
+ def required_comparators(name)
247
+ metadata
248
+ .search_definitions
249
+ .dig(name.to_sym, :comparators)
250
+ .select { |_comparator, expectation| expectation == 'SHALL' }
251
+ .keys
252
+ .map(&:to_s)
253
+ end
254
+
255
+ def perform_comparator_searches(params, patient_id)
256
+ params_with_comparators.each do |name|
257
+ next if search_variant_test_records[:comparator_searches].include? name
258
+
259
+ required_comparators(name).each do |comparator|
260
+ paths = search_param_paths(name).first
261
+ date_element = find_a_value_at(scratch_resources_for_patient(patient_id), paths)
262
+ params_with_comparator = params.merge(name => date_comparator_value(comparator, date_element))
263
+
264
+ search_and_check_response(params_with_comparator)
265
+
266
+ fetch_all_bundled_resources(params: params_with_comparator).each do |resource|
267
+ check_resource_against_params(resource, params_with_comparator) if resource.resourceType == resource_type
268
+ end
269
+ end
270
+
271
+ search_variant_test_records[:comparator_searches] << name
272
+ end
273
+ end
274
+
275
+ def perform_reference_with_type_search(params, resource_count)
276
+ return if resource_count.zero?
277
+ return if search_variant_test_records[:reference_variants]
278
+
279
+ new_search_params = params.merge('patient' => "Patient/#{params['patient']}")
280
+ search_and_check_response(new_search_params)
281
+
282
+ reference_with_type_resources =
283
+ fetch_all_bundled_resources(params: new_search_params)
284
+ .select { |resource| resource.resourceType == resource_type }
285
+
286
+ filter_conditions(reference_with_type_resources) if resource_type == 'Condition' && metadata.version == 'v5.0.1'
287
+ filter_devices(reference_with_type_resources) if resource_type == 'Device'
288
+
289
+ new_resource_count = reference_with_type_resources.count
290
+
291
+ assert new_resource_count == resource_count,
292
+ "Expected search by `#{params['patient']}` to to return the same results as searching " \
293
+ "by `#{new_search_params['patient']}`, but found #{resource_count} resources with " \
294
+ "`#{params['patient']}` and #{new_resource_count} with `#{new_search_params['patient']}`"
295
+
296
+ search_variant_test_records[:reference_variants] = true
297
+ end
298
+
299
+ def perform_search_with_system(params, patient_id)
300
+ return if search_variant_test_records[:token_variants]
301
+
302
+ new_search_params = search_params_with_values(token_search_params, patient_id, include_system: true)
303
+ return if new_search_params.any? { |_name, value| value.blank? }
304
+
305
+ search_params = params.merge(new_search_params)
306
+ search_and_check_response(search_params)
307
+
308
+ resources_returned =
309
+ fetch_all_bundled_resources(params: search_params)
310
+ .select { |resource| resource.resourceType == resource_type }
311
+
312
+ assert resources_returned.present?, 'No resources were returned when searching by `system|code`'
313
+
314
+ search_variant_test_records[:token_variants] = true
315
+ end
316
+
317
+ def perform_search_with_status(
318
+ original_params,
319
+ _patient_id,
320
+ status_search_values: self.status_search_values,
321
+ resource_type: self.resource_type
322
+ )
323
+ assert resource.is_a?(FHIR::OperationOutcome), 'Server returned a status of 400 without an OperationOutcome'
324
+ # TODO: warn about documenting status requirements
325
+ status_search_values.flat_map do |status_value|
326
+ search_params = original_params.merge("#{status_search_param_name}": status_value)
327
+
328
+ search_and_check_response(search_params)
329
+
330
+ entries = resource.entry.select { |entry| entry.resource.resourceType == resource_type }
331
+
332
+ if entries.present?
333
+ original_params.merge!("#{status_search_param_name}": status_value)
334
+ break
335
+ end
336
+ end
337
+ end
338
+
339
+ def status_search_param_name
340
+ @status_search_param_name ||=
341
+ metadata.search_definitions.keys.find { |key| key.to_s.include? 'status' }
342
+ end
343
+
344
+ def status_search_values
345
+ default_search_values(status_search_param_name)
346
+ end
347
+
348
+ def default_search_values(param_name)
349
+ definition = metadata.search_definitions[param_name]
350
+ return [] if definition.blank?
351
+
352
+ definition[:multiple_or] == 'SHALL' ? [definition[:values].join(',')] : Array.wrap(definition[:values])
353
+ end
354
+
355
+ def perform_multiple_or_search_test
356
+ resolved_one = false
357
+
358
+ all_search_params.each do |patient_id, params_list|
359
+ next unless params_list.present?
360
+
361
+ search_params = params_list.first
362
+ existing_values = {}
363
+ missing_values = {}
364
+
365
+ multiple_or_search_params.each do |param_name|
366
+ search_value = default_search_values(param_name.to_sym)
367
+ search_params = search_params.merge(param_name.to_s => search_value)
368
+ existing_values[param_name.to_sym] =
369
+ scratch_resources_for_patient(patient_id).map(&param_name.to_sym).compact.uniq
370
+ end
371
+
372
+ # skip patient without multiple-or values
373
+ next if existing_values.values.any?(&:empty?)
374
+
375
+ resolved_one = true
376
+
377
+ search_and_check_response(search_params)
378
+
379
+ resources_returned =
380
+ fetch_all_bundled_resources(params: search_params)
381
+ .select { |resource| resource.resourceType == resource_type }
382
+
383
+ multiple_or_search_params.each do |param_name|
384
+ missing_values[param_name.to_sym] =
385
+ existing_values[param_name.to_sym] - resources_returned.map(&param_name.to_sym)
386
+ end
387
+
388
+ missing_value_message = missing_values
389
+ .reject { |_param_name, missing_value| missing_value.empty? }
390
+ .map { |param_name, missing_value| "#{missing_value.join(',')} values from #{param_name}" }
391
+ .join(' and ')
392
+
393
+ assert missing_value_message.blank?,
394
+ "Could not find #{missing_value_message} in any of the resources returned for Patient/#{patient_id}"
395
+
396
+ break if resolved_one
397
+ end
398
+ end
399
+
400
+ def test_medication_inclusion(base_resources, params, patient_id)
401
+ return if search_variant_test_records[:medication_inclusion]
402
+
403
+ scratch[:medication_resources] ||= {}
404
+ scratch[:medication_resources][:all] ||= []
405
+ scratch[:medication_resources][patient_id] ||= []
406
+ scratch[:medication_resources][:contained] ||= []
407
+
408
+ base_resources_with_external_reference =
409
+ base_resources
410
+ .select { |request| request&.medicationReference&.present? }
411
+ .reject { |request| request&.medicationReference&.reference&.start_with? '#' }
412
+
413
+ contained_medications =
414
+ base_resources
415
+ .select { |request| request&.medicationReference&.reference&.start_with? '#' }
416
+ .flat_map(&:contained)
417
+ .select { |resource| resource.resourceType == 'Medication' }
418
+
419
+ scratch[:medication_resources][:all] += contained_medications
420
+ scratch[:medication_resources][patient_id] += contained_medications
421
+ scratch[:medication_resources][:contained] += contained_medications
422
+
423
+ return if base_resources_with_external_reference.blank?
424
+
425
+ search_params = params.merge(_include: "#{resource_type}:medication")
426
+
427
+ search_and_check_response(search_params)
428
+
429
+ medications =
430
+ fetch_all_bundled_resources(params: search_params)
431
+ .select { |resource| resource.resourceType == 'Medication' }
432
+ assert medications.present?, 'No Medications were included in the search results'
433
+
434
+ included_medications = medications.map { |medication| "#{medication.resourceType}/#{medication.id}" }
435
+
436
+ matched_base_resources = base_resources_with_external_reference.select do |base_resource|
437
+ included_medications.any? do |medication_reference|
438
+ is_reference_match?(base_resource.medicationReference.reference, medication_reference)
439
+ end
440
+ end
441
+
442
+ not_matched_included_medications = included_medications.select do |medication_reference|
443
+ matched_base_resources.none? do |base_resource|
444
+ is_reference_match?(base_resource.medicationReference.reference, medication_reference)
445
+ end
446
+ end
447
+
448
+ not_matched_included_medications_string = not_matched_included_medications.join(',')
449
+ assert not_matched_included_medications.empty?,
450
+ "No #{resource_type} references #{not_matched_included_medications_string} in the search result."
451
+
452
+ medications.uniq!(&:id)
453
+
454
+ scratch[:medication_resources][:all] += medications
455
+ scratch[:medication_resources][patient_id] += medications
456
+
457
+ search_variant_test_records[:medication_inclusion] = true
458
+ end
459
+
460
+ def is_reference_match?(reference, local_reference)
461
+ regex_pattern = %r{^(#{Regexp.escape(local_reference)}|\S+/#{Regexp.escape(local_reference)}(?:[/|]\S+)*)$}
462
+ reference.match?(regex_pattern)
463
+ end
464
+
465
+ def all_scratch_resources
466
+ scratch_resources[:all] ||= []
467
+ end
468
+
469
+ def scratch_resources_for_patient(patient_id)
470
+ return all_scratch_resources if patient_id.nil?
471
+
472
+ scratch_resources[patient_id] ||= []
473
+ end
474
+
475
+ def references_to_save(resource_type = nil)
476
+ reference_metadata = resource_type == 'Provenance' ? provenance_metadata : metadata
477
+ reference_metadata.delayed_references
478
+ end
479
+
480
+ def fixed_value_search_param_name
481
+ (search_param_names - ['patient']).first
482
+ end
483
+
484
+ def manual_search_param_hash(patient_id)
485
+ single_patient_manual_search_params = [patient_id] + manual_search_params
486
+ split_values = single_patient_manual_search_params.map { |value| value.split(',') }
487
+
488
+ combinations = split_values[0].product(*split_values[1..])
489
+
490
+ combinations.map do |combination|
491
+ search_param_names.zip(combination).to_h
492
+ end
493
+ end
494
+
495
+ def fixed_value_search_param_values
496
+ metadata.search_definitions[fixed_value_search_param_name.to_sym][:values]
497
+ end
498
+
499
+ def fixed_value_search_params(value, patient_id)
500
+ search_param_names.each_with_object({}) do |name, params|
501
+ params[name] = patient_id_param?(name) ? patient_id : value
502
+ end
503
+ end
504
+
505
+ def manual_search_param_name
506
+ (search_param_names - ['patient']).first
507
+ end
508
+
509
+ def manual_search_param_values
510
+ metadata.search_definitions[manual_search_param_name.to_sym][:values]
511
+ end
512
+
513
+ def manual_search_params_full(value, patient_id)
514
+ search_param_names.each_with_object({}) do |name, params|
515
+ params[name] = patient_id_param?(name) ? patient_id : value
516
+ end
517
+ end
518
+
519
+ def search_params_with_values(search_param_names, patient_id, include_system: false)
520
+ resources = scratch_resources_for_patient(patient_id)
521
+
522
+ if resources.empty?
523
+ return search_param_names.each_with_object({}) do |name, params|
524
+ value = patient_id_param?(name) ? patient_id : nil
525
+ params[name] = value
526
+ end
527
+ end
528
+
529
+ resources.each_with_object({}) do |resource, outer_params|
530
+ results_from_one_resource = search_param_names.each_with_object({}) do |name, params|
531
+ value = if patient_id_param?(name)
532
+ patient_id
533
+ else
534
+ search_param_value(name, resource,
535
+ include_system: include_system)
536
+ end
537
+ params[name] = value
538
+ end
539
+
540
+ outer_params.merge!(results_from_one_resource)
541
+
542
+ # stop if all parameter values are found
543
+ return outer_params if outer_params.all? { |_key, value| value.present? }
544
+ end
545
+ end
546
+
547
+ def patient_id_list
548
+ return [nil] unless respond_to? :patient_ids
549
+
550
+ patient_ids.split(',').map(&:strip)
551
+ end
552
+
553
+ def patient_search?
554
+ search_param_names.any? { |name| patient_id_param? name }
555
+ end
556
+
557
+ def patient_id_param?(name)
558
+ name == 'patient' || (name == '_id' && resource_type == 'Patient')
559
+ end
560
+
561
+ def search_param_paths(name)
562
+ paths = metadata.search_definitions[name.to_sym][:paths]
563
+ if paths.first == 'class'
564
+ paths[0] = 'local_class'
565
+ elsif paths.first == 'method'
566
+ paths[0] = 'local_method'
567
+ end
568
+
569
+ paths
570
+ end
571
+
572
+ def all_search_params_present?(params)
573
+ params.all? { |_name, value| value.present? }
574
+ end
575
+
576
+ def array_of_codes(array)
577
+ array.map { |name| "`#{name}`" }.join(', ')
578
+ end
579
+
580
+ def unable_to_resolve_params_message
581
+ "Could not find values for all search params #{array_of_codes(search_param_names)}"
582
+ end
583
+
584
+ def empty_search_params_message(empty_search_params)
585
+ "Could not find values for the search parameters #{array_of_codes(empty_search_params.keys)}"
586
+ end
587
+
588
+ def no_resources_skip_message(resource_type = self.resource_type)
589
+ msg = "No #{resource_type} resources appear to be available"
590
+
591
+ if resource_type == 'Device' && implantable_device_codes.present?
592
+ msg.concat(" with the following Device Type Code filter: #{implantable_device_codes}")
593
+ end
594
+
595
+ "#{msg}. Please use patients with more information"
596
+ end
597
+
598
+ def fetch_all_bundled_resources(
599
+ reply_handler: nil,
600
+ max_pages: 20,
601
+ additional_resource_types: [],
602
+ resource_type: self.resource_type,
603
+ params: nil
604
+ )
605
+ page_count = 1
606
+ resources = []
607
+ bundle = resource
608
+
609
+ until bundle.nil? || page_count == max_pages
610
+ resources += bundle&.entry&.map { |entry| entry&.resource }
611
+ next_bundle_link = bundle&.link&.find { |link| link.relation == 'next' }&.url
612
+ reply_handler&.call(response)
613
+
614
+ break if next_bundle_link.blank?
615
+
616
+ reply = fhir_client.raw_read_url(next_bundle_link)
617
+
618
+ store_request('outgoing', tags: tags(params)) { reply }
619
+ error_message = cant_resolve_next_bundle_message(next_bundle_link)
620
+
621
+ assert_response_status(200)
622
+ assert_valid_json(reply.body, error_message)
623
+
624
+ bundle = fhir_client.parse_reply(FHIR::Bundle, fhir_client.default_format, reply)
625
+
626
+ page_count += 1
627
+ end
628
+
629
+ valid_resource_types = [resource_type, 'OperationOutcome'].concat(additional_resource_types)
630
+ valid_resource_types << 'Medication' if %w[MedicationRequest MedicationDispense].include?(resource_type)
631
+
632
+ invalid_resource_types =
633
+ resources.reject { |entry| valid_resource_types.include? entry.resourceType }
634
+ .map(&:resourceType)
635
+ .uniq
636
+
637
+ if invalid_resource_types.any?
638
+ info "Received resource type(s) #{invalid_resource_types.join(', ')} in search bundle, " \
639
+ "but only expected resource types #{valid_resource_types.join(', ')}. " \
640
+ 'This is unusual but allowed if the server believes additional resource types are relevant.'
641
+ end
642
+
643
+ resources
644
+ end
645
+
646
+ def cant_resolve_next_bundle_message(link)
647
+ "Could not resolve next bundle: #{link}"
648
+ end
649
+
650
+ def search_param_value(name, resource, include_system: false)
651
+ paths = search_param_paths(name)
652
+ search_value = nil
653
+ paths.each do |path|
654
+ element = find_a_value_at(resource, path) { |element| element_has_valid_value?(element, include_system) }
655
+
656
+ search_value =
657
+ case element
658
+ when FHIR::Period
659
+ if element.start.present?
660
+ "gt#{(DateTime.xmlschema(element.start) - 1).xmlschema}"
661
+ else
662
+ end_datetime = get_fhir_datetime_range(element.end)[:end]
663
+ "lt#{(end_datetime + 1).xmlschema}"
664
+ end
665
+ when FHIR::Reference
666
+ element.reference
667
+ when FHIR::CodeableConcept
668
+ if include_system
669
+ coding =
670
+ find_a_value_at(element, 'coding') { |coding| coding.code.present? && coding.system.present? }
671
+ "#{coding.system}|#{coding.code}"
672
+ else
673
+ find_a_value_at(element, 'coding.code')
674
+ end
675
+ when FHIR::Identifier
676
+ include_system ? "#{element.system}|#{element.value}" : element.value
677
+ when FHIR::Coding
678
+ include_system ? "#{element.system}|#{element.code}" : element.code
679
+ when FHIR::HumanName
680
+ element.family || element.given&.first || element.text
681
+ when FHIR::Address
682
+ element.text || element.city || element.state || element.postalCode || element.country
683
+ when CancerRegistryReportingTestKit::PrimitiveType
684
+ element.value
685
+ else
686
+ if metadata.version != 'v3.1.1' &&
687
+ metadata.search_definitions[name.to_sym][:type] == 'date' &&
688
+ params_with_comparators&.include?(name)
689
+ # convert date search to greath-than comparator search with correct precision
690
+ # For all date search parameters:
691
+ # Patient.birthDate does not mandate comparators so cannot be converted
692
+ # Goal.target-date has day precision
693
+ # All others have second + time offset precision
694
+ if /^\d{4}(-\d{2})?$/.match?(element) || # YYYY or YYYY-MM
695
+ (/^\d{4}-\d{2}-\d{2}$/.match?(element) && resource_type != 'Goal') # YYY-MM-DD AND Resource is NOT Goal
696
+ "gt#{(DateTime.xmlschema(element) - 1).xmlschema}"
697
+ else
698
+ element
699
+ end
700
+ else
701
+ element
702
+ end
703
+ end
704
+
705
+ break if search_value.present?
706
+ end
707
+
708
+ search_value&.gsub(',', '\\,')
709
+ end
710
+
711
+ def element_has_valid_value?(element, include_system)
712
+ case element
713
+ when FHIR::Reference
714
+ element.reference.present?
715
+ when FHIR::CodeableConcept
716
+ if include_system
717
+ coding =
718
+ find_a_value_at(element, 'coding') { |coding| coding.code.present? && coding.system.present? }
719
+ coding.present?
720
+ else
721
+ find_a_value_at(element, 'coding.code').present?
722
+ end
723
+ when FHIR::Identifier
724
+ include_system ? element.value.present? && element.system.present? : element.value.present?
725
+ when FHIR::Coding
726
+ include_system ? element.code.present? && element.system.present? : element.code.present?
727
+ when FHIR::HumanName
728
+ (element.family || element.given&.first || element.text).present?
729
+ when FHIR::Address
730
+ (element.text || element.city || element.state || element.postalCode || element.country).present?
731
+ when CancerRegistryReportingTestKit::PrimitiveType
732
+ element.value.present?
733
+ else
734
+ true
735
+ end
736
+ end
737
+
738
+ def save_resource_reference(resource_type, reference)
739
+ scratch[:references] ||= {}
740
+ scratch[:references][resource_type] ||= Set.new
741
+ scratch[:references][resource_type] << reference
742
+ end
743
+
744
+ def save_delayed_references(resources, containing_resource_type = resource_type)
745
+ resources.each do |resource|
746
+ references_to_save(containing_resource_type).each do |reference_to_save|
747
+ resolve_path(resource, reference_to_save[:path])
748
+ .select do |reference|
749
+ reference.is_a?(FHIR::Reference) &&
750
+ !reference.contained? && reference.reference.present?
751
+ end
752
+ .each do |reference|
753
+ resource_type = reference.resource_class.name.demodulize
754
+ need_to_save = reference_to_save[:resources].include?(resource_type)
755
+ next unless need_to_save
756
+
757
+ save_resource_reference(resource_type, reference)
758
+ end
759
+ end
760
+ end
761
+ end
762
+
763
+ #### RESULT CHECKING ####
764
+
765
+ def check_resource_against_params(resource, params)
766
+ params.each do |name, escaped_search_value|
767
+ values_found = []
768
+ search_value = unescape_search_value(escaped_search_value)
769
+
770
+ match_found = resource_matches_param?(resource, name, escaped_search_value, values_found)
771
+
772
+ assert match_found,
773
+ "#{resource_type}/#{resource.id} did not match the search parameters:\n" \
774
+ "* Expected: #{unescape_search_value(search_value)}\n" \
775
+ "* Found: #{values_found.map(&:inspect).join(', ')}"
776
+ end
777
+ end
778
+
779
+ def unescape_search_value(value)
780
+ value&.gsub('\\,', ',')
781
+ end
782
+
783
+ def resource_matches_param?(resource, search_param_name, escaped_search_value, values_found = [])
784
+ search_value = unescape_search_value(escaped_search_value)
785
+ paths = search_param_paths(search_param_name)
786
+
787
+ match_found = false
788
+
789
+ paths.each do |path|
790
+ type = metadata.search_definitions[search_param_name.to_sym][:type]
791
+
792
+ resolve_path(resource, path).each do |value|
793
+ values_found <<
794
+ if value.is_a? FHIR::Reference
795
+ value.reference
796
+ elsif value.is_a? CancerRegistryReportingTestKit::PrimitiveType
797
+ value.value
798
+ else
799
+ value
800
+ end
801
+ end
802
+
803
+ values_found.compact!
804
+ match_found =
805
+ case type
806
+ when 'Period', 'date', 'instant', 'dateTime'
807
+ values_found.any? { |date| validate_date_search(search_value, date) }
808
+ when 'HumanName'
809
+ # When a string search parameter refers to the types HumanName and Address,
810
+ # the search covers the elements of type string, and does not cover elements such as use and period
811
+ # https://www.hl7.org/fhir/search.html#string
812
+ search_value_downcase = search_value.downcase
813
+ values_found.any? do |name|
814
+ name&.text&.downcase&.start_with?(search_value_downcase) ||
815
+ name&.family&.downcase&.start_with?(search_value_downcase) ||
816
+ name&.given&.any? { |given| given.downcase.start_with?(search_value_downcase) } ||
817
+ name&.prefix&.any? { |prefix| prefix.downcase.start_with?(search_value_downcase) } ||
818
+ name&.suffix&.any? { |suffix| suffix.downcase.start_with?(search_value_downcase) }
819
+ end
820
+ when 'Address'
821
+ search_value_downcase = search_value.downcase
822
+ values_found.any? do |address|
823
+ address&.text&.downcase&.start_with?(search_value_downcase) ||
824
+ address&.city&.downcase&.start_with?(search_value_downcase) ||
825
+ address&.state&.downcase&.start_with?(search_value_downcase) ||
826
+ address&.postalCode&.downcase&.start_with?(search_value_downcase) ||
827
+ address&.country&.downcase&.start_with?(search_value_downcase)
828
+ end
829
+ when 'CodeableConcept'
830
+ # FHIR token search (https://www.hl7.org/fhir/search.html#token): "When in doubt, servers SHOULD
831
+ # treat tokens in a case-insensitive manner, on the grounds that including undesired data has
832
+ # less safety implications than excluding desired behavior".
833
+ codings = values_found.flat_map(&:coding)
834
+ if search_value.include? '|'
835
+ system = search_value.split('|').first
836
+ code = search_value.split('|').last
837
+ codings&.any? { |coding| coding.system == system && coding.code&.casecmp?(code) }
838
+ else
839
+ codings&.any? { |coding| coding.code&.casecmp?(search_value) }
840
+ end
841
+ when 'Coding'
842
+ if search_value.include? '|'
843
+ system = search_value.split('|').first
844
+ code = search_value.split('|').last
845
+ values_found.any? { |coding| coding.system == system && coding.code&.casecmp?(code) }
846
+ else
847
+ values_found.any? { |coding| coding.code&.casecmp?(search_value) }
848
+ end
849
+ when 'Identifier'
850
+ if search_value.include? '|'
851
+ values_found.any? { |identifier| "#{identifier.system}|#{identifier.value}" == search_value }
852
+ else
853
+ values_found.any? { |identifier| identifier.value == search_value }
854
+ end
855
+ when 'string'
856
+ searched_values = search_value.downcase.split(/(?<!\\\\),/).map { |string| string.gsub('\\,', ',') }
857
+ values_found.any? do |value_found|
858
+ searched_values.any? { |searched_value| value_found.downcase.starts_with? searched_value }
859
+ end
860
+ else
861
+ # searching by patient requires special case because we are searching by a resource identifier
862
+ # references can also be URLs, so we may need to resolve those URLs
863
+ if %w[subject patient].include? search_param_name.to_s
864
+ id = search_value.split('Patient/').last
865
+ possible_values = [id, "Patient/#{id}", "#{url}/Patient/#{id}"]
866
+ values_found.any? do |reference|
867
+ possible_values.include? reference
868
+ end
869
+ else
870
+ search_values = search_value.split(/(?<!\\\\),/).map { |string| string.gsub('\\,', ',') }
871
+ values_found.any? { |value_found| search_values.include? value_found }
872
+ end
873
+ end
874
+
875
+ break if match_found
876
+ end
877
+
878
+ match_found
879
+ end
880
+
881
+ def tags(params)
882
+ return nil unless config.options[:tag_requests]
883
+
884
+ return nil if params.blank?
885
+
886
+ if %w[Condition DiagnosticReport DocumentReference Observation ServiceRequest].include? resource_type
887
+ return [search_params_tag(params)]
888
+ end
889
+
890
+ nil
891
+ end
892
+
893
+ def search_params_tag(params)
894
+ "#{resource_type}?#{params.keys.join('&')}"
895
+ end
896
+ end
897
+ end