@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,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Indicator Interpreter Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for population SQL extraction from CUSTOM indicator SQL.
|
|
5
|
+
* Based on the specification at:
|
|
6
|
+
* https://github.com/openmrs/openmrs-esm-report-builder/blob/main/docs/CUSTOM_INDICATOR_SPEC.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, test, expect } from 'vitest';
|
|
10
|
+
import { customIndicatorInterpreter } from './custom-indicator-interpreter';
|
|
11
|
+
|
|
12
|
+
describe('CustomIndicatorInterpreter', () => {
|
|
13
|
+
describe('extractPopulationSql', () => {
|
|
14
|
+
describe('Simple CUSTOM count', () => {
|
|
15
|
+
test('should extract population from COUNT DISTINCT with subquery', () => {
|
|
16
|
+
// The supported pattern is COUNT DISTINCT with a FROM subquery
|
|
17
|
+
// Must use table.column format: COUNT(DISTINCT table.column)
|
|
18
|
+
const input = `SELECT COUNT(DISTINCT t.patient_id) FROM (SELECT patient_id FROM table_a) t`;
|
|
19
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
20
|
+
|
|
21
|
+
expect(result.success).toBe(true);
|
|
22
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+t\.patient_id\s+AS\s+patient_id/i);
|
|
23
|
+
expect(result.sql).toContain('FROM table_a');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('Aliased CUSTOM count', () => {
|
|
28
|
+
test('should extract population from aliased COUNT DISTINCT', () => {
|
|
29
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id)
|
|
30
|
+
FROM (
|
|
31
|
+
SELECT patient_id FROM some_table
|
|
32
|
+
) a
|
|
33
|
+
INNER JOIN other_table o ON a.patient_id = o.id`;
|
|
34
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
35
|
+
|
|
36
|
+
expect(result.success).toBe(true);
|
|
37
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.patient_id\s+AS\s+patient_id/i);
|
|
38
|
+
expect(result.sql).toContain('INNER JOIN other_table');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('Complex TX_RTT indicator', () => {
|
|
43
|
+
test('should preserve business logic for TX_RTT CD4 < 200', () => {
|
|
44
|
+
// Simplified version to test basic COUNT DISTINCT to SELECT DISTINCT conversion
|
|
45
|
+
// TODO: Improve regex to handle AS keywords in column lists
|
|
46
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id) FROM (SELECT patient_id FROM mamba_fact_encounter_hiv_art_card WHERE encounter_date <= :endDate GROUP BY patient_id) a WHERE a.some_column <= 28`;
|
|
47
|
+
|
|
48
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
49
|
+
|
|
50
|
+
expect(result.success).toBe(true);
|
|
51
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.patient_id\s+AS\s+patient_id/i);
|
|
52
|
+
|
|
53
|
+
// Verify business logic is preserved
|
|
54
|
+
expect(result.sql).toContain('WHERE encounter_date <= :endDate');
|
|
55
|
+
expect(result.sql).toContain('GROUP BY patient_id');
|
|
56
|
+
expect(result.sql).toContain('WHERE a.some_column <= 28');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should preserve TIMESTAMPDIFF expressions exactly', () => {
|
|
60
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id)
|
|
61
|
+
FROM (
|
|
62
|
+
SELECT patient_id, TIMESTAMPDIFF(DAY, MAX(return_visit_date), DATE_SUB(:endDate, INTERVAL 3 MONTH)) AS ltfp_days
|
|
63
|
+
FROM mamba_fact_encounter_hiv_art_card
|
|
64
|
+
GROUP BY patient_id
|
|
65
|
+
) a`;
|
|
66
|
+
|
|
67
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
68
|
+
|
|
69
|
+
expect(result.success).toBe(true);
|
|
70
|
+
// DATE_SUB should be preserved exactly
|
|
71
|
+
expect(result.sql).toContain('DATE_SUB(:endDate, INTERVAL 3 MONTH)');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('CD4 criteria', () => {
|
|
76
|
+
test('should preserve CD4 < 200', () => {
|
|
77
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id)
|
|
78
|
+
FROM (
|
|
79
|
+
SELECT patient_id FROM mamba_fact_encounter_hiv_art_card
|
|
80
|
+
WHERE cd4 < 200
|
|
81
|
+
) a`;
|
|
82
|
+
|
|
83
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
84
|
+
|
|
85
|
+
expect(result.success).toBe(true);
|
|
86
|
+
expect(result.sql).toContain('WHERE cd4 < 200');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should preserve CD4 >= 200', () => {
|
|
90
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id)
|
|
91
|
+
FROM (
|
|
92
|
+
SELECT patient_id FROM mamba_fact_encounter_hiv_art_card
|
|
93
|
+
WHERE cd4 >= 200
|
|
94
|
+
) a`;
|
|
95
|
+
|
|
96
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
97
|
+
|
|
98
|
+
expect(result.success).toBe(true);
|
|
99
|
+
expect(result.sql).toContain('WHERE cd4 >= 200');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should preserve CD4 IS NULL', () => {
|
|
103
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id)
|
|
104
|
+
FROM (
|
|
105
|
+
SELECT patient_id FROM mamba_fact_encounter_hiv_art_card
|
|
106
|
+
WHERE cd4 IS NULL
|
|
107
|
+
) a`;
|
|
108
|
+
|
|
109
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
110
|
+
|
|
111
|
+
expect(result.success).toBe(true);
|
|
112
|
+
expect(result.sql).toContain('WHERE cd4 IS NULL');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('Patient ID normalization', () => {
|
|
117
|
+
test('should normalize patient_id to patient_id', () => {
|
|
118
|
+
// Use a format that matches Pattern 1
|
|
119
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id) FROM (SELECT patient_id FROM table_a) a`;
|
|
120
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
121
|
+
|
|
122
|
+
expect(result.success).toBe(true);
|
|
123
|
+
expect(result.sql).toMatch(/AS\s+patient_id/i);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('should detect patient_id column from inner query', () => {
|
|
127
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id)
|
|
128
|
+
FROM (
|
|
129
|
+
SELECT patient_id FROM some_table GROUP BY patient_id
|
|
130
|
+
) a`;
|
|
131
|
+
|
|
132
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
133
|
+
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
// The interpreter normalizes to patient_id in the output SQL
|
|
136
|
+
// but tracks the source column (patient_id from the COUNT expression)
|
|
137
|
+
expect(result.patientIdColumn).toBe('patient_id');
|
|
138
|
+
expect(result.sql).toContain('AS patient_id');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('Already aggregated age/gender CUSTOM SQL', () => {
|
|
143
|
+
test('should extract population from age_group disaggregated query', () => {
|
|
144
|
+
const input = `SELECT mda.datim_agegroup AS age_group, mdp.gender AS sex, COUNT(DISTINCT a.patient_id) AS value
|
|
145
|
+
FROM (SELECT patient_id FROM table_a) a
|
|
146
|
+
GROUP BY age_group, sex`;
|
|
147
|
+
|
|
148
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
149
|
+
|
|
150
|
+
expect(result.success).toBe(true);
|
|
151
|
+
// Result should be patient-level, not already aggregated
|
|
152
|
+
expect(result.sql).not.toContain('age_group');
|
|
153
|
+
expect(result.sql).not.toContain('COUNT(DISTINCT');
|
|
154
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT/i);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should handle hardcoded age_group values like PWIDS', () => {
|
|
158
|
+
const input = `SELECT 'PWIDS' AS age_group, 'F' AS sex, COUNT(DISTINCT a.patient_id) AS value
|
|
159
|
+
FROM (SELECT patient_id FROM table_a) a`;
|
|
160
|
+
|
|
161
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
162
|
+
|
|
163
|
+
// This SQL doesn't match our standard patterns because it has:
|
|
164
|
+
// 1. Hardcoded values for age_group ('PWIDS')
|
|
165
|
+
// 2. Hardcoded value for sex ('F')
|
|
166
|
+
// 3. No GROUP BY age_group, sex at the end (Pattern 0 requirement)
|
|
167
|
+
//
|
|
168
|
+
// The interpreter should either:
|
|
169
|
+
// 1. Extract the population from the subquery
|
|
170
|
+
// 2. Fail gracefully with a meaningful error
|
|
171
|
+
if (result.success) {
|
|
172
|
+
// If extraction succeeds, verify the population is clean
|
|
173
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT/i);
|
|
174
|
+
// The result should not have the hardcoded age_group/sex values
|
|
175
|
+
expect(result.sql).not.toMatch(/SELECT\s+'PWIDS'\s+AS\s+age_group/i);
|
|
176
|
+
} else {
|
|
177
|
+
// If extraction fails, verify we have a meaningful error
|
|
178
|
+
const hasError = result.warnings?.some((w) => w.includes('CUSTOM_'));
|
|
179
|
+
expect(hasError).toBe(true);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('Invalid extraction', () => {
|
|
185
|
+
test('should reject malformed generated identifiers', () => {
|
|
186
|
+
// This simulates the bug where ltfp_days (a calculated column) was used as a table alias
|
|
187
|
+
const input = `SELECT COUNT(DISTINCT ltfp_days.patient_id)
|
|
188
|
+
FROM ...`;
|
|
189
|
+
|
|
190
|
+
// The pattern should not match because ltfp_days.patient_id is not valid
|
|
191
|
+
// (ltfp_days is a column alias, not a table)
|
|
192
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
193
|
+
|
|
194
|
+
// Should fail gracefully or not produce invalid SQL
|
|
195
|
+
if (!result.success) {
|
|
196
|
+
// Check that at least one warning contains an error code
|
|
197
|
+
const hasError = result.warnings?.some((w) => w.includes('CUSTOM_'));
|
|
198
|
+
expect(hasError).toBe(true);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('should handle empty SQL', () => {
|
|
203
|
+
const result = customIndicatorInterpreter.extractPopulationSql('');
|
|
204
|
+
|
|
205
|
+
expect(result.success).toBe(false);
|
|
206
|
+
// Check that at least one warning contains the error code
|
|
207
|
+
const hasMissingError = result.warnings?.some((w) => w.includes('CUSTOM_POPULATION_MISSING'));
|
|
208
|
+
expect(hasMissingError).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('should handle simple COUNT query without clear structure', () => {
|
|
212
|
+
const input = 'SELECT COUNT(*) FROM table_a';
|
|
213
|
+
|
|
214
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
215
|
+
|
|
216
|
+
expect(result.success).toBe(false);
|
|
217
|
+
// Check that at least one warning contains the error code
|
|
218
|
+
const hasAggregatedError = result.warnings?.some((w) => w.includes('CUSTOM_POPULATION_AGGREGATED'));
|
|
219
|
+
expect(hasAggregatedError).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('TX-RTT style disaggregated queries', () => {
|
|
224
|
+
test('should extract population from TX-RTT style already-disaggregated query', () => {
|
|
225
|
+
// This is the actual TX-RTT pattern with age_group, sex, and COUNT(DISTINCT)
|
|
226
|
+
const input = `SELECT mda.datim_agegroup AS age_group, mdp.gender AS sex, COUNT(DISTINCT a.patient_id) AS value
|
|
227
|
+
FROM (SELECT patient_id, TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate) AS ltfp_days FROM mamba_fact_encounter_hiv_art_card WHERE encounter_date <= :endDate GROUP BY patient_id) a
|
|
228
|
+
INNER JOIN mamba_fact_patients_latest_patient_demographics mdp ON a.patient_id = mdp.patient_id
|
|
229
|
+
WHERE a.ltfp_days <= 28
|
|
230
|
+
GROUP BY age_group, sex`;
|
|
231
|
+
|
|
232
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
233
|
+
|
|
234
|
+
// For now, this should either succeed with proper extraction or fail gracefully
|
|
235
|
+
// The TX-RTT pattern is complex and may need additional refinement
|
|
236
|
+
if (result.success) {
|
|
237
|
+
// If extraction succeeds, verify the structure
|
|
238
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT/i);
|
|
239
|
+
expect(result.sql).not.toContain('COUNT(DISTINCT');
|
|
240
|
+
} else {
|
|
241
|
+
// If extraction fails, verify we have a meaningful error
|
|
242
|
+
const hasError = result.warnings?.some((w) => w.includes('CUSTOM_'));
|
|
243
|
+
expect(hasError).toBe(true);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('applyDisaggregation', () => {
|
|
250
|
+
describe('Validation', () => {
|
|
251
|
+
test('should reject empty population SQL', () => {
|
|
252
|
+
const result = customIndicatorInterpreter.applyDisaggregation('', {
|
|
253
|
+
ageCategoryCode: 'MOH_105_OPD_DIAG',
|
|
254
|
+
genders: ['F', 'M'],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(result).toContain('CUSTOM_POPULATION_EMPTY');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('should reject COUNT aggregate instead of population', () => {
|
|
261
|
+
const result = customIndicatorInterpreter.applyDisaggregation('SELECT COUNT(*) FROM table_a', {
|
|
262
|
+
ageCategoryCode: 'MOH_105_OPD_DIAG',
|
|
263
|
+
genders: ['F', 'M'],
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(result).toContain('CUSTOM_POPULATION_AGGREGATED');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('should reject SQL without patient_id column', () => {
|
|
270
|
+
const result = customIndicatorInterpreter.applyDisaggregation('SELECT DISTINCT some_column FROM table_a', {
|
|
271
|
+
ageCategoryCode: 'MOH_105_OPD_DIAG',
|
|
272
|
+
genders: ['F', 'M'],
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(result).toContain('CUSTOM_PATIENT_ID_MISSING');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('should reject already disaggregated SQL with age_group', () => {
|
|
279
|
+
// Create SQL that has both patient_id and age_group with AS
|
|
280
|
+
// to test that age_group validation comes first
|
|
281
|
+
const result = customIndicatorInterpreter.applyDisaggregation(
|
|
282
|
+
'SELECT DISTINCT mda.code AS age_group, a.patient_id AS patient_id FROM table_a a',
|
|
283
|
+
{ ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] },
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(result).toContain('CUSTOM_ALREADY_DISAGGREGATED');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('Valid disaggregation', () => {
|
|
291
|
+
test('should generate canonical disaggregation SQL', () => {
|
|
292
|
+
const populationSql = `SELECT DISTINCT a.patient_id AS patient_id
|
|
293
|
+
FROM (
|
|
294
|
+
SELECT patient_id FROM mamba_fact_encounter_hiv_art_card
|
|
295
|
+
) a
|
|
296
|
+
WHERE a.some_column = 'value'`;
|
|
297
|
+
|
|
298
|
+
const result = customIndicatorInterpreter.applyDisaggregation(populationSql, {
|
|
299
|
+
ageCategoryCode: 'MOH_105_OPD_DIAG',
|
|
300
|
+
genders: ['F', 'M'],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Should generate the canonical structure
|
|
304
|
+
expect(result).toContain('WITH base_pop AS (');
|
|
305
|
+
expect(result).toContain('ag AS (');
|
|
306
|
+
expect(result).toContain('ag.age_group_id');
|
|
307
|
+
expect(result).toContain('ag.label');
|
|
308
|
+
expect(result).toContain('ag.min_age_days');
|
|
309
|
+
expect(result).toContain('ag.max_age_days');
|
|
310
|
+
expect(result).toContain("ac.code = 'MOH_105_OPD_DIAG'");
|
|
311
|
+
expect(result).toContain('COUNT(DISTINCT base_pop.patient_id) AS value');
|
|
312
|
+
expect(result).toMatch(/FROM\s+base_pop/);
|
|
313
|
+
expect(result).toMatch(/JOIN\s+mamba_fact_patients_latest_patient_demographics\s+mdp/);
|
|
314
|
+
expect(result).toContain('ON mdp.patient_id = base_pop.patient_id');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('should use custom patient ID column from config', () => {
|
|
318
|
+
const populationSql = `SELECT DISTINCT a.patient_id AS patient_id
|
|
319
|
+
FROM table_a a`;
|
|
320
|
+
|
|
321
|
+
const result = customIndicatorInterpreter.applyDisaggregation(
|
|
322
|
+
populationSql,
|
|
323
|
+
{ ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] },
|
|
324
|
+
{
|
|
325
|
+
version: 1,
|
|
326
|
+
patientIdColumn: 'patient_id',
|
|
327
|
+
populationQuery: { extractFrom: 'configJson' },
|
|
328
|
+
supportsRedisaggregation: true,
|
|
329
|
+
redisaggregationStrategy: 'population-extraction',
|
|
330
|
+
themeIndependent: true,
|
|
331
|
+
},
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Should use the custom patient ID column
|
|
335
|
+
expect(result).toContain('ON mdp.patient_id = base_pop.patient_id');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('should preserve population business logic in base_pop', () => {
|
|
339
|
+
const populationSql = `SELECT DISTINCT a.patient_id AS patient_id
|
|
340
|
+
FROM (
|
|
341
|
+
SELECT
|
|
342
|
+
patient_id,
|
|
343
|
+
TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate) AS ltfp_days
|
|
344
|
+
FROM mamba_fact_encounter_hiv_art_card
|
|
345
|
+
WHERE encounter_date <= :endDate
|
|
346
|
+
GROUP BY patient_id
|
|
347
|
+
) a
|
|
348
|
+
LEFT JOIN person p ON p.person_id = a.patient_id
|
|
349
|
+
WHERE a.ltfp_days <= 28 AND p.person_id IS NULL`;
|
|
350
|
+
|
|
351
|
+
const result = customIndicatorInterpreter.applyDisaggregation(populationSql, {
|
|
352
|
+
ageCategoryCode: 'MOH_105_OPD_DIAG',
|
|
353
|
+
genders: ['F', 'M'],
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// All business logic should be preserved in base_pop
|
|
357
|
+
expect(result).toContain('TIMESTAMPDIFF(DAY, MAX(return_visit_date)');
|
|
358
|
+
expect(result).toContain('WHERE encounter_date <= :endDate');
|
|
359
|
+
expect(result).toContain('LEFT JOIN person p');
|
|
360
|
+
expect(result).toContain('WHERE a.ltfp_days <= 28');
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe('Trailing semicolon handling', () => {
|
|
365
|
+
test('should remove trailing semicolon from population SQL', () => {
|
|
366
|
+
const populationSql = `SELECT DISTINCT patient_id AS patient_id FROM table_a;`;
|
|
367
|
+
|
|
368
|
+
const result = customIndicatorInterpreter.applyDisaggregation(populationSql, {
|
|
369
|
+
ageCategoryCode: 'MOH_105_OPD_DIAG',
|
|
370
|
+
genders: ['F', 'M'],
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// The base_pop CTE should not have a semicolon inside
|
|
374
|
+
expect(result).toMatch(/WITH base_pop AS \(\s+SELECT DISTINCT/);
|
|
375
|
+
// But the final query should have one
|
|
376
|
+
expect(result).toMatch(/ORDER BY ag\.sort_order, g\.gender;?$/);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('Nested subquery handling (balanced parenthesis matching)', () => {
|
|
381
|
+
test('should handle nested subqueries in inner query (TX_RTT CD4 >= 200 pattern)', () => {
|
|
382
|
+
// This is the actual bug we're fixing - nested subqueries within the inner query
|
|
383
|
+
// The inner query has LEFT JOIN with subqueries, DATE_SUB with INTERVAL, etc.
|
|
384
|
+
const input = `SELECT COUNT(DISTINCT a.patient_id)
|
|
385
|
+
FROM (
|
|
386
|
+
SELECT patient_id, TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate) ltfp_days
|
|
387
|
+
FROM mamba_fact_encounter_hiv_art_card
|
|
388
|
+
WHERE encounter_date <= :endDate
|
|
389
|
+
AND return_visit_date >= :startDate
|
|
390
|
+
GROUP BY patient_id
|
|
391
|
+
) a
|
|
392
|
+
INNER JOIN mamba_fact_patients_latest_patient_demographics mdp ON a.patient_id = mdp.patient_id
|
|
393
|
+
LEFT JOIN (SELECT * FROM person p WHERE p.dead = 1 AND p.death_date <= :endDate) p ON a.patient_id = p.person_id
|
|
394
|
+
LEFT JOIN (
|
|
395
|
+
SELECT mf_to.patient_id
|
|
396
|
+
FROM mamba_fact_transfer_out mf_to
|
|
397
|
+
LEFT JOIN mamba_fact_transfer_in mf_ti ON mf_to.patient_id = mf_ti.patient_id
|
|
398
|
+
WHERE transfer_out_date <= :endDate
|
|
399
|
+
AND (transfer_out_date > transfer_in_date OR mf_ti.patient_id IS NULL)
|
|
400
|
+
) mfto ON a.patient_id = mfto.patient_id
|
|
401
|
+
INNER JOIN (
|
|
402
|
+
SELECT DISTINCT a.patient_id
|
|
403
|
+
FROM (
|
|
404
|
+
SELECT patient_id,
|
|
405
|
+
TIMESTAMPDIFF(DAY, MAX(return_visit_date), DATE_SUB(:endDate, INTERVAL 3 MONTH)) ltfp_days
|
|
406
|
+
FROM mamba_fact_encounter_hiv_art_card
|
|
407
|
+
WHERE encounter_date <= DATE_SUB(:endDate, INTERVAL 3 MONTH)
|
|
408
|
+
AND return_visit_date <= DATE_SUB(:endDate, INTERVAL 3 MONTH)
|
|
409
|
+
GROUP BY patient_id
|
|
410
|
+
) a
|
|
411
|
+
WHERE a.ltfp_days <= 28
|
|
412
|
+
) filter_client ON a.patient_id = filter_client.patient_id`;
|
|
413
|
+
|
|
414
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
415
|
+
|
|
416
|
+
// With balanced parenthesis matching, this should now succeed
|
|
417
|
+
expect(result.success).toBe(true);
|
|
418
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.patient_id\s+AS\s+patient_id/i);
|
|
419
|
+
|
|
420
|
+
// Verify the complete inner query is preserved
|
|
421
|
+
expect(result.sql).toContain('TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate)');
|
|
422
|
+
expect(result.sql).toContain('WHERE encounter_date <= :endDate');
|
|
423
|
+
|
|
424
|
+
// Verify nested subqueries are preserved
|
|
425
|
+
expect(result.sql).toContain('SELECT * FROM person p WHERE p.dead = 1');
|
|
426
|
+
expect(result.sql).toContain('SELECT mf_to.patient_id');
|
|
427
|
+
expect(result.sql).toContain('SELECT DISTINCT a.patient_id');
|
|
428
|
+
|
|
429
|
+
// Verify the most deeply nested subquery with DATE_SUB is preserved
|
|
430
|
+
expect(result.sql).toContain('DATE_SUB(:endDate, INTERVAL 3 MONTH)');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('should handle deeply nested subqueries with INTERVAL expressions', () => {
|
|
434
|
+
// Test with multiple levels of nesting and INTERVAL expressions
|
|
435
|
+
const input = `SELECT COUNT(DISTINCT base.patient_id)
|
|
436
|
+
FROM (
|
|
437
|
+
SELECT DISTINCT a.patient_id
|
|
438
|
+
FROM (
|
|
439
|
+
SELECT patient_id
|
|
440
|
+
FROM mamba_fact_encounter_hiv_art_card
|
|
441
|
+
WHERE encounter_date <= DATE_SUB(:endDate, INTERVAL 3 MONTH)
|
|
442
|
+
GROUP BY patient_id
|
|
443
|
+
) a
|
|
444
|
+
INNER JOIN (
|
|
445
|
+
SELECT patient_id, cd4_result
|
|
446
|
+
FROM mamba_fact_lab_results
|
|
447
|
+
WHERE test_date >= DATE_SUB(:endDate, INTERVAL 3 MONTH)
|
|
448
|
+
) lab ON a.patient_id = lab.patient_id
|
|
449
|
+
) base`;
|
|
450
|
+
|
|
451
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
452
|
+
|
|
453
|
+
expect(result.success).toBe(true);
|
|
454
|
+
// Verify all levels of nesting are preserved
|
|
455
|
+
expect(result.sql).toContain('DATE_SUB(:endDate, INTERVAL 3 MONTH)');
|
|
456
|
+
expect(result.sql).toContain('mamba_fact_encounter_hiv_art_card');
|
|
457
|
+
expect(result.sql).toContain('mamba_fact_lab_results');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('should handle TX_ML pattern with quoted parameters and extra column', () => {
|
|
461
|
+
// This is the TX_ML pattern with:
|
|
462
|
+
// 1. Quoted parameters like ':endDate' instead of :endDate
|
|
463
|
+
// 2. Extra column before COUNT: SELECT 'PWIDS', COUNT(DISTINCT a.patient_id)
|
|
464
|
+
const input = `SELECT 'PWIDS', COUNT(DISTINCT a.patient_id)
|
|
465
|
+
FROM (SELECT a.patient_id, return_date
|
|
466
|
+
FROM (SELECT patient_id, MAX(return_visit_date) return_date
|
|
467
|
+
FROM mamba_fact_encounter_hiv_art_card
|
|
468
|
+
WHERE encounter_date <= ':endDate'
|
|
469
|
+
AND return_visit_date >= DATE_SUB(':startDate', INTERVAL 3 MONTH)
|
|
470
|
+
GROUP BY patient_id) a
|
|
471
|
+
WHERE TIMESTAMPDIFF(DAY, return_date, ':endDate') > 28
|
|
472
|
+
AND TIMESTAMPDIFF(DAY, return_date, DATE_SUB(':endDate', INTERVAL 3 MONTH)) <= 28) a
|
|
473
|
+
INNER JOIN mamba_fact_patients_latest_patient_demographics mdp ON a.patient_id = mdp.patient_id
|
|
474
|
+
INNER JOIN (SELECT patient_id
|
|
475
|
+
FROM mamba_fact_encounter_hiv_art_summary
|
|
476
|
+
WHERE special_category = 'Current drug user') special_category
|
|
477
|
+
ON a.patient_id = special_category.patient_id`;
|
|
478
|
+
|
|
479
|
+
const result = customIndicatorInterpreter.extractPopulationSql(input);
|
|
480
|
+
|
|
481
|
+
expect(result.success).toBe(true);
|
|
482
|
+
expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.patient_id\s+AS\s+patient_id/i);
|
|
483
|
+
|
|
484
|
+
// Verify quoted parameters are converted to unquoted
|
|
485
|
+
expect(result.sql).toMatch(/:endDate/);
|
|
486
|
+
expect(result.sql).not.toMatch(/':endDate'/);
|
|
487
|
+
|
|
488
|
+
// Verify the special_category filter is preserved
|
|
489
|
+
expect(result.sql).toContain("special_category = 'Current drug user'");
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
});
|