au_ps_inferno 0.1.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 (195) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/lib/au_ps_inferno/generator/constants.rb +61 -0
  4. data/lib/au_ps_inferno/generator/generator.rb +76 -0
  5. data/lib/au_ps_inferno/generator/generator_group_based_metadata_module.rb +113 -0
  6. data/lib/au_ps_inferno/generator/ig_resources_extractor.rb +149 -0
  7. data/lib/au_ps_inferno/generator/metadata_manager.rb +608 -0
  8. data/lib/au_ps_inferno/generator/naming.rb +52 -0
  9. data/lib/au_ps_inferno/generator/sections_validation_group_generator.rb +182 -0
  10. data/lib/au_ps_inferno/generator/test_file_generator.rb +77 -0
  11. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_bundle_instance.rb +56 -0
  12. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_bundle_must_support_conformance/suite_au_ps_bundle_instance_au_ps_bundle_must_support_conformance_bundle_must_support_populated.rb +21 -0
  13. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_bundle_must_support_conformance.rb +16 -0
  14. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_attester/suite_au_ps_bundle_instance_au_ps_composition_attester_attester_party_ms_elements.rb +21 -0
  15. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_attester/suite_au_ps_bundle_instance_au_ps_composition_attester_attester_party_ms_identifier_slices.rb +21 -0
  16. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_attester/suite_au_ps_bundle_instance_au_ps_composition_attester_attester_party_ms_subelements.rb +21 -0
  17. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_attester.rb +26 -0
  18. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_author/suite_au_ps_bundle_instance_au_ps_composition_author_author_ms_elements.rb +21 -0
  19. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_author/suite_au_ps_bundle_instance_au_ps_composition_author_author_ms_identifier_slices.rb +21 -0
  20. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_author/suite_au_ps_bundle_instance_au_ps_composition_author_author_ms_subelements.rb +21 -0
  21. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_author/suite_au_ps_bundle_instance_au_ps_composition_author_author_resource_type_is_valid.rb +21 -0
  22. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_author.rb +28 -0
  23. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_custodian/suite_au_ps_bundle_instance_au_ps_composition_custodian_custodian_ms_elements.rb +21 -0
  24. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_custodian/suite_au_ps_bundle_instance_au_ps_composition_custodian_custodian_ms_identifier_slices.rb +21 -0
  25. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_custodian/suite_au_ps_bundle_instance_au_ps_composition_custodian_custodian_ms_subelements.rb +21 -0
  26. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_custodian.rb +26 -0
  27. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_mandatory_sections/composition_mandatory_sections_mandatory_sections_entry_profiles.rb +21 -0
  28. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_mandatory_sections/suite_au_ps_bundle_instance_au_ps_composition_mandatory_sections_sections_shall_populated.rb +21 -0
  29. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_mandatory_sections.rb +20 -0
  30. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_mandatory_ms_populated.rb +21 -0
  31. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_ms_subelements_populated.rb +22 -0
  32. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_optional_ms_populated.rb +22 -0
  33. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_optional_ms_slices.rb +22 -0
  34. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_must_support_conformance.rb +28 -0
  35. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_optional_sections/suite_au_ps_bundle_instance_au_ps_composition_optional_sections_optional_sections_entry_profiles.rb +22 -0
  36. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_optional_sections/suite_au_ps_bundle_instance_au_ps_composition_optional_sections_sections_may_populated.rb +24 -0
  37. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_optional_sections.rb +20 -0
  38. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_recommended_sections/composition_recommended_sections_recommended_sections_entry_profiles.rb +21 -0
  39. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_recommended_sections/suite_au_ps_bundle_instance_au_ps_composition_recommended_sections_sections_should_populated.rb +22 -0
  40. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_recommended_sections.rb +20 -0
  41. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_subject/suite_au_ps_bundle_instance_au_ps_composition_subject_subject_ms_elements.rb +21 -0
  42. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_subject/suite_au_ps_bundle_instance_au_ps_composition_subject_subject_ms_identifier_slices.rb +21 -0
  43. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_subject/suite_au_ps_bundle_instance_au_ps_composition_subject_subject_ms_subelements_populated.rb +21 -0
  44. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_subject/suite_au_ps_bundle_instance_au_ps_composition_subject_subject_resource_type_is_valid.rb +21 -0
  45. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_subject.rb +28 -0
  46. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_undefined_sections/suite_au_ps_bundle_instance_au_ps_composition_undefined_sections_sections_may_undefined.rb +24 -0
  47. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/au_ps_composition_undefined_sections.rb +18 -0
  48. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/bundle_validation/suite_au_ps_bundle_instance_bundle_validation_bundle_valid.rb +21 -0
  49. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/bundle_validation/suite_au_ps_bundle_instance_bundle_validation_bundle_valid_ips.rb +21 -0
  50. data/lib/au_ps_inferno/suite/au_ps_bundle_instance/bundle_validation.rb +20 -0
  51. data/lib/au_ps_inferno/suite/au_ps_retrieve_cs_group/au_ps_cs_is_valid_test.rb +21 -0
  52. data/lib/au_ps_inferno/suite/au_ps_retrieve_cs_group/au_ps_cs_supports_au_ps_profiles.rb +40 -0
  53. data/lib/au_ps_inferno/suite/au_ps_retrieve_cs_group/au_ps_cs_supports_ips_recommended_ops.rb +19 -0
  54. data/lib/au_ps_inferno/suite/au_ps_retrieve_cs_group/au_ps_retrieve_cs_group.rb +47 -0
  55. data/lib/au_ps_inferno/suite/au_ps_v100.rb +39 -0
  56. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_bundle_must_support_conformance/bundle_must_support_conformance_bundle_must_support_populated.rb +21 -0
  57. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_bundle_must_support_conformance.rb +16 -0
  58. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_attester/composition_attester_attester_party_ms_elements.rb +21 -0
  59. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_attester/composition_attester_attester_party_ms_identifier_slices.rb +21 -0
  60. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_attester/composition_attester_attester_party_ms_subelements.rb +21 -0
  61. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_attester.rb +26 -0
  62. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_author/composition_author_author_ms_elements.rb +21 -0
  63. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_author/composition_author_author_ms_identifier_slices.rb +21 -0
  64. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_author/composition_author_author_ms_subelements.rb +21 -0
  65. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_author/composition_author_author_resource_type_is_valid.rb +21 -0
  66. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_author.rb +28 -0
  67. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_custodian/composition_custodian_custodian_ms_elements.rb +21 -0
  68. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_custodian/composition_custodian_custodian_ms_identifier_slices.rb +21 -0
  69. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_custodian/composition_custodian_custodian_ms_subelements.rb +21 -0
  70. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_custodian.rb +26 -0
  71. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_mandatory_sections/composition_mandatory_sections_mandatory_sections_entry_profiles.rb +21 -0
  72. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_mandatory_sections/composition_mandatory_sections_sections_shall_populated.rb +21 -0
  73. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_mandatory_sections.rb +20 -0
  74. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_mandatory_ms_populated.rb +21 -0
  75. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_ms_subelements_populated.rb +22 -0
  76. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_optional_ms_populated.rb +22 -0
  77. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_optional_ms_slices.rb +22 -0
  78. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_must_support_conformance.rb +28 -0
  79. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_optional_sections/composition_optional_sections_optional_sections_entry_profiles.rb +22 -0
  80. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_optional_sections/composition_optional_sections_sections_may_populated.rb +24 -0
  81. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_optional_sections.rb +20 -0
  82. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_recommended_sections/composition_recommended_sections_recommended_sections_entry_profiles.rb +21 -0
  83. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_recommended_sections/composition_recommended_sections_sections_should_populated.rb +22 -0
  84. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_recommended_sections.rb +20 -0
  85. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_subject/composition_subject_subject_ms_elements.rb +21 -0
  86. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_subject/composition_subject_subject_ms_identifier_slices.rb +21 -0
  87. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_subject/composition_subject_subject_ms_subelements_populated.rb +21 -0
  88. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_subject/composition_subject_subject_resource_type_is_valid.rb +21 -0
  89. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_subject.rb +28 -0
  90. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_undefined_sections/composition_undefined_sections_sections_may_undefined.rb +24 -0
  91. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/au_ps_composition_undefined_sections.rb +18 -0
  92. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/bundle_validation/suite_generate_au_ps_using_ips_summary_validation_tests_bundle_validation_bundle_valid.rb +21 -0
  93. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/bundle_validation/suite_generate_au_ps_using_ips_summary_validation_tests_bundle_validation_bundle_valid_ips.rb +21 -0
  94. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/bundle_validation.rb +20 -0
  95. data/lib/au_ps_inferno/suite/generate_au_ps_using_ips_summary_validation_tests/generate_au_ps_using_ips_summary_validation_tests.rb +56 -0
  96. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_bundle_must_support_conformance/bundle_must_support_conformance_bundle_must_support_populated.rb +21 -0
  97. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_bundle_must_support_conformance.rb +16 -0
  98. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_attester/composition_attester_attester_party_ms_elements.rb +21 -0
  99. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_attester/composition_attester_attester_party_ms_identifier_slices.rb +21 -0
  100. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_attester/composition_attester_attester_party_ms_subelements.rb +21 -0
  101. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_attester.rb +26 -0
  102. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_author/composition_author_author_ms_identifier_slices.rb +21 -0
  103. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_author/composition_author_author_resource_type_is_valid.rb +21 -0
  104. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_author/suite_retrieve_au_ps_bundle_validation_tests_au_ps_composition_author_author_ms_elements.rb +21 -0
  105. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_author/suite_retrieve_au_ps_bundle_validation_tests_au_ps_composition_author_author_ms_subelements.rb +21 -0
  106. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_author.rb +28 -0
  107. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_custodian/composition_custodian_custodian_ms_identifier_slices.rb +21 -0
  108. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_custodian/composition_custodian_custodian_ms_subelements.rb +21 -0
  109. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_custodian/suite_retrieve_au_ps_bundle_validation_tests_au_ps_composition_custodian_custodian_ms_elements.rb +21 -0
  110. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_custodian.rb +26 -0
  111. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_mandatory_sections/composition_mandatory_sections_mandatory_sections_entry_profiles.rb +21 -0
  112. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_mandatory_sections/composition_mandatory_sections_sections_shall_populated.rb +21 -0
  113. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_mandatory_sections.rb +20 -0
  114. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_mandatory_ms_populated.rb +21 -0
  115. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_ms_subelements_populated.rb +22 -0
  116. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_optional_ms_populated.rb +22 -0
  117. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_must_support_conformance/composition_must_support_conformance_composition_optional_ms_slices.rb +22 -0
  118. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_must_support_conformance.rb +28 -0
  119. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_optional_sections/composition_optional_sections_optional_sections_entry_profiles.rb +22 -0
  120. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_optional_sections/composition_optional_sections_sections_may_populated.rb +24 -0
  121. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_optional_sections.rb +20 -0
  122. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_recommended_sections/composition_recommended_sections_recommended_sections_entry_profiles.rb +21 -0
  123. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_recommended_sections/composition_recommended_sections_sections_should_populated.rb +22 -0
  124. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_recommended_sections.rb +20 -0
  125. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_subject/composition_subject_subject_ms_identifier_slices.rb +21 -0
  126. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_subject/composition_subject_subject_ms_subelements_populated.rb +21 -0
  127. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_subject/composition_subject_subject_resource_type_is_valid.rb +21 -0
  128. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_subject/suite_retrieve_au_ps_bundle_validation_tests_au_ps_composition_subject_subject_ms_elements.rb +21 -0
  129. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_subject.rb +28 -0
  130. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_undefined_sections/composition_undefined_sections_sections_may_undefined.rb +24 -0
  131. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/au_ps_composition_undefined_sections.rb +18 -0
  132. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/bundle_validation/suite_retrieve_au_ps_bundle_validation_tests_bundle_validation_bundle_valid.rb +21 -0
  133. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/bundle_validation/suite_retrieve_au_ps_bundle_validation_tests_bundle_validation_bundle_valid_ips.rb +21 -0
  134. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/bundle_validation.rb +20 -0
  135. data/lib/au_ps_inferno/suite/retrieve_au_ps_bundle_validation_tests/retrieve_au_ps_bundle_validation_tests.rb +56 -0
  136. data/lib/au_ps_inferno/utils/attester/basic_test_attester_identifier_slices.rb +74 -0
  137. data/lib/au_ps_inferno/utils/attester/basic_test_attester_message_helpers.rb +16 -0
  138. data/lib/au_ps_inferno/utils/attester/basic_test_attester_module.rb +14 -0
  139. data/lib/au_ps_inferno/utils/attester/basic_test_attester_resource.rb +54 -0
  140. data/lib/au_ps_inferno/utils/author/basic_test_author_module.rb +12 -0
  141. data/lib/au_ps_inferno/utils/author/basic_test_author_resource.rb +32 -0
  142. data/lib/au_ps_inferno/utils/author/basic_test_author_tests.rb +24 -0
  143. data/lib/au_ps_inferno/utils/basic_test/bundle_module.rb +87 -0
  144. data/lib/au_ps_inferno/utils/basic_test/capability_operations_module.rb +37 -0
  145. data/lib/au_ps_inferno/utils/basic_test/composition_elements_and_slices_module.rb +98 -0
  146. data/lib/au_ps_inferno/utils/basic_test/composition_section_check_resources_ms_elements_module.rb +197 -0
  147. data/lib/au_ps_inferno/utils/basic_test/composition_section_read_issues_helpers_module.rb +66 -0
  148. data/lib/au_ps_inferno/utils/basic_test/composition_section_read_module.rb +127 -0
  149. data/lib/au_ps_inferno/utils/basic_test/composition_subelements_module.rb +84 -0
  150. data/lib/au_ps_inferno/utils/basic_test/ms_elements_populated_helpers_module.rb +38 -0
  151. data/lib/au_ps_inferno/utils/basic_test/ms_elements_populated_module.rb +71 -0
  152. data/lib/au_ps_inferno/utils/basic_test/ms_identifier_slices_module.rb +61 -0
  153. data/lib/au_ps_inferno/utils/basic_test/ms_sub_elements_populated_module.rb +128 -0
  154. data/lib/au_ps_inferno/utils/basic_test/paths_module.rb +26 -0
  155. data/lib/au_ps_inferno/utils/basic_test/resolve_path_debug_module.rb +45 -0
  156. data/lib/au_ps_inferno/utils/basic_test/resolve_resource_type_module.rb +52 -0
  157. data/lib/au_ps_inferno/utils/basic_test/resource_helpers_module.rb +129 -0
  158. data/lib/au_ps_inferno/utils/basic_test/scratch_bundle_entries_module.rb +30 -0
  159. data/lib/au_ps_inferno/utils/basic_test/section_bundle_validation_module.rb +70 -0
  160. data/lib/au_ps_inferno/utils/basic_test_class.rb +56 -0
  161. data/lib/au_ps_inferno/utils/basic_test_contants_module.rb +32 -0
  162. data/lib/au_ps_inferno/utils/basic_test_with_url.rb +8 -0
  163. data/lib/au_ps_inferno/utils/basic_validate_bundle_test.rb +43 -0
  164. data/lib/au_ps_inferno/utils/bundle_decorator.rb +81 -0
  165. data/lib/au_ps_inferno/utils/bundle_entry_decorator.rb +19 -0
  166. data/lib/au_ps_inferno/utils/bundle_is_valid_class.rb +31 -0
  167. data/lib/au_ps_inferno/utils/capability_statement_decorator.rb +20 -0
  168. data/lib/au_ps_inferno/utils/composition_decorator.rb +56 -0
  169. data/lib/au_ps_inferno/utils/composition_utils/boolean_and_stats.rb +28 -0
  170. data/lib/au_ps_inferno/utils/composition_utils.rb +29 -0
  171. data/lib/au_ps_inferno/utils/custodian/basic_test_custodian_identifier_slices.rb +47 -0
  172. data/lib/au_ps_inferno/utils/custodian/basic_test_custodian_module.rb +14 -0
  173. data/lib/au_ps_inferno/utils/custodian/basic_test_custodian_resource.rb +32 -0
  174. data/lib/au_ps_inferno/utils/custodian/basic_test_custodian_tests.rb +19 -0
  175. data/lib/au_ps_inferno/utils/inferno_suite_generator_compat.rb +7 -0
  176. data/lib/au_ps_inferno/utils/ips_bundle_is_valid_class.rb +15 -0
  177. data/lib/au_ps_inferno/utils/ips_retrieve_bundle_test_class.rb +17 -0
  178. data/lib/au_ps_inferno/utils/ips_summary_valid_bundle_class.rb +15 -0
  179. data/lib/au_ps_inferno/utils/metadata_manager.rb +58 -0
  180. data/lib/au_ps_inferno/utils/retrieve_bundle_test_class.rb +81 -0
  181. data/lib/au_ps_inferno/utils/rich_message_class.rb +23 -0
  182. data/lib/au_ps_inferno/utils/rich_validation_message.rb +42 -0
  183. data/lib/au_ps_inferno/utils/section_decorator.rb +23 -0
  184. data/lib/au_ps_inferno/utils/section_names_mapping.rb +31 -0
  185. data/lib/au_ps_inferno/utils/section_test_class.rb +18 -0
  186. data/lib/au_ps_inferno/utils/section_test_module.rb +16 -0
  187. data/lib/au_ps_inferno/utils/structure_definition_decorator.rb +28 -0
  188. data/lib/au_ps_inferno/utils/subject/basic_test_subject_module.rb +12 -0
  189. data/lib/au_ps_inferno/utils/subject/basic_test_subject_patient_ms_other_tests.rb +15 -0
  190. data/lib/au_ps_inferno/utils/subject/basic_test_subject_resource.rb +21 -0
  191. data/lib/au_ps_inferno/utils/summary_valid_bundle_class.rb +84 -0
  192. data/lib/au_ps_inferno/utils/validator_helpers.rb +92 -0
  193. data/lib/au_ps_inferno/version.rb +6 -0
  194. data/lib/au_ps_inferno.rb +3 -0
  195. metadata +310 -0
