@openmrs/esm-report-builder 0.1.1-pre.101

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 (670) hide show
  1. package/.eslintignore +5 -0
  2. package/.eslintrc.js +19 -0
  3. package/.prettierrc +8 -0
  4. package/.tx/config +12 -0
  5. package/LICENSE +401 -0
  6. package/README.md +336 -0
  7. package/__mocks__/react-i18next.js +12 -0
  8. package/dist/128.css +1 -0
  9. package/dist/128.css.map +1 -0
  10. package/dist/128.js +338 -0
  11. package/dist/128.js.map +1 -0
  12. package/dist/1480.js +1 -0
  13. package/dist/1480.js.map +1 -0
  14. package/dist/1646.js +1 -0
  15. package/dist/1646.js.map +1 -0
  16. package/dist/1869.js +1 -0
  17. package/dist/1869.js.map +1 -0
  18. package/dist/1877.js +1 -0
  19. package/dist/1877.js.map +1 -0
  20. package/dist/2131.js +1 -0
  21. package/dist/2131.js.map +1 -0
  22. package/dist/2317.js +1 -0
  23. package/dist/2317.js.map +1 -0
  24. package/dist/2408.js +1 -0
  25. package/dist/2408.js.map +1 -0
  26. package/dist/2416.js +1 -0
  27. package/dist/2416.js.map +1 -0
  28. package/dist/282.js +1 -0
  29. package/dist/282.js.map +1 -0
  30. package/dist/2881.js +1 -0
  31. package/dist/2881.js.map +1 -0
  32. package/dist/2997.js +1 -0
  33. package/dist/2997.js.map +1 -0
  34. package/dist/3378.js +1 -0
  35. package/dist/3378.js.map +1 -0
  36. package/dist/3963.js +1 -0
  37. package/dist/3963.js.map +1 -0
  38. package/dist/4106.js +1 -0
  39. package/dist/4106.js.map +1 -0
  40. package/dist/4111.js +1 -0
  41. package/dist/4111.js.map +1 -0
  42. package/dist/4171.css +1 -0
  43. package/dist/4171.css.map +1 -0
  44. package/dist/4171.js +43 -0
  45. package/dist/4171.js.map +1 -0
  46. package/dist/434.js +1 -0
  47. package/dist/434.js.map +1 -0
  48. package/dist/4348.js +1 -0
  49. package/dist/4348.js.map +1 -0
  50. package/dist/4383.js +1 -0
  51. package/dist/4383.js.map +1 -0
  52. package/dist/4658.js +1 -0
  53. package/dist/4658.js.map +1 -0
  54. package/dist/4668.js +1 -0
  55. package/dist/4668.js.map +1 -0
  56. package/dist/4928.js +1 -0
  57. package/dist/4928.js.map +1 -0
  58. package/dist/5098.js +1 -0
  59. package/dist/5098.js.map +1 -0
  60. package/dist/5117.js +1 -0
  61. package/dist/5117.js.map +1 -0
  62. package/dist/5132.js +1 -0
  63. package/dist/5132.js.map +1 -0
  64. package/dist/5145.js +1 -0
  65. package/dist/5145.js.map +1 -0
  66. package/dist/550.js +1 -0
  67. package/dist/550.js.map +1 -0
  68. package/dist/5503.js +1 -0
  69. package/dist/5503.js.map +1 -0
  70. package/dist/556.js +1 -0
  71. package/dist/556.js.map +1 -0
  72. package/dist/5644.js +1 -0
  73. package/dist/5644.js.map +1 -0
  74. package/dist/5940.js +1 -0
  75. package/dist/5940.js.map +1 -0
  76. package/dist/6047.js +1 -0
  77. package/dist/6047.js.map +1 -0
  78. package/dist/6237.js +1 -0
  79. package/dist/6237.js.map +1 -0
  80. package/dist/6371.js +1 -0
  81. package/dist/6371.js.map +1 -0
  82. package/dist/6377.js +1 -0
  83. package/dist/6377.js.map +1 -0
  84. package/dist/6444.js +1 -0
  85. package/dist/6444.js.map +1 -0
  86. package/dist/6508.js +1 -0
  87. package/dist/6508.js.map +1 -0
  88. package/dist/6652.js +1 -0
  89. package/dist/6652.js.map +1 -0
  90. package/dist/6686.js +1 -0
  91. package/dist/6686.js.map +1 -0
  92. package/dist/6724.js +1 -0
  93. package/dist/6724.js.map +1 -0
  94. package/dist/6867.js +19 -0
  95. package/dist/6867.js.map +1 -0
  96. package/dist/6904.js +1 -0
  97. package/dist/6904.js.map +1 -0
  98. package/dist/7045.js +1 -0
  99. package/dist/7045.js.map +1 -0
  100. package/dist/7175.js +1 -0
  101. package/dist/7175.js.map +1 -0
  102. package/dist/7182.js +1 -0
  103. package/dist/7182.js.map +1 -0
  104. package/dist/7475.js +1 -0
  105. package/dist/7475.js.map +1 -0
  106. package/dist/7742.js +1 -0
  107. package/dist/7742.js.map +1 -0
  108. package/dist/7912.js +1 -0
  109. package/dist/7912.js.map +1 -0
  110. package/dist/8251.js +1 -0
  111. package/dist/8251.js.map +1 -0
  112. package/dist/8358.js +1 -0
  113. package/dist/8358.js.map +1 -0
  114. package/dist/8359.js +1 -0
  115. package/dist/8359.js.map +1 -0
  116. package/dist/8695.js +1 -0
  117. package/dist/8695.js.map +1 -0
  118. package/dist/903.js +1 -0
  119. package/dist/903.js.map +1 -0
  120. package/dist/9072.js +1 -0
  121. package/dist/9072.js.map +1 -0
  122. package/dist/9806.js +1 -0
  123. package/dist/9806.js.map +1 -0
  124. package/dist/997.js +1 -0
  125. package/dist/997.js.map +1 -0
  126. package/dist/openmrs-esm-report-builder.js +3 -0
  127. package/dist/openmrs-esm-report-builder.js.buildmanifest.json +1433 -0
  128. package/dist/openmrs-esm-report-builder.js.map +1 -0
  129. package/dist/routes.json +1 -0
  130. package/e2e/core/env.ts +5 -0
  131. package/e2e/core/global-setup.ts +34 -0
  132. package/e2e/core/test.ts +21 -0
  133. package/e2e/pages/app-shell.page.ts +35 -0
  134. package/e2e/pages/etl-sources.page.ts +36 -0
  135. package/e2e/pages/report-sections.page.ts +21 -0
  136. package/e2e/pages/section-preview.modal.ts +29 -0
  137. package/e2e/specs/etl-sources.spec.ts +51 -0
  138. package/e2e/specs/navigation.spec.ts +13 -0
  139. package/e2e/specs/section-preview-validation.spec.ts +38 -0
  140. package/e2e/specs/section-preview.spec.ts +94 -0
  141. package/e2e/support/github/Dockerfile +32 -0
  142. package/e2e/support/github/Dockerfile.dockerignore +13 -0
  143. package/e2e/support/github/docker-compose.yml +31 -0
  144. package/e2e/support/github/run-e2e-docker-env.sh +131 -0
  145. package/e2e/support/github/spa-assemble-config.json +6 -0
  146. package/e2e/tsconfig.json +11 -0
  147. package/example.env +5 -0
  148. package/linelist_report_builder_design_spec.md +816 -0
  149. package/package.json +98 -0
  150. package/playwright.config.ts +28 -0
  151. package/rspack.config.js +2 -0
  152. package/src/__debug__/all-designs.test.tsx +33 -0
  153. package/src/admin-card-link.extension.module.scss +55 -0
  154. package/src/admin-card-link.extension.tsx +24 -0
  155. package/src/components/admin/admin-page.component.tsx +75 -0
  156. package/src/components/admin/age-categories-page.component.tsx +109 -0
  157. package/src/components/admin/age-groups-page.component.tsx +146 -0
  158. package/src/components/admin/etl-browser/columns-tab.component.tsx +80 -0
  159. package/src/components/admin/etl-browser/etl-browser-page.component.tsx +309 -0
  160. package/src/components/admin/etl-browser/etl-browser.component.scss +424 -0
  161. package/src/components/admin/etl-browser/metadata-tab.component.tsx +65 -0
  162. package/src/components/admin/etl-browser/preview-grid.component.tsx +189 -0
  163. package/src/components/admin/etl-browser/relationships-tab.component.tsx +21 -0
  164. package/src/components/admin/etl-browser/sources-tree.component.tsx +118 -0
  165. package/src/components/admin/etl-browser/table-detail.component.tsx +124 -0
  166. package/src/components/admin/etl-browser/table-info-strip.component.tsx +51 -0
  167. package/src/components/admin/etl-browser/table-list.component.tsx +136 -0
  168. package/src/components/admin/etl-browser/use-table-preview.ts +47 -0
  169. package/src/components/admin/etl-sources-page.component.tsx +355 -0
  170. package/src/components/admin/etl-tasks-page.component.scss +370 -0
  171. package/src/components/admin/etl-tasks-page.component.tsx +768 -0
  172. package/src/components/admin/report-categories-page.component.tsx +267 -0
  173. package/src/components/admin/report-library-page.component.tsx +551 -0
  174. package/src/components/ai-support/ai-assist-button.component.tsx +30 -0
  175. package/src/components/ai-support/ai-assist-modal.component.tsx +81 -0
  176. package/src/components/ai-support/ai.service.ts +21 -0
  177. package/src/components/ai-support/ai.types.ts +12 -0
  178. package/src/components/app-shell/report-builder-shell.component.tsx +242 -0
  179. package/src/components/app-shell/report-builder-shell.scss +18 -0
  180. package/src/components/dashboards/dashboard-config.schema.ts +97 -0
  181. package/src/components/dashboards/dashboard-form-modal.component.tsx +150 -0
  182. package/src/components/dashboards/dashboards-admin.scss +62 -0
  183. package/src/components/dashboards/dashboards-page.component.tsx +153 -0
  184. package/src/components/dashboards/dashboards-table.component.tsx +130 -0
  185. package/src/components/dashboards/sections/dashboard-autoinclude.section.tsx +75 -0
  186. package/src/components/dashboards/sections/dashboard-basics.section.tsx +80 -0
  187. package/src/components/dashboards/sections/dashboard-json.section.tsx +79 -0
  188. package/src/components/dashboards/sections/dashboard-sections-editor.section.tsx +130 -0
  189. package/src/components/dashboards/sections/dashboard-widgets-editor.section.tsx +184 -0
  190. package/src/components/data-themes/data-theme-form.component.tsx +59 -0
  191. package/src/components/data-themes/data-theme-modal.component.tsx +349 -0
  192. package/src/components/data-themes/data-themes-page.component.tsx +139 -0
  193. package/src/components/data-themes/data-themes-table.component.tsx +98 -0
  194. package/src/components/data-themes/icon-registry.ts +35 -0
  195. package/src/components/data-themes/sections/data-theme-basics.section.tsx +63 -0
  196. package/src/components/data-themes/sections/data-theme-condition-columns.section.tsx +397 -0
  197. package/src/components/data-themes/sections/data-theme-fields-editor.section.tsx +125 -0
  198. package/src/components/data-themes/sections/data-theme-metadata.section.tsx +245 -0
  199. package/src/components/data-themes/sections/data-theme-preview.section.tsx +115 -0
  200. package/src/components/data-themes/sections/data-theme-source.section.tsx +184 -0
  201. package/src/components/data-visualizer/components/cqi-components/cqi-data-table.component.tsx +214 -0
  202. package/src/components/data-visualizer/components/cqi-components/cqi-elements.ts +203 -0
  203. package/src/components/data-visualizer/components/cqi-components/detail.component.tsx +110 -0
  204. package/src/components/data-visualizer/components/cqi-components/detail.scss +22 -0
  205. package/src/components/data-visualizer/components/data-table/data-table.component.tsx +208 -0
  206. package/src/components/data-visualizer/components/data-table/data-tables.scss +206 -0
  207. package/src/components/data-visualizer/components/empty-state/empty-state-illustration.component.tsx +53 -0
  208. package/src/components/data-visualizer/components/header/header.component.tsx +43 -0
  209. package/src/components/data-visualizer/components/header/header.scss +71 -0
  210. package/src/components/data-visualizer/components/model/confirm.component.tsx +40 -0
  211. package/src/components/data-visualizer/components/panel/panel.component.tsx +20 -0
  212. package/src/components/data-visualizer/components/panel/panel.scss +25 -0
  213. package/src/components/data-visualizer/components/popover/modifier-panel.tsx +53 -0
  214. package/src/components/data-visualizer/constants.ts +445 -0
  215. package/src/components/data-visualizer/data-visualizer-illustration.component.tsx +31 -0
  216. package/src/components/data-visualizer/data-visualizer.component.tsx +1935 -0
  217. package/src/components/data-visualizer/data-visualizer.resource.ts +462 -0
  218. package/src/components/data-visualizer/data-visualizer.scss +448 -0
  219. package/src/components/data-visualizer/declarations.d.ts +131 -0
  220. package/src/components/data-visualizer/parameter-inputs/boolean-parameter-input.component.tsx +34 -0
  221. package/src/components/data-visualizer/parameter-inputs/concept-parameter-input.component.tsx +81 -0
  222. package/src/components/data-visualizer/parameter-inputs/date-parameter-input.component.tsx +110 -0
  223. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.scss +47 -0
  224. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.tsx +228 -0
  225. package/src/components/data-visualizer/parameter-inputs/identifier-type-parameter-input.component.tsx +69 -0
  226. package/src/components/data-visualizer/parameter-inputs/list-parameter-input.component.tsx +73 -0
  227. package/src/components/data-visualizer/parameter-inputs/location-parameter-input.component.tsx +81 -0
  228. package/src/components/data-visualizer/parameter-inputs/number-parameter-input.component.tsx +40 -0
  229. package/src/components/data-visualizer/parameter-inputs/person-attribute-parameter-input.component.tsx +71 -0
  230. package/src/components/data-visualizer/parameter-inputs/text-parameter-input.component.tsx +52 -0
  231. package/src/components/data-visualizer/report-parameter-modal.component.tsx +383 -0
  232. package/src/components/data-visualizer/types.ts +206 -0
  233. package/src/components/etl-monitor/builder/builder-state-machine.ts +381 -0
  234. package/src/components/etl-monitor/builder/design-registry.tsx +139 -0
  235. package/src/components/etl-monitor/builder/etl-monitor-builder-context.tsx +702 -0
  236. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.component.tsx +461 -0
  237. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.scss +256 -0
  238. package/src/components/etl-monitor/builder/etl-monitor-builder-page.component.tsx +387 -0
  239. package/src/components/etl-monitor/builder/etl-monitor-builder-page.scss +976 -0
  240. package/src/components/etl-monitor/builder/generated-config-panel.component.tsx +168 -0
  241. package/src/components/etl-monitor/builder/generated-config-panel.scss +198 -0
  242. package/src/components/etl-monitor/builder/index.ts +5 -0
  243. package/src/components/etl-monitor/builder/monitor-preview.tsx +124 -0
  244. package/src/components/etl-monitor/builder/steps/data-source-step.component.tsx +667 -0
  245. package/src/components/etl-monitor/builder/steps/data-source-step.scss +284 -0
  246. package/src/components/etl-monitor/builder/steps/design-samples.ts +278 -0
  247. package/src/components/etl-monitor/builder/steps/design-step.component.tsx +299 -0
  248. package/src/components/etl-monitor/builder/steps/design-step.scss +479 -0
  249. package/src/components/etl-monitor/builder/steps/fields-step.component.tsx +859 -0
  250. package/src/components/etl-monitor/builder/steps/fields-step.scss +459 -0
  251. package/src/components/etl-monitor/builder/steps/general-step.component.tsx +288 -0
  252. package/src/components/etl-monitor/builder/steps/general-step.scss +103 -0
  253. package/src/components/etl-monitor/builder/steps/index.ts +5 -0
  254. package/src/components/etl-monitor/builder/steps/preview-step.component.tsx +245 -0
  255. package/src/components/etl-monitor/builder/steps/preview-step.scss +236 -0
  256. package/src/components/etl-monitor/etl-monitor-card-shell.component.tsx +154 -0
  257. package/src/components/etl-monitor/etl-monitor-card-shell.scss +103 -0
  258. package/src/components/etl-monitor/etl-monitor-dashboard.component.tsx +227 -0
  259. package/src/components/etl-monitor/etl-monitor-dashboard.scss +148 -0
  260. package/src/components/etl-monitor/etl-monitor-status-card.component.tsx +192 -0
  261. package/src/components/etl-monitor/etl-monitors-page.component.tsx +621 -0
  262. package/src/components/etl-monitor/renderers/DetailsRenderer.tsx +2 -0
  263. package/src/components/etl-monitor/renderers/ErrorLogRenderer.tsx +2 -0
  264. package/src/components/etl-monitor/renderers/EtlMonitorWidget.tsx +97 -0
  265. package/src/components/etl-monitor/renderers/MetricsGridRenderer.tsx +2 -0
  266. package/src/components/etl-monitor/renderers/MonitorRenderer.tsx +175 -0
  267. package/src/components/etl-monitor/renderers/ProgressRenderer.tsx +2 -0
  268. package/src/components/etl-monitor/renderers/StatusCardRenderer.tsx +2 -0
  269. package/src/components/etl-monitor/renderers/SummaryCardRenderer.tsx +2 -0
  270. package/src/components/etl-monitor/renderers/TableRenderer.tsx +2 -0
  271. package/src/components/etl-monitor/renderers/component-icons.tsx +33 -0
  272. package/src/components/etl-monitor/renderers/components/details-renderer.tsx +195 -0
  273. package/src/components/etl-monitor/renderers/components/error-log-renderer.tsx +104 -0
  274. package/src/components/etl-monitor/renderers/components/index.ts +6 -0
  275. package/src/components/etl-monitor/renderers/components/log-renderer.tsx +86 -0
  276. package/src/components/etl-monitor/renderers/components/metrics-grid-renderer.tsx +133 -0
  277. package/src/components/etl-monitor/renderers/components/progress-renderer.tsx +188 -0
  278. package/src/components/etl-monitor/renderers/components/status-card-renderer.tsx +247 -0
  279. package/src/components/etl-monitor/renderers/components/summary-card-renderer.tsx +160 -0
  280. package/src/components/etl-monitor/renderers/components/table-renderer.tsx +175 -0
  281. package/src/components/etl-monitor/renderers/components/time-series-renderer.tsx +310 -0
  282. package/src/components/etl-monitor/renderers/data-transformer.ts +361 -0
  283. package/src/components/etl-monitor/renderers/index.ts +11 -0
  284. package/src/components/etl-monitor/renderers/legacy-monitor-renderer.tsx +69 -0
  285. package/src/components/etl-monitor/renderers/monitor-renderer.tsx +6 -0
  286. package/src/components/etl-monitor/renderers/monitor-renderers.scss +1621 -0
  287. package/src/components/etl-monitor/renderers/monitor-widget-card.component.tsx +54 -0
  288. package/src/components/etl-monitor/renderers/monitor-widget-card.scss +16 -0
  289. package/src/components/etl-monitor/renderers/v2-monitor-renderer.tsx +227 -0
  290. package/src/components/import-export/artifact-groups.ts +153 -0
  291. package/src/components/import-export/export-report-modal.component.tsx +265 -0
  292. package/src/components/import-export/import-export-page.component.tsx +610 -0
  293. package/src/components/import-export/import-export.styles.scss +825 -0
  294. package/src/components/import-export/import-package-modal.component.tsx +401 -0
  295. package/src/components/import-export/index.ts +3 -0
  296. package/src/components/indicators/README.md +24 -0
  297. package/src/components/indicators/base-indicator-basic-fields.component.tsx +18 -0
  298. package/src/components/indicators/base-indicator-basics.section.tsx +54 -0
  299. package/src/components/indicators/base-indicator-countby.section.tsx +25 -0
  300. package/src/components/indicators/base-indicator-criteria.section.tsx +83 -0
  301. package/src/components/indicators/base-indicator-details-form.component.tsx +80 -0
  302. package/src/components/indicators/base-indicator-theme-unit-fields.component.tsx +39 -0
  303. package/src/components/indicators/create-base-indicator-modal.component.tsx +623 -0
  304. package/src/components/indicators/create-composite-base-indicator-modal.component.tsx +295 -0
  305. package/src/components/indicators/create-custom-indicator-modal.component.tsx +548 -0
  306. package/src/components/indicators/create-final-indicator-modal.component.tsx +285 -0
  307. package/src/components/indicators/custom-condition-input.component.tsx +310 -0
  308. package/src/components/indicators/diagnosis-filters-form.component.tsx +101 -0
  309. package/src/components/indicators/disaggregation-panel.component.tsx +93 -0
  310. package/src/components/indicators/handler/concept-search-multiselect.component.tsx +153 -0
  311. package/src/components/indicators/handler/question-answer-concept-search.component.tsx +336 -0
  312. package/src/components/indicators/indicator-run-preview-modal.component.tsx +391 -0
  313. package/src/components/indicators/indicator-search-select.component.tsx +297 -0
  314. package/src/components/indicators/indicator-status-tag.component.tsx +9 -0
  315. package/src/components/indicators/indicators-page.component.tsx +673 -0
  316. package/src/components/indicators/indicators-page.scss +121 -0
  317. package/src/components/indicators/indicators-table.component.tsx +188 -0
  318. package/src/components/indicators/preview-table.component.tsx +31 -0
  319. package/src/components/indicators/sections/composite-indicator-basics.section.tsx +36 -0
  320. package/src/components/indicators/sections/composite-indicator-picker.section.tsx +116 -0
  321. package/src/components/indicators/sections/composite-indicator-sql-preview.section.tsx +31 -0
  322. package/src/components/indicators/sections/custom-indicator-basics.section.tsx +65 -0
  323. package/src/components/indicators/sections/custom-indicator-disaggregation.section.tsx +93 -0
  324. package/src/components/indicators/sections/custom-indicator-filters.section.tsx +203 -0
  325. package/src/components/indicators/sections/custom-indicator-population.section.tsx +107 -0
  326. package/src/components/indicators/sections/custom-indicator-sql-preview.section.tsx +108 -0
  327. package/src/components/indicators/sections/final-indicator-basics.section.tsx +46 -0
  328. package/src/components/indicators/sections/final-indicator-disaggregation.section.tsx +47 -0
  329. package/src/components/indicators/sections/final-indicator-picker.section.tsx +60 -0
  330. package/src/components/indicators/sections/final-indicator-preview.section.tsx +20 -0
  331. package/src/components/indicators/sections/final-indicator-results-preview.section.tsx +65 -0
  332. package/src/components/indicators/sections/indicator-basics.section.tsx +40 -0
  333. package/src/components/indicators/sections/indicator-conditions.section.tsx +279 -0
  334. package/src/components/indicators/sections/indicator-sql-preview.section.tsx +63 -0
  335. package/src/components/indicators/sections/indicator-theme.section.tsx +48 -0
  336. package/src/components/indicators/sql-preview.component.tsx +19 -0
  337. package/src/components/indicators/tag-input.component.tsx +177 -0
  338. package/src/components/indicators/types/base-indicator-form.types.ts +17 -0
  339. package/src/components/indicators/types/base-indicator-theme.constants.ts +8 -0
  340. package/src/components/indicators/types/composite-indicator.types.ts +27 -0
  341. package/src/components/indicators/types/condition-ui.types.ts +11 -0
  342. package/src/components/indicators/types/custom-indicator.types.ts +161 -0
  343. package/src/components/indicators/types/data-theme-config.types.ts +88 -0
  344. package/src/components/indicators/types/indicator-types.ts +57 -0
  345. package/src/components/indicators/types/sql-builders.ts +43 -0
  346. package/src/components/indicators/utils/composite-indicator-sql.utils.test.ts +143 -0
  347. package/src/components/indicators/utils/composite-indicator-sql.utils.ts +426 -0
  348. package/src/components/indicators/utils/concept-preload.utils.ts +21 -0
  349. package/src/components/indicators/utils/custom-indicator-interpreter.test.ts +493 -0
  350. package/src/components/indicators/utils/custom-indicator-interpreter.ts +702 -0
  351. package/src/components/indicators/utils/final-indicator-sql.utils.ts +190 -0
  352. package/src/components/indicators/utils/indicator-conditions-hydration.utils.ts +254 -0
  353. package/src/components/indicators/utils/indicator-sql-resolution.utils.ts +204 -0
  354. package/src/components/indicators/utils/indicator-sql.utils.ts +721 -0
  355. package/src/components/indicators/utils/population-sql.compiler.test.ts +985 -0
  356. package/src/components/indicators/utils/population-sql.compiler.ts +858 -0
  357. package/src/components/landing/landing-action-item.component.tsx +50 -0
  358. package/src/components/landing/landing-action-list.component.tsx +12 -0
  359. package/src/components/landing/landing-activity-list.component.tsx +47 -0
  360. package/src/components/landing/landing-footer-grid.component.tsx +45 -0
  361. package/src/components/landing/landing-hero.component.tsx +18 -0
  362. package/src/components/landing/landing-section-card.component.tsx +33 -0
  363. package/src/components/landing/report-builder-landing-page.component.tsx +186 -0
  364. package/src/components/landing/report-builder-landing-page.scss +131 -0
  365. package/src/components/legacy-reports/legacy-report-detail-page.component.tsx +300 -0
  366. package/src/components/legacy-reports/legacy-report-editor-page.component.tsx +324 -0
  367. package/src/components/legacy-reports/legacy-report-upload-modal.component.tsx +307 -0
  368. package/src/components/legacy-reports/legacy-reports-page.component.tsx +206 -0
  369. package/src/components/legacy-reports/legacy-reports-table.component.tsx +154 -0
  370. package/src/components/legacy-reports/tabs/legacy-report-datasets-tab.component.tsx +344 -0
  371. package/src/components/legacy-reports/tabs/legacy-report-dimensions-tab.component.tsx +590 -0
  372. package/src/components/legacy-reports/tabs/legacy-report-indicators-tab.component.tsx +490 -0
  373. package/src/components/legacy-reports/tabs/legacy-report-json-preview-tab.component.tsx +272 -0
  374. package/src/components/legacy-reports/tabs/legacy-report-overview-tab.component.tsx +218 -0
  375. package/src/components/legacy-reports/tabs/legacy-report-parameters-tab.component.tsx +279 -0
  376. package/src/components/linelist/definition/indicator-to-sql.utils.ts +281 -0
  377. package/src/components/linelist/definition/linelist-definition-editor.component.tsx +1676 -0
  378. package/src/components/linelist/definition/linelist-definition-editor.scss +461 -0
  379. package/src/components/linelist/definition/linelist-definition.types.ts +108 -0
  380. package/src/components/linelist/definition/panels/column-properties-panel.component.tsx +328 -0
  381. package/src/components/linelist/definition/panels/column-selector-panel.component.tsx +1082 -0
  382. package/src/components/linelist/definition/panels/column-selector-panel.scss +100 -0
  383. package/src/components/linelist/definition/panels/custom-sql-column-modal.component.tsx +248 -0
  384. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.component.tsx +300 -0
  385. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.scss +21 -0
  386. package/src/components/linelist/definition/panels/multi-datasource-selector.component.tsx +332 -0
  387. package/src/components/linelist/definition/panels/multi-datasource-selector.scss +161 -0
  388. package/src/components/linelist/definition/panels/observation-column-modal.component.tsx +450 -0
  389. package/src/components/linelist/definition/panels/observation-column-modal.scss +29 -0
  390. package/src/components/linelist/definition/panels/parameter-panel.component.tsx +659 -0
  391. package/src/components/linelist/definition/panels/parameter-panel.scss +64 -0
  392. package/src/components/linelist/definition/panels/population-source-selector.component.tsx +407 -0
  393. package/src/components/linelist/definition/panels/population-source-selector.scss +187 -0
  394. package/src/components/linelist/design/panels/column-category-selector.component.tsx +433 -0
  395. package/src/components/linelist/design/panels/column-category-selector.scss +324 -0
  396. package/src/components/linelist/design/panels/column-debug-panel.component.tsx +197 -0
  397. package/src/components/linelist/design/panels/column-debug-panel.scss +141 -0
  398. package/src/components/linelist/design/panels/data-catalogue.component.tsx +1062 -0
  399. package/src/components/linelist/design/panels/data-catalogue.scss +263 -0
  400. package/src/components/linelist/design/panels/display-settings-panel.component.tsx +405 -0
  401. package/src/components/linelist/design/panels/display-settings-panel.scss +78 -0
  402. package/src/components/linelist/design/panels/filtermap-editor.component.tsx +434 -0
  403. package/src/components/linelist/design/panels/filtermap-editor.scss +176 -0
  404. package/src/components/linelist/design/panels/json-preview.component.tsx +114 -0
  405. package/src/components/linelist/design/panels/json-preview.scss +85 -0
  406. package/src/components/linelist/design/panels/query-config-panel.component.tsx +1137 -0
  407. package/src/components/linelist/design/panels/query-config-panel.scss +341 -0
  408. package/src/components/linelist/design/panels/sort-config-panel.component.tsx +330 -0
  409. package/src/components/linelist/design/panels/sort-config-panel.scss +77 -0
  410. package/src/components/linelist/design/panels/sql-builder-panel.component.tsx +475 -0
  411. package/src/components/linelist/design/panels/sql-builder-panel.scss +226 -0
  412. package/src/components/linelist/design/panels/transformation-panel.component.tsx +687 -0
  413. package/src/components/linelist/design/panels/transformation-panel.scss +81 -0
  414. package/src/components/linelist/design/panels/visual-filter-panel.component.tsx +595 -0
  415. package/src/components/linelist/design/panels/visual-filter-panel.scss +76 -0
  416. package/src/components/linelist/linelist-dashboard.page.component.tsx +756 -0
  417. package/src/components/linelist/linelist-dashboard.page.scss +240 -0
  418. package/src/components/linelist/linelist-editor.page.component.tsx +134 -0
  419. package/src/components/linelist/linelist-editor.page.scss +28 -0
  420. package/src/components/linelist/linelist-run-report.page.component.tsx +715 -0
  421. package/src/components/linelist/linelist-run-report.page.scss +136 -0
  422. package/src/components/linelist/modals/linelist-report-builder-modal.component.tsx +1454 -0
  423. package/src/components/linelist/modals/linelist-report-builder-modal.scss +164 -0
  424. package/src/components/linelist/modals/preview-parameter-modal.component.tsx +361 -0
  425. package/src/components/report/dashboard/reports-table.component.tsx +186 -0
  426. package/src/components/report/definition/panels/properties-panel.component.tsx +38 -0
  427. package/src/components/report/definition/panels/report-structure-panel.component.tsx +61 -0
  428. package/src/components/report/definition/panels/selection-panel.component.tsx +58 -0
  429. package/src/components/report/definition/report-definition-editor.component.tsx +99 -0
  430. package/src/components/report/definition/report-definition.types.ts +66 -0
  431. package/src/components/report/design/panels/IndicatorPropertiesPanel.scss +33 -0
  432. package/src/components/report/design/panels/IndicatorPropertiesPanel.tsx +389 -0
  433. package/src/components/report/design/panels/JsonPreviewPanel.scss +32 -0
  434. package/src/components/report/design/panels/JsonPreviewPanel.tsx +104 -0
  435. package/src/components/report/design/panels/mapping-preview-panel.component.tsx +82 -0
  436. package/src/components/report/design/panels/mapping-preview-panel.scss +55 -0
  437. package/src/components/report/design/panels/template-structure-panel.component.tsx +296 -0
  438. package/src/components/report/design/panels/template-structure-panel.scss +81 -0
  439. package/src/components/report/design/report-design-editor.component.tsx +1170 -0
  440. package/src/components/report/design/report-design.types.ts +48 -0
  441. package/src/components/report/design/report-design.utils.ts +86 -0
  442. package/src/components/report/panels/report-detail.component.tsx +71 -0
  443. package/src/components/report/panels/report-panel-shell.component.tsx +60 -0
  444. package/src/components/report/report-dashboard.page.component.tsx +554 -0
  445. package/src/components/report/report-editor.page.component.tsx +590 -0
  446. package/src/components/report/report.routes.tsx +32 -0
  447. package/src/components/report-sections/create-report-section-modal.component.tsx +388 -0
  448. package/src/components/report-sections/hooks/useAgeCategories.ts +25 -0
  449. package/src/components/report-sections/hooks/useSectionEditorState.ts +449 -0
  450. package/src/components/report-sections/panels/Dhis2MappingPanel.tsx +157 -0
  451. package/src/components/report-sections/panels/DisaggregationPanel.tsx +97 -0
  452. package/src/components/report-sections/panels/IndicatorPickerPanel.tsx +201 -0
  453. package/src/components/report-sections/panels/SectionBasicsPanel.tsx +27 -0
  454. package/src/components/report-sections/report-section-preview-modal.component.tsx +548 -0
  455. package/src/components/report-sections/report-sections-page.component.tsx +325 -0
  456. package/src/components/report-sections/section-disaggregation.utils.ts +637 -0
  457. package/src/components/report-sections/section-types.ts +55 -0
  458. package/src/components/report-sections/section-utils.ts +50 -0
  459. package/src/components/report-sections/sections-page.scss +79 -0
  460. package/src/components/run-reports/run-reports-page.component.tsx +8 -0
  461. package/src/components/security/access-denied-page.component.tsx +64 -0
  462. package/src/components/security/access-denied-page.scss +39 -0
  463. package/src/components/security/privilege-route.component.test.tsx +76 -0
  464. package/src/components/security/privilege-route.component.tsx +39 -0
  465. package/src/components/shared/compile-setup-modal.component.tsx +182 -0
  466. package/src/components/shared/concept-selector.component.tsx +245 -0
  467. package/src/components/shared/concept-selector.scss +121 -0
  468. package/src/components/shared/header/builder-header-actions.component.tsx +38 -0
  469. package/src/components/shared/header/header.component.tsx +85 -0
  470. package/src/components/shared/header/header.scss +151 -0
  471. package/src/components/shared/header/illustration.component.tsx +34 -0
  472. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.scss +33 -0
  473. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.tsx +389 -0
  474. package/src/components/shared/json-preview-panel/JsonPreviewPanel.scss +32 -0
  475. package/src/components/shared/json-preview-panel/JsonPreviewPanel.tsx +104 -0
  476. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.component.tsx +82 -0
  477. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.scss +55 -0
  478. package/src/components/shared/preview/query-results-preview.component.tsx +204 -0
  479. package/src/components/shared/template-structure-panel/template-structure-panel.component.tsx +296 -0
  480. package/src/components/shared/template-structure-panel/template-structure-panel.scss +81 -0
  481. package/src/config-schema.ts +43 -0
  482. package/src/constants/privileges.test.ts +54 -0
  483. package/src/constants/privileges.ts +118 -0
  484. package/src/features/dashboards/DashboardRendererPage.tsx +174 -0
  485. package/src/features/dashboards/DashboardsPage.tsx +164 -0
  486. package/src/features/dashboards/EtlDashboardPage.tsx +32 -0
  487. package/src/features/dashboards/components/DashboardEmptyState.tsx +41 -0
  488. package/src/features/dashboards/components/DashboardGrid.tsx +48 -0
  489. package/src/features/dashboards/components/DashboardHeader.tsx +58 -0
  490. package/src/features/dashboards/components/DashboardSection.tsx +46 -0
  491. package/src/features/dashboards/components/ReportLaunchCard.tsx +51 -0
  492. package/src/features/dashboards/components/WidgetSlotDispatcher.tsx +61 -0
  493. package/src/features/dashboards/components/WidgetUnavailableCard.tsx +36 -0
  494. package/src/features/dashboards/dashboards.scss +196 -0
  495. package/src/features/dashboards/hooks/useDashboard.ts +61 -0
  496. package/src/features/dashboards/hooks/useDashboardWidgets.ts +74 -0
  497. package/src/features/dashboards/hooks/useDashboards.ts +56 -0
  498. package/src/features/dashboards/hooks/useMonitorDataMap.ts +84 -0
  499. package/src/features/dashboards/index.ts +11 -0
  500. package/src/features/dashboards/utils/dashboard-config.util.test.ts +286 -0
  501. package/src/features/dashboards/utils/dashboard-config.util.ts +382 -0
  502. package/src/features/report-visualizer/ReportVisualizerPage.tsx +448 -0
  503. package/src/features/report-visualizer/components/Export/ExportMenu.tsx +141 -0
  504. package/src/features/report-visualizer/components/Export/index.ts +2 -0
  505. package/src/features/report-visualizer/components/ReportExplorer/QuickFilters.tsx +152 -0
  506. package/src/features/report-visualizer/components/ReportExplorer/ReportCategoryFilter.tsx +54 -0
  507. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerPanel.tsx +201 -0
  508. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerRail.tsx +202 -0
  509. package/src/features/report-visualizer/components/ReportExplorer/ReportList.tsx +287 -0
  510. package/src/features/report-visualizer/components/ReportExplorer/ReportSearch.tsx +151 -0
  511. package/src/features/report-visualizer/components/ReportExplorer/ReportTagFilter.tsx +100 -0
  512. package/src/features/report-visualizer/components/ReportExplorer/RunReportButton.tsx +42 -0
  513. package/src/features/report-visualizer/components/ReportExplorer/RuntimeParameterForm.tsx +259 -0
  514. package/src/features/report-visualizer/components/ReportExplorer/index.ts +8 -0
  515. package/src/features/report-visualizer/components/ReportWorkspace/ReportLayer.tsx +33 -0
  516. package/src/features/report-visualizer/components/ReportWorkspace/ReportSummary.tsx +88 -0
  517. package/src/features/report-visualizer/components/ReportWorkspace/ReportViewTabs.tsx +72 -0
  518. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspaceHeader.tsx +92 -0
  519. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspacePanel.tsx +242 -0
  520. package/src/features/report-visualizer/components/ReportWorkspace/index.ts +5 -0
  521. package/src/features/report-visualizer/components/TableView/ColumnVisibilityMenu.tsx +206 -0
  522. package/src/features/report-visualizer/components/TableView/ReportTable.tsx +253 -0
  523. package/src/features/report-visualizer/components/TableView/index.ts +2 -0
  524. package/src/features/report-visualizer/hooks/index.ts +4 -0
  525. package/src/features/report-visualizer/hooks/useExplorerPreferences.ts +103 -0
  526. package/src/features/report-visualizer/hooks/useReportCapabilities.ts +112 -0
  527. package/src/features/report-visualizer/hooks/useReportParameters.ts +152 -0
  528. package/src/features/report-visualizer/hooks/useReports.ts +135 -0
  529. package/src/features/report-visualizer/index.ts +6 -0
  530. package/src/features/report-visualizer/report-visualizer.scss +593 -0
  531. package/src/features/report-visualizer/types/index.ts +1 -0
  532. package/src/features/report-visualizer/types/report-visualizer.types.ts +104 -0
  533. package/src/features/report-visualizer/utils/status-colors.ts +117 -0
  534. package/src/globals.d.ts +9 -0
  535. package/src/hooks/address-template.ts +86 -0
  536. package/src/hooks/concept.ts +112 -0
  537. package/src/hooks/indicator/index.ts +1 -0
  538. package/src/hooks/indicator/useIndicatorsByTheme.ts +41 -0
  539. package/src/hooks/openmrs-reference-data.ts +432 -0
  540. package/src/hooks/theme/index.ts +4 -0
  541. package/src/hooks/theme/useDataTheme.ts +61 -0
  542. package/src/hooks/theme/useDataThemes.ts +28 -0
  543. package/src/hooks/theme/useMambaTableMeta.ts +34 -0
  544. package/src/hooks/theme/useMambaTables.ts +95 -0
  545. package/src/hooks/use-report-builder-privileges.test.tsx +92 -0
  546. package/src/hooks/use-report-builder-privileges.ts +58 -0
  547. package/src/index.ts +29 -0
  548. package/src/resources/address-template/address-template.api.ts +180 -0
  549. package/src/resources/agegroup/agegroups.api.ts +58 -0
  550. package/src/resources/concept/concept.api.ts +116 -0
  551. package/src/resources/concepts/concept-mapper.ts +21 -0
  552. package/src/resources/concepts/concept-types.ts +31 -0
  553. package/src/resources/concepts/concepts.resource.ts +44 -0
  554. package/src/resources/concepts/useConceptSearch.ts +41 -0
  555. package/src/resources/dashboard/dashboard.api.ts +107 -0
  556. package/src/resources/etl-monitor/etl-monitor-test.api.ts +409 -0
  557. package/src/resources/etl-monitor/etl-monitor.api.ts +381 -0
  558. package/src/resources/etl-source/etl-source.api.ts +65 -0
  559. package/src/resources/etl-task/etl-task.api.ts +96 -0
  560. package/src/resources/etl-task/etl-task.types.ts +31 -0
  561. package/src/resources/indicator/indicators.api.ts +125 -0
  562. package/src/resources/legacy-reports/legacy-reports.api.ts +144 -0
  563. package/src/resources/linelist/linelist-reports.api.ts +616 -0
  564. package/src/resources/location/location.api.ts +135 -0
  565. package/src/resources/openmrs-api.ts +40 -0
  566. package/src/resources/patient-identifier-type/patient-identifier-type.api.ts +65 -0
  567. package/src/resources/person-attribute-type/person-attribute-type.api.ts +71 -0
  568. package/src/resources/preview/sql-preview.api.ts +54 -0
  569. package/src/resources/report/reports.api.ts +140 -0
  570. package/src/resources/report-category/report-category.api.ts +41 -0
  571. package/src/resources/report-import-export/import-export-api.ts +164 -0
  572. package/src/resources/report-library/report-library.api.ts +63 -0
  573. package/src/resources/report-section/report-sections.api.ts +73 -0
  574. package/src/resources/report-section/section-preview.api.ts +25 -0
  575. package/src/resources/theme/data-theme.api.ts +88 -0
  576. package/src/resources/theme/etl-schema.api.ts +101 -0
  577. package/src/resources/theme/etl-table-meta.api.ts +45 -0
  578. package/src/root.component.tsx +20 -0
  579. package/src/routes/report-builder.routes.tsx +160 -0
  580. package/src/routes/report-builder.scss +56 -0
  581. package/src/routes.json +31 -0
  582. package/src/sample-template.ts +121 -0
  583. package/src/template-utils.ts +110 -0
  584. package/src/types/carbon-react-inert.d.ts +13 -0
  585. package/src/types/condition-operators.ts +63 -0
  586. package/src/types/dashboard/dashboard.types.ts +137 -0
  587. package/src/types/dashboard/index.ts +4 -0
  588. package/src/types/etl/etl-types.ts +238 -0
  589. package/src/types/etl-monitor/etl-monitor-builder.types.ts +555 -0
  590. package/src/types/etl-monitor/etl-monitor-v2.types.ts +397 -0
  591. package/src/types/etl-monitor/etl-monitor.types.ts +188 -0
  592. package/src/types/etl-monitor/index.ts +6 -0
  593. package/src/types/linelist/compile-config.ts +455 -0
  594. package/src/types/linelist-types.ts +2059 -0
  595. package/src/types/theme/data-theme.types.ts +88 -0
  596. package/src/utils/api-error.utils.test.ts +147 -0
  597. package/src/utils/api-error.utils.ts +131 -0
  598. package/src/utils/config-enhancer.ts +62 -0
  599. package/src/utils/etl-browser/etl-browser.util.test.ts +151 -0
  600. package/src/utils/etl-browser/export-csv.util.ts +43 -0
  601. package/src/utils/etl-browser/group-tables.util.ts +84 -0
  602. package/src/utils/etl-browser/index.ts +4 -0
  603. package/src/utils/etl-browser/summarize-columns.util.ts +27 -0
  604. package/src/utils/etl-browser/table-sql.util.ts +20 -0
  605. package/src/utils/etl-monitor/config-v2-adapter.util.ts +460 -0
  606. package/src/utils/etl-monitor/endpoint-config-normalizer.util.ts +383 -0
  607. package/src/utils/etl-monitor/index.ts +10 -0
  608. package/src/utils/etl-monitor/jsonpath-resolver.util.ts +118 -0
  609. package/src/utils/etl-monitor/normalization-examples.ts +0 -0
  610. package/src/utils/etl-monitor/schema-inspector.util.ts +76 -0
  611. package/src/utils/etl-monitor/semantic-type-detector.util.ts +76 -0
  612. package/src/utils/etl-monitor/status-mapper.util.ts +105 -0
  613. package/src/utils/etl-monitor/value-formatters.util.ts +167 -0
  614. package/src/utils/etl-task/etl-task-card.util.test.ts +240 -0
  615. package/src/utils/etl-task/etl-task-card.util.ts +276 -0
  616. package/src/utils/etl-task/index.ts +1 -0
  617. package/src/utils/html-entities.utils.ts +34 -0
  618. package/src/utils/parameter-resolution.ts +218 -0
  619. package/src/views/etl-monitor-builder/add-monitor-modal.component.scss +0 -0
  620. package/src/views/etl-monitor-builder/add-monitor-modal.component.tsx +210 -0
  621. package/src/views/etl-monitor-builder/etl-monitor-builder.component.scss +157 -0
  622. package/src/views/etl-monitor-builder/etl-monitor-builder.component.tsx +252 -0
  623. package/src/views/etl-monitor-builder/etl-monitor-builder.store.ts +126 -0
  624. package/tools/i18next-parser.config.js +0 -0
  625. package/tools/setup-tests.ts +1 -0
  626. package/tools/update-openmrs-deps.mjs +42 -0
  627. package/translations/am.json +65 -0
  628. package/translations/ar.json +65 -0
  629. package/translations/ar_SY.json +65 -0
  630. package/translations/bn.json +65 -0
  631. package/translations/cs.json +65 -0
  632. package/translations/de.json +65 -0
  633. package/translations/en.json +65 -0
  634. package/translations/en_US.json +65 -0
  635. package/translations/es.json +65 -0
  636. package/translations/es_MX.json +65 -0
  637. package/translations/fr.json +65 -0
  638. package/translations/he.json +65 -0
  639. package/translations/hi.json +65 -0
  640. package/translations/hi_IN.json +65 -0
  641. package/translations/id.json +65 -0
  642. package/translations/it.json +65 -0
  643. package/translations/ka.json +65 -0
  644. package/translations/km.json +65 -0
  645. package/translations/ku.json +65 -0
  646. package/translations/ky.json +65 -0
  647. package/translations/lg.json +65 -0
  648. package/translations/ne.json +65 -0
  649. package/translations/pl.json +65 -0
  650. package/translations/pt.json +65 -0
  651. package/translations/pt_BR.json +65 -0
  652. package/translations/qu.json +65 -0
  653. package/translations/ro_RO.json +65 -0
  654. package/translations/ru_RU.json +65 -0
  655. package/translations/si.json +65 -0
  656. package/translations/sq.json +65 -0
  657. package/translations/sw.json +65 -0
  658. package/translations/sw_KE.json +65 -0
  659. package/translations/tr.json +65 -0
  660. package/translations/tr_TR.json +65 -0
  661. package/translations/uk.json +65 -0
  662. package/translations/uz.json +65 -0
  663. package/translations/uz@Latn.json +65 -0
  664. package/translations/uz_UZ.json +65 -0
  665. package/translations/vi.json +65 -0
  666. package/translations/zh.json +65 -0
  667. package/translations/zh_CN.json +65 -0
  668. package/translations/zh_TW.json +65 -0
  669. package/tsconfig.json +24 -0
  670. package/vitest.config.mts +42 -0
