@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.
- package/.eslintignore +5 -0
- package/.eslintrc.js +19 -0
- package/.prettierrc +8 -0
- package/.tx/config +12 -0
- package/LICENSE +401 -0
- package/README.md +336 -0
- package/__mocks__/react-i18next.js +12 -0
- package/dist/128.css +1 -0
- package/dist/128.css.map +1 -0
- package/dist/128.js +338 -0
- package/dist/128.js.map +1 -0
- package/dist/1480.js +1 -0
- package/dist/1480.js.map +1 -0
- package/dist/1646.js +1 -0
- package/dist/1646.js.map +1 -0
- package/dist/1869.js +1 -0
- package/dist/1869.js.map +1 -0
- package/dist/1877.js +1 -0
- package/dist/1877.js.map +1 -0
- package/dist/2131.js +1 -0
- package/dist/2131.js.map +1 -0
- package/dist/2317.js +1 -0
- package/dist/2317.js.map +1 -0
- package/dist/2408.js +1 -0
- package/dist/2408.js.map +1 -0
- package/dist/2416.js +1 -0
- package/dist/2416.js.map +1 -0
- package/dist/282.js +1 -0
- package/dist/282.js.map +1 -0
- package/dist/2881.js +1 -0
- package/dist/2881.js.map +1 -0
- package/dist/2997.js +1 -0
- package/dist/2997.js.map +1 -0
- package/dist/3378.js +1 -0
- package/dist/3378.js.map +1 -0
- package/dist/3963.js +1 -0
- package/dist/3963.js.map +1 -0
- package/dist/4106.js +1 -0
- package/dist/4106.js.map +1 -0
- package/dist/4111.js +1 -0
- package/dist/4111.js.map +1 -0
- package/dist/4171.css +1 -0
- package/dist/4171.css.map +1 -0
- package/dist/4171.js +43 -0
- package/dist/4171.js.map +1 -0
- package/dist/434.js +1 -0
- package/dist/434.js.map +1 -0
- package/dist/4348.js +1 -0
- package/dist/4348.js.map +1 -0
- package/dist/4383.js +1 -0
- package/dist/4383.js.map +1 -0
- package/dist/4658.js +1 -0
- package/dist/4658.js.map +1 -0
- package/dist/4668.js +1 -0
- package/dist/4668.js.map +1 -0
- package/dist/4928.js +1 -0
- package/dist/4928.js.map +1 -0
- package/dist/5098.js +1 -0
- package/dist/5098.js.map +1 -0
- package/dist/5117.js +1 -0
- package/dist/5117.js.map +1 -0
- package/dist/5132.js +1 -0
- package/dist/5132.js.map +1 -0
- package/dist/5145.js +1 -0
- package/dist/5145.js.map +1 -0
- package/dist/550.js +1 -0
- package/dist/550.js.map +1 -0
- package/dist/5503.js +1 -0
- package/dist/5503.js.map +1 -0
- package/dist/556.js +1 -0
- package/dist/556.js.map +1 -0
- package/dist/5644.js +1 -0
- package/dist/5644.js.map +1 -0
- package/dist/5940.js +1 -0
- package/dist/5940.js.map +1 -0
- package/dist/6047.js +1 -0
- package/dist/6047.js.map +1 -0
- package/dist/6237.js +1 -0
- package/dist/6237.js.map +1 -0
- package/dist/6371.js +1 -0
- package/dist/6371.js.map +1 -0
- package/dist/6377.js +1 -0
- package/dist/6377.js.map +1 -0
- package/dist/6444.js +1 -0
- package/dist/6444.js.map +1 -0
- package/dist/6508.js +1 -0
- package/dist/6508.js.map +1 -0
- package/dist/6652.js +1 -0
- package/dist/6652.js.map +1 -0
- package/dist/6686.js +1 -0
- package/dist/6686.js.map +1 -0
- package/dist/6724.js +1 -0
- package/dist/6724.js.map +1 -0
- package/dist/6867.js +19 -0
- package/dist/6867.js.map +1 -0
- package/dist/6904.js +1 -0
- package/dist/6904.js.map +1 -0
- package/dist/7045.js +1 -0
- package/dist/7045.js.map +1 -0
- package/dist/7175.js +1 -0
- package/dist/7175.js.map +1 -0
- package/dist/7182.js +1 -0
- package/dist/7182.js.map +1 -0
- package/dist/7475.js +1 -0
- package/dist/7475.js.map +1 -0
- package/dist/7742.js +1 -0
- package/dist/7742.js.map +1 -0
- package/dist/7912.js +1 -0
- package/dist/7912.js.map +1 -0
- package/dist/8251.js +1 -0
- package/dist/8251.js.map +1 -0
- package/dist/8358.js +1 -0
- package/dist/8358.js.map +1 -0
- package/dist/8359.js +1 -0
- package/dist/8359.js.map +1 -0
- package/dist/8695.js +1 -0
- package/dist/8695.js.map +1 -0
- package/dist/903.js +1 -0
- package/dist/903.js.map +1 -0
- package/dist/9072.js +1 -0
- package/dist/9072.js.map +1 -0
- package/dist/9806.js +1 -0
- package/dist/9806.js.map +1 -0
- package/dist/997.js +1 -0
- package/dist/997.js.map +1 -0
- package/dist/openmrs-esm-report-builder.js +3 -0
- package/dist/openmrs-esm-report-builder.js.buildmanifest.json +1433 -0
- package/dist/openmrs-esm-report-builder.js.map +1 -0
- package/dist/routes.json +1 -0
- package/e2e/core/env.ts +5 -0
- package/e2e/core/global-setup.ts +34 -0
- package/e2e/core/test.ts +21 -0
- package/e2e/pages/app-shell.page.ts +35 -0
- package/e2e/pages/etl-sources.page.ts +36 -0
- package/e2e/pages/report-sections.page.ts +21 -0
- package/e2e/pages/section-preview.modal.ts +29 -0
- package/e2e/specs/etl-sources.spec.ts +51 -0
- package/e2e/specs/navigation.spec.ts +13 -0
- package/e2e/specs/section-preview-validation.spec.ts +38 -0
- package/e2e/specs/section-preview.spec.ts +94 -0
- package/e2e/support/github/Dockerfile +32 -0
- package/e2e/support/github/Dockerfile.dockerignore +13 -0
- package/e2e/support/github/docker-compose.yml +31 -0
- package/e2e/support/github/run-e2e-docker-env.sh +131 -0
- package/e2e/support/github/spa-assemble-config.json +6 -0
- package/e2e/tsconfig.json +11 -0
- package/example.env +5 -0
- package/linelist_report_builder_design_spec.md +816 -0
- package/package.json +98 -0
- package/playwright.config.ts +28 -0
- package/rspack.config.js +2 -0
- package/src/__debug__/all-designs.test.tsx +33 -0
- package/src/admin-card-link.extension.module.scss +55 -0
- package/src/admin-card-link.extension.tsx +24 -0
- package/src/components/admin/admin-page.component.tsx +75 -0
- package/src/components/admin/age-categories-page.component.tsx +109 -0
- package/src/components/admin/age-groups-page.component.tsx +146 -0
- package/src/components/admin/etl-browser/columns-tab.component.tsx +80 -0
- package/src/components/admin/etl-browser/etl-browser-page.component.tsx +309 -0
- package/src/components/admin/etl-browser/etl-browser.component.scss +424 -0
- package/src/components/admin/etl-browser/metadata-tab.component.tsx +65 -0
- package/src/components/admin/etl-browser/preview-grid.component.tsx +189 -0
- package/src/components/admin/etl-browser/relationships-tab.component.tsx +21 -0
- package/src/components/admin/etl-browser/sources-tree.component.tsx +118 -0
- package/src/components/admin/etl-browser/table-detail.component.tsx +124 -0
- package/src/components/admin/etl-browser/table-info-strip.component.tsx +51 -0
- package/src/components/admin/etl-browser/table-list.component.tsx +136 -0
- package/src/components/admin/etl-browser/use-table-preview.ts +47 -0
- package/src/components/admin/etl-sources-page.component.tsx +355 -0
- package/src/components/admin/etl-tasks-page.component.scss +370 -0
- package/src/components/admin/etl-tasks-page.component.tsx +768 -0
- package/src/components/admin/report-categories-page.component.tsx +267 -0
- package/src/components/admin/report-library-page.component.tsx +551 -0
- package/src/components/ai-support/ai-assist-button.component.tsx +30 -0
- package/src/components/ai-support/ai-assist-modal.component.tsx +81 -0
- package/src/components/ai-support/ai.service.ts +21 -0
- package/src/components/ai-support/ai.types.ts +12 -0
- package/src/components/app-shell/report-builder-shell.component.tsx +242 -0
- package/src/components/app-shell/report-builder-shell.scss +18 -0
- package/src/components/dashboards/dashboard-config.schema.ts +97 -0
- package/src/components/dashboards/dashboard-form-modal.component.tsx +150 -0
- package/src/components/dashboards/dashboards-admin.scss +62 -0
- package/src/components/dashboards/dashboards-page.component.tsx +153 -0
- package/src/components/dashboards/dashboards-table.component.tsx +130 -0
- package/src/components/dashboards/sections/dashboard-autoinclude.section.tsx +75 -0
- package/src/components/dashboards/sections/dashboard-basics.section.tsx +80 -0
- package/src/components/dashboards/sections/dashboard-json.section.tsx +79 -0
- package/src/components/dashboards/sections/dashboard-sections-editor.section.tsx +130 -0
- package/src/components/dashboards/sections/dashboard-widgets-editor.section.tsx +184 -0
- package/src/components/data-themes/data-theme-form.component.tsx +59 -0
- package/src/components/data-themes/data-theme-modal.component.tsx +349 -0
- package/src/components/data-themes/data-themes-page.component.tsx +139 -0
- package/src/components/data-themes/data-themes-table.component.tsx +98 -0
- package/src/components/data-themes/icon-registry.ts +35 -0
- package/src/components/data-themes/sections/data-theme-basics.section.tsx +63 -0
- package/src/components/data-themes/sections/data-theme-condition-columns.section.tsx +397 -0
- package/src/components/data-themes/sections/data-theme-fields-editor.section.tsx +125 -0
- package/src/components/data-themes/sections/data-theme-metadata.section.tsx +245 -0
- package/src/components/data-themes/sections/data-theme-preview.section.tsx +115 -0
- package/src/components/data-themes/sections/data-theme-source.section.tsx +184 -0
- package/src/components/data-visualizer/components/cqi-components/cqi-data-table.component.tsx +214 -0
- package/src/components/data-visualizer/components/cqi-components/cqi-elements.ts +203 -0
- package/src/components/data-visualizer/components/cqi-components/detail.component.tsx +110 -0
- package/src/components/data-visualizer/components/cqi-components/detail.scss +22 -0
- package/src/components/data-visualizer/components/data-table/data-table.component.tsx +208 -0
- package/src/components/data-visualizer/components/data-table/data-tables.scss +206 -0
- package/src/components/data-visualizer/components/empty-state/empty-state-illustration.component.tsx +53 -0
- package/src/components/data-visualizer/components/header/header.component.tsx +43 -0
- package/src/components/data-visualizer/components/header/header.scss +71 -0
- package/src/components/data-visualizer/components/model/confirm.component.tsx +40 -0
- package/src/components/data-visualizer/components/panel/panel.component.tsx +20 -0
- package/src/components/data-visualizer/components/panel/panel.scss +25 -0
- package/src/components/data-visualizer/components/popover/modifier-panel.tsx +53 -0
- package/src/components/data-visualizer/constants.ts +445 -0
- package/src/components/data-visualizer/data-visualizer-illustration.component.tsx +31 -0
- package/src/components/data-visualizer/data-visualizer.component.tsx +1935 -0
- package/src/components/data-visualizer/data-visualizer.resource.ts +462 -0
- package/src/components/data-visualizer/data-visualizer.scss +448 -0
- package/src/components/data-visualizer/declarations.d.ts +131 -0
- package/src/components/data-visualizer/parameter-inputs/boolean-parameter-input.component.tsx +34 -0
- package/src/components/data-visualizer/parameter-inputs/concept-parameter-input.component.tsx +81 -0
- package/src/components/data-visualizer/parameter-inputs/date-parameter-input.component.tsx +110 -0
- package/src/components/data-visualizer/parameter-inputs/date-range-input.component.scss +47 -0
- package/src/components/data-visualizer/parameter-inputs/date-range-input.component.tsx +228 -0
- package/src/components/data-visualizer/parameter-inputs/identifier-type-parameter-input.component.tsx +69 -0
- package/src/components/data-visualizer/parameter-inputs/list-parameter-input.component.tsx +73 -0
- package/src/components/data-visualizer/parameter-inputs/location-parameter-input.component.tsx +81 -0
- package/src/components/data-visualizer/parameter-inputs/number-parameter-input.component.tsx +40 -0
- package/src/components/data-visualizer/parameter-inputs/person-attribute-parameter-input.component.tsx +71 -0
- package/src/components/data-visualizer/parameter-inputs/text-parameter-input.component.tsx +52 -0
- package/src/components/data-visualizer/report-parameter-modal.component.tsx +383 -0
- package/src/components/data-visualizer/types.ts +206 -0
- package/src/components/etl-monitor/builder/builder-state-machine.ts +381 -0
- package/src/components/etl-monitor/builder/design-registry.tsx +139 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-context.tsx +702 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-modal.component.tsx +461 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-modal.scss +256 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-page.component.tsx +387 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-page.scss +976 -0
- package/src/components/etl-monitor/builder/generated-config-panel.component.tsx +168 -0
- package/src/components/etl-monitor/builder/generated-config-panel.scss +198 -0
- package/src/components/etl-monitor/builder/index.ts +5 -0
- package/src/components/etl-monitor/builder/monitor-preview.tsx +124 -0
- package/src/components/etl-monitor/builder/steps/data-source-step.component.tsx +667 -0
- package/src/components/etl-monitor/builder/steps/data-source-step.scss +284 -0
- package/src/components/etl-monitor/builder/steps/design-samples.ts +278 -0
- package/src/components/etl-monitor/builder/steps/design-step.component.tsx +299 -0
- package/src/components/etl-monitor/builder/steps/design-step.scss +479 -0
- package/src/components/etl-monitor/builder/steps/fields-step.component.tsx +859 -0
- package/src/components/etl-monitor/builder/steps/fields-step.scss +459 -0
- package/src/components/etl-monitor/builder/steps/general-step.component.tsx +288 -0
- package/src/components/etl-monitor/builder/steps/general-step.scss +103 -0
- package/src/components/etl-monitor/builder/steps/index.ts +5 -0
- package/src/components/etl-monitor/builder/steps/preview-step.component.tsx +245 -0
- package/src/components/etl-monitor/builder/steps/preview-step.scss +236 -0
- package/src/components/etl-monitor/etl-monitor-card-shell.component.tsx +154 -0
- package/src/components/etl-monitor/etl-monitor-card-shell.scss +103 -0
- package/src/components/etl-monitor/etl-monitor-dashboard.component.tsx +227 -0
- package/src/components/etl-monitor/etl-monitor-dashboard.scss +148 -0
- package/src/components/etl-monitor/etl-monitor-status-card.component.tsx +192 -0
- package/src/components/etl-monitor/etl-monitors-page.component.tsx +621 -0
- package/src/components/etl-monitor/renderers/DetailsRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/ErrorLogRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/EtlMonitorWidget.tsx +97 -0
- package/src/components/etl-monitor/renderers/MetricsGridRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/MonitorRenderer.tsx +175 -0
- package/src/components/etl-monitor/renderers/ProgressRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/StatusCardRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/SummaryCardRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/TableRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/component-icons.tsx +33 -0
- package/src/components/etl-monitor/renderers/components/details-renderer.tsx +195 -0
- package/src/components/etl-monitor/renderers/components/error-log-renderer.tsx +104 -0
- package/src/components/etl-monitor/renderers/components/index.ts +6 -0
- package/src/components/etl-monitor/renderers/components/log-renderer.tsx +86 -0
- package/src/components/etl-monitor/renderers/components/metrics-grid-renderer.tsx +133 -0
- package/src/components/etl-monitor/renderers/components/progress-renderer.tsx +188 -0
- package/src/components/etl-monitor/renderers/components/status-card-renderer.tsx +247 -0
- package/src/components/etl-monitor/renderers/components/summary-card-renderer.tsx +160 -0
- package/src/components/etl-monitor/renderers/components/table-renderer.tsx +175 -0
- package/src/components/etl-monitor/renderers/components/time-series-renderer.tsx +310 -0
- package/src/components/etl-monitor/renderers/data-transformer.ts +361 -0
- package/src/components/etl-monitor/renderers/index.ts +11 -0
- package/src/components/etl-monitor/renderers/legacy-monitor-renderer.tsx +69 -0
- package/src/components/etl-monitor/renderers/monitor-renderer.tsx +6 -0
- package/src/components/etl-monitor/renderers/monitor-renderers.scss +1621 -0
- package/src/components/etl-monitor/renderers/monitor-widget-card.component.tsx +54 -0
- package/src/components/etl-monitor/renderers/monitor-widget-card.scss +16 -0
- package/src/components/etl-monitor/renderers/v2-monitor-renderer.tsx +227 -0
- package/src/components/import-export/artifact-groups.ts +153 -0
- package/src/components/import-export/export-report-modal.component.tsx +265 -0
- package/src/components/import-export/import-export-page.component.tsx +610 -0
- package/src/components/import-export/import-export.styles.scss +825 -0
- package/src/components/import-export/import-package-modal.component.tsx +401 -0
- package/src/components/import-export/index.ts +3 -0
- package/src/components/indicators/README.md +24 -0
- package/src/components/indicators/base-indicator-basic-fields.component.tsx +18 -0
- package/src/components/indicators/base-indicator-basics.section.tsx +54 -0
- package/src/components/indicators/base-indicator-countby.section.tsx +25 -0
- package/src/components/indicators/base-indicator-criteria.section.tsx +83 -0
- package/src/components/indicators/base-indicator-details-form.component.tsx +80 -0
- package/src/components/indicators/base-indicator-theme-unit-fields.component.tsx +39 -0
- package/src/components/indicators/create-base-indicator-modal.component.tsx +623 -0
- package/src/components/indicators/create-composite-base-indicator-modal.component.tsx +295 -0
- package/src/components/indicators/create-custom-indicator-modal.component.tsx +548 -0
- package/src/components/indicators/create-final-indicator-modal.component.tsx +285 -0
- package/src/components/indicators/custom-condition-input.component.tsx +310 -0
- package/src/components/indicators/diagnosis-filters-form.component.tsx +101 -0
- package/src/components/indicators/disaggregation-panel.component.tsx +93 -0
- package/src/components/indicators/handler/concept-search-multiselect.component.tsx +153 -0
- package/src/components/indicators/handler/question-answer-concept-search.component.tsx +336 -0
- package/src/components/indicators/indicator-run-preview-modal.component.tsx +391 -0
- package/src/components/indicators/indicator-search-select.component.tsx +297 -0
- package/src/components/indicators/indicator-status-tag.component.tsx +9 -0
- package/src/components/indicators/indicators-page.component.tsx +673 -0
- package/src/components/indicators/indicators-page.scss +121 -0
- package/src/components/indicators/indicators-table.component.tsx +188 -0
- package/src/components/indicators/preview-table.component.tsx +31 -0
- package/src/components/indicators/sections/composite-indicator-basics.section.tsx +36 -0
- package/src/components/indicators/sections/composite-indicator-picker.section.tsx +116 -0
- package/src/components/indicators/sections/composite-indicator-sql-preview.section.tsx +31 -0
- package/src/components/indicators/sections/custom-indicator-basics.section.tsx +65 -0
- package/src/components/indicators/sections/custom-indicator-disaggregation.section.tsx +93 -0
- package/src/components/indicators/sections/custom-indicator-filters.section.tsx +203 -0
- package/src/components/indicators/sections/custom-indicator-population.section.tsx +107 -0
- package/src/components/indicators/sections/custom-indicator-sql-preview.section.tsx +108 -0
- package/src/components/indicators/sections/final-indicator-basics.section.tsx +46 -0
- package/src/components/indicators/sections/final-indicator-disaggregation.section.tsx +47 -0
- package/src/components/indicators/sections/final-indicator-picker.section.tsx +60 -0
- package/src/components/indicators/sections/final-indicator-preview.section.tsx +20 -0
- package/src/components/indicators/sections/final-indicator-results-preview.section.tsx +65 -0
- package/src/components/indicators/sections/indicator-basics.section.tsx +40 -0
- package/src/components/indicators/sections/indicator-conditions.section.tsx +279 -0
- package/src/components/indicators/sections/indicator-sql-preview.section.tsx +63 -0
- package/src/components/indicators/sections/indicator-theme.section.tsx +48 -0
- package/src/components/indicators/sql-preview.component.tsx +19 -0
- package/src/components/indicators/tag-input.component.tsx +177 -0
- package/src/components/indicators/types/base-indicator-form.types.ts +17 -0
- package/src/components/indicators/types/base-indicator-theme.constants.ts +8 -0
- package/src/components/indicators/types/composite-indicator.types.ts +27 -0
- package/src/components/indicators/types/condition-ui.types.ts +11 -0
- package/src/components/indicators/types/custom-indicator.types.ts +161 -0
- package/src/components/indicators/types/data-theme-config.types.ts +88 -0
- package/src/components/indicators/types/indicator-types.ts +57 -0
- package/src/components/indicators/types/sql-builders.ts +43 -0
- package/src/components/indicators/utils/composite-indicator-sql.utils.test.ts +143 -0
- package/src/components/indicators/utils/composite-indicator-sql.utils.ts +426 -0
- package/src/components/indicators/utils/concept-preload.utils.ts +21 -0
- package/src/components/indicators/utils/custom-indicator-interpreter.test.ts +493 -0
- package/src/components/indicators/utils/custom-indicator-interpreter.ts +702 -0
- package/src/components/indicators/utils/final-indicator-sql.utils.ts +190 -0
- package/src/components/indicators/utils/indicator-conditions-hydration.utils.ts +254 -0
- package/src/components/indicators/utils/indicator-sql-resolution.utils.ts +204 -0
- package/src/components/indicators/utils/indicator-sql.utils.ts +721 -0
- package/src/components/indicators/utils/population-sql.compiler.test.ts +985 -0
- package/src/components/indicators/utils/population-sql.compiler.ts +858 -0
- package/src/components/landing/landing-action-item.component.tsx +50 -0
- package/src/components/landing/landing-action-list.component.tsx +12 -0
- package/src/components/landing/landing-activity-list.component.tsx +47 -0
- package/src/components/landing/landing-footer-grid.component.tsx +45 -0
- package/src/components/landing/landing-hero.component.tsx +18 -0
- package/src/components/landing/landing-section-card.component.tsx +33 -0
- package/src/components/landing/report-builder-landing-page.component.tsx +186 -0
- package/src/components/landing/report-builder-landing-page.scss +131 -0
- package/src/components/legacy-reports/legacy-report-detail-page.component.tsx +300 -0
- package/src/components/legacy-reports/legacy-report-editor-page.component.tsx +324 -0
- package/src/components/legacy-reports/legacy-report-upload-modal.component.tsx +307 -0
- package/src/components/legacy-reports/legacy-reports-page.component.tsx +206 -0
- package/src/components/legacy-reports/legacy-reports-table.component.tsx +154 -0
- package/src/components/legacy-reports/tabs/legacy-report-datasets-tab.component.tsx +344 -0
- package/src/components/legacy-reports/tabs/legacy-report-dimensions-tab.component.tsx +590 -0
- package/src/components/legacy-reports/tabs/legacy-report-indicators-tab.component.tsx +490 -0
- package/src/components/legacy-reports/tabs/legacy-report-json-preview-tab.component.tsx +272 -0
- package/src/components/legacy-reports/tabs/legacy-report-overview-tab.component.tsx +218 -0
- package/src/components/legacy-reports/tabs/legacy-report-parameters-tab.component.tsx +279 -0
- package/src/components/linelist/definition/indicator-to-sql.utils.ts +281 -0
- package/src/components/linelist/definition/linelist-definition-editor.component.tsx +1676 -0
- package/src/components/linelist/definition/linelist-definition-editor.scss +461 -0
- package/src/components/linelist/definition/linelist-definition.types.ts +108 -0
- package/src/components/linelist/definition/panels/column-properties-panel.component.tsx +328 -0
- package/src/components/linelist/definition/panels/column-selector-panel.component.tsx +1082 -0
- package/src/components/linelist/definition/panels/column-selector-panel.scss +100 -0
- package/src/components/linelist/definition/panels/custom-sql-column-modal.component.tsx +248 -0
- package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.component.tsx +300 -0
- package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.scss +21 -0
- package/src/components/linelist/definition/panels/multi-datasource-selector.component.tsx +332 -0
- package/src/components/linelist/definition/panels/multi-datasource-selector.scss +161 -0
- package/src/components/linelist/definition/panels/observation-column-modal.component.tsx +450 -0
- package/src/components/linelist/definition/panels/observation-column-modal.scss +29 -0
- package/src/components/linelist/definition/panels/parameter-panel.component.tsx +659 -0
- package/src/components/linelist/definition/panels/parameter-panel.scss +64 -0
- package/src/components/linelist/definition/panels/population-source-selector.component.tsx +407 -0
- package/src/components/linelist/definition/panels/population-source-selector.scss +187 -0
- package/src/components/linelist/design/panels/column-category-selector.component.tsx +433 -0
- package/src/components/linelist/design/panels/column-category-selector.scss +324 -0
- package/src/components/linelist/design/panels/column-debug-panel.component.tsx +197 -0
- package/src/components/linelist/design/panels/column-debug-panel.scss +141 -0
- package/src/components/linelist/design/panels/data-catalogue.component.tsx +1062 -0
- package/src/components/linelist/design/panels/data-catalogue.scss +263 -0
- package/src/components/linelist/design/panels/display-settings-panel.component.tsx +405 -0
- package/src/components/linelist/design/panels/display-settings-panel.scss +78 -0
- package/src/components/linelist/design/panels/filtermap-editor.component.tsx +434 -0
- package/src/components/linelist/design/panels/filtermap-editor.scss +176 -0
- package/src/components/linelist/design/panels/json-preview.component.tsx +114 -0
- package/src/components/linelist/design/panels/json-preview.scss +85 -0
- package/src/components/linelist/design/panels/query-config-panel.component.tsx +1137 -0
- package/src/components/linelist/design/panels/query-config-panel.scss +341 -0
- package/src/components/linelist/design/panels/sort-config-panel.component.tsx +330 -0
- package/src/components/linelist/design/panels/sort-config-panel.scss +77 -0
- package/src/components/linelist/design/panels/sql-builder-panel.component.tsx +475 -0
- package/src/components/linelist/design/panels/sql-builder-panel.scss +226 -0
- package/src/components/linelist/design/panels/transformation-panel.component.tsx +687 -0
- package/src/components/linelist/design/panels/transformation-panel.scss +81 -0
- package/src/components/linelist/design/panels/visual-filter-panel.component.tsx +595 -0
- package/src/components/linelist/design/panels/visual-filter-panel.scss +76 -0
- package/src/components/linelist/linelist-dashboard.page.component.tsx +756 -0
- package/src/components/linelist/linelist-dashboard.page.scss +240 -0
- package/src/components/linelist/linelist-editor.page.component.tsx +134 -0
- package/src/components/linelist/linelist-editor.page.scss +28 -0
- package/src/components/linelist/linelist-run-report.page.component.tsx +715 -0
- package/src/components/linelist/linelist-run-report.page.scss +136 -0
- package/src/components/linelist/modals/linelist-report-builder-modal.component.tsx +1454 -0
- package/src/components/linelist/modals/linelist-report-builder-modal.scss +164 -0
- package/src/components/linelist/modals/preview-parameter-modal.component.tsx +361 -0
- package/src/components/report/dashboard/reports-table.component.tsx +186 -0
- package/src/components/report/definition/panels/properties-panel.component.tsx +38 -0
- package/src/components/report/definition/panels/report-structure-panel.component.tsx +61 -0
- package/src/components/report/definition/panels/selection-panel.component.tsx +58 -0
- package/src/components/report/definition/report-definition-editor.component.tsx +99 -0
- package/src/components/report/definition/report-definition.types.ts +66 -0
- package/src/components/report/design/panels/IndicatorPropertiesPanel.scss +33 -0
- package/src/components/report/design/panels/IndicatorPropertiesPanel.tsx +389 -0
- package/src/components/report/design/panels/JsonPreviewPanel.scss +32 -0
- package/src/components/report/design/panels/JsonPreviewPanel.tsx +104 -0
- package/src/components/report/design/panels/mapping-preview-panel.component.tsx +82 -0
- package/src/components/report/design/panels/mapping-preview-panel.scss +55 -0
- package/src/components/report/design/panels/template-structure-panel.component.tsx +296 -0
- package/src/components/report/design/panels/template-structure-panel.scss +81 -0
- package/src/components/report/design/report-design-editor.component.tsx +1170 -0
- package/src/components/report/design/report-design.types.ts +48 -0
- package/src/components/report/design/report-design.utils.ts +86 -0
- package/src/components/report/panels/report-detail.component.tsx +71 -0
- package/src/components/report/panels/report-panel-shell.component.tsx +60 -0
- package/src/components/report/report-dashboard.page.component.tsx +554 -0
- package/src/components/report/report-editor.page.component.tsx +590 -0
- package/src/components/report/report.routes.tsx +32 -0
- package/src/components/report-sections/create-report-section-modal.component.tsx +388 -0
- package/src/components/report-sections/hooks/useAgeCategories.ts +25 -0
- package/src/components/report-sections/hooks/useSectionEditorState.ts +449 -0
- package/src/components/report-sections/panels/Dhis2MappingPanel.tsx +157 -0
- package/src/components/report-sections/panels/DisaggregationPanel.tsx +97 -0
- package/src/components/report-sections/panels/IndicatorPickerPanel.tsx +201 -0
- package/src/components/report-sections/panels/SectionBasicsPanel.tsx +27 -0
- package/src/components/report-sections/report-section-preview-modal.component.tsx +548 -0
- package/src/components/report-sections/report-sections-page.component.tsx +325 -0
- package/src/components/report-sections/section-disaggregation.utils.ts +637 -0
- package/src/components/report-sections/section-types.ts +55 -0
- package/src/components/report-sections/section-utils.ts +50 -0
- package/src/components/report-sections/sections-page.scss +79 -0
- package/src/components/run-reports/run-reports-page.component.tsx +8 -0
- package/src/components/security/access-denied-page.component.tsx +64 -0
- package/src/components/security/access-denied-page.scss +39 -0
- package/src/components/security/privilege-route.component.test.tsx +76 -0
- package/src/components/security/privilege-route.component.tsx +39 -0
- package/src/components/shared/compile-setup-modal.component.tsx +182 -0
- package/src/components/shared/concept-selector.component.tsx +245 -0
- package/src/components/shared/concept-selector.scss +121 -0
- package/src/components/shared/header/builder-header-actions.component.tsx +38 -0
- package/src/components/shared/header/header.component.tsx +85 -0
- package/src/components/shared/header/header.scss +151 -0
- package/src/components/shared/header/illustration.component.tsx +34 -0
- package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.scss +33 -0
- package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.tsx +389 -0
- package/src/components/shared/json-preview-panel/JsonPreviewPanel.scss +32 -0
- package/src/components/shared/json-preview-panel/JsonPreviewPanel.tsx +104 -0
- package/src/components/shared/mapping-preview-panel/mapping-preview-panel.component.tsx +82 -0
- package/src/components/shared/mapping-preview-panel/mapping-preview-panel.scss +55 -0
- package/src/components/shared/preview/query-results-preview.component.tsx +204 -0
- package/src/components/shared/template-structure-panel/template-structure-panel.component.tsx +296 -0
- package/src/components/shared/template-structure-panel/template-structure-panel.scss +81 -0
- package/src/config-schema.ts +43 -0
- package/src/constants/privileges.test.ts +54 -0
- package/src/constants/privileges.ts +118 -0
- package/src/features/dashboards/DashboardRendererPage.tsx +174 -0
- package/src/features/dashboards/DashboardsPage.tsx +164 -0
- package/src/features/dashboards/EtlDashboardPage.tsx +32 -0
- package/src/features/dashboards/components/DashboardEmptyState.tsx +41 -0
- package/src/features/dashboards/components/DashboardGrid.tsx +48 -0
- package/src/features/dashboards/components/DashboardHeader.tsx +58 -0
- package/src/features/dashboards/components/DashboardSection.tsx +46 -0
- package/src/features/dashboards/components/ReportLaunchCard.tsx +51 -0
- package/src/features/dashboards/components/WidgetSlotDispatcher.tsx +61 -0
- package/src/features/dashboards/components/WidgetUnavailableCard.tsx +36 -0
- package/src/features/dashboards/dashboards.scss +196 -0
- package/src/features/dashboards/hooks/useDashboard.ts +61 -0
- package/src/features/dashboards/hooks/useDashboardWidgets.ts +74 -0
- package/src/features/dashboards/hooks/useDashboards.ts +56 -0
- package/src/features/dashboards/hooks/useMonitorDataMap.ts +84 -0
- package/src/features/dashboards/index.ts +11 -0
- package/src/features/dashboards/utils/dashboard-config.util.test.ts +286 -0
- package/src/features/dashboards/utils/dashboard-config.util.ts +382 -0
- package/src/features/report-visualizer/ReportVisualizerPage.tsx +448 -0
- package/src/features/report-visualizer/components/Export/ExportMenu.tsx +141 -0
- package/src/features/report-visualizer/components/Export/index.ts +2 -0
- package/src/features/report-visualizer/components/ReportExplorer/QuickFilters.tsx +152 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportCategoryFilter.tsx +54 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerPanel.tsx +201 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerRail.tsx +202 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportList.tsx +287 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportSearch.tsx +151 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportTagFilter.tsx +100 -0
- package/src/features/report-visualizer/components/ReportExplorer/RunReportButton.tsx +42 -0
- package/src/features/report-visualizer/components/ReportExplorer/RuntimeParameterForm.tsx +259 -0
- package/src/features/report-visualizer/components/ReportExplorer/index.ts +8 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportLayer.tsx +33 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportSummary.tsx +88 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportViewTabs.tsx +72 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspaceHeader.tsx +92 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspacePanel.tsx +242 -0
- package/src/features/report-visualizer/components/ReportWorkspace/index.ts +5 -0
- package/src/features/report-visualizer/components/TableView/ColumnVisibilityMenu.tsx +206 -0
- package/src/features/report-visualizer/components/TableView/ReportTable.tsx +253 -0
- package/src/features/report-visualizer/components/TableView/index.ts +2 -0
- package/src/features/report-visualizer/hooks/index.ts +4 -0
- package/src/features/report-visualizer/hooks/useExplorerPreferences.ts +103 -0
- package/src/features/report-visualizer/hooks/useReportCapabilities.ts +112 -0
- package/src/features/report-visualizer/hooks/useReportParameters.ts +152 -0
- package/src/features/report-visualizer/hooks/useReports.ts +135 -0
- package/src/features/report-visualizer/index.ts +6 -0
- package/src/features/report-visualizer/report-visualizer.scss +593 -0
- package/src/features/report-visualizer/types/index.ts +1 -0
- package/src/features/report-visualizer/types/report-visualizer.types.ts +104 -0
- package/src/features/report-visualizer/utils/status-colors.ts +117 -0
- package/src/globals.d.ts +9 -0
- package/src/hooks/address-template.ts +86 -0
- package/src/hooks/concept.ts +112 -0
- package/src/hooks/indicator/index.ts +1 -0
- package/src/hooks/indicator/useIndicatorsByTheme.ts +41 -0
- package/src/hooks/openmrs-reference-data.ts +432 -0
- package/src/hooks/theme/index.ts +4 -0
- package/src/hooks/theme/useDataTheme.ts +61 -0
- package/src/hooks/theme/useDataThemes.ts +28 -0
- package/src/hooks/theme/useMambaTableMeta.ts +34 -0
- package/src/hooks/theme/useMambaTables.ts +95 -0
- package/src/hooks/use-report-builder-privileges.test.tsx +92 -0
- package/src/hooks/use-report-builder-privileges.ts +58 -0
- package/src/index.ts +29 -0
- package/src/resources/address-template/address-template.api.ts +180 -0
- package/src/resources/agegroup/agegroups.api.ts +58 -0
- package/src/resources/concept/concept.api.ts +116 -0
- package/src/resources/concepts/concept-mapper.ts +21 -0
- package/src/resources/concepts/concept-types.ts +31 -0
- package/src/resources/concepts/concepts.resource.ts +44 -0
- package/src/resources/concepts/useConceptSearch.ts +41 -0
- package/src/resources/dashboard/dashboard.api.ts +107 -0
- package/src/resources/etl-monitor/etl-monitor-test.api.ts +409 -0
- package/src/resources/etl-monitor/etl-monitor.api.ts +381 -0
- package/src/resources/etl-source/etl-source.api.ts +65 -0
- package/src/resources/etl-task/etl-task.api.ts +96 -0
- package/src/resources/etl-task/etl-task.types.ts +31 -0
- package/src/resources/indicator/indicators.api.ts +125 -0
- package/src/resources/legacy-reports/legacy-reports.api.ts +144 -0
- package/src/resources/linelist/linelist-reports.api.ts +616 -0
- package/src/resources/location/location.api.ts +135 -0
- package/src/resources/openmrs-api.ts +40 -0
- package/src/resources/patient-identifier-type/patient-identifier-type.api.ts +65 -0
- package/src/resources/person-attribute-type/person-attribute-type.api.ts +71 -0
- package/src/resources/preview/sql-preview.api.ts +54 -0
- package/src/resources/report/reports.api.ts +140 -0
- package/src/resources/report-category/report-category.api.ts +41 -0
- package/src/resources/report-import-export/import-export-api.ts +164 -0
- package/src/resources/report-library/report-library.api.ts +63 -0
- package/src/resources/report-section/report-sections.api.ts +73 -0
- package/src/resources/report-section/section-preview.api.ts +25 -0
- package/src/resources/theme/data-theme.api.ts +88 -0
- package/src/resources/theme/etl-schema.api.ts +101 -0
- package/src/resources/theme/etl-table-meta.api.ts +45 -0
- package/src/root.component.tsx +20 -0
- package/src/routes/report-builder.routes.tsx +160 -0
- package/src/routes/report-builder.scss +56 -0
- package/src/routes.json +31 -0
- package/src/sample-template.ts +121 -0
- package/src/template-utils.ts +110 -0
- package/src/types/carbon-react-inert.d.ts +13 -0
- package/src/types/condition-operators.ts +63 -0
- package/src/types/dashboard/dashboard.types.ts +137 -0
- package/src/types/dashboard/index.ts +4 -0
- package/src/types/etl/etl-types.ts +238 -0
- package/src/types/etl-monitor/etl-monitor-builder.types.ts +555 -0
- package/src/types/etl-monitor/etl-monitor-v2.types.ts +397 -0
- package/src/types/etl-monitor/etl-monitor.types.ts +188 -0
- package/src/types/etl-monitor/index.ts +6 -0
- package/src/types/linelist/compile-config.ts +455 -0
- package/src/types/linelist-types.ts +2059 -0
- package/src/types/theme/data-theme.types.ts +88 -0
- package/src/utils/api-error.utils.test.ts +147 -0
- package/src/utils/api-error.utils.ts +131 -0
- package/src/utils/config-enhancer.ts +62 -0
- package/src/utils/etl-browser/etl-browser.util.test.ts +151 -0
- package/src/utils/etl-browser/export-csv.util.ts +43 -0
- package/src/utils/etl-browser/group-tables.util.ts +84 -0
- package/src/utils/etl-browser/index.ts +4 -0
- package/src/utils/etl-browser/summarize-columns.util.ts +27 -0
- package/src/utils/etl-browser/table-sql.util.ts +20 -0
- package/src/utils/etl-monitor/config-v2-adapter.util.ts +460 -0
- package/src/utils/etl-monitor/endpoint-config-normalizer.util.ts +383 -0
- package/src/utils/etl-monitor/index.ts +10 -0
- package/src/utils/etl-monitor/jsonpath-resolver.util.ts +118 -0
- package/src/utils/etl-monitor/normalization-examples.ts +0 -0
- package/src/utils/etl-monitor/schema-inspector.util.ts +76 -0
- package/src/utils/etl-monitor/semantic-type-detector.util.ts +76 -0
- package/src/utils/etl-monitor/status-mapper.util.ts +105 -0
- package/src/utils/etl-monitor/value-formatters.util.ts +167 -0
- package/src/utils/etl-task/etl-task-card.util.test.ts +240 -0
- package/src/utils/etl-task/etl-task-card.util.ts +276 -0
- package/src/utils/etl-task/index.ts +1 -0
- package/src/utils/html-entities.utils.ts +34 -0
- package/src/utils/parameter-resolution.ts +218 -0
- package/src/views/etl-monitor-builder/add-monitor-modal.component.scss +0 -0
- package/src/views/etl-monitor-builder/add-monitor-modal.component.tsx +210 -0
- package/src/views/etl-monitor-builder/etl-monitor-builder.component.scss +157 -0
- package/src/views/etl-monitor-builder/etl-monitor-builder.component.tsx +252 -0
- package/src/views/etl-monitor-builder/etl-monitor-builder.store.ts +126 -0
- package/tools/i18next-parser.config.js +0 -0
- package/tools/setup-tests.ts +1 -0
- package/tools/update-openmrs-deps.mjs +42 -0
- package/translations/am.json +65 -0
- package/translations/ar.json +65 -0
- package/translations/ar_SY.json +65 -0
- package/translations/bn.json +65 -0
- package/translations/cs.json +65 -0
- package/translations/de.json +65 -0
- package/translations/en.json +65 -0
- package/translations/en_US.json +65 -0
- package/translations/es.json +65 -0
- package/translations/es_MX.json +65 -0
- package/translations/fr.json +65 -0
- package/translations/he.json +65 -0
- package/translations/hi.json +65 -0
- package/translations/hi_IN.json +65 -0
- package/translations/id.json +65 -0
- package/translations/it.json +65 -0
- package/translations/ka.json +65 -0
- package/translations/km.json +65 -0
- package/translations/ku.json +65 -0
- package/translations/ky.json +65 -0
- package/translations/lg.json +65 -0
- package/translations/ne.json +65 -0
- package/translations/pl.json +65 -0
- package/translations/pt.json +65 -0
- package/translations/pt_BR.json +65 -0
- package/translations/qu.json +65 -0
- package/translations/ro_RO.json +65 -0
- package/translations/ru_RU.json +65 -0
- package/translations/si.json +65 -0
- package/translations/sq.json +65 -0
- package/translations/sw.json +65 -0
- package/translations/sw_KE.json +65 -0
- package/translations/tr.json +65 -0
- package/translations/tr_TR.json +65 -0
- package/translations/uk.json +65 -0
- package/translations/uz.json +65 -0
- package/translations/uz@Latn.json +65 -0
- package/translations/uz_UZ.json +65 -0
- package/translations/vi.json +65 -0
- package/translations/zh.json +65 -0
- package/translations/zh_CN.json +65 -0
- package/translations/zh_TW.json +65 -0
- package/tsconfig.json +24 -0
- 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
|
+
};
|