@@ -0,0 +1,608 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require_relative 'constants'
6
+ require_relative '../../au_ps_inferno/utils/structure_definition_decorator'
7
+
8
+ class Generator
9
+ # Builds and persists metadata for Composition sections from IG resources.
10
+ #
11
+ # Extracts Composition StructureDefinition sections and their entry constraints
12
+ # (profiles, cardinality, mustSupport, section codes) and serializes them to YAML.
13
+ # Output hash keys: +composition_sections+, +composition_mandatory_ms_elements+,
14
+ # +composition_optional_ms_elements+, +profiles+.
15
+ #
16
+ # @see Generator::IGResourcesExtractor for loading IG resources
17
+ # rubocop:disable Metrics/ClassLength
18
+ class MetadataManager
19
+ # @return [Array<Hash>] Array of section metadata hashes
20
+ attr_reader :composition_sections
21
+
22
+ include Constants
23
+
24
+ RECOMMENDED_SECTIONS_CODES = %w[11369-6 30954-2 47519-4 46264-8].freeze
25
+ OPTIONAL_SECTIONS_CODES = %w[42348-3 104605-1 47420-5 11348-0 10162-6 81338-6 18776-5 29762-2 8716-3].freeze
26
+ SUB_ELEMENTS_TO_SKIP = %w[event.period event.code section.emptyReason section.entry section.code section.text
27
+ section.title meta.profile].freeze
28
+
29
+ ATTESTER_PROFILE_REFS = [
30
+ 'Patient|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-patient',
31
+ 'RelatedPerson|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-relatedperson',
32
+ 'Practitioner|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-practitioner',
33
+ 'PractitionerRole|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-practitionerrole',
34
+ 'Organization|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-organization'
35
+ ].freeze
36
+
37
+ AUTHOR_PROFILE_REFS = [
38
+ 'Practitioner|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-practitioner',
39
+ 'PractitionerRole|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-practitionerrole',
40
+ 'Patient|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-patient',
41
+ 'RelatedPerson|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-relatedperson',
42
+ 'Organization|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-organization',
43
+ 'Device|http://hl7.org/fhir/uv/ips/StructureDefinition/Device-uv-ips'
44
+ ].freeze
45
+
46
+ CUSTODIAN_PROFILE_REFS = [
47
+ 'Organization|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-organization'
48
+ ].freeze
49
+
50
+ SUBJECT_PROFILE_REFS = [
51
+ 'Patient|http://hl7.org.au/fhir/ps/StructureDefinition/au-ps-patient'
52
+ ].freeze
53
+
54
+ # Initializes a MetadataManager for the given IG resources.
55
+ #
56
+ # @param ig_resources [Array<FHIR::Model>] Parsed IG resources (e.g. from IGResourcesExtractor#ig_resources)
57
+ def initialize(ig_resources)
58
+ @ig_resources = ig_resources
59
+ reset_composition_metadata_ivars!
60
+ end
61
+
62
+ def reset_composition_metadata_ivars!
63
+ @composition_sections = []
64
+ @composition_mandatory_ms_elements = []
65
+ @composition_mandatory_ms_sub_elements = []
66
+ @composition_optional_ms_elements = []
67
+ @composition_optional_ms_sub_elements = []
68
+ @composition_mandatory_ms_slices = []
69
+ @composition_optional_ms_slices = []
70
+ @profiles = []
71
+ @resources_filters = {}
72
+ @normalized_sections_data = []
73
+ end
74
+
75
+ # Generates composition section metadata from IG resources (in-memory only).
76
+ # Populates the internal composition sections and related metadata used by {#save_to_file}.
77
+ #
78
+ # @return [void]
79
+ def initiate_build
80
+ generate_metadata_for_composition
81
+ extract_required_ms_elements
82
+ extract_optional_ms_elements
83
+ extract_profiles
84
+ extract_resource_filters
85
+ normalize_sections_data
86
+ end
87
+
88
+ def normalize_sections_data
89
+ @normalized_sections_data = @composition_sections.map do |section|
90
+ normalize_section_data(section[:id])
91
+ end
92
+ end
93
+
94
+ def extract_resource_filters
95
+ @resources_filters = RESOURCES_FILTERS_MAPPING.map do |resource_profile, filters|
96
+ {
97
+ resource_profile: resource_profile,
98
+ filters: filters
99
+ }
100
+ end
101
+ end
102
+
103
+ def metadata_to_dump
104
+ metadata_dump_sections.merge(metadata_dump_ms_elements).merge(metadata_dump_tail)
105
+ end
106
+
107
+ def metadata_dump_sections
108
+ {
109
+ composition_sections: @composition_sections,
110
+ subject: build_metadata_for_subject,
111
+ author: build_metadata_for_author,
112
+ custodian: build_metadata_for_custodian,
113
+ attester: build_metadata_for_attester
114
+ }
115
+ end
116
+
117
+ def metadata_dump_ms_elements
118
+ {
119
+ composition_mandatory_ms_elements: @composition_mandatory_ms_elements,
120
+ composition_mandatory_ms_sub_elements: @composition_mandatory_ms_sub_elements,
121
+ composition_optional_ms_elements: @composition_optional_ms_elements,
122
+ composition_optional_ms_sub_elements: @composition_optional_ms_sub_elements,
123
+ composition_mandatory_ms_slices: @composition_mandatory_ms_slices,
124
+ composition_optional_ms_slices: @composition_optional_ms_slices
125
+ }
126
+ end
127
+
128
+ def metadata_dump_tail
129
+ {
130
+ profiles: @profiles,
131
+ resources_filters: @resources_filters
132
+ }
133
+ end
134
+
135
+ def normalize_section_data(section_id)
136
+ section_data = @composition_sections.find { |section| section[:id] == section_id }
137
+ return section_data if section_data.nil?
138
+
139
+ {
140
+ 'code' => section_data[:code],
141
+ 'display' => section_data[:short],
142
+ 'resources' => build_section_resources(section_data)
143
+ }
144
+ end
145
+
146
+ def composition_ms_sections_elements
147
+ filtered = composition_extract_ms_elements_without_slices.filter do |element|
148
+ element&.path&.include?('Composition.section.')
149
+ end
150
+ filtered.map { |element| section_element_expression_min(element) }.uniq
151
+ end
152
+
153
+ def section_element_expression_min(element)
154
+ path = element.path.gsub('Composition.section.', '')
155
+ { expression: path, min: element.min }
156
+ end
157
+
158
+ def build_section_resources(section_data)
159
+ resources = {}
160
+ section_data[:entries].each do |entry|
161
+ entry[:profiles].each do |profile|
162
+ requirements = requirements_for_profile(profile)
163
+ resources[profile] = { 'requirements' => requirements }
164
+ end
165
+ end
166
+ resources
167
+ end
168
+
169
+ def requirements_for_profile(profile)
170
+ filter = @resources_filters.find { |f| f[:resource_profile] == profile }
171
+ filter ? filter[:filters] : []
172
+ end
173
+
174
+ # Populates @profiles with AU PS StructureDefinitions (url, name, title, required).
175
+ #
176
+ # @return [void]
177
+ def extract_profiles
178
+ @profiles = main_profiles.map do |profile|
179
+ {
180
+ url: profile.url.to_s,
181
+ name: profile.name,
182
+ title: profile.title,
183
+ required: profile_required?(profile)
184
+ }
185
+ end
186
+ end
187
+
188
+ # Returns whether the given AU PS StructureDefinition is required.
189
+ #
190
+ # @param profile [FHIR::StructureDefinition] AU PS StructureDefinition
191
+ # @return [Boolean] true if the profile URL is in {Generator::Constants::REQUIRED_PROFILES}
192
+ def profile_required?(profile)
193
+ REQUIRED_PROFILES.include?(profile.url.to_s)
194
+ end
195
+
196
+ # StructureDefinitions from the IG whose URL is an AU PS profile
197
+ # (http://hl7.org.au/fhir/ps/StructureDefinition/...)
198
+ #
199
+ # @return [Array<FHIR::StructureDefinition>]
200
+ def main_profiles
201
+ @ig_resources.filter do |resource|
202
+ next unless resource.resourceType == 'StructureDefinition'
203
+
204
+ resource.url.to_s.include?('http://hl7.org.au/fhir/ps/StructureDefinition/')
205
+ end
206
+ end
207
+
208
+ # Populates @composition_optional_ms_elements with mustSupport elements where min is 0.
209
+ #
210
+ # @return [void]
211
+ def extract_optional_all_ms_elements
212
+ extract_ms_elements_by_predicate(->(element) { element.min.zero? })
213
+ end
214
+
215
+ def path_is_not_slice?(path)
216
+ !composition_mandatory_ms_slices_paths.include?(path) && !composition_optional_ms_slices_paths.include?(path)
217
+ end
218
+
219
+ def extract_optional_ms_elements
220
+ @composition_optional_ms_elements = extract_optional_all_ms_elements.filter do |path|
221
+ path_is_not_slice?(path) && !path.include?('.') && path != 'section'
222
+ end + ['event']
223
+ @composition_optional_ms_sub_elements = extract_optional_all_ms_elements.filter do |path|
224
+ path_is_not_slice?(path) && path.include?('.') && !SUB_ELEMENTS_TO_SKIP.include?(path)
225
+ end
226
+ @composition_optional_ms_slices = composition_optional_ms_slices
227
+ end
228
+
229
+ # Populates @composition_mandatory_ms_elements with mustSupport elements where min > 0.
230
+ #
231
+ # @return [void]
232
+ def extract_required_all_ms_elements
233
+ extract_ms_elements_by_predicate(->(element) { element.min.positive? })
234
+ end
235
+
236
+ def extract_required_ms_elements
237
+ @composition_mandatory_ms_elements = extract_required_all_ms_elements.filter do |path|
238
+ path_is_not_slice?(path) && !path.include?('.') && path != 'section'
239
+ end
240
+ @composition_mandatory_ms_sub_elements = extract_required_all_ms_elements.filter do |path|
241
+ path_is_not_slice?(path) && path.include?('.') && !SUB_ELEMENTS_TO_SKIP.include?(path)
242
+ end
243
+ @composition_mandatory_ms_slices = composition_mandatory_ms_slices
244
+ end
245
+
246
+ # Filters Composition mustSupport elements (no slices) by predicate and returns path suffixes.
247
+ # Uses element.path so suffixes are relative to Composition (e.g. subject.reference), not the base type.
248
+ #
249
+ # @param predicate [Proc] Called with each element; keeps element when truthy (e.g. min.zero?, min.positive?)
250
+ # @return [Array<String>] Sorted unique paths with "Composition." prefix removed (e.g. +subject.reference+)
251
+ def extract_ms_elements_by_predicate(predicate)
252
+ elements = composition_extract_ms_elements_without_slices.filter do |element|
253
+ predicate.call(element)
254
+ end
255
+ elements.map do |element|
256
+ element.path.gsub('Composition.', '')
257
+ end.uniq.sort
258
+ end
259
+
260
+ # Returns Composition snapshot elements that are mustSupport, non-sliced, and under Composition.
261
+ # Uses element.path (profile path) not element.base.path, so inherited sub-elements like
262
+ # Composition.subject.reference are included (base.path would be Reference.reference).
263
+ #
264
+ # @return [Array<FHIR::Element>] Snapshot elements; empty if no Composition StructureDefinition
265
+ def composition_extract_ms_elements_without_slices
266
+ composition_structure_definition = get_structure_definition_by_type('Composition')
267
+ return [] if composition_structure_definition.nil?
268
+
269
+ elements = composition_structure_definition.snapshot.element
270
+ elements.filter { |element| ms_composition_path?(element) }
271
+ end
272
+
273
+ def ms_composition_path?(element)
274
+ element.mustSupport == true && !element.path.include?(':') && element.path.include?('Composition.')
275
+ end
276
+
277
+ def all_ms_elements_related_to_slice(slice)
278
+ composition_structure_definition = get_structure_definition_by_type('Composition')
279
+ return [] if composition_structure_definition.nil?
280
+
281
+ elements = composition_structure_definition.snapshot.element
282
+ elements.filter do |element|
283
+ ms_composition_path?(element) && element.path.include?(slice)
284
+ end
285
+ end
286
+
287
+ def composition_mandatory_ms_slices
288
+ composition_extract_slices.filter { |slice| slice[:mustSupport] == true && slice[:min].positive? }
289
+ end
290
+
291
+ def composition_mandatory_ms_slices_paths
292
+ composition_mandatory_ms_slices.map { |slice| slice[:path] }
293
+ end
294
+
295
+ def composition_optional_ms_slices
296
+ composition_extract_slices.filter { |slice| slice[:mustSupport] == true && slice[:min].zero? }
297
+ end
298
+
299
+ def composition_optional_ms_slices_paths
300
+ composition_optional_ms_slices.map { |slice| slice[:path] }
301
+ end
302
+
303
+ def composition_extract_slices
304
+ composition_structure_definition = get_structure_definition_by_type('Composition')
305
+ return [] if composition_structure_definition.nil?
306
+
307
+ elements = composition_structure_definition.snapshot.element
308
+ filtered_elements = elements.filter { |element| element_is_slice?(element) }
309
+ filtered_elements.map { |element| build_metadata_for_slice(element) }
310
+ end
311
+
312
+ def build_metadata_for_slice(element)
313
+ related = all_ms_elements_related_to_slice(element.path)
314
+ base = element.path
315
+ slice_metadata_base(element).merge(
316
+ mandatory_ms_sub_elements: slice_relative_sub_paths(related, base, &:zero?),
317
+ optional_ms_sub_elements: slice_relative_sub_paths(related, base, &:positive?)
318
+ )
319
+ end
320
+
321
+ def slice_metadata_base(element)
322
+ {
323
+ path: element.path.gsub('Composition.', ''),
324
+ sliceName: element.sliceName,
325
+ min: element.min,
326
+ max: element.max,
327
+ mustSupport: element.mustSupport
328
+ }
329
+ end
330
+
331
+ def slice_relative_sub_paths(related_elements, base_path, &min_pred)
332
+ picked = related_elements.filter { |el| min_pred.call(el.min) }
333
+ paths = picked.map { |el| el.path.gsub(base_path, '').gsub('.', '') }
334
+ paths.reject(&:empty?)
335
+ end
336
+
337
+ def element_is_slice?(element)
338
+ element.id.include?(':') && element&.sliceName &&
339
+ !element.path.include?('section') &&
340
+ !element.path.include?('extension')
341
+ end
342
+
343
+ # Returns IG resources whose resourceType matches the given type.
344
+ #
345
+ # @param type [String] FHIR resource type (e.g. +StructureDefinition+)
346
+ # @return [Array<FHIR::Model>]
347
+ def get_resources_by_type(type)
348
+ @ig_resources.filter do |resource|
349
+ resource.resourceType == type
350
+ end
351
+ end
352
+
353
+ # Finds the StructureDefinition for a logical resource type (e.g. Composition, Patient).
354
+ #
355
+ # @param type [String] Logical type (e.g. +Composition+)
356
+ # @return [FHIR::StructureDefinition, nil]
357
+ def get_structure_definition_by_type(type)
358
+ get_resources_by_type('StructureDefinition').find do |resource|
359
+ resource.type == type
360
+ end
361
+ end
362
+
363
+ # Finds a StructureDefinition in ig_resources whose canonical URL matches the given profile.
364
+ # Matches by URL with or without version (canonical form "url|version").
365
+ #
366
+ # @param profile_url [String] Canonical profile URL (e.g. from Reference.type.targetProfile)
367
+ # @param entry_id [String, nil] Optional entry element id for error context
368
+ # @return [FHIR::StructureDefinition]
369
+ # @raise [RuntimeError] when no StructureDefinition in ig_resources has this profile URL
370
+ def get_structure_definition_by_profile(profile_url, entry_id = nil)
371
+ base_url = profile_url.to_s.split('|').first
372
+ sd = find_structure_definition_by_base_url(base_url)
373
+ return sd if sd
374
+
375
+ available = structure_definition_urls
376
+ raise profile_not_found_error(profile_url, entry_id, available)
377
+ end
378
+
379
+ # Finds a StructureDefinition by canonical base URL (version stripped).
380
+ #
381
+ # @param base_url [String] Canonical URL without version (e.g. +http://hl7.org/fhir/StructureDefinition/Patient+)
382
+ # @return [FHIR::StructureDefinition, nil]
383
+ def find_structure_definition_by_base_url(base_url)
384
+ get_resources_by_type('StructureDefinition').find do |resource|
385
+ resource.url.to_s.split('|').first == base_url
386
+ end
387
+ end
388
+
389
+ # Collects all non-empty canonical URLs from StructureDefinitions in the IG.
390
+ #
391
+ # @return [Array<String>] Sorted list of URL strings
392
+ def structure_definition_urls
393
+ get_resources_by_type('StructureDefinition').map { |r| r.url.to_s }.reject(&:empty?).sort
394
+ end
395
+
396
+ # Builds the error message when a profile has no matching StructureDefinition.
397
+ #
398
+ # @param profile_url [String] Requested profile URL
399
+ # @param entry_id [String, nil] Optional entry id for context
400
+ # @param available [Array<String>] List of available StructureDefinition URLs
401
+ # @return [String] Error message suitable for RuntimeError
402
+ def profile_not_found_error(profile_url, entry_id, available)
403
+ context = entry_id ? " (entry: #{entry_id})" : ''
404
+ hint = 'Add JSON via ADDITIONAL_IG_RESOURCES or additional_resources_path.'
405
+ sample = available.join(', ')
406
+ "No StructureDefinition found in IG resources for profile: #{profile_url.inspect}#{context}. " \
407
+ "Ensure the IG includes it or #{hint} " \
408
+ "Available URLs (#{available.size}): #{sample}"
409
+ end
410
+
411
+ # Populates @composition_sections from the Composition StructureDefinition.
412
+ # No-op when no Composition StructureDefinition is present in the IG.
413
+ #
414
+ # @return [void]
415
+ def generate_metadata_for_composition
416
+ composition_structure_definition = get_structure_definition_by_type('Composition')
417
+ return if composition_structure_definition.nil?
418
+
419
+ elements = composition_structure_definition.snapshot.element
420
+ sections = elements.filter do |element|
421
+ element.path == 'Composition.section' && !element.sliceName.nil?
422
+ end
423
+
424
+ sections.each do |section|
425
+ @composition_sections << build_section_data(section, elements)
426
+ end
427
+ end
428
+
429
+ # Builds a full section metadata hash including code and entries.
430
+ #
431
+ # @param section [FHIR::Element] Snapshot element for Composition.section (with sliceName)
432
+ # @param elements [Array<FHIR::Element>] All snapshot elements from the Composition StructureDefinition
433
+ # @return [Hash] Section metadata hash including keys:
434
+ # :id, :short, :definition, :min, :max, :required, :mustSupport, :code, :entries
435
+ def build_section_data(section, elements)
436
+ basic_section_data = build_basic_section_data(section)
437
+ {
438
+ **basic_section_data,
439
+ code: section_code(section.id, elements),
440
+ entries: get_section_entry_data(elements, section.id)
441
+ }
442
+ end
443
+
444
+ # Builds the basic section fields from a snapshot element (no code or entries).
445
+ #
446
+ # @param section [FHIR::Element] Snapshot element for Composition.section (with sliceName)
447
+ # @return [Hash] Basic section fields:
448
+ # :id, :short, :definition, :min, :max, :required, :mustSupport
449
+ def build_basic_section_data(section)
450
+ {
451
+ id: section.id,
452
+ short: section.short,
453
+ definition: section.definition,
454
+ min: section.min,
455
+ max: section.max,
456
+ required: section.min.positive?,
457
+ mustSupport: section.mustSupport || false,
458
+ ms_elements: composition_ms_sections_elements
459
+ }
460
+ end
461
+
462
+ # Extracts the LOINC (or other) section code from the section's +code+ element.
463
+ #
464
+ # @param section_id [String] Section element id (e.g. +Composition.section:sectionProblems+)
465
+ # @param elements [Array<FHIR::Element>] All snapshot elements
466
+ # @return [String, nil] First coding code from the section's pattern CodeableConcept, or nil if absent
467
+ def section_code(section_id, elements)
468
+ filtered_element = elements.find do |element|
469
+ element.id == "#{section_id}.code"
470
+ end
471
+ return nil if filtered_element.nil?
472
+
473
+ filtered_element.patternCodeableConcept.coding.first.code
474
+ end
475
+
476
+ # Collects entry elements for a section and builds entry metadata for each.
477
+ #
478
+ # @param elements [Array<FHIR::Element>] Snapshot elements
479
+ # @param section_id [String] Section element id (e.g. +Composition.section:sectionProblems+)
480
+ # @return [Array<Hash>] Entry metadata hashes (see {#build_section_entry_data})
481
+ def get_section_entry_data(elements, section_id)
482
+ entries = elements.filter do |element|
483
+ element.id.include?("#{section_id}.entry")
484
+ end
485
+
486
+ entries.map do |entry|
487
+ build_section_entry_data(entry)
488
+ end
489
+ end
490
+
491
+ # Builds entry metadata from a section entry snapshot element (id, cardinality, mustSupport, profiles).
492
+ #
493
+ # @param entry [FHIR::Element] Snapshot element for section entry (e.g. Composition.section:...entry:problem)
494
+ # @return [Hash] Entry metadata hash including keys:
495
+ # :id, :min, :max, :required, :mustSupport, :profiles
496
+ def build_section_entry_data(entry)
497
+ {
498
+ id: entry.id,
499
+ min: entry.min,
500
+ max: entry.max,
501
+ required: entry.min.positive?,
502
+ mustSupport: entry.mustSupport || false,
503
+ profiles: get_section_entry_profiles(entry.id, entry)
504
+ }
505
+ end
506
+
507
+ # Resolves Reference type targetProfile URLs to "resourceType|profile" using IG StructureDefinitions.
508
+ #
509
+ # @param entry_id [String] Entry element id (for error messages when profile is missing)
510
+ # @param element [FHIR::Element] Snapshot element with type Reference and targetProfile
511
+ # @return [Array<String>] Strings in the form "resourceType|profile" for each target profile
512
+ # @raise [RuntimeError] when no StructureDefinition matches a targetProfile.
513
+ # See {#get_structure_definition_by_profile}.
514
+ def get_section_entry_profiles(entry_id, element)
515
+ ref_types = element.type&.select { |t| t.code == 'Reference' } || []
516
+ profile_urls = ref_types.flat_map(&:targetProfile).to_a.compact
517
+
518
+ profile_urls.map do |profile_url|
519
+ sd = get_structure_definition_by_profile(profile_url, entry_id)
520
+ "#{sd.type}|#{profile_url}"
521
+ end
522
+ end
523
+
524
+ def normalize_path_to_hash(path)
525
+ { expression: path, label: path }
526
+ end
527
+
528
+ def mandatory_ms_sub_elements
529
+ @composition_mandatory_ms_sub_elements.map { |element| normalize_path_to_hash(element) }
530
+ end
531
+
532
+ def build_metadata_for_subject
533
+ SUBJECT_PROFILE_REFS.map { |profile| metadata_hash_for_profile_ref(profile) }
534
+ end
535
+
536
+ def build_metadata_for_custodian
537
+ # Custodian is only AU PS Organization
538
+ build_metadata_for_mapping(CUSTODIAN_PROFILE_REFS)
539
+ end
540
+
541
+ def build_metadata_for_attester
542
+ # attester.party: AU PS Patient | RelatedPerson | Practitioner | PractitionerRole | Organization (no Device)
543
+ build_metadata_for_mapping(ATTESTER_PROFILE_REFS)
544
+ end
545
+
546
+ def build_metadata_for_author
547
+ # AU PS Practitioner, AU PS PractitionerRole, AU PS Patient, AU PS RelatedPerson,
548
+ # AU PS Organization profiles or Device resource
549
+ build_metadata_for_mapping(AUTHOR_PROFILE_REFS)
550
+ end
551
+
552
+ def build_metadata_for_mapping(profile_refs)
553
+ profile_refs.map { |profile| metadata_hash_for_profile_ref(profile) }
554
+ end
555
+
556
+ def metadata_hash_for_profile_ref(profile)
557
+ url = profile.split('|')[1]
558
+ sd = get_structure_definition_by_profile(url)
559
+ {
560
+ resource_type: sd.type,
561
+ profile: url,
562
+ elements: get_elements_from_structure_definition(sd),
563
+ slices: get_extension_slices_from_structure_definition(sd)
564
+ }
565
+ end
566
+
567
+ def get_extension_slices_from_structure_definition(sd_data)
568
+ # Return mandatory MS extension slices
569
+ sd_decorator = StructureDefinitionDecorator.new(sd_data)
570
+ extension_slices = sd_decorator.extension_slices
571
+ filtered_extension_slices = extension_slices.filter { |element| element.mustSupport == true }
572
+ filtered_extension_slices.map do |element|
573
+ build_normalized_element_data(element, sd_decorator)
574
+ end.uniq
575
+ end
576
+
577
+ def build_normalized_element_data(element, sd_decorator)
578
+ first_element_type = element&.type&.first
579
+ profile = first_element_type&.profile&.first || ''
580
+ {
581
+ id: element.id,
582
+ expression: element.path.gsub("#{sd_decorator.type}.", ''),
583
+ label: element.sliceName,
584
+ min: element.min,
585
+ max: element.max,
586
+ profile: profile
587
+ }
588
+ end
589
+
590
+ def get_elements_from_structure_definition(sd_data)
591
+ structure_definition_data = StructureDefinitionDecorator.new(sd_data)
592
+ elements = structure_definition_data.simple_elements(include_str: "#{sd_data.type}.")
593
+ filtered_elements = elements.reject { |element| element.id.include?(':') }
594
+ filtered_elements.map do |element|
595
+ {
596
+ id: element.id,
597
+ expression: element.path.gsub("#{sd_data.type}.", ''),
598
+ min: element.min
599
+ }
600
+ end.uniq
601
+ end
602
+
603
+ private :reset_composition_metadata_ivars!, :metadata_dump_sections, :metadata_dump_ms_elements,
604
+ :metadata_dump_tail, :section_element_expression_min, :slice_metadata_base,
605
+ :slice_relative_sub_paths, :ms_composition_path?, :metadata_hash_for_profile_ref
606
+ end
607
+ # rubocop:enable Metrics/ClassLength
608
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Generator
4
+ # Shared naming and ID helpers for suite, group, and test generation.
5
+ # Use these when adding new groups or tests so IDs and class names stay consistent.
6
+ module Naming
7
+ class << self
8
+ # @param string [String] space-separated or mixed-case string
9
+ # @return [String] PascalCase (e.g. "Bundle Validation" => "BundleValidation")
10
+ def camel_case(string)
11
+ string.to_s.split.map(&:capitalize).join
12
+ end
13
+
14
+ # @param string [String]
15
+ # @return [String] with $ . , ( ) - removed
16
+ def remove_special_characters(string)
17
+ special_characters = ['$', '.', ',', '(', ')', '-']
18
+ string.to_s.gsub(Regexp.union(special_characters), '')
19
+ end
20
+
21
+ # @param string [String] human-readable name
22
+ # @return [String] Ruby class name (PascalCase, no spaces/special chars)
23
+ def build_class_name(string)
24
+ camel_case(remove_special_characters(string)).gsub(' ', '')
25
+ end
26
+
27
+ # @param string [String, Symbol] human-readable name or test type id (symbol); Symbol converted via .to_s
28
+ # @return [String] snake_case id (lowercase, underscores, no special chars)
29
+ def build_id(string)
30
+ s = string.to_s
31
+ remove_special_characters(s.include?(' ') ? s.gsub(' ', '_') : s).downcase
32
+ end
33
+ end
34
+
35
+ # Instance methods for use with include Naming
36
+ def camel_case(string)
37
+ Naming.camel_case(string)
38
+ end
39
+
40
+ def remove_special_characters(string)
41
+ Naming.remove_special_characters(string)
42
+ end
43
+
44
+ def build_class_name(string)
45
+ Naming.build_class_name(string)
46
+ end
47
+
48
+ def build_id(string)
49
+ Naming.build_id(string)
50
+ end
51
+ end
52
+ end