@@ -0,0 +1,637 @@
1
+ /**
2
+ * Section Disaggregation SQL Builder
3
+ *
4
+ * This module builds age/sex disaggregation SQL for report sections.
5
+ * It provides both synchronous (for backward compatibility) and asynchronous
6
+ * (for nested composite indicators) versions.
7
+ *
8
+ * Now supports CUSTOM indicators with complex SQL via the query interpreter.
9
+ */
10
+
11
+ import type { IndicatorDto } from '../../resources/indicator/indicators.api';
12
+ import {
13
+ compilePopulationSql,
14
+ generateAgeSexDisaggregationSql,
15
+ clearCompilationCache,
16
+ type CompilerOptions
17
+ } from '../indicators/utils/population-sql.compiler';
18
+ import {
19
+ countSqlToPopulationSql,
20
+ tryGetPatientIdColumnFromConfig
21
+ } from '../indicators/utils/composite-indicator-sql.utils';
22
+ import {
23
+ resolveIndicatorSql,
24
+ isSqlResolutionError
25
+ } from '../indicators/utils/indicator-sql-resolution.utils';
26
+ import {
27
+ customIndicatorInterpreter,
28
+ type CustomIndicatorConfig
29
+ } from '../indicators/utils/custom-indicator-interpreter';
30
+
31
+ type BuildSectionDisaggSqlArgs = {
32
+ /** The indicator to disaggregate */
33
+ indicator: IndicatorDto;
34
+ /** The age category code (e.g., 'HTS', 'HMIS_106A') */
35
+ ageCategoryCode: string;
36
+ /** The genders to include */
37
+ genders: Array<'F' | 'M'>;
38
+ /** Function to fetch referenced indicators by UUID */
39
+ getIndicator: (uuid: string) => Promise<IndicatorDto | null>;
40
+ /** Compiler options */
41
+ compilerOptions?: CompilerOptions;
42
+ };
43
+
44
+ /**
45
+ * Result of building section disaggregation SQL (async version).
46
+ */
47
+ export type SectionDisaggResult = {
48
+ /** The generated SQL */
49
+ sql: string;
50
+ /** Any warnings from compilation */
51
+ warnings?: string[];
52
+ };
53
+
54
+ /**
55
+ * Builds section disaggregation SQL for any indicator type (BASE, COMPOSITE, FINAL).
56
+ *
57
+ * This is the synchronous version for backward compatibility. It extracts
58
+ * population SQL from the indicator's sql_template without recursive compilation.
59
+ *
60
+ * For nested composite indicators, this version may fail or produce incorrect results.
61
+ * Use `buildSectionDisaggregationSqlAsync` instead for nested composite indicators.
62
+ *
63
+ * @deprecated Use buildSectionDisaggregationSqlAsync for composite indicators.
64
+ * This version cannot recursively compile composite indicators and will be removed in a future version.
65
+ *
66
+ * @returns The generated SQL as a string
67
+ */
68
+ export function buildSectionDisaggregationSql(args: {
69
+ indicator: IndicatorDto;
70
+ ageCategoryCode: string;
71
+ genders: Array<'F' | 'M'>;
72
+ }): string {
73
+ const { indicator, ageCategoryCode, genders } = args;
74
+
75
+ // Handle CUSTOM indicators using the query interpreter
76
+ if (indicator.kind === 'CUSTOM') {
77
+ // Try to parse custom indicator config
78
+ let customConfig: CustomIndicatorConfig | null = null;
79
+ if (indicator.configJson) {
80
+ try {
81
+ const parsed = JSON.parse(indicator.configJson);
82
+ // Check if it's a custom indicator config
83
+ if (parsed && parsed.version === 1 && parsed.patientIdColumn) {
84
+ customConfig = parsed;
85
+ }
86
+ } catch (e) {
87
+ // Invalid JSON, continue with default handling
88
+ }
89
+ }
90
+
91
+ // Use query interpreter to extract population SQL
92
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
93
+ const analysis = customIndicatorInterpreter.analyze(indicator.sqlTemplate || '', customConfig || undefined);
94
+ const extractionResult = customIndicatorInterpreter.extractPopulationSql(indicator.sqlTemplate || '', customConfig || undefined);
95
+
96
+ if (extractionResult.success && extractionResult.sql) {
97
+ // Apply section disaggregation using the query interpreter
98
+ return customIndicatorInterpreter.applyDisaggregation(
99
+ extractionResult.sql,
100
+ { ageCategoryCode, genders: genders ?? [] },
101
+ customConfig || undefined
102
+ );
103
+ }
104
+
105
+ // If extraction failed, return error message
106
+ return `-- Error: Could not process CUSTOM indicator ${indicator.name || indicator.uuid}.\n-- ${extractionResult.warnings?.join(', ') || 'Unknown error'}\n-- Ensure the indicator has a clear population query structure.`;
107
+ }
108
+
109
+ // Check for composite indicators and return error
110
+ if (indicator.kind === 'COMPOSITE') {
111
+ console.warn(`[buildSectionDisaggregationSql] DEPRECATED: Cannot use sync version for COMPOSITE indicator '${indicator.code || indicator.uuid}'. Use buildSectionDisaggregationSqlAsync instead.`);
112
+ return `-- Error: Cannot use sync version for COMPOSITE indicator ${indicator.code || indicator.uuid}.\n-- Use buildSectionDisaggregationSqlAsync for composite indicators.\n-- This synchronous version cannot recursively compile composite indicators.`;
113
+ }
114
+
115
+ // genders is required - must be explicitly provided by caller
116
+ const selectedGenders = genders ?? [];
117
+
118
+ const escapedCode = escapeSql(ageCategoryCode);
119
+
120
+ // Build gender list - if empty, don't add gender filter
121
+ // Note: Don't include quotes here - quotes are added separately in VALUES and IN clauses
122
+ const genderList = selectedGenders.length > 0
123
+ ? selectedGenders.join(',')
124
+ : null;
125
+
126
+ // Build quoted gender list for IN clause
127
+ const genderListQuoted = selectedGenders.length > 0
128
+ ? selectedGenders.map((g) => `'${g}'`).join(',')
129
+ : null;
130
+
131
+ // Get patient ID column from theme config
132
+ const pidCol = tryGetPatientIdColumnFromConfig(indicator);
133
+
134
+ // Try to get population SQL from the indicator
135
+ const populationSql = tryGetPopulationSql(indicator);
136
+
137
+ if (!populationSql) {
138
+ return `-- Unable to build section disaggregation SQL: indicator does not contain usable population SQL.\n-- Ensure the indicator has a valid sqlTemplate or configJson.\n-- Indicator: ${indicator.name} (${indicator.code})\n-- Note: For nested composite indicators, use buildSectionDisaggregationSqlAsync.`;
139
+ }
140
+
141
+ return `
142
+ WITH base_pop AS (
143
+ ${indent(populationSql, 2)}
144
+ ),
145
+ ag AS (
146
+ SELECT
147
+ ag.age_group_id,
148
+ ag.label,
149
+ ag.min_age_days,
150
+ ag.max_age_days,
151
+ ag.sort_order
152
+ FROM report_builder_dim_age_group ag
153
+ JOIN report_builder_dim_age_category ac
154
+ ON ac.age_category_id = ag.age_category_id
155
+ WHERE ac.code = '${escapedCode}'
156
+ AND ag.is_active = 1
157
+ ),
158
+ genders AS (
159
+ ${genderList
160
+ ? genderList.split(',').map((g, i) => `${i === 0 ? `SELECT '${g}' AS gender` : `UNION ALL\n SELECT '${g}' AS gender`}`).join('\n ')
161
+ : `SELECT DISTINCT gender\n FROM mamba_fact_patients_latest_patient_demographics\n WHERE gender IS NOT NULL`}
162
+ ),
163
+ cnt AS (
164
+ SELECT
165
+ ag.age_group_id AS age_group_id,
166
+ mdp.gender AS gender,
167
+ COUNT(DISTINCT base_pop.${pidCol}) AS value
168
+ FROM base_pop
169
+ JOIN mamba_fact_patients_latest_patient_demographics mdp
170
+ ON mdp.${pidCol} = base_pop.${pidCol}
171
+ JOIN ag
172
+ ON TIMESTAMPDIFF(DAY, mdp.birthdate, :endDate)
173
+ BETWEEN ag.min_age_days AND ag.max_age_days
174
+ WHERE mdp.birthdate IS NOT NULL
175
+ AND mdp.gender IS NOT NULL
176
+ ${genderListQuoted ? `AND mdp.gender IN (${genderListQuoted})` : ''}
177
+ GROUP BY ag.age_group_id, mdp.gender
178
+ )
179
+ SELECT
180
+ ag.label AS age_group,
181
+ g.gender AS gender,
182
+ COALESCE(cnt.value, 0) AS value
183
+ FROM ag
184
+ CROSS JOIN genders g
185
+ LEFT JOIN cnt
186
+ ON cnt.age_group_id = ag.age_group_id
187
+ AND cnt.gender = g.gender
188
+ ORDER BY ag.sort_order, g.gender;
189
+ `.trim();
190
+ }
191
+
192
+ /**
193
+ * Async version of section disaggregation SQL builder.
194
+ *
195
+ * This function uses the recursive population SQL compiler to properly handle
196
+ * nested composite indicators. The compiler:
197
+ * 1. Recursively compiles each indicator to population SQL
198
+ * 2. Detects circular dependencies
199
+ * 3. Handles missing references
200
+ * 4. Fixes common typos like `:stratDate` -> `:startDate`
201
+ *
202
+ * Use this version for composite indicators that reference other composite indicators.
203
+ *
204
+ * @returns The generated SQL and any warnings
205
+ */
206
+ export async function buildSectionDisaggregationSqlAsync({
207
+ indicator,
208
+ ageCategoryCode,
209
+ genders,
210
+ getIndicator,
211
+ compilerOptions = {}
212
+ }: BuildSectionDisaggSqlArgs): Promise<SectionDisaggResult> {
213
+ try {
214
+ // Handle CUSTOM indicators using the query interpreter
215
+ if (indicator.kind === 'CUSTOM') {
216
+ // Use centralized SQL resolution for consistent behavior
217
+ const resolvedSql = resolveIndicatorSql(indicator);
218
+
219
+ if (isSqlResolutionError(resolvedSql)) {
220
+ console.error('❌ [CUSTOM] SQL resolution failed:', resolvedSql.error);
221
+ return {
222
+ sql: `-- Error: ${resolvedSql.error}\n-- Indicator: ${indicator.name} (${indicator.code || indicator.uuid})`,
223
+ warnings: [resolvedSql.error]
224
+ };
225
+ }
226
+
227
+ const sqlTemplate = resolvedSql.sql;
228
+
229
+ console.log('📦 [CUSTOM] Extracted SQL:', {
230
+ source: resolvedSql.source,
231
+ length: sqlTemplate?.length || 0,
232
+ preview: sqlTemplate?.substring(0, 200) || 'EMPTY'
233
+ });
234
+
235
+ console.log('🔍 [CUSTOM] Processing custom indicator:', {
236
+ uuid: indicator.uuid,
237
+ name: indicator.name,
238
+ code: indicator.code,
239
+ hasSqlTemplate: !!indicator.sqlTemplate,
240
+ hasConfigJson: !!indicator.configJson,
241
+ finalSqlTemplateLength: sqlTemplate?.length || 0,
242
+ sqlTemplatePreview: sqlTemplate?.substring(0, 300) || 'EMPTY'
243
+ });
244
+
245
+ // NOTE: We DON'T check for "already disaggregated" here because CUSTOM indicators
246
+ // should ALWAYS be re-disaggregated according to the section's configuration.
247
+ // Even if an indicator has its own disaggregation (e.g., DATIM age groups),
248
+ // the section might want to use a different age category (e.g., MOH_105_OPD_DIAG).
249
+ //
250
+ // The "already disaggregated" detection has been removed to ensure all CUSTOM
251
+ // indicators go through the population SQL extraction and re-disaggregation flow.
252
+
253
+ // Try to parse custom indicator config
254
+ let customConfig: CustomIndicatorConfig | null = null;
255
+ if (indicator.configJson) {
256
+ try {
257
+ const parsed = JSON.parse(indicator.configJson);
258
+ // Check if it's a custom indicator config
259
+ if (parsed && parsed.version === 1 && parsed.patientIdColumn) {
260
+ customConfig = parsed;
261
+ console.log('✅ [CUSTOM] Parsed custom config:', customConfig);
262
+ } else {
263
+ console.warn('⚠️ [CUSTOM] Config JSON not valid custom indicator config, using fallback');
264
+ // Check if this config has sqlPreview (theme-based config)
265
+ const hasSqlPreview = !!(parsed?.sqlPreview || parsed?.sqlTemplate);
266
+ // Create a basic config from available data
267
+ customConfig = {
268
+ version: 1,
269
+ patientIdColumn: parsed?.patientIdColumn || parsed?.themeConfig?.patientIdColumn || 'patient_id',
270
+ populationQuery: hasSqlPreview ? { extractFrom: 'configJson' as const } : { extractFrom: 'sqlTemplate' as const },
271
+ supportsRedisaggregation: true,
272
+ redisaggregationStrategy: 'population-extraction',
273
+ themeIndependent: parsed?.themeIndependent !== false
274
+ };
275
+ console.log('🔧 [CUSTOM] Created fallback config:', {
276
+ patientIdColumn: customConfig.patientIdColumn,
277
+ extractFrom: customConfig.populationQuery.extractFrom,
278
+ hasSqlPreview
279
+ });
280
+ }
281
+ } catch (e) {
282
+ console.error('❌ [CUSTOM] Failed to parse configJson:', e);
283
+ // Invalid JSON, continue with default handling
284
+ }
285
+ }
286
+
287
+ // Use query interpreter to extract population SQL
288
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
289
+ const analysis = customIndicatorInterpreter.analyze(sqlTemplate || '', customConfig || undefined);
290
+ const extractionResult = customIndicatorInterpreter.extractPopulationSql(sqlTemplate || '', customConfig || undefined);
291
+
292
+ console.log('📊 [CUSTOM] Extraction result:', {
293
+ success: extractionResult.success,
294
+ hasSql: !!extractionResult.sql,
295
+ sqlLength: extractionResult.sql?.length || 0,
296
+ warnings: extractionResult.warnings,
297
+ patientIdColumn: extractionResult.patientIdColumn
298
+ });
299
+
300
+ if (extractionResult.success && extractionResult.sql) {
301
+ // Apply section disaggregation using the query interpreter
302
+ const sql = customIndicatorInterpreter.applyDisaggregation(
303
+ extractionResult.sql,
304
+ { ageCategoryCode, genders: genders ?? [] },
305
+ customConfig || undefined
306
+ );
307
+ console.log('✅ [CUSTOM] Successfully generated disaggregation SQL, length:', sql.length);
308
+ return { sql, warnings: extractionResult.warnings };
309
+ }
310
+
311
+ // FALLBACK: If extraction failed, check if the indicator already has valid disaggregation
312
+ // If so, use it as-is (even though it won't match the section's age category exactly)
313
+ if (sqlTemplate && /SELECT\s+.*?AS\s+(?:age_group|gender|sex)/i.test(sqlTemplate)) {
314
+ console.log('⚠️ [CUSTOM] Could not extract population SQL; using indicator SQL as-is with warning');
315
+ return {
316
+ sql: sqlTemplate,
317
+ warnings: [
318
+ 'Could not extract population SQL; using indicator SQL as-is',
319
+ `Indicator uses its own age groups, not section age category (${ageCategoryCode})`,
320
+ 'Results may not match section configuration',
321
+ ...extractionResult.warnings
322
+ ]
323
+ };
324
+ }
325
+
326
+ // If extraction failed and no valid SQL, return error
327
+ console.error('❌ [CUSTOM] Extraction failed:', extractionResult.warnings);
328
+ return {
329
+ sql: `-- Error: Could not process CUSTOM indicator ${indicator.name || indicator.uuid}.\n-- ${extractionResult.warnings?.join(', ') || 'Unknown error'}`,
330
+ warnings: extractionResult.warnings || ['Failed to extract population SQL']
331
+ };
332
+ }
333
+
334
+ // Clear cache to ensure fresh compilation
335
+ clearCompilationCache();
336
+
337
+ // Recursively compile the indicator to population SQL
338
+ const result = await compilePopulationSql(indicator, getIndicator, new Set(), compilerOptions);
339
+
340
+ // Check for compilation errors
341
+ if (result.error) {
342
+ return {
343
+ sql: `-- Error: ${result.error.message}\n-- Indicator: ${indicator.name} (${indicator.code || indicator.uuid})\n-- Error Code: ${result.error.code}`,
344
+ warnings: result.warnings || [result.error.message]
345
+ };
346
+ }
347
+
348
+ // Generate the disaggregation SQL
349
+ const sql = generateAgeSexDisaggregationSql({
350
+ populationSql: result.sql,
351
+ ageCategoryCode,
352
+ genders
353
+ });
354
+
355
+ return {
356
+ sql,
357
+ warnings: result.warnings
358
+ };
359
+ } catch (error) {
360
+ // Re-throw PopulationSqlError to be handled by caller
361
+ if (error instanceof Error && error.name === 'PopulationSqlError') {
362
+ throw error;
363
+ }
364
+
365
+ // Return an error comment in the SQL for other errors
366
+ const errorMsg = error instanceof Error ? error.message : String(error);
367
+ return {
368
+ sql: `-- Error building disaggregation SQL: ${errorMsg}\n-- Indicator: ${indicator.name} (${indicator.code || indicator.uuid})`,
369
+ warnings: [`Compilation error: ${errorMsg}`]
370
+ };
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Match a balanced parenthesis group in SQL
376
+ * Handles nested subqueries by counting opening/closing parens
377
+ *
378
+ * @param sql - SQL string to match
379
+ * @param startIndex - Index to start matching from (after opening paren)
380
+ * @returns Object with matched text and end index, or null if no match
381
+ */
382
+ function matchBalancedParens(sql: string, startIndex: number): { text: string; endIndex: number } | null {
383
+ let depth = 1;
384
+ let i = startIndex;
385
+ const len = sql.length;
386
+
387
+ while (i < len && depth > 0) {
388
+ const char = sql[i];
389
+ if (char === '(') {
390
+ depth++;
391
+ } else if (char === ')') {
392
+ depth--;
393
+ }
394
+ i++;
395
+ }
396
+
397
+ if (depth !== 0) {
398
+ // Unbalanced parentheses
399
+ return null;
400
+ }
401
+
402
+ return {
403
+ text: sql.substring(startIndex, i - 1),
404
+ endIndex: i
405
+ };
406
+ }
407
+
408
+ /**
409
+ * Extract the inner population SQL from a complex indicator.
410
+ * Complex indicators have the structure:
411
+ * SELECT COUNT(DISTINCT id) FROM (population_query_with_joins) alias
412
+ *
413
+ * We need to extract just the population_query part to use with section disaggregation.
414
+ */
415
+ function extractPopulationSqlFromComplexIndicator(sql: string): string | null {
416
+ if (!sql) return null;
417
+
418
+ // Pattern 1: COUNT DISTINCT with FROM subquery that has JOINs and WHERE
419
+ // SELECT COUNT(DISTINCT a.patient_id) FROM (SELECT ...) a LEFT JOIN ... WHERE ...
420
+ // Use balanced parenthesis matching to handle nested subqueries
421
+ const countDistinctFromPattern = /SELECT\s+COUNT\s*\(\s*DISTINCT\s+\w+\.?\w*\s*\)\s*FROM\s*\(\s*SELECT/i;
422
+ const countDistinctMatch = sql.match(countDistinctFromPattern);
423
+
424
+ if (countDistinctMatch) {
425
+ // Find the position after "FROM (SELECT"
426
+ const afterFromIndex = countDistinctMatch.index + countDistinctMatch[0].length;
427
+
428
+ // Use balanced parenthesis matching to get the complete inner query
429
+ const balancedMatch = matchBalancedParens(sql, afterFromIndex);
430
+
431
+ if (balancedMatch) {
432
+ const populationSql = `SELECT${balancedMatch.text}`.trim();
433
+ const afterInnerQuery = balancedMatch.endIndex;
434
+
435
+ // Extract the outer alias (should be right after the closing paren)
436
+ const aliasPattern = /\s*(\w+)\s*([\s\S]*)/;
437
+ const aliasMatch = sql.substring(afterInnerQuery).match(aliasPattern);
438
+
439
+ if (aliasMatch) {
440
+ const alias = aliasMatch[1];
441
+ const restOfQuery = aliasMatch[2] || '';
442
+
443
+ // Check if the inner query has GROUP BY patient_id or similar
444
+ if (/GROUP\s+BY\s+(patient_id|patient_id|person_id)/i.test(populationSql)) {
445
+ // Build the full population query including JOINs and WHERE
446
+ // Replace the alias references in JOINs and WHERE with the actual table
447
+ let fullPopulationSql = `SELECT DISTINCT ${alias}.patient_id AS patient_id\nFROM (\n ${indent(populationSql, 2)}\n) ${alias}\n${restOfQuery.trim()}`;
448
+
449
+ // Fix column references in rest of query
450
+ fullPopulationSql = fullPopulationSql.replace(new RegExp(`${alias}\\.patient_id`, 'g'), 'patient_id');
451
+
452
+ return fullPopulationSql;
453
+ }
454
+ }
455
+ }
456
+ }
457
+
458
+ // Pattern 2: Simple FROM subquery (for backward compatibility)
459
+ // FROM (SELECT ... GROUP BY patient_id) alias
460
+ const fromSubqueryPattern = /FROM\s*\(\s*SELECT/i;
461
+ const fromMatch = sql.match(fromSubqueryPattern);
462
+
463
+ if (fromMatch) {
464
+ // Find the position after "FROM (SELECT"
465
+ const afterFromIndex = fromMatch.index + fromMatch[0].length;
466
+
467
+ // Use balanced parenthesis matching to get the complete inner query
468
+ const balancedMatch = matchBalancedParens(sql, afterFromIndex);
469
+
470
+ if (balancedMatch) {
471
+ const populationSql = `SELECT${balancedMatch.text}`.trim();
472
+ const afterInnerQuery = balancedMatch.endIndex;
473
+
474
+ // Extract the outer alias (should be right after the closing paren)
475
+ const aliasPattern = /\s*(\w+)\s*(?:WHERE|GROUP BY|ORDER BY|HAVING|$)/i;
476
+ const aliasMatch = sql.substring(afterInnerQuery).match(aliasPattern);
477
+
478
+ if (aliasMatch) {
479
+ // Check if it has GROUP BY patient_id or similar
480
+ if (/GROUP\s+BY\s+(patient_id|patient_id|person_id)/i.test(populationSql)) {
481
+ // This looks like a valid population query
482
+ return populationSql;
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ // Pattern 3: Find first WITH clause that looks like population data
489
+ // WITH base_pop AS (SELECT disaggregation, COUNT(*) FROM (population_query) ...)
490
+ const withPattern = /WITH\s+\w+\s+AS\s*\(\s*SELECT[\s\S]*?FROM\s*\(\s*SELECT/i;
491
+ const withMatch = sql.match(withPattern);
492
+
493
+ if (withMatch) {
494
+ // Find the position after the second "FROM (SELECT"
495
+ const nestedFromIndex = withMatch.index + withMatch[0].length;
496
+
497
+ // Use balanced parenthesis matching to get the complete inner query
498
+ const balancedMatch = matchBalancedParens(sql, nestedFromIndex);
499
+
500
+ if (balancedMatch) {
501
+ const populationSql = `SELECT${balancedMatch.text}`.trim();
502
+
503
+ // Check if it has GROUP BY patient_id or similar
504
+ if (/GROUP\s+BY\s+(patient_id|patient_id|person_id)/i.test(populationSql)) {
505
+ return populationSql;
506
+ }
507
+ }
508
+ }
509
+
510
+ // Pattern 4: Look for the core population query pattern
511
+ // SELECT patient_id, ... FROM table WHERE ... GROUP BY patient_id
512
+ const corePattern = /SELECT\s+(patient_id|patient_id|person_id)[\s\S]*?FROM\s+[\w_]+[\s\S]*?GROUP\s+BY\s+(patient_id|patient_id|person_id)/i;
513
+ const coreMatch = sql.match(corePattern);
514
+
515
+ if (coreMatch) {
516
+ return coreMatch[0];
517
+ }
518
+
519
+ return null;
520
+ }
521
+
522
+ /**
523
+ * Check if an indicator is complex (has built-in disaggregation logic).
524
+ * Complex indicators should not be processed through COUNT-to-population conversion
525
+ * because they already have their own disaggregation structure.
526
+ */
527
+ function isComplexIndicator(sql: string): boolean {
528
+ if (!sql) return false;
529
+
530
+ // Signs of complex indicators with built-in disaggregation:
531
+ // 1. Multiple GROUP BY clauses
532
+ const groupByCount = (sql.match(/GROUP BY/gi) || []).length;
533
+ if (groupByCount > 1) return true;
534
+
535
+ // 2. Has age_group/gender columns in SELECT with aggregation
536
+ if (/age_group.*AS.*aggregat|gender.*AS.*sex|disaggregat/i.test(sql)) return true;
537
+
538
+ // 3. Has complex WITH clauses (multiple CTEs with aggregation)
539
+ const withCount = (sql.match(/WITH.*AS\s*\(/gi) || []).length;
540
+ if (withCount > 1 && /GROUP BY/i.test(sql)) return true;
541
+
542
+ return false;
543
+ }
544
+
545
+ /**
546
+ * Try to extract population SQL from an indicator's sql_template or configJson.
547
+ *
548
+ * This attempts to convert COUNT SQL to population SQL for simple cases.
549
+ * For complex nested composite indicators, use the async version instead.
550
+ */
551
+ function tryGetPopulationSql(indicator: IndicatorDto): string | null {
552
+ // First, try sqlTemplate directly
553
+ let sql = indicator.sqlTemplate || '';
554
+
555
+ // If not found, try config_json fields (like tryGetCountSqlFromIndicator does)
556
+ if (!sql && indicator.configJson) {
557
+ try {
558
+ const parsed = JSON.parse(indicator.configJson);
559
+ // Try various config paths
560
+ sql = parsed?.sqlPreview ||
561
+ parsed?.sqlTemplate ||
562
+ parsed?.base?.sqlPreview ||
563
+ parsed?.base?.sqlTemplate ||
564
+ parsed?.authoring?.base?.sqlPreview ||
565
+ parsed?.authoring?.base?.sqlTemplate ||
566
+ parsed?.baseIndicator?.sqlPreview ||
567
+ parsed?.baseIndicator?.sqlTemplate ||
568
+ '';
569
+ } catch (e) {
570
+ // Invalid JSON, continue
571
+ }
572
+ }
573
+
574
+ const trimmed = sql.trim();
575
+
576
+ if (!trimmed) {
577
+ return null;
578
+ }
579
+
580
+ // Check if this is a complex indicator with built-in disaggregation
581
+ // If so, try to extract the population SQL from it
582
+ if (isComplexIndicator(trimmed)) {
583
+ const extractedPopulationSql = extractPopulationSqlFromComplexIndicator(trimmed);
584
+ if (extractedPopulationSql) {
585
+ // Use the extracted population SQL for section disaggregation
586
+ return extractedPopulationSql;
587
+ }
588
+ // If extraction fails, return an error message
589
+ return `-- Error: Could not extract population SQL from this complex indicator.\n-- Indicator: ${indicator.name} (${indicator.code})\n-- Complex indicators with multiple GROUP BY clauses or age_group/gender aggregation need to have a clear population query structure.\n-- Ensure the indicator has a subquery with: SELECT patient_id FROM ... GROUP BY patient_id`;
590
+ }
591
+
592
+ // Remove trailing semicolons - they cause "Multiple statements" errors when used in CTEs
593
+ const withoutSemicolon = trimmed.replace(/;+\s*$/, '');
594
+
595
+ // Handle escaped newlines - convert literal \n to actual newlines
596
+ // This can happen when SQL is serialized through JSON in the backend
597
+ let fixed = withoutSemicolon.replace(/\\n/g, '\n');
598
+
599
+ // Fix common typos
600
+ fixed = fixed.replace(/:stratDate\b/g, ':startDate');
601
+
602
+ // Check if it's already a population query
603
+ if (/SELECT\s+DISTINCT\s+(?:\w+\.?patient_id|patient_id)/i.test(fixed)) {
604
+ return fixed;
605
+ }
606
+
607
+ // Try to convert COUNT SQL to population SQL using the existing utility
608
+ const pidCol = tryGetPatientIdColumnFromConfig(indicator);
609
+
610
+ try {
611
+ const populationSql = countSqlToPopulationSql(fixed, pidCol, 'Patients');
612
+ if (populationSql) {
613
+ return populationSql;
614
+ }
615
+ } catch (e) {
616
+ // Conversion failed, continue to fallback
617
+ }
618
+
619
+ // As a last resort, if it looks like a population query, return it
620
+ if (/^SELECT\s+.*?\s+FROM\s+/i.test(fixed)) {
621
+ return fixed;
622
+ }
623
+
624
+ return null;
625
+ }
626
+
627
+ function escapeSql(s: string): string {
628
+ return String(s).replace(/'/g, "''");
629
+ }
630
+
631
+ function indent(sql: string, spaces: number): string {
632
+ const pad = ' '.repeat(spaces);
633
+ return sql
634
+ .split('\n')
635
+ .map((line) => (line.trim() ? pad + line : line))
636
+ .join('\n');
637
+ }
@@ -0,0 +1,55 @@
1
+ import type { ReportSectionDto } from '../../resources/report-section/report-sections.api';
2
+
3
+ export type SectionIndicatorType = 'BASE' | 'COMPOSITE' | 'FINAL' | 'CUSTOM';
4
+
5
+ export type SectionIndicatorRef = {
6
+ id: string;
7
+ type: SectionIndicatorType;
8
+ code: string;
9
+ name: string;
10
+ };
11
+
12
+ export type CreateSectionPayload = {
13
+ id?: string; // section uuid when editing
14
+ name: string;
15
+ description?: string;
16
+
17
+ disaggregationEnabled: boolean;
18
+ ageCategoryUuid: string | null;
19
+ ageCategoryCode?: string | null;
20
+ genders: Array<'F' | 'M'>;
21
+
22
+ indicators: Array<{ id: string; type: SectionIndicatorType; sortOrder: number; finalDisagg?: any }>;
23
+
24
+ /**
25
+ * JSON config that includes compiled SQL + strategy metadata.
26
+ * This is the payload you can persist as-is on the backend.
27
+ */
28
+ configJson: string;
29
+ };
30
+
31
+ export type Dhis2MappingV1 = {
32
+ version: 1;
33
+ enabled: boolean;
34
+ datasetId?: string;
35
+ periodType?: string;
36
+ orgUnitStrategy?: 'location' | 'fixed';
37
+ mappingMode?: 'dataElement-categoryOptionCombo';
38
+ indicatorMappings: Array<{
39
+ indicatorUuid: string;
40
+ dataElementId?: string;
41
+ categoryOptionComboByDisagg?: Record<string, string>;
42
+ }>;
43
+ };
44
+
45
+ export type ReportSectionEditorMode = 'create' | 'edit';
46
+
47
+ export type ReportSectionEditorProps = {
48
+ open: boolean;
49
+ onClose: () => void;
50
+ onSubmit: (payload: CreateSectionPayload) => void;
51
+ indicators: SectionIndicatorRef[];
52
+
53
+ mode?: ReportSectionEditorMode;
54
+ initialSection?: ReportSectionDto | null;
55
+ };