From efadbdaf8abe3cd190deb1392ddf9a77c6aa00b8 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 21 May 2024 10:55:38 +0300 Subject: [PATCH 01/44] feat: new app version data API implementation --- .prettierignore | 2 +- .vscode/settings.json | 4 +- src/config/filtering/index.json | 2 + src/config/mapping/allocations/cycles.json | 8 + src/config/mapping/allocations/radial.json | 11 + src/config/mapping/allocations/sunburst.json | 7 + src/config/mapping/allocations/table.json | 8 + src/config/mapping/allocations/treemap.json | 37 + src/config/mapping/budgets/breakdown.json | 7 + src/config/mapping/budgets/cycles.json | 6 + src/config/mapping/budgets/metrics.json | 16 + src/config/mapping/budgets/radial.json | 7 + src/config/mapping/budgets/sankey.json | 9 + src/config/mapping/budgets/table.json | 9 + src/config/mapping/budgets/treemap.json | 37 + .../mapping/disbursements/barChart.json | 8 + .../mapping/disbursements/lineChart.json | 8 + src/config/mapping/disbursements/table.json | 11 + src/config/mapping/documents/list.json | 8 + src/config/mapping/eligibility/heatmap.json | 33 + src/config/mapping/eligibility/stats.json | 6 + src/config/mapping/eligibility/table.json | 15 + src/config/mapping/expenditures/bar.json | 8 + src/config/mapping/expenditures/heatmap.json | 12 + src/config/mapping/expenditures/table.json | 9 + .../mapping/filter-options/components.json | 5 + src/config/mapping/filter-options/donors.json | 6 + .../mapping/filter-options/geography.json | 9 + .../filter-options/principal-recipients.json | 5 + .../filter-options/replenishment-periods.json | 5 + .../mapping/financialInsightsStats.json | 9 + .../fundingrequests/location-table.json | 16 + .../mapping/fundingrequests/table-2.json | 28 + src/config/mapping/globalsearch/index.json | 104 +- src/config/mapping/grants/grant.json | 24 + .../mapping/grants/grantImplementation.json | 24 + src/config/mapping/grants/grantOverview.json | 33 + src/config/mapping/grants/list.json | 21 + .../coordinating-mechanism-contacts.json | 18 + src/config/mapping/location/geographies.json | 8 + src/config/mapping/location/info.json | 19 + src/config/mapping/location/pieCharts.json | 11 + .../mapping/pledgescontributions/bar.json | 17 + .../mapping/pledgescontributions/stats.json | 12 + .../pledgescontributions/sunburst.json | 11 + .../mapping/results/location-table.json | 8 + src/config/mapping/results/polyline.json | 15 + src/config/mapping/results/stats-home.json | 7 + src/config/mapping/results/table.json | 8 + src/config/urls/index.json | 17 +- src/controllers/allocations.controller.ts | 392 +++++- src/controllers/budgets.controller.ts | 711 +++++++++++ src/controllers/disbursements.controller.ts | 180 +++ src/controllers/documents.controller.ts | 49 +- src/controllers/eligibility.controller.ts | 232 +++- src/controllers/expenditures.controller.ts | 315 +++++ src/controllers/filteroptions.controller.ts | 261 +++- src/controllers/fundingrequests.controller.ts | 79 +- src/controllers/grants.controller.ts | 528 +++++++- src/controllers/index.ts | 1 + src/controllers/location.controller.ts | 479 +++++++ .../pledgescontributions.controller.ts | 553 ++++++-- src/controllers/results.controller.ts | 185 ++- src/interfaces/budgetSankey.ts | 18 + src/interfaces/filters.ts | 3 + src/interfaces/geographies.ts | 5 + src/interfaces/grantList.ts | 16 +- src/static-assets/country-descriptions.json | 1124 +++++++++++++++++ src/utils/filtering/documents.ts | 53 + src/utils/filtering/donors.ts | 64 + src/utils/filtering/eligibility.ts | 52 + src/utils/filtering/financialIndicators.ts | 129 ++ src/utils/filtering/fundingRequests.ts | 87 ++ src/utils/filtering/grants.ts | 92 ++ src/utils/filtering/programmaticIndicators.ts | 65 + 75 files changed, 6232 insertions(+), 169 deletions(-) create mode 100644 src/config/mapping/allocations/cycles.json create mode 100644 src/config/mapping/allocations/radial.json create mode 100644 src/config/mapping/allocations/sunburst.json create mode 100644 src/config/mapping/allocations/table.json create mode 100644 src/config/mapping/allocations/treemap.json create mode 100644 src/config/mapping/budgets/breakdown.json create mode 100644 src/config/mapping/budgets/cycles.json create mode 100644 src/config/mapping/budgets/metrics.json create mode 100644 src/config/mapping/budgets/radial.json create mode 100644 src/config/mapping/budgets/sankey.json create mode 100644 src/config/mapping/budgets/table.json create mode 100644 src/config/mapping/budgets/treemap.json create mode 100644 src/config/mapping/disbursements/barChart.json create mode 100644 src/config/mapping/disbursements/lineChart.json create mode 100644 src/config/mapping/disbursements/table.json create mode 100644 src/config/mapping/documents/list.json create mode 100644 src/config/mapping/eligibility/heatmap.json create mode 100644 src/config/mapping/eligibility/stats.json create mode 100644 src/config/mapping/eligibility/table.json create mode 100644 src/config/mapping/expenditures/bar.json create mode 100644 src/config/mapping/expenditures/heatmap.json create mode 100644 src/config/mapping/expenditures/table.json create mode 100644 src/config/mapping/filter-options/components.json create mode 100644 src/config/mapping/filter-options/donors.json create mode 100644 src/config/mapping/filter-options/geography.json create mode 100644 src/config/mapping/filter-options/principal-recipients.json create mode 100644 src/config/mapping/filter-options/replenishment-periods.json create mode 100644 src/config/mapping/financialInsightsStats.json create mode 100644 src/config/mapping/fundingrequests/location-table.json create mode 100644 src/config/mapping/fundingrequests/table-2.json create mode 100644 src/config/mapping/grants/grant.json create mode 100644 src/config/mapping/grants/grantImplementation.json create mode 100644 src/config/mapping/grants/grantOverview.json create mode 100644 src/config/mapping/grants/list.json create mode 100644 src/config/mapping/location/coordinating-mechanism-contacts.json create mode 100644 src/config/mapping/location/geographies.json create mode 100644 src/config/mapping/location/info.json create mode 100644 src/config/mapping/location/pieCharts.json create mode 100644 src/config/mapping/pledgescontributions/bar.json create mode 100644 src/config/mapping/pledgescontributions/stats.json create mode 100644 src/config/mapping/pledgescontributions/sunburst.json create mode 100644 src/config/mapping/results/location-table.json create mode 100644 src/config/mapping/results/polyline.json create mode 100644 src/config/mapping/results/stats-home.json create mode 100644 src/config/mapping/results/table.json create mode 100644 src/controllers/expenditures.controller.ts create mode 100644 src/interfaces/budgetSankey.ts create mode 100644 src/interfaces/geographies.ts create mode 100644 src/static-assets/country-descriptions.json create mode 100644 src/utils/filtering/documents.ts create mode 100644 src/utils/filtering/donors.ts create mode 100644 src/utils/filtering/eligibility.ts create mode 100644 src/utils/filtering/financialIndicators.ts create mode 100644 src/utils/filtering/fundingRequests.ts create mode 100644 src/utils/filtering/grants.ts create mode 100644 src/utils/filtering/programmaticIndicators.ts diff --git a/.prettierignore b/.prettierignore index c6911da..4d8df4d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ dist -*.json +# *.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 0731366..4d57e61 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,8 +5,8 @@ "editor.trimAutoWhitespace": true, "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": true, - "source.fixAll.eslint": true + "source.organizeImports": "explicit", + "source.fixAll.eslint": "explicit" }, "files.exclude": { diff --git a/src/config/filtering/index.json b/src/config/filtering/index.json index ed73f33..718cc7c 100644 --- a/src/config/filtering/index.json +++ b/src/config/filtering/index.json @@ -10,5 +10,7 @@ "or_operator": "OR", "eq": " eq ", "in": " in ", + "gte": " ge ", + "lte": " le ", "multi_param_separator": "," } diff --git a/src/config/mapping/allocations/cycles.json b/src/config/mapping/allocations/cycles.json new file mode 100644 index 0000000..e1f10d1 --- /dev/null +++ b/src/config/mapping/allocations/cycles.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "value": "value", + "cycle": "periodCovered", + "component": "activityArea.name", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#D9D9D9"], + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((periodCovered,activityArea/name),aggregate(actualAmount with sum as value))" +} diff --git a/src/config/mapping/allocations/radial.json b/src/config/mapping/allocations/radial.json new file mode 100644 index 0000000..317a317 --- /dev/null +++ b/src/config/mapping/allocations/radial.json @@ -0,0 +1,11 @@ +{ + "dataPath": "value", + "cycle": "periodFrom", + "name": "activityArea.name", + "tooltipItem": "geography.parent.parent.name", + "value": "value", + "countryCode": "geography/code", + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((activityArea/name),aggregate(actualAmount with sum as value))", + "countriesCountUrlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((geography/name))", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD"] +} diff --git a/src/config/mapping/allocations/sunburst.json b/src/config/mapping/allocations/sunburst.json new file mode 100644 index 0000000..bfadf49 --- /dev/null +++ b/src/config/mapping/allocations/sunburst.json @@ -0,0 +1,7 @@ +{ + "dataPath": "value", + "value": "value", + "region": "geography.parent.name", + "country": "geography.name", + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((geography/parent/name,geography/name),aggregate(actualAmount with sum as value))" +} diff --git a/src/config/mapping/allocations/table.json b/src/config/mapping/allocations/table.json new file mode 100644 index 0000000..f19e1d8 --- /dev/null +++ b/src/config/mapping/allocations/table.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "value": "value", + "cycle": "periodCovered", + "component": "activityArea.name", + "geography": "geography.name", + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((activityArea/name,geography/name,periodCovered),aggregate(actualAmount with sum as value))" +} diff --git a/src/config/mapping/allocations/treemap.json b/src/config/mapping/allocations/treemap.json new file mode 100644 index 0000000..2def8e2 --- /dev/null +++ b/src/config/mapping/allocations/treemap.json @@ -0,0 +1,37 @@ +{ + "dataPath": "value", + "value": "value", + "component": "activityArea.name", + "geography": "geography.name", + "colors": [ + { + "bg": "#0A2840", + "text": "#FFFFFF", + "items": "#164366" + }, + { + "bg": "#013E77", + "text": "#FFFFFF", + "items": "#0B5191" + }, + { + "bg": "#00B5AE", + "text": "#000000", + "items": "#18CCC5" + }, + { + "bg": "#C3EDFD", + "text": "#FFFFFF", + "items": "#B3D7E5" + }, + { + "bg": "#F3F5F4", + "text": "#FFFFFF", + "items": "#DDDDDD" + } + ], + "urlParams": [ + "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((activityArea/name),aggregate(actualAmount with sum as value))", + "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((activityArea/name,geography/name),aggregate(actualAmount with sum as value))" + ] +} diff --git a/src/config/mapping/budgets/breakdown.json b/src/config/mapping/budgets/breakdown.json new file mode 100644 index 0000000..18a3009 --- /dev/null +++ b/src/config/mapping/budgets/breakdown.json @@ -0,0 +1,7 @@ +{ + "dataPath": "value", + "name": "activityAreaGroup.parent.parent.name", + "value": "value", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/parent/name),aggregate(plannedAmount with sum as value))", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#F3F5F4"] +} diff --git a/src/config/mapping/budgets/cycles.json b/src/config/mapping/budgets/cycles.json new file mode 100644 index 0000000..843c091 --- /dev/null +++ b/src/config/mapping/budgets/cycles.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "from": "implementationPeriod/periodFrom", + "to": "implementationPeriod/periodTo", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/perioFrom,implementationPeriod/periodTo))" +} diff --git a/src/config/mapping/budgets/metrics.json b/src/config/mapping/budgets/metrics.json new file mode 100644 index 0000000..2a7307e --- /dev/null +++ b/src/config/mapping/budgets/metrics.json @@ -0,0 +1,16 @@ +{ + "dataPath": "value", + "indicatorNameField": "indicatorName", + "expenditureIndicatorName": "Total Expenditure to Last Expenditure Reporting Period - Reference Rate", + "budgetIndicatorName": "Total Budget to Last Expenditure Reporting Period - Reference Rate", + "disbursementIndicatorName": "Total Disbursement to Last Expenditure Reporting Period - Reference Rate", + "cashBalanceIndicatorName": "Opening Cash Balance - Reference Rate", + "organisationName": "implementationPeriod.grant.principalRecipient.name", + "organisationType": "implementationPeriod.grant.principalRecipient.type.name", + "expenditureValue": "actualCumulative", + "budgetValue": "plannedCumulative", + "disbursementValue": "actualCumulative", + "cashBalanceValue": "actual", + "urlParams": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))", + "urlParamsOrganisations": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName,implementationPeriod/grant/principalRecipient/type/name,implementationPeriod/grant/principalRecipient/name),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))" +} diff --git a/src/config/mapping/budgets/radial.json b/src/config/mapping/budgets/radial.json new file mode 100644 index 0000000..18a3009 --- /dev/null +++ b/src/config/mapping/budgets/radial.json @@ -0,0 +1,7 @@ +{ + "dataPath": "value", + "name": "activityAreaGroup.parent.parent.name", + "value": "value", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/parent/name),aggregate(plannedAmount with sum as value))", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#F3F5F4"] +} diff --git a/src/config/mapping/budgets/sankey.json b/src/config/mapping/budgets/sankey.json new file mode 100644 index 0000000..94fef84 --- /dev/null +++ b/src/config/mapping/budgets/sankey.json @@ -0,0 +1,9 @@ +{ + "dataPath": "value", + "level1Field": "activityAreaGroup.parent.name", + "level2Field": "activityAreaGroup.name", + "valueField": "value", + "cycle": "periodFrom", + "nodeColors": ["#252C34", "#252C34", "#252C34"], + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/name,activityAreaGroup/name),aggregate(plannedAmount with sum as value))" +} diff --git a/src/config/mapping/budgets/table.json b/src/config/mapping/budgets/table.json new file mode 100644 index 0000000..b1fdf46 --- /dev/null +++ b/src/config/mapping/budgets/table.json @@ -0,0 +1,9 @@ +{ + "dataPath": "value", + "parentField": "activityAreaGroup.parent.name", + "childrenField": "activityAreaGroup.name", + "valueField": "value", + "cycle": "periodFrom", + "countField": "count", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/name,activityAreaGroup/name),aggregate(plannedAmount with sum as value,implementationPeriod/grantId with countdistinct as count))" +} diff --git a/src/config/mapping/budgets/treemap.json b/src/config/mapping/budgets/treemap.json new file mode 100644 index 0000000..605947d --- /dev/null +++ b/src/config/mapping/budgets/treemap.json @@ -0,0 +1,37 @@ +{ + "dataPath": "value", + "cycle": "periodFrom", + "name": "activityAreaGroup.parent.parent.name", + "value": "value", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/parent/name),aggregate(plannedAmount with sum as value))", + "textbgcolors": [ + { + "color": "#0A2840", + "textcolor": "#FFFFFF" + }, + { + "color": "#013E77", + "textcolor": "#FFFFFF" + }, + { + "color": "#00B5AE", + "textcolor": "#FFFFFF" + }, + { + "color": "#10708F", + "textcolor": "#FFFFFF" + }, + { + "color": "#9EDBE9", + "textcolor": "#000000" + }, + { + "color": "#C3EDFD", + "textcolor": "#000000" + }, + { + "color": "#F3F5F4", + "textcolor": "#000000" + } + ] +} diff --git a/src/config/mapping/disbursements/barChart.json b/src/config/mapping/disbursements/barChart.json new file mode 100644 index 0000000..65b6061 --- /dev/null +++ b/src/config/mapping/disbursements/barChart.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "name": "activityArea.name", + "value": "value", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((activityArea/name),aggregate(actualAmount with sum as value))&$orderby=value desc", + "biggerBarColor": "#00B5AE", + "barColor": "#013E77" +} diff --git a/src/config/mapping/disbursements/lineChart.json b/src/config/mapping/disbursements/lineChart.json new file mode 100644 index 0000000..f5b6f46 --- /dev/null +++ b/src/config/mapping/disbursements/lineChart.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "cycle": "periodFrom", + "value": "value", + "line": "activityArea.name", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,activityArea/name),aggregate(actualAmount with sum as value))", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD"] +} diff --git a/src/config/mapping/disbursements/table.json b/src/config/mapping/disbursements/table.json new file mode 100644 index 0000000..949a060 --- /dev/null +++ b/src/config/mapping/disbursements/table.json @@ -0,0 +1,11 @@ +{ + "dataPath": "value", + "indicatorField": "indicatorName", + "signedIndicator": "Total Signed Amount - Reference Rate", + "commitmentIndicator": "Total Commitment Amount - Reference Rate", + "disbursementIndicator": "Total Disbursed Amount - Reference Rate", + "component": "activityArea.name", + "grants": "count", + "valueField": "value", + "urlParams": "?$apply=filter(indicatorName in ('Total Signed Amount - Reference Rate','Total Commitment Amount - Reference Rate','Total Disbursed Amount - Reference Rate'))/groupby((activityArea/name,indicatorName),aggregate(actualAmount with sum as value,implementationPeriod/grantId with countdistinct as count))&$orderby=value desc" +} diff --git a/src/config/mapping/documents/list.json b/src/config/mapping/documents/list.json new file mode 100644 index 0000000..019cb1b --- /dev/null +++ b/src/config/mapping/documents/list.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "type": "documentType.parent.name", + "title": "title", + "url": "url", + "geography": "geography.name", + "urlParams": "?$apply=filter()/groupby((geography/name,documentType/index,documentType/parent/name,title,url))&$orderby=documentType/index asc,title asc" +} diff --git a/src/config/mapping/eligibility/heatmap.json b/src/config/mapping/eligibility/heatmap.json new file mode 100644 index 0000000..9f668a9 --- /dev/null +++ b/src/config/mapping/eligibility/heatmap.json @@ -0,0 +1,33 @@ +{ + "dataPath": "value", + "cycle": "fundingStream", + "incomeLevel": "incomeLevel", + "diseaseBurden": "diseaseBurden", + "isEligible": "isEligible", + "eligibilityYear": "eligibilityYear", + "component": "activityArea.name", + "urlParams": "?$select=fundingStream,incomeLevel,isEligible,diseaseBurden,eligibilityYear,geography,activityArea&$expand=activityArea&$filter=geography/code eq ''", + "isEligibleValueMapping": { + "true": "Eligible", + "false": "Not Eligible" + }, + "incomeLevelValueMapping": { + "Lower middle income": "LM", + "Small Island income": "SI", + "Low income": "Low", + "Lower-Lower middle income": "LLM", + "Upper middle income": "UM", + "NA": "NA", + "High income": "High", + "Upper-Lower middle income": "ULM" + }, + "diseaseBurdenValueMapping": { + "NA": "NA", + "Low": "L", + "Moderate": "M", + "High": "H", + "Severe": "S", + "Extreme": "E", + "Not High": "NH" + } +} diff --git a/src/config/mapping/eligibility/stats.json b/src/config/mapping/eligibility/stats.json new file mode 100644 index 0000000..0a40bfc --- /dev/null +++ b/src/config/mapping/eligibility/stats.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "geography": "geography.name", + "component": "activityArea.name", + "urlParams": "?$apply=filter(isEligible eq true)/groupby((activityArea/name,geography/name))" +} diff --git a/src/config/mapping/eligibility/table.json b/src/config/mapping/eligibility/table.json new file mode 100644 index 0000000..6f435ac --- /dev/null +++ b/src/config/mapping/eligibility/table.json @@ -0,0 +1,15 @@ +{ + "dataPath": "value", + "geography": "geography.name", + "year": "eligibilityYear", + "component": "activityArea.name", + "incomeLevel": "incomeLevel", + "diseaseBurden": "diseaseBurden", + "isEligible": "isEligible", + "eligibilityValues": { + "eligible": "Eligible", + "notEligible": "Not Eligible", + "transitionFunding": "Transition Funding" + }, + "urlParams": "?$apply=/groupby((geography/name,eligibilityYear,activityArea/name,isEligible,incomeLevel,diseaseBurden))&$orderby=geography/name,activityArea/name" +} diff --git a/src/config/mapping/expenditures/bar.json b/src/config/mapping/expenditures/bar.json new file mode 100644 index 0000000..4ed090d --- /dev/null +++ b/src/config/mapping/expenditures/bar.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "name": "activityArea.parent.parent.name", + "itemName": "activityArea.parent.name", + "indicatorName": "indicatorName", + "value": "actualCumulative", + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,activityArea/parent/name,activityArea/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative))" +} diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json new file mode 100644 index 0000000..9d12de3 --- /dev/null +++ b/src/config/mapping/expenditures/heatmap.json @@ -0,0 +1,12 @@ +{ + "dataPath": "value", + "budget": "value2", + "expenditure": "value1", + "cycle": "periodCovered", + "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true)/groupby((,),aggregate(actualAmountCumulative with sum as value1,plannedAmountCumulative with sum as value2))", + "fields": { + "principalRecipient": "implementationPeriod/grant/principalRecipient/name", + "principalRecipientType": "implementationPeriod/grant/principalRecipient/type/name", + "component": "implementationPeriod/grant/activityArea/name" + } +} diff --git a/src/config/mapping/expenditures/table.json b/src/config/mapping/expenditures/table.json new file mode 100644 index 0000000..8a2a030 --- /dev/null +++ b/src/config/mapping/expenditures/table.json @@ -0,0 +1,9 @@ +{ + "dataPath": "value", + "name": "activityArea.parent.parent.name", + "itemName": "activityArea.parent.name", + "indicatorName": "indicatorName", + "cumulativeExpenditureValue": "actualCumulative", + "periodExpenditureValue": "actual", + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,activityArea/parent/name,activityArea/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" +} diff --git a/src/config/mapping/filter-options/components.json b/src/config/mapping/filter-options/components.json new file mode 100644 index 0000000..50642d1 --- /dev/null +++ b/src/config/mapping/filter-options/components.json @@ -0,0 +1,5 @@ +{ + "dataPath": "value", + "label": "name", + "value": "name" +} diff --git a/src/config/mapping/filter-options/donors.json b/src/config/mapping/filter-options/donors.json new file mode 100644 index 0000000..1c3da10 --- /dev/null +++ b/src/config/mapping/filter-options/donors.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "label": "name", + "value": "id", + "type": "type.name" +} diff --git a/src/config/mapping/filter-options/geography.json b/src/config/mapping/filter-options/geography.json new file mode 100644 index 0000000..327299c --- /dev/null +++ b/src/config/mapping/filter-options/geography.json @@ -0,0 +1,9 @@ +{ + "dataPath": "value[0].children", + "label": "name", + "value": "code", + "children": "children", + "isDonor": "isDonor", + "isRecipient": "isRecipient", + "level": "level" +} diff --git a/src/config/mapping/filter-options/principal-recipients.json b/src/config/mapping/filter-options/principal-recipients.json new file mode 100644 index 0000000..50642d1 --- /dev/null +++ b/src/config/mapping/filter-options/principal-recipients.json @@ -0,0 +1,5 @@ +{ + "dataPath": "value", + "label": "name", + "value": "name" +} diff --git a/src/config/mapping/filter-options/replenishment-periods.json b/src/config/mapping/filter-options/replenishment-periods.json new file mode 100644 index 0000000..58360b5 --- /dev/null +++ b/src/config/mapping/filter-options/replenishment-periods.json @@ -0,0 +1,5 @@ +{ + "dataPath": "value", + "label": "periodCovered", + "value": "periodCovered" +} diff --git a/src/config/mapping/financialInsightsStats.json b/src/config/mapping/financialInsightsStats.json new file mode 100644 index 0000000..54c1c25 --- /dev/null +++ b/src/config/mapping/financialInsightsStats.json @@ -0,0 +1,9 @@ +{ + "dataPath": "value", + "valueField": "value", + "indicatorField": "indicatorName", + "signed": "Total Signed Amount - Reference Rate", + "commitment": "Total Commitment Amount - Reference Rate", + "disbursement": "Total Disbursed Amount - Reference Rate", + "urlParams": "?$apply=filter(indicatorName in ('Total Signed Amount - Reference Rate','Total Commitment Amount - Reference Rate','Total Disbursed Amount - Reference Rate'))/groupby((indicatorName),aggregate(actualAmount with sum as value))" +} diff --git a/src/config/mapping/fundingrequests/location-table.json b/src/config/mapping/fundingrequests/location-table.json new file mode 100644 index 0000000..e079145 --- /dev/null +++ b/src/config/mapping/fundingrequests/location-table.json @@ -0,0 +1,16 @@ +{ + "dataPath": "value", + "tableMap": [ + "value[]", + { + "components": "", + "submissionDate": "submissionDate", + "approach": "reviewApproach", + "trpWindow": "window", + "trpOutcome": "reviewOutcome", + "portfolioCategorization": "", + "boardApproval": "boardApproval", + "items": [] + } + ] +} diff --git a/src/config/mapping/fundingrequests/table-2.json b/src/config/mapping/fundingrequests/table-2.json new file mode 100644 index 0000000..1631983 --- /dev/null +++ b/src/config/mapping/fundingrequests/table-2.json @@ -0,0 +1,28 @@ +{ + "dataPath": "value", + "groupby": "geography", + "map": [ + "value", + { + "geography": "geography.name", + "components": "name", + "submissionDate": "submissionDate", + "approach": "reviewApproach", + "trpWindow": "window", + "trpOutcome": "reviewOutcome", + "portfolioCategorization": "differentiationCategory", + "gacMeeting": "reviewDate", + "items": [ + "implementationPeriods", + { + "boardApproval": "milestones[1].date", + "grant": "grant.code", + "startingDate": "periodStartDate", + "endingDate": "periodEndDate", + "principalRecipient": "grant.principalRecipient.name" + } + ] + } + ], + "urlParams": "?$filter=&$expand=geography,implementationPeriods($expand=milestones,grant($expand=principalRecipient))" +} diff --git a/src/config/mapping/globalsearch/index.json b/src/config/mapping/globalsearch/index.json index 88dd822..4a9569f 100644 --- a/src/config/mapping/globalsearch/index.json +++ b/src/config/mapping/globalsearch/index.json @@ -4,25 +4,20 @@ "name": "Location(s)", "type": "Country", "link": "/location//overview", - "url": "https://fetch.theglobalfund.org/v3.3/odata/VGrantAgreements?$apply=filter()/groupby((GeographicAreaCode_ISO3,GeographicAreaName,MultiCountryName),aggregate(GrantAgreementId with countdistinct as count))", + "url": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$apply=filter()/groupby((geography/code,geography/name),aggregate(id with countdistinct as count))", "itemname": "", "filterFields": [ - "GeographicAreaCode_ISO3", - "GeographicAreaName", - "ComponentName", - "MultiCountryName" + "geography/code", + "geography/name", + "activityArea/parent/parent/name" ], "filterTemplate": "contains(,)", - "order": [ - "desc" - ], + "order": ["desc"], "mappings": [ "value[]", { - "name": "geographicAreaName", - "altName": "multiCountryName", - "code": "geographicAreaCode_ISO3", - "altCode": "multiCountryName", + "name": "geography.name", + "code": "geography.code", "count": "count", "order": "count" } @@ -32,26 +27,24 @@ { "name": "Partner(s)", "type": "Partner", - "link": "/partner//investments", - "url": "https://fetch.theglobalfund.org/v3.3/odata/VGrantAgreements?$apply=filter()/groupby((PrincipalRecipientId,PrincipalRecipientName,PrincipalRecipientShortName),aggregate(GrantAgreementId with countdistinct as count))", + "link": "", + "url": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$apply=filter()/groupby((principalRecipient/id,principalRecipient/name,principalRecipient/shortName),aggregate(id with countdistinct as count))", "itemname": "", "filterFields": [ - "GeographicAreaCode_ISO3", - "GeographicAreaName", - "ComponentName", - "PrincipalRecipientName", - "PrincipalRecipientShortName" + "geography/code", + "geography/name", + "activityArea/parent/parent/name", + "principalRecipient/name", + "principalRecipient/shortName" ], "filterTemplate": "contains(,)", - "order": [ - "desc" - ], + "order": ["desc"], "mappings": [ "value[]", { - "longName": "principalRecipientName", - "shortName": "principalRecipientShortName", - "code": "principalRecipientId", + "longName": "principalRecipient/name", + "shortName": "principalRecipient/shortName", + "code": "principalRecipient/id", "count": "count", "order": "count" } @@ -61,7 +54,7 @@ { "name": "Donor(s)", "type": "Donor", - "link": "/viz/pledges-contributions/time-cycle?donors=", + "link": "", "url": "http://localhost:4200/filter-options/donors?q=", "itemname": "", "filterFields": [], @@ -79,31 +72,27 @@ "name": "Grant(s)", "type": "Grant", "link": "/grant/", - "url": "https://fetch.theglobalfund.org/v3.3/odata/VGrantAgreements?$select=grantAgreementTitle,grantAgreementNumber,grantAgreementStatusTypeName&$filter=()&$orderby=GrantAgreementStatusTypeName asc,GrantAgreementTitle asc", - "itemname": " | ", + "url": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$select=code,status&$expand=status(select=statusName)&$filter=()&$orderby=status/statusName asc", + "itemname": "", "filterFields": [ - "GeographicAreaCode_ISO3", - "GeographicAreaName", - "ComponentName", - "GrantAgreementTitle", - "GrantAgreementNumber", - "PrincipalRecipientName", - "PrincipalRecipientShortName", - "GrantAgreementStatusTypeName" + "geography/code", + "geography/name", + "activityArea/parent/parent/name", + "title", + "code", + "principalRecipient/name", + "principalRecipient/shortName", + "status/statusName" ], "filterTemplate": "contains(,)", - "order": [ - "asc", - "asc" - ], + "order": ["asc", "asc"], "mappings": [ "value[]", { - "name": "grantAgreementTitle", - "code": "grantAgreementNumber", - "type": "grantAgreementStatusTypeName", - "order": "grantAgreementStatusTypeName", - "order1": "GrantAgreementTitle" + "code": "code", + "type": "status/statusName", + "order": "status/statusName", + "order1": "code" } ], "options": [] @@ -111,24 +100,22 @@ { "name": "Result(s)", "type": "Result", - "link": "/results/#", - "url": "https://fetch.theglobalfund.org/v3.3/odata/VReportingResults?$apply=filter()/groupby((reportingResultIndicatorName,reportingResultIndicatorId), aggregate(ReportingResultValue with sum as value))", + "link": "", + "url": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allProgrammaticIndicators?$apply=filter( AND programmaticDataset eq 'Annual_Results')/groupby((indicatorName,recordId),aggregate(resultValueNumerator with sum as value))", "itemname": "", "filterFields": [ - "GeographicAreaCode_ISO3", - "GeographicAreaName", - "ComponentName", - "ReportingResultIndicatorName" + "geography/code", + "geography/name", + "activityArea/parent/parent/name", + "indicatorName" ], "filterTemplate": "contains(,)", - "order": [ - "desc" - ], + "order": ["desc"], "mappings": [ "value[]", { - "name": "reportingResultIndicatorName", - "code": "reportingResultIndicatorId", + "name": "indicatorName", + "code": "recordId", "order": "value" } ], @@ -148,10 +135,7 @@ "OrganizationName" ], "filterTemplate": "contains(tolower(),tolower())", - "order": [ - "desc", - "desc" - ], + "order": ["desc", "desc"], "mappings": [ "value[]", { diff --git a/src/config/mapping/grants/grant.json b/src/config/mapping/grants/grant.json new file mode 100644 index 0000000..7ac6c99 --- /dev/null +++ b/src/config/mapping/grants/grant.json @@ -0,0 +1,24 @@ +{ + "dataPath": "value[0]", + "code": "code", + "titleArray": "narratives", + "title": { + "text": "narrativeText", + "lang": "narrativeLanguage" + }, + "implementationPeriodArray": "implementationPeriods", + "implementationPeriodFrom": "implementationPeriodFrom", + "implementationPeriodTitle": "title", + "countryCode": "geography.code", + "countryName": "geography.name", + "principalRecipientId": "principalRecipient.id", + "principalRecipientName": "principalRecipient.name", + "principalRecipientShortName": "principalRecipient.shortName", + "component": "activityArea.name", + "FPMSalutation": "fundPortfolioManager.salutation", + "FPMFirstName": "fundPortfolioManager.firstName", + "FPMMiddleName": "fundPortfolioManager.middleName", + "FPMLastName": "fundPortfolioManager.lastName", + "FPMEmail": "fundPortfolioManager.emailAddress", + "urlParams": "?$filter=code eq ''&$expand=geography,principalRecipient,activityArea,implementationPeriods,fundPortfolioManager" +} diff --git a/src/config/mapping/grants/grantImplementation.json b/src/config/mapping/grants/grantImplementation.json new file mode 100644 index 0000000..2ae8222 --- /dev/null +++ b/src/config/mapping/grants/grantImplementation.json @@ -0,0 +1,24 @@ +{ + "dataPath": "value", + "cycle": "periodFrom", + "value": "actualAmount", + "category": "indicatorName", + "categories": { + "Disbursement Amount - Reference Rate": "Disbursed", + "Commitment Amount - Reference Rate": "Committed", + "Total Signed Amount - Reference Rate": "Signed" + }, + "radialUrlParams": "?$filter=implementationPeriod/grant/code eq '' AND implementationPeriod/code eq '' AND indicatorName in ('Disbursement Amount - Reference Rate', 'Commitment Amount - Reference Rate', 'Total Signed Amount - Reference Rate')&$select=indicatorName,actualAmount", + "colors": ["#0A2840", "#013E77", "#00B5AE"], + "date": "valueDate", + "barUrlParams": "?$filter=indicatorName eq 'Disbursement Amount - Reference Rate' AND implementationPeriod/grant/code eq '' AND implementationPeriod/code eq ''&$select=valueDate,actualAmount", + "barColors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#D9D9D9"], + "sankeyValue": "totalAmount", + "sankeyVariant1Category1": "activityArea.parent.name", + "sankeyVariant1Category2": "activityArea.name", + "sankeyVariant1UrlParams": "?$apply=filter(indicatorName eq 'Budget - Reference Rate' AND financialDataSet eq 'GrantBudget_ReferenceRate' AND implementationPeriod/grant/code eq '' AND implementationPeriod/code eq '')/groupby((activityArea/name,activityArea/parent/name),aggregate(plannedAmount with sum as totalAmount))", + "sankeyVariant2Category1": "financialCategory.parent.parent.name", + "sankeyVariant2Category2": "financialCategory.parent.name", + "sankeyVariant2Category3": "financialCategory.name", + "sankeyVariant2UrlParams": "?$apply=filter(indicatorName eq 'Budget - Reference Rate' AND financialDataSet eq 'GrantBudget_ReferenceRate' AND implementationPeriod/grant/code eq '' AND implementationPeriod/code eq '')/groupby((financialCategory/name,financialCategory/parent/name,financialCategory/parent/parent/name),aggregate(plannedAmount with sum as totalAmount))" +} diff --git a/src/config/mapping/grants/grantOverview.json b/src/config/mapping/grants/grantOverview.json new file mode 100644 index 0000000..011b7e9 --- /dev/null +++ b/src/config/mapping/grants/grantOverview.json @@ -0,0 +1,33 @@ +{ + "dataPath": "value[0]", + "implementationPeriodArray": "implementationPeriods", + "implementationPeriodFrom": "implementationPeriodFrom", + "implementationPeriod": { + "financialIndicators": "financialIndicators", + "financialIndicator": { + "value": "actualAmount", + "type": "indicatorName", + "types": [ + { + "label": "Disbursement", + "value": "Disbursement Amount - Reference Rate" + }, + {"label": "Commitment", "value": "Commitment Amount - Reference Rate"}, + {"label": "Signed", "value": "Total Signed Amount - Reference Rate"} + ] + }, + "status": "status.statusName", + "narratives": "narratives", + "narrative": { + "text": "narrativeText", + "index": "narrativeIndex", + "lang": "narrativeLanguage", + "type": "narrativeType", + "types": ["Goal", "Objective"] + }, + "boardApprovedDate": "milestones[0].date", + "startDate": "periodStartDate", + "endDate": "periodEndDate" + }, + "urlParams": "?$filter=code eq ''&$expand=implementationPeriods($expand=financialIndicators,status,narratives,milestones)" +} diff --git a/src/config/mapping/grants/list.json b/src/config/mapping/grants/list.json new file mode 100644 index 0000000..a6c7aa4 --- /dev/null +++ b/src/config/mapping/grants/list.json @@ -0,0 +1,21 @@ +{ + "map": [ + "value[]", + { + "status": "status.statusName", + "location": "geography.name", + "rating": "rating", + "component": "activityArea.name", + "number": "code", + "principalRecipient": "principalRecipient.name", + "startDate": "periodStartDate", + "endDate": "periodEndDate", + "title": "narratives[0].narrativeText", + "signed": "totalSignedAmount_ReferenceRate", + "disbursed": "totalDisbursedAmount_ReferenceRate", + "committed": "totalCommitmentAmount_ReferenceRate" + } + ], + "count": "@odata.count", + "urlParams": "?$filter=&$count=true&$expand=status($select=statusName),geography($select=name),activityArea($select=name),principalRecipient($select=name),narratives($select=narrativeType,narrativeLanguage,narrativeText)&$select=code,status,geography,activityArea,principalRecipient,periodStartDate,periodEndDate,narratives,totalSignedAmount_ReferenceRate,totalCommitmentAmount_ReferenceRate,totalDisbursedAmount_ReferenceRate&$orderby=status/statusName asc,periodStartDate desc" +} diff --git a/src/config/mapping/location/coordinating-mechanism-contacts.json b/src/config/mapping/location/coordinating-mechanism-contacts.json new file mode 100644 index 0000000..6fb515c --- /dev/null +++ b/src/config/mapping/location/coordinating-mechanism-contacts.json @@ -0,0 +1,18 @@ +{ + "dataPath": "value", + "orgName": "name", + "items": "contacts", + "itemRole": "roleName", + "itemTitle": "title", + "itemSalutation": "salutation", + "itemFirstName": "firstName", + "itemMiddleName": "middleName", + "itemLastName": "lastName", + "itemEmail": "emailAddress", + "itemRoleOrder": { + "Chair": 0, + "Vice Chair": 1, + "Admin Focal Point": 2 + }, + "urlParams": "?$expand=contacts&$filter=geography/code eq ''" +} diff --git a/src/config/mapping/location/geographies.json b/src/config/mapping/location/geographies.json new file mode 100644 index 0000000..4724bbe --- /dev/null +++ b/src/config/mapping/location/geographies.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value[0].children", + "name": "name", + "value": "code", + "category": "level", + "isRecipient": "isRecipient", + "items": "children" +} diff --git a/src/config/mapping/location/info.json b/src/config/mapping/location/info.json new file mode 100644 index 0000000..d717b9c --- /dev/null +++ b/src/config/mapping/location/info.json @@ -0,0 +1,19 @@ +{ + "dataPath": "value", + "name": "[0].name", + "regionName": "[0].parent.name", + "level": "[0].level", + "countryLevel": "Country", + "multicountryLevel": "Multicountry", + "FPMSalutation": "[0].fundPortfolioManager.salutation", + "FPMFirstName": "[0].fundPortfolioManager.firstName", + "FPMMiddleName": "[0].fundPortfolioManager.middleName", + "FPMLastName": "[0].fundPortfolioManager.lastName", + "FPMEmail": "[0].fundPortfolioManager.emailAddress", + "principalRecipientName": "principalRecipient.name", + "principalRecipientCode": "principalRecipient.shortName", + "urlParams": "?$filter=code eq ''&$expand=parent", + "FPMurlParams": "?$top=1&$filter=geography/code eq ''&$expand=fundPortfolioManager", + "currentPrincipalRecipientsUrlParams": "?$apply=filter(status/statusName eq 'Active' AND geography/code eq '')/groupby((principalRecipient/name))", + "formerPrincipalRecipientsUrlParams": "?$apply=filter(status/statusName in ('Administratively Closed','In Closure','Terminated') AND geography/code eq '')/groupby((principalRecipient/name))" +} diff --git a/src/config/mapping/location/pieCharts.json b/src/config/mapping/location/pieCharts.json new file mode 100644 index 0000000..7584690 --- /dev/null +++ b/src/config/mapping/location/pieCharts.json @@ -0,0 +1,11 @@ +{ + "dataPath": "value", + "count": "count", + "pie1Field": "activityArea.name", + "pie2Field": "principalRecipient.name", + "pie3Field": "financialDataSet", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#F3F5F4"], + "pie1UrlParams": "?$apply=filter(geography/code eq '')/groupby((activityArea/name),aggregate(id with countdistinct as count))", + "pie2UrlParams": "?$apply=filter(geography/code eq '')/groupby((principalRecipient/name),aggregate(id with countdistinct as count))", + "pie3UrlParams": "?$apply=filter(implementationPeriod/grant/geography/code eq '' AND financialDataSet in ('Disbursement_ReferenceRate', 'Commitment_ReferenceRate'))/groupby((financialDataSet),aggregate(actualAmount with sum as count))" +} diff --git a/src/config/mapping/pledgescontributions/bar.json b/src/config/mapping/pledgescontributions/bar.json new file mode 100644 index 0000000..fb99ce7 --- /dev/null +++ b/src/config/mapping/pledgescontributions/bar.json @@ -0,0 +1,17 @@ +{ + "dataPath": "value", + "cycle": "periodFrom", + "name": "periodCovered", + "value": "value", + "countryCode": "donor/geography/code", + "pledgesUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Pledge - Reference Rate')/groupby((periodCovered),aggregate(plannedAmount with sum as value))&$orderby=periodCovered asc", + "contributionsUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Contribution - Reference Rate')/groupby((periodCovered),aggregate(actualAmount with sum as value))&$orderby=periodCovered asc", + "donorBarType": "donor.type.name", + "donorBarDonor": "donor.name", + "donorBarIndicatorField": "indicatorName", + "donorBarIndicatorPledge": "Pledge - Reference Rate", + "donorBarIndicatorContribution": "Contribution - Reference Rate", + "donorBarIndicatorPledgeAmount": "plannedAmount", + "donorBarIndicatorContributionAmount": "actualAmount", + "donorBarUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((donor/name,donor/type/name,indicatorName),aggregate(plannedAmount with sum as plannedAmount,actualAmount with sum as actualAmount))" +} diff --git a/src/config/mapping/pledgescontributions/stats.json b/src/config/mapping/pledgescontributions/stats.json new file mode 100644 index 0000000..09d48a8 --- /dev/null +++ b/src/config/mapping/pledgescontributions/stats.json @@ -0,0 +1,12 @@ +{ + "dataPath": "value", + "pledgeAmount": "plannedAmount", + "contributionAmount": "actualAmount", + "pledgeIndicator": "Pledge - Reference Rate", + "indicatorField": "indicatorName", + "contributionIndicator": "Contribution - Reference Rate", + "totalValuesUrlParams": "?$apply=filter(((financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Pledge - Reference Rate') OR (financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Contribution - Reference Rate')))/groupby((indicatorName),aggregate(plannedAmount with sum as plannedAmount,actualAmount with sum as actualAmount))", + "donorType": "donor.type.name", + "count": "count", + "donorTypesCountUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((donor/type/name),aggregate(donor/name with countdistinct as count))" +} diff --git a/src/config/mapping/pledgescontributions/sunburst.json b/src/config/mapping/pledgescontributions/sunburst.json new file mode 100644 index 0000000..3520224 --- /dev/null +++ b/src/config/mapping/pledgescontributions/sunburst.json @@ -0,0 +1,11 @@ +{ + "dataPath": "value", + "type": "donor.type.name", + "donor": "donor.name", + "indicatorField": "indicatorName", + "indicatorPledge": "Pledge - Reference Rate", + "indicatorContribution": "Contribution - Reference Rate", + "amount": "amount", + "pledgeUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Pledge - Reference Rate')/groupby((donor/name,donor/type/name),aggregate(plannedAmount with sum as amount))", + "contributionUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Contribution - Reference Rate')/groupby((donor/name,donor/type/name),aggregate(actualAmount with sum as amount))" +} diff --git a/src/config/mapping/results/location-table.json b/src/config/mapping/results/location-table.json new file mode 100644 index 0000000..f7dfe0b --- /dev/null +++ b/src/config/mapping/results/location-table.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "cycle": "resultValueYear", + "name": "indicatorName", + "component": "activityArea.name", + "value": "value", + "urlParams": "?$apply=filter(geography/code eq '' AND resultValueYear eq AND programmaticDataset eq 'Annual_Results')/groupby((indicatorName,activityArea/name),aggregate(resultValueNumerator with sum as value))" +} diff --git a/src/config/mapping/results/polyline.json b/src/config/mapping/results/polyline.json new file mode 100644 index 0000000..1be3014 --- /dev/null +++ b/src/config/mapping/results/polyline.json @@ -0,0 +1,15 @@ +{ + "dataPath": "value", + "year": "resultValueYear", + "name": "indicatorName", + "component": "activityArea.name", + "value": "value", + "componentColors": { + "HIV/AIDS": "#0A2840", + "Malaria": "#013E77", + "Tuberculosis": "#00B5AE", + "HIV/TB": "#C3EDFD", + "RSSH": "#D9D9D9" + }, + "urlParams": "?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((indicatorName,activityArea/name,resultValueYear),aggregate(resultValueNumerator with sum as value))&$orderby=value desc" +} diff --git a/src/config/mapping/results/stats-home.json b/src/config/mapping/results/stats-home.json new file mode 100644 index 0000000..042d37b --- /dev/null +++ b/src/config/mapping/results/stats-home.json @@ -0,0 +1,7 @@ +{ + "dataPath": "value", + "cycle": "resultValueYear", + "name": "indicatorName", + "value": "value", + "urlParams": "?$apply=filter((indicatorName eq 'People on antiretroviral therapy for HIV' OR indicatorName eq 'People with TB treated' OR indicatorName eq 'Mosquito nets distributed'))/groupby((indicatorName),aggregate(resultValueNumerator with sum as value))" +} diff --git a/src/config/mapping/results/table.json b/src/config/mapping/results/table.json new file mode 100644 index 0000000..de64f03 --- /dev/null +++ b/src/config/mapping/results/table.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "year": "resultValueYear", + "name": "indicatorName", + "component": "activityArea.name", + "value": "value", + "urlParams": "?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((indicatorName,activityArea/name,resultValueYear),aggregate(resultValueNumerator with sum as value))&$orderby=value desc" +} diff --git a/src/config/urls/index.json b/src/config/urls/index.json index 94f3a34..6950421 100644 --- a/src/config/urls/index.json +++ b/src/config/urls/index.json @@ -31,5 +31,20 @@ "filteroptionsstatus1": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreementImplementationPeriods?$apply=groupby((implementationPeriodStatusTypeName))", "filteroptionsdonors": "https://fetch.theglobalfund.org/v3.4/odata/donors/?$filter=donorId%20eq%20c06eaea9-c81b-442b-b403-dc348f3734eb%20or%20donorId%20eq%2025188dfc-7567-4e08-b8d0-088a6b83f238%20or%20donorId%20eq%20641c05fa-129d-4b57-a213-b5f6852ddc5f%20or%20donorid%20eq%20cc30fc59-b2fa-49e3-aa5a-762727daa68c&$expand=members($expand=members($expand=members($expand=members)))", "filteroptionsreplenishmentperiods": "https://fetch.theglobalfund.org/v3.4/odata/replenishmentperiods", - "multicountriescountriesdata": "https://fetch.theglobalfund.org/v3.4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition" + "multicountriescountriesdata": "https://fetch.theglobalfund.org/v3.4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", + "FILTER_OPTIONS_GEOGRAPHY": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=type eq 'Component'", + "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))", + "FILTER_OPTIONS_DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors?$apply=groupby((id,name,type/name))", + "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/OrganizationTypes?$filter=group eq 'PrincipalRecipients'&$expand=children", + "FINANCIAL_INDICATORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators", + "DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors", + "PROGRAMMATIC_INDICATORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allProgrammaticIndicators", + "COORDINATING_MECHANISMS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/CoordinatingMechanisms", + "GRANTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants", + "HIERARCHICAL_GEOGRAPHIES": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", + "GEOGRAPHIES": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies", + "FUNDING_REQUESTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/FundingRequests", + "DOCUMENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Documents", + "ELIGIBILITY": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Eligibility" } diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index 0eeb574..c30006e 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -12,16 +13,22 @@ import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; +import AllocationCyclesFieldsMapping from '../config/mapping/allocations/cycles.json'; import AllocationsDrilldownFieldsMapping from '../config/mapping/allocations/drilldown.json'; import AllocationsGeomapFieldsMapping from '../config/mapping/allocations/geomap.json'; import AllocationsFieldsMapping from '../config/mapping/allocations/index.json'; import AllocationsPeriodsFieldsMapping from '../config/mapping/allocations/periods.json'; +import AllocationRadialFieldsMapping from '../config/mapping/allocations/radial.json'; +import AllocationSunburstFieldsMapping from '../config/mapping/allocations/sunburst.json'; +import AllocationTableFieldsMapping from '../config/mapping/allocations/table.json'; +import AllocationTreemapFieldsMapping from '../config/mapping/allocations/treemap.json'; import urls from '../config/urls/index.json'; import {AllocationsTreemapDataItem} from '../interfaces/allocations'; import {SimpleTableRow} from '../interfaces/simpleTable'; import staticCountries from '../static-assets/countries.json'; import {handleDataApiError} from '../utils/dataApiError'; import {getFilterString} from '../utils/filtering/allocations/getFilterString'; +import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; import {formatFinancialValue} from '../utils/formatFinancialValue'; const ALLOCATIONS_RESPONSE: ResponseObject = { @@ -47,9 +54,390 @@ const ALLOCATIONS_RESPONSE: ResponseObject = { }, }; +async function getAllocationsData(url: string) { + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, AllocationRadialFieldsMapping.dataPath, []); + const groupedByName = _.groupBy(raw, AllocationRadialFieldsMapping.name); + const data: any = []; + Object.keys(groupedByName).forEach((name: string, index: number) => { + const value = _.sumBy( + groupedByName[name], + AllocationRadialFieldsMapping.value, + ); + data.push({ + name, + value, + itemStyle: { + color: _.get( + AllocationRadialFieldsMapping.colors, + `[${index}]`, + '', + ), + }, + tooltip: { + items: groupedByName[name].map((item: any) => ({ + name: _.get(item, AllocationRadialFieldsMapping.tooltipItem, ''), + value: _.get(item, AllocationRadialFieldsMapping.value, 0), + })), + }, + }); + }); + return data; + }) + .catch(handleDataApiError); +} + export class AllocationsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/allocations/cumulative-by-cycles') + @response(200) + async cumulativeByCycles() { + let filterString = filterFinancialIndicators( + this.req.query, + AllocationCyclesFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get( + resp.data, + AllocationCyclesFieldsMapping.dataPath, + [], + ); + + const data: { + name: string; + values: number[]; + itemStyle?: { + color: string; + }; + }[] = []; + + const groupedByCycle = _.groupBy( + raw, + AllocationCyclesFieldsMapping.cycle, + ); + + const cycles = Object.keys(groupedByCycle); + + const groupedByComponent = _.groupBy( + raw, + AllocationCyclesFieldsMapping.component, + ); + + _.forEach(groupedByComponent, (component, componentKey) => { + const values: number[] = []; + _.forEach(cycles, cycle => { + const cycleData = _.find(component, { + [AllocationCyclesFieldsMapping.cycle]: cycle, + }); + values.push( + _.get(cycleData, AllocationCyclesFieldsMapping.value, 0), + ); + }); + + data.push({ + name: componentKey, + values, + itemStyle: { + color: _.get( + AllocationCyclesFieldsMapping.colors, + data.length + 1, + ), + }, + }); + }); + + data.unshift({ + name: 'Total Allocation', + values: cycles.map((cycle, index) => + _.sumBy(data, `values[${index}]`), + ), + itemStyle: { + color: _.get(AllocationCyclesFieldsMapping.colors, 0), + }, + }); + + return { + keys: cycles, + data, + }; + }) + .catch(handleDataApiError); + } + + @get('/allocations/sunburst') + @response(200) + async allocationsSunburst() { + const filterString = filterFinancialIndicators( + this.req.query, + AllocationSunburstFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get( + resp.data, + AllocationSunburstFieldsMapping.dataPath, + [], + ); + + const groupedByRegion = _.groupBy( + raw, + AllocationSunburstFieldsMapping.region, + ); + + return { + data: _.map(groupedByRegion, (regionData, region) => { + return { + name: region, + value: _.sumBy(regionData, AllocationSunburstFieldsMapping.value), + children: _.map(regionData, item => ({ + name: _.get(item, AllocationSunburstFieldsMapping.country, ''), + value: _.get(item, AllocationSunburstFieldsMapping.value, 0), + })), + }; + }), + }; + }) + .catch(handleDataApiError); + } + + @get('/allocations/treemap') + @response(200) + async allocationsTreemap() { + let urlParams = AllocationTreemapFieldsMapping.urlParams[0]; + if (this.req.query.nestedField) { + if (this.req.query.nestedField === 'geography') { + urlParams = AllocationTreemapFieldsMapping.urlParams[1]; + } + } + const filterString = filterFinancialIndicators(this.req.query, urlParams); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get( + resp.data, + AllocationTreemapFieldsMapping.dataPath, + [], + ); + + const groupedByComponent = _.groupBy( + raw, + AllocationTreemapFieldsMapping.component, + ); + + return { + data: _.map(groupedByComponent, (componentData, component) => { + const color = _.get( + AllocationTreemapFieldsMapping.colors, + `[${_.findIndex( + Object.keys(groupedByComponent), + c => c === component, + )}]`, + AllocationTreemapFieldsMapping.colors[0], + ); + return { + name: component, + value: _.sumBy( + componentData, + AllocationTreemapFieldsMapping.value, + ), + itemStyle: { + color: color.bg, + }, + label: { + normal: { + color: color.text, + }, + }, + children: this.req.query.nestedField + ? _.map(componentData, item => ({ + name: _.get( + item, + AllocationTreemapFieldsMapping.geography, + '', + ), + value: _.get(item, AllocationTreemapFieldsMapping.value, 0), + itemStyle: { + color: color.items, + }, + label: { + normal: { + color: color.text, + }, + }, + })) + : undefined, + }; + }), + }; + }) + .catch(handleDataApiError); + } + + @get('/allocations/table') + @response(200) + async allocationsTable() { + const filterString = filterFinancialIndicators( + this.req.query, + AllocationTableFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, AllocationTableFieldsMapping.dataPath, []); + + const groupedByGeography = _.groupBy( + raw, + AllocationTableFieldsMapping.geography, + ); + + const data: { + [key: string]: + | string + | number + | boolean + | null + | object + | Array; + }[] = _.map(groupedByGeography, (value, key) => { + let item: { + [key: string]: + | string + | number + | boolean + | null + | object + | Array; + } = { + name: key, + _children: [], + }; + + const geoGroupedByYear = _.groupBy( + value, + AllocationTableFieldsMapping.cycle, + ); + + _.forEach(geoGroupedByYear, (value, key) => { + item = { + ...item, + [key]: _.sumBy(value, AllocationTableFieldsMapping.value), + }; + }); + + const groupedByComponent = _.groupBy( + value, + AllocationTableFieldsMapping.component, + ); + + item._children = _.map(groupedByComponent, (value, key) => { + const componentItem: { + [key: string]: + | string + | number + | boolean + | null + | object + | Array; + } = { + name: key, + }; + + const componentGroupedByYear = _.groupBy( + value, + AllocationTableFieldsMapping.cycle, + ); + + _.forEach(componentGroupedByYear, (value, key) => { + componentItem[key] = _.sumBy( + value, + AllocationTableFieldsMapping.value, + ); + }); + + return componentItem; + }); + + return item; + }); + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/allocations/radial') + @response(200) + async allocationsRadialChart() { + let filterString = filterFinancialIndicators( + this.req.query, + AllocationRadialFieldsMapping.urlParams, + ); + let filterString2 = filterFinancialIndicators( + this.req.query, + AllocationRadialFieldsMapping.countriesCountUrlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + const data = await getAllocationsData(url); + const countriesCount = await axios + .get(url2) + .then( + (resp: AxiosResponse) => + _.get(resp.data, AllocationRadialFieldsMapping.dataPath, []).length, + ) + .catch(() => 0); + + return {data: {chart: data, countries: countriesCount}}; + } + + @get('/allocations/radial/{countryCode}') + @response(200) + async allocationsRadialChartInLocation( + @param.path.string('countryCode') countryCode: string, + ) { + let filterString = AllocationRadialFieldsMapping.urlParams; + if (this.req.query.cycle && countryCode) { + filterString = filterString.replace( + '', + ` AND ${AllocationRadialFieldsMapping.cycle} eq '${this.req.query.cycle}' AND ${AllocationRadialFieldsMapping.countryCode} eq '${countryCode}'`, + ); + } else if (this.req.query.cycle && !countryCode) { + filterString = filterString.replace( + '', + ` AND ${AllocationRadialFieldsMapping.cycle} eq '${this.req.query.cycle}'`, + ); + } else if (!this.req.query.cycle && countryCode) { + filterString = filterString.replace( + '', + ` AND ${AllocationRadialFieldsMapping.countryCode} eq '${countryCode}'`, + ); + } else { + filterString = filterString.replace('', ''); + } + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return getAllocationsData(url); + } + + // v2 + @get('/allocations') @response(200, ALLOCATIONS_RESPONSE) allocations(): object { @@ -502,9 +890,9 @@ export class AllocationsController { .catch(handleDataApiError); } - @get('/allocations/table') + @get('/v2/allocations/table') @response(200, ALLOCATIONS_RESPONSE) - allocationsTable(): object { + allocationsTableV2(): object { const filterString = getFilterString(this.req.query); const params = querystring.stringify( {}, diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index ac77552..4fe9cc6 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -12,11 +13,19 @@ import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; +import BudgetsBreakdownFieldsMapping from '../config/mapping/budgets/breakdown.json'; +import BudgetsCyclesMapping from '../config/mapping/budgets/cycles.json'; import BudgetsFlowFieldsMapping from '../config/mapping/budgets/flow.json'; import BudgetsFlowDrilldownFieldsMapping from '../config/mapping/budgets/flowDrilldown.json'; import BudgetsGeomapFieldsMapping from '../config/mapping/budgets/geomap.json'; +import BudgetsMetricsFieldsMapping from '../config/mapping/budgets/metrics.json'; +import BudgetsRadialFieldsMapping from '../config/mapping/budgets/radial.json'; +import BudgetsSankeyFieldsMapping from '../config/mapping/budgets/sankey.json'; +import BudgetsTableFieldsMapping from '../config/mapping/budgets/table.json'; import BudgetsTimeCycleFieldsMapping from '../config/mapping/budgets/timeCycle.json'; +import BudgetsTreemapFieldsMapping from '../config/mapping/budgets/treemap.json'; import urls from '../config/urls/index.json'; +import {BudgetSankeyChartData} from '../interfaces/budgetSankey'; import {BudgetsFlowData} from '../interfaces/budgetsFlow'; import {BudgetsTimeCycleData} from '../interfaces/budgetsTimeCycle'; import {BudgetsTreemapDataItem} from '../interfaces/budgetsTreemap'; @@ -24,6 +33,7 @@ import staticCountries from '../static-assets/countries.json'; import {handleDataApiError} from '../utils/dataApiError'; import {getDrilldownFilterString} from '../utils/filtering/budgets/getDrilldownFilterString'; import {getFilterString} from '../utils/filtering/budgets/getFilterString'; +import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; import {formatFinancialValue} from '../utils/formatFinancialValue'; const BUDGETS_FLOW_RESPONSE: ResponseObject = { @@ -86,6 +96,707 @@ const BUDGETS_TIME_CYCLE_RESPONSE: ResponseObject = { export class BudgetsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/budgets/radial') + @response(200) + async radial() { + const filterString = filterFinancialIndicators( + this.req.query, + BudgetsRadialFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + return _.get(resp.data, BudgetsRadialFieldsMapping.dataPath, []).map( + (item: any, index: number) => ({ + name: _.get(item, BudgetsRadialFieldsMapping.name, ''), + value: _.get(item, BudgetsRadialFieldsMapping.value, 0), + itemStyle: { + color: _.get(BudgetsRadialFieldsMapping.colors, index, ''), + }, + }), + ); + }) + .catch(handleDataApiError); + } + + @get('/budgets/sankey') + @response(200) + async sankey() { + const filterString = filterFinancialIndicators( + this.req.query, + BudgetsSankeyFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + BudgetsSankeyFieldsMapping.dataPath, + [], + ); + const data: BudgetSankeyChartData = { + nodes: [ + { + name: 'Total budget', + level: 0, + itemStyle: { + color: BudgetsSankeyFieldsMapping.nodeColors[0], + }, + }, + ], + links: [], + }; + const groupedDataLevel1 = _.groupBy( + rawData, + BudgetsSankeyFieldsMapping.level1Field, + ); + _.forEach(groupedDataLevel1, (level1Data, level1) => { + data.nodes.push({ + name: level1, + level: 1, + itemStyle: { + color: BudgetsSankeyFieldsMapping.nodeColors[1], + }, + }); + data.links.push({ + source: data.nodes[0].name, + target: level1, + value: _.sumBy(level1Data, BudgetsSankeyFieldsMapping.valueField), + }); + const groupedDataLevel2 = _.groupBy( + level1Data, + BudgetsSankeyFieldsMapping.level2Field, + ); + _.forEach(groupedDataLevel2, (level2Data, level2) => { + const level2inLevel1 = _.find(data.nodes, { + name: level2, + level: 1, + }); + data.nodes.push({ + name: level2inLevel1 ? `${level2}1` : level2, + level: 2, + itemStyle: { + color: BudgetsSankeyFieldsMapping.nodeColors[2], + }, + }); + data.links.push({ + source: level1, + target: level2inLevel1 ? `${level2}1` : level2, + value: _.sumBy(level2Data, BudgetsSankeyFieldsMapping.valueField), + }); + }); + }); + data.nodes = _.uniqBy(data.nodes, 'name'); + data.nodes = _.orderBy( + data.nodes, + node => { + const links = _.filter(data.links, {target: node.name}); + return _.sumBy(links, 'value'); + }, + 'desc', + ); + data.links = _.orderBy(data.links, 'value', 'desc'); + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/budgets/treemap') + @response(200) + async treemap() { + const filterString = filterFinancialIndicators( + this.req.query, + BudgetsTreemapFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + return { + data: _.get(resp.data, BudgetsTreemapFieldsMapping.dataPath, []).map( + (item: any, index: number) => ({ + name: _.get(item, BudgetsTreemapFieldsMapping.name, ''), + value: _.get(item, BudgetsTreemapFieldsMapping.value, 0), + itemStyle: { + color: _.get( + BudgetsTreemapFieldsMapping.textbgcolors, + `[${index}].color`, + '', + ), + }, + label: { + normal: { + color: _.get( + BudgetsTreemapFieldsMapping.textbgcolors, + `[${index}].textcolor`, + '', + ), + }, + }, + }), + ), + }; + }) + .catch(handleDataApiError); + } + + @get('/budgets/table') + @response(200) + async table() { + const filterString = filterFinancialIndicators( + this.req.query, + BudgetsTableFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + BudgetsTableFieldsMapping.dataPath, + [], + ); + const groupedByParent = _.groupBy( + rawData, + BudgetsTableFieldsMapping.parentField, + ); + + const data: { + name: string; + grants: number; + amount: number; + _children: { + name: string; + grants: number; + amount: number; + }[]; + }[] = []; + + _.forEach(groupedByParent, (parentData, parent) => { + const children = parentData.map((child: any) => { + return { + name: _.get(child, BudgetsTableFieldsMapping.childrenField, ''), + grants: _.get(child, BudgetsTableFieldsMapping.countField, 0), + amount: _.get(child, BudgetsTableFieldsMapping.valueField, 0), + }; + }); + data.push({ + name: parent, + grants: _.sumBy(children, 'grants'), + amount: _.sumBy(children, 'amount'), + _children: children, + }); + }); + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/budgets/cycles') + @response(200) + async cycles() { + const url = `${urls.FINANCIAL_INDICATORS}${BudgetsCyclesMapping.urlParams}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + return _.get(resp.data, BudgetsCyclesMapping.dataPath, []).map( + (item: any) => + `${_.get(item, BudgetsCyclesMapping.from, '')}-${_.get( + item, + BudgetsCyclesMapping.to, + '', + )}`, + ); + }) + .catch(handleDataApiError); + } + + @get('/budgets/breakdown/{cycle}') + @response(200) + async breakdown(@param.path.string('cycle') cycle: string) { + const years = cycle.split('-'); + const filterString = filterFinancialIndicators( + { + ...this.req.query, + years: years[0], + yearsTo: years[1], + }, + BudgetsBreakdownFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get( + resp.data, + BudgetsBreakdownFieldsMapping.dataPath, + [], + ); + const total = _.sumBy(raw, BudgetsBreakdownFieldsMapping.value); + const data = raw.map((item: any) => ({ + name: _.get(item, BudgetsRadialFieldsMapping.name, ''), + value: + (_.get(item, BudgetsRadialFieldsMapping.value, 0) / total) * 100, + color: '', + })); + return { + data: _.orderBy(data, 'value', 'desc').map((item, index) => { + item.color = _.get(BudgetsBreakdownFieldsMapping.colors, index, ''); + return item; + }), + }; + }) + .catch(handleDataApiError); + } + + @get('/budgets/utilization') + @response(200) + async utilization() { + // (disbursement + cash balance) / budget + const filterString1 = filterFinancialIndicators( + this.req.query, + BudgetsMetricsFieldsMapping.urlParams, + ); + const filterString2 = filterFinancialIndicators( + this.req.query, + BudgetsMetricsFieldsMapping.urlParamsOrganisations, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + return axios + .all([axios.get(url1), axios.get(url2)]) + .then( + axios.spread((...responses) => { + const raw1 = _.get( + responses[0].data, + BudgetsMetricsFieldsMapping.dataPath, + [], + ); + const raw2 = _.get( + responses[1].data, + BudgetsMetricsFieldsMapping.dataPath, + [], + ); + const disbursement = _.find(raw1, { + [BudgetsMetricsFieldsMapping.indicatorNameField]: + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + }); + const cashBalance = _.find(raw1, { + [BudgetsMetricsFieldsMapping.indicatorNameField]: + BudgetsMetricsFieldsMapping.cashBalanceIndicatorName, + }); + const budget = _.find(raw1, { + [BudgetsMetricsFieldsMapping.indicatorNameField]: + BudgetsMetricsFieldsMapping.budgetIndicatorName, + }); + const disbursementValue = _.get( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + 0, + ); + const cashBalanceValue = _.get( + cashBalance, + BudgetsMetricsFieldsMapping.cashBalanceValue, + 0, + ); + const budgetValue = _.get( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + 0, + ); + const totalValue = disbursementValue + cashBalanceValue; + const utilization = (totalValue / budgetValue) * 100; + + const groupedByOrganisationType = _.groupBy( + raw2, + BudgetsMetricsFieldsMapping.organisationType, + ); + const items = _.map(groupedByOrganisationType, (value, key) => { + const disbursement = _.filter( + value, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + ); + const cashBalance = _.filter( + value, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.cashBalanceIndicatorName, + ); + const budget = _.filter( + value, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.budgetIndicatorName, + ); + const disbursementValue = _.sumBy( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + ); + const cashBalanceValue = _.sumBy( + cashBalance, + BudgetsMetricsFieldsMapping.cashBalanceValue, + ); + const budgetValue = _.sumBy( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + ); + const totalValue = disbursementValue + cashBalanceValue; + const utilization = (totalValue / budgetValue) * 100; + const groupedByOrganisations = _.groupBy( + value, + BudgetsMetricsFieldsMapping.organisationName, + ); + return { + level: 0, + name: key, + value: utilization, + color: '#013E77', + items: _.orderBy( + _.map(groupedByOrganisations, (value2, key2) => { + const disbursement = _.filter( + value2, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + ); + const cashBalance = _.filter( + value2, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.cashBalanceIndicatorName, + ); + const budget = _.filter( + value2, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.budgetIndicatorName, + ); + const disbursementValue = _.sumBy( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + ); + const cashBalanceValue = _.sumBy( + cashBalance, + BudgetsMetricsFieldsMapping.cashBalanceValue, + ); + const budgetValue = _.sumBy( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + ); + const totalValue = disbursementValue + cashBalanceValue; + const utilization = (totalValue / budgetValue) * 100; + return { + level: 1, + name: key2, + value: utilization, + color: '#013E77', + items: [], + }; + }), + 'name', + 'asc', + ), + }; + }); + + return { + data: [ + { + value: utilization, + items: _.orderBy(items, 'name', 'asc'), + }, + ], + }; + }), + ) + .catch(handleDataApiError); + } + + @get('/budgets/absorption') + @response(200) + async absorption() { + // expenditure / budget + const filterString1 = filterFinancialIndicators( + this.req.query, + BudgetsMetricsFieldsMapping.urlParams, + ); + const filterString2 = filterFinancialIndicators( + this.req.query, + BudgetsMetricsFieldsMapping.urlParamsOrganisations, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + return axios + .all([axios.get(url1), axios.get(url2)]) + .then( + axios.spread((...responses) => { + const raw1 = _.get( + responses[0].data, + BudgetsMetricsFieldsMapping.dataPath, + [], + ); + const raw2 = _.get( + responses[1].data, + BudgetsMetricsFieldsMapping.dataPath, + [], + ); + const expenditure = _.find(raw1, { + [BudgetsMetricsFieldsMapping.indicatorNameField]: + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + }); + const budget = _.find(raw1, { + [BudgetsMetricsFieldsMapping.indicatorNameField]: + BudgetsMetricsFieldsMapping.budgetIndicatorName, + }); + const expenditureValue = _.get( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + 0, + ); + const budgetValue = _.get( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + 0, + ); + const absorption = (expenditureValue / budgetValue) * 100; + + const groupedByOrganisationType = _.groupBy( + raw2, + BudgetsMetricsFieldsMapping.organisationType, + ); + const items = _.map(groupedByOrganisationType, (value, key) => { + const expenditure = _.filter( + value, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + ); + const budget = _.filter( + value, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.budgetIndicatorName, + ); + const expenditureValue = _.sumBy( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + ); + const budgetValue = _.sumBy( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + ); + const absorption = (expenditureValue / budgetValue) * 100; + const groupedByOrganisations = _.groupBy( + value, + BudgetsMetricsFieldsMapping.organisationName, + ); + return { + level: 0, + name: key, + value: absorption, + color: '#00B5AE', + items: _.orderBy( + _.map(groupedByOrganisations, (value2, key2) => { + const expenditure = _.filter( + value2, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + ); + const budget = _.filter( + value2, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.budgetIndicatorName, + ); + const expenditureValue = _.sumBy( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + ); + const budgetValue = _.sumBy( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + ); + const absorption = (expenditureValue / budgetValue) * 100; + return { + level: 1, + name: key2, + value: absorption, + color: '#00B5AE', + items: [], + }; + }), + 'name', + 'asc', + ), + }; + }); + + return { + data: [ + { + value: absorption, + items: _.orderBy(items, 'name', 'asc'), + }, + ], + }; + }), + ) + .catch(handleDataApiError); + } + + @get('/disbursements/utilization') + @response(200) + async disbursementsUtilization() { + // expenditure / disbursement + const filterString1 = filterFinancialIndicators( + this.req.query, + BudgetsMetricsFieldsMapping.urlParams, + ); + const filterString2 = filterFinancialIndicators( + this.req.query, + BudgetsMetricsFieldsMapping.urlParamsOrganisations, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + return axios + .all([axios.get(url1), axios.get(url2)]) + .then( + axios.spread((...responses) => { + const raw1 = _.get( + responses[0].data, + BudgetsMetricsFieldsMapping.dataPath, + [], + ); + const raw2 = _.get( + responses[1].data, + BudgetsMetricsFieldsMapping.dataPath, + [], + ); + const expenditure = _.find(raw1, { + [BudgetsMetricsFieldsMapping.indicatorNameField]: + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + }); + const disbursement = _.find(raw1, { + [BudgetsMetricsFieldsMapping.indicatorNameField]: + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + }); + const expenditureValue = _.get( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + 0, + ); + const disbursementValue = _.get( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + 0, + ); + const utilization = (expenditureValue / disbursementValue) * 100; + + const groupedByOrganisationType = _.groupBy( + raw2, + BudgetsMetricsFieldsMapping.organisationType, + ); + const items = _.map(groupedByOrganisationType, (value, key) => { + const expenditure = _.filter( + value, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + ); + const disbursement = _.filter( + value, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + ); + const expenditureValue = _.sumBy( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + ); + const disbursementValue = _.sumBy( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + ); + const utilization = (expenditureValue / disbursementValue) * 100; + const groupedByOrganisations = _.groupBy( + value, + BudgetsMetricsFieldsMapping.organisationName, + ); + return { + level: 0, + name: key, + value: utilization, + color: '#0A2840', + items: _.orderBy( + _.map(groupedByOrganisations, (value2, key2) => { + const expenditure = _.filter( + value2, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + ); + const disbursement = _.filter( + value2, + (item: any) => + item[BudgetsMetricsFieldsMapping.indicatorNameField] === + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + ); + const expenditureValue = _.sumBy( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + ); + const disbursementValue = _.sumBy( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + ); + const utilization = + (expenditureValue / disbursementValue) * 100; + return { + level: 1, + name: key2, + value: utilization, + color: '#0A2840', + items: [], + }; + }), + 'name', + 'asc', + ), + }; + }); + + return { + data: [ + { + value: utilization, + items: _.orderBy(items, 'name', 'asc'), + }, + ], + }; + }), + ) + .catch(handleDataApiError); + } + + // v2 + @get('/budgets/flow') @response(200, BUDGETS_FLOW_RESPONSE) flow(): object { diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index 62b1c29..77e4821 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -13,14 +13,18 @@ import _ from 'lodash'; import querystring from 'querystring'; import filteringGrants from '../config/filtering/grants.json'; import filtering from '../config/filtering/index.json'; +import BarChartFieldsMapping from '../config/mapping/disbursements/barChart.json'; import GeomapFieldsMapping from '../config/mapping/disbursements/geomap.json'; import GrantCommittedTimeCycleFieldsMapping from '../config/mapping/disbursements/grantCommittedTimeCycle.json'; import GrantDetailTimeCycleFieldsMapping from '../config/mapping/disbursements/grantDetailTimeCycle.json'; import GrantDetailTreemapFieldsMapping from '../config/mapping/disbursements/grantDetailTreemap.json'; +import LineChartFieldsMapping from '../config/mapping/disbursements/lineChart.json'; +import TableFieldsMapping from '../config/mapping/disbursements/table.json'; import TimeCycleFieldsMapping from '../config/mapping/disbursements/timeCycle.json'; import TimeCycleDrilldownFieldsMapping from '../config/mapping/disbursements/timeCycleDrilldown.json'; import TimeCycleDrilldownFieldsMapping2 from '../config/mapping/disbursements/timeCycleDrilldown2.json'; import TreemapFieldsMapping from '../config/mapping/disbursements/treemap.json'; +import FinancialInsightsStatsMapping from '../config/mapping/financialInsightsStats.json'; import urls from '../config/urls/index.json'; import {BudgetsTreemapDataItem} from '../interfaces/budgetsTreemap'; import {DisbursementsTreemapDataItem} from '../interfaces/disbursementsTreemap'; @@ -32,6 +36,7 @@ import { grantDetailTreemapGetFilterString, } from '../utils/filtering/disbursements/grantDetailGetFilterString'; import {getGeoMultiCountriesFilterString} from '../utils/filtering/disbursements/multicountries/getFilterString'; +import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; import {formatFinancialValue} from '../utils/formatFinancialValue'; const DISBURSEMENTS_TIME_CYCLE_RESPONSE: ResponseObject = { @@ -175,6 +180,181 @@ const DISBURSEMENTS_TREEMAP_RESPONSE: ResponseObject = { export class DisbursementsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/financial-insights/stats') + @response(200) + async financialInsightsStats() { + const filterString = filterFinancialIndicators( + this.req.query, + FinancialInsightsStatsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get( + resp.data, + FinancialInsightsStatsMapping.dataPath, + [], + ); + return { + data: [ + { + signed: _.get( + _.find(raw, { + [FinancialInsightsStatsMapping.indicatorField]: + FinancialInsightsStatsMapping.signed, + }), + FinancialInsightsStatsMapping.valueField, + 0, + ), + committed: _.get( + _.find(raw, { + [FinancialInsightsStatsMapping.indicatorField]: + FinancialInsightsStatsMapping.commitment, + }), + FinancialInsightsStatsMapping.valueField, + 0, + ), + disbursed: _.get( + _.find(raw, { + [FinancialInsightsStatsMapping.indicatorField]: + FinancialInsightsStatsMapping.disbursement, + }), + FinancialInsightsStatsMapping.valueField, + 0, + ), + }, + ], + }; + }) + .catch(handleDataApiError); + } + + @get('/disbursements/bar-chart') + @response(200) + async barChart() { + const filterString = filterFinancialIndicators( + this.req.query, + BarChartFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, BarChartFieldsMapping.dataPath, []); + + return { + data: raw.map((item: any, index: number) => ({ + name: _.get(item, BarChartFieldsMapping.name, ''), + value: _.get(item, BarChartFieldsMapping.value, 0), + itemStyle: { + color: + index === 0 + ? BarChartFieldsMapping.biggerBarColor + : BarChartFieldsMapping.barColor, + }, + })), + }; + }) + .catch(handleDataApiError); + } + + @get('/disbursements/line-chart') + @response(200) + async lineChart() { + const filterString = filterFinancialIndicators( + this.req.query, + LineChartFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, LineChartFieldsMapping.dataPath, []); + const groupedByLine = _.groupBy(raw, LineChartFieldsMapping.line); + const lines = Object.keys(groupedByLine); + const years = _.groupBy(raw, LineChartFieldsMapping.cycle); + return { + data: _.map(groupedByLine, (items, line) => ({ + name: line, + data: _.orderBy( + items.map((item: any) => item[LineChartFieldsMapping.value]), + 'x', + 'asc', + ), + itemStyle: { + color: _.get( + LineChartFieldsMapping.colors, + `[${lines.indexOf(line)}]`, + '', + ), + }, + })), + xAxisKeys: Object.keys(years), + }; + }) + .catch(handleDataApiError); + } + + @get('/disbursements/table') + @response(200) + async table() { + const filterString = filterFinancialIndicators( + this.req.query, + TableFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, TableFieldsMapping.dataPath, []); + + const groupedByComponent = _.groupBy(raw, TableFieldsMapping.component); + + return { + data: _.map(groupedByComponent, (items, component) => { + return { + component, + grants: _.get(items[0], TableFieldsMapping.grants, 0), + signed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.signedIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + committed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.commitmentIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + disbursed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.disbursementIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + }; + }), + }; + }) + .catch(handleDataApiError); + } + + // v2 + // Time/Cycle @get('/disbursements/time-cycle') diff --git a/src/controllers/documents.controller.ts b/src/controllers/documents.controller.ts index e3f791f..e58f598 100644 --- a/src/controllers/documents.controller.ts +++ b/src/controllers/documents.controller.ts @@ -10,10 +10,12 @@ import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import {mapTransform} from 'map-transform'; import docsMap from '../config/mapping/documents/index.json'; +import DocumentsListMapping from '../config/mapping/documents/list.json'; import docsUtils from '../config/mapping/documents/utils.json'; import urls from '../config/urls/index.json'; import {DocumentsTableRow} from '../interfaces/documentsTable'; import {handleDataApiError} from '../utils/dataApiError'; +import {filterDocuments} from '../utils/filtering/documents'; import {getFilterString} from '../utils/filtering/documents/getFilterString'; const RESULTS_RESPONSE: ResponseObject = { @@ -56,9 +58,54 @@ const RESULTS_RESPONSE: ResponseObject = { export class DocumentsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + @get('/documents') + @response(200) + async documents() { + const filterString = filterDocuments( + this.req.query, + DocumentsListMapping.urlParams, + ); + const url = `${urls.DOCUMENTS}${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, DocumentsListMapping.dataPath, []); + + const groupedByLocation = _.groupBy( + raw, + DocumentsListMapping.geography, + ); + + return { + data: _.map(groupedByLocation, (value, key) => { + const groupedByType = _.groupBy(value, DocumentsListMapping.type); + + return { + name: key, + documents: Object.keys(value).length, + _children: _.map(groupedByType, (value1, key) => ({ + name: key, + documents: Object.keys(value1).length, + _children: _.map(value1, value2 => ({ + name: _.get(value2, DocumentsListMapping.title, ''), + documents: _.get(value2, DocumentsListMapping.url, ''), + })), + })), + }; + }), + }; + }) + .catch(handleDataApiError); + } + + // v2 + + @get('/v2/documents') @response(200, RESULTS_RESPONSE) - documents(): object { + documentsV2(): object { const mapper = mapTransform(docsMap); const filterString = getFilterString( this.req.query, diff --git a/src/controllers/eligibility.controller.ts b/src/controllers/eligibility.controller.ts index 9d841ba..0c95ac6 100644 --- a/src/controllers/eligibility.controller.ts +++ b/src/controllers/eligibility.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -11,12 +12,16 @@ import _ from 'lodash'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; import EligibilityFieldsMapping from '../config/mapping/eligibility/dotsChart.json'; +import EligibilityHeatmap from '../config/mapping/eligibility/heatmap.json'; import ScatterplotFieldsMapping from '../config/mapping/eligibility/scatterplot.json'; +import EligibilityStatsMapping from '../config/mapping/eligibility/stats.json'; +import EligibilityTableMapping from '../config/mapping/eligibility/table.json'; import EligibilityYearsFieldsMapping from '../config/mapping/eligibility/years.json'; import urls from '../config/urls/index.json'; import {EligibilityDotDataItem} from '../interfaces/eligibilityDot'; import {EligibilityScatterplotDataItem} from '../interfaces/eligibilityScatterplot'; import {handleDataApiError} from '../utils/dataApiError'; +import {filterEligibility} from '../utils/filtering/eligibility'; import {getFilterString} from '../utils/filtering/eligibility/getFilterString'; const ELIGIBILITY_RESPONSE: ResponseObject = { @@ -92,6 +97,229 @@ const ELIGIBILITY_COUNTRY_RESPONSE: ResponseObject = { export class EligibilityController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/eligibility/stats/{year}') + @response(200) + async eligibilityStats(@param.path.string('year') year: string) { + const filterString = filterEligibility( + {...this.req.query, years: year}, + EligibilityStatsMapping.urlParams, + ); + const url = `${urls.ELIGIBILITY}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, EligibilityStatsMapping.dataPath, []); + const groupedByComponent = _.groupBy( + raw, + EligibilityStatsMapping.component, + ); + return { + data: _.map(groupedByComponent, (value, key) => ({ + name: key, + value: value.length, + })), + }; + }) + .catch(handleDataApiError); + } + + @get('/eligibility/table') + @response(200) + async eligibilityTable() { + const filterString = filterEligibility( + this.req.query, + EligibilityTableMapping.urlParams, + ); + const url = `${urls.ELIGIBILITY}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, EligibilityTableMapping.dataPath, []); + + const groupedByGeography = _.groupBy( + raw, + EligibilityTableMapping.geography, + ); + + const data: { + [key: string]: + | string + | number + | boolean + | null + | object + | Array; + }[] = _.map(groupedByGeography, (value, key) => { + const item: { + [key: string]: + | string + | number + | boolean + | null + | object + | Array; + } = { + name: key, + _children: [ + { + name: 'Income Level', + }, + ], + }; + + const geoGroupedByYear = _.groupBy( + value, + EligibilityTableMapping.year, + ); + + _.forEach(geoGroupedByYear, (value, key) => { + (item._children as object[])[0] = { + ...(item._children as object[])[0], + [key]: _.get(value, '[0].incomeLevel', ''), + }; + }); + + const groupedByComponent = _.groupBy( + value, + EligibilityTableMapping.component, + ); + + item._children = _.map(groupedByComponent, (value, key) => { + const componentItem: { + [key: string]: + | string + | number + | boolean + | null + | object + | Array; + } = { + name: key, + _children: [ + { + name: 'Disease Burden', + }, + { + name: 'Eligibility', + }, + ], + }; + + const componentGroupedByYear = _.groupBy( + value, + EligibilityTableMapping.year, + ); + + _.forEach(componentGroupedByYear, (value, key) => { + let isEligible = _.get( + value, + `[0]["${EligibilityTableMapping.isEligible}"]`, + '', + ); + if (isEligible) { + isEligible = EligibilityTableMapping.eligibilityValues.eligible; + } else if (isEligible === false) { + isEligible = + EligibilityTableMapping.eligibilityValues.notEligible; + } else { + isEligible = + EligibilityTableMapping.eligibilityValues.transitionFunding; + } + (componentItem._children as object[])[0] = { + ...(componentItem._children as object[])[0], + [key]: _.get( + value, + `[0]["${EligibilityTableMapping.diseaseBurden}"]`, + '', + ), + }; + (componentItem._children as object[])[1] = { + ...(componentItem._children as object[])[1], + [key]: isEligible, + }; + }); + + return componentItem; + }); + + return item; + }); + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/eligibility/heatmap/{countryCode}') + @response(200) + async eligibilityHeatmap( + @param.path.string('countryCode') countryCode: string, + ) { + let filterString = EligibilityHeatmap.urlParams.replace( + '', + countryCode, + ); + const url = `${urls.ELIGIBILITY}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, EligibilityHeatmap.dataPath, []); + const data = raw.map((item: any) => ({ + column: _.get(item, EligibilityHeatmap.eligibilityYear, ''), + row: _.get(item, EligibilityHeatmap.component, ''), + value: _.get( + EligibilityHeatmap.isEligibleValueMapping, + _.get(item, EligibilityHeatmap.isEligible, '').toString(), + '', + ), + diseaseBurden: _.get( + EligibilityHeatmap.diseaseBurdenValueMapping, + _.get(item, EligibilityHeatmap.diseaseBurden, ''), + '', + ), + })); + + const groupedByYears = _.groupBy( + raw, + EligibilityHeatmap.eligibilityYear, + ); + Object.keys(groupedByYears).forEach(year => { + data.push({ + column: year, + row: '_Income Level', + value: '', + diseaseBurden: _.get( + EligibilityHeatmap.incomeLevelValueMapping, + _.get( + groupedByYears[year][0], + EligibilityHeatmap.incomeLevel, + '', + ), + '', + ), + }); + }); + + return { + data: _.orderBy(data, ['column', 'row'], ['desc', 'asc']).map( + (item: any) => ({ + ...item, + column: item.column.toString(), + row: item.row === '_Income Level' ? 'Income Level' : item.row, + }), + ), + }; + }) + .catch(handleDataApiError); + } + + // v2 + @get('/eligibility') @response(200, ELIGIBILITY_RESPONSE) eligibility(): object { @@ -428,9 +656,9 @@ export class EligibilityController { .catch(handleDataApiError); } - @get('/eligibility/table') + @get('/v2/eligibility/table') @response(200) - eligibilityTable(): object { + eligibilityTableV2(): object { const aggregateByField = this.req.query.aggregateBy && this.req.query.aggregateBy.toString().length > 0 diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts new file mode 100644 index 0000000..8c531ab --- /dev/null +++ b/src/controllers/expenditures.controller.ts @@ -0,0 +1,315 @@ +import {inject} from '@loopback/core'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; +import axios, {AxiosResponse} from 'axios'; +import _ from 'lodash'; +import ExpendituresBarChartMapping from '../config/mapping/expenditures/bar.json'; +import ExpendituresHeatmapMapping from '../config/mapping/expenditures/heatmap.json'; +import ExpendituresTableMapping from '../config/mapping/expenditures/table.json'; +import urls from '../config/urls/index.json'; +import {handleDataApiError} from '../utils/dataApiError'; +import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; + +export class ExpendituresController { + constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + + @get('/expenditures/heatmap/{row}/{column}') + @response(200) + async heatmap( + @param.path.string('row') row: string, + @param.path.string('column') column: string, + ) { + let filterString = ExpendituresHeatmapMapping.urlParams; + let rowField = ''; + let subRowField = ''; + let columnField = ''; + let subColumnField = ''; + if (row.split(',').length > 1) { + rowField = _.get( + ExpendituresHeatmapMapping, + `fields["${row.split(',')[0]}"]`, + ExpendituresHeatmapMapping.fields.principalRecipientType, + ); + subRowField = _.get( + ExpendituresHeatmapMapping, + `fields["${row.split(',')[1]}"]`, + ExpendituresHeatmapMapping.fields.principalRecipient, + ); + } else { + rowField = _.get( + ExpendituresHeatmapMapping, + `fields["${row}"]`, + ExpendituresHeatmapMapping.fields.principalRecipientType, + ); + } + if (column.split(',').length > 1) { + columnField = _.get( + ExpendituresHeatmapMapping, + `fields["${column.split(',')[0]}"]`, + ExpendituresHeatmapMapping.fields.principalRecipientType, + ); + subColumnField = _.get( + ExpendituresHeatmapMapping, + `fields["${column.split(',')[1]}"]`, + ExpendituresHeatmapMapping.fields.principalRecipient, + ); + } else { + columnField = _.get( + ExpendituresHeatmapMapping, + `fields["${column}"]`, + ExpendituresHeatmapMapping.fields.component, + ); + } + filterString = filterString.replace( + '', + [rowField, subRowField].join(',').replace(/(^,)|(,$)/g, ''), + ); + filterString = filterString.replace( + '', + [columnField, subColumnField].join(',').replace(/(^,)|(,$)/g, ''), + ); + filterString = filterFinancialIndicators(this.req.query, filterString); + + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + rowField = rowField.replace(/\//g, '.'); + subRowField = subRowField.replace(/\//g, '.'); + columnField = columnField.replace(/\//g, '.'); + subColumnField = subColumnField.replace(/\//g, '.'); + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, ExpendituresHeatmapMapping.dataPath, []); + const data = _.filter( + raw.map((item: any) => { + const budget = _.get(item, ExpendituresHeatmapMapping.budget, 0); + const expenditure = _.get( + item, + ExpendituresHeatmapMapping.expenditure, + 0, + ); + return { + row: subRowField + ? _.get(item, subRowField, '') + : _.get(item, rowField), + parentRow: subRowField ? _.get(item, rowField) : undefined, + column: subColumnField + ? _.get(item, subColumnField, '') + : _.get(item, columnField), + parentColumn: subColumnField + ? _.get(item, columnField) + : undefined, + value: expenditure, + budget, + percentage: + budget && budget > 0 + ? Math.round((expenditure / budget) * 100) + : 120, + }; + }), + (item: any) => item.row && item.column, + ); + + const rows = _.uniq(data.map((item: any) => item.row)); + const columns = _.uniq(data.map((item: any) => item.column)); + const parentRows = _.uniq( + _.filter( + data.map((item: any) => item.parentRow), + (item: any) => item, + ), + ); + const parentColumns = _.uniq( + _.filter( + data.map((item: any) => item.parentColumn), + (item: any) => item, + ), + ); + + if (parentRows.length > 0) { + parentRows.forEach((parentRow: any) => { + columns.forEach((column: any) => { + const expenditure = _.sumBy( + data.filter( + (item: any) => + item.parentRow === parentRow && item.column === column, + ), + 'value', + ); + const budget = _.sumBy( + data.filter( + (item: any) => + item.parentRow === parentRow && item.column === column, + ), + 'budget', + ); + data.push({ + row: parentRow, + column, + value: expenditure, + budget, + percentage: + budget && budget > 0 + ? Math.round((expenditure / budget) * 100) + : 120, + }); + }); + }); + } + + if (parentColumns.length > 0) { + console.log(1.1, parentColumns.length); + parentColumns.forEach((parentColumn: any) => { + rows.forEach((row: any) => { + const expenditure = _.sumBy( + data.filter( + (item: any) => + item.parentColumn === parentColumn && item.row === row, + ), + 'value', + ); + const budget = _.sumBy( + data.filter( + (item: any) => + item.parentColumn === parentColumn && item.row === row, + ), + 'budget', + ); + data.push({ + row, + column: parentColumn, + value: expenditure, + budget, + percentage: + budget && budget > 0 + ? Math.round((expenditure / budget) * 100) + : 120, + }); + }); + }); + } + + if (parentRows.length > 0 && parentColumns.length > 0) { + console.log(1.2, parentRows.length, parentColumns.length); + parentRows.forEach((parentRow: any) => { + parentColumns.forEach((parentColumn: any) => { + const expenditure = _.sumBy( + data.filter( + (item: any) => + item.parentRow === parentRow && + item.parentColumn === parentColumn, + ), + 'value', + ); + const budget = _.sumBy( + data.filter( + (item: any) => + item.parentRow === parentRow && + item.parentColumn === parentColumn, + ), + 'budget', + ); + data.push({ + row: parentRow, + column: parentColumn, + value: expenditure, + budget, + percentage: + budget && budget > 0 + ? Math.round((expenditure / budget) * 100) + : 120, + }); + }); + }); + } + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/expenditures/expandable-bar') + @response(200) + async expandableBar() { + const filterString = filterFinancialIndicators( + this.req.query, + ExpendituresBarChartMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, ExpendituresBarChartMapping.dataPath, []); + + const groupedByName = _.groupBy(raw, ExpendituresBarChartMapping.name); + + const data = _.map(groupedByName, (value, key) => { + const groupedByItem = _.groupBy( + value, + ExpendituresBarChartMapping.itemName, + ); + return { + name: key, + value: _.sumBy(value, ExpendituresBarChartMapping.value), + items: _.map(groupedByItem, (subValue, subKey) => ({ + name: subKey, + value: _.sumBy(subValue, ExpendituresBarChartMapping.value), + })), + }; + }); + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/expenditures/table') + @response(200) + async table() { + const filterString = filterFinancialIndicators( + this.req.query, + ExpendituresTableMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, ExpendituresTableMapping.dataPath, []); + + const groupedByName = _.groupBy(raw, ExpendituresTableMapping.name); + + const data = _.map(groupedByName, (value, key) => { + const groupedByItem = _.groupBy( + value, + ExpendituresTableMapping.itemName, + ); + return { + name: key, + cumulativeExpenditure: _.sumBy( + value, + ExpendituresTableMapping.cumulativeExpenditureValue, + ), + periodExpenditure: _.sumBy( + value, + ExpendituresTableMapping.periodExpenditureValue, + ), + _children: _.map(groupedByItem, (subValue, subKey) => ({ + name: subKey, + cumulativeExpenditure: _.sumBy( + subValue, + ExpendituresTableMapping.cumulativeExpenditureValue, + ), + periodExpenditure: _.sumBy( + subValue, + ExpendituresTableMapping.periodExpenditureValue, + ), + })), + }; + }); + + return {data}; + }) + .catch(handleDataApiError); + } +} diff --git a/src/controllers/filteroptions.controller.ts b/src/controllers/filteroptions.controller.ts index c9c2937..aff991b 100644 --- a/src/controllers/filteroptions.controller.ts +++ b/src/controllers/filteroptions.controller.ts @@ -8,6 +8,10 @@ import { } from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; +import ComponentMapping from '../config/mapping/filter-options/components.json'; +import DonorMapping from '../config/mapping/filter-options/donors.json'; +import GeographyMapping from '../config/mapping/filter-options/geography.json'; +import ReplenishmentPeriodMapping from '../config/mapping/filter-options/replenishment-periods.json'; import mappingComponents from '../config/mapping/filteroptions/components.json'; import mappingDonors from '../config/mapping/filteroptions/donors.json'; import mappingLocations from '../config/mapping/filteroptions/locations.json'; @@ -71,7 +75,256 @@ const FILTER_OPTIONS_RESPONSE: ResponseObject = { export class FilteroptionsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - @get('/filter-options/locations') + // v3 + + @get('/filter-options/geography') + @response(200) + async filterOptionsGeography() { + const url = urls.FILTER_OPTIONS_GEOGRAPHY; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get(resp.data, GeographyMapping.dataPath, []); + const data: FilterGroupOption[] = []; + + rawData.forEach((item1: any) => { + const subOptions = _.get(item1, GeographyMapping.children, []); + data.push({ + label: _.get(item1, GeographyMapping.label, ''), + value: _.get(item1, GeographyMapping.value, ''), + extraInfo: { + isDonor: _.get(item1, GeographyMapping.isDonor, false), + isRecipient: _.get(item1, GeographyMapping.isRecipient, false), + level: _.get(item1, GeographyMapping.level, undefined), + }, + subOptions: + subOptions && subOptions.length > 0 + ? _.orderBy( + subOptions.map((item2: any) => { + const item2SubOptions = _.get( + item2, + GeographyMapping.children, + [], + ); + return { + label: _.get(item2, GeographyMapping.label, ''), + value: _.get(item2, GeographyMapping.value, ''), + extraInfo: { + isDonor: _.get( + item2, + GeographyMapping.isDonor, + false, + ), + isRecipient: _.get( + item2, + GeographyMapping.isRecipient, + false, + ), + level: _.get( + item2, + GeographyMapping.level, + undefined, + ), + }, + subOptions: + item2SubOptions && item2SubOptions.length > 0 + ? _.orderBy( + item2SubOptions.map((item3: any) => { + const item3SubOptions = _.get( + item3, + GeographyMapping.children, + [], + ); + return { + label: _.get( + item3, + GeographyMapping.label, + '', + ), + value: _.get( + item3, + GeographyMapping.value, + '', + ), + extraInfo: { + isDonor: _.get( + item3, + GeographyMapping.isDonor, + false, + ), + isRecipient: _.get( + item3, + GeographyMapping.isRecipient, + false, + ), + level: _.get( + item3, + GeographyMapping.level, + undefined, + ), + }, + subOptions: + item3SubOptions && + item3SubOptions.length > 0 + ? _.orderBy( + item3SubOptions.map( + (item4: any) => { + return { + label: _.get( + item4, + GeographyMapping.label, + '', + ), + value: _.get( + item4, + GeographyMapping.value, + '', + ), + extraInfo: { + isDonor: _.get( + item4, + GeographyMapping.isDonor, + false, + ), + isRecipient: _.get( + item4, + GeographyMapping.isRecipient, + false, + ), + level: _.get( + item4, + GeographyMapping.level, + undefined, + ), + }, + }; + }, + ), + 'label', + 'asc', + ) + : undefined, + }; + }), + 'label', + 'asc', + ) + : undefined, + }; + }), + 'label', + 'asc', + ) + : undefined, + }); + }); + + return { + name: 'Geography', + options: _.orderBy(data, 'label', 'asc'), + }; + }) + .catch(handleDataApiError); + } + + @get('/filter-options/components') + @response(200) + async filterOptionsComponents() { + const url = urls.FILTER_OPTIONS_COMPONENTS; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get(resp.data, ComponentMapping.dataPath, []); + + return { + name: 'Component', + options: rawData.map((item: any) => ({ + label: _.get(item, ComponentMapping.label, ''), + value: _.get(item, ComponentMapping.value, ''), + })), + }; + }) + .catch(handleDataApiError); + } + + @get('/filter-options/replenishment-periods') + @response(200) + async filterOptionsReplenishmentPeriods() { + const url = urls.FILTER_OPTIONS_REPLENISHMENT_PERIODS; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + ReplenishmentPeriodMapping.dataPath, + [], + ); + + return { + name: 'Replenishment period', + options: _.orderBy( + rawData.map((item: any) => ({ + label: _.get(item, ReplenishmentPeriodMapping.label, ''), + value: _.get(item, ReplenishmentPeriodMapping.value, ''), + })), + 'label', + 'asc', + ), + }; + }) + .catch(handleDataApiError); + } + + @get('/filter-options/donors') + @response(200) + async filterOptionsDonors() { + const url = urls.FILTER_OPTIONS_DONORS; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get(resp.data, DonorMapping.dataPath, []); + + const options: FilterGroupOption[] = []; + + _.map(_.groupBy(rawData, DonorMapping.type), (donors, type) => { + const typeOptions: FilterGroupOption = { + label: type, + value: type, + subOptions: donors.map((donor: any) => ({ + label: donor[DonorMapping.label], + value: donor[DonorMapping.value], + })), + }; + + options.push(typeOptions); + }); + + return { + name: 'Donor', + options: _.orderBy(options, 'label', 'asc'), + }; + }) + .catch(handleDataApiError); + } + + @get('/filter-options/principal-recipients') + @response(200) + async filterOptionsPRs() { + const url = urls.FILTER_OPTIONS_PRINCIPAL_RECIPIENTS; + + return axios + .get(url) + .then((resp: AxiosResponse) => {}) + .catch(handleDataApiError); + } + + // v2 + + @get('/v2/filter-options/locations') @response(200, FILTER_OPTIONS_RESPONSE) locations(): object { const url = urls.filteroptionslocations; @@ -212,7 +465,7 @@ export class FilteroptionsController { .catch(handleDataApiError); } - @get('/filter-options/components') + @get('/v2/filter-options/components') @response(200, FILTER_OPTIONS_RESPONSE) components(): object { const url = urls.filteroptionscomponents; @@ -328,7 +581,7 @@ export class FilteroptionsController { .catch(handleDataApiError); } - @get('/filter-options/replenishment-periods') + @get('/v2/filter-options/replenishment-periods') @response(200, FILTER_OPTIONS_RESPONSE) replenishmentPeriods(): object { const url = urls.filteroptionsreplenishmentperiods; @@ -357,7 +610,7 @@ export class FilteroptionsController { .catch(handleDataApiError); } - @get('/filter-options/donors') + @get('/v2/filter-options/donors') @response(200, FILTER_OPTIONS_RESPONSE) donors(): object { const keyword = (this.req.query.q ?? '').toString().trim(); diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 8a266dc..e95a73f 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -1,19 +1,96 @@ import {inject} from '@loopback/core'; -import {get, Request, response, RestBindings} from '@loopback/rest'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import {mapTransform} from 'map-transform'; import moment from 'moment'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; +import Table2FieldsMapping from '../config/mapping/fundingrequests/table-2.json'; import TableFieldsMapping from '../config/mapping/fundingrequests/table.json'; import urls from '../config/urls/index.json'; import {handleDataApiError} from '../utils/dataApiError'; +import {filterFundingRequests} from '../utils/filtering/fundingRequests'; import {getFilterString} from '../utils/filtering/fundingrequests/getFilterString'; export class FundingRequestsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/funding-requests') + @response(200) + async fundingRequests() { + const filterString = filterFundingRequests( + this.req.query, + Table2FieldsMapping.urlParams, + ); + const url = `${urls.FUNDING_REQUESTS}/${filterString}&$orderby=geography/name asc`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const mapper = mapTransform(Table2FieldsMapping.map); + const data = mapper(resp.data) as never[]; + + const groupedByGeo = _.groupBy(data, Table2FieldsMapping.groupby); + + return { + data: _.map(groupedByGeo, (items, key) => { + return { + components: key, + _children: items.map((item: any) => { + return { + components: item.components, + submissionDate: moment(item.submissionDate).format( + 'D MMMM YYYY', + ), + approach: item.approach, + trpWindow: item.trpWindow, + trpOutcome: item.trpOutcome, + portfolioCategorization: item.portfolioCategorization, + _children: item.items.map((subitem: any) => { + return { + boardApproval: subitem.boardApproval, + gacMeeting: moment(item.gacMeeting).format('MMMM YYYY'), + grant: subitem.grant.code, + startingDate: moment(subitem.startDate).format( + 'DD-MM-YYYY', + ), + endingDate: moment(subitem.endDate).format('DD-MM-YYYY'), + principalRecipient: subitem.principalRecipient, + }; + }), + }; + }), + }; + }), + submittedCount: _.map(groupedByGeo, (items, key) => ({ + name: key, + count: items.length, + })), + signedCount: _.map(groupedByGeo, (items, key) => ({ + name: key, + count: _.filter(items, (item: any) => item.items.length > 0).length, + })), + }; + }) + .catch(handleDataApiError); + } + + @get('/funding-requests/{countryCode}') + @response(200) + async fundingRequestsByCountry( + @param.path.string('countryCode') countryCode: string, + ) { + return axios + .get(`http://localhost:4200/funding-requests?geographies=${countryCode}`) + .then((resp: AxiosResponse) => resp.data) + .catch(handleDataApiError); + } + + // v2 + @get('/funding-requests/table') @response(200) fundingRequestsLocationTable(): object { diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index b025ed2..c1a641f 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -9,16 +10,21 @@ import { import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import {mapTransform} from 'map-transform'; +import moment from 'moment'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; import {getPage} from '../config/filtering/utils'; +import GrantMapping from '../config/mapping/grants/grant.json'; import grantDetailMap from '../config/mapping/grants/grantDetail.json'; import grantDetailUtils from '../config/mapping/grants/grantDetail.utils.json'; +import GrantImplementationMapping from '../config/mapping/grants/grantImplementation.json'; +import GrantOverviewMapping from '../config/mapping/grants/grantOverview.json'; import grantPeriodGoalsObjectivesMap from '../config/mapping/grants/grantPeriodGoalsObjectives.json'; import grantPeriodInfoMap from '../config/mapping/grants/grantPeriodInfo.json'; import grantPeriodsMap from '../config/mapping/grants/grantPeriods.json'; import GrantsRadialMapping from '../config/mapping/grants/grantsRadial.json'; import grantsMap from '../config/mapping/grants/index.json'; +import GrantsListMapping from '../config/mapping/grants/list.json'; import grantsUtils from '../config/mapping/grants/utils.json'; import urls from '../config/urls/index.json'; import { @@ -26,8 +32,12 @@ import { GrantDetailPeriod, GrantDetailPeriodInformation, } from '../interfaces/grantDetail'; -import {GrantListItemModel} from '../interfaces/grantList'; +import { + GrantListItemModel, + GrantListItemModelV2, +} from '../interfaces/grantList'; import {handleDataApiError} from '../utils/dataApiError'; +import {filterGrants} from '../utils/filtering/grants'; import {getFilterString} from '../utils/filtering/grants/getFilterString'; import {getFilterString as getFilterStringPF} from '../utils/filtering/performancerating/getFilterString'; @@ -66,9 +76,521 @@ const GRANTS_RESPONSE: ResponseObject = { export class GrantsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/grants/{page}/{pageSize}') + @response(200) + async grants( + @param.path.string('page') page: string, + @param.path.string('pageSize') pageSize: string, + ) { + const mapper = mapTransform(GrantsListMapping.map); + const params = querystring.stringify( + { + ...getPage(filtering.page, parseInt(page, 10), parseInt(pageSize, 10)), + [filtering.page_size]: pageSize, + }, + '&', + filtering.param_assign_operator, + { + encodeURIComponent: (str: string) => str, + }, + ); + const filterString = filterGrants( + this.req.query, + GrantsListMapping.urlParams, + ); + const url = `${urls.GRANTS}${filterString}&${params}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const res: GrantListItemModel[] = mapper(resp.data) as never[]; + res.forEach(item => { + item.percentage = Math.round((item.disbursed / item.committed) * 100); + }); + return { + count: _.get(resp.data, GrantsListMapping.count, 0), + data: res, + }; + }) + .catch(handleDataApiError); + } + + @get('/grant/{id}') + @response(200) + async grant(@param.path.string('id') id: string) { + const url = `${urls.GRANTS}/${GrantMapping.urlParams.replace( + '', + id, + )}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, GrantMapping.dataPath, {}); + return { + data: [ + { + code: _.get(raw, GrantMapping.code, ''), + title: _.get( + _.get(raw, GrantMapping.titleArray, []), + `[0].${GrantMapping.title.text}`, + '', + ), + periods: _.orderBy( + _.get(raw, GrantMapping.implementationPeriodArray, []), + [GrantMapping.implementationPeriodFrom], + ['asc'], + ).map((p, index: number) => ({ + code: index + 1, + name: `Implementation Period ${index + 1}`, + title: _.get(p, GrantMapping.implementationPeriodTitle, ''), + })), + countryName: _.get(raw, GrantMapping.countryName, ''), + countryCode: _.get(raw, GrantMapping.countryCode, ''), + principalRecipientId: _.get( + raw, + GrantMapping.principalRecipientId, + '', + ), + principalRecipientName: _.get( + raw, + GrantMapping.principalRecipientName, + '', + ), + principalRecipientShortName: _.get( + raw, + GrantMapping.principalRecipientShortName, + '', + ), + component: _.get(raw, GrantMapping.component, ''), + FPMName: [ + _.get(raw, GrantMapping.FPMSalutation, ''), + _.get(raw, GrantMapping.FPMFirstName, ''), + _.get(raw, GrantMapping.FPMMiddleName, ''), + _.get(raw, GrantMapping.FPMLastName, ''), + ] + .join(' ') + .trim() + .replace(/ /g, ' '), + FPMEmail: _.get(raw, GrantMapping.FPMEmail, ''), + }, + ], + }; + }) + .catch(handleDataApiError); + } + + @get('grant/{id}/{ip}/overview') + @response(200) + async grantOverview( + @param.path.string('id') id: string, + @param.path.string('ip') ip: number, + ) { + const url = `${urls.GRANTS}/${GrantOverviewMapping.urlParams.replace( + '', + id, + )}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, GrantOverviewMapping.dataPath, {}); + const periods = _.orderBy( + _.get(raw, GrantOverviewMapping.implementationPeriodArray, []), + [GrantOverviewMapping.implementationPeriodFrom], + ['asc'], + ); + const period = periods[ip - 1]; + const data: { + status: string; + goals: string[]; + objectives: string[]; + disbursement: number; + commitment: number; + signed: number; + boardApprovedDate: string; + dates: string[]; + } = { + status: '', + goals: [], + objectives: [], + disbursement: 0, + commitment: 0, + signed: 0, + boardApprovedDate: '', + dates: ['', ''], + }; + if (period) { + const narratives = _.orderBy( + _.get( + period, + GrantOverviewMapping.implementationPeriod.narratives, + [], + ), + [ + GrantOverviewMapping.implementationPeriod.narrative.index, + GrantOverviewMapping.implementationPeriod.narrative.text, + ], + ['asc', 'asc'], + ); + data.goals = _.filter(narratives, { + [GrantOverviewMapping.implementationPeriod.narrative.type]: + GrantOverviewMapping.implementationPeriod.narrative.types[0], + }).map((n: any) => + _.get( + n, + GrantOverviewMapping.implementationPeriod.narrative.text, + '', + ), + ); + data.objectives = _.filter(narratives, { + [GrantOverviewMapping.implementationPeriod.narrative.type]: + GrantOverviewMapping.implementationPeriod.narrative.types[1], + }).map((n: any) => + _.get( + n, + GrantOverviewMapping.implementationPeriod.narrative.text, + '', + ), + ); + data.status = _.get( + period, + GrantOverviewMapping.implementationPeriod.status, + '', + ); + const financialIndicators = _.get( + period, + GrantOverviewMapping.implementationPeriod.financialIndicators, + [], + ); + data.disbursement = _.sumBy( + _.filter(financialIndicators, { + [GrantOverviewMapping.implementationPeriod.financialIndicator + .type]: + GrantOverviewMapping.implementationPeriod.financialIndicator + .types[0].value, + }), + GrantOverviewMapping.implementationPeriod.financialIndicator.value, + ); + data.commitment = _.sumBy( + _.filter(financialIndicators, { + [GrantOverviewMapping.implementationPeriod.financialIndicator + .type]: + GrantOverviewMapping.implementationPeriod.financialIndicator + .types[1].value, + }), + GrantOverviewMapping.implementationPeriod.financialIndicator.value, + ); + data.signed = _.sumBy( + _.filter(financialIndicators, { + [GrantOverviewMapping.implementationPeriod.financialIndicator + .type]: + GrantOverviewMapping.implementationPeriod.financialIndicator + .types[2].value, + }), + GrantOverviewMapping.implementationPeriod.financialIndicator.value, + ); + data.boardApprovedDate = moment( + _.get( + period, + GrantOverviewMapping.implementationPeriod.boardApprovedDate, + '', + ), + ).format('DD/MM/YYYY - hh:mm A'); + data.dates = [ + moment( + _.get( + period, + GrantOverviewMapping.implementationPeriod.startDate, + '', + ), + ).format('DD/MM/YYYY hh:mm A'), + moment( + _.get( + period, + GrantOverviewMapping.implementationPeriod.endDate, + '', + ), + ).format('DD/MM/YYYY hh:mm A'), + ]; + } + return {data: [data]}; + }) + .catch(handleDataApiError); + } + + @get('grant/{id}/{ip}/grant-implementation/radial') + @response(200) + async grantImplementationRadial( + @param.path.string('id') id: string, + @param.path.string('ip') ip: number, + ) { + let filterString = GrantImplementationMapping.radialUrlParams + .replace('', id) + .replace('', `${id}P0${ip}`); + if (this.req.query.cycle) { + filterString = filterString.replace( + '', + ` AND ${GrantImplementationMapping.cycle} eq '${this.req.query.cycle}'`, + ); + } else { + filterString = filterString.replace('', ''); + } + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, GrantImplementationMapping.dataPath, []); + const groupedByName = _.groupBy( + raw, + GrantImplementationMapping.category, + ); + const data: any = []; + Object.keys(groupedByName).forEach((name: string, index: number) => { + const value = _.sumBy( + groupedByName[name], + GrantImplementationMapping.value, + ); + data.push({ + name: _.get( + GrantImplementationMapping.categories, + `["${name}"]`, + name, + ), + value, + itemStyle: { + color: _.get(GrantImplementationMapping.colors, `[${index}]`, ''), + }, + }); + }); + return data; + }) + .catch(handleDataApiError); + } + + @get('grant/{id}/{ip}/grant-implementation/bar') + @response(200) + async grantImplementationBar( + @param.path.string('id') id: string, + @param.path.string('ip') ip: number, + ) { + let filterString = GrantImplementationMapping.barUrlParams + .replace('', id) + .replace('', `${id}P0${ip}`); + if (this.req.query.cycle) { + filterString = filterString.replace( + '', + ` AND ${GrantImplementationMapping.cycle} eq '${this.req.query.cycle}'`, + ); + } else { + filterString = filterString.replace('', ''); + } + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, GrantImplementationMapping.dataPath, []); + const data: { + name: string; + value: number; + itemStyle: {color: string}; + }[] = []; + + _.orderBy(raw, [GrantImplementationMapping.date], ['asc']).forEach( + (item: any, index: number) => { + data.push({ + name: new Date(item[GrantImplementationMapping.date]) + .getFullYear() + .toString(), + value: item[GrantImplementationMapping.value], + itemStyle: { + color: '', + }, + }); + }, + ); + + const groupedByYear = _.groupBy(data, 'name'); + + return { + data: _.map(groupedByYear, (value, key) => ({ + name: key, + value: _.sumBy(value, 'value'), + })).map((item, index) => ({ + ...item, + itemStyle: { + color: _.get( + GrantImplementationMapping.barColors, + `[${index % GrantImplementationMapping.barColors.length}]`, + '', + ), + }, + })), + }; + }) + .catch(handleDataApiError); + } + + @get('grant/{id}/{ip}/grant-implementation/sankey/{variant}') + @response(200) + async grantImplementationSankey( + @param.path.string('id') id: string, + @param.path.string('ip') ip: number, + @param.path.number('variant') variant: 1 | 2, + ) { + let category1 = GrantImplementationMapping.sankeyVariant1Category1; + let category2 = GrantImplementationMapping.sankeyVariant1Category2; + let urlParams = GrantImplementationMapping.sankeyVariant1UrlParams; + let category3 = ''; + if (variant === 2) { + category1 = GrantImplementationMapping.sankeyVariant2Category1; + category2 = GrantImplementationMapping.sankeyVariant2Category2; + urlParams = GrantImplementationMapping.sankeyVariant2UrlParams; + category3 = GrantImplementationMapping.sankeyVariant2Category3; + } + let filterString = urlParams + .replace('', id) + .replace('', `${id}P0${ip}`); + if (this.req.query.cycle) { + filterString = filterString.replace( + '', + ` AND ${GrantImplementationMapping.cycle} eq '${this.req.query.cycle}'`, + ); + } else { + filterString = filterString.replace('', ''); + } + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, GrantImplementationMapping.dataPath, []); + const data: { + nodes: { + name: string; + level: number; + itemStyle?: {color: string}; + }[]; + links: { + source: string; + target: string; + value: number; + }[]; + } = { + nodes: [ + { + name: 'Total budget', + level: 0, + }, + ], + links: [], + }; + const groupedByCategory1 = _.groupBy(raw, category1); + Object.keys(groupedByCategory1).forEach((category1: string) => { + const category1Items = _.get( + groupedByCategory1, + `["${category1}"]`, + [], + ); + data.nodes.push({ + name: category1, + level: 1, + }); + data.links.push({ + source: 'Total budget', + target: category1, + value: _.sumBy( + category1Items, + GrantImplementationMapping.sankeyValue, + ), + }); + const groupedByCategory2 = _.groupBy(category1Items, category2); + Object.keys(groupedByCategory2).forEach((category2: string) => { + const category2Items = _.get( + groupedByCategory2, + `["${category2}"]`, + [], + ); + const sameLevel1Category = _.find( + data.nodes, + node => node.name === category2 && node.level === 1, + ); + const name = `${category2}${sameLevel1Category ? ' (1)' : ''}`; + data.nodes.push({ + name, + level: 2, + }); + data.links.push({ + source: category1, + target: name, + value: _.sumBy( + category2Items, + GrantImplementationMapping.sankeyValue, + ), + }); + if (category3) { + const groupedByCategory3 = _.groupBy(category2Items, category3); + Object.keys(groupedByCategory3).forEach((category3: string) => { + const category3Items = _.get( + groupedByCategory3, + `["${category3}"]`, + [], + ); + const sameLevel1Or2Category = _.find( + data.nodes, + node => + node.name === category3 && + (node.level === 1 || node.level === 2), + ); + const existingCategoryIndex = _.findIndex( + data.nodes, + node => node.name === category3 && node.level === 3, + ); + if (existingCategoryIndex === -1) { + const name = `${category3}${ + sameLevel1Or2Category ? ' (1)' : '' + }`; + data.nodes.push({ + name, + level: 3, + }); + + data.links.push({ + source: category2, + target: name, + value: _.sumBy( + category3Items, + GrantImplementationMapping.sankeyValue, + ), + }); + } else { + data.links.push({ + source: category2, + target: category3, + value: _.sumBy( + category3Items, + GrantImplementationMapping.sankeyValue, + ), + }); + } + }); + } + }); + }); + return {data: [data]}; + }) + .catch(handleDataApiError); + } + + // v2 + @get('/grants') @response(200, GRANTS_RESPONSE) - grants(): object { + grantsV2(): object { const mapper = mapTransform(grantsMap); const page = (this.req.query.page ?? '1').toString(); const pageSize = (this.req.query.pageSize ?? '10').toString(); @@ -92,7 +614,7 @@ export class GrantsController { return axios .get(url) .then((resp: AxiosResponse) => { - const res: GrantListItemModel[] = mapper(resp.data) as never[]; + const res: GrantListItemModelV2[] = mapper(resp.data) as never[]; return { count: resp.data[grantsUtils.countPath], data: res, diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 7e6eb94..9f3e68f 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -4,6 +4,7 @@ export * from './data-themes/raw-data.controller'; export * from './disbursements.controller'; export * from './documents.controller'; export * from './eligibility.controller'; +export * from './expenditures.controller'; export * from './filteroptions.controller'; export * from './fundingrequests.controller'; export * from './global-search.controller'; diff --git a/src/controllers/location.controller.ts b/src/controllers/location.controller.ts index c8a1ce9..1ce7723 100644 --- a/src/controllers/location.controller.ts +++ b/src/controllers/location.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -8,8 +9,14 @@ import { } from '@loopback/rest'; import axios from 'axios'; import _ from 'lodash'; +import CoordinatingMehanismContactsMapping from '../config/mapping/location/coordinating-mechanism-contacts.json'; +import GeographiesMapping from '../config/mapping/location/geographies.json'; import locationMappingFields from '../config/mapping/location/index.json'; +import LocationInfoMapping from '../config/mapping/location/info.json'; +import PieChartsMapping from '../config/mapping/location/pieCharts.json'; import urls from '../config/urls/index.json'; +import {GeographyModel} from '../interfaces/geographies'; +import CountryDescriptions from '../static-assets/country-descriptions.json'; import {handleDataApiError} from '../utils/dataApiError'; import {getFilterString} from '../utils/filtering/grants/getFilterString'; @@ -29,6 +36,478 @@ const LOCATION_INFO_RESPONSE: ResponseObject = { export class LocationController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/location/{code}/grants/pie-charts') + @response(200) + async locationGrantsPieCharts(@param.path.string('code') code: string) { + let filterString1 = PieChartsMapping.pie1UrlParams.replace('', code); + let filterString2 = PieChartsMapping.pie2UrlParams.replace('', code); + let filterString3 = PieChartsMapping.pie3UrlParams.replace('', code); + filterString1 = filterString1.replace('', ''); + filterString2 = filterString2.replace('', ''); + filterString3 = filterString3.replace('', ''); + const url1 = `${urls.GRANTS}/${filterString1}`; + const url2 = `${urls.GRANTS}/${filterString2}`; + const url3 = `${urls.FINANCIAL_INDICATORS}/${filterString3}`; + + return axios + .all([axios.get(url1), axios.get(url2), axios.get(url3)]) + .then( + axios.spread((...responses) => { + const rawPie1 = _.get( + responses[0].data, + PieChartsMapping.dataPath, + [], + ); + const rawPie2 = _.get( + responses[1].data, + PieChartsMapping.dataPath, + [], + ); + const rawPie3 = _.get( + responses[2].data, + PieChartsMapping.dataPath, + [], + ); + + const totalPie1 = _.sumBy(rawPie1, PieChartsMapping.count); + const dataPie1 = rawPie1.map((item: any, index: number) => ({ + name: _.get(item, PieChartsMapping.pie1Field, ''), + value: (_.get(item, PieChartsMapping.count, 0) * 100) / totalPie1, + itemStyle: { + color: + PieChartsMapping.colors[index % PieChartsMapping.colors.length], + }, + })); + + const totalPie2 = _.sumBy(rawPie2, PieChartsMapping.count); + const dataPie2 = rawPie2.map((item: any, index: number) => ({ + name: _.get(item, PieChartsMapping.pie2Field, ''), + value: (_.get(item, PieChartsMapping.count, 0) * 100) / totalPie2, + itemStyle: { + color: + PieChartsMapping.colors[index % PieChartsMapping.colors.length], + }, + })); + + const totalPie3 = _.sumBy(rawPie3, PieChartsMapping.count); + const dataPie3 = rawPie3.map((item: any, index: number) => ({ + name: _.get(item, PieChartsMapping.pie3Field, ''), + value: (_.get(item, PieChartsMapping.count, 0) * 100) / totalPie3, + itemStyle: { + color: + PieChartsMapping.colors[index % PieChartsMapping.colors.length], + }, + })); + + return { + data: { + pie1: dataPie1, + pie2: dataPie2, + pie3: dataPie3, + }, + }; + }), + ) + .catch(handleDataApiError); + } + + @get('/geographies') + @response(200) + async geographies() { + const url = urls.HIERARCHICAL_GEOGRAPHIES; + + return axios + .get(url) + .then(response => { + const raw = _.get(response.data, GeographiesMapping.dataPath, []); + let data: GeographyModel[] = []; + + const world: GeographyModel = { + name: 'World', + value: 'QMZ', + items: [ + { + name: 'Multicountries', + value: 'Multicountries', + items: [], + }, + ], + }; + + raw.forEach((item1: any) => { + if ( + _.get(item1, GeographiesMapping.category, '') === 'Multicountry' + ) { + // @ts-ignore + world.items[0].items.push({ + name: _.get(item1, GeographiesMapping.name, ''), + value: _.get(item1, GeographiesMapping.value, ''), + }); + } else { + const subOptions = _.get(item1, GeographiesMapping.items, []); + const mcSubOptions = _.remove(subOptions, (i: any) => { + return ( + _.get(i, GeographiesMapping.category, '') === 'Multicountry' + ); + }); + if (subOptions.length > 0 || mcSubOptions.length > 0) { + data.push({ + name: _.get(item1, GeographiesMapping.name, ''), + value: _.get(item1, GeographiesMapping.value, ''), + items: + subOptions && subOptions.length > 0 + ? _.filter( + _.orderBy( + subOptions.map((item2: any) => { + const item2SubOptions = _.filter( + _.get(item2, GeographiesMapping.items, []), + (i: any) => { + if ( + _.get(i, GeographiesMapping.category, '') === + 'Country' || + _.get(i, GeographiesMapping.category, '') === + 'Multicountry' + ) { + return _.get( + i, + GeographiesMapping.isRecipient, + false, + ); + } + return true; + }, + ); + const mcSubSubOptions = _.remove( + item2SubOptions, + (i: any) => { + return ( + _.get(i, GeographiesMapping.category, '') === + 'Multicountry' + ); + }, + ); + let items: GeographyModel[] = []; + if (item2SubOptions && item2SubOptions.length > 0) { + items = _.orderBy( + item2SubOptions.map((item3: any) => { + const item3SubOptions = _.filter( + _.get(item3, GeographiesMapping.items, []), + (i: any) => { + if ( + _.get( + i, + GeographiesMapping.category, + '', + ) === 'Country' || + _.get( + i, + GeographiesMapping.category, + '', + ) === 'Multicountry' + ) { + return _.get( + i, + GeographiesMapping.isRecipient, + false, + ); + } + return true; + }, + ); + return { + name: _.get( + item3, + GeographiesMapping.name, + '', + ), + value: _.get( + item3, + GeographiesMapping.value, + '', + ), + subOptions: + item3SubOptions && + item3SubOptions.length > 0 + ? _.orderBy( + item3SubOptions.map( + (item4: any) => { + return { + name: _.get( + item4, + GeographiesMapping.name, + '', + ), + value: _.get( + item4, + GeographiesMapping.value, + '', + ), + }; + }, + ), + 'name', + 'asc', + ) + : undefined, + }; + }), + 'name', + 'asc', + ); + } + if (mcSubSubOptions && mcSubSubOptions.length > 0) { + items.concat( + _.orderBy( + mcSubSubOptions.map((mc: any) => ({ + name: _.get( + mc, + GeographiesMapping.name, + '', + ), + value: _.get( + mc, + GeographiesMapping.value, + '', + ), + })), + 'name', + 'asc', + ), + ); + } + return { + name: _.get(item2, GeographiesMapping.name, ''), + value: _.get(item2, GeographiesMapping.value, ''), + items: items.length > 0 ? items : [], + }; + }), + 'name', + 'asc', + ), + i => i.items.length > 0, + ) + : undefined, + }); + if (mcSubOptions.length > 0) { + data[data.length - 1].items?.push({ + name: 'Multicountries', + value: 'Multicountries', + items: _.orderBy( + mcSubOptions.map((mc: any) => ({ + name: _.get(mc, GeographiesMapping.name, ''), + value: _.get(mc, GeographiesMapping.value, ''), + })), + 'name', + 'asc', + ), + }); + } + } + } + }); + + data = _.orderBy(data, ['name'], ['asc']); + + data = [world, ...data]; + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/location/{code}/info') + @response(200) + async locationInfo(@param.path.string('code') code: string) { + const url = `${urls.GEOGRAPHIES}/${LocationInfoMapping.urlParams.replace( + '', + code, + )}`; + const FPMurl = `${urls.GRANTS}/${LocationInfoMapping.FPMurlParams.replace( + '', + code, + )}`; + const currPrincipalRecipientsUrl = `${ + urls.GRANTS + }/${LocationInfoMapping.currentPrincipalRecipientsUrlParams.replace( + '', + code, + )}`; + const formerPrincipalRecipientsUrl = `${ + urls.GRANTS + }/${LocationInfoMapping.formerPrincipalRecipientsUrlParams.replace( + '', + code, + )}`; + + return axios + .all([ + axios.get(url), + axios.get(FPMurl), + axios.get(currPrincipalRecipientsUrl), + axios.get(formerPrincipalRecipientsUrl), + ]) + .then( + axios.spread((...responses) => { + const raw = _.get( + responses[0].data, + LocationInfoMapping.dataPath, + [], + ); + const rawFPM = _.get( + responses[1].data, + LocationInfoMapping.dataPath, + [], + ); + const rawCurrPrincipalRecipients = _.get( + responses[2].data, + LocationInfoMapping.dataPath, + [], + ); + const rawFormerPrincipalRecipients = _.get( + responses[3].data, + LocationInfoMapping.dataPath, + [], + ); + + const currentPrincipalRecipients = rawCurrPrincipalRecipients.map( + (pr: any) => ({ + code: _.get(pr, LocationInfoMapping.principalRecipientCode, ''), + name: _.get(pr, LocationInfoMapping.principalRecipientName, ''), + }), + ); + + const description = _.find(CountryDescriptions, {iso3: code}); + + return { + data: [ + { + name: _.get(raw, LocationInfoMapping.name, ''), + region: _.get(raw, LocationInfoMapping.regionName, ''), + description: _.get(description, 'summary', ''), + FPMName: [ + _.get(rawFPM, LocationInfoMapping.FPMSalutation, ''), + _.get(rawFPM, LocationInfoMapping.FPMFirstName, ''), + _.get(rawFPM, LocationInfoMapping.FPMMiddleName, ''), + _.get(rawFPM, LocationInfoMapping.FPMLastName, ''), + ] + .join(' ') + .trim() + .replace(/ /g, ' '), + FPMEmail: _.get(rawFPM, LocationInfoMapping.FPMEmail, ''), + currentPrincipalRecipients, + formerPrincipalRecipients: _.filter( + rawFormerPrincipalRecipients.map((pr: any) => ({ + code: _.get( + pr, + LocationInfoMapping.principalRecipientCode, + '', + ), + name: _.get( + pr, + LocationInfoMapping.principalRecipientName, + '', + ), + })), + (fpr: any) => + !_.find(currentPrincipalRecipients, { + name: fpr.name, + }), + ), + countries: [], + multicountries: [], + }, + ], + }; + }), + ) + .catch(handleDataApiError); + } + + @get('/location/coordinating-mechanism/{code}/contacts') + @response(200) + async locationCoordinatingMechanismContacts( + @param.path.string('code') code: string, + ) { + const filterString = CoordinatingMehanismContactsMapping.urlParams.replace( + '', + code, + ); + const url = `${urls.COORDINATING_MECHANISMS}/${filterString}`; + + return axios + .get(url) + .then(response => { + const raw = _.get( + response.data, + CoordinatingMehanismContactsMapping.dataPath, + [], + ); + return { + data: raw.map((org: any) => ({ + name: _.get(org, CoordinatingMehanismContactsMapping.orgName, ''), + items: _.get(org, CoordinatingMehanismContactsMapping.items, []) + .map((item: any) => ({ + role: _.get( + item, + CoordinatingMehanismContactsMapping.itemRole, + '', + ), + fullname: [ + _.get( + item, + CoordinatingMehanismContactsMapping.itemSalutation, + '', + ), + _.get( + item, + CoordinatingMehanismContactsMapping.itemFirstName, + '', + ), + _.get( + item, + CoordinatingMehanismContactsMapping.itemMiddleName, + '', + ), + _.get( + item, + CoordinatingMehanismContactsMapping.itemLastName, + '', + ), + ].join(' '), + title: _.get( + item, + CoordinatingMehanismContactsMapping.itemTitle, + '', + ), + email: _.get( + item, + CoordinatingMehanismContactsMapping.itemEmail, + '', + ), + })) + .sort( + (a: any, b: any) => + _.get( + CoordinatingMehanismContactsMapping.itemRoleOrder, + `[${a.role}]`, + 0, + ) - + _.get( + CoordinatingMehanismContactsMapping.itemRoleOrder, + `[${b.role}]`, + 0, + ), + ), + })), + }; + }) + .catch(handleDataApiError); + } + + // v2 + @get('/location/detail') @response(200, LOCATION_INFO_RESPONSE) locationDetail(): object { diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index 99b6270..534aaf6 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -10,16 +11,18 @@ import axios, {AxiosResponse} from 'axios'; import _, {orderBy} from 'lodash'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; +import PledgesContributionsBarFieldsMapping from '../config/mapping/pledgescontributions/bar.json'; import PledgesContributionsGeoFieldsMapping from '../config/mapping/pledgescontributions/geo.json'; -import PledgesContributionsTableFieldsMapping from '../config/mapping/pledgescontributions/table.json'; +import PledgesContributionsStatsFieldsMapping from '../config/mapping/pledgescontributions/stats.json'; +import PledgesContributionsSunburstFieldsMapping from '../config/mapping/pledgescontributions/sunburst.json'; import PledgesContributionsTimeCycleFieldsMapping from '../config/mapping/pledgescontributions/timeCycle.json'; import PledgesContributionsTimeCycleDrilldownFieldsMapping from '../config/mapping/pledgescontributions/timeCycleDrilldown.json'; import urls from '../config/urls/index.json'; import {BudgetsTreemapDataItem} from '../interfaces/budgetsTreemap'; import {FilterGroupOption} from '../interfaces/filters'; import {PledgesContributionsTreemapDataItem} from '../interfaces/pledgesContributions'; -import {SimpleTableRow} from '../interfaces/simpleTable'; import {handleDataApiError} from '../utils/dataApiError'; +import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; import {getFilterString} from '../utils/filtering/pledges-contributions/getFilterString'; import {formatFinancialValue} from '../utils/formatFinancialValue'; import {getD2HCoordinates} from '../utils/pledgescontributions/getD2HCoordinates'; @@ -49,9 +52,387 @@ const PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE: ResponseObject = { }, }; +async function getBarData(urls: string[]) { + return axios + .all(urls.map(url => axios.get(url))) + .then(async responses => { + const pledgesData = _.get( + responses[0].data, + PledgesContributionsBarFieldsMapping.dataPath, + [], + ); + const contributionsData = _.get( + responses[1].data, + PledgesContributionsBarFieldsMapping.dataPath, + [], + ); + + const pledges = _.groupBy( + pledgesData, + PledgesContributionsBarFieldsMapping.name, + ); + const contributions = _.groupBy( + contributionsData, + PledgesContributionsBarFieldsMapping.name, + ); + + const years = _.uniq([ + ...Object.keys(pledges), + ...Object.keys(contributions), + ]); + + const data = years.map(year => { + return { + name: year, + value: _.sumBy( + pledges[year], + PledgesContributionsBarFieldsMapping.value, + ), + value1: _.sumBy( + contributions[year], + PledgesContributionsBarFieldsMapping.value, + ), + }; + }); + + return data; + }) + .catch(handleDataApiError); +} + export class PledgescontributionsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/pledges-contributions/stats') + @response(200) + async stats() { + const filterString1 = filterFinancialIndicators( + this.req.query, + PledgesContributionsStatsFieldsMapping.totalValuesUrlParams, + ); + const filterString2 = filterFinancialIndicators( + this.req.query, + PledgesContributionsStatsFieldsMapping.donorTypesCountUrlParams, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + return axios + .all([axios.get(url1), axios.get(url2)]) + .then(responses => { + const totalValues = _.get( + responses[0].data, + PledgesContributionsStatsFieldsMapping.dataPath, + [], + ); + const donorTypesCount = _.get( + responses[1].data, + PledgesContributionsStatsFieldsMapping.dataPath, + [], + ); + + const totalPledges = _.get( + _.find(totalValues, { + [PledgesContributionsStatsFieldsMapping.indicatorField]: + PledgesContributionsStatsFieldsMapping.pledgeIndicator, + }), + PledgesContributionsStatsFieldsMapping.pledgeAmount, + 0, + ); + + const totalContributions = _.get( + _.find(totalValues, { + [PledgesContributionsStatsFieldsMapping.indicatorField]: + PledgesContributionsStatsFieldsMapping.contributionIndicator, + }), + PledgesContributionsStatsFieldsMapping.contributionAmount, + 0, + ); + + const data = { + totalPledges, + totalContributions, + percentage: (totalContributions * 100) / totalPledges, + donorTypesCount: _.map(donorTypesCount, item => ({ + name: _.get( + item, + `[${PledgesContributionsStatsFieldsMapping.donorType}]`, + '', + ), + value: _.get(item, PledgesContributionsStatsFieldsMapping.count, 0), + })), + }; + + return data; + }) + .catch(handleDataApiError); + } + + @get('/pledges-contributions/expandable-bar') + @response(200) + async expandableBar() { + const filterString = filterFinancialIndicators( + this.req.query, + PledgesContributionsBarFieldsMapping.donorBarUrlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + PledgesContributionsBarFieldsMapping.dataPath, + [], + ); + + const groupedByIndicator = _.groupBy( + rawData, + PledgesContributionsBarFieldsMapping.donorBarType, + ); + + const data: { + name: string; + value: number; + value1: number; + items: { + name: string; + value: number; + value1: number; + }[]; + }[] = []; + + _.forEach(groupedByIndicator, (value, key) => { + const groupedByDonor = _.groupBy( + value, + PledgesContributionsBarFieldsMapping.donorBarDonor, + ); + data.push({ + name: key, + value: _.sumBy( + _.filter(value, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + ), + value1: _.sumBy( + _.filter(value, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, + ), + items: _.map(groupedByDonor, (donorValue, donorKey) => ({ + name: donorKey, + value: _.sumBy( + _.filter(donorValue, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + ), + value1: _.sumBy( + _.filter(donorValue, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, + ), + })), + }); + }); + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/pledges-contributions/sunburst/{type}') + @response(200) + async sunburst(@param.path.string('type') type: string) { + const urlParams = + type === 'pledge' + ? PledgesContributionsSunburstFieldsMapping.pledgeUrlParams + : PledgesContributionsSunburstFieldsMapping.contributionUrlParams; + const filterString = filterFinancialIndicators(this.req.query, urlParams); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + PledgesContributionsSunburstFieldsMapping.dataPath, + [], + ); + + const groupedByDonorType = _.groupBy( + rawData, + PledgesContributionsSunburstFieldsMapping.type, + ); + + const data: { + name: string; + value: number; + children: { + name: string; + value: number; + }[]; + }[] = []; + + _.forEach(groupedByDonorType, (value, key) => { + const groupedByDonor = _.groupBy( + value, + PledgesContributionsSunburstFieldsMapping.donor, + ); + data.push({ + name: key, + value: _.sumBy( + value, + PledgesContributionsSunburstFieldsMapping.amount, + ), + children: _.map(groupedByDonor, (donorValue, donorKey) => ({ + name: donorKey, + value: _.sumBy( + donorValue, + PledgesContributionsSunburstFieldsMapping.amount, + ), + })), + }); + }); + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/pledges-contributions/table') + @response(200) + async table() { + const filterString = filterFinancialIndicators( + this.req.query, + PledgesContributionsBarFieldsMapping.donorBarUrlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + PledgesContributionsBarFieldsMapping.dataPath, + [], + ); + + const groupedByIndicator = _.groupBy( + rawData, + PledgesContributionsBarFieldsMapping.donorBarType, + ); + + const data: { + name: string; + pledge: number; + contribution: number; + _children: { + name: string; + pledge: number; + contribution: number; + }[]; + }[] = []; + + _.forEach(groupedByIndicator, (value, key) => { + const groupedByDonor = _.groupBy( + value, + PledgesContributionsBarFieldsMapping.donorBarDonor, + ); + data.push({ + name: key, + pledge: _.sumBy( + _.filter(value, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + ), + contribution: _.sumBy( + _.filter(value, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, + ), + _children: _.map(groupedByDonor, (donorValue, donorKey) => ({ + name: donorKey, + pledge: _.sumBy( + _.filter(donorValue, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + ), + contribution: _.sumBy( + _.filter(donorValue, { + [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: + PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, + }), + PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, + ), + })), + }); + }); + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/pledges-contributions/bar') + @response(200) + async bar() { + let filterString1 = filterFinancialIndicators( + this.req.query, + PledgesContributionsBarFieldsMapping.pledgesUrlParams, + ); + let filterString2 = filterFinancialIndicators( + this.req.query, + PledgesContributionsBarFieldsMapping.contributionsUrlParams, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + const data = await getBarData([url1, url2]); + + return {data}; + } + + @get('/pledges-contributions/bar/{countryCode}') + @response(200) + async barInLocation(@param.path.string('countryCode') countryCode: string) { + let filterString1 = filterFinancialIndicators( + { + ...this.req.query, + geographies: countryCode, + }, + PledgesContributionsBarFieldsMapping.pledgesUrlParams, + ); + let filterString2 = filterFinancialIndicators( + { + ...this.req.query, + geographies: countryCode, + }, + PledgesContributionsBarFieldsMapping.contributionsUrlParams, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + return getBarData([url1, url2]); + } + + // v2 + @get('/pledges-contributions/time-cycle') @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) timeCycle(): object { @@ -730,90 +1111,90 @@ export class PledgescontributionsController { .catch(handleDataApiError); } - @get('/pledges-contributions/table') - @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) - table(): object { - const aggregation = - PledgesContributionsTableFieldsMapping.aggregations[ - this.req.query.aggregateBy === - PledgesContributionsTableFieldsMapping.aggregations[0].key - ? 0 - : 1 - ].value; - const aggregationKey = - this.req.query.aggregateBy === - PledgesContributionsTableFieldsMapping.aggregations[1].key - ? PledgesContributionsTableFieldsMapping.aggregations[1].key - : PledgesContributionsTableFieldsMapping.aggregations[0].key; - const filterString = getFilterString(this.req.query, aggregation); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.pledgescontributions}/?${params}${filterString}`; - const sortBy = this.req.query.sortBy; - const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'asc'; + // @get('/pledges-contributions/table') + // @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) + // table(): object { + // const aggregation = + // PledgesContributionsTableFieldsMapping.aggregations[ + // this.req.query.aggregateBy === + // PledgesContributionsTableFieldsMapping.aggregations[0].key + // ? 0 + // : 1 + // ].value; + // const aggregationKey = + // this.req.query.aggregateBy === + // PledgesContributionsTableFieldsMapping.aggregations[1].key + // ? PledgesContributionsTableFieldsMapping.aggregations[1].key + // : PledgesContributionsTableFieldsMapping.aggregations[0].key; + // const filterString = getFilterString(this.req.query, aggregation); + // const params = querystring.stringify( + // {}, + // '&', + // filtering.param_assign_operator, + // { + // encodeURIComponent: (str: string) => str, + // }, + // ); + // const url = `${urls.pledgescontributions}/?${params}${filterString}`; + // const sortBy = this.req.query.sortBy; + // const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; + // const sortByDirection: any = + // sortBy && sortBy.toString().split(' ').length > 1 + // ? sortBy.toString().split(' ')[1].toLowerCase() + // : 'asc'; - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get( - resp.data, - PledgesContributionsTableFieldsMapping.dataPath, - [], - ); - const groupedByData = _.groupBy( - rawData, - _.get( - PledgesContributionsTableFieldsMapping, - aggregationKey, - PledgesContributionsTableFieldsMapping.Donor, - ), - ); - const data: SimpleTableRow[] = []; - Object.keys(groupedByData).forEach(key => { - data.push({ - name: key, - Pledges: _.sumBy( - _.filter( - groupedByData[key], - dataItem => - _.get( - dataItem, - PledgesContributionsTableFieldsMapping.indicator, - '', - ) === PledgesContributionsTableFieldsMapping.pledge, - ), - PledgesContributionsTableFieldsMapping.amount, - ), - Contributions: _.sumBy( - _.filter( - groupedByData[key], - dataItem => - _.get( - dataItem, - PledgesContributionsTableFieldsMapping.indicator, - '', - ) === PledgesContributionsTableFieldsMapping.contribution, - ), - PledgesContributionsTableFieldsMapping.amount, - ), - }); - }); + // return axios + // .get(url) + // .then((resp: AxiosResponse) => { + // const rawData = _.get( + // resp.data, + // PledgesContributionsTableFieldsMapping.dataPath, + // [], + // ); + // const groupedByData = _.groupBy( + // rawData, + // _.get( + // PledgesContributionsTableFieldsMapping, + // aggregationKey, + // PledgesContributionsTableFieldsMapping.Donor, + // ), + // ); + // const data: SimpleTableRow[] = []; + // Object.keys(groupedByData).forEach(key => { + // data.push({ + // name: key, + // Pledges: _.sumBy( + // _.filter( + // groupedByData[key], + // dataItem => + // _.get( + // dataItem, + // PledgesContributionsTableFieldsMapping.indicator, + // '', + // ) === PledgesContributionsTableFieldsMapping.pledge, + // ), + // PledgesContributionsTableFieldsMapping.amount, + // ), + // Contributions: _.sumBy( + // _.filter( + // groupedByData[key], + // dataItem => + // _.get( + // dataItem, + // PledgesContributionsTableFieldsMapping.indicator, + // '', + // ) === PledgesContributionsTableFieldsMapping.contribution, + // ), + // PledgesContributionsTableFieldsMapping.amount, + // ), + // }); + // }); - return { - count: data.length, - data: _.orderBy(data, sortByValue, sortByDirection), - }; - }) - .catch(handleDataApiError); - } + // return { + // count: data.length, + // data: _.orderBy(data, sortByValue, sortByDirection), + // }; + // }) + // .catch(handleDataApiError); + // } } diff --git a/src/controllers/results.controller.ts b/src/controllers/results.controller.ts index 7236539..801c30b 100644 --- a/src/controllers/results.controller.ts +++ b/src/controllers/results.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -10,12 +11,17 @@ import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import {mapTransform} from 'map-transform'; import resultsMap from '../config/mapping/results/index.json'; +import ResultsTableLocationMappingFields from '../config/mapping/results/location-table.json'; +import ResultsPolylineMappingFields from '../config/mapping/results/polyline.json'; +import ResultsStatsMappingFields from '../config/mapping/results/stats-home.json'; import resultStatsMap from '../config/mapping/results/stats.json'; +import ResultsTableMappingFields from '../config/mapping/results/table.json'; import resultsUtils from '../config/mapping/results/utils.json'; import ResultsYearsMappingFields from '../config/mapping/results/years.json'; import urls from '../config/urls/index.json'; import {ResultListItemModel} from '../interfaces/resultList'; import {handleDataApiError} from '../utils/dataApiError'; +import {filterProgrammaticIndicators} from '../utils/filtering/programmaticIndicators'; import { getFilterString, getFilterStringForStats, @@ -86,6 +92,177 @@ const RESULT_STATS_RESPONSE: ResponseObject = { export class ResultsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + // v3 + + @get('/results/polyline/{cycle}') + @response(200) + async polyline(@param.path.string('cycle') cycle: string) { + const filterString = filterProgrammaticIndicators( + { + ...this.req.query, + years: this.req.query.years + ? `${this.req.query.years},${cycle}` + : cycle, + }, + ResultsPolylineMappingFields.urlParams, + ); + const url = `${urls.PROGRAMMATIC_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, ResultsPolylineMappingFields.dataPath, []); + const groupedByComponent = _.groupBy( + raw, + ResultsPolylineMappingFields.component, + ); + + const data = { + name: cycle, + children: _.orderBy( + _.map(groupedByComponent, (componentData, component) => ({ + name: component, + children: componentData.map((item: any) => ({ + name: _.get(item, ResultsPolylineMappingFields.name, ''), + value: _.get(item, ResultsPolylineMappingFields.value, 0), + itemStyle: { + color: _.get( + ResultsPolylineMappingFields.componentColors, + component, + '', + ), + }, + })), + itemStyle: { + color: _.get( + ResultsPolylineMappingFields.componentColors, + component, + '', + ), + }, + })), + (item: any) => item.children.length, + 'desc', + ), + }; + + return {data}; + }) + .catch(handleDataApiError); + } + + @get('/results/table') + @response(200) + async table() { + const filterString = filterProgrammaticIndicators( + this.req.query, + ResultsTableMappingFields.urlParams, + ); + const url = `${urls.PROGRAMMATIC_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = ResultsTableMappingFields.dataPath + ? _.get(resp.data, ResultsTableMappingFields.dataPath, []) + : resp.data; + + const groupedByYear = _.groupBy(raw, ResultsTableMappingFields.year); + + return { + data: _.map(groupedByYear, (yearData, year) => { + const groupedByComponent = _.groupBy( + yearData, + ResultsTableMappingFields.component, + ); + + return { + name: year, + _children: _.map( + groupedByComponent, + (componentData, component) => ({ + name: component, + _children: componentData.map((item: any) => ({ + name: _.get(item, ResultsTableMappingFields.name, ''), + value: _.get(item, ResultsTableMappingFields.value, 0), + })), + }), + ), + }; + }).reverse(), + }; + }) + .catch(handleDataApiError); + } + + @get('/results/stats') + @response(200) + async resultsStats() { + const cycle = this.req.query.cycle ?? new Date().getFullYear() - 1; + const filterString = filterProgrammaticIndicators( + {...this.req.query, years: cycle.toString()}, + ResultsStatsMappingFields.urlParams, + ); + const url = `${urls.PROGRAMMATIC_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = ResultsStatsMappingFields.dataPath + ? _.get(resp.data, ResultsStatsMappingFields.dataPath, []) + : resp.data; + return { + stats: raw.map((item: any) => ({ + label: `${_.get( + item, + ResultsStatsMappingFields.name, + '', + )} in ${cycle}`, + value: _.get(item, ResultsStatsMappingFields.value, 0), + })), + }; + }) + .catch(handleDataApiError); + } + + @get('/results/table/{countryCode}/{cycle}') + @response(200) + async resultsTable( + @param.path.string('cycle') cycle: string, + @param.path.string('countryCode') countryCode: string, + ) { + let filterString = ResultsTableLocationMappingFields.urlParams + .replace('', countryCode) + .replace('', cycle); + const url = `${urls.PROGRAMMATIC_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = ResultsTableLocationMappingFields.dataPath + ? _.get(resp.data, ResultsTableLocationMappingFields.dataPath, []) + : resp.data; + return { + data: raw.map((item: any) => ({ + description: _.get( + item, + ResultsTableLocationMappingFields.name, + '', + ), + result: _.get(item, ResultsTableLocationMappingFields.value, 0), + component: _.get( + item, + ResultsTableLocationMappingFields.component, + '', + ), + })), + }; + }) + .catch(handleDataApiError); + } + + // v2 + @get('/results') @response(200, RESULTS_RESPONSE) results(): object { @@ -139,11 +316,9 @@ export class ResultsController { .get(url) .then((resp: AxiosResponse) => { return { - data: _.get( - resp.data, - ResultsYearsMappingFields.dataPath, - [], - ).map((item: any) => _.get(item, ResultsYearsMappingFields.year, '')), + data: _.get(resp.data, ResultsYearsMappingFields.dataPath, []).map( + (item: any) => _.get(item, ResultsYearsMappingFields.year, ''), + ), }; }) .catch(handleDataApiError); diff --git a/src/interfaces/budgetSankey.ts b/src/interfaces/budgetSankey.ts new file mode 100644 index 0000000..8a0494f --- /dev/null +++ b/src/interfaces/budgetSankey.ts @@ -0,0 +1,18 @@ +export interface BudgetSankeyChartNode { + name: string; + level: number; + itemStyle?: { + color: string; + }; +} + +export interface BudgetSankeyChartLink { + value: number; + source: string; + target: string; +} + +export interface BudgetSankeyChartData { + nodes: BudgetSankeyChartNode[]; + links: BudgetSankeyChartLink[]; +} diff --git a/src/interfaces/filters.ts b/src/interfaces/filters.ts index 97a9746..26d315e 100644 --- a/src/interfaces/filters.ts +++ b/src/interfaces/filters.ts @@ -2,6 +2,9 @@ export interface FilterGroupOption { label: string; value: string; subOptions?: FilterGroupOption[]; + extraInfo?: { + [key: string]: any; + }; } export interface FilterGroup { diff --git a/src/interfaces/geographies.ts b/src/interfaces/geographies.ts new file mode 100644 index 0000000..c915fd6 --- /dev/null +++ b/src/interfaces/geographies.ts @@ -0,0 +1,5 @@ +export interface GeographyModel { + name: string; + value: string; + items?: GeographyModel[]; +} diff --git a/src/interfaces/grantList.ts b/src/interfaces/grantList.ts index f11de88..fc871ef 100644 --- a/src/interfaces/grantList.ts +++ b/src/interfaces/grantList.ts @@ -1,4 +1,4 @@ -export interface GrantListItemModel { +export interface GrantListItemModelV2 { id: string; title: string; status: string; @@ -11,3 +11,17 @@ export interface GrantListItemModel { recipientName: string; recipientShortName: string; } + +export interface GrantListItemModel { + code: string; + title: string; + status: string; + component: string; + location: string; + rating: string | null; + principalRecipient: string; + disbursed: number; + committed: number; + signed: number; + percentage: number; +} diff --git a/src/static-assets/country-descriptions.json b/src/static-assets/country-descriptions.json new file mode 100644 index 0000000..9069468 --- /dev/null +++ b/src/static-assets/country-descriptions.json @@ -0,0 +1,1124 @@ +[ + { + "iso3": "CIV", + "summary": "
\n

\nThe Global Fund currently has six active investments in Côte d'Ivoire, with funding of up to EUR 236 million allocated for 2021-2023. Our investments are geared toward improving the country’s health systems and supporting interventions aimed at continuing and extending hard-won progress in the fight against HIV, tuberculosis (TB) and malaria.\n

\n

\nProgress\n

\n

\nCôte d'Ivoire has made significant progress in its fight against HIV, TB and malaria in the last two decades. \n

\n

\nThe coastal West African country of 26 million people has seen large reductions in AIDS-related deaths (down 83% since 2002) and new HIV cases (down almost 90% since 2002). Downward trends in TB results are not as dramatic, but still significant – new cases and mortality have fallen by almost 40% since 2002. \n

\n

\nThe Global Fund has contributed to the country’s efforts to address one of the highest malaria burdens in the world. Our funding has supported a mix of case management, rapid diagnostic tests, artemisinin combination therapies, insecticidal net distribution, preventative therapy for pregnant women and other interventions that have driven a 50% decline in malaria-related deaths over the past two decades.\n

\n

\nChallenges\n

\n

\nCôte d'Ivoire continues to face inadequacies in its health system. Suboptimal coordination, planning, data collection and reporting, as well as supply chain inefficiencies, continue to hinder Côte d'Ivoire’s HIV, TB and malaria response.\n

\n

\nCôte d'Ivoire remains one of the countries most affected by HIV and AIDS in West and Central Africa, with around 2.8% of the adult population estimated to be living with HIV. Reaching all key populations – mainly sex workers, transgender people and men who have sex with men – remains a major challenge. In addition, perinatal HIV transmission rates remain high, and prevention activities and education for pregnant women are critical areas of focus.\n

\n

\nTB remains a major public health problem in Côte d'Ivoire. Although new cases have trended downward over the past two decades, it is estimated there are still many people with TB who are “missing”. Challenges around GeneXpert rollout and strengthening transportation and laboratory systems continue to impede efforts to find and treat everyone with TB in the country.\n

\n

\nThe whole of Côte d'Ivoire is malaria-endemic and controlling transmission, particularly given the country’s mix of equatorial and tropical climates in which malaria-carrying mosquitoes thrive, is a key challenge. Scaling up community health services and overcoming major challenges in distributing commodities at the district level are critical efforts in expanding quality malaria case management throughout the country.\n

\n

\nGlobal Fund investments\n

\n

\nThree programs addressing HIV and/or TB in Côte d'Ivoire were allocated funding of up to a combined total of EUR 99 million for 2021-2023. Our investments support the national strategic plans for each disease, which aim to achieve a 70% reduction in new HIV infections and a 50% reduction in HIV mortality by 2025, and reduce TB deaths by 60% and TB incidence by 50% by the same year.\n

\n

\nThe Global Fund has allocated funding to two malaria programs in Côte d'Ivoire of up to a combined total of EUR 113 million for 2021-2023. These investments support interventions designed to reignite progress toward ending malaria as a public health threat and support the country in its ambition of reducing malaria incidence and mortality rates by at least 75% (by 2025, compared to 2015).\n

\n

\nFunding of up to EUR 23 million was allocated for 2021-2023 to strengthen the resilience of health systems in Côte d'Ivoire. The grant is supporting the government in delivering the priorities of the National Health Development Plan, while simultaneously enabling continued and increased impact for HIV, TB and malaria programs.\n

\n

\nLast updated June 2022.\t\n

\n
", + "_mby": "e032e85161303584ff0002f8", + "_by": "e032e85161303584ff0002f8", + "_modified": 1655133594, + "_created": 1646839856, + "_id": "590bd80035653750470002d8", + "summary_de": null, + "summary_fr": null + }, + { + "_id": "28d4de533138326d7a0003de", + "iso3": "BGD", + "summary": "
\n

\nThe Global Fund currently funds seven grants addressing HIV, tuberculosis (TB) and malaria in Bangladesh, with a total investment of up to US$169 million allocated for 2021-2023. These grants support innovative, targeted interventions aimed at continuing Bangladesh’s progress toward ending the three diseases as public health threats within the decade.\n

\n

\nProgress\n

\n

\nBangladesh is one of the most densely populated countries in the world, with over 160 million people. The country in South Asia has made significant progress in public health in recent years, including in testing, treatment and prevention of HIV, TB and malaria.\n

\n

\nAlthough malaria is endemic in 13 of the country’s 64 districts, Bangladesh has elimination of the disease in its sights. Large decreases have been seen in malaria deaths and new cases – both have fallen by over 90% since 2002.\n

\n

\nBangladesh’s significant TB burden is being addressed by scaling up testing and treatment. Every year, over 2 million people with TB symptoms are tested and since 2005 the country has sustained a treatment success rate of over 90%. TB mortality has fallen by 50% since 2001, and approximately 1 million lives have been saved from TB in the last decade.\n

\n

\nChallenges\n

\n

\nBangladesh is recognized by the World Health Organization as one of the 30 high-burden countries for TB and for multidrug-resistant TB. A key concern for TB in Bangladesh is the relatively low level of case detection. Global Fund investment is helping to address this by supporting innovative programs to find cases of TB missed by public health systems and report them to the national program.\n

\n

\nThe epidemiology of malaria in Bangladesh is highly complex. All four species of parasites that can infect humans are present, and the disease remains a critical public health concern. The most vulnerable people are those living in hilly, forested and forest fringe areas. These hard-to-reach locations are home to communities and mobile and migrant populations that are often socioeconomically marginalized. Malaria interventions need to be adapted to suit specific groups and vector behaviors, as well as aligned with the availability of local infrastructure and health service coverage.\n

\n

\nAlthough Bangladesh has maintained a low HIV prevalence among its general population, the epidemic continues to increase. Since 2010, HIV incidence has risen by 56% and HIV-related mortality by 110%. Almost a quarter of new HIV infections are among people who inject drugs. HIV prevalence is much lower among other key populations.\n

\n

\nGlobal Fund investments\n

\n

\nThree HIV grants were allocated funding for 2021-2023 of up to a combined total of US$21 million. These grants are working toward a collective goal of preventing new HIV infections and reducing HIV incidence, morbidity and mortality by optimizing and expanding interventions focused on key populations and their partners.\n

\nTwo TB grants were allocated funding of up to a combined total of US$124 million for 2021-2023. The grants support the government of Bangladesh, together with its many and diverse partners from the public, private and community sectors, in intensifying efforts to sustain the progress achieved so far in TB control and to reach the TB targets linked to the World Health Organization's End TB Strategy.\n

\n

\nFunding of up to a combined total of US$24 million was allocated for two malaria programs for 2021-2023. The funding supports Bangladesh’s activities to interrupt local malaria transmission in all but three endemic districts by 2025, accelerate control efforts in high-transmission areas and achieve malaria-free status by 2030.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "291b2525613436e91f0003da", + "iso3": "KGZ", + "summary": "
\n

\nThe Global Fund currently has one active investment in Kyrgyzstan: A multi-component HIV/AIDS and tuberculosis (TB) grant of up to US$27.5 million allocated for 2021-2023. Our funding supports interventions aimed at accelerating reductions in HIV infections and deaths and significantly increasing the treatment success rate among drug resistant forms of TB.\n

\n

\nProgress\n

\n

\nKyrgyzstan is a land-locked, lower-middle-income country of almost 7 million people in Central Asia. The country has experienced instability since its independence in 1991, which has made it difficult to gain traction in the fight against HIV and TB. \n

\n

\nNevertheless, Kyrgyzstan has recently made significant progress in providing a better legal framework for TB services and improving TB diagnosis and treatment. While new TB cases have decreased moderately since 2002, the shift in TB mortality has been more significant, with deaths falling by 73% over the same period.\n

\n

\nProgress against HIV has also been achieved in some key areas. People living with HIV who receive antiretrovirals increased from 9% in 2010 to 48% in 2020, while the percentage of people living with HIV who have a suppressed viral load doubled.\n

\n

\nChallenges\n

\n

\nDespite the progress made in the fight against TB, drug resistance still presents a major challenge. The WHO identifies Kyrgyzstan as one of the 30 high multidrug-resistant/rifampicin-resistant TB burden countries globally.\n

\n

\nKyrgyzstan’s HIV epidemic is concentrated among key populations, mostly people who inject drugs, sex workers and gay men and other men who have sex with men. From 2012 there was a steady upward trend in sexual transmission of HIV, and by 2019 sexual transmission accounted for 70% of all registered HIV cases. Most new HIV cases are among sexual partners of people who inject drugs. Strengthening prevention and treatment among these key and vulnerable populations will help unlock further gains against the epidemic in Kyrgyzstan.\n

\n

\nGlobal Fund investments\n

\n

\nThe “Effective HIV and TB control in the Kyrgyz Republic” program was allocated funding of up to US$27.5 million for the 2021-2023 implementation period.\n

\n

\nOur investment supports interventions that are working to reduce the TB burden in Kyrgyzstan by striving to see that all people have access to timely and quality diagnosis and treatment. It also focuses on key populations with HIV prevention activities and allows people living with HIV to receive better care and support. \n

\n

\nThe grant is underpinned by goals of the National TB and HIV Program, which aims to reduce HIV incidence and mortality by 50% by 2023 (compared to 2015), end the HIV epidemic in Kyrgyzstan by 2030 and raise the treatment success rate among drug resistant forms of TB to 75% by 2023.\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "29467d9f62393247060003c1", + "iso3": "KHM", + "summary": "
\n

\nThe Global Fund currently has two active investments in Cambodia: One grant of up to US$62 million focusing on HIV and AIDS and tuberculosis (TB), and another of up to US$4 million supporting the strengthening of systems for health in the country. This funding is allocated for 2021-2023 and invests in Cambodia’s strategies to achieve continued significant reductions in incidence and death rates for HIV, TB and malaria.\n

\n

\nProgress\n

\n

\nCambodia has made strong progress in its fight against HIV over the past two decades. A successful and coordinated HIV response has reduced estimated new adult infections from a peak of 21,000 in 1996 to a little over 1,000 in 2020. AIDS-related deaths have seen a similarly significant decline, falling by 73% over the past two decades. These achievements are a testament to the concerted and collaborative efforts of government, development partners, community and civil society. \n

\n

\nSince 2002, TB deaths (excluding people living with HIV) have reduced by 43% and new TB cases have fallen by 32%. This resulted in Cambodia transitioning out of the list of 30 high TB burden countries in the 2021 WHO Global Tuberculosis Report.\n

\n

\nCambodia has also made gains in controlling malaria through mosquito net distribution campaigns and providing case management services in endemic villages. These interventions have significantly reduced malaria deaths by 87% since 2002 and cases by 60% over the same period.\n

\n

\nChallenges\n

\n

\nA key challenge in Cambodia’s HIV response is the need to rapidly identify and respond to changes in the distribution and risk profile of people living with HIV. People in key populations (male sex workers, transgender women and men who have sex with men) may be hard to reach, wary of stigma and discrimination, or not identify as part of the population. Better targeting of prevention and HIV services for these groups of people remains a challenge.\n

\n

\nCambodia is on the WHO’s “Global TB Watchlist” of countries that have exited the list of 30 high TB burden countries, but which nonetheless warrant continued attention and remain a priority in terms of support. The country has a significant prevalence of undernutrition, diabetes, and tobacco and alcohol use, all of which contribute to the TB epidemic. In addition, national TB prevalence surveys have highlighted difficulties in TB detection.\n

\n

\nArtemisinin resistance in Cambodia and other Greater Mekong countries has emerged in recent years and threatens hard-won gains against malaria. The Global Fund’s Regional Artemisinin-resistance Initiative is supporting countries in the region to achieve malaria elimination.\n

\n

\nGlobal Fund investments\n

\n

\nCambodia’s HIV and AIDS and TB program was allocated funding of up to US$62 million for the 2021-2023 implementation period. Our funding supports interventions that aim to end AIDS as a public health threat in Cambodia by 2025 and, in alignment with the goals of the National Strategic Plan for TB, reduce TB deaths by 90% and incidence by 80% by 2030.\n

\n

\nA multilayered program to scale-up health care and systems in Cambodia was allocated funding of up to US$4 million for 2021-2023. The grant supports activities aimed at helping the health system become more resilient, efficient and better equipped to deliver HIV, TB and malaria services in a sustainable manner.\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "fe3e55993339398aab00019b", + "iso3": "CRI", + "summary": "
\n

\nThe Global Fund currently has one active investment in Costa Rica: An HIV program that has been allocated up to US$2.2 million for 2021-2024. The grant supports interventions geared toward improving testing and treatment for key and vulnerable populations and strengthening national HIV surveillance and support systems.\n

\n

\nProgress\n

\n

\nCosta Rica is an upper-middle-income country with a population of 5.1 million. Its HIV epidemic is concentrated among key populations, primarily transgender women and gay men and other men who have sex with men. HIV prevalence is geographically concentrated: 65% of Costa Rica’s cases occur in just two of its seven provinces. \n

\n

\nMonitoring progress toward the 90-90-90 targets for 2020 and the 95-95-95 targets for 2025 in the fight against HIV has proven difficult as the national health information system has not had the functionality to separate data into subpopulations, which can provide valuable insights and enhance the impact of interventions. The capability to disaggregate data (including for the key populations among which Costa Rica’s HIV epidemic is most prevalent) was being addressed as of 2021. Once operational, this functionality will prove valuable in building and monitoring targeted response strategies.\n

\n

\nChallenges\n

\n

\nAlthough Costa Rica provides publicly funded universal access to health care, there are several challenges to overcome in the fight against HIV in the country.\n

\n

\nRecent high fiscal deficit and insufficient contributions to social security could hamper equitable coverage of health care, notably for undocumented migrants.\n

\n

\nStigma and discrimination, low condom usage, socioeconomic differences and gender inequality present structural barriers to testing and treatment. Situations of extreme gender violence and human rights abuse have been documented, including impeded access to health and social services.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund has allocated funding of up to US$2.2 million to Costa Rica over 2021-2024 for a comprehensive intersectoral HIV response program. The grant supports:\n

    \n
  • The roll-out of pre-exposure prophylaxis for key populations.\n
  • Reducing barriers to testing access at a facility, community and individual level.\n
  • Prevention services and behavior change interventions for key populations, and in high prevalence provinces.\n
  • Retention and adherence to antiretroviral therapy through integrated care support for people living with HIV.\n
  • Improvements in the national HIV surveillance system, implementation of prioritized policy changes and advocacy actions to improve laws, regulations and policies relating to HIV and HIV/TB and remove barriers to access – specifically stigma and discrimination.\n
  • Building capacity and support for civil society organizations to obtain funding from domestic resources for HIV-related activities.\n
  • A range of other HIV actions that are inclusive, impactful and sustainable.\n
\n

\nOur funding is aligned with the goals of Costa Rica’s National Strategic Plan for HIV 2021-2026.\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "fe70c77f3837615248000383", + "iso3": "MWI", + "summary": "
\n

\nThe Global Fund currently has four active investments in Malawi: two dual-component TB/HIV grants of up to a combined total of US$430 million and two malaria grants of up to a combined total of US$95 million. This funding is allocated for the 2021-2024 investment period and supports Malawi’s bold targets for incidence and mortality reduction for the three diseases.\n

\n

\nProgress\n

\n

\nMalawi, a low-income country of 20 million people, has made significant progress in controlling its HIV epidemic in recent years. Adult HIV prevalence has fallen by about 40% since 2010 (yet remains one of the highest in the world), AIDS-related deaths have decreased dramatically, and the country is close to achieving the 95-95-95 treatment targets. \n

\n

\nCollaborative interventions for tuberculosis (TB) and HIV have improved over the last half-decade and have driven high rates of HIV testing among TB patients (99%) and high antiretroviral therapy coverage among co-infected patients (99%).\n

\n

\nAlthough Malawi is still in the control phase of the malaria epidemic, strong gains have nonetheless been made against the disease. Malaria deaths have decreased by about 70% in two decades and a doubling in the rates of access and use of long-lasting insecticidal nets has helped set new cases on a slow yet steady downward course.\n

\n

\nChallenges\n

\n

\nDespite the gains made in the fight against HIV in Malawi, key challenges remain, including a low yield of HIV testing (especially in men), suboptimal retention of people on antiretroviral therapy, low TB preventive therapy for eligible patients, and low expansion of prevention services for high-risk groups.\n

\n

\nYoung people are particularly at risk of HIV infection in Malawi. Around one-third of all new HIV infections occur among 15- to 24-year-olds, and knowledge of prevention among young people is poor. Key populations, especially female sex workers and men who have sex with men, also remain at significant HIV risk.\n

\n

\nMalawi’s high TB burden is primarily driven by the HIV epidemic, and the country remains on the WHO’s list of 30 high TB/HIV burden countries. Increasing TB treatment coverage from its current rate of about 50% is a key challenge, as is improving case detection and management of drug-resistant TB.\n

\n

\nMalaria continues to be a major public health problem, accounting for 23% of all outpatient visits in all age groups. The disease is hyperendemic in Malawi, and controlling transmission, particularly given the country’s high temperatures, rainfall and humidity, presents a key challenge that can only be met with sustained, high-impact interventions.\n

\n

\nGlobal Fund investments\n

\n

\nTwo programs focusing on TB and HIV were allocated funding of up to a combined total of US$430 million for the 2021-2024 implementation period. Our investment supports the activities of the National Strategic Plan for HIV and AIDS and the National Strategic Plan for TB, which aim to end HIV and AIDS as public health threats in Malawi by 2030 and significantly reduce TB incidence and mortality by 2025.\n

\n

\nTwo malaria programs in Malawi are working together to achieve ambitious reductions in malaria deaths and incidence by 2024. The Global Fund supports critical prevention and treatment activities through our investment of up to a combined total of US$95 million for 2021-2024.\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "fe8c8bf637313237c600022c", + "iso3": "ZWE", + "summary": "
\n

\nThe Global Fund currently has three active investments in Zimbabwe: One HIV grant of up to US$449 million, one tuberculosis (TB) grant of up to US$26 million and one malaria grant of up to US$63 million. This funding is allocated for 2021-2023 and supports Zimbabwe’s continued progress toward ending the three diseases as public health threats.\n

\n

\nProgress\n

\n

\nDespite suffering a prolonged economic crisis which has presented challenges to health infrastructure and services, Zimbabwe has made significant progress in the fight against HIV, TB and malaria. \n

\n

\nAs of 2019, the country of 15 million people had achieved two out of the three 95-95-95 treatment targets for HIV. Of the 1.4 million people living with HIV, 91% knew their status, 93% of people who know their status were on antiretroviral therapy and 86% of people on antiretroviral therapy were virally suppressed.\n

\n

\nIncidence and deaths from TB have steadily reduced since 2011, while malaria mortality has fallen by 75% and malaria incidence by 84% over the past two decades.\n

\n

\nChallenges\n

\n

\nAlthough Zimbabwe has made strong gains against the three diseases, significant challenges remain. As of 2020, the country was off track to reach a key target identified in its National HIV and AIDS Strategic Plan of reducing new HIV infections to 12,600 by 2025. Significant gender, age and geographical inequities present key challenges in Zimbabwe’s fight against HIV.\n

\n

\nTB remains a major public health threat, primarily driven by the concurrent HIV epidemic, high levels of socioeconomic deprivation, migration and incomplete resolution of care, and access needs of high-risk populations.\n

\n

\nRegarding malaria, much investment and effort is still needed to achieve the target of lowering incidence to the levels targeted by Zimbabwe’s National Malaria Strategic Plan.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund has allocated Zimbabwe funding of up to US$449 million for 2021-2023 to engage in innovative approaches to reduce new HIV infections and eliminate stigma and discrimination related to the disease. Our investment supports interventions aligned with Zimbabwe’s National HIV and AIDS Strategic Plan, which aims to accelerate progress toward ending HIV as a public health threat by 2030.\n

\n

\nThe “Gearing up to End TB in Zimbabwe” program was allocated funding of up to US$26 million for 2021-2023. Our investment supports continued activation and scaling-up of TB care, prevention and treatment regimens and intensified screening, case finding and monitoring.\n

\n

\nThe “Moving Zimbabwe Towards Malaria Elimination” program was allocated funding of up to US$63 million for 2021-2023 to activate case detection in new locations and refine vector control measures. Our investment supports the goals of the National Malaria Strategic Plan, which aims to reduce malaria incidence to 5 cases per 1,000 people and malaria deaths by at least 90% by 2025 (compared to 2015).\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "feb539e7633434550b00018a", + "iso3": "NPL", + "summary": "
\n

\nThe Global Fund currently has three active investments in Nepal: One HIV grant of up to US$28 million, one tuberculosis (TB) grant of up to US$20 million and one malaria grant of up to US$4 million. This funding is allocated for the 2021-2024 implementation period and supports Nepal’s continued progress toward ending the three diseases as public health threats.\n

\n

\nProgress\n

\n

\nThe end of a decade-long conflict in 2006 has seen Nepal make lengthy and complex transitions toward a new constitution and federal structure. This increased stability has enabled Nepal to strengthen its health systems and improve the provision and quality of services. \n

\n

\nProgress is reflected in the steadily declining numbers of people being infected with HIV every year. Significant increases have been recorded in the percentage of people living with HIV who know their status (79%), who know their HIV-positive status and receive antiretroviral therapy (81%), or who are on treatment and have a suppressed viral load (86%).\n

\n

\nThe strengthening of health systems has also improved clarity around TB. Although a 2020 National TB Prevalence Survey showed the disease burden to be 1.6 times higher than previously thought, it has created an opportunity to help thousands of people whose needs would have otherwise been unmet.\n

\n

\nNepal has made strong gains against malaria in recent years. The country has transitioned from the control phase to the pre-elimination phase of the disease, and no malaria-related deaths have been reported since 2017.\n

\n

\nChallenges\n

\n

\nDespite the ongoing progress in building resilient and sustainable systems for health in Nepal, financial, sociocultural, geographical, and institutional barriers continue to prevent many people from accessing HIV services.\n

\n

\nGlobal Fund investments support human rights and gender-related interventions to reduce stigma and discrimination against people living with HIV, women, girls and sexual and gender minorities.\n

\n

\nTB remains one of the most prevalent infectious diseases in Nepal and continues to pose a serious threat to the country and its development. While the gap between incidence and notification is lessening, the large disparity that remains presents a key challenge in the fight against the disease.\n

\n

\nWhile cases of people contracting malaria within Nepal are near zero, about 80% of malaria cases appear in people who have been in neighboring India, usually for work-related migration. Reducing the caseload of people who import the disease from other countries remains a challenge.\n

\n

\nGlobal Fund investments\n

\n

\nThe “Fast-track strategy to end the AIDS epidemic by 2030” was allocated funding of up to US$28 million for the 2021-2024 implementation period. Our investment supports the activities of the National HIV/AIDS Strategic Plan, which is guiding Nepal toward achieving the ambitious 95-95-95 targets: 95% of people living with HIV knowing their HIV status; 95% of people who know their status on treatment; and 95% of people on treatment with suppressed viral loads.\n

\n

\nThe Global Fund and our partners are fighting TB in Nepal by scaling up TB care services, ensuring meaningful community engagement and private sector collaboration, increasing the provision of social support to patients, boosting TB preventative services and addressing the social determinants of the disease. These strategies are supported by our investment of up to US$20 million for 2021-2024.\t\n

\n

\nNepal’s “Get to zero” malaria program was allocated funding of up to US$4 million for the 2021-2024 implementation period. Our investment supports community-based treatment, active case detection and investigation, and ongoing distribution of long-lasting insecticidal nets with a goal of eliminating malaria in the country by 2025.\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "fc030bf6653061f1730001ee", + "iso3": "SLB", + "summary": "
\n

\nThe Global Fund currently has two active investments in the Solomon Islands, with funding totaling up to US$9.2 million allocated for 2021-2023. The Solomon Islands has made progress against malaria and tuberculosis (TB); however, challenges remain. Our funding supports the country in its ongoing efforts to end malaria and TB as public health threats.\n

\n

\nProgress\n

\n

\nSustained investment and effective interventions targeting malaria over the last decade have had a significant impact on the disease in the Solomon Islands. \n

\n

\nSevere infections have become uncommon, and there are usually less than 10 malaria-related deaths each year. Almost 100% of people with suspected malaria now receive a test or examination, and the proportion of patients who receive treatment after a positive test or examination has doubled since 2015.\n

\n

\nAlthough trends in TB cases and deaths have remained relatively unchanged, there has been progress against the disease on other fronts. Treatment success rates have consistently been over 90%, failure rates are at 2% and loss to follow up is extremely low. Reassuringly, there are few cases of drug-resistant TB.\n

\n

\nHIV has not traditionally been a dominant focus of disease control in the Solomon Islands as prevalence remains very low at 0.002%.\n

\n

\nChallenges\n

\n

\nThe six major islands and more than 900 smaller islands that make up the Solomon Islands remain one of the most highly malaria-endemic areas in the Western Pacific region. This makes protecting gains in the annual parasite incidence (API) rate an ongoing challenge. The API rate was reduced drastically between the mid-90s and 2014 but has almost quadrupled since. Returning to pre-2015 API rates is a priority.\n

\n

\nStock-outs in rapid diagnostic tests and antimalarial drugs are an additional challenge for the local malaria response. These have presented difficulties in recent years and highlight a need for improvements in supply chain monitoring and function.\n

\n

\nThe Solomon Islands has one of the highest TB caseloads in the Pacific Islands countries and territories. Finding and managing cases remains a challenge – it is estimated that about 20% of TB cases are not detected or reported.\n

\n

\nScreening of presumptive TB cases has fallen in recent years, which has led to a drop in the detection of all forms of TB cases. Notification rates among the elderly are also considerably high.\n

\n

\nGlobal Fund investments\n

\n

\nWe are supporting the Solomon Islands in the fight against malaria through an investment of up to US$8 million for 2021-2023. This funding supports malaria case management and vector control interventions and is helping to enhance the resilience and sustainability of local health systems.\n

\n

\nThe “Reducing the burden of TB and TB/HIV” program was allocated funding of up to US$1.2 million for 2021-2023. Our investment supports case detection and diagnosis, collaborative interventions and coordination and management of disease control programs. These grant activities are helping the Solomon Islands make progress toward its goal of eliminating TB by 2035.\t\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "fe3f801661343157b1000067", + "iso3": "LAO", + "summary": "
\n

\nThe Global Fund currently has one active investment in Lao People’s Democratic Republic (Lao PDR), with tuberculosis (TB) and HIV funding of up to US$15.5 million allocated for 2021-2023. Our investment in malaria in the country is delivered as part of the Global Fund’s Regional Artemisinin-resistance Initiative (RAI). Our funding aims to improve access to quality health and nutrition services in targeted areas of Lao PDR, which will enable the country to extend the strong gains it has made against the three diseases.\n

\n

\nProgress\n

\n

\nLao PDR is a landlocked country in Southeast Asia which, until a recent slowdown worsened by the COVID-19 pandemic, had one of the fastest growing economies in East Asia and the Pacific. Economic growth has allowed Lao PDR to make development progress over the past 20 years, halving poverty, reducing malnutrition and improving education and health outcomes. \n

\n

\nThe national TB and HIV programs have made substantial progress against the two diseases. Since 2002, TB deaths have fallen by 73% and new TB cases by 35%. The TB treatment success rate has remained consistently high, reaching almost 90% for more than five years.\n

\n

\nIn epidemic “hotspots” targeted by earlier grants, HIV testing for key populations has significantly improved. In 2019, 61% of men who have sex with men were tested, compared to 8% in 2016, and over the same period the HIV testing rate for female sex workers increased from 70% to 92%.\n

\n

\nChallenges\n

\n

\nLao PDR still faces large inequities in health coverage due to limited access to services and lack of financial protection for poor and vulnerable populations. This results in stark differences among provinces in the achievement of health outcomes.\n

\n

\nTB and HIV programs can struggle to find and treat cases at a national level due to an array of programmatic challenges. A 2019 review of Lao PDR’s TB and HIV programs highlighted the need to increase the quality of primary health care at health centers, enhance case finding among key and vulnerable groups nationwide and strengthen prevention activities.\n

\n

\nDespite large decreases in malaria deaths and new cases (down by 94% and 84% respectively between 2002-2020), the emergence of drug-resistant malaria in the Greater Mekong region is a significant threat.\n

\n

\nGlobal Fund investments\n

\n

\nFunding of up to US$15.5 million for TB and HIV interventions has been allocated to Lao PDR for 2021-2023. Much of this investment will go toward a “payment for results” mechanism designed to improve access to quality health and nutrition services in targeted areas of the country.\n

\n

\nThe funding model being used in Lao PDR by the Global Fund and our partners supports health sector reform, with a focus on increasing the sustainability and resilience of services and systems. For TB, the objectives of this reform are to increase notification and TB treatment coverage. The focus for HIV is on increasing testing coverage among key populations and expanding treatment.\t\n

\n

\nThe threat of drug-resistant malaria in Lao PDR is being addressed by the Global Fund’s Regional Artemisinin-resistance Initiative (RAI), which also covers neighboring Cambodia, Myanmar, Thailand, and Vietnam.\t\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "fe708d41366362585a0000e4", + "iso3": "MOZ", + "summary": "
\n

\nThe Global Fund currently has four core grants of up to a combined total of US$744 million signed for 2021-2023 for Mozambique. Despite a challenging operating environment, Mozambique has made solid gains against HIV, tuberculosis (TB) and malaria. Our investment supports the country’s work to consolidate this progress and continue to reduce infections and deaths from the three diseases.\n

\n

\nProgress\n

\n

\nMozambique bears among the highest HIV, TB and malaria burdens in the world, and its human resources for health face significant barriers. Within this challenging operating environment, and due in large part to the efforts of advocates and community health workers, the country has achieved solid gains against the three diseases. \n

\n

\nOver the last decade, new HIV cases have fallen by 34%, AIDS-related deaths have declined by 27% and the number of people on antiretroviral therapy has risen by more than 1 million.\n

\n

\nMozambique was one of only a handful of high TB burden countries estimated to have achieved WHO’s “End TB Strategy” 2020 milestone of a 35% reduction in the number of TB deaths between 2015 and 2020.\n

\n

\nIntensive malaria control efforts have helped to reduce malaria deaths by more than 50% since 2002 and have restricted percentage increases in new cases to single figures – significant achievements given that 31 million Mozambicans are at risk of infection.\n

\n

\nChallenges\n

\n

\nThe total number of new HIV infections remains persistently high in Mozambique. Despite new HIV infections among adolescent girls and young women falling by one-fifth between 2010 to 2020, this vulnerable population is still three times more likely to be infected compared to males in the same age group. Low condom usage is a major driver of new infections, while antiretroviral therapy retention presents a major ongoing challenge.\n

\n

\nAlthough TB notification rates have steadily increased, significant gaps remain. The health system misses tens of thousands of people each year due to diagnostic and infrastructure issues. Challenges in sample collection and transportation from remote areas, and shortfalls in logistics and human resources compound the issue. Significant human rights- and gender-related barriers also prevent access to TB services and treatment.\n

\n

\nMalaria incidence is growing in Mozambique, and considerable environmental and logistical complexities often hinder efforts to scale up essential countermeasures. In addition, insecticide resistance is a growing threat, and periodic environmental disasters and ongoing conflict in the country’s northern region are among the many challenges to hard-won progress.\n

\n

\nGlobal Fund investments\n

\n

\nWe are supporting the response to HIV in Mozambique through an investment of up to US$466 million for 2021-2023. This supports two HIV and AIDS programs with the goal of reducing HIV infections and deaths in line with the National HIV Strategy.\n

\n

\nOne TB program and one dual-component (TB/HIV) program were allocated combined funding of up to US$108 million for 2021-2023. Our investment supports interventions aimed at reducing TB incidence and mortality rates, improving and sustaining TB case notification and treatment success rates, and helping to reduce new HIV infections and HIV-related deaths.\t\n

\n

\nThe “Accelerating and Strengthening the Quality of Malaria Control Interventions” program was allocated funding of up to US$200 million for 2021-2023. Our investment supports strengthening program management, vector control, testing, surveillance and other key interventions aligned with Mozambique’s Malaria Strategic Plan.\t\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "273e9f943366388292000025", + "_modified": 1661262260 + }, + { + "_id": "fe98bb09353165e316000202", + "iso3": "AGO", + "summary": "
\n

\nThe Global Fund currently has one active investment in Angola: An innovative, multicomponent program encompassing HIV, tuberculosis (TB), malaria and resilient and sustainable systems for health. Our grant supports a new approach to disease response in Angola, where progress has been limited in the fight against HIV, TB and malaria.\n

\n

\nProgress\n

\n

\nAngola is the largest African country by area completely south of the equator and has a population of 33 million people. The former Portuguese colony suffered a decades-long civil war after gaining independence in 1975, and has been rebuilding its administrative, social and economic infrastructure since a peace agreement in 2002. An economic contraction starting in 2015 led to half a decade of reductions in the health budget and compromised already fragile health systems. \n

\n

\nGiven Angola’s complex operating environment, ensuring that Global Fund grant resources are invested strategically has proven challenging. Our current investment is an opportunity to enhance Angola’s epidemic response and spark sustainable progress in the fight against HIV, TB and malaria.\n

\n

\nWorking with our partners to build on what we have learned from previous programs, we have shifted our investment focus from a national level to a more localized one. We are supporting interventions in two targeted provinces that will provide a scalable point of reference for the three diseases, as well as adaptable frameworks for stronger systems for health.\n

\n

\nChallenges\n

\n

\nAngola has a generalized HIV epidemic; however, rates of prevalence differ vastly between provinces. The HIV burden is not only uneven in a geographic respect – women are disproportionately affected and account for almost 70% of people living with HIV.\n

\n

\nThe country has one of the highest rates of mother-to-child transmission of HIV in the world. Low uptake of antenatal care hampers prevention of mother-to-child transmission of HIV, and access to early infant diagnosis is limited due to capacity being available in only two provinces. Stigma, discrimination, gender-based violence and lack of knowledge are significant barriers to accessing HIV services for key and vulnerable populations.\n

\n

\nThe World Health Organization lists Angola as one of 30 high-burden countries for TB, and for multidrug-resistant/rifampicin-resistant TB. Inequitable distribution of services – 60% of health facilities offering TB care and prevention are in Luanda, the capital – contributes to persistent case detection gaps and poor treatment outcomes. A lack of services at provincial, municipal and rural levels presents a critical barrier to progress against TB in Angola; this is a challenge being addressed by our current investment to optimize the epidemic response.\n

\n

\nMalaria remains the principal cause of morbidity in Angola and is among the most common causes of death. The entire population is at risk of disease, especially in rural areas. Data on malaria vectors and their susceptibility has traditionally been weak; however, it is known that net access and use remain suboptimal. Although testing of suspected cases has increased over time, frequent stock-outs of treatment materials result in low treatment coverage and are an ongoing challenge.\n

\n

\nGlobal Fund investments\n

\n

\nWe are providing support for a program encompassing HIV, TB, malaria and resilient and sustainable systems for health through our investment of up to US$83 million for 2021-2024. This multicomponent grant is an innovative approach to fighting the three diseases in Angola. It is structured to promote compatibility between disease programs, strengthen health systems for quality service delivery, and improve impact by taking a subnational approach that can later be scaled-up to other provinces, or nationally.\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "38dd25b364633120b7000179", + "iso3": "IND", + "summary": "
\n

\nThe Global Fund has ten core grants currently active in India, worth up to a combined total of US$510 million. Our significant investment supports interventions that build on the solid gains India has made against HIV, tuberculosis (TB) and malaria over the past two decades.\n

\n

\nProgress\n

\n

\nWith a population approaching 1.4 billion, India makes up a sizeable proportion of the global HIV, tuberculosis (TB) and malaria burden. Yet, the country has made notable progress in the fight against all three diseases in the past two decades. \n

\n

\nIndia has made strong progress against malaria. Although the country accounted for 85% of the total malaria incidence in Southeast Asia (2018), India reduced the number of malaria cases and deaths by more than 70% between 2002 and 2020.\n

\n

\nIndia has also made significant gains against HIV: New infections dropped by 85% between 2000 and 2020, and AIDS-related deaths have decreased by 22%.\n

\n

\nThe government of India’s ambition to end TB as a public health threat by 2025 – five years ahead of the UN Sustainable Development Goal 3 target – highlights its determination to fight the disease. This bold target is borne out of steady progress: Treatment coverage has doubled since 2010, and new cases and deaths have fallen by 16% and 28% respectively since 2002.\n

\n

\nChallenges\n

\n

\nAlthough HIV prevalence in India is relatively low compared to other middle-income nations, the country’s huge population means that there are roughly 2.1 million people living with HIV – the third largest HIV epidemic in the world. HIV prevalence among key populations and in some defined geographical areas remains high, highlighting the need for increased investments in programs that support vulnerable groups.\n

\n

\nDespite steady progress against TB, there are still more TB-related deaths in India than in any other country. Drug-resistant TB is still a major public health threat, and improving access to care and increasing coverage of preventive services for marginalized, vulnerable and poor communities remain key challenges in India’s fight against the disease.\n

\n

\nRegarding malaria, progress in the implementation of vital interventions (such as distributing long-lasting insecticidal nets) is affected by resourcing challenges. Increasing critical human resources at the implementation and service delivery level is pivotal to malaria elimination in high burden states. In addition, the spread of artemisinin resistance from the Greater Mekong sub-region remains a threat.\n

\n

\nGlobal Fund investments\n

\n

\nDue to the huge scope and scale of interventions needed to effectively fight HIV, TB and malaria in India, the Global Fund invests in multiple programs for each disease.\n

\n

\nThree HIV grants of up to a combined total of US$153 million were signed for 2021-2024. These grants help fund crucial interventions that support the government’s National Strategic Plan (2017-2024) in its goal of ending HIV as a public health threat within the decade.\n

\n

\nFour TB grants and one dual-component (TB and HIV) grant of up to a combined total of US$292 million were signed for 2021-2024. These grants support critical interventions aligned with the National Tuberculosis Elimination Programme, which aims to end TB as a public health threat in India by 2025 by driving rapid declines in disease burden, morbidity, and mortality.\n

\n

\nFunding of up to US$65 million was signed for two malaria grants for 2021-2024. The funding supports Global Fund partners in their mission to reduce malaria-related morbidity by 50% and mortality by 75% in targeted regions by 2023 (compared to 2018).\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "273e9f943366388292000025", + "_modified": 1668683818 + }, + { + "_id": "390a08e93939304bc40000b3", + "iso3": "MLI", + "summary": "
\n

\nThe Global Fund currently has four active investments in Mali, with funding totaling up to €168 million allocated for 2021-2024. Our investments are helping strengthen the country’s fragile health systems and support interventions aimed at extending hard-won progress in the fight against HIV, tuberculosis (TB) and malaria.\n

\n

\nProgress\n

\n

\nMali, a landlocked Sahelian country with a population of 20 million people, has been experiencing instability and conflict since a military coup in 2012 and the occupation of the north by armed groups. Despite the significant constraints this challenging operating environment imposes on the health system, progress against HIV, TB and malaria has still been made. \n

\n

\nThe country, the eighth largest in Africa, has made gains against HIV: New infections have reduced by half since 2002 and AIDS-related deaths have decreased by 52% over the same period.\n

\n

\nDespite the immense disruption to health services, the country has reduced TB deaths over the last decade. It has also achieved small but significant increases in the TB treatment coverage and success rate.\n

\n

\nCompared to a decade ago, a higher percentage of people in Mali now have access to and use long-lasting insecticidal nets. The percentage of people with suspected malaria who receive a diagnostic test has more than doubled, increasing from 42% in 2010 to 93% in 2019.\n

\n

\nChallenges\n

\n

\nReversing the weaknesses of the health system and improving the availability of and access to quality primary health care for the whole population are the central challenges in Mali’s fight against the three diseases. The Global Fund is helping to address this by funding interventions that are making Malian health systems more resilient and sustainable.\n

\n

\nDespite Mali’s downward trend in HIV deaths and new infections, progress toward 95-95-95 HIV testing and treatment targets has been slow. Although the HIV epidemic is generalized, prevalence rates among key and vulnerable populations are many multiples higher than in the general population. Adolescent girls and young women are a specific risk group and are disproportionately at risk of HIV.\n

\n

\nFinding the thousands of people with TB who are missed by health systems after failing to be diagnosed, treated or reported each year is critical to reducing Mali’s TB burden. Our current investment is addressing this challenge by supporting interventions that tackle TB and HIV in community settings with locally led strategies delivered by key populations, community networks and peer educators.\n

\n

\nMalaria is endemic in the central and southern regions of Mali, where 90% of the population lives, while the north is prone to epidemics due to its desert climate. The country’s climate and prevalence of conflict make controlling transmission a significant challenge.\n

\n

\nGlobal Fund investments\n

\n

\nTwo programs focusing on TB and HIV were allocated funding of up to a combined total of €71 million for 2021-2023. Our investment supports the activities of the national TB, HIV and hepatitis program, which aims to reduce TB incidence by 30%, TB mortality by 50% (compared to 2018), and HIV incidence and mortality by 50% (compared to 2019) by 2025.\n

\n

\nOur investment of up to a combined total of €74 million to fight malaria in Mali throughout 2022-2024 prioritizes high-impact interventions that expand on the prevention and case management activities of our previous grant. The funding includes a digitalization component to improve the quality and efficiency of interventions and will be critical in achieving the objectives set by the national strategic plan to fight malaria.\n

\n

\nFunding of up to €22 million was allocated for 2021-2023 to support a program to improve the resilience of health and community systems in Mali. A central focus of the grant is establishing and supporting community health centers with well-trained, supervised community health workers – an initiative that is expected to provide better access to HIV, TB and malaria testing and treatment.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "394087386630301d28000200", + "iso3": "TCD", + "summary": "
\n

\nThe Global Fund currently has three active investments in Chad, with funding totaling up to €114 million allocated for 2021-2024. Our investments aim to strengthen the country’s health systems and extend hard-won progress in the fight against HIV, tuberculosis (TB) and malaria.\n

\n

\nProgress\n

\n

\nChad is a landlocked country in Central Africa with about 17 million inhabitants, more than half of whom are under 18. The country has suffered from political instability, food insecurity, natural disasters and security threats associated with conflicts in bordering countries. \n

\n

\nDespite these considerable challenges, Chad has made hard-won progress against HIV, TB and malaria.\n

\n

\nSignificant reductions in AIDS-related deaths have been achieved (down 60% since 2002) and new HIV cases have fallen by two-thirds since 2002.\n

\n

\nAlthough TB cases and mortality rates remain high, traction has been gained in other respects. The TB treatment success rate is 80%, and as of 2020, 99% of TB patients who have HIV are receiving antiretroviral therapy.\n

\n

\nAs with TB, malaria cases and mortality have not seen a downward trend. Yet, encouragingly, prevention measures are reaching more of the population than ever. The percentage of people with access to and – vitally, using – long-lasting insecticidal nets has increased to almost 50% – up from 8% and 7% respectively in 2010.\n

\n

\nChad has also made progress in strengthening community health systems, including (but not limited to) a robust community health worker program, which has been co-funded by the government, the Global Fund and other donors.\n

\n

\nChallenges\n

\n

\nLow levels of development and education in Chad contribute to poor access to all basic social services.\n

\n

\nHarmful gender norms are prevalent, which contributes to higher HIV prevalence and incidence in young women compared to men. Perinatal HIV transmission has almost halved since 2005, but access to early infant diagnosis and low retention of children at 18 months are key barriers to continued progress in ending mother-to-child transmission of HIV.\n

\n

\nWeaknesses in the health system present a significant, continuing challenge in responding to HIV, TB and malaria in Chad. These deficiencies are exacerbated by human rights and gender barriers to accessing health services. Persistent supply chain problems, such as stock-outs and suboptimal quality of drugs and health commodities, were planned to be addressed by a supply chain transformation plan. However, the implementation of the program has been stymied by lack of funding.\n

\n

\nGlobal Fund investments\n

\n

\nA program focusing on TB and HIV was allocated funding of up to €55 million for 2022-2024. Our investment finances interventions designed to support Chad in achieving 95-95-95 targets for HIV testing and treatment by 2025, lowering TB mortality and morbidity by 75% and 50% respectively by 2025 and reducing human rights-related barriers to HIV and TB services.\n

\n

\nTwo malaria programs in Chad have been allocated funding of up to a combined total of €59 million for 2021-2024. These investments aim to improve the health of the population through a mix of malaria interventions including vector control, targeted prevention initiatives, diagnosis and treatment, and strengthened health systems and supply chains.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "39742a593130367c830003df", + "iso3": "BLZ", + "summary": "
\n

\nThe Global Fund currently has one active investment in Belize: An HIV grant of up to US$3.2 million allocated for 2022-2024. Our funding is helping to step up the fight against HIV in Belize by supporting interventions designed to accelerate the country's progress toward bold testing and treatment targets.\n

\n

\nProgress\n

\n

\nBelize is a small country in Central America with a population of about half a million people. Although progress toward treatment targets for HIV has been gradual, gains have nonetheless been made in some key areas. The number of new HIV infections is lower now than twenty years ago, and the percentage of people living with HIV who are receiving antiretroviral treatment has trended upward over the past decade. \n

\n

\nThere is still much work to be done, but the government has shown steady commitment to public health and has set bold HIV testing and treatment targets. Its current HIV National Strategic Plan embraces evidence-based interventions, innovative and focused strategies to better meet the needs of each priority group and tailored communications strategies to support effective messaging around HIV.\n

\n

\nChallenges\n

\n

\nBelize has the highest HIV prevalence in Latin America and the fourth highest in the Caribbean. The epidemic is concentrated among transgender people and gay men and other men who have sex with men.\n

\n

\nReaching these key populations with effective, sustainable interventions is the central challenge in the country’s HIV response. Information delivery, communication strategies and human resources need to be strengthened to meet the needs of each priority group and support the scale-up of HIV services.\n

\n

\nGlobal Fund investments\n

\n

\nBelize’s “Getting to 90-90-90” HIV program has been allocated funding of up to US$3.2 million for the 2022-2024 implementation period.\n

\n

\nOur investment supports the expansion of differentiated care approaches to increase access to HIV testing and prevention services, strengthening linkage to care for people testing positive for HIV and capacity building for health care providers and community health workers.\n

\n

\nThe grant is supporting Belize’s progress toward its 2025 targets of:\n

    \n
  • 90% of men who have sex with men having had an HIV test in the last 12 months.\n
  • 90% of people living with HIV knowing their status.\n
  • 90% of those who know their status being on antiretroviral treatment.\n
  • 90% of those on treatment achieving viral suppression.\n
\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "6eaff8443535362ab80002df", + "iso3": "ALB", + "summary": "
\n

\nThe Global Fund currently has one active investment in Albania: A dual-component tuberculosis (TB)/HIV program that has funding of up to US$1.6 million allocated for 2021-2022. The country has a low burden of TB and HIV, and this grant is supporting interventions that are preparing Albania’s disease response programs to operate sustainably without Global Fund resources.\n

\n

\nProgress\n

\n

\nLocated on southeastern Europe’s Balkan Peninsula, Albania is a middle-income country that has been transitioning to an open market economy. \n

\n

\nThe country of 3 million people has made solid progress in the fight against TB. Over the past two decades, TB incidence rates have remained low, new TB cases have trended steadily downward and the death rate for the disease has more than halved.\n

\n

\nAlbania continues to experience a low-prevalence HIV epidemic. HIV incidence was recently estimated at less than 0.05% and there were estimated to be about 1,400 people living with HIV.\n

\n

\nIn light of the country’s HIV and TB progress, our current investment is a transition grant. It aims to prepare Albania’s HIV and TB programs to be able to sustain their performance and scale up diagnostics and treatment without Global Fund resources.\n

\n

\nChallenges\n

\n

\nAlthough the number of new HIV cases remains low, it has increased in recent years, more than doubling between 2008 and 2018. Albania's HIV cases are concentrated among people who inject drugs, sex workers, gay men and other men who have sex with men. Reaching these key populations requires interventions that address stigma and discrimination, both of which present an ongoing barrier to reporting, testing and treatment.\n

\n

\nCases of people with both TB and HIV are relatively few; however, co-infection is slowly on the rise. Only half of TB patients were tested for HIV in the last available dataset (2020), which highlights the need for increased vigilance and robust testing to ensure that a complete picture of both diseases is available.\n

\n

\nAmid reconstruction efforts from a deadly earthquake that struck the country in 2019, the COVID-19 pandemic has put added pressure on the Albanian government’s budget and programs. Overcoming logistical and economic challenges faced by the health system is critical to developing resilient and sustainable systems for health and to the success of Albania’s long-term TB and HIV response.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund has allocated a dual-component TB/HIV program funding of up to US$1.6 million for 2020-2022. The TB component of the grant is focused on diagnosing TB patients, linking them to treatment and establishing systems of care for multidrug-resistant TB. The HIV component supports interventions that focus on prevention in key populations and linking HIV-positive patients to care.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "6f32eba23038618fac0001db", + "iso3": "ARM", + "summary": "
\n

\nThe Global Fund currently has one active investment in Armenia: A tuberculosis (TB) and HIV grant of up to US$10 million allocated for 2021-2024. The grant supports interventions aimed at closing the gap in achieving 95-95-95 testing and treatment goals, accelerating progress toward ending TB in Armenia and optimizing health and community systems essential to the country’s TB and HIV response.\n

\n

\nProgress\n

\n

\nLocated in the mountainous Caucasus region between Asia and Europe, Armenia is a lower middle-income country that has made strong gains against TB. Over the past two decades, TB incidence has fallen by 62% and TB deaths (excluding people living with HIV) have reduced by 83%. \n

\n

\nThe country of 3 million people continues to experience a low-level concentrated HIV epidemic, with HIV incidence recently estimated to be less than 0.1%. The last decade has seen double-digit increases in the percentages of people who either know their HIV status, who know their HIV status and are receiving antiretroviral therapy, or who are receiving antiretroviral therapy and have a suppressed viral load. New HIV infections have fallen by 40% since 2002, and although the trend in new diagnoses has crept up in recent years, this is likely due to increased testing rates.\n

\n

\nChallenges\n

\n

\nA large-scale conflict between Armenia and Azerbaijan in late 2020 resulted in a humanitarian and political crisis. The consequences of the conflict, combined with the impact of COVID-19, overloaded the health care system, which led to an acute lack of medical resources, essential medical equipment and supplies. The disruption of service provisions and diversion of critical resources has been a barrier to greater progress in the fight against HIV and TB.\n

\n

\nArmed conflict is not the only cross-border influence on the TB and HIV burden in Armenia. Around 60,000 migrants cross into Russia each year seeking temporary or seasonal work. Armenians who get sick with TB outside the country face challenges in receiving quality treatment due to legal constraints and limitations in access to services. The proportion of people who are diagnosed with TB and have a history of labor migration has more than doubled over the past decade.\n

\n

\nA similar trend has been observed for HIV. Between 2014 and 2019, 69% of those who were newly diagnosed with HIV were people, or partners of people, who had either lived or traveled abroad. 91% of those with a labor migrant history between 2009 and 2018 were related to Russia. Disease knows no borders, and coordinated, collaborative interventions such as those supported by Global Fund investments are critical to the epidemic response, especially in countries like Armenia with many labor migrants.\n

\n

\nGlobal Fund investments\n

\n

\nA program focusing on TB and HIV was allocated funding of up to US$10 million for 2021-2024. Our investment is supporting interventions designed to close the gap in achieving 95-95-95 targets, accelerate progress in fulfilling commitments to end TB in the country and leverage health and community system resources to sustain effective HIV and TB responses, free of barriers to access for key and marginalized populations.\n

\n

\nThe government of Armenia plans to reform the health care financing system by introducing health insurance (planned for 2022). Preventive, diagnostic and medical TB and HIV services are set to be included in the basic benefits package covered by the insurance. This, combined with an extensive existing network of health clinics across the country and interventions supported by the Global Fund investment outlined above, is expected to help reinforce effective, people-centered TB and HIV service delivery.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "6f5789e861666563b7000018", + "iso3": "BTN", + "summary": "
\n

\nThe Global Fund currently has two active investments in Bhutan: A joint HIV and tuberculosis (TB) grant of up to US$2.2 million, and a malaria grant of up to US$1.4 million. This funding is allocated for 2021-2024 and supports Bhutan in continuing the strong progress it has made toward ending HIV and TB as public health threats by 2030 and achieving malaria-free certification from the World Health Organization (WHO) by 2025.\n

\n

\nProgress\n

\n

\nA small, landlocked country nestled in the Himalayas between India and China, Bhutan is considered by the World Bank and WHO to be a development success story. Revenues from hydropower and tourism have helped the country invest substantially in human capital development, which has led to significant improvements in health outcomes. \n

\n

\nThe Bhutan Malaria Program has made strong progress and the disease is now on the verge of elimination. Gains have been so significant that the program aims to achieve zero indigenous malaria cases by 2022 and obtain WHO malaria-free certification by 2025.\n

\n

\nProgress has also been made in the fight against HIV. New HIV infections have halved since 2002, 97% of people living with HIV are enrolled in antiretroviral treatment and 91% of these people have achieved viral load suppression.\n

\n

\nBhutan has shifted its strategic focus from “controlling” TB to “ending” TB. This decision is grounded in the gains made against the disease over the last two decades. Treatment coverage is high (80%), the treatment success rate is over 90% and all people diagnosed with TB are also tested for HIV.\n

\n

\nChallenges\n

\n

\nDiseases do not stop at borders, and Bhutan’s Ministry of Health recognizes that the increasing proportion of cases imported and introduced from neighboring countries presents the greatest threat to eliminating malaria in the country. To address this challenge, Bhutan has strengthened cross-border collaborative interventions, including screening at border entry points, mobile clinics and mass screening of migrant workers at hydropower project sites.\n

\n

\nHIV case numbers estimated by UNAIDS indicate that there is likely a significant gap between known and actual infections in Bhutan. Until recently, HIV case data was not disaggregated by risk behaviors, meaning that a disproportionate burden of infection in key populations is still unrecognized. The Global Fund’s current investment is supporting intensified case finding through a focused approach for timely diagnosis and treatment of missing cases.\n

\n

\nThe estimated incidence of TB in Bhutan depends solely on notification data and has wide confidence limits. Although TB treatment rates are high, one-fifth of cases are estimated to be missed. Additionally, cases of multidrug-resistant TB are on the rise, highlighting the need to strengthen infection control measures.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund has allocated funding of up to US$2.2 million for a joint HIV and TB program in Bhutan for the 2021-2024 investment period. Integration of HIV and TB activities into one grant presents a significant opportunity for efficiencies in screening, testing and diagnosis, treatment and prevention, and administration. Our investment supports the national strategic plans for each disease in their goals of reducing the burden of HIV and TB until they no longer pose a public health threat.\n

\n

\nFunding of up to US$1.4 million has been allocated for 2021-2024 to support Bhutan's national strategic plan for ending malaria in the country. Our investments in activities such as universal access to early diagnosis, the building of rapid detection and response mechanisms, and strategic and targeted vector control interventions are supporting Bhutan in its goal of achieving WHO malaria-free certification by 2025.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "702e69763465314bd8000254", + "iso3": "BEN", + "summary": "
\n

\nThe Global Fund currently supports four programs targeting HIV, tuberculosis (TB) and malaria in Benin, with a total investment of up to €84 million. This funding is allocated for 2021-2023 and supports interventions that build on the hard-won progress the country has made against the three diseases since 2002.\n

\n

\nProgress\n

\n

\nThe partnership between Benin and the Global Fund has achieved significant impact in the fight against HIV, TB and malaria. Those efforts have helped to turn the rapidly rising tide of HIV infections that was inundating the country 20 years ago. New HIV cases have fallen by 67% and AIDS-related deaths have reduced by 59% compared to 2002. \n

\n

\nMalaria is endemic in all 34 health zones across Benin. Yet, hard-won progress against the disease has been made. The percentage of people with suspected malaria who receive a diagnostic test has risen to over 90%, from a base of near-zero in 2010. Over the same period, the number of people who have access to and, critically, use, long-lasting insecticidal nets has doubled.\n

\n

\nBenin’s National TB Program is recognized as one of the most robust in west and central Africa. TB deaths have decreased by 25% and the treatment success rate has remained stable at around 90%. Furthermore, almost 100% of people diagnosed with TB receive an HIV test and multidrug-resistant TB is low in new TB patients.\n

\n

\nChallenges\n

\n

\nDespite the significant gains made against HIV, the fight is far from over. Prevalence among key populations is many multiples higher than in the general population. And, although almost 100% of pregnant women living with HIV start antiretroviral therapy (ART), retaining these women on ART throughout their pregnancy and during the breastfeeding period remains a challenge – one which translates into high rates of mother-to-child-transmission.\n

\n

\nAfter several years of relative stability, malaria incidence is rising. Changing climatic conditions, increased mosquito resistance to insecticides, suboptimal coverage of interventions and environmental changes such as increased urbanization all present ongoing challenges to malaria control in Benin.\n

\n

\nBenin has a low TB detection rate, highlighting the importance of intensifying case detection in the community. Furthermore, shortfalls in TB screening, late presentation to health facilities by TB patients and the costs incurred by patients all remain barriers to timely diagnosis and patient management.\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV programs in Benin were allocated funding for 2021-2023 of up to a combined total of €38 million. These grants help fund crucial interventions that support the government’s National Strategic Plan in its goal of halving new infections and reducing HIV-related morbidity and mortality by two-thirds.\n

\n

\nOur investment of up to €40 million to fight malaria in Benin throughout 2021-2023 is supporting high-impact vector control interventions geared toward cutting morbidity and mortality by at least 50%.\n

\n

\nFunding of up to €7 million was allocated for a program to strengthen the national response to TB throughout 2021-2023. The grant supports the scale-up of community case detection, decentralization and expansion of modern TB diagnostic techniques, targeted preventive therapies and other critical anti-TB interventions.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "707e43b83265307fb200003b", + "iso3": "BFA", + "summary": "
\n

\nThe government of Burkina Faso, the Global Fund and health partners strengthened their partnership in 2021 by launching four new grants to accelerate ending HIV, tuberculosis (TB) and malaria as public health threats and to strengthen health systems. The grants, worth a total of €205 million, cover the 2021-2023 implementation period.\n

\n

\nProgress\n

\n

\nBurkina Faso is a low-income country in the Sahel that, despite facing recent security, humanitarian, social and pandemic-related challenges, has made significant progress in the fight against HIV, TB and malaria. \n

\n

\nSince the Global Fund started investing in Burkina Faso in 2003, AIDS-related deaths and new HIV infections have both fallen by 80%. Uplift in the 95-95-95 testing and treatment measures has been steady: Among people living with HIV in 2020, 76% knew their status (up from 56% in 2015), 72% were receiving antiretrovirals (up from 26% in 2010) and 59% had a suppressed viral load (up from 34% in 2015). \n

\n

\nThe country of 20 million people has also made gains against malaria. The number of people who have access to and use long-lasting insecticidal nets has more than doubled since 2010. The percentage of people with suspected malaria who receive a diagnostic test has risen over the same period from 19% to 90%. Malaria deaths have halved since 2002.\n

\n

\nAlthough trends in TB cases and deaths have remained relatively unchanged, there has been progress against the disease on other fronts. TB treatment coverage has risen to 60%, treatment success rates are now over 80% and the percentage of people living with HIV and TB who receive antiretroviral therapy (ART) has increased from 60% to 85% in the last decade.\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in Burkina Faso has a prevalence in the general population of 0.7%. However, prevalence is many multiples higher among key populations, including female sex workers and men who have sex with men.\n

\n

\nLack of data for other key populations (such as people in prisons and people who inject drugs), ongoing stigma and discrimination, suboptimal levels of ART retention and widespread insecurity in geographic areas with a high HIV burden all present significant challenges to the country’s HIV response.\n

\n

\nTB remains a major public health challenge. Laboratory and health product management were identified as critical to improving the performance of the TB program, and are being supported by a new testing algorithm, updated diagnostics technology and a range of other testing, treatment and management strategies.\n

\n

\nMalaria also remains a significant public health threat and is one of the leading causes of death in Burkina Faso. It is endemic throughout the country and controlling transmission, even with the optimized mix of interventions being used in the current malaria strategy, is a major ongoing challenge.\n

\n

\nGlobal Fund investments\n

\n

\nFour grants, worth a total of €205 million, were allocated to Burkina Faso for the 2021-2023 implementation period. There are grants for HIV and AIDS, TB/HIV, TB, and malaria.\n

\n

\nOur current investments are supporting efforts to scale up HIV programs to achieve the 95-95-95 targets by enrolling more than 84,500 people on ART by 2023, strengthening differentiated HIV services and expanding access to prevention and treatment for key populations.\n

\n

\nOur funding also aims to strengthen the country’s supply chain management, improve TB case detection and procure 16 million mosquito nets for a mass distribution campaign. An important investment is also being made to strengthen community health systems and data quality.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8", + "_modified": 1655133594 + }, + { + "_id": "a6bd481c6164624570000373", + "iso3": "BWA", + "summary": "
\n

\nThe Global Fund currently has one active investment in Botswana: A multicomponent grant of up to US$25 million allocated for 2022-2024. Our funding supports interventions that are helping Botswana make continued progress toward bold targets for reductions in HIV and TB infections and deaths by 2024.\n

\n

\nProgress\n

\n

\nBotswana is a middle-income country with a population of 2.4 million people. Strong and sustained political will within the country has ensured robust investment in programs that have achieved strong gains against HIV, tuberculosis (TB), and malaria.\n

\n

\nProgress against HIV has been significant. AIDS-related deaths fell by 72% between 2002 and 2020, while new HIV infections reduced by 61% over the same period. Botswana has also made great strides in treatment, care and support for people living with HIV who also have TB, with all antiretrovirals being procured directly by the government.\n

\n

\nMalaria control has proven effective in Botswana. Ongoing prevention and treatment interventions by the country have been effective in countering potential increases in cases and deaths.\n

\n

\nChallenges\n

\n

\nDespite the gains made against HIV, Botswana still has the third-highest HIV prevalence in the world, estimated at almost 20% for people between the ages of 15 and 49. Reaching key populations with testing and prevention support remains a key challenge in the country, as does HIV gender-related discrimination, stigma and violence against women and girls in all their diversity.\n

\n

\nBotswana remains one of 30 countries identified by the World Health Organization as having a high TB/HIV burden. HIV is the main driver of TB in the country, with a TB/HIV coinfection rate as high as 49%. Year-on-year performance of TB detection and treatment has been erratic. Global Fund investment is helping to address this by supporting the development and implementation of community-focused activities aimed at improving TB notification, contact tracing and treatment adherence.\n

\n

\nGlobal Fund investments\n

\n

\nThe “Strengthening HIV prevention, reduction of TB and COVID-19 morbidity and mortality” program was allocated funding of up to US$25 million for the 2022-2024 implementation period. The grant is geared toward:\n

    \n
  • Expanding HIV prevention, with an emphasis on key and vulnerable populations.\n
  • Strengthening TB/HIV collaborative activities at planning and delivery levels.\n
  • Preventing TB among people living with HIV, with a focus on training community health workers on therapy initiation and monitoring.\n
  • Increasing awareness of human rights relating to HIV and TB/HIV, improving laws, regulation, and policy, and reducing stigma, discrimination and violence linked to the diseases.\n
  • Supporting complementary procurement methods between government and other Global Fund partners.\n
\n

\nOur investment is underpinned by Botswana’s National Multi-Sectoral HIV and AIDS Response Strategic Framework (2019-2023), which outlines specific targets for significantly reducing TB mortality and incidence, and for cutting new HIV infections and AIDS-related deaths by 2024.\n

\n

\nLast updated May 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8" + }, + { + "_id": "a6e8aaeb383839370e0003c1", + "iso3": "KEN", + "summary": "
\n

\nThe Global Fund currently invests in six grants addressing HIV, tuberculosis (TB) and malaria in Kenya, with total funding of up to US$441 million allocated for 2021-2024. These grants support critical interventions geared toward achieving the country’s bold targets for reducing new cases and deaths from the three diseases.\n

\n

\nProgress\n

\n

\nKenya has made significant progress economically in the last decade, moving from a low-income to a middle-income economy. However, the country of 53 million people still faces challenges in inequality, poverty, transparency and accountability. The COVID-19 pandemic has compounded some of these challenges.\n

\n

\nNonetheless, Kenya continues to make progress in the fight against HIV, TB and malaria. New HIV infections declined from 1.18 per 1,000 population in 2015 to 0.72 in 2020. HIV prevalence declined from 4.9% in 2018 to 4.5% in 2020, and the HIV incidence rate reduced from 0.27% in 2016 to 0.14% in 2020. The TB incidence rate fell by 11% between 2018 and 2020. Malaria prevalence among children under 5 declined from 8% in 2016 to 6% in 2020.\n

\n

\nKenya has also adapted HIV, TB and malaria programs to the COVID-19 pandemic and is successfully mitigating the pandemic’s impact on the three diseases.\n

\n

\nChallenges\n

\n

\nHIV prevalence among key populations and vulnerable groups is disproportionately higher than the national averages, with rates highest among female sex workers, adolescent girls and young women, people who inject drugs and gay men and other men who have sex with men. Kenya’s national HIV strategy identifies the need to focus more on key populations, as well as the continued rollout of quality improvement approaches to support differentiated care delivery for vulnerable groups, as well as the general population.\n

\n

\nDespite downward trends in new TB cases and mortality over the last decade, declining trends in TB case notification and treatment success rates are concerning. Reasons for continued “missing” cases are low awareness of TB, inadequate TB screening in health facilities, suboptimal TB diagnostic and treatment services coverage and limited community and private sector engagement.\n

\n

\nMalaria remains a significant public health and socioeconomic challenge in Kenya. It is endemic in the coastal and lake regions, with high levels of transmission throughout the year. Seasonal malaria transmission occurs in the arid and semi-arid areas of the country. Environmental conditions, along with a need to boost universal coverage of insecticide-treated mosquito nets and disruptions in diagnostic availability caused by supply chain difficulties, are key challenges in Kenya's malaria response.\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV grants in Kenya were allocated funding for 2021-2024 of up to a combined total of US$264 million. Our investments support interventions that aim to reduce new HIV infections by 75%, AIDS-related mortality by 50%, HIV-related stigma and discrimination by 25% and significantly increase domestic financing of Kenya’s HIV response.\n

\nTwo TB grants were allocated funding of up to a combined total of US$96 million for 2021-2024. The investments work together to deliver interventions aimed at ensuring provision of quality care and prevention services for all people in Kenya with TB.\n

\n

\nFunding of up to a combined total of US$81 million was allocated for two malaria programs for 2021-2024. The funding supports Kenya in its mission to reduce malaria incidence and deaths by at least 75% of 2016 levels through a range of high-impact prevention, diagnosis and treatment interventions.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8" + }, + { + "_id": "a7342bf3646131481b000378", + "iso3": "AZE", + "summary": "
\n

\nThe Global Fund currently has one active investment in Azerbaijan: A multi-component HIV and tuberculosis (TB) grant of up to US$17 million allocated for 2021-2024. Our funding supports interventions aimed at accelerating reductions in HIV infections and deaths, driving significant declines in drug-resistant TB and lowering TB incidence, prevalence and mortality.\n

\n

\nProgress\n

\n

\nAzerbaijan is a middle-income country of about 10 million people positioned on the transcontinental boundary of Europe and Asia. In recent years, the country has taken steps to strengthen its health system, improve the quality of health services and decrease out-of-pocket spending on health.\n

\n

\nAzerbaijan is progressing toward achieving the 95-95-95 testing and treatment targets for HIV. Of the approximately 10,000 people living with HIV in the country, 70% knew their status, 75% were on antiretroviral therapy and 81% of people on antiretroviral therapy were virally suppressed (2019 data).\n

\n

\nTB incidence in the country is decreasing, yet the proportion of bacteriologically confirmed cases is increasing. This is due to the increased availability of GeneXpert machines and improvements in sample transportation. TB deaths (excluding people living with HIV) have almost halved since 2002.\n

\n

\nChallenges\n

\n

\nThe key challenges in Azerbaijan’s HIV response are strengthening HIV diagnosis and scaling up antiretroviral therapy enrolment and adherence among the key populations disproportionately affected by HIV in the country (people who inject drugs and gay men and other men who have sex with men).\n

\n

\nDespite the gains made against TB in Azerbaijan over the last two decades, the country remains on the WHO’s global list of 30 high multidrug-/rifampicin-resistant TB (MDR/RR TB) burden countries. MDR/RR TB is an ongoing threat in Azerbaijan and addressing significant regional differences in MDR/RR TB treatment success rates is critical to consolidating and continuing progress against TB.\n

\n

\nGlobal Fund investments\n

\n

\nGlobal Fund investment of US$17 million for 2021-2024 funds essential components of Azerbaijan’s national response programs for HIV and TB. \n

\nOur grant supports HIV interventions that are geared toward reducing HIV incidence and AIDS-related mortality through scaled access to key populations and delivering essential HIV prevention, diagnostic, treatment, care and support services.\n

\n

\nTB interventions covered by the grant are also supporting the country’s bold goals for controlling TB. Azerbaijan aims to reduce the socioeconomic burden of TB by driving significant declines in TB (including drug-resistant TB) incidence, prevalence and mortality.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8" + }, + { + "_id": "9b789d3c643431caf2000222", + "iso3": "NGA", + "summary": "
\n

\nThe Global Fund currently funds eight grants addressing HIV, tuberculosis (TB), malaria and supporting resilient and sustainable systems for health in Nigeria, with a total investment of up to US$903 million allocated for 2021-2023. These grants support critical interventions geared toward continuing the country's significant progress against the three diseases and accelerating the delivery of prevention and treatment services to people who need them most.\n

\n

\nProgress\n

\n

\nWith over 200 million people, Nigeria is the most populous country in Africa, and its economy is the largest on the continent. Nigeria is one of the countries that the Global Fund invests most heavily in. Fighting HIV, TB and malaria in the country requires significant collaboration and resources, due to its size and challenging operating environment.\n

\n

\nDespite the impact of COVID-19 on HIV, TB and malaria programs, Nigeria has continued to make solid progress in the fight against the three diseases. HIV performance has improved significantly, with 90% of people living with HIV knowing their status, 98% of those who know their HIV-positive status on treatment and 95% of those on treatment having suppressed viral loads. New HIV infections have also decreased by 28% since 2012.\n

\n

\nTB notification has increased, and TB treatment coverage rose from 24% to 40% between 2019-2021. Nigeria has also made notable progress in malaria control, reducing the prevalence of the disease by almost 50% between 2010 and 2018.\n

\n

\nChallenges\n

\n

\nNigeria’s political and geographical complexities, combined with security impediments, make it a challenging environment for investments in global health.\n

\n

\nDespite strong gains over the past decade, Nigeria still bears some of the world’s highest burdens of HIV, TB and malaria. With approximately 61 million estimated malaria cases recorded every year, Nigeria has the biggest malaria burden globally. There are approximately 1.8 million people living with HIV in the country – the second-highest number of people living with HIV in sub-Saharan Africa. Nigeria also has the highest number of TB cases on the continent, with around 440,000 cases recorded every year.\n

\n

\nGlobal Fund investments\n

\n

\nDue to the immense scope and scale of interventions needed to effectively fight HIV, TB and malaria in Nigeria, the Global Fund invests in multiple grants for each disease. \n

\nTwo HIV grants were allocated funding for 2021-2023 of up to a combined total of US$311 million. These grants build on learnings from previous investments while contributing directly to the attainment of Nigeria’s national HIV strategic plan goals. The grants aim to reduce HIV incidence and its associated morbidity and mortality by expanding access to HIV prevention, treatment, care and support for general and key populations.\n

\n

\nThree TB grants were allocated funding of up to a combined total of US$154 million for 2021-2023. The investments interoperate with one another to accelerate efforts to end TB as a public health threat by ensuring all Nigerians can access comprehensive, high-quality, patient-centered, community-owned TB services.\n

\n

\nFunding of up to a combined total of US$404 million was allocated for two malaria programs for 2021-2023. The funding supports Nigeria in its mission to reduce the malaria burden to pre-elimination levels and significantly lower malaria-related morbidity and mortality.\n

\n

\nThe Global Fund is also supporting more resilient and sustainable systems for health in Nigeria through a grant of up to US$36 million for 2021-2023.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8" + }, + { + "_id": "9b96228f623538b7d8000187", + "iso3": "SOM", + "summary": "
\n

\nThe Global Fund currently has three active investments in Somalia, with funding of up to a combined total of US$85 million allocated for 2021-2023 for HIV, tuberculosis (TB) and malaria programs. Despite a highly challenging operating environment, Somalia has made gains against the three diseases in several key areas. Our funding supports critical work to consolidate progress and continue to reduce infections and deaths from HIV, TB and malaria. Our investments are also helping to rebuild Somalia’s health system, which has been weakened by decades of conflict.\n

\n

\nProgress\n

\n

\nSomalia is a country of approximately 16 million people located on the Horn of Africa. Decades of civil war and instability, coupled with humanitarian crises such as drought and floods, have weakened Somalia’s health system and contributed to some of the poorest health indicators in the world.\n

\n

\nYet, within this challenging environment, the efforts of advocates, community health workers and other dedicated Global Fund partners have achieved gains in several key areas against HIV, TB and malaria.\n

\n

\nSomalia has one of the lowest HIV infection rates in Africa. Since 2002, AIDS-related deaths have declined by 38% and new HIV infections have fallen by 84%. Although TB deaths and new cases have not trended downward, the success rate for TB treatment is high (91%) and TB treatment coverage is slowly but steadily increasing. Malaria control efforts have helped to reduce malaria deaths and cases by 30% since 2002.\n

\n

\nChallenges\n

\n

\nAfter years of crisis, conditions are unstable and sometimes dangerous, presenting a highly challenging operating environment. Somalia's public healthcare system was largely destroyed during the early stages of the conflict and is in the process of being rebuilt.\n

\n

\nHealth indicators remain poor, including low coverage of essential health services, lack of access to hygiene and sanitation, malnutrition, high burdens of communicable and noncommunicable diseases, high infant and maternal mortality and low life expectancy. This is compounded by an active armed insurgency, governance challenges, weakened service-delivery capacity and fiscal capacity constraints.\n

\n

\nThe health of many Somalis is already extremely compromised due to drought and famine, especially children suffering from malnutrition. We are working closely with our partners to support strategic interventions that build upon the lessons learned from previous grants, with a focus on people living in high-risk and hard-to-reach areas. With these focused and pragmatic investments in prevention and treatment, we are striving to lessen the significant impact HIV, TB and malaria have on the lives of many people in Somalia.\n

\n

\nGlobal Fund investments\n

\n

\nWe are supporting the HIV response in Somalia through an investment of up to US$18.6 million for 2021-2023. Our investment funds interventions that support Somalia in its goals of accelerating progress towards 95-95-95 HIV testing and treatment targets, reducing new HIV cases, mortality and morbidity by 30% and significantly reducing HIV-related discrimination in healthcare settings. \n

\n

\nA TB program in Somalia was allocated funding of up to US$29 million for 2021-2023. The funding is geared toward increasing case detection, raising the rate of treatment completion and success for all forms of notified drug-susceptible TB cases and multidrug-resistant/rifampicin-resistant TB, and achieving significant cuts in TB deaths, incidence rate and the percentage of families facing catastrophic costs because of the disease.\n

\n

\nSomalia’s “Maximizing efforts to control and eliminate malaria” program was allocated funding of up to US$37 million for 2021-2023. Our investment supports activities to attain zero malaria deaths and reduce malaria incidence to at least 0.5/1,000 population by 2025. It also incorporates a funding component to help strengthen the country’s health system capacity for evidence generation, supply management and community engagement.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8" + }, + { + "_id": "9bbb101c3437344c0400013d", + "iso3": "LKA", + "summary": "
\n

\nThe Global Fund currently has three active investments in Sri Lanka, with funding of up to US$35 million allocated for 2021-2024 to HIV, tuberculosis (TB) and health systems strengthening programs. Our investments are supporting critical work to continue to reduce infections and deaths from HIV and TB, and to help build a more resilient and sustainable health system.\n

\n

\nProgress\n

\n

\nSri Lanka is an island country of 22 million people located to the south of the Indian subcontinent in the Indian Ocean. The country’s health system has achieved positive health outcomes above what is commensurate with its income level and has gained international recognition as a model of “good health at low cost.”\n

\n

\nSri Lanka was certified by the World Health Organization in 2016 as having eliminated malaria. However, health authorities in the country stay vigilant as a reintroduction of the disease from imported cases remains a threat.\n

\n

\nAIDS-related deaths have trended downward for the last decade, reversing the increases that were observed until 2012. New HIV infections have fallen by 71% since 2002, and TB deaths have almost halved over the same period.\n

\n

\nChallenges\n

\n

\nEconomic and political instability are issues of concern in the country. April 2022 saw the tipping point in a financial crisis that resulted in Sri Lanka struggling to pay for essentials, including food, fuel and medicines.\n

\n

\nStigma and discrimination remain key factors in Sri Lanka’s HIV epidemic, leaving most key populations hidden. Prevalence among young gay men and other men who have sex with men is of particular concern, as new HIV infections are concentrated among those aged 15-24 in these groups. Peer educators are hired to conduct direct outreach within the community; however, the legal environment can be a prohibitive factor in reaching key populations and offering HIV testing. Key populations and people living with HIV continue to face stigma and discrimination when accessing health services.\n

\n

\nAlthough the TB burden in Sri Lanka is classified as low, incidence has stagnated since 2000, with no decline in the number of new cases per year. The National Strategic Plan for TB reports that treatment coverage is only 59% and up to 5,500 people with TB may be missing each year. Challenges in the country’s TB response also include low TB preventative therapy among vulnerable groups, low case notifications and treatment success for drug-sensitive and/or multidrug-/rifampicin-resistant TB, and lack of interventions for key populations.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund is supporting Sri Lanka’s HIV response through an investment of up to US$6 million for 2022-2024. The core focus of the grant is to scale up the prevention program for key populations and provide HIV testing for 90% of key populations reached, with a goal of ending HIV in the country by 2025. The implementation arrangement of the grant is designed to pave the way toward a planned transition out of Global Fund funding. \n

\n

\nFunding of up to US$3 million was allocated for 2022-2024 for Sri Lanka’s TB program. The grant has six key targets, which are aligned with the country’s goals of achieving a 90% reduction in TB incidence and an 80% reduction in mortality by 2030 (compared to 2015 levels).\n

\n

\nWe are also investing in strengthening Sri Lanka’s health system, with up to US$25 million allocated for 2021-2024 for interventions that support an effective, efficient, economical and equitable health service.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "e032e85161303584ff0002f8" + }, + { + "_id": "9bfbc77b32656264ca00007e", + "iso3": "VNM", + "summary": "
\n

\nThe Global Fund has three core grants currently active in Vietnam, worth up to a combined US$120 million. Our investments support Vietnam’s strategies to continue to reduce new cases and deaths for HIV and tuberculosis (TB) and limit the ongoing threat of drug-resistant TB.\n

\n

\nProgress\n

\n

\nVietnam is a lower middle-income country in Southeast Asia that has made steady progress against HIV and TB.\n

\n

\nVietnam was one of only six high TB burden countries estimated to have achieved the World Health Organization (WHO) “End TB Strategy 2020” milestone of a 35% reduction in the absolute number of TB deaths between 2015 and 2020. WHO also identified Vietnam as one of 30 high multidrug-/rifampicin-resistant TB burden countries to have achieved rifampicin resistance testing coverage of more than 80% in 2020.\n

\n

\nSteady gains have been made against HIV. AIDS-related deaths fell by 48% between 2002 (when the Global Fund was founded) and 2021, while new HIV infections declined by 72% over the same period. Progress toward 95-95-95 HIV testing and treatment targets has trended upward, and prevention of mother-to-child transmission coverage was 75% in 2021.\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in Vietnam is concentrated among three key populations: female sex workers, people who inject drugs and gay men and other men who have sex with men. Sexual partners of people in these key populations, along with transgender women and amphetamine-type stimulant users, also have a high risk of HIV infection.\n

\n

\nHealth authorities in Vietnam are concerned with the rising prevalence of HIV among gay men and other men who have sex with men, particularly those aged 16-29. Additionally, reaching people who inject drugs presents a challenge as roughly 70% of this population live in mountainous areas where geographic access is a problem. Strengthening testing, diagnosis and treatment for these populations and their partners is a key focus of the national HIV control strategy, toward which the Global Fund currently contributes funding.\n

\n

\nThe threat of HIV drug resistance is being monitored as part of a national prevention and surveillance plan. Health authorities believe monitoring will prove critical in preventing the emergence of HIV drug resistance in the future as there can be an increased risk of antiretroviral drug resistance stemming from treatment interruptions.\n

\n

\nVietnam is identified by WHO as being one of 30 countries with the highest burdens of TB globally. Finding missing people with TB, and finding them earlier, are key challenges in Vietnam’s TB response. Despite progress against drug-resistant TB, it remains an ongoing threat, with WHO identifying Vietnam as having one of the 30 highest multidrug-/rifampicin-resistant TB burdens in the world.\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV grants totaling up to US$55 million have been signed for Vietnam for 2021-2023. Our investments are supporting the country in its goal of reducing new HIV infections to below 1,000 cases per year, reducing the AIDS mortality rate to less than one death per 100,000 per year and ending HIV as a public health threat in the country by 2030. \n

\n

\nA grant of up to US$66 million was signed for 2021-2023 to support Vietnam’s National Tuberculosis Program. Our investment supports efforts to find people with TB, latent TB infection treatment and a range of other focused interventions aimed at reducing TB prevalence and mortality and keeping the drug-resistant TB incidence rate under 5% of new cases.\n

\n

\nVietnam is included in several multicountry grants being implemented across groups of countries in the region. It also receives funding as part of the Global Fund’s COVID-19 Response Mechanism, which supports countries to mitigate the impact of COVID-19 on programs to fight HIV, TB and malaria, and initiates urgent improvements in health and community systems.\n

\n

\nLast updated June 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "e032e85161303584ff0002f8", + "_mby": "273e9f943366388292000025", + "_modified": 1668686769 + }, + { + "iso3": "CAF", + "summary": "
\n

\nThe Global Fund currently has three active investments in the Central African Republic, with funding totaling up to €125 million allocated for 2021-2023. Our investments aim to strengthen the country’s health systems and extend hard-won progress in the fight against HIV, tuberculosis (TB) and malaria.\n

\n

\nProgress\n

\n

\nThe Central African Republic has experienced conflict and political instability for more than a decade. Yet despite the considerable challenges, the country has made progress in some key aspects of the fight against HIV, TB and malaria.\n

\n

\nSignificant reductions in AIDS-related deaths have been achieved (down 73% in 2020 compared to 2002) and new HIV cases have fallen by three-quarters since 2002.\n

\n

\nAlthough the numbers of TB cases and deaths have not trended downward, traction has been gained in other important respects. The TB treatment success rate is above 80% (2019), TB treatment coverage almost doubled between 2010 and 2020, and in 2020 93% of people living with HIV who also had TB were receiving antiretroviral therapy.\n

\n

\nMalaria testing and treatment measures have helped to cut malaria deaths significantly – by 35% between 2002 and 2020. The number of new malaria cases per year remains high, yet it too has seen a slight decline since 2002.\n

\n

\nChallenges\n

\n

\nDespite the hard-won gains made against HIV, TB and malaria in the Central African Republic, there is still much work to be done. Health systems compromised by years of conflict and volatile macroeconomic conditions remain fragile, and hundreds of thousands of internally displaced people and refugees present a care burden larger than current resources can support.\n

\n

\nViolence in some areas of the country increases the displacement of not just the general population, but also the health staff and humanitarian workers critical to the response to diseases in the poorest and most vulnerable communities. The unpredictable and often shifting geographic scope and scale of violence and insecurity results in barriers to accessing regular testing and treatment services at health facilities and affects the availability and readiness of health personnel and services at community and facility levels.\n

\n

\nIncreasing the accessibility of HIV treatment in more of the Central African Republic’s health facilities is a central challenge in the country’s epidemic response, as is reaching key and vulnerable populations with timely, tailored interventions. Prevalence rates among sex workers and gay men and other men who have sex with men are many multiples higher than in the general population. Stigma and discrimination remain significant barriers to HIV testing and treatment.\n

\n

\nTB is a leading cause of death among communicable, maternal, neonatal and nutritional diseases in the Central African Republic. Multidrug-resistant TB presents a particular threat, with the number of confirmed cases rising since the country started reporting data in 2010.\n

\n

\nMalaria remains the leading cause of morbidity and mortality in the Central African Republic. Gaining access to conflict-affected areas and reaching displaced populations are key challenges in the country’s malaria response. Other critical areas of focus are scaling up appropriate diagnostics and treatment services both at health facilities and at the community level, reinforcing the national supply chain management system and supporting quality reporting data through supervision and on-the-job training.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund’s 2021-2023 TB and HIV grant of up to €69 million continues our commitment to fighting these diseases in the Central African Republic. Our investment is focused on disease and resilient and sustainable systems for health strategies that support the country in achieving its 2023 goals for HIV and TB testing and treatment.\n

\nWe are also supporting the fight against malaria in the Central African Republic through a grant of up to €55 million signed for 2021-2023. This investment aims to improve the health of the population through a mix of strategic malaria and resilient and sustainable systems for health (RSSH) interventions including vector control, chemoprevention for pregnant women, supply chain quality improvements and behavior change and education initiatives. These activities are designed to support the Central African Republic in achieving its goal of a 50% reduction in malaria morbidity and mortality in the general population by 2023 (compared to 2015).\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1656678496, + "_created": 1656678257, + "_id": "4130bc3c3330333dd100028c" + }, + { + "iso3": "GUY", + "summary": "
\n

\nThe Global Fund currently invests in two country-specific grants in Guyana: A multicomponent HIV and tuberculosis (TB) grant of over US$4 million allocated for 2022-2024, and a malaria grant of over US$3 million allocated for 2020-2022. Our investments support activities that build on Guyana's steady gains against HIV, TB and malaria and aim to accelerate the country’s progress toward ending the three diseases as public health threats.\n

\n

\nProgress\n

\n

\nGuyana has made steady progress in the fight against HIV, TB and malaria over the past two decades. AIDS-related deaths have fallen by 20% since 2002 and new HIV infections have declined by 56% over the same period.\n

\n

\nNinety-four percent of people living with HIV know their status, and while progress has been less significant in terms of other 95-95-95 testing and treatment criteria, the percentages of people living with HIV who receive antiretroviral drugs (66% in 2020) and who have a suppressed viral load (57% in 2020) have trended upward over the past decade.\n

\n

\nImprovements in TB control activities have contributed to reductions in deaths (down 18% since 2002) and new TB cases (down 16%). The percentage of people who are co-infected with TB and HIV and receive antiretroviral drugs rose from 59% in 2010 to reach 77% in 2020.\n

\n

\nAlthough there have been periods in the past decade when new malaria cases in Guyana have increased – due in part to inflows of migrants to mining camps in forested regions – their overall trend has been downward, declining by almost one-third. Malaria deaths fell by 33% between 2002 and 2020.\n

\n

\nChallenges\n

\n

\nGuyana’s HIV epidemic is concentrated among transgender people, sex workers and gay men and other men who have sex with men. Reaching these key populations with effective, sustainable interventions is the central challenge in the country’s HIV response. Our current investment focuses on increasing coverage for key populations through a combined prevention approach, strengthening and expanding access to treatment and care services for vulnerable groups, and optimizing antiretroviral delivery to strengthen adherence, retention and viral suppression for people living with HIV.\n

\n

\nDespite Guyana’s gains against TB, it still faces challenges in case detection, contact tracing and reaching vulnerable groups – mainly migrants and indigenous people. A lack of drug-susceptibility testing, limited lab support in hinterland regions and a suboptimal courier system for transporting multidrug-resistant TB samples have contributed to poor detection rates in drug-resistant TB cases. Additionally, although antiretroviral coverage is relatively high for people who are co-infected with TB and HIV, HIV testing among people with TB has declined, highlighting a need to scale up that intervention for this group. These challenges are being addressed by dedicated modules within the current Global Fund investment.\n

\n

\nGuyana represents about 3% of malaria cases in the Americas, with approximately 20,000 cases a year. Cases occur primarily in the hinterland regions of the country, which attract migrant populations drawn by gold and diamond mining and logging. Reaching people in these remote areas – where there are few established health facilities and limited access to health services – with timely malaria prevention interventions, diagnosis and treatment is the central challenge in the country’s malaria response. Additionally, evidence of artemisinin resistance has appeared in some border areas and presents an ongoing threat.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund investment of over US$4 million for 2022-2024 funds essential components of Guyana’s national programs for HIV and TB.\n

\nThe grant supports HIV interventions geared toward reducing HIV incidence and AIDS-related mortality through scaled access to key populations and delivering essential HIV prevention, diagnostic, treatment, care and support services. TB interventions focus on TB care and prevention, increased detection and success rates for multidrug-resistant TB, and TB/HIV collaborative activities that further integrate the delivery of services for both these public health threats.\n

\n

\nMalaria funding of over US$3 million was signed for 2020-2022. Our investment funds activities support Guyana in its aim of achieving a 75% reduction in malaria cases by 2025 (relative to 2015), and a 90% reduction by 2030.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1656679507, + "_created": 1656679302, + "_id": "41d0437f316663aa96000320" + }, + { + "iso3": "LBR", + "summary": "
\n

\nThe Global Fund has three active investments in Liberia: Two grants totaling up to US$38 million for 2021-2023 focusing on HIV and tuberculosis (TB), and another of up to US$18 million for 2021-2024 that is contributing to the country’s malaria response. Our investments support strengthened promotion, prevention, care and follow-up interventions for HIV, TB and malaria, with a focus on key and vulnerable populations and an emphasis on community-led service delivery and epidemic surveillance.\n

\n

\nProgress\n

\n

\nLiberia was the epicenter of a 2014 Ebola outbreak that claimed more than 11,000 lives across West Africa. The epidemic overwhelmed the country’s health systems and interrupted prevention and treatment for HIV, TB and malaria. Yet, progress in some key areas against the three diseases has been made.\n

\n

\nAIDS-related deaths and new HIV infections both fell by two-thirds between 2002 and 2020. Over the same period, malaria deaths were reduced by 25%. Although TB deaths and new cases have not trended downward, significant gains in the TB treatment success rate have been sustained for several years at around 75%. Additionally, the percentage of TB patients living with HIV who receive antiretroviral drugs increased from near-zero in 2010 to 90% percent in 2020.\n

\n

\nChallenges\n

\n

\nLiberia has a low generalized HIV epidemic. However, there are significant variations in prevalence according to gender, age and location. Nine out of 15 counties account for 90% of the disease burden, and HIV prevalence among women aged 15-24 is three times higher than for men in the same age group.\n

\n

\nKey populations are disproportionately affected by HIV in Liberia. Infection rates among transgender people, female sex workers, people who inject drugs and gay men and other men who have sex with men are many multiples higher than in the general population. The Global Fund’s current investment is aligning HIV interventions to address this disproportionate burden of disease among key populations, as well as among rural communities and adolescent girls and young women.\n

\n

\nTuberculosis remains a major public health problem in Liberia. Human rights and gender inequalities, stigma and discrimination increase vulnerability to TB infection and reduce access to services (this also applies to HIV). These barriers, as well as the ongoing threat of drug-resistant TB, present key challenges in the fight against TB in the country.\n

\n

\nMalaria is endemic in Liberia, with continuous transmission throughout the year. The country’s climate and geography make vector control an ongoing challenge, and the entire population of almost 5 million people is at risk of the disease. Pregnant women, children under five (for whom malaria remains the leading cause of death) and people living in remote communities are the most vulnerable to developing malaria and suffering severe outcomes.\n

\n

\nGlobal Fund investments\n

\n

\nLiberia's two joint-component HIV and TB programs were allocated funding of up to US$38 million for 2021-2023. Our investments support interventions that aim to contribute to an HIV-free Liberia and help end TB in the country by 2035.\n

\nWe are also investing in a program that aims to reduce the malaria burden in Liberia by 75% by 2025 (compared to 2016). Our investment of up to US$18 million for 2021-2024 supports a spectrum of interventions designed to increase prevention and reduce malaria cases and deaths, with a focus on children, pregnant women and vulnerable communities. The program is providing ongoing funding for community health workers, who have been pivotal in delivering epidemic surveillance.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1656680183, + "_created": 1656680013, + "_id": "423cbb5330376191fc00025d" + }, + { + "iso3": "NIC", + "summary": "
\n

\nThe Global Fund has two active investments in Nicaragua for 2022-2024: One grant of up to US$12 million focusing on HIV and tuberculosis (TB), and another of up to US$12 million contributing to the country’s malaria response. Our investments support strengthened promotion, prevention, care and follow-up interventions in key and vulnerable populations and in geographic areas with the highest burdens of HIV, TB and malaria.\n

\n

\nProgress\n

\n

\nNicaragua is a country of almost 7 million people in Central America that has made improvements in some key health outcomes over the past few decades.\n

\n

\nStrategies to expand testing and counseling, link newly diagnosed individuals to treatment, intensify case finding and preventive treatment, implement differentiated service delivery models and strengthen laboratory, health information, and surveillance systems have helped to guide progress against HIV, TB and malaria.\n

\n

\nKey HIV testing and treatment areas have seen significant increases. 65% of people living with HIV know their status (up from 47% in 2015). The percentage of people living with HIV who are receiving antiretroviral therapy rose from 18% in 2010 to 50% in 2020, while the percentage of people living with HIV who have a suppressed viral load increased from 22% in 2015 to 86% in 2020.\n

\n

\nDeaths from TB have almost halved since 2002, and the fight against malaria has been strengthened in recent years by a structured diagnostic, treatment, investigation and response strategy.\n

\n

\nChallenges\n

\n

\nNicaragua faces a variety of challenges in controlling the spread of HIV. Gender norms can inhibit women from actively speaking to their male partners about safe sex. There is also continuing stigma and discrimination against sex workers, people living with HIV and gay men and other men who have sex with men.\n

\n

\nApproximately 20% of the country is at risk of malaria. Limitations on health care delivery models and unequal distribution of resources and medical personnel have contributed to a suboptimal quality of care in remote areas of Nicaragua, especially among rural communities in the Central and Atlantic regions where the burden of malaria is concentrated. To respond to the dynamic needs of these places, the government has adopted a decentralized model that emphasizes community-based preventive and primary medical care.\n

\n

\nGlobal Fund investments\n

\n

\nNicaragua's HIV and TB program was allocated funding of up to US$12 million for the 2022-2024 implementation period. Our funding supports strengthening promotion, prevention, care and follow-up interventions aimed at key and vulnerable populations in geographic areas with the highest burdens of HIV and TB.\n

\nThe Global Fund’s current investment is one of the main axes of integration between Nicaragua’s HIV and TB programs. Integration opportunities identified through the program have reinforced sustainability, enhanced efficiency and improved results for both diseases.\n

\n

\nWe are also investing in the elimination of local malaria transmission and preventing its reestablishment in Nicaragua. A program to strengthen detection and diagnosis, treatment, case investigation and response, vector control interventions, and information systems and training was allocated funding of up to US$12 million for 2022-2024. Community participation through a network of voluntary collaborators is a key pillar of the program, and will be critical to its effectiveness, efficiency and success.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1656680883, + "_created": 1656680706, + "_id": "42a65e3939303634f60001eb" + }, + { + "iso3": "GHA", + "summary": "
\n

\nThe Global Fund currently has five active investments in Ghana: two dual-component tuberculosis (TB) and HIV grants of up to a combined total of US$93 million, two malaria grants of up to a combined total of US$128 million and an HIV grant of up to US$17 million. This funding is allocated for the 2021-2023 investment period and supports Ghana’s aims of continuing progress against HIV, securing stronger gains against TB and achieving bold targets in the fight against malaria.\n

\n

\nProgress\n

\n

\nGhana has made steady progress against HIV in the last two decades. In 2020, 68% of people living with HIV knew their status, up from 48% five years earlier. The percentage of people living with HIV who know their status and are receiving antiretroviral therapy has significantly increased, rising from 13% to 60% (2010-2020), as has the percentage of people living with HIV who are receiving antiretroviral therapy and are virally suppressed – up from 13% in 2015 to 44% in 2020. AIDS-related deaths have fallen by 42% since 2002, and new HIV infections have fallen by one-third over the same period.\n

\n

\nAlthough TB deaths and new cases have not trended downward, the success rate for TB treatment has remained relatively high (85%) since 2010. Gains were seen in the success rate for drug-resistant TB, which rose from 33% in 2010 to 56% in 2018. Additionally, in 2020, 90% of people who were co-infected with HIV and TB were receiving antiretroviral therapy, compared to just 18% ten years earlier.\n

\n

\nSignificant progress has been made against malaria. Mortality from the disease fell from 10.8 per 100,000 people in 2012 to 1.1 per 100,000 people in 2019 (a 90% decrease), while the fatality rate among children under five reduced from 0.6% to 0.1% in the same period. Progress has also been made in testing suspected malaria cases, up from 76.3% in 2015 to 93.7% in 2019.\n

\n

\nChallenges\n

\n

\nGhana is classified as having a generalized HIV epidemic, with key populations disproportionately affected. While HIV prevalence among female sex workers has reduced since 2015, it has trended upward among gay men and other men who have sex with men. This is being addressed by expanding behavioral change interventions such as distributing and promoting the use of condoms and lubricants to reach more people within key populations, as well as expanding interconnected community models to better meet their needs.\n

\n

\nPrevention of mother-to-child transmission is also a key focus of Ghana’s HIV response. Our current investments support interventions aimed at improving antiretroviral therapy access, linkage, initiation and retention for pregnant women and breastfeeding mothers who are living with HIV.\n

\n

\nTB remains a major public health problem in Ghana, with high numbers of deaths and new cases. The epidemic is generalized, with case notification varying according to geographic location due to regional differences in access to health facilities. Drug-resistant TB presents an ongoing threat, although the exact burden is unknown. This challenge is being addressed by scaling up early-detection activities and strengthening treatment initiation and adherence.\n

\n

\nDespite the significant progress made against malaria over the past decade, challenges remain. Malaria parasitemia for children under five remains high (although it has declined in recent years), and the disease burden varies at regional and district levels. Our current malaria grant is geared toward protecting populations in the highest malaria burden districts (in the Upper West and Upper East regions of the country) and all prisons through the implementation of indoor residual spraying, health care and community capacity building, monitoring and evaluation, operational research, and behavior change communication.\n

\n

\nGlobal Fund investments\n

\n

\nTwo grants focusing on TB and HIV were allocated funding of up to a combined total of US$93 million for 2021-2023. An HIV-specific grant of up to US$17 million to scale up the quality of HIV care cascade through community engagement and addressing human rights barriers was also signed for the same period. Our investments support the activities of the National Strategic Plan for HIV and the National Strategic Plan for TB. Both plans focus on reducing new infections and deaths, increasing treatment and reaching more people among key populations.\n

\nTwo Global Fund-supported malaria programs in Ghana are working together to achieve ambitious reductions in malaria deaths and incidence by 2023. Our malaria grants of up to a combined total of US$128 million for the 2021-2023 implementation period support critical prevention and treatment activities in the country.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1656681301, + "_created": 1656681149, + "_id": "42e9fb3531326155780001eb" + }, + { + "iso3": "SLE", + "summary": "
\n

\nThe Global Fund has allocated funding of up to US$126 million for two multicomponent grants in Sierra Leone for 2021-2024. Our investments support the country in its goal of reducing its high burdens of HIV, tuberculosis (TB) and malaria and making quality health care more accessible and less expensive.\n

\n

\nProgress\n

\n

\nSierra Leone had made hard-fought gains in public health when an outbreak of the Ebola virus erupted in 2014. The disease killed nearly 4,000 people, compromised the country’s health systems – already weakened by 11 years of armed conflict – interrupted prevention and treatment for HIV, TB and malaria, and increased the number of deaths from the three diseases.\n

\n

\nIn the years since, investments from the Global Fund and other development partners have supported the country’s Health System Recovery Plan to rebuild resilient and sustainable systems for health (RSSH). While there is still much work to be done, RSSH activities have been a key driver of progress in the fight against HIV, TB and malaria.\n

\n

\nMalaria deaths have fallen by 45% since 2002. Sierra Leone was one of only six high TB burden countries estimated to have achieved the World Health Organization (WHO) “End TB Strategy 2020” milestone of a 35% reduction in the absolute number of TB deaths between 2015 and 2020. Gains against HIV have been moderate but meaningful, with deaths and new cases trending steadily downward over the past two decades.\n

\n

\nChallenges\n

\n

\nSierra Leone’s entire population is at risk of malaria. While incidence of the disease has reduced in recent years, it remains both a serious public health challenge and a driver of poverty. Scaling up prevention measures (such as access to and use of insecticide-treated nets), implementing effective community-based monitoring and standardizing national monitoring and reporting have been identified as critical steps toward reducing Sierra Leone’s malaria burden.\n

\n

\nWHO reports that reductions in TB can more successfully be achieved if diagnostic, treatment and prevention services are accessible to all without suffering financial hardship. WHO reports that a high proportion of the general population in Sierra Leone faces “catastrophic” health expenditure and rates service coverage as “low.” These determinants of universal health coverage, as well as several thousand missing people with TB and the presence of drug-resistant TB, are key challenges in Sierra Leone’s TB response.\n

\n

\nKey and vulnerable populations are disproportionately affected by HIV in Sierra Leone, and the prevalence among women aged 15-49 is double that of men in the same age group. Increasing HIV testing and treatment coverage from current levels – some metrics have only seen small rises in recent years – is a key challenge.\n

\n

\nGlobal Fund investments\n

\n

\nTwo Global Fund grants of up to a combined total of US$126 million for 2021-2024 are addressing HIV, TB, malaria and RSSH in Sierra Leone.\n

\nOur investments build on the lessons learned from earlier grants and include a strong focus on activities at a community level. The grants continue to prioritize achieving quality and accessible health care for all. A spectrum of interventions that make up the RSSH component of the grant will underpin the effective delivery of HIV, TB and malaria services and support Sierra Leone in achieving its prevention, testing and treatment goals for each disease.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1656681586, + "_created": 1656681468, + "_id": "431abeb7366534261700023d" + }, + { + "iso3": "KAZ", + "summary": "
\n

\nThe Global Fund currently has two core grants active in Kazakhstan: an HIV grant of up to US$7 million signed for 2021-2023 and a tuberculosis (TB) grant of up to US$8 million signed for 2020-2022 . Our investment supports catalytic investments designed to strengthen the sustainability and continuity of prevention, care and treatment services for HIV and AIDS, and to supply effective responses to drug-resistant TB, which remains an ongoing challenge in Kazakhstan.\n

\n

\nProgress\n

\n

\nKazakhstan is a transcontinental country located on the border of Asia and Europe. After becoming independent from the Soviet Union in 1991, the country experienced rapid economic growth, which transformed it into an upper middle-income economy. Revenues from abundant hydrocarbon resources have helped to reduce poverty (although people in rural and economically isolated areas are still vulnerable) and finance the country’s HIV and TB responses.\n

\n

\nKazakhstan has made progress toward 95-95-95 testing and treatment targets for HIV. In 2020, 78% of people living with HIV knew their status, 73% of those who knew their status received antiretroviral therapy and 84% of people receiving antiretroviral therapy were virally suppressed.\n

\n

\nTB re-emerged as a significant public health threat in the 1990s. Over the last decade, however, estimated TB incidence in Kazakhstan substantially decreased. New TB cases have halved since 2002, and TB deaths (excluding people living with HIV) have reduced by 86%.\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in Kazakhstan remains concentrated among key populations, mostly among people who inject drugs and gay men and other men who have sex with men . Prevalence among the latter key population doubled between 2015-2019.\n

\n

\nHuman rights-related barriers to HIV prevention, treatment and care services for key populations and people living with HIV present an ongoing challenge. The current Global Fund HIV grant supports interventions to address stigma and discrimination and offer community based legal support to key populations and other people living with HIV.\n

\n

\nA central challenge for Kazakhstan’s National Tuberculosis Program is the high burden of drug-resistant TB. Drug susceptibility testing in 2018 revealed that the proportion of rifampicin-resistant TB was 25% among new cases and 46% among previously treated cases. The primary objective of Kazakhstan’s national TB program is to implement effective responses to drug-resistant TB, and to sustain them through people-centered and evidence-based approaches, primarily for vulnerable and at-risk populations.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$7 million has been signed for Kazakhstan for 2021-2023 . Kazakhstan’s HIV program was allocated funding of up to US$7 million for 2021-2023 . Our investment supports interventions aimed at strengthening the sustainability and continuity of HIV prevention, care and treatment services for key populations.\n

\nWe are continuing to support the TB response in Kazakhstan through a grant of up to US$8 million signed for 2020-2022 . Funding of up to US$8 million was allocated for a program to support Kazakhstan’s national response to TB throughout 2020-2022. Our investment supports high-impact interventions, such as rolling out modern molecular diagnostic technologies at a district level and scaling up drug susceptibility testing and treatment with new World Health Organization-recommended regimens in prisons.\n

\n

\nGlobal Fund support is also being used to increase the uptake of successful practices by local authorities through sustainable involvement of civil society organizations and broader local coalitions to end TB. A new TB grant has been approved by the Global Fund Board and will start in January 2023.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1662475792, + "_created": 1656681698, + "_id": "433ddce4313237b2c9000016" + }, + { + "iso3": "AFG", + "summary": "
\n

\nThe Global Fund has allocated funding in Afghanistan of up to US$85 million to address HIV, tuberculosis (TB), malaria, resilient and sustainable systems for health, COVID-19 and, following the Taliban’s takeover in August 2021, emergency primary and secondary health care service delivery. Within Afghanistan’s highly challenging operating environment, our grants support interventions designed to protect hard-won gains against the three diseases, foster continued progress and strengthen the country’s fractured health system.\n

\n

\nProgress\n

\n

\nAfghanistan is one of the most impoverished nations in the world due to decades of conflict. As a result of the takeover of the country by the Taliban movement in August 2021, the Global Fund’s Afghanistan portfolio is managed under our Additional Safeguard Policy – a set of extra measures the Global Fund implements in particularly risky environments to strengthen fiscal and oversight controls.\n

\n

\nAfghanistan’s health care system is almost entirely supported by externally backed mechanisms. Multidonor funded programs have played a pivotal role in improving basic health and essential hospital services, strengthening the overall performance of the health sector and boosting demand for key health services.\n

\n

\nThe Global Fund’s HIV, TB and malaria grants in Afghanistan are reliant on the primary and secondary level health care delivery systems made possible by external sources. Until the takeover by the Taliban, increased coverage of health services throughout the country gave rise to invaluable progress against the three diseases in some key areas.\n

\n

\nLarge gains have been made against malaria – between 2002-2020, deaths were reduced by 90% and new cases fell by 82%. TB deaths fell by 17% between 2002-2020, TB treatment coverage steadily increased over the same period and a TB treatment success rate of around 90% was sustained for almost a decade. Regarding HIV, a sustained low prevalence among the population (except for people who inject drugs) has been a positive result in highly challenging circumstances.\n

\n

\nDespite the unstable conditions following the Taliban’s takeover, the Global Fund partnership has been able to continue to deliver services and save lives in Afghanistan with minimal disruption – a significant achievement.\n

\n

\nChallenges\n

\n

\nThe Taliban’s regime is not recognized by any government*, and organizations previously investing in or delivering health services are not directly engaging with it. The continuation of health services – and therefore the Global Fund grant interventions made possible by service providers within Afghanistan – is dependent on delinking measures to sustain humanitarian support from the political recognition of any new government.\n

\n

\nEven before the regime change, health and development indicators in Afghanistan were among the lowest in the region. The country’s health system has long faced a critical shortage of key ingredients – qualified health care workers, especially female health care workers to ensure that women can seek health services; safe and equipped facilities, particularly in areas that are experiencing active conflict; and supplies of medicines, equipment and vaccines.\n

\n

\nInsecurity, gender imbalances, and lack of government revenue severely limit the availability of even the most basic health care for most Afghans – and the situation has become particularly acute during the COVID-19 pandemic. Additionally, the national programs for HIV, TB and malaria face significant gaps in terms of up-to-date strategic information. This makes it difficult to understand the exact magnitude of the diseases in order to plan effectively and set correct targets.\n

\n

\nThe Taliban’s takeover of Afghanistan increased the complexity of an already highly challenging environment. Yet, despite the difficulties, there are new opportunities presented by reduced levels of armed conflict across the country, greater access to all regions by health authorities and improved chances for cross-border collaboration on control and ending HIV, TB and malaria as public health threats.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund has allocated funding of up to US$58.5 million throughout 2021-2023 for a grant that encompasses HIV, TB, malaria and resilient and sustainable systems for health.\n

\nWe have also allocated up to US$15 million for COVID-19 support and up to US$10 million in emergency funding to support critical primary and secondary health care services following the Taliban’s takeover.\n

\n

\nOur investments are supporting interventions that aim to reduce new HIV infections and improve the health and quality of life of people living with HIV; significantly lower TB deaths and incidence in the next five years; reset the country on a course to eliminate malaria by 2030; and strengthen the health system to reduce mortality and morbidity associated with the three diseases and across the reproductive, maternal, newborn, and child health continuum of care.\n

\n

\n*Last updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1659100865, + "_created": 1659100655, + "_id": "e50d601f373661d7120003df" + }, + { + "iso3": "LSO", + "summary": "
\n

\nThe Global Fund currently has one active investment in Lesotho: A multicomponent grant of up to US$71 million supporting HIV and tuberculosis (TB) programs throughout 2021-2024. Despite significant, hard-won progress against HIV and TB over the past two decades, the two diseases remain Lesotho’s greatest health challenges.\n

\n

\nProgress\n

\n

\nLesotho is a small, mountainous, landlocked country, surrounded by its much larger neighbor, South Africa. Political instability in recent years has delayed developmental progress, yet the country has nonetheless made gains in the fight against HIV and TB. Malaria is not a major public health threat in the country.\n

\n

\nSignificant headway has been made in reducing one of the highest HIV burdens in the world. AIDS-related deaths and new HIV cases have both fallen by more than two-thirds since 2002. Large increases have also been achieved in the percentages of people living with HIV who know their status, who receive antiretrovirals and who have a suppressed viral load. TB deaths and new cases have also fallen – by 24% and 32% respectively.\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in Lesotho is generalized and hyper-endemic, with prevalence among adults aged 15-59 years of 25.6% (2017). Rates are significantly higher in women than in men, especially women aged 35-39 years, among whom prevalence is 50%. Key populations – female sex workers, people in prisons, female factory workers and gay men and other men who have sex with men – also experience high prevalence. The challenge of removing gender- and human rights-related barriers to service delivery, accessibility and use is being addressed by Global Fund investments.\n

\n

\nAlthough AIDS-related deaths have significantly declined in recent years, the rate of reduction has plateaued since 2015. Our current funding is supporting interventions to bridge key gaps in HIV treatment, including HIV drug resistance, loss to follow-up and advanced HIV disease.\n

\n

\nLesotho has one of the highest incidences of TB in the world and has one of the highest mortality rates in the world among people co-infected with HIV and TB. These challenges stem from several factors: Remote, rural communities have historically had limited access to prevention, testing and treatment services; and a large part of the workforce migrates to neighboring countries for extended periods, increasing behavioral and environmental risk factors, and diminishing access to health services.\n

\n

\nDespite hard-won progress against TB, significant challenges remain in treatment coverage – a further indication of the need to continuously invest in finding people ill with TB and supporting interventions to address suboptimal rates of multidrug-resistant TB diagnosis.\n

\n

\nGlobal Fund investments\n

\n

\nWe are supporting Lesotho’s HIV and TB responses through an investment of up to US$71 million for 2021-2024. This multicomponent grant supports Lesotho’s ambitious targets to reduce HIV incidence by 75%, AIDS-related deaths by 50% (both by 2023), TB incidence by 50% and TB mortality by 75%.\n

\nTo enable Lesotho to achieve its objectives and effectively reach key and vulnerable populations, the Global Fund’s current investment is directed toward the highest priority response strategies: HIV prevention, differentiated HIV testing services, HIV treatment, care and support, prevention of mother to child transmission, TB care and prevention, TB and HIV coinfection, multidrug-resistant TB and addressing health system barriers.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1659101078, + "_created": 1659100896, + "_id": "e532200a32363072d9000175" + }, + { + "iso3": "BOL", + "summary": "
\n

\nThe Global Fund currently has three active investments in Bolivia, with funding of up to US$22 million allocated for HIV, tuberculosis (TB) and malaria programs. Our investments are supporting the country’s efforts to extend hard-won progress against HIV and TB, and to regain lost ground after COVID-19 compromised the significant gains made against malaria over the last 25 years.\n

\n

\nProgress\n

\n

\nBolivia is located in central-western South America and has a population of around 12 million people. The country has made significant progress in health equity in recent years, which has underpinned gains against HIV, TB and malaria.\n

\n

\nSince 2002, AIDS-related deaths have declined by 65% and TB deaths by 40%. Despite malaria being endemic in the Amazon regions of the country, Bolivia has made strong progress in lowering deaths (down 33%) and new cases (down 17%).\n

\n

\nChallenges\n

\n

\nHIV prevalence among gay, bisexual, and other men who have sex with men is 21% (the rate estimated for the general population is 0.3%). Improving access to prevention and HIV diagnosis for this key population, as well as for female and transgender sex workers, is a key focus of Bolivia’s HIV response.\n

\n

\nAlthough the diagnostic coverage of HIV has increased in recent years, there is still a significant gap in diagnosis – one factor contributing to this is that the offer of free HIV tests in health facilities only includes pregnant women (as of 2019). Additionally, around one-fifth of people are lost between diagnosis and linkage to treatment (2019). Strengthening linkage, retention and access to comprehensive care is imperative to reducing HIV in the country.\n

\n

\nBolivia’s TB and HIV epidemics are both concentrated along geographic lines. Most TB cases and 87% of diagnosed HIV cases occur in three departments along the country's central axis: La Paz, Cochabamba and Santa Cruz. Other challenges are also posed by several thousand instances of unreported TB cases and the ongoing presence of multidrug-resistant TB.\n

\n

\nMalaria is endemic in the low and humid plains of Bolivia that contain thousands of square kilometers of dense tropical rainforest. Communication in these areas is mostly by river, with few roads for rural access. This makes conditions favorable to continuous malaria transmission, with a high probability of outbreaks. The situation has been exacerbated by COVID-19 and the presence of other vector-borne diseases that make it difficult to distinguish between malaria, COVID-19 and dengue infections, and hinder prompt clinical diagnosis. After years of strong reductions in new infections, rising case numbers in 2019 and 2020 have jeopardized the country’s progress toward achieving the World Health Organization’s Sustainable Development Goals for malaria.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund is supporting Bolivia's HIV response through a grant of up to US$9 million for 2019-2022. Our investment is designed to contribute to the reduction of new HIV cases and to keep overall HIV prevalence below 0.3%, with a strong focus on key populations.\n

\nA grant of up to US$6 million was allocated for 2020-2022 to support Bolivia’s TB response. Our investment is strongly focused on strategies to strengthen prevention, case detection, diagnosis and treatment for multidrug-resistant TB. These activities are being delivered in concert with other interventions to reduce all forms of TB.\n

\n

\nWe are investing in Bolivia’s malaria response with a grant of up to US$7 million allocated for 2022-2024. This funding supports the country’s efforts to reach pre-elimination targets and is aligned with the country’s national strategic plan for malaria, which aims to reach zero cases by 2025.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1659101609, + "_created": 1659101103, + "_id": "e551c6e163653365a4000228" + }, + { + "iso3": "CUB", + "summary": "
\n

\nThe Global Fund has allocated funding of up to US$17.4 million for an HIV grant in Cuba operating throughout 2021-2023. Our investment supports interventions to continue Cuba’s significant success against HIV and prepare the country’s HIV program to operate sustainably without Global Fund resources.\n

\n

\nProgress\n

\n

\nLocated where the northern Caribbean Sea, Gulf of Mexico, and the Atlantic Ocean meet, Cuba is the second-most populous country in the Caribbean, with over 11 million inhabitants.\n

\n

\nCuba has achieved considerable success in the fight against HIV. In 2015 it became the first country in the world to receive validation from the World Health Organization that it had ended mother-to-child transmission of HIV and syphilis.\n

\n

\nCuba achieved this result – an important step toward having an HIV-free generation – by providing early access to prenatal care, HIV and syphilis testing for both pregnant women and their partners, treatment for women who test positive and their babies, substitution of breastfeeding, and prevention of HIV and syphilis before and during pregnancy through condom use and other prevention measures.\n

\n

\nThese services are provided as part of an equitable, accessible and universal health system in which maternal and child health programs are integrated with programs for HIV and sexually transmitted infections. The country's HIV program has achieved a 23% reduction in new infections since 2010 and has robust interventions for finding people living with HIV (85% of people living with HIV know their status) and linking these people to care and treatment (99% of people living with HIV who need antiretroviral treatment receive it).\n

\n

\nChallenges\n

\n

\nWhile Cuba’s HIV response shows notable results, it is vital to keep up the momentum behind its success. Seventy-two percent of Cubans living with HIV are men who have sex with men. Accordingly, the current Global Fund investment in Cuba focuses on strengthening the role of the key populations network in the fight against HIV. Our current grant continues to support the scale-up of high-yielding HIV testing modalities (i.e., index testing) and combining community testing with focused outreach (self-testing, social networking) to find hidden populations and people at higher risk of HIV infection.\n

\n

\nContinued links with regional and global partners will be important in helping to identify and address new challenges, and in ensuring the lessons Cuba has learned so far are shared and adapted in other contexts. Ensuring sustainability of interventions once Cuba transitions from Global Fund support is also an important consideration in the country's ongoing HIV response.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund has allocated funding of up to US$17.4 million for an HIV grant in Cuba for 2021-2023. Our investment is aimed at accelerating progress toward ending HIV in Cuba by supporting high-impact interventions that ensure timely access to sustainable and efficient services through the continuum of service delivery, especially for key populations.\n

\nIn addition, the grant aims to promote and protect human rights and gender and health equality, and to expand public funding of the national HIV response to reach a sustainable level beyond Global Fund investment.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1659101830, + "_created": 1659101663, + "_id": "e5a729f361386222130003c8" + }, + { + "iso3": "MRT", + "summary": "
\n

\nHIV, tuberculosis (TB) and malaria remain among Mauritania’s greatest health challenges, despite recent hard-won progress in key areas. A multicomponent grant addressing the three diseases in the country has been allocated up to US$24.7 million for 2022-2024. Our investment aims to substantially reduce the HIV, TB and malaria burdens and strengthen health and community systems in Mauritania.\n

\n

\nProgress\n

\n

\nAlthough Mauritania is located on the Atlantic coast of Africa, it is a desert country, with 90% of its territory situated in the Sahara.\n

\n

\nIn recent years, the country has received influxes of refugees due to instability in Mali, with which it shares long southern and eastern borders. Mauritania has experienced political instability in the past 15 years, which has impacted human rights and development indicators. Like other countries in the Sahel, Mauritania experiences chronic nutritional crises, with peaks in lean periods that reach the emergency threshold in some regions.\n

\n

\nDespite the strain, these conditions place on health systems, progress in some key areas has been made against HIV, TB and malaria.\n

\n

\nBetween 2002 and 2020, new HIV infections fell by 57%, TB deaths by 50% and new TB cases by one-third. Mass and routine distribution of long-lasting insecticidal nets in recent years have contributed to a decline in malaria incidence in areas considered endemic. Nationwide, malaria cases have reduced by 60%.\n

\n

\nChallenges\n

\n

\nMauritania is one of the least densely populated countries in the world. A significant proportion of its approximately 5 million people live in small, remote communities, which can make accessing basic health services difficult. Health coverage is low, with one-third of the population living more than five kilometers from a health facility. Malaria is endemic, particularly in the southern rice-growing areas of the country. The WHO estimates that two-thirds of the population lives in areas at high risk of malaria transmission.\n

\n

\nReaching key populations is a key challenge in the country’s HIV response. Prevalence rates among key populations are many multiples higher than in the general population, with sex workers and gay men and other men who have sex with men most at risk of HIV infection. Although the percentages of people living with HIV who receive antiretroviral therapy or who have a suppressed viral load have seen substantial increases since 2010, the country is less than halfway to reaching 95-95-95 HIV testing and treatment targets.\n

\n

\nTB remains a major challenge for Mauritania. Low case detection of TB patients has been a recurrent TB control problem – health authorities estimate that more than one-third of the expected number of cases go undetected each year. This low detection rate is due to several factors, including a limited network of laboratories and X-ray machines, suboptimal use of GeneXpert machines, services that are not free of charge, low physical access to TB services, limited staffing capacity and the impact of COVID-19. Current Global Fund investment is supporting measures to address these factors and strengthen TB case detection and treatment.\n

\n

\nGlobal Fund investments\n

\n

\nThe Global Fund is supporting Mauritania’s HIV, TB and malaria responses through a core grant of up to US$24.7 million for 2022-2024. The investments under this grant aim to substantially reduce the burdens of HIV, TB and malaria in line with World Health Organization goals through targeted interventions and approaches to reach key populations and remove barriers to accessing health care.\n

\n

\nIn addition, the grant aims to promote and protect human rights and gender and health equality, and to expand public funding of the national HIV response to reach a sustainable level beyond Global Fund investment.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1659102123, + "_created": 1659101849, + "_id": "e5c386c634343843100000b4" + }, + { + "iso3": "COL", + "summary": "
\n

\nThe Global Fund currently has one core grant active in Colombia: an HIV grant with funding totalling up to US$19.4 million signed for 2022-2025. The funding is focused on improving access to diagnosis and treatment for vulnerable groups and supporting prevention and control measures in communities most affected by HIV. Our investment aims to support Colombia in continuing its progress toward 95-95-95 HIV testing and treatment targets.\n

\n

\nProgress\n

\n

\nColombia is the fourth-largest country in South America and one of the continent’s most populous nations. Economic challenges and decades of armed conflict – now ended – slowed development projects and complicated the national fight against HIV, tuberculosis (TB) and malaria.\n

\n

\nCurrent Global Fund core financing for Colombia focuses on addressing HIV (there are no active grants for TB or malaria). In 2021, 78% of people living with HIV knew their status, 74% of people who knew their HIV status were receiving antiretroviral therapy and 66% of people receiving antiretroviral therapy had a suppressed viral load. AIDS-related deaths fell by more than two-thirds between 2002 and 2021, while new HIV infections decreased slightly over the same period.\n

\n

\nChallenges\n

\n

\nColombia has made significant progress toward universal health coverage goals and has implemented national policies for HIV treatment and response. However, hundreds of thousands of migrants who have entered the country from neighboring Venezuela are without health coverage. This presents a significant challenge to ensuring that detection, diagnosis, treatment and transmission control are undertaken for all those within the country’s borders who are either at risk of HIV infection or already living with the virus.\n

\n

\nThe HIV epidemic in Columbia also disproportionately affects key populations. Rates of HIV prevalence among men who have sex with men and transgender women are many multiples higher than in the general population. These groups face barriers to accessing HIV services due to stigma and discrimination.\n

\n

\nGlobal Fund investments\n

\n

\nFunding of up to US$19.4 million was signed for Colombia for 2022-2025 to improve access to diagnosis and treatment, and support prevention and control of HIV.\n

\n

\nOur investment is geared toward expanding prevention services, testing coverage and comprehensive quality care from seven to 15 territories, and for communities most affected by HIV in the country. The grant supports interventions focused on key populations and irregular migrants. These interventions aim to increase demand in diagnostics and to further integrate self-testing and HIV medicines (pre-exposure prophylaxis and post-exposure prophylaxis) into the epidemic response.\n

\n

\nLast updated March 2023.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1684241804, + "_created": 1659102148, + "_id": "e5f13ae63636626814000268" + }, + { + "iso3": "MMR", + "summary": "
\n

\nThe Global Fund currently has four active investments in Myanmar, with funding totaling up to US$227 million allocated for 2021-2023. Our investments support strategic interventions designed to reduce one of the world’s highest burdens of tuberculosis (TB), overcome barriers to access to HIV testing and treatment, and keep up the fight against both diseases.\n

\n

\nProgress\n

\n

\nMyanmar is a country in Southeast Asia of 55 million people that in 2011 began to undergo gradual liberalization as it transitioned from decades of military rule to civilian leadership. This led to improvements in social welfare and development indicators, significant reductions in the poverty rate, and progress against HIV, TB and malaria. The military reassumed power in February 2021.\n

\n

\nSince 2011, the Global Fund has been the single largest financing source for the national HIV response in Myanmar. This allowed the country to scale up and decentralize service provision for antiretroviral therapy, HIV counseling and testing, and harm reduction. HIV incidence and mortality rates almost halved between 2011 and 2020. Vitally, there was also a rapid increase in the number of people receiving antiretroviral therapy – a significant shift from pre-2011 levels, which were among the lowest in the world.\n

\n

\nIn the last decade Myanmar has registered steady progress in reducing its high burden of TB. The TB incidence rate declined from 475 cases per 100,000 people in 2011 to 321 cases per 100,000 people in 2019. The estimated mortality rate also fell significantly – from 88 deaths per 100,000 people in 2011 to 36 deaths per 100,000 people in 2019. The overall TB case notification rate has also declined steadily, as has notification of TB among children. Treatment success rates among new and relapsed TB cases reached as high as 87% in 2017 and, notably, the treatment success rate for multidrug-resistant TB has also been as high as 79%.\n

\n

\nChallenges\n

\n

\nThe combined effects of the military coup and the COVID-19 pandemic have deepened Myanmar’s economic and humanitarian challenges, and they have the potential to jeopardize the development progress that has been achieved over the past decade.\n

\n

\nPolitical instability, internal conflict and other difficulties have led to Global Fund investments in Myanmar being managed under our Challenging Operating Environments Policy. This allows us to adapt our framework and strategies to the shifting dynamics within the country, while still maintaining responsible oversight of funds and facilitating service delivery as effectively as possible to people in need.\n

\n

\nConflict, closures and limited government funding for health systems make accessing quality primary health care difficult for much of Myanmar’s population. This has delayed diagnosis or interrupted treatment for thousands of people – a devastating outcome for people living with HIV or TB. Overcoming the weaknesses of the health system and improving access to testing and treatment are key challenges in the country’s fight against HIV and TB. The Global Fund supports interventions in Myanmar that address the current challenges of HIV and TB control activities, fill gaps in service provision and support the continuation of care for both diseases.\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV grants of up to a combined total of US$128 million have been signed for Myanmar for 2021-2023. Our investments support strategies to reduce HIV incidence among key and vulnerable populations and their partners, improve quality of care and increase the accessibility of antiretroviral therapy, support viral suppression for all people living with HIV and improve the enabling environment to support the national HIV response.\n

\n

\nWe are supporting the TB response in Myanmar through two grants of up to a combined total of US$99 million for 2021-2023. The grants support Myanmar’s National Tuberculosis Strategic Plan (2021-2025) in its goal of reducing TB incidence to fewer than 10 cases per 100,000 people by 2035. Our investment also supports activities geared toward a shorter-term goal of achieving a 50% reduction in TB incidence by 2025 compared to 2015.\n

\n

\nLast updated July 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1659102459, + "_created": 1659102298, + "_id": "e60810f6336334092600039f" + }, + { + "iso3": "ETH", + "summary": "
\n

\nThe Global Fund currently has four core grants active in Ethiopia, with funding totaling up to US$453 million signed for 2021-2024. Our investments aim to support the country to continue the significant progress made in the fight against HIV, tuberculosis (TB) and malaria, build more resilient and sustainable systems for health, and protect existing gains in the face of conflict, mass displacement, food insecurity and severe drought.\n

\n

\nProgress\n

\n

\nEthiopia has made significant progress in addressing its HIV, TB and malaria epidemics over the past two decades. Gains against HIV have been strong: Since the early 2000s, incidence and mortality rates have reduced by almost 100 infections/deaths per 100,000 people. The country has been moving steadily toward the 95-95-95 testing and treatment targets. As of 2020, 82% of people living with HIV knew their status, 78% of people who knew their HIV status were receiving antiretroviral treatment, and 72% of people on treatment had a suppressed viral load.\n

\n

\nSteady progress has also been made against TB. Continued increases in treatment coverage (which sat at 71% in 2020) and treatment success (90% in 2020) have helped drive reductions in TB incidence and deaths since 2002 (by 45% and 66% respectively).\n

\n

\nEthiopia started scaling up malaria prevention and control measures in 2004, rolling out large-scale interventions such as mass-distribution of long-lasting insecticidal nets and indoor residual spraying, and introducing additional diagnosis and treatment mechanisms such as artemisinin-based combination therapy and rapid diagnostic tests. This has led to strong progress against the disease: Since 2002, deaths have almost halved and annual case numbers have fallen by 57%.\n

\n

\nChallenges\n

\n

\nSome of the greatest challenges in responding to the HIV, TB and malaria epidemics in Ethiopia do not stem directly from the diseases themselves. Conflict in northern Ethiopia has led to over 5 million people being displaced (Internal Displacement Monitoring Centre, 2021) and in need of humanitarian assistance. Years of drought alongside the long-term impacts of climate change have severely undermined agriculture, pastoral livelihoods and food security. The mass movement of people, diversion of resources, and the health of millions weakened by hunger threaten the fragile gains made against HIV, TB and malaria.\n

\n

\nThe spread of HIV in Ethiopia – over 10,000 new cases are reported each year – makes the disease a significant, ongoing public health threat. Young people bear the largest burden of HIV: Two-thirds of new infections occur in people under 30, with 20% of those occurring in people aged 20-24.\n

\n

\nAdolescent girls and young women experience higher rates of infection than boys and men of the same age. The disparity is especially pronounced among girls aged 15-19, for whom incidence is six times higher than their male peers. Key populations – female sex workers, people in prisons, people who inject drugs and long-distance drivers – are also disproportionately affected by HIV.\n

\n

\nPrevention among key and vulnerable populations is the primary focus of Ethiopia’s HIV strategy, which is oriented toward achieving maximum public health impact within a resource-constrained setting.\n

\n

\nEthiopia remains on the World Health Organization’s list of 30 high-TB burden countries globally and has one of the highest rates of HIV-associated TB in the world. Nearly one-third of people estimated to have TB were not notified to the national program – a figure that is higher for drug-resistant TB. Finding these “missing” people and treating them is a priority for ending the TB epidemic in Ethiopia. In addition to addressing gaps across the patient pathway, other key challenges in the country’s epidemic response include strengthening supportive systems for health and generating strategic TB information.\n

\n

\nEthiopia’s strong progress against malaria is threatened by a set of emerging challenges: mosquito resistance to insecticides, the emergence of new vectors, the potential side effects of irrigation and hydropower reservoirs (Ethiopia is expanding irrigation and hydropower dams – infrastructure that has been found to intensify malaria transmission), and climate change and variability (temperature suitability for malaria is climbing into the highlands). Sustaining and improving vector control, diagnosis and treatment, program management, community systems strengthening, and health management information systems are vital to keeping momentum in fighting malaria, and continue to be the strategic foundation of Ethiopia’s antimalaria program.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$258 million has been signed for Ethiopia for 2021-2024. Our investment is geared toward reaching more key and priority populations with combination prevention interventions, enhancing case finding so that by 2025, 95% people of people living with HIV know their status, attaining virtual elimination of perinatal transmission, and a spectrum of additional interventions to help drive progress toward epidemic control nationally by 2025.\n

\n

\nWe are continuing to support the TB response in Ethiopia through a grant of up to US$57 million for 2021-2024. The grant supports Ethiopia in its goal of ending TB as a public health threat by funding interventions that address gaps across the patient pathway, prevent infection and active disease, provide quality, equitable, people-centric services, strengthen supportive systems and generate strategic information and research.\n

\n

\nA malaria grant of up to US$105 million was signed for Ethiopia for 2021-2024. Our investment supports interventions designed to reduce malaria mortality and morbidity by 50% by 2025 (compared to 2020). Also by 2025, Ethiopia aims to achieve zero indigenous malaria cases in districts with an annual parasite index (a measure of malaria morbidity) of less than 10, and prevent the re-introduction of malaria in districts that report zero indigenous cases of malaria.\n

\n

\nFunding of up to US$33 million was allocated for 2021-2024 to strengthen the resilience of health systems in Ethiopia. The grant is supporting the government in delivering the priorities of the national health plan, while simultaneously enabling continued and increased impact for HIV, TB and malaria programs.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1660906292, + "_created": 1660905824, + "_id": "190452d5316633ead900005e" + }, + { + "iso3": "PHL", + "summary": "
\n

\nThe Global Fund currently has three core grants active in the Philippines, with funding totaling up to US$159 million signed for 2021-2023. Our investments support strategic interventions designed to reduce one of the world’s highest burdens of tuberculosis (TB), expand prevention measures in an increasing HIV epidemic, and make continued progress toward malaria elimination in most of the country’s provinces.\n

\n

\nProgress\n

\n

\nAn archipelago of more than 7,000 islands populated by 111 million people, the Philippines is one of Asia’s fastest growing economies. The country has made great progress in the fight against malaria by ensuring high coverage of effective control interventions such as insecticide-treated nets and indoor spraying with insecticides. As a result, 60 out of 81 provinces have been declared malaria-free and an additional 18 provinces report zero indigenous cases.\n

\n

\nWith rising numbers of new HIV infections and TB cases, gains against both diseases have been limited. Although there have been increases in the percentages of people living with HIV who know their status, who know their HIV status and are on treatment, and who are on treatment and have a suppressed viral load, there is much progress yet to be made to achieve the 95-95-95 goals for these indicators (which were 68%, 42% and 39% in 2020). For TB, gains have been made in the form of an 8% reduction in deaths between 2002-2020 and increases in treatment coverage and drug-resistant TB treatment success.\n

\n

\nChallenges\n

\n

\nThe Philippines has one of the fastest growing HIV epidemics in the world, with new HIV infections increasing by 203% between 2010-2018. Key populations are disproportionately affected. Transgender people and gay men and other men who have sex with men account for 80% of cases, with an increasing proportion of cases among people aged 15-24 within key populations. The political climate in the Philippines has severely limited access to HIV testing and treatment services by people who use or inject drugs. From 2010, there was a sharp increase in the proportion of HIV cases among people who use or inject drugs. Additionally, leakages at different points in the HIV care cascade have the potential to lead to infections in other vulnerable people.\n

\n

\nTB remains a major public health threat in the Philippines. The World Health Organization identifies the Philippines as one of the 30 high-TB burden countries, accounting for around 6% of global TB cases. While TB case notifications are increasing, a significant proportion of people with TB are missing. There are gaps in the design and implementation of screening and diagnosis, low utilization of GeneXpert machines and limited monitoring of resistance to second-line TB drugs. Improving notification and treatment through stronger TB and drug-resistant TB management is a key challenge in the country’s epidemic response.\n

\n

\nPalawan, an island province, is the epicenter of malaria in the Philippines. Progress has been made against the disease in the province, however, and work is ongoing to overcome the geographic, socioeconomic and health system factors that contribute to continued malaria transmission there. With regards to elimination for the rest of the country, the primary focus is to prevent the reintroduction of malaria transmission by strengthening local health systems, establishing information structures and creating Malaria Elimination Hubs to spearhead surveillance and response efforts.\n

\n

\nHuman rights-related obstacles continue to threaten progress against HIV, TB, and malaria in the Philippines. While programs to remove stigma, discrimination and criminalization do exist, there is a need for them to be scaled up. Without a strong and varied stream of funding for programs and increased political will to support them (particularly at local levels), human rights-related barriers are likely to continue to undermine the country's responses to all three diseases.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$22 million has been signed for the Philippines for 2021-2023. Our investment is geared toward increasing access to testing services, strengthening prevention by scaling up effective information, education and communication initiatives and condom distribution, boosting domestic investment in HIV programs and services, and strengthening the human rights component of HIV programs.\n

\n

\nWe are continuing to support the TB response in the Philippines through a grant of up to US$129 million for 2021-2023. The grant supports the Philippines in its 2023 goals of decreasing TB deaths to 22,000 (from 26,000), ensuring no TB-affected families face catastrophic costs, and reducing TB incidence by 12% compared to 2018 levels.\n

\n

\nA malaria grant of up to US$8 million was signed for 2021-2023. Our investment supports interventions designed to reduce the number of indigenous malaria cases to less than 1,200, interrupt malaria transmission in all provinces except Palawan, and enable 75 provinces be declared malaria-free.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1660906627, + "_created": 1660906419, + "_id": "195f0811306630705b0002d0" + }, + { + "iso3": "TZA", + "summary": "
\n

\nThe Global Fund currently has four core grants active in Tanzania, with funding totaling up to US$608 million signed for 2021-2023. Our investments aim to support the country in continuing the significant progress it has made in the fight against HIV, tuberculosis (TB) and malaria, and build more resilient and sustainable systems for health.\n

\n

\nProgress\n

\n

\nTanzania has led a robust multisectoral effort to tackle HIV, TB and malaria through prevention, care, treatment, and support services. Between 2002 and 2020, HIV incidence fell by 68%, from 395 infections per 100,000 people to 126. The country has undertaken an impressive national expansion of antiretroviral therapy, which saw the percentage of people enrolled in treatment surge from 18% in 2010 to 86% in 2021.\n

\n

\nThe East African country of 61 million people was one of only seven high TB burden and global TB watchlist countries estimated to have achieved the World Health Organization’s End TB Strategy 2020 milestone of a 35% reduction in the absolute number of TB deaths between 2015 and 2020. Case notifications have increased, and TB incidence more than halved between 2000 and 2019.\n

\n

\nTanzania has stepped up the fight against malaria through investments in prevention, care, treatment and support services, which has resulted in the country nearly halving the mortality of children under 5. The number of confirmed malaria cases declined by 1 million between 2015 and 2019.\n

\n

\nOver the years, Tanzania has also benefited from Global Fund partnership support to build sustainable and resilient systems for health. Our investment has supported human resource capacity building through on-the-job training and deployment of health workers to remote areas, supply chain improvement, and strengthening routine data reporting.\n

\n

\nChallenges\n

\n

\nDespite the great gains made against HIV in the last two decades, Tanzania continues to have high prevalence and infection rates. Adolescent girls and young women and key populations – gay men and other men who have sex with men, sex workers, transgender people, people who inject drugs – are disproportionately affected by the disease. Current Global Fund investment supports a spectrum of interventions focused on these groups, including optimized education and behavior-change initiatives, gender- and identity-based violence awareness and screening, biomedical services, and activities to reduce human rights-related barriers to HIV and TB services. The need for scale-up of testing services for the whole population was also identified, and this is being addressed by the rollout of self-testing and the expansion of community-based testing.\n

\n

\nWith the improvements Tanzania has made in treatment coverage, a strategic focus with more and tailored interventions was needed to ensure the country can achieve the End TB Strategy targets for 2025. Current TB investment addresses several areas that have presented challenges in the fight against the disease. This includes closing gaps in case detection; addressing community rights and gender barriers – particularly for children, refugees, migrants and miners who experience a high burden of disease or face barriers to access; improving the timeliness and functionality of TB diagnostic networks and expanding private sector partnerships.\n

\n

\nAlthough the prevalence of malaria across Tanzania overall has declined, some areas of the country continue to have high transmission and hence bear a high malaria burden, while other areas are rapidly moving toward local elimination of transmission. The current Global Fund grant recognizes that a “one-size-fits-all” approach in the combination of interventions is no longer possible for effective epidemic response. To maximize impact, interventions have been targeted to meet specific prevention, testing and diagnostic challenges for different geographies and populations based on epidemiological data.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$352 million has been signed for Tanzania for 2021-2023. Our investment is geared toward significantly reducing infections, deaths, stigma and discrimination, which will help drive progress in achieving the 95-95-95 testing and treatment targets by 2025.\n

\n

\nWe are continuing to support the TB response in Tanzania through a grant of up to US$42 million for 2021-2023. The grant supports Tanzania in its goal of reducing the TB burden by 50% in incidence and 75% in deaths by 2025 (compared to 2015). A combined HIV and TB grant of up to US$34 million for 2021-2023 interoperates with the above grants to further support Tanzania’s HIV and TB reduction strategies.\n

\n

\nA malaria grant of up to US$179 million was signed for Tanzania for 2021-2023. Our investment supports interventions designed to lower malaria prevalence in children under 5 to 3.5% by 2025, reduce malaria transmission from an average of 121 per 1,000 in 2019 to less than 30 per 1,000 by 2025. By implementing the interventions that lead to these targets, as well as achieving universal health coverage as planned, Tanzania aims to eliminate malaria by 2030.\n

\n

\nTanzania is included in several multicountry grants being implemented across groups of countries in the region. It also receives funding as part of the Global Fund’s COVID-19 Response Mechanism, which supports countries to mitigate the impact of COVID-19 on programs to fight HIV, TB and malaria, and initiates urgent improvements in health and community systems.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1668680460, + "_created": 1660906703, + "_id": "198a783e65666208f3000272" + }, + { + "iso3": "ZAF", + "summary": "
\n

\nThe Global Fund currently has four core grants active in South Africa. Our significant investment aims to consolidate hard-won gains against HIV and tuberculosis (TB) and supports efforts to continue to bend the curve of new infections and deaths for both diseases.\n

\n

\nProgress\n

\n

\nSouth Africa has made considerable strides to improve the well-being of its citizens since its transition to democracy in the mid-1990s. However, it remains a dual economy with one of the highest rates of inequality in the world.\n

\n

\nThe country has made progress toward finding and diagnosing HIV, with 94% of people living with HIV knowing their status in 2021. Seventy-four percent of people living with HIV were receiving antiretroviral treatment in 2021, and new HIV infections and AIDS-related deaths have decreased each year since the mid-2000s.\n

\n

\nEmphasis on integrating TB and HIV diagnosis and treatment has boosted the response to both diseases. While progress against TB requires further strengthening, the country is making notable gains against the disease. TB incidence and mortality rates have both almost halved since the mid-2000s.\n

\n

\nThe risk of malaria in South Africa is low and is limited to three provinces bordering Mozambique and Swaziland. As such, the Global Fund has no current malaria investment in the country.\n

\n

\nIn terms of total funding, South Africa is one of the leading countries for Global Fund investments. The country itself has also mobilized and managed an enormous flow of domestic resources over the last decade. The government of South Africa has significantly boosted domestic financing and contributes around 75% of HIV and TB spending. Our grants to South Africa for 2022-2025 are designed to leverage opportunities within the country to further strengthen our partnerships to deliver quality health services to all.\n

\n

\nChallenges\n

\n

\nSouth Africa continues to have the largest HIV epidemic in the world, with 7.8 million people living with HIV (2021) out of a total population of 59 million.\n

\n

\nWomen experience a disproportionate burden of the disease. This gender disparity is most pronounced among adolescent girls and young women aged 15-24 years, whose HIV prevalence is much higher than males of the same age (24.7% compared with 13.5%).\n

\n

\nThe country’s HIV burden is not only uneven in terms of gender; it also shows a significant geographic variation. To better respond to the disparities between populations and locations, the country’s national strategic plan for HIV and TB prioritizes intensified interventions in 27 of South Africa’s health districts (out of a total of 52) that account for 85% of people living with HIV.\n

\n

\nTB and HIV co-infection is high in South Africa; it was estimated at 58% in 2019, and of the 58,000 people who died from TB that year, 36,000 were people living with HIV. The World Health Organization includes South Africa on each of its 30 high-burden country lists for TB, HIV-associated TB and multidrug-resistant TB. The country accounts for 3.6% of the global TB caseload.\n

\n

\nPopulations in informal settlements, a large mining sector, and depressed immunity among people living with HIV lead to a high burden of TB in the country. Finding missing people with TB is a national priority, as is closing gaps in access and adherence to treatment. Reaching key populations with interventions tailored to their needs is a key focus in the fight against the disease.\n

\n

\nGlobal Fund investments\n

\n

\nIn 2022 the Global Fund and South Africa signed four HIV and TB grants totaling up to US$547 million for 2022-2025. The total signed amount represents an increase of over 50% from the previous cycle of grants and continues our deep commitment to the country's HIV and TB response throughout the next three years.\n

\n

\nOur funding will support South Africa in maintaining the progress achieved so far in the fight against HIV and TB, further scaling up tailored support to HIV and TB programs and investing considerably in more inclusive quality health systems.\n

\n

\nSouth Africa is included in several multicountry grants being implemented across groups of countries in the region. It also receives funding as part of the Global Fund’s COVID-19 Response Mechanism, which supports countries to mitigate the impact of COVID-19 on programs to fight HIV, TB and malaria, and initiates urgent improvements in health and community systems.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1668680933, + "_created": 1660907285, + "_id": "19e33d4b656332429b00030b" + }, + { + "iso3": "IDN", + "summary": "
\n

\nThe Global Fund has seven country-specific grants active in Indonesia with total funding of up to US$285 million allocated until the end of the current investment period in 2023. Our investments support strategic interventions aimed at accelerating progress against HIV, securing stronger gains against TB and achieving bold targets in the fight against malaria.\n

\n

\nProgress\n

\n

\nIndonesia is the world’s fourth-most populous nation and its economy is the largest in Southeast Asia. The country has made significant gains in poverty reduction, cutting the poverty rate by more than half since 1999, to under 10% in 2019.\n

\n

\nAlthough numbers of new HIV infections and deaths remain high, incidence and mortality rates are declining. The reduction in HIV incidence has been particularly significant, falling from 21 infections per 100,000 people in 2006 to 10 infections per 100,000 people in 2020. Out of the estimated half a million people living with HIV in 2020, 66% knew their status, 40% of those who knew their status were receiving antiretrovirals and 94% of people receiving antiretrovirals were virally suppressed (UNAIDS 2021 data release).\n

\n

\nTB remains the deadliest infectious disease in Indonesia, yet the mortality rate in 2019 was almost half of what it was in the year 2000. The estimated TB incidence also declined over the same period, but at a much smaller rate.\n

\n

\nIndonesia’s National Malaria Control Program has had a significant impact on the disease and continues to guide the country's progress towards malaria elimination. In 2018, 285 out of 514 districts had received certificates of malaria elimination. A further 168 districts were in the pre-elimination phase.\n

\n

\nChallenges\n

\n

\nEpidemic modeling shows that 70% of people living with HIV in Indonesia are either not from a key population group or do not self-identify as part of one. Given that Indonesia has 273 million people spread across more than 6,000 islands, finding people who are not part of (or who do not self-identify as part of) key populations requires intensive efforts.\n

\n

\nOur current HIV grants recognize that a modified outreach approach accordant to Indonesia’s changing sociopolitical environment is needed to effectively reach key populations and vulnerable groups, as well as the general population. Ending stigma and discrimination and reaching patients who are lost to follow up are additional challenges in Indonesia’s HIV response.\n

\n

\nIndonesia has one of the highest TB burdens in the world, accounting for more than 8% of global cases. TB treatment coverage in 2020 was below 50%, and the World Health Organization reports that the COVID-19 pandemic lowered the number of case notifications by several hundred thousand.\n

\n

\nDrug-resistant TB remains a significant threat, as does the substantial number of people with TB estimated to be missed by testing each year. Overcoming social and structural barriers such as stigma and discrimination, human rights violations and gender inequities in the workplace and at home is a key challenge – and it is one which our current TB investments are helping to address.\n

\n

\nWhile solid gains have been made against malaria, progress has been slower in eastern Indonesia – particularly in the provinces of Papua, West Papua and East Nusa Tenggara, where 28 high-endemic districts are located and 80% of the country’s malaria burden exists. Health authorities in Indonesia report that, given the current disease burden, elimination by 2030 will only be possible by aggressively and successfully reducing malaria transmission in the next five years to very low levels. This is being addressed through strategic interventions that our current malaria grants are helping to support.\n

\n

\nGlobal Fund investments\n

\n

\nThree HIV grants totaling up to US$87 million have been signed for Indonesia for 2022-2023. These grants help fund crucial interventions that support the government’s National Strategic Plan in its goals of accelerating progress toward 95-95-95 testing and treatment targets and ending HIV as a public health threat by 2030.\n

\n

\nThe Global Fund’s 2021-2023 TB grants of up to a combined US$157 million continue our deep commitment to reducing the TB burden in one of the world’s highest TB burden countries. Our investment supports focused interventions designed to end the TB epidemic in Indonesia.\n

\n

\nFunding of up to US$39 million was allocated for two malaria programs for 2021-2023. Our grants support the national malaria program in its activities to eliminate malaria in at least 75% of Indonesian districts and work toward none of the remaining districts being highly endemic.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1660907981, + "_created": 1660907704, + "_id": "1a2320c0376466a6730000d7" + }, + { + "iso3": "COD", + "summary": "
\n

\nThe Global Fund currently has six active investments in the Democratic Republic of the Congo (DRC), with funding totaling up to US$659 million allocated for 2021-2023. Our investments support strategic interventions designed to reduce some of the world’s highest burdens of HIV, TB and malaria and build more resilient and sustainable systems for health.\n

\n

\nProgress\n

\n

\nWith a surface area equivalent to that of Western Europe, the Democratic Republic of Congo (DRC) is the largest country in sub-Saharan Africa. Political and security challenges have at times undermined the design and implementation of effective health programs; however, the country has made substantial progress against HIV, TB and malaria in some important respects.\n

\n

\nHIV incidence and mortality rates have declined steadily over the past two decades, and the number of AIDS-related deaths and new HIV infections have reduced by more than 60% over the same period.\n

\n

\nTB case notifications have steadily risen as a result of the combined effects of improved case finding using mobile units, strengthened community strategies and increased diagnostic coverage. TB incidence has seen only a small reduction since 2002, while the TB mortality rate has reduced more significantly.\n

\n

\nDRC’s high malaria transmission level makes it highly challenging to gain traction in the fight against the disease; however, malaria deaths have fallen by one-fifth since 2002. Increased coverage of malaria prevention and case management services is highly likely to have contributed significantly to a reduction in mortality rates for children under 5.\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in DRC has significant disparities according to sex (in 2018 women accounted for more than 70% of people living with HIV), population type and location (prevalence in urban areas is almost three times higher than in rural ones). Current Global Fund investments are geared toward improving access to HIV and TB programs, sexual and reproductive health and legal services for adolescent girls and young women, and all groups at higher risk of contracting HIV.\n

\n

\nKey populations are disproportionately affected by HIV in DRC. Only about half of those in key populations who know they have HIV are on treatment. Stigma and discrimination are key reasons for this low level of treatment coverage. Providing psychosocial and judicial care to victims of human rights abuses among key populations is a major focus of DRC’s HIV and TB programs, as is scaling up prevention programs for key populations through community-focused approaches.\n

\n

\nTB continues to be a major public health threat in DRC, with the country appearing on the World Health Organization’s global lists of high-burden countries for TB, HIV-associated TB and drug-resistant TB. Despite the improvements in the TB notification rate over the past 25 years, case detection is still a major challenge. A survey in 2019 showed that prevalence in high-burden sites (such as prisons or informal settlements) may be four to five times higher than what was estimated for these settings. While the treatment success rate for drug-resistant TB has increased, it remains a significant threat. Fighting drug-resistant TB by scaling up notification, case management and treatment is a key focus of current Global Fund grants.\n

\n

\nDRC has the second-highest malaria burden in the world and accounts for about 11% of the total global malaria burden. The disease is the number one cause of morbidity and mortality in the country. It is also a significant driver of poverty.\n

\n

\nTrends in malaria prevalence, incidence and test positivity show that, despite significant malaria control efforts, it is possible that cases may continue to increase at a rate that challenges DRC’s capacity to reduce its malaria burden and reach the 2030 goals. The country’s current malaria program is addressing this challenge by building on strategic improvements made over the course of the previous investment period. Increased budget also allows for an expanded intervention framework. An assessment is being undertaken to gain a clearer understanding of the drivers behind the malaria caseload increase and will likely inform potential new and impactful interventions in the country’s fight against malaria.\n

\n

\nGlobal Fund investments\n

\n

\nGrants of up to US$17 million for HIV, US$19 million for TB and US$198 million for HIV and TB have been signed for DRC for 2021-2023. Our HIV investment aims to increase the number of people living with HIV who know their status and significantly expand access to lifesaving antiretroviral therapy. The TB component aims to boost the national TB response, increase the notification of new TB cases and reach a 95% treatment success rate. The grant will also support the country’s efforts to boost treatment success for people with drug-resistant TB.\n

\n

\nTwo malaria grants of up to a combined US$378 million for 2021-2023 will support DRC’s goal of continuing mass distribution of mosquito nets, expanding access to quality malaria diagnostics and treatment tools and significantly reducing malaria morbidity and mortality rates by 40% and 50% respectively (compared to 2018 rates).\n

\n

\nA grant of up to US$45 million will play a pivotal role in supporting the Ministry of Health to build a resilient and sustainable health system in DRC and improve the accessibility of people-centered health services.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1660908226, + "_created": 1660908042, + "_id": "1a56c4083363652e73000028" + }, + { + "iso3": "DZA", + "summary": "
\n

\nThe Global Fund currently has one active investment in Algeria: A grant of up to US$8.7 million supporting HIV-related activities throughout 2020-2022. This grant supports interventions that are preparing Algeria’s HIV response program to operate sustainably without Global Fund resources.\n

\n

\nProgress\n

\n

\nStretching from the heart of the Sahara to the Mediterranean coast, Algeria is the largest country in Africa. A hydrocarbon boom in the past two decades has allowed the country to introduce redistributive social policies that have helped to alleviate poverty and resulted in improvements in the UN Development Programme (UNDP) Human Development Indicators.\n

\n

\nAnalysis of Algeria's HIV care and treatment cascade shows encouraging results thanks to the government’s substantial political and financial commitment, and the efforts of technical and financial partners in responding to the HIV epidemic.\n

\n

\nIn light of the country’s HIV progress, our current investment is a transition grant (the Global Fund does not have any current TB or malaria investments in Algeria). It aims to prepare Algeria's HIV program to be able to sustain its performance without Global Fund resources.\n

\n

\nChallenges\n

\n

\nAlthough Algeria is close to achieving the 95-95-95 testing and treatment targets for HIV, crossing the threshold for all three remains a challenge, particularly in regard to sustained suppression of viral loads among people living with HIV who are receiving antiretroviral therapy.\n

\n

\nA further challenge is presented by the HIV epidemic’s continued concentration among key and vulnerable populations. HIV prevalence among key populations is 71 times higher than the national average. Reducing HIV prevalence among people who inject drugs, sex workers and gay men and other men who have sex with men requires continuous interventions that consolidate current progress and scale up outreach.\n

\n

\nGlobal Fund investments\n

\n

\nWe are supporting Algeria’s HIV response through an investment of up to US$8.7 million for 2020-2022. The grant supports interventions that aim to reduce the number of new infections, stabilize the HIV-specific mortality rate and reduce HIV- and AIDS-related stigma and discrimination in all its forms.\n

\n

\nOur investment is supporting Algeria to transition away from Global Fund funding. It supports activities geared toward ensuring that prevention, outreach and other community-based activities currently funded by the Global Fund are progressively funded by the government or other donors.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1660908416, + "_created": 1660908292, + "_id": "1a7cdf616130639c19000337" + }, + { + "iso3": "UGA", + "summary": "
\n

\nThe Global Fund currently has five core grants currently active in Uganda, with funding totaling up to US$602 million signed for 2021-2023. Our investments support strategic interventions designed to reduce some of the world’s highest burdens of HIV, tuberculosis (TB) and malaria, and overcome human rights-related barriers to testing and treatment.\n

\n

\nProgress\n

\n

\nUganda has made significant progress in reducing the extremely high burden of HIV it experienced during the 1980s and early 1990s and is moving steadily toward the 95-95-95 testing and treatment targets for the disease. In 2020, 91% of people living with HIV in Uganda knew their status. More than 95% of people who knew their HIV status were receiving antiretroviral therapy – 90% of whom were virally suppressed.\n

\n

\nTB incidence and mortality rates have fallen over the past two decades, and TB coverage and success rates have increased moderately over the same period.\n

\n

\nUganda has made substantial gains in the fight against malaria. Deaths from the disease fell by almost two-thirds between 2002-2020. The percentage of people with access to – and, vitally, using – long-lasting insecticidal nets almost doubled over the same period. In 2020, almost every person with suspected malaria received a diagnostic test.\n

\n

\nChallenges\n

\n

\nDespite Uganda’s progress in fighting HIV, there are still 1.4 million people estimated to be living with the disease. Significant disparities exist between regions, and HIV continues to affect adolescents, especially girls, disproportionately; HIV prevalence among adolescent girls and young women is approximately four times higher than males in the same age group.\n

\n

\nMajor barriers to testing and treatment persist for key populations and vulnerable groups. While Global Fund partners in Uganda have been able to carry out activities aimed at removing human rights-related barriers to access (which is especially laudable given recent challenging circumstances, including a restrictive legal environment and COVID-19), there is a need for such interventions to be scaled up to spark sustainable and widespread change. Continued support for reducing stigma and discrimination is a key component of the current Global Fund investment. Uganda is one of 20 focus countries within the Global Fund’s Breaking Down Barriers initiative to have benefited from additional capacity building on human rights and gender.\n

\n

\nTB continues to be a major public health threat, with Uganda appearing on the World Health Organization’s global lists of high-burden countries for TB, and for HIV-associated TB. There is significant variation in the treatment success rate across the country. Rural settings typically report a treatment success rate lower than their urban counterparts and are more likely to be less advanced in their progress toward achieving the goals of the End TB Strategy.\n

\n

\nRecent evidence of artemisinin partial resistance emerging in Uganda is concerning. Our current investments support a range of strategies to fight drug-resistant TB in Uganda, including early detection, enhanced access to drug susceptibility testing, increased treatment enrolment and improved patient management.\n

\n

\nMalaria remains the leading cause of morbidity and mortality in Uganda. In 2020, the country accounted for 5.4% of all malaria cases and deaths globally. Uganda’s largely tropical climate and mix of warm, humid forests and large regions with dense agricultural settlements contribute to some of the highest recorded malaria transmission intensities in the world. Additionally, there are almost 1 million refugees in Uganda, who are particularly vulnerable to malaria infection.\n

\n

\nTo address the many challenges in the malaria response in Uganda, the Global Fund has supported significant scale-up of prevention and control measures, including distributing over 25 million long-lasting insecticidal nets and improving access to parasite-based diagnosis and artemisinin-based combination therapy.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$277 million has been signed for Uganda for 2021-2023. Our investment is geared toward reducing the number of youth and adult HIV infections by 65% (by 2025), lowering pediatric HIV infections by 95% (by 2025) and reinforcing social and economic protections to reduce vulnerability to HIV for key populations and other vulnerable groups.\n

\n

\nWe continue to support the TB response in Uganda through a grant of up to US$29 million for 2021-2023. The grant supports Uganda in its goal of reducing TB incidence to 160 cases per 100,000 people by 2025, boosting both treatment coverage and the TB treatment success rate to 90%, and increasing TB awareness and the number of people with TB symptoms who seek appropriate care. A combined HIV and TB grant of up to US$33 million interoperates with the above grants to further support Uganda’s HIV and TB reduction strategies.\n

\n

\nTwo malaria grants of up to a combined US$263 million were signed for 2021-2023. Our investments support interventions aligned with Uganda’s Malaria Reduction and Elimination Strategy, which aims to reduce malaria infection by 50%, morbidity by 50% and malaria-related mortality by 75% by 2025 (compared to 2019 levels).\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1674565731, + "_created": 1660908598, + "_id": "1aab972d3464646d690003d2" + }, + { + "iso3": "PAK", + "summary": "
\n

\nThe Global Fund has six country-specific grants active in Pakistan, with total funding of up to US$252 million signed for 2021-2023. Our investments support strategic interventions aimed at continuing the country’s progress against malaria and bending the curve for HIV and TB, which are seeing rising numbers of new infections and cases.\n

\n

\nProgress\n

\n

\nPakistan is the world's fifth-most populous country, with a population of 220 million people. Since gaining independence in 1947, the country has struggled to attain political stability and sustained social development.\n

\n

\nChallenges in Pakistan’s operating environment, and within the HIV and TB epidemic responses themselves, have hampered progress against the two diseases. HIV deaths, new infections, incidence rates and mortality rates continue to climb. Notification of TB cases has improved in recent years, but TB incidence continues to remain high, indicating a need to intensify efforts to close the treatment gap.\n

\n

\nA key positive in the country's TB response is that when people are identified as having the disease, they are likely to receive effective treatment. There is also a relatively high level of treatment adherence when people are diagnosed with TB – as the treatment success rate of 93% for drug-susceptible TB shows. However, a comparatively lower treatment success rate for drug-resistant TB (70%) indicates room for improvement in this aspect of the epidemic response, particularly considering that new effective treatment regimens are available.\n

\n

\nPeople who inject drugs bear a disproportionate burden of HIV compared to the general population in Pakistan. To help address the very high HIV prevalence within this key population, the country has established a harm reduction and prevention programming model in over 40 cities. The prevention program uses a community-based model with the critical involvement of locally focused organizations representative of key populations. While the harm reduction package does not currently include opioid substitution therapy (OST – an important element of HIV prevention among people who inject drugs), an OST pilot is expected to be implemented in 2023.\n

\n

\nReductions in malaria deaths and new cases have been significant – in 2020, both were around 75% less than in 2011 (although the number of new cases remains high at around half a million a year). Almost 100% of people with suspected malaria now receive a diagnostic test, up from 53% a decade ago. Global Fund investment supports the distribution of long-lasting insecticidal nets to people living in malaria-endemic areas.\n

\n

\nThe Global Fund has supported the implementation of a district health information system for TB and malaria. All TB and malaria program indicators are intended to be reported at district level through this system, which is expected to improve the accuracy and timeliness of programmatic results. The Global Fund also supported a recent renovation of Pakistan’s central warehouse to ensure that procured health products are stored safely and under acceptable conditions.\n

\n

\nChallenges\n

\n

\nKey populations are disproportionately affected by HIV in Pakistan. The highest estimated proportion of people living with HIV is among people who inject drugs (22.9%). Although antiretroviral therapy is available in the country and the number of HIV treatment centers has been scaled up, multiple challenges remain. Stigma and discrimination (sometimes from within the health care environment itself), lack of community and social support, and sexual and gender-based violence present barriers to the uptake of HIV services for all key populations.\n

\n

\nPakistan accounts for around 5% of new TB infections globally and is classified as having both a high TB burden and a high drug-resistant TB burden. It is estimated the country has more than half a million new TB cases each year.\n

\n

\nWhile improvement in TB case notification has helped to close the gap between the number of estimated and confirmed cases, weaknesses in identifying patients remain in some areas. Given the size of the TB epidemic in Pakistan, and the large number of people left without diagnosis and treatment in densely populated areas and in the poorest communities, there is an ongoing focus on implementing strategies to harness the full potential of community and private sector health workers. It is expected this will help to increase TB notification rates.\n

\n

\nGlobal Fund grants are predicated on governments financing specific activities to support the overall achievement of impact. However, the resources that Pakistan disburses for HIV and TB activities often fall short of what is needed to scale up HIV and TB interventions; a situation that can stifle progress in the fight against the two diseases.\n

\n

\nDespite the gains Pakistan has made against malaria over the last decade, the disease is still a major public health problem. Twenty-nine percent of Pakistan’s population lives in areas at high risk for malaria, 69% in areas at low and medium risk and just 2% in areas with no risk. The actual extent of the malaria burden in Pakistan is still largely unknown. This is complicated by the fact that a sizeable portion of malaria cases are in tribal areas that border Afghanistan and Iran, which have poor access to services. Information on deaths due to malaria is very limited, and malaria mortality is not regularly reported – a challenge the district health information system mentioned above aims to address.\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV grants totaling up to US$69 million have been signed for Pakistan for 2021-2023. These grants aim to address low prevention and testing coverage among key populations, strengthen the national monitoring and evaluation system, increase equitable access to quality continuum of care services and support interventions to reduce HIV morbidity and mortality.\n

\nThe Global Fund’s two 2021-2023 TB grants of up to a combined US$148 million continue our deep commitment to reducing the disease burden in one of the world’s highest TB burden countries. Our investment supports interventions that contribute to achieving the targets of Pakistan’s national strategic plan for TB control.\n

\n

\nFunding of up to US$34 million was allocated for two malaria programs for 2021-2023. Our grants support the national malaria program in its activities to reduce the reported malaria incidence by 20% in 60 districts by 2023.\n

\n

\nPakistan is included in several multicountry grants being implemented across groups of countries in the region. It also receives Global Fund emergency funding, as well as investment as part of our COVID-19 Response Mechanism, which supports countries to mitigate the impact of COVID-19 on programs to fight HIV, TB and malaria, and initiates urgent improvements in health and community systems.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1662544500, + "_created": 1662543632, + "_id": "e939f7433066306a550000ca" + }, + { + "iso3": "SSD", + "summary": "
\n

\nThe Global Fund currently has two core grants active in South Sudan, with funding totaling up to US$117 million signed for 2021-2023. Our investment supports health systems strengthening and HIV, tuberculosis (TB) and malaria interventions in South Sudan, where significant challenges have limited progress in the fight against the three diseases.\n

\n

\nProgress\n

\n

\nSouth Sudan gained independence from Sudan in 2011. It is one of the poorest countries in the world, ranked 186 out of 189 countries on the United Nations Human Development Index in 2019. Conflict, climate-related shocks, COVID-19, extreme food insecurity, large internal displacement and an ongoing humanitarian crisis have severely impacted the country’s development efforts.\n

\n

\nProgress against HIV, TB and malaria remains extremely difficult to catalyze in South Sudan. Yet, despite many significant challenges, some gains are being achieved. HIV interventions targeting key populations have been scaled up in several cities, and plans are underway to increase their coverage in more locations. The TB program exceeded its targets for case notification in 2018 and 2019 (before COVID-19 interrupted services) and TB treatment coverage has shown a steady upward trend since the country gained independence in 2011. Critical work is ongoing to develop and provide free, efficient, accessible, and quality-assured malaria interventions for all people in South Sudan. Gradual gains such as these are the foundation of future success in the country’s fight against the three diseases.\n

\n

\nChallenges\n

\n

\nA significant proportion of South Sudan’s 11 million people have almost no access to health services, with an estimated 44% of the population living within a 5-kilometer radius of a functional health facility. Most health facilities suffer from a lack of medical equipment, transport, communication technology, water, and power supply. There is also a critical shortage of qualified human resources for health, and a significant proportion of health facilities do not have the minimum required number of clinical staff recommended by the World Health Organization. These weaknesses in health systems are a central challenge in South Sudan’s response to diseases.\n

\n

\nSeveral million people in South Sudan need humanitarian assistance, including food aid, protection, and access to safe water sources and sanitation. People weakened by hunger and poor living conditions are less able to fight illness and disease, and displacement of communities can make people vulnerable to infectious disease.\n

\n

\nSexual and gender-based violence is recognized as a major health and human rights challenge in South Sudan and is a major impediment to accessing HIV testing and treatment. Stigma, discrimination and other violations of human rights affect the well-being of people living with HIV and key and vulnerable populations (sex workers, truck drivers, people in uniformed services, people in prisons and gay men and other men who have sex with men).\n

\n

\nTB prevalence remains high in South Sudan. Drug-resistant TB is an ongoing threat – especially as people with TB fleeing conflict and humanitarian crises are often forced to interrupt their TB treatment, raising the risk that they will develop resistance to anti-TB drugs.\n

\n

\nMalaria is endemic throughout South Sudan and continues to be a major public health problem. Transmission occurs throughout the year and peaks during the rainy season, which lasts for about six to eight months. In the face of increasing annual case numbers, strengthening and sustaining the management and coordination capacity of the national malaria program is a central challenge in the country’s fight against malaria.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$71 million has been signed for South Sudan for 2021-2023. Our investment supports bold reduction targets for the coming years, including a 50% decrease in new HIV infections, a 50% decrease in AIDS-related deaths, and a 30% reduction in TB incidence.\n

\n

\nWe continue to support the malaria response in South Sudan through a grant of up to US$45 million for 2021-2023. Our investment supports efforts to, by 2025, strengthen and sustain the management and coordination capacity of the malaria program, protect 85% of the population at risk through recommended malaria prevention methods, achieve 100% parasitological diagnosis and treatment of all presented cases of malaria, in addition to other high-impact interventions designed to eliminate malaria as a public health threat in South Sudan.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663756090, + "_created": 1663755583, + "_id": "bb9b0b7f3265343db40000ee" + }, + { + "iso3": "THA", + "summary": "
\n

\nThe Global Fund currently has two core grants active in Thailand, with funding totaling up to US$61 million signed for 2021-2023. Our investments aim to extend the country’s significant progress in the fight against HIV and tuberculosis (TB) by reaching more people in key populations, who bear a disproportionate burden of these diseases.\n

\n

\nProgress\n

\n

\nThailand has made significant progress in expanding access to HIV testing and treatment. In 2020, 94% of people living with HIV knew their HIV status, the percentage of people who knew their status and were on treatment had risen to 79% (up from 34% a decade earlier) and 77% of people receiving treatment had a suppressed viral load. Retention on antiretroviral therapy is high, with 90% of people still on treatment after 12 months.\n

\n

\nGains have also been made against TB. The mortality rate fell by one-third between 2002, when the Global Fund was founded, and 2019; treatment coverage rose from 55% (2010) to 82% (2020), and the TB treatment success rate has remained high at 85%. TB continues to be one of Thailand’s greatest health threats, however, and the epidemic response is now reaching deeper into the communities where those at the highest risk of TB live and work.\n

\n

\nChallenges\n

\n

\nThailand has the highest HIV prevalence in Southeast Asia, with around 480,000 people living with the virus. An estimated 138,000 people lack sufficient access to testing and treatment interventions. These people are distributed across various key populations, such as transgender women, people who inject drugs, male sex workers and gay men and other men who have sex with men.\n

\n

\nPeople who inject drugs have proven particularly difficult to reach. To increase the opportunity to address HIV for this key population, an urgent effort to tackle the barriers they encounter, including the discrimination and stigma that can exist in the health system, is needed. The Ministry of Health works to integrate the principle of harm reduction into drug treatment programs and educate drug treatment staff and law enforcement officials about its key role in HIV prevention.\n

\n

\nCurrent Global Fund investments support expanding community-based efforts to reach deeper into affected communities and social networks and engage their social and sexual contacts to stem the potential for the continued spread of HIV. In addition to these efforts to find more people living with HIV and start them on treatment more quickly, Thailand will continue to expand HIV prevention interventions. These include providing condoms, pre-exposure prophylaxis and sterile injection equipment to those whose behaviors may put them at risk of HIV infection, and ensuring they have the knowledge and means to protect themselves.\n

\n

\nThree key challenges in the response to end TB as a public health threat have been identified: continued under-diagnosis and under-reporting of TB (including drug-resistant TB, which remains a problem), suboptimal treatment outcomes, and the minimal use of prevention. Global Fund support has been instrumental in introducing rapid diagnosis, streamlining and improving reporting, supporting intensified case finding and providing patient support. However, ongoing focus and resources are still needed to ensure a successful financial and programmatic transition to domestic funding and support the high levels of capacity needed to achieve the End TB goals.\n

\n

\nGlobal Fund investments\n

\n

\nTwo TB/HIV grants of up to a combined total of US$61 million have been signed for Thailand for 2021-2023. Our investment is geared toward the RRTTPR approach: reach, recruit, test, treat, prevent, retain. This aims to optimize network and delivery models for both HIV and TB and increase the uptake of services for both diseases. The interventions that underpin this strategy are designed to support Thailand in its goal of reducing new HIV infections to less than 1,000 in 2030 and lowering TB incidence to 133 cases per 100,000 people by 2023.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663756297, + "_created": 1663756161, + "_id": "bbf343723431336559000337" + }, + { + "iso3": "SDN", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Sudan, with funding totaling up to US$128 million signed for 2021-2023. Our investments aim to support the country in protecting the gains it has made against HIV and tuberculosis (TB) and getting back on track in the fight against malaria, which continues to be a huge public health threat.\n

\n

\nProgress\n

\n

\nDespite numerous challenges, Sudan has made progress in the fight against HIV, with a 20% drop in new infections between 2002 and 2020. Greater ground has been gained against TB over the same period: Deaths fell by 63% and new cases by 43%. With continued investment, Sudan can consolidate those gains, and accelerate progress toward the Sustainable Development Goal target of ending the three diseases as public health threats by 2030.\n

\n

\nChallenges\n

\n

\nYears of economic shocks and political instability present ongoing challenges in the fight against HIV, TB and malaria in the country, with devastating consequences for the most vulnerable communities. COVID-19 further restricted access to health services.\n

\n

\nMalaria is a leading cause of death in the country and presents a huge public health threat: 87% of the population is classified as being at high risk of the disease. From 2012 to 2020, malaria deaths soared by 157% in Sudan. This represents the highest percentage increase among the 10 countries that receive the most support from the Global Fund for malaria programs.\n

\n

\nOver the last decade, the number of notified TB cases has been in the range of 19,000–22,000 annually. The Global Fund partnership aims to improve service coverage for TB in hard-to-reach areas as well as among refugees and displaced persons, increase the proportion of drug-resistant TB cases detected, reinforce cross-testing between TB and HIV programs, and expand the availability of TB preventive treatment.\n

\n

\nRegarding HIV, there are many sociocultural barriers in Sudan that stigmatize and discriminate against people living with HIV and the key populations who are disproportionately affected by the disease. Laws that criminalize sex work and homosexuality stifle access to services. Age-related structural barriers, both cultural and legal, hinder the access of adolescents and young adults to HIV prevention and testing services. Despite challenges such as the hidden nature of key populations, limited health infrastructure and human resources for health, HIV prevention interventions among key populations represent a key success of Sudan's HIV response in recent years. Ways of reaching key populations have been regularly refined and improved to offer prevention and care to an increasing number of people.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$25 million has been signed for Sudan for 2021-2023. Our investment is geared toward increasing access to prevention and treatment services for key populations, boosting coverage of antiretroviral therapy, improving the quality of life for people living with HIV, and enhancing the HIV monitoring and evaluation system to inform Sudan’s HIV response more strategically.\n

\n

\nWe are continuing to support the TB response in Sudan through a grant of up to US$13 million for 2021-2023. The grant supports Sudan in its 2025 goal of decreasing TB deaths by 75% compared to 2015 levels.\n

\n

\nA malaria grant of up to US$89 million was signed for 2021-2023. Our investment supports interventions designed to significantly increase the use of long-lasting insecticidal nets to 85%, ensure universal access to quality-assured malaria case management, control malaria during pregnancy, and strengthen epidemic monitoring. These strategies underpin Sudan’s goal of reducing malaria morbidity and mortality by 30% by 2025 (compared to 2018) and accelerating efforts toward malaria elimination where feasible.\n

\n

\nLast updated August 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663756536, + "_created": 1663756357, + "_id": "bc1129033362631436000182" + }, + { + "iso3": "ERI", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Eritrea, with funding totaling up to US$44 million signed until 2023. Our investments support the country in continuing the solid gains it has made against HIV and tuberculosis (TB), and in consolidating its significant progress against malaria, with pre-elimination activities for the disease taking place in some sub-regions.\n

\n

\nProgress\n

\n

\nEritrea is a small, coastal country located along the Red Sea in the Horn of Africa. Despite limited resources, the country has made significant progress in expanding and improving access to health services, which has led to progress in the fight against HIV, TB and malaria in some key respects.\n

\n

\nEritrea has experienced a long and steady downward trend in HIV incidence, which fell from 52 per 100,000 people in 2002 to 7 per 100,000 in 2020. Mortality rates also reduced over the same period; from 61 per 100,000 people to 8 per 100,000.\n

\n

\nAlthough much work remains to be done to achieve the UNAIDS 95-95-95 HIV testing and treatment targets, all criteria have seen progress over the last decade, with a strong upswing in the percentage of people who know their HIV status and are receiving antiretroviral therapy (up from 29% in 2010 to 73% in 2020). Important reductions were also achieved in the HIV infection rate among pregnant women, with a steady and significant decline from 2.4% in 2003 to 0.36% in 2019.\n

\n

\nStrong political commitment, a community-based health system and high community engagement have significantly contributed to a declining burden of TB in Eritrea. Although TB incidence and mortality have seen occasional annual increases over the last decade, both have declined over the longer term: Between 2003 and 2019, incidence fell from 298 to 86 per 100,000 people, while mortality fell from 46 to 15 per 100,000 people. There was also strong performance in the percentage of people co-infected with TB and HIV who receive antiretroviral therapy (95% in 2020), and in the treatment success rate, which until 2019 (most recent results available) had remained above 90% for four years.\n

\n

\nEritrea has made strong progress in controlling malaria. Significant reductions in morbidity and mortality have been realized as a result of strategic interventions, including the distribution of mosquito nets, early diagnosis, a high level of community awareness and access to health facilities throughout the country. Malaria-specific deaths have decreased exponentially for all ages, and, despite upsurges in malaria cases in some sub-regions in 2019 and 2020, Eritrea is steadily progressing towards malaria pre-elimination.\n

\n

\nChallenges\n

\n

\nEpidemiological surveillance data shows that the HIV epidemic in Eritrea is transitioning from a generalized one to an epidemic that disproportionately affects key populations: female sex workers, long-distance truck drivers and people in prisons. There are also marked differences in the burden of disease according to geographic location within the country. Addressing these key population and geographical inequities are central challenges in Eritrea’s fight against HIV.\n

\n

\nFree TB treatment and management are available in Eritrea, yet treatment coverage remains relatively low (55% in 2020) and is an ongoing challenge. Planned interventions to address this include deploying specialized diagnosis outreach services in hard-to-reach areas to increase TB detection, and equipping health facilities in remote areas (as well as urban areas) with GeneXpert machines. Although the TB treatment success rate sits at over 90%, treatment failure, loss to follow-up and non-evaluation are ongoing challenges in controlling TB.\n

\n

\nMalaria in Eritrea is unstable and seasonal, with transmission patterns differing across three ecological zones that differ by altitude. An estimated 70% of the country’s 3.2 million people live in malaria-prone areas, and, despite achievements in the control and prevention of the disease, it remains a serious public health concern. The threat of resurgence of the disease due to climatic changes is a concern, and the period from September to December is considered “malaria season” – especially in the Gash Barka and Southern regions, the two most susceptible malaria-risk regions in the country.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$21 million has been signed for Eritrea for 2021-2023. Our investment is geared toward reducing new HIV infections among adults and children by 50% by 2026 (compared to 2019 levels), lowering AIDS-related deaths among people living with HIV by 75% by 2026, reinforcing health and community systems delivery, and strengthening demand creation for HIV services.\n

\n

\nWe continue to support the TB response in Eritrea through a grant of up to US$5 million for 2021-2023. The grant supports Eritrea in its goal of reducing TB incidence to 45 per 100,000 people by 2026 and driving down TB mortality to 10 per 100,000 people by the same year.\n

\n

\nA malaria grant of up to US$18 million was signed for 2021-2023. Our investments support interventions aligned with Eritrea’s Malaria Strategic Plan, which is leveraging bold targets for testing, treatment, vector control methods, surveillance, monitoring, research and evaluation to achieve its goal of a “malaria-free Eritrea.”\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663756962, + "_created": 1663756715, + "_id": "bc47bcbb663837dd830003a8" + }, + { + "iso3": "GIN", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Guinea, with funding totaling up to US$138 million signed for 2021-2023. Our investments support the country in continuing the progress it has achieved against HIV, tuberculosis (TB) and malaria in the face of significant challenges.\n

\n

\nProgress\n

\n

\nDespite facing significant challenges due to weak infrastructure, low domestic financing, limited human resources in the health sector, COVID-19 and recurrent Ebola outbreaks, Guinea has made significant progress against HIV, TB and malaria.\n

\n

\nHIV incidence and mortality have both reduced significantly since the early 2000s. Community engagement has resulted in the ongoing improvement of HIV programs and to progress in testing, prevention, treatment adherence and psychosocial support. The promotion and defense of human rights is also central to communities, and community engagement has contributed to addressing stigma and discrimination and to monitoring the quality of services.\n

\n

\nEfforts to end TB as a public health threat are bearing fruit, with treatment coverage (66% in 2020), treatment success (89% in 2019) and drug-resistant TB treatment success (71% in 2018) all increasing since 2010. The TB mortality rate has dropped almost two-thirds since 2001, although new cases remain high.\n

\n

\nGuinea has made important progress in malaria control and prevention, substantially reducing malaria prevalence in children under 5, annual malaria incidence, and malaria-related deaths (which fell by almost 30% since 2002, when the Global Fund was founded, and 2020). With support from the Global Fund, Guinea began implementing seasonal malaria chemoprevention in several districts that met the criteria for this safe, effective and cost-effective intervention.\n

\n

\nChallenges\n

\n

\nDespite efforts to address stigma in Guinea, it remains a considerable barrier to access to HIV care. Sex workers, people who inject drugs and gay men and other men who have sex with men are still subject to acts of social exclusion, intolerance, rejection and, sometimes, violence. Additionally, stigma leads to late-stage consultations and strong reluctance among partners of people in key populations to get tested. It also contributes to higher rates of prevalence – among gay men and other men who have sex with men, prevalence is 10 times higher than for men in the general population. For female sex workers, prevalence is five times higher than for women in the general population.\n

\n

\nThe government of Guinea has made preventing mother-to-child transmission a priority, with more than 100 perinatal transmission centers added in recent years. Accelerating the elimination of mother-to-child transmission of HIV by ensuring people living with HIV are treated following WHO recommendations (aimed at guaranteeing essential aspects such as the quality of screening tests) is a critical element in the ongoing fight against HIV in the country.\n

\n

\nAlthough TB incidence has fallen overall since the turn of the century, the decline has been less marked over the last decade due to social unrest, socioeconomic factors, financing and health systems constraints and Ebola outbreaks in 2013-2016 and 2021. Guinea’s national TB program identifies a geographically differentiated TB service delivery care model as a priority in addressing the country’s TB challenges. This will put a necessary focus on reinforcing case finding in the three regions that account for 80% of \"missing” TB cases (Nzérékoré, Kankan and Conakry), optimizing laboratory networks, expanding the use of GeneXpert machines, decentralizing testing and treatment centers and better integrating the joint TB/HIV response.\n

\n

\nMalaria is a serious public health problem in Guinea and is the leading cause of visits to health facilities. In addition to endemic or moderate-transmission zones placing much of the population at risk of contracting the disease, the Guinean health system faces complex challenges that create difficulties for the delivery of malaria services. Suboptimal governance, funding shortfalls and inconsistent supply of health commodities hamper the rollout of the community health strategy and operational plan. Weaknesses in health information management systems and health product management restrict the full functionality of the health care network. The Global Fund’s current malaria grant is investing in strengthening health systems to address these critical areas, in addition to direct malaria interventions such as vector control and case management.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$44 million has been signed for Guinea for 2021-2023. Our investment is geared toward halving new infections and reducing mortality for people living with HIV by almost two-thirds in the coming years.\n

\n

\nWe continue to support both the TB and HIV response in Guinea through a TB/HIV grant of up to US$22 million for 2021-2023. The grant supports Guinea in its goal of halving the TB mortality rate and reducing the incidence rate by 18% in the coming years. The HIV component of the grant is focused on supporting a differentiated HIV prevention strategy that enhances linkage to care, strengthens community systems, and removes barriers to care – particularly for key populations.\n

\n

\nA malaria grant of up to US$71 million was signed for 2021-2023. Our investment supports efforts to increase the resilience and sustainability of Guinea’s health systems, as well as the implementation of targeted vector control, case management and specific prevention interventions.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663757197, + "_created": 1663756994, + "_id": "bc72415861336539390002c5" + }, + { + "iso3": "ZMB", + "summary": "
\n

\nThe Global Fund currently has four core grants active in Zambia, with funding totaling up to US$327 million signed for 2021-2023. Our investments aim to support the country in continuing the strong progress it has made in the fight against HIV and tuberculosis (TB), countering recent worsening epidemiologic trends for malaria, and protecting gains against the three diseases.\n

\n

\nProgress\n

\n

\nZambia is a landlocked, resource-rich country in the center of southern Africa. The country is experiencing a sizeable demographic shift; its population of 19 million is growing by almost 3% each year and is one of the world’s youngest by median age.\n

\n

\nZambia has made significant progress toward halting and reversing its HIV, TB and malaria epidemics over the last two decades. HIV incidence has fallen by 58% since 2000 and TB incidence by 56% over the same period. AIDS-related deaths have declined by almost 70%. Hard-won gains have been made against malaria, which is endemic in Zambia. Between 2002-2020, deaths fell by 23% and new cases by 16%. Almost every person with suspected malaria received a parasitological diagnosis in 2019.\n

\n

\nChallenges\n

\n

\nDespite health having been prioritized as a key economic investment, spending is under pressure due to a deep recession sparked by the COVID-19 pandemic and a collapse in the price of copper, which accounts for around 70% of Zambia’s exports. In 2022, the World Bank reclassified the country as low-income for the first time since 2011, effectively reversing a decade of development gains.\n

\n

\nSustaining the procurement and supply of laboratory commodities is one of the key priority areas for the Zambian health sector. Plans to scale up warehouse management systems are expected to improve order processing and boost responsiveness to the needs of health facilities. Order variability, stock-outs and wastage present challenges for health facilities in terms of the timeliness and type of medicines and health products they can provide – an issue expected to be addressed by better operational support to logistics hubs.\n

\n

\nAlthough HIV incidence has more than halved, the number of new cases remains high, at around 70,000 a year. Significant gender- and age-related disparities in the HIV burden also remain. Incidence among females of all age groups is higher than their male counterparts. The difference is most pronounced for adolescent girls and young women aged 15-24, among whom HIV incidence is 10 times higher than boys and men of the same age. Key populations are at much higher risk of HIV exposure than the general population, and they are subjected to stigma, discrimination, violence and harassment, and have limited access to health services.\n

\n

\nZambia is among the 30 highest TB/HIV burden countries in the world, and TB accounts for over 40% of deaths among people living with HIV. Though there are health care systems in place for the prevention and treatment of TB among people living with HIV, gaps in diagnosis for HIV and TB co-infection persist. Drug-resistant TB remains a significant threat. Although the treatment success rate for drug-resistant TB has crept up in recent years, at 78% it remains substantially lower than the success rate for non-drug-resistant TB.\n

\n

\nMalaria remains endemic in Zambia, with the entire population considered to be at risk of contracting the disease. Risk is highest in the wetter, rural, poor provinces of Luapula, Northern, Muchinga and North-Western. Despite significant progress in control interventions since 2000, epidemiologic trends in malaria indicators worsened in 2020. To protect gains, Zambia’s malaria elimination strategic plan identified that indoor residual spraying (IRS) needs to be undertaken at a near-universal level throughout 2022. From 2023, IRS will be scaled back and deployed only in malaria hot spots, with long-lasting insecticidal nets being used as the primary vector control intervention.\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV/TB grants of up to a combined total of US$262 million have been signed for Zambia for 2021-2023. The grants support Zambia in its goal of reducing new HIV infections and AIDS-related mortality by 50% compared to 2019 levels, cutting HIV-related stigma and discrimination by half, achieving significant increases in TB case notifications and the drug-resistant TB success rate and scaling up TB/HIV collaborative activities.\n

\n

\nWe are continuing to support a robust malaria response in Zambia through two grants of up to a combined total of US$65 million signed for 2021-2023. Our investment is geared toward increasing the implementation rate of interventions to 95% (up from 36% in 2015), increasing the malaria-free health facility catchment areas to 100% (up from 0.5% in 2015) and reducing malaria deaths to less than five deaths per 100,000 people (down from 15.2 deaths in 2015).\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1681899427, + "_created": 1663757247, + "_id": "bc98ee16363462f1450000de" + }, + { + "iso3": "GNB", + "summary": "
\n

\nThe Global Fund currently has two core grants active in Guinea-Bissau, with funding totaling up to €51 million allocated for 2021-2023. Our investments aim to strengthen the country’s health systems and extend hard-won progress in the fight against HIV, tuberculosis (TB) and malaria.\n

\n

\nProgress\n

\n

\nGuinea-Bissau lies on the Atlantic coast of western Africa and has a population of almost 2 million people. The country has a history of political and institutional fragility dating back to its independence from Portugal in 1974. Yet, despite this instability impacting Guinea-Bissau's ability to sustain resilient health systems, progress against HIV, TB and malaria has been achieved.\n

\n

\nMass distribution of mosquito nets coupled with sustained prevention and treatment programs have led to a significant decrease in malaria prevalence, especially among children under 5. The TB treatment success rate has remained stable at 73% in the face of rising cases.\n

\n

\nThe country continues to improve HIV testing among pregnant women, and critical increases have been achieved in the percentages of people who know their HIV status, who know their HIV status and receive antiretroviral therapy, and who receive antiretroviral therapy and are virally suppressed.\n

\n

\nChallenges\n

\n

\nStakeholders from government, civil society, and donor organizations recognize that decades of instability in Guinea-Bissau have hampered the creation of structures and processes needed to mount an optimal response to HIV, TB and malaria. Health systems in the country require significant strengthening, something that Global Fund grants are supporting through investments in community systems, laboratory networks, information systems, procurement and supply chain management, and monitoring and evaluation.\n

\n

\nWomen are disproportionately affected by HIV in Guinea-Bissau. Prevalence among adolescent girls and young women is three times that of their male counterparts. And, while progress has been achieved in pregnant women tested for HIV and enrolled on antiretroviral therapy, retention in treatment remains challenging. Indeed, there is a high rate of loss to follow-up for all adults who receive antiretroviral therapy. Frequent HIV clinic relocations, inadequate drug supply leading to treatment interruptions, and the movement of people living with HIV are key factors in people stopping treatment.\n

\n

\nStigma, discrimination and sexual and gender-based violence are a challenge in Guinea-Bissau and present a major barrier to key and vulnerable populations accessing HIV testing and treatment. Although HIV data is limited, it is known that key populations – sex workers, people who inject drugs, people in prisons and gay men and other men who have sex with men – bear a higher HIV burden than the general population. Vulnerable groups – long-distance drivers, female vendors, men in uniform and people with disabilities – also bear a higher burden of disease.\n

\n

\nGuinea-Bissau is classified by the World Health Organization as one of the 30 high-burden countries for TB/HIV co-infection. Poor immune systems as a result of HIV infection or weakened by poverty have made it difficult to slow down the progression of TB, and new cases continue to climb (although mortality rates have remained relatively stable since 2016). The country’s weakened health care systems continue to present a significant challenge to strengthening TB case detection, improving quality of care and boosting TB treatment success rates.\n

\n

\nMalaria is endemic throughout Guinea-Bissau and, as the leading cause of morbidity, remains a significant public health threat. Prevalence nearly doubles during the rainy season from June to November. The central focus of the malaria response in Guinea-Bissau is on consolidating existing gains and making continued progress through high-impact interventions: long-lasting insecticidal net mass campaigns every three years; extending seasonal malaria chemoprevention to more regions; intermittent preventive treatment of malaria in pregnancy; facility- and community-based case management; and research, surveillance and insecticide resistance monitoring. \n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to €26 million has been signed for Guinea-Bissau for 2021-2023. Our investment supports bold reduction targets for the coming years, including a 50% decrease in new HIV infections and a 90% reduction in AIDS-related deaths (both compared to 2017 levels). And for TB, the goal is a one-third reduction in TB incidence and deaths (compared to 2018 levels).\n

\n

\nWe continue to support the malaria response in Guinea-Bissau through a grant of up to €24 million for 2021-2023. Our investment contributes to reducing malaria-related morbidity and mortality by supporting activities to increase malaria diagnosis and treatment, scale up integrated community case management interventions and continue vector control and preventive interventions.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663757882, + "_created": 1663757612, + "_id": "bcd0921f333962b4cd0002f2" + }, + { + "iso3": "BDI", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Burundi, with funding totaling up to US$119 million signed until 2023. Our investments support the country in continuing the hard-won progress it has made against HIV, tuberculosis (TB) and malaria, and in addressing challenges within the health system to make it stronger and more resilient.\n

\n

\nProgress\n

\n

\nBurundi is a landlocked country bordered by Rwanda to the north, Tanzania to the east and southeast, and the Democratic Republic of the Congo to the west. The country of over 12 million people has undergone periods of civil war, ethnic conflict and political instability that have impacted progress in economic and human development indicators. Despite these challenges, critical gains have been made in the fight against HIV, TB and malaria.\n

\n

\nHIV incidence and mortality have both reduced significantly since the early 2000s. Incidence fell from 127 per 100,000 people in 2002 to 15 per 100,000 in 2020. Mortality rates dropped from 176 per 100,000 people to 14 per 100,000 people over the same period. Increases in the UNAIDS 95-95-95 HIV testing and treatment target criteria have been strong: As of 2020, 89% of people living with HIV knew their status, 88% of people who knew their HIV status were receiving lifesaving antiretroviral therapy (up from 24% in 2010), and 79% of people on treatment had a suppressed viral load (almost double the 2015 result).\n

\n

\nEfforts to end TB as a public health threat are bearing fruit, with the treatment success rate sitting above 90%. Incidence and mortality rates have roughly halved since 2002.\n

\n

\nPeriodic malaria outbreaks over the last decade have turned the previously declining trend of new cases in the wrong direction, with cases in 2020 higher than at any point in the last two decades. Malaria deaths, while increasing, have not risen at the same rate as new cases, and in 2020 had fallen by 38% overall since 2002.\n

\n

\nChallenges\n

\n

\nDespite progress against the three diseases, Burundi still faces numerous public health challenges. Prevention of mother-to-child transmission of HIV remains a key issue. Approximately 50% of new infections occur in children aged 0-4. Analysis shows that the main reasons behind the high level of perinatal transmission are either pregnant women living with HIV not receiving treatment, or women living with HIV dropping off antiretroviral therapy during the breastfeeding period.\n

\n

\nAlthough TB incidence and mortality trends have declined in the long term, they have stagnated in recent years (TB deaths have increased year-on-year since 2017). Treatment coverage has shifted very little over the past decade, and finding “missing” people with TB remains a challenge –particularly those with drug-resistant TB.\n

\n

\nMalaria is endemic and a leading cause of morbidity and mortality in Burundi. The entire population is at risk of infection, and pregnant women and children under 5 are particularly vulnerable. Cross-border coordination is one of the greatest barriers to malaria elimination across Africa, and it is particularly pronounced in Africa’s Great Lakes region. This cluster of malaria-endemic countries, which includes Burundi, in East and Central Africa has experienced instabilities that have displaced millions. Refugees and migrant workers regularly cross borders, making conditions ripe for malaria transmission. Countries in Africa’s Great Lakes region also share other common malaria transmission risk factors, including extreme weather patterns, insecticide resistance, rapid population growth, and limited financial resources.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$44 million has been signed for Burundi for 2021-2023. For HIV, our investment supports activities to achieve bold reduction targets in new HIV infections and mortality, significantly lower the risk of mother-to-child transmission, improve the living conditions for people living with HIV and ensure the management of the national HIV response is efficient and effective.\n

\n

\nThe grant’s TB interventions are aimed at boosting detection, diagnosis and treatment (especially for drug-resistant TB), testing 100% of TB patients for HIV and putting 100% of TB/HIV co-infected patients on antiretroviral therapy, maintaining a treatment success rate of over 90%, and improving the management capacity of the national TB control program.\n

\n

\nWe continue to support the malaria response in Burundi through a grant of up to US$65 million for 2021-2023. The grant supports Burundi in its goal to achieve and maintain universal coverage of long-lasting insecticidal nets (with at least 80% of the general population using them), maintain a very high coverage of indoor residual spraying in targeted areas, and increase protection coverage for pregnant women, in addition to a spectrum of other high-impact objectives to fight malaria.\n

\n

\nFunding of up to US$8 million was allocated for 2022-2023 to strengthen the resilience of health systems in Burundi. Interventions in health management information systems, logistics management lab information systems, laboratory strengthening and governance are essential for achieving progress against HIV, TB and malaria.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663758146, + "_created": 1663757900, + "_id": "bcfc934c396232732f00013a" + }, + { + "iso3": "NER", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Niger. Our investments support health systems strengthening and service delivery enhancements for HIV, tuberculosis (TB) and malaria in a highly challenging environment.\n

\n

\nProgress\n

\n

\nNiger is a large, landlocked country in West Africa. Almost half of Niger’s 25 million people live below the poverty line, and the country faces several challenges such as desert terrain, high fertility rates, chronic food insecurity, natural disasters and security threats. Despite the significant constraints this imposes on the health system, progress against HIV, TB and malaria has still been made.\n

\n

\nSignificant reductions in AIDS-related deaths have been achieved (down 64% in 2020 compared to 2002) and new HIV cases have fallen by three-quarters since 2002. TB incidence and mortality have both reduced significantly since the early 2000s: Incidence fell from 190 per 100,000 people in 2000 to 84 per 100,000 in 2019, while the mortality rate dropped from 57 per 100,000 people to 15 per 100,000 people over the same period.\n

\n

\nAlthough malaria cases continue to increase, traction has been gained in other important respects. Almost 100% of people with suspected malaria now receive a diagnostic test, compared to 68% in 2010. A reduction in deaths has accompanied the increase in testing; more than 7,000 fewer people died from malaria in 2020 than in 2010.\n

\n

\nChallenges\n

\n

\nSecurity conditions have worsened in Niger in recent years, particularly where the country borders Nigeria, Mali and Burkina Faso. In these areas, armed groups have established bases and carried out repeated attacks against security forces and civilians. The security situation poses a major risk to Niger’s economic growth and public finances, which in turn impacts its already fragile systems for health. The Global Fund uses a flexible approach to deliver needed services and medicines in this challenging operating environment.\n

\n

\nThe HIV epidemic in Niger has a low prevalence in the general population. However, prevalence is many multiples higher among key populations, including sex workers, people in prisons and gay men and other men who have sex with men. There are also significant disparities according to age and geographical location. Analysis of the prevention of mother-to-child transmission cascade revealed weaknesses in diagnosis and treatment that need to be addressed in order to maximize coverage and impact.\n

\n

\nTesting remains low, with around 25% of adults unaware of their HIV status. Approximately 50% of children are undiagnosed. Several factors contribute to this, including insufficient numbers of nurses or community health workers trained in HIV testing and counseling, the need for increased supervision and support, stock-outs of tests, and widespread stigma still associated with HIV.\n

\n

\nWhile TB incidence and mortality in Niger have fallen in recent years, new case numbers remain high. Access to sustainable diagnosis and treatment services remains a challenge due to a lack of predictable funding, sustained technical support and health care workers. These factors, along with uneven distribution of facilities throughout the country, undermine capacity to distribute drugs and provide quality, far-reaching, ongoing TB services. Global Fund investments aim to expand and enhance TB services and support access to treatment for people facing discrimination and for other vulnerable people, especially those living in nomadic communities, migrant groups and prisons.\n

\n

\nMalaria is a serious public health concern in Niger. The entire country is endemic, with a seasonal upsurge during the rainy season. Fighting the disease in Niger is particularly complex due to a combination of increased resistance to the insecticides used to treat long-lasting insecticidal nets, seasonal malaria transmission that differs across regions, and ongoing insecurity in border regions that hampers the rollout of prevention initiatives.\n

\n

\nTo address these challenges, the Global Fund invests in a mix of interventions. Armed with new data on insecticide resistance across the country and innovative new nets treated with a combination of insecticides, the Global Fund partnership is evolving the way we distribute nets to stay ahead of the malaria parasite as it adapts. These new nets – designed to fight insecticide resistance – are key to making sure that families are protected with the latest innovations.\n

\n

\nA common element impacting the response for all three diseases is the country’s health system, which can present challenges in terms of functionality, organization and management of health commodities, supply chains, information systems, laboratory networks, and human resources for health. This is being addressed through a dedicated Global Fund grant that supports activities to enhance the resilience and sustainability of systems for health.\n

\n

\nGlobal Fund investments\n

\n

\nOur HIV grant is geared toward achieving bold reduction targets in new HIV infections and mortality, improving the living conditions for people living with HIV, and strengthening the demand for and supply of quality health care and services for the entire population.\n

\n

\nWe continue to support the TB response in Niger through a grant that supports interventions to increase the screening and treatment success rates and strengthen program management capacity, with the aim of achieving significant reductions in incidence and mortality rates by 2026.\n

\n

\nOur malaria grant supports Niger in its goal of reducing malaria morbidity and mortality in the general population by 40% in the coming years, compared to 2015 levels.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1676648124, + "_created": 1663758209, + "_id": "bd2bb4c8306230e1f90001e6" + }, + { + "iso3": "HTI", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Haiti, with funding totaling up to US$119 million signed for 2021-2023. Our investments support the country in continuing the hard-won progress it has made against HIV, tuberculosis (TB) and malaria, and in addressing chronic and systemic challenges within the health system to make it stronger and more resilient. \n

\n

\nProgress\n

\n

\nGlobal Fund-supported programs in Haiti have made significant gains despite political instability, economic hardship and recurrent disasters, including devastating earthquakes in 2010 and 2021.\n

\n

\nSince the Global Fund was founded in 2002, AIDS-related deaths have fallen by 82% and new infections by almost two-thirds. By 2020, 80% of people living with HIV knew their status, the percentage of these people who were receiving lifesaving antiretroviral therapy treatment had risen significantly to 80% (up from 21% in 2010), and the percentage of people on antiretroviral therapy who were virally suppressed had nearly doubled compared to 2015.\n

\n

\nInterventions from the Global Fund and other agencies such as the U.S. Agency for International Development and the U.S. Centers for Disease Control and Prevention have supported the country in making solid gains against TB. Between 2002 and 2019, TB incidence fell from 277 cases per 100,000 people to 179 cases. TB mortality also fell significantly over the same period – from 16 deaths per 100,000 people to 9 per 100,000.\n

\n

\nHaiti has made significant progress against malaria, with deaths and new cases halving between 2002 and 2020. This significant reduction in the disease burden has resulted in Haiti moving from the control phase of its epidemic to the elimination phase.\n

\n

\nChallenges\n

\n

\nThere are significant disparities in Haiti’s HIV epidemic based on gender, geography and population profile. HIV prevalence for women is 2.6%, compared to 1.6% for men. Seventy-five percent of people who tested positive for HIV in 2019 were in three of Haiti’s 10 departments (Ouest, Artibonite and Nord), and there are large variations in prevalence between departments. Prevalence among key populations – sex workers, people in prisons and gay men and other men who have sex with men – is many multiples higher than for the general population. Stigma and discrimination persist and creating a defined legal environment that is supportive of the rights of key populations and people living with HIV remains a key challenge.\n

\n

\nDespite the progress made against TB, numbers are still high, and Haiti tops the list of countries with the highest rate of incidence in the Americas. The country’s health system, which experiences chronic and systemic issues, presents a fundamental challenge in accelerating progress against TB, as well as HIV and malaria.\n

\n

\nSignificant problems exist in terms of functionality, organization and management of health commodities, the national network of laboratories, and human resources for health, including community health workers and health information management.\n

\n

\nReorganizing the service delivery model is a key element in successfully strengthening and broadening the level of care and services offered in Haiti. This is being supported through a Global Fund grant that aims to meet the needs of the HIV, TB and malaria programs by supporting cross-cutting activities that strengthen health systems.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$79 million has been signed for Haiti for 2021-2023. Our investment supports the country in its goal of ending TB as a public health threat by 2025, and controlling the HIV epidemic using quality interventions that promote human rights and dignity.\n

\n

\nWe continue to support the malaria response in Haiti through a grant of up to US$16 million for 2021-2023. The grant supports interventions aimed at ensuring 100% of populations in malaria-affected areas have access to early diagnosis and immediate treatment, strengthening surveillance and program monitoring, achieving universal coverage of malaria protection measures in outbreak areas, and developing communications that help drive malaria elimination of local malaria transmission.\n

\n

\nFunding of up to US$23 million was allocated for 2021-2023 to strengthen the resilience of health systems in Haiti. Our investment supports the development, expansion and/or optimization of product management systems; information management, monitoring and evaluation systems; human resources for health, with a focus on community health workers; and laboratory systems. These activities are aimed at better meeting the needs of the HIV, TB and malaria programs and supporting Haiti’s hard-won gains against the three diseases.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663763171, + "_created": 1663758511, + "_id": "bd59daf939393482b200031b" + }, + { + "iso3": "UKR", + "summary": "
\n

\nThe Global Fund has funding totaling up to US$190 million signed for Ukraine for 2021-2023. Our investments – emergency funding, COVID-19 investment and three HIV/tuberculosis (TB) grants – aim to support the country in adapting HIV and TB service delivery and protecting gains against the diseases amidst health systems affected by conflict.\n

\n

\nProgress\n

\n

\nUntil the conflict between Ukraine and Russian Federation started in late February 2022, Ukraine had made significant gains in combating HIV and TB.\n

\n

\nGood progress had been achieved against HIV since the Global Fund began investing in the country 20 years ago, both in terms of linkage to treatment and viral suppression, as well as in significantly reducing infections and deaths. In 2021, 75% of people living with HIV knew their status, 83% of those diagnosed were on antiretroviral therapy treatment and 94% were virally suppressed. Domestic financing for HIV prevention services for key and vulnerable populations also increased, as did the capacity of community-led organizations.\n

\n

\nThe TB incidence rate fell steadily between 2015 and 2020, from 91 to 73 cases per 100,000 people, according to WHO data. Antiretroviral therapy treatment coverage among people living with both TB and HIV increased from 65% in 2015 to 91% in 2020. However, the prevalence and deaths from TB remain high in the country and drug-resistant TB remains a public health threat.\n

\n

\nDuring the COVID-19 pandemic, HIV and TB programs in Ukraine quickly evolved in order to sustain service delivery. This adaptation and increased resilience of health services and community systems has proven invaluable in the current emergency, especially as hospitals have been damaged or destroyed and people continue to be displaced, causing them to lose access to health care, including treatment for HIV and TB.\n

\n

\nChallenges\n

\n

\nSince the start of the emergency in Ukraine, more than 14 million people have been internally displaced or forced to flee to neighboring countries as refugees. For these displaced people, access to health care has been severely limited, HIV and TB prevention and diagnosis services have been disrupted, and many people with HIV and TB have been forced to interrupt their treatment.\n

\n

\nEven before the conflict, Ukraine had a high HIV and TB disease burden. Since it began, hundreds of health facilities have been damaged or destroyed, leaving health care workers and patients displaced, injured, or killed. Our work with partners in Ukraine and neighboring countries has focused on prevention, testing and treatment for HIV and TB – and this has now been challenged by the current emergency. Dangerous conflict zones, the mass movement of people, and the diversion of resources threaten the fragile gains made against HIV and TB.\n

\n

\nGlobal Fund investments\n

\n

\nIn March 2022, the Global Fund approved US$15 million in emergency funding to support the continuity of HIV and TB prevention, testing and treatment services in Ukraine.\n

\n

\nThis is in addition to the US$135.7 million in grants and catalytic matching funds allocated to Ukraine to support the fight against HIV and TB in the country over the 2021-2023 period and US$54.5 million for the country's COVID-19 response – totaling nearly US$190 million.\n

\n

\nThe Global Fund has also approved over US$28 million to reprogram existing grants to respond and adapt to the programmatic needs in the country.\n

\n

\nSome of our partner organizations are still working at partial capacity due to the fighting and insecurity, and we are working with them to carry out critical work required to strengthen health care and community systems and ensure patients have ongoing access to prevention, testing and treatment for HIV and TB. This includes funding for:\n

    \n
  • Generators for regional laboratories where power supplies are limited or at risk.\n
  • Retrofitting vans to deliver essential medicines and supplies.\n
  • Community-led organizations that support affected and displaced members of their communities and link them to HIV and TB services.\n
  • Work to help patients displaced in Ukraine and nearby countries get reconnected to the health care and medicine they need.\n
  • Providing food and care packages for TB and HIV patients and those who benefit from prevention/testing programs.\n
  • Funding legal support for communities and displaced people.\n
  • Locating appropriate accommodation for patients with infectious conditions like multidrug-resistant TB.\n
  • Providing funding for additional mental health services, with a particular focus on support for women who have suffered sexual violence as a result of the conflict.\n
\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663759340, + "_created": 1663759340, + "_id": "bdd833bf333137a2300001be" + }, + { + "iso3": "MDG", + "summary": "
\n

\nThe Global Fund currently has five core grants active in Madagascar, with funding totaling up to US$89 million signed for 2021-2023/4. Our investments aim to support the country in strengthening health systems and making greater progress against HIV, tuberculosis (TB) and malaria, in the face of significant economic, structural and environmental challenges. \n

\n

\nProgress\n

\n

\nMadagascar, an island country located in the Indian Ocean off the coast of southern Africa, is the fifth largest island in the world. Exposure to frequent, deep, and persistent crises has hampered human development in the country and created a challenging environment for effectively fighting HIV, TB and malaria. Despite these challenges, gains against the three diseases have been made in some important respects.\n

\n

\nInitiatives to collect the strategic data needed to refine and improve the efficiency of the country’s response against HIV have been undertaken. The share of health spending in the domestically funded budget has increased. Significant efforts have been made to improve the finding of people with TB and expanding treatment coverage, and the success rate for drug-resistant TB treatment has risen considerably since 2010. There has been a reduction in the number of districts with high malaria transmission and, with malaria diagnosis and treatment in public facilities now free-of-charge, nearly 100% of suspected cases of malaria are tested.\n

\n

\nChallenges\n

\n

\nChallenges due to weak economic growth and rapid rises in the population have been compounded by political instability and extreme weather events. Despite steady increases in the share of health in the domestically funded budget, the health system remains fragile, characterized by weaknesses in administrative frameworks, low utilization of health services, a shortage of qualified human resources, fragmented health financing and stock-outs of drugs and medical supplies. Weakened health systems hamper progress against the three diseases, and the Global Fund is supporting the country to build stronger and more resilient systems for health in the country.\n

\n

\nKey populations are disproportionately affected by HIV in Madagascar. The incidence rate among sex workers, people who inject drugs and gay men and other men who have sex with men is many multiples higher than among the general population. Prevention of mother-to-child transmission coverage is low, as is the percentage of people living with HIV who know their status. Madagascar’s epidemic response aims to better address these challenges by scaling up differentiated HIV prevention services that focus on key and vulnerable populations and their sexual partners, as well as the most-at-risk age groups and geographic locations.\n

\n

\nOver 80% of Madagascar’s population is estimated to be living in extreme poverty, which makes a large proportion of the Malagasy population susceptible to TB. The mainly airborne bacterial disease spreads easily in crowded living conditions, and immune systems weakened by undernourishment are less equipped to fight off the disease. Drug-resistant TB is an ongoing threat, as is the level of treatment coverage which, despite slightly improving over the last decade, remains relatively low at 55%. Given that one person with active TB can spread the disease to as many as 15-20 people in a year, finding “missing” people with TB is critical to kickstart reductions in incidence, which has stagnated at a very high rate for the past decade.\n

\n

\nMalaria is endemic in Madagascar. It remains a leading cause of mortality, and the entire population is at risk. Overall incidence is trending upward (although the epidemiology of the disease varies across regions), with more cases recorded during rainy seasons. While all malaria diagnosis and treatment in public facilities is free, effective case management can be challenging due to the geographical distance between homes and health care facilities, and between primary health care facilities and referral hospitals. Additionally, consultation of traditional healers can result in a delay in treatment. Other persistent challenges include stock-outs of commodities, insufficient data from community sites and suboptimal distribution strategies down to the last mile.\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV grants of up to a combined US$17 million have been signed for Madagascar for 2021-2023. Our investment supports Madagascar in working toward its bold targets for reductions in HIV incidence and morbidity, improving the quality of services for key and vulnerable populations and people living with HIV, and optimizing the management, monitoring and evaluation of the national HIV program.\n

\n

\nWe continue to support the TB response in Madagascar through a grant of up to US$15 million for 2021-2023. The grant supports interventions designed to help reduce TB incidence by 50% and TB mortality by 75% (by 2025 compared to 2018).\n

\n

\nA malaria grant of up to US$44 million was signed for Madagascar for 2021-2024. Our investment supports the country in its goal of achieving a 75% reduction in malaria mortality and morbidity in the coming years.\n

\n

\nFunding of up to US$12 million was allocated for 2021-2024 to strengthen the resilience of health and community systems in Madagascar. The grant is supporting the government in delivering the priorities of the national health plan, while also enabling increased impact for HIV, TB and malaria programs.\n

\n

\nLast updated September 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1663763528, + "_created": 1663763348, + "_id": "c03be2b43565624d550003d9" + }, + { + "iso3": "VEN", + "summary": "
\n

\nThe Global Fund currently has investments of up to a combined total of US$31.8 million to support the HIV, tuberculosis (TB) and malaria response in Venezuela. After almost eliminating malaria and achieving steady reductions in HIV and TB, the country has experienced upswings in all three diseases triggered by an ongoing economic crisis. Our investment aims to address the malaria resurgence by improving case management and vector controls, and it supports the procurement of lifesaving HIV and TB commodities for the Venezuelan population. \n

\n

\nProgress\n

\n

\nIn inland Venezuela, where the climate is particularly conducive to the development of malaria-transmitting mosquitoes, malaria incidence and mortality were extremely high until the mid-20th century. The country undertook a large and highly effective antimalarial campaign between the 1930s and 1970s that, by the early 1960s, eliminated the disease from 68% of Venezuelan territory and reduced the mortality rate to zero.\n

\n

\nA severe economic contraction starting in 2013 precipitated the collapse of Venezuela’s health systems and the government’s investment in the malaria response dramatically decreased. Consequently, the disease has re-emerged at an unprecedented rate, surging to almost half a million new cases in 2019 – a sharp reversal in a country that was once seen as a flag-bearer for global malaria eradication.\n

\n

\nThe protracted economic crisis has also severely affected Venezuela’s HIV and TB programs. Testing and diagnosis services have been diminished, and many TB patients and people living with HIV have lost access to regular care, treatment and medication.\n

\n

\nChallenges\n

\n

\nThe upsurge of malaria, coupled with Venezuelans' mass migration to neighboring countries due to socioeconomic instability and the ongoing humanitarian crisis, jeopardizes efforts in malaria-endemic countries bordering Venezuela to achieve their goals for disease control. This situation is worsened by the presence of mutations linked to the risk of artemisinin resistance in neighboring Guyana, raising concerns about Venezuela becoming a site for the potential regional spread of mutations linked to drug-resistance due to its high case numbers.\n

\n

\nLimited availability of HIV testing has significantly affected the program’s ability to diagnose new HIV infections. Weaknesses in health systems hamper the provision of continuous antiretroviral treatment, diagnosis and treatment of opportunistic infections, viral load and laboratory monitoring, and the supply and distribution of key prevention commodities.\n

\n

\nDiagnostic capacity for TB also remains limited, and, after years of gradual reductions, the incidence rate for the disease has trended sharply upward since the beginning of the economic crisis.\n

\n

\nGlobal Fund investments\n

\n

\nThe World Bank had long labeled Venezuela as an upper middle-income country – a classification that constrains assistance from key funders of HIV, TB and malaria research and control (including the Global Fund). Since 2021, the World Bank has listed Venezuela as \"unclassified” due to a lack of available data.\n

\n

\nDespite Venezuela’s current “unclassified” status, Global Fund policies have enabled investment there through a mix of exceptional funding approaches that can be implemented for non-eligible countries in crisis or areas experiencing a significant resurgence of disease.\n

\n

\nA malaria grant of up to US$19.8 million has been signed for Venezuela for 2021-2023. Our investment is geared toward controlling the resurgence of malaria and reducing malaria morbidity by expanding access to diagnosis, treatment and control in prioritized states, and by expanding the capacity of health services, including community health services.\n

\n

\nThe Global Fund Board has also approved an investment of US$12 million for 2022-2023 to continue to support the procurement of lifesaving commodities for the Venezuelan population. This funding, as well as the malaria grant, follows previous investments approved by the Global Fund Board under exceptional circumstances.\n

\n

\nVenezuela also receives investment as part of our COVID-19 Response Mechanism, which supports countries to mitigate the impact of COVID-19 on programs to fight HIV, TB and malaria, and initiates urgent improvements in health and community systems.\n

\n

\nConsidering the emergency circumstances in Venezuela, our investment there requires unique implementation and management arrangements that differ substantially from the standard Global Fund funding model and applicable policies.\n

\n

\nLast updated November 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669213837, + "_created": 1669209438, + "_id": "6e5b322739366137b20002cd" + }, + { + "iso3": "SWZ", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Eswatini, with funding totaling up to US$57 million signed for 2021-2024. Our investments aim to support the country in continuing to make strong gains against HIV and tuberculosis (TB) and in eliminating local transmission of malaria. \n

\n

\nProgress\n

\n

\nHome to nearly 1.2 million people, the landlocked Kingdom of Eswatini has made significant progress in the fight against HIV, TB and malaria.\n

\n

\nHIV incidence and mortality rates both declined by over 70% between 2002 and 2020. TB is in retreat; it spread rapidly across the country in the early 2000s and peaked in 2010 with an incidence of 1,592 cases per 100,000 people. In 2019, the incidence had dropped to 352 cases per 100,000 people.\n

\n

\nMalaria elimination has been a top priority for Eswatini for over a decade and is now within its grasp. Although elimination has not yet been realized, great gains have been made in lowering the number of cases and deaths. Systems for elimination have been improved, and an active and robust surveillance system has been created, building a solid platform to support successful elimination activities in the coming years.\n

\n

\nChallenges\n

\n

\nDespite the significant progress made against HIV and TB, both diseases continue to present a significant public health threat for Eswatini.\n

\n

\nHIV prevalence among adults aged 15-49 is 27%, with the burden of the virus highly disproportionate among women (36% prevalence, compared to 18% for their male counterparts). The gender disparity is most pronounced among people aged 20-24, where HIV prevalence is five times higher among young women (20%) than young men (4%). The government has identified adolescents and young people (15-24) as a new center of the HIV epidemic. Adolescent girls and young women are a key focus of the country's HIV prevention strategy, a critical aspect of which is the peer-led promotion of pre-exposure prophylaxis (PrEP). Reducing stigma around PrEP uptake among this vulnerable group, however, remains a challenge.\n

\n

\nKey populations and vulnerable groups are also at the center of the national strategic framework for HIV. Supporting safe and healthy livelihoods and removing gender- and human rights-related barriers to service delivery and accessibility is a key focus of current Global Fund investments.\n

\n

\nThere are age- and gender-related disparities among people with TB, with men aged 25-44 most affected. Behavioral factors contribute to the gender disparity. Men in Eswatini are 10 times more likely than women to smoke, an activity shown to double – or even triple – the risk of developing TB, and one that is associated with poorer treatment outcomes than those seen in non-smokers. Demographic and economic inequities also contribute to the burden of HIV and TB in Eswatini. Almost two-thirds of the population lives in poverty, and strong bidirectional linkages have been shown to exist between the two diseases and poverty in resource-poor settings.\n

\n

\nWhile Eswatini continues to advance toward malaria elimination, cases are still consistently reported, albeit at low rates. To achieve elimination, the country is focused on covering 100% of at-risk populations with appropriate vector control interventions, testing 100% of suspected malaria cases with quality-assured diagnostics, ensuring prompt and effective treatment, empowering the entire population to adopt malaria prevention and control practices, and expanding multisectoral partnerships to optimize program management.\n

\n

\nGlobal Fund investments\n

\n

\nTwo multicomponent HIV and TB grants of up to a combined US$55 million have been signed for Eswatini for 2021-2024. Our investment supports innovative HIV prevention approaches for adolescent girls and young women, accelerated scale-up of antiretroviral therapy, focused interventions for key and vulnerable populations, and other high-impact interventions aimed at halting the spread of HIV and reversing its impact in Eswatini. For TB, we support interventions focused on driving down incidence and mortality, with a goal of respective 40% and 50% reductions in the coming years.\n

\n

\nWe continue to support Eswatini’s strong malaria response through a grant of up to US$2.6 million for 2021-2023. Our investment supports Eswatini in its goal of eliminating local transmission of malaria by 2023.\n

\n

\nLast updated November 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669213808, + "_created": 1669212133, + "_id": "6ff6630a306230bc4200006a" + }, + { + "iso3": "TLS", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Timor-Leste, with funding totaling up to US$15.7 million signed for 2021-2023. Our investments aim to support the country in continuing to make progress against HIV, bending the curve for tuberculosis (TB) and achieving malaria-free certification in the coming years.\n

\n

\nProgress\n

\n

\nTimor-Leste became a sovereign state in 2002 after fighting for decades to gain independence from Indonesia. Faced with the task of rebuilding public infrastructure after years of conflict, the country of 1.3 million people has made significant progress in key areas since gaining independence, including in its fight against HIV, TB and malaria.\n

\n

\nIncreases in the UNAIDS 95-95-95 HIV testing and treatment target criteria have been encouraging: As of 2021, 91% of people living with HIV knew their status, 55% of people who knew their HIV status were receiving lifesaving antiretroviral therapy (up from 10% in 2010), and 42% of people on treatment had a suppressed viral load (more than double the 2015 result).\n

\n

\nRegarding TB, immense efforts have been made over the past two decades to improve the outreach of services and increase the quality of care. The COVID-19 pandemic impacted TB services and led to a more than 20% decline in case notification – the ongoing effects of which have the potential to lead to an increase in the number of people being infected with TB and dying from it. To stem the rising tide of people being infected with TB and ensure the work done over the last 20 years is not derailed, in 2021 Timor-Leste formally renewed its pledge to end the disease as a public health threat and launched the National Plan for Accelerated Actions for Ending TB by 2025.\n

\nLike many countries in the Asia-Pacific region that continue to accelerate and scale up their malaria control interventions, Timor-Leste has progressively reduced its malaria burden. The country has dramatically reduced cases from 220,000 in 2006 to zero indigenous cases in 2017. There have been no malaria-related deaths since 2015.\n

\n

\n

\nChallenges\n

\n

\nSex workers, transgender people, and gay men and other men who have sex with men bear a disproportionate HIV burden in Timor-Leste. Prevalence among these groups is many multiples higher than among the general population. Reaching these key populations and other people living with HIV requires interventions that address stigma and discrimination, both of which present an ongoing barrier to reporting, testing and treatment in the country.\n

\n

\nOther challenges within the HIV response include limited coverage of key population outreach programs, relatively low levels of HIV testing (especially among key populations), low adherence to antiretroviral treatment (33% lost to follow-up), low levels of prevention of mother-to-child transmission coverage and a shortage of comprehensive data on key monitoring and evaluation criteria for HIV. Current Global Fund investments support interventions to address these challenges and contribute to the renewed national strategic plan’s efforts to accelerate progress toward the 95-95-95 targets.\n

\n

\nTimor-Leste has one of the highest rates of TB incidence in Southeast Asia and has among the top 10 highest incidence rates in the world. Undernutrition and smoking have both been historically high in Timor-Leste and are the top two known drivers of the TB epidemic, contributing to more than half of the estimated new cases of TB.\n

\n

\nDrug-resistant TB is an ongoing threat. Recent years have seen large gaps in the number of people with drug-resistant TB and those identified by the national TB program. The national strategic program is responding to this threat by aiming to provide the correct diagnostic services to 100% of people with presumptive drug-resistant TB, and to achieve a success rate of 85% for drug-resistant TB treatment.\n

\n

\nThe few cases of malaria that there are in Timor-Leste appear in people who have been in the Indonesian province of West Timor, which shares a land border with Timor-Leste. Ongoing surveillance, testing and treatment are critical to preventing the re-establishment of malaria transmission and remain an ongoing challenge.\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$3.4 million has been signed for Timor-Leste for 2021-2023. Our investment supports HIV prevention approaches for key and vulnerable populations, activities to prevent loss to follow up of antiretroviral therapy, increasing coverage of prevention of mother-to-child transmission activities, and other high-impact interventions aimed at halting HIV transmission and significantly reducing AIDS-related deaths in Timor-Leste.\n

\n

\nA TB grant of up to US$8.3 million has been signed for Timor-Leste for 2021-2023. Our investment is geared toward supporting integrated, patient-centered TB care and prevention; bold policies and supportive systems; and intensified research and innovation.\n

\n

\nLast updated November 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669213776, + "_created": 1669213033, + "_id": "707fcbd937656306c4000243" + }, + { + "iso3": "GMB", + "summary": "
\n

\nThe Global Fund has three core grants currently active in The Gambia, with funding totaling US$38 million signed for 2021-2024. Our investments aim to support the country in accelerating progress in the fight against HIV, tuberculosis (TB) and malaria and in building resilient and sustainable systems for health that can respond to these diseases as well as future ones.\n

\n

\nProgress\n

\n

\nThe Gambia is a country of 2.5 million people in West Africa that, except for a narrow strip of Atlantic coastline, is bordered almost completely by Senegal. The fight against infectious diseases such as HIV, TB and malaria remains challenging, but gains have been achieved over the past two decades. HIV incidence has fallen from 168 per 100,000 people in 2002 (when the Global Fund was founded) to 98 per 100,000 in 2020. A trend of rising AIDS-related deaths began to turn in 2008 and HIV mortality has decreased in most years since.\n

\n

\nFor TB, the incidence has gradually declined, and the treatment success rate has remained relatively high, sitting at 84% in 2019. The country has its sights set on eliminating malaria, with the bold aim of achieving zero malaria deaths and no cases of indigenous transmission by 2025. Falling case numbers (down by 57% between 2002 and 2020) underscore The Gambia’s progress against the disease.\n

\n

\n

\nChallenges\n

\n

\nAcross the three diseases, challenges remain. Treatment coverage for HIV is low: Just 33% of people living with HIV were receiving antiretroviral therapy (ART) in 2021. Ensuring people receive ongoing ART treatment is a challenge – a 2020 study showed that 55% of men who started ART were alive and continuing treatment 12 months later. Late diagnosis leading to death despite initiation on ART, as well as the need to travel long distances due to a limited number of ART facilities, are factors that contribute to poor ART retention. Nine additional ART sites are being opened in The Gambia to help address this.\n

\n

\nPrevention of mother-to-child transmission of HIV remains a challenge. The current Global Fund grant supports interventions to reverse rising infection rates among children, such as opening 32 additional maternal and child health sites, introducing HIV testing at more health outreach sites and upgrading maternal and child health facilities so that more sites can provide ART services. These efforts appear to be bearing fruit; after recently declining for several consecutive years, prevention of mother-to-child transmission coverage rates rose in 2021 to 85%.\n

\n

\nRemoving human rights and gender-related barriers to accessing HIV services is a key focus of the epidemic response in The Gambia. Our current grant supports the provision of psychological and legal support for key populations, training for health workers on reducing stigma and discrimination, training for key populations on knowing their rights, and training for police and security personnel on intercultural diversity and inclusion.\n

\n

\nRegarding TB, the population in The Gambia is rapidly urbanizing, with nearly 60% of people living in urban areas. Two regions (Western Health Region 1 and Western Health Region 2) accounted for 78% of TB cases in 2019 – population density and overcrowding are key drivers of this high concentration of cases. Despite an improvement in TB case notification rates in the years prior to the COVID-19 pandemic, treatment coverage remains low, at 64% in 2020. To increase case notification The Gambia is focused on introducing GeneXpert testing for all presumptive TB cases, actively finding people with TB among vulnerable populations, expanding contract tracing, and scaling up preventive treatment initiatives.\n

\n

\nMalaria transmission in The Gambia is perennial, with variations that are highly seasonal (more than 90% of cases occur in the latter part of the rainy season from October to December). Overall rates of prevalence have declined but can vary significantly between regions. To better address the diverse intervention needs of different areas and optimize the country's overall malaria response, the country has shifted from a “one-size-fits-all” approach to a localized, data-driven one.\n

\nA key challenge for malaria control in The Gambia is maintaining coverage of access to, and use of, key malaria interventions – long-lasting insecticidal nets, intermittent preventive treatment of malaria in pregnancy, prompt and accurate diagnosis and treatment, indoor residual spraying, seasonal malaria chemoprevention, and social and behavior change communications.\n

\n

\nSome key cross-cutting issues continue to affect effective service delivery for all three diseases, including suboptimal procurement, supply chain and health management information systems, and a shortage of trained human resources for health. Global Fund grants in The Gambia all have components dedicated to supporting the development of stronger, more resilient systems for health in the country.\n

\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV and TB grants (one of up to US$15.9 million, another of up to US$3 million) are being implemented in The Gambia throughout 2021-2023. Our grants support the country’s goal of delivering an impactful, efficient and sustainable HIV and ТВ response. For HIV, the country aims to boost the percentage of people who know their HIV status, who know their status and are on ART, and who are on ART and are virally suppressed to 90% by 2025. For TB, the goal is to significantly reduce incidence by strengthening early detection, diagnosis and treatment.\n

\n

\nMalaria is a disease of great public health importance in The Gambia, and it features prominently in the country’s major developmental policies and frameworks. The government continues to work closely with partners and is committed to controlling and, ultimately, eliminating malaria through its fourth-generation malaria strategic plan. This plan provides the basis for a Global Fund malaria grant of up to US$19.1 million, which is being implemented in The Gambia throughout 2021-2024.\n

\n

\nLast updated November 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669213694, + "_created": 1669213402, + "_id": "70b80ddf356462255d0003a4" + }, + { + "iso3": "PRY", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Paraguay: an HIV grant aimed at supporting the country in breaking down human rights-related barriers to HIV services and closing gaps in diagnosis, antiretroviral treatment and viral suppression for key populations.\n

\n

\nProgress\n

\n

\nParaguay’s economy has grown over the past two decades, delivering benefits to human development, reducing poverty and contributing to the successful elimination of malaria in 2018. Those efforts have also led to a reduction in the country's tuberculosis (TB) burden, supporting Paraguay’s transition away from Global Fund investment to fight the disease.\n

\n

\nParaguay’s HIV program has also made progress, particularly regarding legislation, the provision of legal advice, sensitization training on HIV-related stigma and discrimination, the provision of antiretroviral therapy and health management information systems. This has led to significant reductions in new HIV infections and AIDS-related deaths. In 2020, the HIV mortality rate was one-third of what it was at its peak in 2005, and incidence had fallen from 31 per 100,000 people in 2002 (when the Global Fund was founded) to 13. Strong gains were also achieved in the percentage of people living with HIV who were receiving antiretroviral therapy (66% in 2021, up from 27% in 2010) and who were virally suppressed (62% in 2021, up from 30% in 2015).\n

\n

\n

\nChallenges\n

\n

\nRates of HIV prevalence among key populations in Paraguay are many multiples higher than in the general population, among which prevalence is very low. While there is no legislation in the country that explicitly criminalizes people because of their gender identity and/or sexual orientation, stigmatizing and discriminatory attitudes toward key populations persist in both the health system and the community.\n

\n

\nA lack of data on the stigma and discrimination that sex workers, transgender women and gay men and other men who have sex with men experience has hampered the implementation of a comprehensive approach to breaking down barriers that prevent these groups from accessing HIV services. However, the current Global Fund grant is strongly focused on increasing equitable access and coverage of interventions for HIV prevention among key populations, and on delivering effective interventions that contribute to defending the rights of key populations, vulnerable groups and people living with HIV and other sexually transmitted infections.\n

\n

\nOur current grant aims to close gaps in diagnosis, increase viral suppression for key populations and improve retention on antiretroviral therapy, work toward zero infections in children, strengthen information, planning, distribution and storage systems as well as improve the management of TB/HIV co-infections.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$6.73 million is being implemented in Paraguay throughout 2021-2024. Our investment supports the country in its aim of reducing HIV incidence and AIDS-related deaths based on a framework of respect for human rights.\n

\n

\nLast updated November 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669213990, + "_created": 1669213886, + "_id": "7101e59337643030bb000300" + }, + { + "iso3": "COG", + "summary": "
\n

\nThe Global Fund currently has two core grants active in the Republic of the Congo with funding totaling up to US$57 million signed for 2021-2023. Our investments aim to support the country in accelerating progress in the fight against HIV, tuberculosis (TB) and malaria and in building resilient and sustainable systems for health that can respond to these diseases as well as future ones.\n

\n

\nProgress\n

\n

\nThe Republic of the Congo, sometimes referred to as Congo-Brazzaville to distinguish it from the neighboring Democratic Republic of the Congo, is a Central African nation of about 5.7 million people. After a period of conflict in the 1990s, the country has stabilized and is making steady progress in its human development index. However, the country, which is heavily dependent on revenues from oil, has faced economic challenges since 2014, following a drop in oil prices.\n

\n

\nNevertheless, the fight against infectious diseases such as HIV, TB and malaria continues to make gains. In the fight against HIV, the Republic of the Congo has a generalized, stable, and relatively low prevalence of HIV of about 3%. As for TB, the incidence of the disease peaked in 2005 and has been declining steadily until recently. Regarding malaria, the Congo has made great progress in protecting families against the disease through the distribution of mosquito nets. In 2015 only about 37% of households used insecticide-treated mosquito nets, but that had increased to 60% in 2019 – with more than 2.6 million mosquito nets distributed that year.\n

\n

\n

\nChallenges\n

\n

\nAcross the three diseases, challenges remain. In the fight against HIV, key populations remain disproportionately affected by the disease with 41% prevalence among gay men and other men who have sex with men, and 8.1% among sex workers. Among people who inject drugs as well as transgender people, prevalence is unknown. Mother-to-child transmission of HIV remains high at 29%. For TB, the Congo is ranked among the top 30 countries with a high burden of TB globally. In 2019, TB treatment coverage was still low at 59%, leaving 1 in every 4 people with TB without treatment. The treatment success rate has also trended downwards in recent years, from 77% in 2016 to 62% in 2018. As for malaria, the entire population of the country is at risk of the disease. For children under 5, malaria accounts for more than half of sickness and close to one-third of deaths.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$27.5 million has been signed for the Republic of the Congo for 2021-2023. For HIV, our investment supports activities to achieve bold reduction targets in new HIV infections and co-morbidities, including viral hepatitis and TB, reach more key populations with HIV testing services and accelerate progress toward the elimination of mother-to-child transmission of HIV.\n

\n

\nFor TB, that grant seeks to find more missing people with TB, improve the supply of quality TB care services, especially among areas with high TB burden, and deploy rapid diagnostic tools such as GeneXpert machines to improve diagnosis and treatment of the disease. Additionally, the grant seeks to integrate HIV and TB services to improve care and treatment for people affected by both diseases.\n

\n

\nA malaria grant of up to US$29.6 million seeks to support the Republic of the Congo to halve malaria-related morbidity and mortality by December 2023, compared to 2019, strengthen epidemiological surveillance and strengthen management capacities of the national malaria control programs.\n

\n

\nLast updated October 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669214224, + "_created": 1669214124, + "_id": "71264a55616464e41c00013e" + }, + { + "iso3": "RWA", + "summary": "
\n

\nThe Global Fund currently has two core grants active in Rwanda with funding totaling up to US$192 million signed for 2021-2024. Our investments aim to support the country in accelerating progress in the fight against HIV, tuberculosis (TB) and malaria and in building resilient and sustainable systems for health that can respond to these diseases as well as future ones.\n

\n

\nProgress\n

\n

\nRwanda is a country in east-central Africa with a population of about 13 million people. In 1994, Rwanda experienced a horrific genocide that claimed an estimated 1 million people in the span of 100 days. The genocide also decimated the country’s economy and health systems. In the intervening years, Rwanda has worked tremendously hard to recover and rebuild, registering great progress in human development. Popularly known as “the land of a thousand hills”, Rwanda has made a steady climb in the fight against HIV, TB and malaria in the last two decades.\n

\n

\nIn the fight against HIV, Rwanda has reduced the number of new HIV infections by about 50% between 2010 and 2019. HIV prevalence has stabilized at around 3%, with about 230,000 people living with the virus in 2019. On the 95-95-95 treatment targets, Rwanda has made significant progress, with 94% of people living with HIV knowing their status, 93% of people living with HIV being on HIV treatment, and 91% of people living with HIV recording suppressed viral loads.\n

\n

\nFor TB, progress has been equally impressive with a treatment coverage rate of 80% and a treatment success rate of 86% in 2019. As for malaria, Rwanda has recorded recent gains against the disease with a decrease in malaria cases from 4.8 million in 2017 to 3.5 million in 2019 and a 60% reduction in severe malaria cases and malaria-related deaths in the same period.\n

\n

\n

\nChallenges\n

\n

\nHIV, TB and malaria remain significant public health threats in the country. Following the decline of malaria in the earlier part of the decade, Rwanda recorded an upsurge in the disease between 2012 and 2016, showing how fragile gains against the disease can be.\n

\n

\nRwanda’s entire population remains at risk of malaria. Gains against HIV remain uneven among certain geographical areas and groups, with the infection rate remaining high in Kigali, the capital, and among key populations such as female sex workers and gay men and other men who have sex with men. TB among key populations such as people living with HIV and people in prisons remains a challenge.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$139.5 million has been signed for Rwanda for 2021-2024. For HIV, our investment supports activities of the HIV national strategic plan, which aims to achieve bold reduction targets in new HIV infections and mortality as well as strive to see that people living with HIV have better standardized and adequate care and support, which can accord them equal opportunities to other people.\n

\n

\nFor TB, the grant aims to invest in targeted approaches that seek to eliminate key drivers of the disease among key populations as well as offer social protections and nutritional support. The TB component of the grant supports Rwanda’s national strategic plan, which seeks to invest in innovations and systems that can accelerate the fight against TB in the country. The TB-HIV grant is implemented under the National Strategy Financing model, which seeks to offer incentives and more flexibility in programming to high-performing grantees.\n

\n

\nA malaria grant of up to US$52.7 million seeks to support Rwanda’s national malaria strategic plan for 2020-2024, which aims at achieving the overall goal of reducing malaria morbidity and mortality by at least 50% of the 2019 levels by 2024.\n

\n

\nLast updated October 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669215562, + "_created": 1669215407, + "_id": "71e9f973343736d5d100033c" + }, + { + "iso3": "MUS", + "summary": "
\n

\nThe Global Fund has one core grant in Mauritius: an HIV grant of up to US$2.2 million for 2021-2023. Our investment compliments government funding for HIV prevention, treatment and care for key and vulnerable populations, and supports the country in its efforts to end HIV as a public health threat by 2030.\n

\n

\nProgress\n

\n

\nMauritius is an island country located off the eastern coast of Africa in the Indian Ocean. The country of 1.2 million people has achieved gradual decreases in HIV incidence in most years since 2001. As of 2021, 56% of the estimated number of people living with HIV knew their status, 47% of people who knew their HIV status were receiving lifesaving antiretroviral therapy, and 69% of people on treatment had a suppressed viral load.\n

\n

\n

\nChallenges\n

\n

\nHIV prevalence among sex workers, people in prisons, people who inject drugs, and gay men and other men who have sex with men is high compared to the general population in Mauritius. Reducing human rights-related barriers to HIV services for these key populations is a core strategy in the country’s fight against HIV, as is improving the quality of and access to harm reduction programs.\n

\n

\nThe Global Fund partnership in Mauritius is also focused on improving linkage to care and adherence to treatment for people living with HIV, updating HIV testing and treatment guidelines and refining monitoring and evaluation to optimize management of the national HIV program.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$2.2 million is being implemented in Mauritius throughout 2021-2023. Our investment supports the country in strengthening existing HIV prevention and treatment interventions and building strong systems for health to continue progress toward achieving the UNAIDS 95-95-95 testing and treatment targets by 2030.\n

\n

\nLast updated October 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669216104, + "_created": 1669215756, + "_id": "721f55cd3437610761000013" + }, + { + "iso3": "ECU", + "summary": "
\n

\nGlobal Fund investment in Ecuador supports intensifying HIV prevention activities and reducing barriers that discourage key populations from accessing health services. These activities are aimed at continuing the country's solid progress toward the 95-95-95 HIV testing and treatment targets.\n

\n

\nProgress\n

\n

\nThere have been marked reductions in HIV mortality and incidence in Ecuador over the past two decades. Consistent declines in deaths and new infections since 2014 align with the country’s introduction in 2013 of universal and free-of-charge access to antiretroviral treatment. In 2021, among people living with HIV, 85% knew their status, 74% were receiving lifesaving antiretroviral therapy and 65% had a suppressed viral load.\n

\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in Ecuador disproportionately affects key populations, with prevalence among sex workers, gay men and other men who have sex with men, and transgender women many multiples higher than in the general population. Although there is no data available on prevalence among transgender men, people who inject drugs and people in prisons and other closed settings, testing and prevention strategies for these groups are also taken into consideration as part of the country’s delivery of differentiated HIV services. Intensifying preventive activities and reducing barriers that hinder access of key populations to health services is at the heart of Ecuador’s HIV response.\n

\n

\nEcuador is one of the most significant transit and destination countries for the migrant and refugee population in Latin America and the Caribbean. Although there is limited data on the scale of the HIV epidemic within this population, the Ministry of Health estimates that rates of viral suppression among migrants and refugees are lower than in the general population. The Global Fund partnership is supporting the country in establishing an HIV prevalence study that will focus on populations that are difficult to reach or “hidden”, i.e., undiagnosed, untreated or unreported to the health systems.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$5.3 million ends in December 2022 (after commencing in January 2020), with a subsequent grant planned to continue the fight against HIV in the country from 2023-2025.\n

\n

\nGlobal Fund investments in Ecuador support the country in its efforts to increase the access of key populations to HIV prevention and early diagnosis (including the scale-up of HIV self-testing and pre-exposure prophylaxis), promote linkages between and among communities and health services, continue to support early initiation of treatment and adherence, and strengthen community-based approaches that pave the way for social contracting.\n

\n

\nLast updated November 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669216080, + "_created": 1669215990, + "_id": "724300b7643365eb7f000142" + }, + { + "iso3": "SLV", + "summary": "
\n

\nThe Global Fund has two core grants currently active in El Salvador, with funding totaling up to US$22.9 million signed for 2022-2024. Our investment aims to support the country in continuing to make progress against HIV and accelerating progress against tuberculosis (TB), with a particular focus on TB control in prisons and other closed settings.\n

\n

\nProgress\n

\n

\nEl Salvador is the smallest (by area) and most densely populated of the seven Central American countries. The country of 6.5 million people has increased investment in social security, education and health, and has achieved significant declines in poverty and inequality. Progress has also been made against HIV and TB.\n

\n

\nHIV incidence has steadily declined over the last two decades and gradual, consistent increases have been achieved in all the UNAIDS 95-95-95 HIV testing and treatment criteria. As of 2021, 79% of people living with HIV knew their status, 75% of people who knew their HIV status were receiving lifesaving antiretroviral therapy, and 92% of people on treatment had a suppressed viral load.\n

\n

\nPeople in prisons account for almost half of El Salvador’s TB burden. Between 2018 and 2019, the country reduced the TB incidence rate among this population by 64%. TB deaths almost halved between 2002 (when the Global Fund was founded) and 2020. The TB treatment success rate has remained consistently high at around 90%.\n

\n

\n

\nChallenges\n

\n

\nStigma, discrimination and violations of human rights still pose barriers to accessing services for key and vulnerable populations in El Salvador. This contributes to rates of HIV prevalence among sex workers, transgender people and gay men and other men who have sex with men that are significantly higher than in the general population. Removing human rights-related barriers and following up on the recommendations of a recent stigma and discrimination assessment is a focus of our current grant.\n

\n

\nImproving testing and treatment for key populations is an ongoing challenge. This is being addressed by combination prevention approaches that include biomedical, behavioral, social and structural interventions designed to reduce HIV incidence. Strategies to improve health information systems and monitoring and evaluation are also being supported by Global Fund investment. These are designed to optimize HIV testing for key and vulnerable populations and strengthen treatment and care services for people living with HIV.\n

\n

\nEl Salvador has one of the highest per-capita imprisonment rates in the world. People in prisons accounted for 44% of TB notifications in 2019 and TB incidence among this group was 3,355 per 100,000 people, compared to 47 per 100,000 in the general population. New legislation aims to strengthen the institutional response to TB in prisons. By doing so, El Salvador aims to significantly reduce its TB cases and focus fewer resources on a subset of the population that bears a highly disproportionate burden of TB.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$8.6 million is being implemented in El Salvador throughout 2022-2024. Our investment supports the country in strengthening the prevention and treatment cascade through innovative approaches to case detection, early enrolment in antiretroviral therapy and better adherence to treatment. Key populations and pregnant women are at the center of the grant.\n

\n

\nWe continue to support the TB response in El Salvador through a multicomponent HIV and TB grant of up to US$14.3 million for 2022-2024. In addition to supporting the strengthening of the national HIV response, our investment supports the country in its goal of achieving 90% TB treatment coverage, 92% treatment success and diagnosing 82% of patients through rapid tests.\n

\n

\nBoth these grants also support COVID-19-related interventions aimed at strengthening the national response to the pandemic. Interventions include mitigation measures for HIV programs, support for COVID-19 control and containment, and strengthening formal and community systems for health.\n

\n

\nLast updated October 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669216606, + "_created": 1669216457, + "_id": "728a453c3538360ad8000019" + }, + { + "iso3": "MNG", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Mongolia: an HIV and TB grant of up to US$13.3 million signed for 2021-2023. Our investment aims to support the country in increasing case detection for HIV and tuberculosis (TB) and strengthening prevention, treatment and care services to reduce the burden of both diseases.\n

\n

\nProgress\n

\n

\nSince the end of the 1980s, when Mongolia ended a political monopoly in favor of free multiparty elections, the country has tripled the level of GDP per capita and achieved dramatic declines in maternal and child mortality. Investment in health has also strengthened the country's strategies in the fight against HIV and TB.\n

\n

\nAs a result, HIV incidence and mortality rates have remained low, and gains have been made in key testing and treatment target criteria. In 2021, 44% of people living with HIV knew their status, 38% were receiving lifesaving antiretroviral therapy (up from 7% in 2010), and 36% had a suppressed viral load (up from 24% in 2015). TB mortality rates have steadily declined over the past two decades and the treatment success rate for TB is now approaching 90%.\n

\n

\n

\nChallenges\n

\n

\nA common difficulty in the fight against both HIV and TB in Mongolia is the country’s vast land area, which presents challenges in delivering health services to those living in remote rural areas, including vulnerable and nomadic populations, migrants and unregistered people. A range of sources (governments, development partners and private companies) continue to support the delivery and expansion of mobile health units, with the aim of making health services geographically, financially and administratively accessible to all.\n

\n

\nAlthough Mongolia has a low prevalence of HIV, key populations – predominantly female sex workers and gay men who have sex with men – are disproportionately affected by the virus. The country’s HIV program is strongly focused on these groups and is working to expand new, effective approaches to increase their uptake of HIV testing and prevention services. The Global Fund partnership is also investing in initiatives to strengthen the HIV care and treatment continuum for key populations and people living with HIV, as well as improving data systems to accelerate progress toward a sustainable national HIV response.\n

\n

\nMongolia is also working to increase connection and collaboration in the fight against the virus. Improving and formalizing links between community and government services, and an array of advocacy activities, including awareness-raising training to reduce stigma and discrimination, are priorities within the current Global Fund grant.\n

\n

\nMongolia has one of the highest TB burdens in the Western Pacific region, with the highest prevalence areas being Ulaanbaatar, the nation’s capital, and provinces along the Trans-Siberian Railway that runs through the country. Groups at the highest risk of TB infection are contacts of people who have TB, poor people in urban areas (migrants from rural to urban areas are particularly vulnerable), people in prisons, people experiencing homelessness, miners and other workers exposed to silica, and people living with HIV who are co-infected with TB.\n

\n

\nMongolia’s TB response is working to address various challenges in providing comprehensive, people-centered services for these groups, adopting innovative methods for TB detection, screening, diagnosis and treatment, and expanding multisectoral collaboration.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA multicomponent HIV and TB grant of up to US$13.3 million is being implemented in Mongolia throughout 2021-2023. Our investment is geared toward interventions that strengthen the national systems for prevention, treatment, care and support for both diseases. The Global Fund grant supports Mongolia in its goal of reducing HIV transmission and AIDS-related deaths and increasing TB treatment coverage to 55% and the treatment success rate to above 90% in the coming years.\n

\n

\nLast updated October 2022.\t\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669216859, + "_created": 1669216700, + "_id": "72af4ea2376465efea000252" + }, + { + "iso3": "IRN", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Iran: an HIV grant that aims to help strengthen testing, prevention, treatment and care services for key populations and support continued reductions in HIV incidence and mortality.\n

\n

\nProgress\n

\n

\nLocated at the crossroads of Central Asia, South Asia, the Middle East and the Caucasus, Iran is home to 85 million people. HIV incidence in the country has declined markedly from its peak in 2005. In that year, there were an estimated 16 new infections per 100,000 people. By 2020, this had fallen to just 3 per 100,000 people. HIV mortality rates have also decreased each year since 2013 (UNAIDS 2021 Data Release).\n

\n

\nAmong people in prisons and people who inject drugs – two groups that have previously experienced a significantly higher HIV burden than the general population – prevalence rates are declining. Between 2010 and 2019 prevalence among people who inject drugs decreased from 15% to 3%. Among people in prisons, prevalence fell from 2.1% in 2009 to 0.8% a decade later.\n

\n

\n

\nChallenges\n

\n

\nAlthough gains have been achieved in reducing incidence and mortality, there is more progress to be made to reach the UNAIDS 95-95-95 testing and treatment targets. In 2021, an estimated 43% of people living with HIV knew their status, 69% of people who knew their HIV status were receiving antiretroviral therapy, and 91% of people on treatment had a suppressed viral load. Case finding is a key challenge in the country’s HIV cascade, as demonstrated by the lower percentage of people who know their HIV status. To address this, the current HIV program is employing new approaches to reach those most affected by HIV in the country and make testing and counseling services available to them.\n

\n

\nPeople who inject drugs continue to account for the greatest share of new HIV infections in Iran, despite the recent reductions in prevalence. Other key populations are also estimated to be disproportionately affected by HIV, although available data is limited. Making testing more accessible for these key populations and providing harm reduction programs and differentiated antiretroviral treatment for them is an important pillar of Iran’s HIV response.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$11.2 million is being implemented in Iran throughout 2021-2024. Our investment is geared toward scaling up differentiated HIV testing services, expanding HIV prevention interventions and improving the coverage and quality of treatment care and support.\n

\n

\nThese activities support the country’s efforts to, in the coming years, maintain HIV prevalence at less than 5% among people who inject drugs and for people who are exposed to sexual transmission, and decrease HIV mortality by 20%.\n

\n

\nIran has also been allocated funding of up to US$5.7 million as part of our COVID-19 Response Mechanism, which supports countries to mitigate the impact of COVID-19 on programs to fight HIV, TB and malaria, and initiates urgent improvements in health and community systems.\n

\n

\nGlobal Fund investment in Iran is managed under our Additional Safeguards Policy. This ensures that goods and services reach the populations they are intended for, and that any applicable sanctions are adhered to.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669217953, + "_created": 1669217823, + "_id": "735aac6c3564354bed00022b" + }, + { + "iso3": "MNE", + "summary": "
\n

\nThe Global Fund has one core grant in Montenegro: an HIV grant of up to €840,000 for 2022-2024. Our investment aims to support the country in providing and scaling up HIV preventive and support services, with the goal of maintaining a low HIV prevalence among key and vulnerable populations.\n

\n

\nProgress\n

\n

\nLocated on the Balkan Peninsula in eastern Europe, Montenegro is home to almost 650,000 people and gained full independence from Serbia in June 2006. HIV prevalence and AIDS-related deaths have been restricted to very low levels: No more than five AIDS-related deaths have been reported in any year for the past two decades.\n

\n

\n

\nChallenges\n

\n

\nThere are sociocultural barriers in Montenegro that stigmatize and discriminate against people living with HIV and the key populations who are disproportionately affected by the virus (sex workers, people who inject drugs, people in prisons and gay men and other men who have sex with men).\n

\n

\nExpanding and implementing new ways of reaching key populations to maintain low prevalence rates and address the growing trend of HIV incidence among gay men and other men who have sex with men is a priority for Montenegro. The country’s HIV program has a particular focus on strengthening HIV testing through the implementation of flexible HIV testing services, community-based testing, self-testing, new testing technologies and the development of revised HIV testing guidelines.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to €840,000 is being implemented in Montenegro throughout 2022-2024. Our investment is geared toward interventions that maintain or scale up preventive and support services for key and vulnerable populations, with the goal of maintaining a low HIV prevalence among these groups.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669218240, + "_created": 1669218137, + "_id": "738a9cff3935309898000202" + }, + { + "iso3": "MAR", + "summary": "
\n

\nThe Global Fund has one core grant in Morocco: a joint HIV and tuberculosis (TB) grant of up to €15.9 million for 2021-2023. Our investment aims to support the country in maintaining and scaling up HIV prevention and support services, particularly for key and vulnerable populations, and in continuing to strengthen its TB response with the aim of significantly reducing TB mortality in the coming years.\n

\n

\nProgress\n

\n

\nConcerted efforts to fight HIV in Morocco have resulted in steady reductions in incidence and mortality over the past decade, making it an outlier in the Middle East and North African region, where new infections are on the rise.\n

\n

\nConsistent increases have been made in key testing and treatment criteria. Among people living with HIV in 2021, 83% knew their status, 80% were receiving lifesaving antiretroviral therapy (up from 17% in 2010), and 76% had a suppressed viral load (up from 38% in 2015).\n

\n

\nWhile TB remains a significant public health threat, slowly increasing incidence and mortality rates were reversed in 2016 and continued to fall in the years that followed (data for 2020 and beyond were not available at the time of writing, and it is likely that COVID-19 will have impacted this progress). The TB treatment success rate had remained consistently high (sitting at 89% in 2019), as had the treatment rate for people co-infected with HIV and TB, with 98% of people living with HIV who also have TB receiving antiretroviral therapy in 2020.\n

\n

\n

\nChallenges\n

\n

\nAlthough the number of people living with HIV in Morocco is relatively low, the virus disproportionately affects key populations, particularly sex workers, people who inject drugs and gay men and other men who have sex with men. Stigma and discrimination persist and can present a barrier to HIV services. Breaking down these human rights-related barriers, as well as scaling up combined prevention services and developing differentiated approaches to prevention and screening, are key strategies in supporting key and vulnerable populations in accessing the health services they need in Morocco.\n

\n

\nAnother ongoing challenge is the strengthening of structural elements in the country's HIV response. Efforts to address this continue through initiatives to improve the health information management system, implement a strategy of delegation and collaboration at different touchpoints within the health care system to aid retention and reactivate people lost to follow-up, and improve the management and quality control of drugs and other health commodities.\n

\n

\nOur current TB investment is focused on optimizing TB detection and treatment, improving the response capacity of health units and the laboratory network (including acquiring GeneXpert machines), strengthening drug-resistant TB detection and management, improving quality of care, and integrating prevention as well as control interventions for TB and HIV to improve co-infection management. Morocco’s TB program also recognizes the need for TB-related advocacy activities, including establishing institutional arrangements to combat discrimination and, in collaboration with civil society organizations, implementing awareness and information campaigns to combat stigma.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to €15.9 million is being implemented in Morocco throughout 2021-2023. Our investment is geared toward interventions that aim to reduce new HIV infections and AIDS-related deaths, fight discrimination and improve psychosocial support, and strengthen national, regional and local governance for an accelerated and sustainable response. For TB, our investment aims to support Morocco in its goal of reducing the number of TB-related deaths by 60% by 2023, compared to 2015 levels.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669218478, + "_created": 1669218361, + "_id": "73acb7856430337d860001a3" + }, + { + "iso3": "BLR", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Belarus: a joint HIV and tuberculosis (TB) grant of up to €32.2 million for 2022-2024. Our investment aims to support the country in reducing new HIV infections and AIDS-related deaths among key and vulnerable populations, and in continuing to focus its TB response on fighting drug-resistant TB, which poses a significant public health threat in Belarus.\n

\n

\nProgress\n

\n

\nAfter experiencing steadily increasing rates of HIV incidence and mortality during the first decade of the new century, Belarus was able to bend the curve and cut new infections almost in half between 2012 and 2020. Solid gains have also been made in key testing and treatment criteria. Among people living with HIV in 2021, 84% knew their status, 70% were receiving lifesaving antiretroviral therapy (up from 19% in 2010), and 65% had a suppressed viral load (up from 24% in 2015).\n

\n

\nProgress has also been made in the country's fight against TB. TB-related deaths declined by 75% between 2002, when the Global Fund was founded, and 2020. New TB cases fell by almost two-thirds over the same period.\n

\n

\n

\nChallenges\n

\n

\nIn Belarus, injection drug use plays a central role in the spread of HIV. People who inject drugs are among the most vulnerable members of the population and face deep-rooted stigma and prejudice. Shifting attitudes to harm reduction interventions have enabled the continued rollout of programs that are playing a key role in the fight against HIV in Belarus. Sex workers and gay men and other men who have sex with men also experience rates of HIV prevalence that are significantly higher than the general population. Supporting safe behaviors, strengthening testing and linkage to care and removing gender- and human rights-related barriers to service delivery for these groups is a core focus of current Global Fund investments.\n

\n

\nDespite the gains made against TB in Belarus, the disease remains a significant public health threat. Rifampicin-/multidrug-resistant TB accounts for more than half of all reported TB cases, and the World Health Organization lists Belarus as one of 30 countries with the highest burden of drug-resistant TB in the world. Belarus is addressing this central challenge in its TB response through a spectrum of targeted interventions to optimize case detection and diagnosis, strengthen prevention and treatment (particularly for people in prisons, who are especially vulnerable to the disease) and enhance program adaptability, reporting, financing and monitoring.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to €32.2 million is being implemented in Belarus throughout 2022-2024. Our investment is geared toward scaling up HIV prevention and treatment services for key populations and people living with HIV, strengthening community systems, addressing human-rights-related barriers to access, and building capacity to fully uptake the financial and programmatic responsibility of the HIV response in Belarus. For TB, our investment aims to support Belarus in its goal of reducing TB-related morbidity and mortality and improving treatment outcomes, especially in patients with drug-resistant TB.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669218741, + "_created": 1669218575, + "_id": "73cd69ba313338af2d0003b3" + }, + { + "iso3": "NAM", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Namibia: a joint HIV, tuberculosis (TB) and malaria grant of up to US$39.5 million for 2021-2023. Our investment supports the country’s ongoing epidemic response, which has made progress against all three diseases.\n

\n

\nProgress\n

\n

\nLocated on the on the southwest coast of Africa, Namibia is a geographically large country with a population of around 2.6 million people, making it one of the least densely populated countries in the world.\n

\n

\nNamibia has implemented an aggressive and continued campaign against HIV that has delivered strong progress. By leveraging a combination of interventions targeting behavioral, biomedical and structural drivers of the epidemic, the HIV program reduced AIDS-related deaths by 72% between 2002 and 2021, and new HIV infections by 61% over the same period.\n

\n

\nNamibia was one of only a handful of high TB burden countries estimated to have achieved the World Health Organization (WHO) End TB Strategy 2020 milestone of a 20% reduction in TB incidence between 2015 and 2020. The fight again the disease continues, and gradual yet critical gains are being made in key prevention and treatment criteria. Significant progress has also been achieved against malaria, with deaths and new cases falling by over 70% since 2002.\n

\n

\n

\nChallenges\n

\n

\nThe World Bank classifies Namibia as an upper middle-income country, a status that masks the very real poverty of many and the overall high levels of inequality in society. A lack of sufficient socioeconomic resources for large segments of the population presents an ongoing challenge in the fight against HIV, TB and malaria. The country’s large size and low population density present an additional challenge, making health care costly in terms of providing equitable services, maintaining effective supply chains and ensuring efficient coordination of services and systems monitoring.\n

\n

\nNamibia’s HIV epidemic is mature and generalized, with wide variation in prevalence between different age groups, towns and regions. HIV in Namibia is primarily transmitted through heterosexual sex and mother-to-child transmission. Key and vulnerable populations – female sex workers, gay men and other men who have sex with men, adolescent girls and young women, migrant communities and hard-to-reach populations – are also disproportionately affected. As part of the current Global Fund grant, interventions are prioritized according to the most at-risk areas and individuals.\n

\n

\nCommunity mobilization is a major strategy to engage communities in creating demand for HIV services, promoting adherence, and strengthening behavior change. Social and behavior change complements and promotes the effectiveness of biomedical gains such as male circumcision, treatment access and adherence, and pre-exposure prophylaxis and microbicides, and is critical to the overall success of the national HIV response. These people- and community-centered strategies are essential to creating an enabling environment with greater gender equality and reduced gender violence; to reduce stigma and discrimination; and to ensure that people living with HIV and key and vulnerable groups enjoy full human rights and equitable access to services.\n

\n

\nDespite Namibia’s progress in the fight against TB, the disease remains a significant public health threat. WHO lists Namibia as one of the 30 highest burden countries in the world for TB, and for HIV/TB co-infection. The epidemic is largely attributable to poverty, as well as high HIV prevalence. Strengthening the TB prevention and treatment cascade throughout the country, particularly in remote and indigenous communities where the TB burden is often many multiples higher than among the general population, is a critical element of the country’s TB response.\n

\n

\nNamibia aimed to eliminate malaria by 2020, but a period of unseasonably heavy rains in preceding years contributed to outbreaks in the north of the country. This prompted a refocusing of the disease response that has restarted the downward trend in morbidity and mortality. With a focus on northern Namibia, where malaria risk is greatest, the malaria control program aims to build capacity at a district level, gather and bring to action detailed risk data and boost the effectiveness and efficiency of human resources for health at both facility and community levels.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA consolidated HIV, TB and malaria grant of up to US$39.5 million is being implemented in Namibia throughout 2021-2023. Our investment is geared toward significantly contributing to the national targets for epidemic disease control and driving continued reductions in incidence and mortality for all three diseases. The grant supports sustainable interventions that focus on the most at-risk people, effectively engage civil society and support the country in its efforts to reduce its dependence on external donors.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669219375, + "_created": 1669218832, + "_id": "73f4ad2637366286b2000143" + }, + { + "iso3": "ROU", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Romania: a tuberculosis (TB) grant that supports the country in addressing health systems-related challenges in TB care, with the aim of achieving ongoing reductions in TB cases and deaths.\n

\n

\nProgress\n

\n

\nA country of 19 million people in southeastern Europe, Romania has achieved economic growth and improved prosperity over the past two decades. Accompanying this socioeconomic advancement has been progress in reducing the country’s high TB burden. Between 2002, when the Global Fund was founded, and 2019, TB incidence fell from 156 cases per 100,000 people to 65. TB mortality also significantly declined over the same period, dropping from 10 deaths per 100,000 people to 4.\n

\n

\n

\nChallenges\n

\n

\nDespite the gains against TB in recent years, the TB burden in Romania remains among the highest in Europe. In order to address the TB epidemic in the country, a spectrum of interventions continues to be deployed to strengthen health systems and increase the capacity of TB programming.\n

\n

\nA key focus has been supporting the Ministry of Health’s capacity for policy formulation, program planning and procurement and supply chain enhancement. Improving monitoring and evaluation to better inform evidence-based decisions has been a priority, as has developing legislation to ensure universal health coverage for key populations. Drug-resistant TB remains an ongoing threat and is being addressed by strategies to strengthen treatment regimens and avoid shortages and stock-outs of anti-TB drugs.\n

\n

\nThe needs of key and vulnerable populations are at the center of the grant, which supports the scale-up of advocacy efforts aimed at monitoring the central and local government response to TB and TB/HIV co-infection in the context of the rights of key and vulnerable populations. Improving treatment outcomes in the most vulnerable, underserved, at-risk groups is being supported by efforts to develop a community-based, integrated model of services.\n

\n

\nRomania is hosting more than 80,000 Ukrainian refugees. This has placed an additional burden on Romania’s already strained TB program. In response, the Global Fund approved emergency funding that supports the country’s urgent need to maintain uninterrupted and equitable TB treatment and diagnostics for the Romanian population, as well as for Ukrainian refugees. The emergency funding also supports active case detection to prevent TB outbreaks in the ten areas of the country most affected by the refugee crisis.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA TB grant of up to €10.3 million is being implemented in Romania throughout 2018-2023. Our investment is supporting the country in addressing health system-related challenges in TB care, implementing interventions that focus on the most at-risk people, effectively engaging civil society and helping drive further reductions in TB incidence and mortality, and extending the fight against TB to cover the Ukrainian refugees within its borders.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669219960, + "_created": 1669219853, + "_id": "74907dc5343238774d000253" + }, + { + "iso3": "DJI", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Djibouti: a joint HIV, tuberculosis (TB) and malaria grant that supports the country in consolidating its significant gains against HIV, continuing steady progress against TB and fighting a rapidly increasing burden of malaria.\n

\n

\nProgress\n

\n

\nLocated at the southern entrance to the Red Sea and marking a bridge between Africa and the Middle East, Djibouti is one of the smallest countries in Africa.\n

\n

\nBetween 2002, when the Global Fund was founded, and 2020, HIV incidence in the country of 1 million people fell from 168 cases per 100,000 people to 65. HIV mortality also significantly declined over the same period, dropping from 104 deaths per 100,000 people to 35. Efforts to end TB as a public health threat are bearing fruit, with the incidence rate halving between 2002 and 2019, and TB mortality falling by 37% over the same period.\n

\n

\n

\nChallenges\n

\n

\nKey populations – sex workers and gay men and other men who have sex with men – are disproportionately affected by HIV in Djibouti. Other vulnerable groups include migrants and refugees, adolescents and young adults, pregnant women and TB patients. Tackling human rights- and gender-related barriers to HIV testing and treatment presents an ongoing challenge. Another key focus in the country's HIV response is implementing differentiated testing services and scaling up diagnosis, treatment, care monitoring, follow-up and support.\n

\n

\nAlthough TB incidence and mortality rates have steadily declined, significant challenges in the fight against the disease remain. More than 15% of the population live in extreme poverty, around 10% are undernourished and financial barriers prevent a large segment of the population from accessing health care. Accelerated urbanization is bringing larger numbers of people closer together, increasing the risk of TB infection. Drug-resistant TB also poses an ongoing threat. In addition, there is low coverage of antiretroviral therapy for people living with HIV, who are more likely than others to become sick with TB.\n

\n

\nDjibouti had reached the pre-elimination phase of its malaria response in 2012; however, these gains were reversed and overrun the following year. Since 2013, cases have significantly increased – and have sometimes doubled – year over year. A combination of several factors has been identified as contributing to the resurgence of the disease. Reductions in funding (2009-2015) and limited preventive interventions (2017-2019), the emergence of an alteration in the genes of some malaria parasites that causes infections to go undetected, and population movements across borders all played a role. As did the incursion of Anopheles stephensi – an invasive species of mosquito well adapted to urban environments. Given that 98% of cases are reported in the Djibouti City region, the appearance of this malaria vector presents a significant challenge to malaria control in urban areas of the country.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA consolidated HIV, TB and malaria grant of up to US$10.8 million is being implemented in Djibouti throughout 2021-2023. For HIV, our investment supports the country in driving continued gains across the testing and treatment cascade, particularly in regard to increasing the percentage of people living with HIV who know their status and receive antiretroviral therapy.\n

\n

\nThe TB component of the grant supports ongoing activities to prevent, detect, treat and follow up both TB and drug-resistant TB. Strategies to strengthen community systems and improve health information management, monitoring and evaluation underpin Djibouti’s goal of identifying at least 9,000 cases of TB and 300 of drug-resistant TB in the coming years.\n

\n

\nThe grant supports Djibouti's malaria response in its aim of increasing access to malaria screening and treatment across the country, and in implementing indoor residual spraying in the most affected areas in the Djibouti City region. The program is also geared toward strengthening epidemiological surveillance, supporting active case detection and monitoring insecticide resistance.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669220626, + "_created": 1669220456, + "_id": "74ec7b55633234eb48000144" + }, + { + "iso3": "GAB", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Gabon: A tuberculosis (TB) grant geared toward supporting the country in continuing to reduce its high rate of TB incidence and turning the tide of rising TB mortality.\n

\n

\nProgress\n

\n

\nGabon is a small country of about 2 million people located on the west coast of Africa. The country has one of the highest urbanization rates in Africa, with more than four in five of its citizens living in cities.\n

\n

\nGabon has one of the highest TB burdens in Africa. Despite the significant challenges the country faces in its fight to end the disease as a public health threat, it has made progress in some critical respects. Gradual yet consistent decreases in TB incidence were achieved every year between 2006 to 2019 (results beyond 2019 are not currently available). The TB treatment success rate has risen to 67%, and increasing numbers of people living with HIV who also have TB are receiving lifesaving antiretroviral therapy.\n

\n

\n

\nChallenges\n

\n

\nPeople living with HIV are more likely than others to become sick with TB. Given that 46,000 people in Gabon are estimated to be living with HIV, and with coverage of antiretroviral therapy relatively low, a large swathe of the population is at an increased risk of TB infection. TB rates in the town of Lambarene highlight this vulnerability, which is reflected across the country: In Lambarene in 2020, 42% of adults and 16% of children with TB were also living with HIV.\n

\n

\nCurrent Global Fund investment supports efforts to strengthen the coordination mechanisms for the national HIV and TB programs, provide additional training for staff at HIV and TB treatment centers, enhance the procurement of health products for TB preventive therapy and re-operationalize three TB treatment centers.\n

\n

\nDrug-resistant TB is a significant threat in Gabon. Finding people with drug-resistant strains of the disease is a significant challenge; a report from 2020 notes that only around one-fifth of the estimated people with drug-resistant TB were detected. Strategies to improve the diagnosis, treatment and management of drug-resistant TB is a key focus of the TB response in Gabon, as is strengthening community systems for health that target both drug-susceptible and drug-resistant forms of the disease.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA TB grant of up to €2.4 million is being implemented in Gabon throughout 2022-2024. Our investment supports the country in its goal of reducing TB mortality from 84 deaths per 100,000 people (2019) to 63 per 100,000 by 2024, as well as driving down TB incidence over the same period from 521 cases per 100,000 people to 498.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669220844, + "_created": 1669220704, + "_id": "75124fde336538a4c60001a5" + }, + { + "iso3": "STP", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Sao Tome and Principe: A joint HIV, tuberculosis (TB) and malaria grant that is geared toward an increased focus on the HIV burden and needs of key and vulnerable populations; improving the diagnosis, care and treatment cascade for TB; and achieving malaria elimination in the coming years.\n

\n

\nProgress\n

\n

\nSao Tome and Principe is an archipelago in the Gulf of Guinea, 350 kilometers off the west coast of Africa. Most of the country's 220,000 people live on the islands of Sao Tome and the Autonomous Region of Principe, which are about 150 kilometers apart.\n

\n

\nSince 2005, there has been a marked increase in the number of people living with HIV who know their status and who are on antiretroviral treatment. There has also been improvement in the number of pregnant women tested for HIV and on antiretroviral treatment, which is helping to address mother-to-child transmission of the virus. In 2020, 100% of people infected with both HIV and TB were receiving antiretroviral treatment, and TB preventive therapy among people living with HIV increased from 8.7% in 2018 to 30% in 2019.\n

\n

\nSao Tome and Principe is one of the countries in Africa that is potentially set for malaria elimination, and the government is committed to ending the disease as a public health threat in the coming years. In February 2020 the country hosted a high-level event in collaboration with the World Health Organization to share the main findings from a malaria program review and define key steps to achieve malaria elimination well before the end of the decade. In July 2022, Sao Tome and Principe and Cabo Verde began a south-south cooperation initiative that supports the former in learning from the latter’s experience in malaria reduction and is helping to bolster Sao Tome and Principe’s progress toward eliminating the disease.\n

\n

\n

\nChallenges\n

\n

\nHIV prevalence in Sao Tome and Principe is higher among key populations, mainly sex workers and gay men and other men who have sex with men. Data for transgender people and people who inject drugs does not currently exist. Given that ensuring universal access to prevention services for all key populations is a primary goal of the current Global Fund grant, gaining a better epidemiological understanding of HIV among transgender people and people who inject drugs is a priority. Reducing mother-to-child transmission of HIV is also a key focus. Strategies to achieve its elimination are underpinned by interventions to improve the quality of HIV services and increase rates of viral suppression.\n

\n

\nIn 2020 the TB notification rate in Sao Tome and Principe was 36%, suggesting a large proportion of people with TB are still “missing” – undiagnosed, untreated or unreported to the health systems. The country has adopted a rapid TB diagnostic framework with the aim of finding more people with TB and ensuring they access treatment earlier. Reaching all key and vulnerable populations by removing human rights-related barriers to TB services is a key focus of the epidemic response, as is strengthening TB preventive measures to reduce the overall number of new TB infections.\n

\n

\nDespite the progress Sao Tome and Principe has achieved in reducing its malaria burden, the fight to end the disease is not over. The country continues to optimize its malaria response with the aim of detecting 100% of malaria cases, achieving a quality biological diagnosis and treating all infections correctly in accordance with the national case management policy. Capacity-building is underway to improve program management at a national level, and the ongoing rollout of epidemiological monitoring, investigation georeferencing, early case-detection, immediate notification and community interventions is helping to strengthen malaria surveillance systems.\n

\n

\nTo support resilient and sustainable systems for health, the Global Fund partnership has supported the roll-out of an integrated District Health Information Software (DHIS2). The system is designed to monitor patient health, improve disease surveillance, map disease outbreaks and speed up health data access for health facilities and the government. The Global Fund also supports the strengthening of community health worker networks and community-based organizations. These play an important role in providing integrated services to the population, including tracing TB and HIV patients who are lost to follow-up and ensuring a robust COVID-19 response.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA multicomponent HIV, TB and malaria grant of up to €11.6 million is being implemented in Sao Tome and Principe throughout 2021-2023. Our investment supports the country in its goal of stabilizing HIV morbidity and continuing its significant reductions in HIV mortality, increasing the success rate for all forms of TB to at least 85% and, by 2025, reducing the incidence of malaria by at least 1 case per 1,000 people within all districts of Sao Tome and recording zero endemic cases within Principe.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669221072, + "_created": 1669220940, + "_id": "75364f50386462b9c5000323" + }, + { + "iso3": "QNA", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Kosovo: A joint HIV and tuberculosis (TB) grant geared toward maintaining low levels of HIV prevalence and optimizing the country's TB response to achieve continued reductions in incidence.\n

\n

\nProgress\n

\n

\nKosovo’s HIV program has played a key role in preventing the virus from spreading among the general population and maintaining low prevalence rates among key populations. By 2019, a total of 129 HIV cases and 51 AIDS-related deaths had been recorded since the first recorded case of the virus in the country in 1986.\n

\n

\nTB re-emerged as a public health threat in Kosovo in 1999, coinciding with the end of an armed conflict that occurred between February 1998 and June 1999. In the years following, the TB notification rate rose rapidly, but substantial reductions have since been made. In 2019 the TB notification rate was 34.4 cases per 100,000 people – less than half of what it was at its peak in 2001 (78 per 100,000). TB treatment coverage is 100%, and treatment success rates for people with pulmonary bacteriological TB have been consistently high at around 90% for the past decade.\n

\n

\n

\nChallenges\n

\n

\nHIV prevalence rates among gay men and other men who have sex with men increased with each quadrennial HIV data survey between 2011 and 2018. Although small, the increases highlight the need for ongoing interventions aimed at reducing the transmission and impact of HIV among key populations. This is being addressed through strategies to reduce human rights-related barriers to accessing HIV services, and deliver quality prevention, treatment, care and support services tailored to those who need them.\n

\n

\nThe Global Fund partnership works to provide people living with HIV with universal access to treatment, health care and support services. It also focuses on ensuring the sustainability of an effective HIV response in Kosovo by supporting health systems strengthening and empowering and engaging civil society organizations in HIV policy, programming, advocacy and service provision.\n

\n

\nAlthough drug-resistant TB is a relatively minor problem in Kosovo, its existence still presents a threat. This is being addressed through efforts to enhance patient management and implement community care delivery for drug-resistant TB. Optimizing TB care and prevention is an ongoing challenge. The current Global Fund grant supports interventions to increase the capacity of TB laboratories, effectively implement the national infection control plan, pilot programs to actively find people with TB in high-burden areas, improve prevention, diagnosis and treatment for children, minorities and vulnerable groups, and strengthen advocacy to boost awareness of TB among communities, health care networks and government.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA joint HIV and TB grant of up to €2.8 million is being implemented in Kosovo throughout 2022-2024. Our investment supports the country in its goal of maintaining a low prevalence of HIV among key populations as well as the general population, and improving the quality of life for all people living with HIV. For TB, the grant is geared towards supporting Kosovo in reducing the number of people infected with TB each year from 33.8 people per 100,000 in 2019 to 23.6 per 100,000 in the coming years.\n

\n

\nThe integrated HIV and TB program is also focused on preparing Kosovo for a gradual transition from a national response that relies on major support from the Global Fund to one that is sustainably reliant on national resources.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669221399, + "_created": 1669221267, + "_id": "75683c463635336193000151" + }, + { + "iso3": "SRB", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Serbia: An HIV grant geared toward scaling up testing and prevention services for key populations, with a particular focus on men who have sex with men, who experience rates of HIV prevalence significantly higher than the rest of the population.\n

\n

\nProgress\n

\n

\nAfter Global Fund grants for Serbia ended in 2014, the country became re-eligible for Global Fund financing in the 2017-2019 funding cycle following a resurgence of HIV among gay men and other men who have sex with men. In the years since, HIV incidence rates leveled off, coinciding with the renewed Global Fund investment and the government’s re-establishment of the Commission for the Fight Against HIV/AIDS and Tuberculosis, the creation of a new national AIDS strategy and the development of social contracting mechanisms for non-governmental organizations. AIDS-related deaths have fallen significantly over the past two decades, down from 110 in 2000 to 20 in 2021.\n

\n

\n

\nChallenges\n

\n

\nGay men and other men who have sex with men are more affected by HIV than any other population in Serbia. Prevalence among this key population was 5.8% in 2021. Among people who inject drugs prevalence was 2.3% and for sex workers, 1.5%. Drug possession is penalized and criminalized, while sex workers and their clients face new legislative restrictions. Although the legal environment has improved for transgender people and gay men and other men who have sex with men, stigma remains high for all key populations.\n

\n

\nGiven the challenges faced and the disproportionate HIV burden experienced by key populations, outreach, testing and linkage to care for these groups is the central challenge in Serbia’s HIV response.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to €1.5 million is being implemented in Serbia throughout 2022-2025. Our investment supports the country in its goal of scaling up HIV testing services for all key populations. In addition, the grant aims to formalize and strengthen the Ministry of Health’s mechanism for empowering non-governmental organizations to deliver HIV prevention services. Structured with yearly increasing government contributions and coverage targets, our grant is a direct contribution to the sustainability of the HIV response in the country.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669221653, + "_created": 1669221561, + "_id": "75950fdb616139a8ec0000c6" + }, + { + "iso3": "TJK", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Tajikistan: A joint HIV and TB grant aimed at supporting the country in reducing its burden of both diseases by addressing challenges related to HIV, drug-resistant TB, HIV/TB co-infection, human rights-related barriers to access and health systems strengthening.\n

\n

\nProgress\n

\n

\nTajikistan lies at the heart of Central Asia, bordered by Kyrgyzstan on the north, China to the east, Afghanistan on the south and Uzbekistan on the west and northwest. Over the past decade, the country has made steady progress in reducing poverty and growing its economy, which has coincided with gains against HIV and TB.\n

\n

\nHIV incidence has declined steadily, falling from 19 new infections per 100,000 people in 2010 to 9 in 2020. Steady increases have been made in key testing and treatment criteria: In 2021, 75% of people living with HIV knew their status, 87% of people who knew their HIV status were receiving lifesaving antiretroviral therapy and 88% of people on treatment had a suppressed viral load.\n

\n

\nThe country has made solid progress in the fight against TB, witnessing a downward trend in TB mortality and incidence rates. Incidence decreased from 220 per 100,000 people in 2002 to 82 per 100,000 people in 2019. Mortality decreased from 14 per 100,000 in 2002 to 7 per 100,000 in 2019. The TB treatment success rate has climbed steadily and in 2019 was 91%.\n

\n

\n

\nChallenges\n

\n

\nThere are sociocultural barriers in Tajikistan that stigmatize and discriminate against people living with HIV and the key populations who are disproportionately affected by the disease, including people who inject drugs, sex workers, and gay men and other men who have sex with men. The discrimination these groups experience can prevent them from accessing preventive care and treatment services, which can result in late diagnosis and treatment, leading to poor treatment outcomes and the spread of infection.\n

\n

\nThe trend of HIV infection in Tajikistan is shifting from transmission through people who inject drugs to transmission through sexual activity. To address this challenge, the current Global Fund grant supports a spectrum of prevention strategies focused on all key populations. These include harm reduction programs, community outreach, peer-to-peer education, distribution of prevention health commodities and information materials, promotion of support services, and strengthening advocacy and communication activities to enable supportive, non-discriminatory home and work environments.\n

\n

\nDespite a steadily improving epidemiological situation, Tajikistan remains among the 30 highest-burden countries for drug-resistant TB. The disease affects predominantly the younger and most economically active segment of the population: Almost two-thirds of all new TB cases occur in people aged between 15 and 44 years. Although the number of TB cases among children has remained steady in recent years, there is a growing number of children with drug-resistant strains of the disease.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA joint HIV and TB grant of up to US$25 million is being implemented in Tajikistan throughout 2021-2023. Our investment supports the country in its goal of providing effective HIV response measures for key populations; scaling up access for timely, quality diagnosis, treatment and care; delivering integrated care for those affected both by TB and HIV; and strengthening the national public heath response to promote universal, patient-centered, human rights- and gender-sensitive HIV and TB services.\n

\n

\nAdditionally, our investment will contribute to national health care reform by supporting the improvement of technical and managerial capacities of health professionals, promoting participation of civil society organizations in the HIV and TB response and enhancing collaboration between non-government organizations and the public health sector.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669221952, + "_created": 1669221781, + "_id": "75b6941a633163831600028b" + }, + { + "iso3": "JAM", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Jamaica: An HIV grant aimed at increasing access to comprehensive treatment and care services for key populations, with a focus on addressing human rights-related barriers to accessing HIV services.\n

\n

\nProgress\n

\n

\nAn island nation in the Caribbean Sea of almost 3 million people, Jamaica has achieved steady reductions in HIV incidence and mortality. Incidence almost halved between 2002, when the Global Fund was founded, and 2020. Mortality rates declined by a similar degree over the same period, dropping from 65 deaths per 100,000 people to 28.\n

\n

\nSince the introduction of a public access antiretroviral therapy program in 2004, usage of treatment and care services has increased, although there is still more progress to be achieved to meet the UNAIDS 95-95-95 testing and treatment targets.\n

\n

\n

\nChallenges\n

\n

\nWhile Jamaica has a generalized HIV epidemic, its key populations continue to be disproportionately affected. The most affected communities are transgender women (with a prevalence rate of 51%) and gay men and other men who have sex with men (with a prevalence rate of 29%). Prevalence among people experiencing homelessness, sex workers and people in prisons is also many multiples higher than in the general population.\n

\n

\nJamaica’s 2017 HIV/AIDS Knowledge, Attitudes, Behavior and Practice Survey found that just 12% of respondents were accepting of people living with HIV. HIV-related stigma and discrimination is still a big challenge in the community and can cause people to delay accessing services and, as a result, some are diagnosed with HIV at an advanced stage. Jamaica’s testing and treatment outcomes bear this out. While 81% of people living with HIV were aware of their status in 2021, just 47% of people living with HIV were on antiretroviral treatment.\n

\n

\nKey populations are the focus of Jamaica’s HIV response. It recognizes that protecting and promoting human rights for these groups is critical to improving access to HIV treatment and care services.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$16.7 million is being implemented in Jamaica throughout 2022-2024. Our investment supports the country in its goal of implementing strategies to increase prevention activities for key populations, scale up HIV testing and provide timely linkage to care, and address stigma and discrimination. It also aims to strengthen the development and implementation of the national HIV program by improving monitoring and evaluation and increasing collaboration among civil society organizations and HIV-affected communities.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669222716, + "_created": 1669222623, + "_id": "76371abb616638777e000254" + }, + { + "iso3": "TUN", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Tunisia: An HIV grant aimed at removing human rights-related barriers to access for key populations and achieving significant reductions in HIV incidence and mortality.\n

\n

\nProgress\n

\n

\nIn Tunisia, a country of 12.9 million people on the Mediterranean coast of North Africa, important progress has been made on political transition toward an open, democratic system of governance. Compared to other nations in the Middle East and North Africa region, the country reports more frequent and consistent data on the HIV epidemic. The percentage of people living with HIV who know their status has increased significantly in recent years, rising from 37% in 2015 to 80% in 2021.\n

\n

\n

\nChallenges\n

\n

\nThere are sociocultural barriers in Tunisia that stigmatize and discriminate against people living with HIV and the key populations who are disproportionately affected by the disease, including people who inject drugs, sex workers, transgender people, and gay men and other men who have sex with men. These social, structural and legal barriers can deter people from accessing HIV services. Steadily rising incidence rates and a low percentage of people living with HIV on antiretroviral therapy (29% in 2021) demonstrate the impact that stigma and discrimination can have on HIV prevention and treatment. \n

\n

\nDespite the adoption of a national strategy on human rights and HIV, and support from the Global Fund’s “Breaking Down Barriers” initiative, much work remains to be done to protect the rights of people living with HIV in Tunisia. Strategies to reduce HIV-related stigma and discrimination and provide legal assistance for victims of rights violations are a major component of the current Global Fund grant.\n

\n

\nAlthough the current economic and political context in Tunisia is challenging, the government remains committed to purchasing most of the antiretroviral drugs and biological tests needed for Tunisian nationals, as well as providing care for migrant populations whose numbers have increased in recent years.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$5.69 million is being implemented in Tunisia throughout 2022-2024. Our investment supports the country in its goal of reducing new HIV infections by at least 50% and AIDS-related deaths by 70% by 2025. It also supports efforts to significantly boost the use by key populations of the full package of available prevention services, and eliminate barriers to accessing HIV services related to inequality, stigma, discrimination, gender and violations of the rights of people living with HIV. Given the crucial role that civil society plays in promoting available services and supporting key populations in accessing care, the grant places a strong emphasis on civil society in Tunisia.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669222892, + "_created": 1669222798, + "_id": "7651c86f353966290b0003e2" + }, + { + "iso3": "EGY", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Egypt: A joint HIV and tuberculosis (TB) grant geared toward improving access to HIV services for key populations and continuing to drive reductions in Egypt’s TB burden.\n

\n

\nProgress\n

\n

\nEgypt has made gradual yet steady progress in some key testing and treatment criteria for HIV over the past decade. The percentage of people living with HIV who know their status increased from 32% in 2015 to 65% in 2021, while the percentage of people living with HIV who are on antiretroviral therapy rose from 8% in 2010 to 40% in 2021.\n

\n

\nThe country of 105 million people has also made gains against TB. Incidence and mortality have both trended down over the long term. Incidence fell from 25 cases per 100,000 people in 2000 to 11 cases per 100,000 in 2019, while mortality declined from 1.51 deaths per 100,000 people to 0.43 in 2019.\n

\n

\n

\nChallenges\n

\n

\nThere are sociocultural barriers in Egypt that stigmatize and discriminate against people living with HIV and the key populations who are disproportionately affected by the disease, including people who inject drugs and gay men and other men who have sex with men. In the absence of recent data on key populations, and given the relatively limited scope of HIV prevention interventions for them, it is probable that the prevalence among these key populations has remained constant since the last integrated HIV bio-behavioral surveillance survey in 2010, or, more likely, increased. \n

\n

\nImproving HIV-related strategic information by supporting the implementation of key studies and a stigma index is a focus of the current Global Fund grant. Other strategies to address the HIV burden among key populations include scaling up access to prevention, testing and treatment services for key populations, and stigma reduction strategies among health care workers and influential actors such as religious leaders and media personnel.\n

\n

\nDespite incidence rates steadily declining over the last two decades, TB continues to be a public health problem in Egypt. In 2015 TB deaths, after falling by around two-thirds since the year 2000, began to rise and continued to do so each year until 2019 (the most recent year for which data is available). The need remains for TB to be addressed and handled as a health problem affecting large sections of society, especially the poor and the vulnerable.\n

\n

\nEgypt is actively trying to find people with TB through an integrated services delivery approach in high transmission governorates: Five out of 27 governorates (Alexandria, Assyut, Cairo, Sharquia, and Sohag) account for more than 50% of notified TB cases. Our current grant also supports the country’s efforts to improve TB monitoring and evaluation systems and procurement capacities, and to continue fighting the ongoing threat of drug-resistant TB. Additionally, an important element of both the HIV and TB components of our current grant is multisectoral engagement to foster effective collaboration in Egypt’s epidemic response.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$5.27 million is being implemented in Egypt throughout 2022-2025. Our investment supports the country in its goal of reducing TB incidence by 50% and TB deaths by 75% by 2025 (compared to 2015 levels). It also supports efforts to significantly reduce catastrophic health care costs for patients and their families, and to continue working toward ending HIV as a public health threat by 2030 by supporting safe and healthy livelihoods and removing human rights-related barriers to HIV services.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669223096, + "_created": 1669222968, + "_id": "766bb1fd3132316692000188" + }, + { + "iso3": "TKM", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Turkmenistan: A tuberculosis (TB) grant aimed at fighting the country’s high rates of drug-resistant TB.\n

\n

\nProgress\n

\n

\nTurkmenistan is located at the center of the Eurasian continent, bordering Kazakhstan, Uzbekistan, Iran, Afghanistan, and, to the west of the country, the Caspian Sea. The country of 6 million people has made strong gains in reducing its TB burden over the past two decades.\n

\n

\nTB incidence has more than halved, falling from 99.5 cases per 100,000 people in 2002, when the Global Fund was founded, to 44.5 per 100,000 in 2019. TB mortality also reduced significantly over the same period: falling from 17.9 deaths per 100,000 people to 10.1 per 100,000.\n

\n

\n

\nChallenges\n

\n

\nDespite the significant progress Turkmenistan has achieved against TB, the disease remains a public health threat due to a high prevalence of drug-resistant TB. A 2018 drug resistance survey showed significant increases in drug resistance over the preceding years. Rifampicin resistance or multidrug resistance was seen in 54% of people with TB who had received treatment before and 23% of new cases, up from 37% and 14% respectively since the previous survey in 2015. Turkmenistan’s treatment success rates for drug-resistant TB are above the regional average, yet as with treatment success rates for extensively drug-resistant TB (which are on par with the rest of the region), they remain low. \n

\n

\nAddressing drug-resistant TB is the central challenge in the country's epidemic response. The current Global Fund grant is geared toward addressing suboptimal treatment coverage and success rates by supporting a targeted approach to finding people with TB, early TB diagnosis and ensuring people stay on TB treatment, with a focus on key and vulnerable populations, including children and people in prisons.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA TB grant of up to US$5 million is being implemented in Turkmenistan throughout 2021-2024. Our investment is aligned with the National Program for Prevention and Control of Tuberculosis in Turkmenistan for 2021-2025. The main goal of the program is to decrease the burden of TB in the country by ensuring universal access to timely and quality diagnosis and treatment of all forms of TB.\n

\n

\nThe national tuberculosis program aims to, by 2025, increase treatment coverage for drug-resistant TB to 90% and treatment success to 70%. It also aims to lower TB mortality to 5.2 deaths per 100,000 people and TB incidence to 35.5 cases per 100,000 people.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669223277, + "_created": 1669223182, + "_id": "768c5d3a66613843a1000017" + }, + { + "iso3": "CPV", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Cabo Verde: A joint HIV, tuberculosis (TB) and malaria grant that supports the country in continuing the strong progress it has made against HIV and TB, maintaining zero malaria cases and deaths and strengthening its systems for health.\n

\n

\nProgress\n

\n

\nIn Cabo Verde, a strong and stable political environment and a robust health system have resulted in significant gains in the fight against HIV, TB and malaria.\n

\n

\nThe country has been a leader in efforts to eliminate vertical transmission of HIV – when the virus passes from a mother to her child during pregnancy – and is one of only a few countries in West and Central Africa to be close to achieving this goal. Cabo Verde has also gone several consecutive years without local malaria transmission, becoming eligible to apply for the World Health Organization certification for malaria elimination.\n

\n

\n

\nChallenges\n

\n

\nKey and vulnerable populations in Cabo Verde are disproportionately affected by HIV. Prevalence rates among sex workers, people with disabilities, people who inject drugs and gay men and other men who have sex with men are higher than in the general population. Scaling up prevention interventions for these groups is a key focus of the country’s HIV response. Other priorities include increasing the percentage of people who receive antiretroviral therapy, training health personnel on a spectrum of topics to better meet the needs of vulnerable groups, strengthening community-based organizations to support the continued prevention of mother-to-child transmission, and updating HIV and TB treatment protocols.\n

\n

\nCabo Verde continues to build on the progress it has made in reducing its TB burden since 2012. The country has adopted rapid TB diagnostic methods, mainly using GeneXpert machines to lift its case detection rate and increase the likelihood of detecting drug-resistant strains of the disease. Maintenance of and training for GeneXpert machines is key to Cabo Verde’s ongoing TB response. Other strategies to support the fight against the disease include the purchase of LED microscopes, HIV and TB screening upon entry into prisons, actively finding people with TB among vulnerable populations and marginalized neighborhoods, and a host of interventions to optimize how the country finds, treats and supports people with drug-resistant TB.\n

\n

\nWith no malaria-related deaths between 2018 and 2020 and zero indigenous malaria cases in 2019 and 2020, Cabo Verde is close to eliminating the disease. When cases do arise, the type of transmission is seasonal, hypo-endemic and highly dependent on rainfall. Ongoing surveillance, prevention, testing and treatment interventions are critical to avoid the re-establishment of malaria transmission and remain an ongoing health priority for the country.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA joint HIV, TB and malaria grant of up to €4.3 million is being implemented in Cabo Verde throughout 2021-2023. Our investment supports the country’s goal of eliminating vertical transmission of HIV and syphilis by 2023, reducing TB deaths by 50% (compared to 2015 levels), treating 100% of people with drug-resistant TB and maintaining Cabo Verde’s status of zero indigenous cases of malaria.\n

\n

\nThe grant also supports a selection of targeted activities designed to help make Cabo Verde’s health systems even more resilient and sustainable.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669223478, + "_created": 1669223387, + "_id": "76aba854323532ae0500036b" + }, + { + "iso3": "UZB", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Uzbekistan: A joint HIV and tuberculosis (TB) grant aimed at strengthening the country's HIV response, particularly for key populations, improving TB care and addressing the country's high burden of drug-resistant TB.\n

\n

\nProgress\n

\n

\nAfter experiencing increasing numbers of new HIV infections and AIDS-related deaths during the 2000s, Uzbekistan has achieved steady reductions in HIV incidence (since 2014) and mortality (since 2015). Increases have also been made in key testing and treatment criteria. Among people living with HIV in 2021, 77% knew their status, 51% were receiving lifesaving antiretroviral therapy (up from 12% in 2010), and 44% had a suppressed viral load (up from 26% in 2015).\n

\n

\nProgress has also been made in the country's fight against TB. TB-related deaths declined by 78% between 2002, when the Global Fund was founded, and 2020. New TB cases fell by almost a quarter over the same period. The TB treatment success rate has been consistently high, and in 2019 sat at 90%.\n

\n

\n

\nChallenges\n

\n

\nFive out of the 14 regions in Uzbekistan contain three-quarters of the people living with HIV (Tashkent City, Tashkent Region, Andijan, Fergana and Samarkand). Key populations – people who inject drugs, sex workers and gay men and other men who have sex with men – bear much of the HIV burden in the country. Reaching these people with effective interventions is a key focus of HIV programming, which aims to remove human rights and gender-related barriers to reporting, testing and treatment for key populations.\n

\n

\nThe national HIV program of Uzbekistan was enacted by a presidential decree in 2018. The government adopted a resolution aimed at meeting the country's challenges around the disease by increasing the quality of HIV diagnosis services, supporting early detection of new infections, providing medical care for people living with HIV, implementing preventive measures in a timely way, and ensuring the fulfillment of international obligations in the fight against HIV.\n

\n

\nDespite the gains made against TB in Uzbekistan over the past decade, the disease remains a significant public health threat. Rifampicin-/multidrug-resistant TB accounts for around 15% of new cases and over one-third of previously treated cases, and the World Health Organization lists Uzbekistan as one of 30 countries with the highest burden of drug-resistant TB in the world.\n

\n

\nThe government of Uzbekistan has placed a particular focus on improving ТВ care, and in 2019 the president of Uzbekistan signed a decree to support a care reform. Accordingly, the National ТВ Strategic Program for 2021-2025 has been developed to support the country’s commitment to the Sustainable Development Goal 3.3 target of ending ТВ as a global health threat by 2030.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA joint HIV and TB grant of up to US$44.1 million is being implemented in Uzbekistan throughout 2021-2024. Our investment supports the country’s goal of delivering an impactful, efficient and sustainable HIV and ТВ response.\n

\n

\nFor HIV, the grant is aligned with the national HIV strategy and mainly focuses on providing prevention services to key populations as well as quality antiretroviral therapy, care and support. For TB, the grant focuses on diagnostics and treatment of drug-resistant TB, counseling and psychosocial support for people with TB, treatment monitoring, health information systems and monitoring and evaluation, as well as strengthening Uzbekistan’s laboratory systems.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669223728, + "_created": 1669223613, + "_id": "76ce2e7b3338667180000215" + }, + { + "iso3": "MYS", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Malaysia: An HIV grant that is geared toward reducing the transmission and impact of HIV among key populations, who bear most of the country's HIV burden.\n

\n

\nProgress\n

\n

\nHIV has been recognized as a serious public health issue and a challenge to Malaysia’s ongoing development since 1986, when the first infection was diagnosed in the country. Sustained efforts to increase the percentage of people living with HIV who are receiving antiretroviral therapy (55% in 2021) and people living with HIV who are virally suppressed (45% in 2021) have underpinned strong reductions in the mortality rate over the last decade (down 50% between 2010 and 2020). An extensive harm reduction program focused on people who inject drugs – formerly the group most affected by HIV in the country – has resulted in significant reductions in prevalence among this group.\n

\n

\n

\nChallenges\n

\n

\nThe central challenge in Malaysia’s HIV response is reducing the transmission and impact of HIV among key populations. Although progress has been achieved in reducing the burden of HIV among people who inject drugs, ongoing efforts are focused on driving it further into retreat for this group.\n

\n

\nOther key populations also continue to be disproportionately affected by the virus. Rising rates of infection among gay men and other men who have sex with men reflect an epidemiological shift in transmission away from injection drug use to sexual transmission.\n

\n

\nIn 2019, only 3.7% of new HIV infections were among people who inject drugs, while over 90% were attributable to sexual transmission. Nearly two-thirds of all new infections were among gay men and other men who have sex with men – a sharp increase from 10% less than a decade earlier.\n

\n

\nRates of HIV prevalence among female sex workers are many multiples higher than the general population. Prevalence is low among transgender women; however, it has been on the rise in recent years.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$3.9 million is being implemented in Malaysia throughout 2022-2025.\n

\n

\nOur investment supports the country’s Differentiated HIV Services for Key Populations Program, which is the national mechanism for the delivery of services for people most at risk of HIV infection. The program aims to drive progress toward the 95-95-95 testing and treatment targets by strengthening the outreach capacity of civil society and community-based organizations, enhancing health management information systems, monitoring and evaluation, reducing human rights-related barriers to HIV services, and supporting prevention and testing strategies focused on those who need them most.\n

\n

\nLast updated November 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669224138, + "_created": 1669224020, + "_id": "770c4d3f66343030430001ae" + }, + { + "iso3": "SEN", + "summary": "
\n

\nThe Global Fund currently has four core grants active in Senegal, with funding totaling up to €70 million signed for 2021-2023. Our investments support interventions aimed at accelerating progress against HIV, securing strong gains against TB, achieving bold reduction targets for malaria, and increasing the resilience and sustainability of health systems.\n

\n

\nProgress\n

\n

\nSenegal lies on the westernmost point of the African continent and is a nation of young people: Around two-thirds of its 17 million people are under the age of 25. Its stability has supported steady progress against HIV, TB and malaria.\n

\n

\nIncreases in the UNAIDS 95-95-95 HIV testing and treatment target criteria have been strong: As of 2021, 81% of people living with HIV knew their status, 79% of people who knew their HIV status were receiving lifesaving antiretroviral therapy (up from 25% in 2010), and 69% of people on treatment had a suppressed viral load (more than double the 2015 result).\n

\n

\nEfforts to end TB as a public health threat are bearing fruit, with the treatment success rate sitting above 90% for more than a decade and the mortality rate falling by 43% between 2002 and 2019.\n

\n

\nSenegal has continued to scale up malaria control efforts such as integrated community case management, seasonal malaria chemoprevention, vector control interventions and disease surveillance. Population coverage of mosquito nets reached 76% in 2020, and despite annual fluctuations over the last decade, new malaria cases have fallen significantly (by 64% between 2002 and 2020).\n

\n

\n

\nChallenges\n

\n

\nIn Senegal, key populations – sex workers, people who inject drugs, and gay men and other men who have sex with men – bear a significantly higher HIV burden than the general population. Prevalence among vulnerable groups – especially people in prisons and people with disabilities – is also many times higher than the general population. To address the stigma, discrimination and violence that key populations experience, Senegal is participating in the Global Fund’s “Breaking Down Barriers” initiative, which provides funding for tackling human rights- and gender-related barriers to HIV testing and treatment.\n

\n

\nGlobal Fund investments also support technical activities in Senegal’s HIV response. These include scaling up preventive interventions for key populations, intensifying differentiated HIV testing services and a host of other strategies to fight HIV.\n

\n

\nCurrent Global Fund investment for TB in Senegal aims to build on the lessons learned from previous grants to close programmatic gaps and improve performance. Six regions (Dakar, Thiès, Diourbel, Kaolack, Ziguinchor and Saint-Louis) have been prioritized for implementation as they are the most densely populated, have the highest health service coverage in Senegal, and account for over four-fifths of TB notifications and 80% of the treatment coverage gap.\n

\n

\nMalaria transmission is uneven across Senegal, with distribution of the disease being highly seasonal. Due to the geographical variations in the epidemic, the national malaria program has stratified the country into pre-elimination zones, intermediate-burden zones, moderate-burden zones and high-burden zones to optimize its epidemic response.\n

\n

\nDespite progress in the fight against malaria, achieving high coverage of long-lasting insecticidal nets has proven challenging, as has implementing intermittent preventive treatment in pregnancy to the targeted level. Stock-outs of anti-malaria medicine, irregular attendance at antenatal care visits (which affects coverage of intermittent preventive treatment in pregnancy), costs of some consultation services, and women’s often limited power to make decisions about their own bodies have all presented challenges. Additionally, in hard-to-reach areas, access to health services is difficult for pregnant women, livestock herders, miners and fishermen.\n

\n

\n

\nGlobal Fund investments\n

\n

\nTwo HIV grants of up to a combined €24 million have been signed for Senegal for 2021-2023. Our investment supports the country in working toward its bold target of reducing AIDS-related deaths by 82% in the coming years.\n

\n

\nTo optimize grant sustainability and integration, and streamline investment by the Global Fund, Senegal has developed a shared-service approach to manage TB, malaria and health systems strengthening activities. Two grants addressing multiple components and totaling up to a combined €46 million were signed for 2021-2023. Our investment supports interventions aimed at reducing TB deaths by 40% (compared to 2015), reducing malaria incidence and malaria-related mortality by at least 75% (compared to 2019) and improving the health of the people of Senegal in a sustainable manner.\n

\n

\nLast updated September 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669224774, + "_created": 1669224225, + "_id": "772b7d593332312f4f000390" + }, + { + "iso3": "CMR", + "summary": "
\n

\nThe Global Fund currently has four core grants active in Cameroon, with funding totaling up to €274 million signed for 2021-2023. Our investments aim to support the country in continuing its progress against HIV and tuberculosis (TB) and expanding gains against malaria, which is endemic and highly challenging to control in the country.\n

\n

\nProgress\n

\n

\nCameroon is a country of 27 million people located on the Atlantic coast in West and Central Africa. After several decades of stability, violence and disasters in recent years have displaced hundreds of thousands of people. Despite these challenges, Cameroon has continued to make progress against HIV, TB and malaria.\n

\n

\nHIV incidence and AIDS-related mortality have both reduced significantly: Incidence fell from 354 per 100,000 people in 2002 to 60 in 2020, while the mortality rate dropped from 165 per 100,000 people to 54 over the same period.\n

\n

\nSignificant reductions in TB mortality and incidence have also been achieved, reducing by 55% and 44% respectively between 2002 and 2019.\n

\n

\nCameroon has intensified its malaria prevention efforts in recent years. While malaria morbidity and mortality have trended slightly upward over the past decade, multiple mass-distribution campaigns for long-lasting insecticidal nets, targeted seasonal malaria chemoprevention, improved case management via a network of community health workers, and a focus on continued improvement in the quality of diagnosis have proven effective in identifying and treating cases early enough to prevent a higher number of deaths.\n

\n

\n

\nChallenges\n

\n

\nThe distribution of HIV in Cameroon highlights the need to further embed regionalized and differentiated approaches to HIV testing, prevention and treatment. Women bear a significantly higher burden of the virus than men, accounting for two-thirds of new infections in the 15-49 age group. Young people are also significantly affected, with people under 24 accounting for half of the new infections.\n

\n

\nDespite progress in increasing access to HIV testing and antiretroviral therapy for key populations, barriers to access persist. Sex workers, transgender people and gay men and other men who have sex with men continue to be socially marginalized and criminalized, which impacts the uptake of testing and treatment services. The rate of mother-to-child transmission remains persistently high – a challenge being addressed through the national HIV program, which aims to achieve universal access to prevention of mother-to-child transmission programs.\n

\n

\nAlthough recent years have seen a slight increase in the TB notification rate, approximately 50% of people with TB are still “missing” – undiagnosed, untreated or unreported to the health systems. Many of these people are children. In 2018, 5.5% of people notified with TB were children under 5 – well below the 10-12% estimated by pediatric TB projections. A key focus of current Global Fund investment is supporting finding of the missing among children and young people (as well as vulnerable groups such as refugees, people in prisons and people living with HIV).\n

\n

\nDrug-resistant TB remains an ongoing threat. To help address this, Global Fund investment supports the procurement of GeneXpert machines and the creation of four new drug-resistant TB centers in the high-burden cities of Yaoundé and Douala.\n

\n

\nMalaria is highly endemic in Cameroon, with the country’s entire population exposed to the disease. A key challenge in the country's epidemic response is improving adherence to chemoprevention treatment. While community health workers can observe the administration of the first dose, the second and third doses are given by the caregiver without supervision. To verify administration, health authorities have identified “leader” households within each village who remind parents or caregivers to give their children their second and third doses.\n

\n

\nAnother key challenge is ensuring that health facilities can report data more regularly through the national health information system platform to improve visibility on trends in morbidity and mortality. Quality of diagnosis has historically been an issue in Cameroon, and is being addressed by training and refresher courses, and by putting in place laboratory mentors that help validate results.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to €125 million, a TB grant of up to €16 million and an HIV and TB grant of up to €31 million have been signed for Cameroon for 2021-2023. Our investment supports the country in working toward its target of significant reductions in HIV infections and AIDS-related deaths, with a strong focus on driving down morbidity and mortality for key populations and young people. For TB, the goal is to reduce incidence from 186 per 100,000 people to 130 in the coming years, and mortality from 54 per 100,000 people to 32.\n

\n

\nWe continue to support the malaria response in Cameroon through a grant of up to €101 million for 2021-2023. Our investment supports activities to increase malaria diagnosis and treatment, scale up integrated community case management interventions and continue vector control and preventive interventions, with a goal of reducing malaria-related morbidity and mortality by 60% (compared to 2015 levels).\n

\n

\nLast updated September 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669224585, + "_created": 1669224585, + "_id": "776269ce343565b6ad000091" + }, + { + "iso3": "TGO", + "summary": "
\n

\nThe Global Fund currently has three core grants active in Togo, with funding totaling up to €98.9 million signed for 2021-2023. Our investments aim to support the country in accelerating the implementation of prevention services and quality care in the fight against HIV and tuberculosis (TB) and consolidating progress in the fight against malaria.\n

\n

\nProgress\n

\n

\nTogo has made progress in the HIV response. Between 2010 and 2018, new HIV infections decreased by 31%. AIDS-related deaths decreased by 34% over the same period. Of the estimated 69% of people in Togo who knew their HIV status in 2019, 96% of them were on antiretroviral therapy.\n

\n

\nTogo is making gains in the fight against malaria. In 2018, 96% of children living in the Central, Kara and Savanes regions received seasonal malaria chemoprevention. In recent years, more and more health facilities have begun to offer malaria diagnostic services, and there is greater availability of rapid diagnostic tests (RDTs) and an increased number of mobile clinics have facilitated access to care. In 2017, 97% of confirmed cases of malaria received first-line antimalarial treatment.\n

\n

\nThe country has made significant progress in the fight against TB, witnessing a downward trend in TB mortality and incidence rates. Incidence decreased from 76 per 100,000 people in 2010 to 36 per 100,000 people in 2018. Mortality decreased from 10 per 100,000 in 2013 to 2.7 per 100,000 in 2018. In 2018, 99% of people diagnosed with TB received an HIV test.\n

\n

\nWith continued investment, Togo can build upon those gains and accelerate progress toward the Sustainable Development Goal target of ending the three diseases as public health threats by 2030.\n

\n

\n

\nChallenges\n

\n

\nThere are sociocultural barriers in Togo that stigmatize and discriminate against people living with HIV and the key populations who are disproportionately affected by the disease, including gay men and other men who have sex with men, sex workers, people who inject drugs, and people in prisons. Adolescent girls and young women are also particularly affected. Twenty-five percent of new infections occur among young people aged 15-24; of these young people, 75% are adolescents and young girls. Thirty percent of new HIV infections occur in women aged 25-34. In 2020, a high mother-to-child HIV transmission rate was reported (14.9%). Only 35% of newborns of HIV-positive mothers who were eligible for antiretroviral therapy received it.\n

\n

\nMalaria presents a significant public health threat in Togo, especially to pregnant women and children under 5 years old. In 2019, children under 5 accounted for 58% of malaria cases and 73% of malaria-related deaths in hospitals. The entire population is exposed to malaria throughout the year, with risks increasing during the rainy seasons.\n

\n

\nIn Togo, TB screening in children remains a challenge. In 2018, children accounted for 3.39% of all cases detected. Drug-resistant TB remains a serious threat, with people dying from the disease before being able to access treatment and others diagnosed in time, but unable to enroll in treatment. The Global Fund partnership aims to improve service coverage for TB among the general population, and specifically among children, people diagnosed with multidrug-resistant TB, people in prisons, people living with HIV, and itinerant groups.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to €40.8 million has been signed for Togo for 2021-2023. Our investment is geared toward supporting a social, political and legal environment that fosters continued access to HIV services for key populations and people living with HIV. With a more effectively managed response to HIV and AIDS in the country, Togo aims to reduce new infections by 75% among the general population and reduce AIDS-related deaths by 80% by 2025.\n

\n

\nWe continue to support the malaria response in Togo through a grant of up to €54.6 million for 2021-2023. The grant supports Togo in its goal of reducing the incidence of malaria by at least 50% compared to 2015; reducing the malaria mortality rate by at least 40% compared to 2015; and strengthening and maintaining program management capacity at all levels in the coming years. Activities include mosquito net distribution campaigns, intermittent preventive treatment during pregnancy, seasonal malaria chemoprevention and improved malaria case management.\n

\n

\nA TB grant of up to €EUR 3.5 million has been signed for 2021-2023. Our investment supports interventions designed to expand service coverage to reach more people with TB (including people with drug-resistant TB), strengthen testing capacity, improve early detection efforts, and build capacity to diagnose TB among people living with HIV and ensure that they have access to treatment.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669307550, + "_created": 1669302905, + "_id": "a61137e23932331e35000045" + }, + { + "iso3": "SUR", + "summary": "
\n

\nThe Global Fund currently has two core grants active in Suriname with funding totaling up to US$5.7 million signed for 2021-2024. Our investments aim to support the country in strengthening its national response to HIV and AIDS and in containing – and ultimately eliminating – malaria by expanding test, track and treat programs in at-risk areas.\n

\n

\nProgress\n

\n

\nSuriname has made progress in the fight against HIV. While an estimated 6,100 people are living with HIV in the country (about 1% of the population), new HIV infections have decreased by 30% between 2010 and 2018. Over the same period, AIDS-related deaths declined by 20%, and the mortality rate declined from 22.9 per 100,000 people in 2010 to 14.9 per 100,000 people in 2017.\n

\n

\nSince 2006, Suriname has experienced a dramatic decline in the number of malaria cases reported, from 3,289 in 2006 to just 235 cases in 2018 – a 93% decline. Progress toward malaria elimination has been made in Paramaribo and upper Suriname.\n

\n

\nWith continued investment, Suriname has the ambitious goal to achieve malaria-free status in 2025 and accelerate progress toward the Sustainable Development Goal target of ending HIV as a public health threat by 2030.\n

\n

\n

\nChallenges\n

\n

\nThere are sociocultural barriers in Suriname that stigmatize and discriminate against people living with HIV and the key populations who are disproportionately affected by the disease. Knowledge about HIV prevention among young people aged 15-24 remains low. Despite these challenges, HIV prevention interventions among key populations represent a key success of Suriname’s HIV response in recent years. Ways of reaching key populations are an important priority for the country and they have been regularly refined and improved to offer prevention and care to an increasing number of people.\n

\n

\nWhile Suriname has made great progress toward malaria elimination in certain parts of the country, malaria remains a threat in the gold-mining Savannah/Lake areas, the border with French Guiana, West Suriname, and the interior communities. To progress toward malaria elimination, it is critical to address human rights and gender-related vulnerabilities and barriers to accessing malaria services in these areas, where an estimated 81,000 people are still at risk.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$2.4 million has been signed for Suriname for 2022-2024. Our investment is geared toward interventions that maintain or scale up evidence-based programs for key and vulnerable populations, including gay men and other men who have sex with men, transgender people, female sex workers and young people in prioritized rural areas of the country. Investments also prioritize HIV prevention and more coverage of people on antiretroviral therapy, and they support collaborative HIV/TB programs to support people who are co-infected with HIV and TB. The grant aims to provide differentiated services for key populations and vulnerable groups by tailoring service delivery frequency, timing, location, and providers to better meet client needs and preferences and eliminate stigma and discrimination.\n

\n

\nWe continue to support the malaria response in Suriname through a grant of up to US$3.3 million for 2021-2024. The grant supports Suriname in its goal of preventing a re-introduction of malaria by containing imported cases of malaria and managing local outbreaks. This will be achieved through strengthened cross-border collaboration and partnerships to foster resilient and sustainable systems for health.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669307818, + "_created": 1669307673, + "_id": "a8e8a5803935654be30000ec" + }, + { + "iso3": "GEO", + "summary": "
\n

\nThe Global Fund has one core grant in Georgia: a consolidated HIV and TB grant that is being implemented throughout 2023-2025. Our investment aims to support Georgia in countering increasing HIV infections and continuing to make progress in reducing the country’s TB burden.\n

\n

\nProgress\n

\n

\nGeorgia is a transcontinental country at the intersection of Eastern Europe and Western Asia with a population of 3.7 million people. The country is committed to fighting HIV and TB, and in the coming years, it will strengthen the national responses to both diseases by sustaining and expanding existing disease prevention, detection, linkage to care and treatment interventions.\n

\n

\nAccording to the National AIDS Centre, at the end of 2020, 76% of the estimated 8,400 people living with HIV in Georgia knew their HIV status. Eighty-four percent of those who knew their status were on antiretroviral therapy and 91% of people on antiretroviral therapy were virally suppressed. Gains against TB have been strong, with both incidence and mortality rates falling significantly over the past two decades.\n

\n

\n

\nChallenges\n

\n

\nAlthough the number of new HIV infections in Georgia remains relatively low by global standards, HIV incidence has increased rapidly since the turn of the century, rising from 2 per 100,000 people in 2001 to 17 per 100,000 in 2020. People who inject drugs, female sex workers and gay men and other men who have sex with men experience an HIV burden many multiples higher than the general population. Reaching these key populations requires interventions that address stigma and discrimination, both of which present an ongoing barrier to reporting, testing and treatment.\n

\n

\nOpioid substitution therapy (OST) has been used as a harm-reduction strategy for several years. Georgia reports one of the highest OST coverage rates in the region (studies have shown a positive impact of retention in OST on HIV outcomes). Current HIV programming aims to introduce OST in prisons and other closed settings to support long-term virological success.\n

\n

\nTuberculosis remains a major public health challenge in Georgia. Drug-resistant TB presents a serious threat and the proportion of it as part of the overall TB burden is high. The estimated TB notification rate has steadily decreased year-on-year, reflecting the incidence decline. However, the proportion of undiagnosed people with TB has increased significantly. Treatment coverage decreased from 80% in 2016 to 72% in 2019, further falling to 59% in 2020 (due to the influence of the COVID-19 pandemic).\n

\n

\n

\nGlobal Fund investments\n

\n

\nA consolidated HIV and TB grant is being implemented in Georgia throughout 2023-2025. The goal of the HIV component of the program is to stop the spread of the HIV epidemic through strengthened interventions that focus on key populations and strive to bring significant improvement in health outcomes for people living with HIV.\n

\n

\nThe goal of the TB component of the project is to decrease the burden of TB and its impact on social and economic development in Georgia. The project aims to ensure universal access to timely and quality diagnosis and treatment of all forms of TB, which would in turn decrease illness, drug resistance and death.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669308198, + "_created": 1669308017, + "_id": "a91d2425653564573e0003bf" + }, + { + "iso3": "COM", + "summary": "
\n

\nThe Global Fund has two core grants currently active in Comoros, with funding totaling up to €6.3 million signed for 2022-2024. Our investments aim to support the country in continuing to make progress against HIV, bending the curve for tuberculosis (TB) and achieving malaria elimination in the coming years.\n

\n

\nProgress\n

\n

\nComoros is an archipelago to the northeast of Madagascar in the Indian Ocean. The country of almost 1 million people has achieved significant increases in all the UNAIDS 95-95-95 HIV testing and treatment criteria. As of 2021, 86% of people living with HIV knew their status, 61% of people who knew their HIV status were receiving lifesaving antiretroviral therapy (up from 13% in 2010), and 52% of people on treatment had a suppressed viral load (more than double the 2015 result).\n

\n

\nProgress against TB has proven challenging, with declines in incidence and mortality replaced by gradual upward trends since 2012. Comoros aims to send TB into retreat with bold reduction targets, and policies and strategies for TB control aligned to the recommendations of the World Health Organization’s “End TB Strategy.”\n

\n

\nEliminating malaria is a national health and development priority for Comoros. The government has positioned this as an essential element of its developmental and economic vision for the country, and progress against the disease reflects this strong commitment. New cases and deaths have declined by almost 90% since 2002, and epidemiological data shows that Comoros is in the pre-elimination phase for malaria.\n

\n

\n

\nChallenges\n

\n

\nAlthough diagnostic and treatment services for HIV and TB are standardized for all patients regardless of their nationality, gender, race, religion or where they live, there is still stigma and discrimination attached to the two diseases. Removing obstacles associated with human rights and gender that hinder access to services – particularly those related to HIV for sex workers, people who use drugs, and gay men and other men who have sex with men – is a key focus of the current national strategic plans for both diseases.\n

\n

\nDisparities in access to care exist both between and within the islands that make up Comoros. There are more and higher quality health services on Grand Comoros (the largest island, with a population of over 300,000). On other islands, limited road infrastructure and the rugged topography of the volcanic islands can inhibit access to treatment and impact the quality of care. The geography of Comoros can also limit diagnosis; supporting the transportation of sputum samples to the laboratory with the GeneXpert machine is a priority for the TB component of the current Global Fund grant.\n

\n

\nGiven that data indicates Comoros is in the pre-elimination phase for malaria, stratification-based interventions are being prioritized to continue to drive gains against the disease. The measures taken since the 2000s – free treatment, distribution of long-lasting insecticidal nets, mass drug administration and targeted indoor residual spraying – have led to a significant reduction in malaria transmission and are the foundation of the country’s ongoing malaria response.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to €1.5 million has been signed for Comoros for 2022-2024. For HIV, our investment supports activities to scale up prevention of sexually transmitted infections (STI) and HIV among key populations, increase levels of optimized screening (including community-based screening), boost prevention of mother-to-child transmission interventions and increase the coverage of antiretroviral therapy. The grant’s TB interventions aim to reduce TB mortality by 75% and incidence by 50% by 2025 (compared to 2015 levels).\n

\n

\nWe continue to support Comoros’ significant malaria progress through a grant of up to €4.8 million for 2022-2024. Our investment supports the country in its goal of eliminating local malaria transmission in the coming years.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669374624, + "_created": 1669374509, + "_id": "d0bf07c0373362429500031e" + }, + { + "iso3": "PER", + "summary": "
\n

\nThe Global Fund has one core grant currently active in Peru: a joint HIV and tuberculosis (TB) grant with funding totaling up to US$21.3 million signed for 2022-2025. Our investment aims to support the country in recovering ground lost in the fight against HIV and TB due to COVID-19 and strengthening health and community systems to support access to health for all in Peru.\n

\n

\nProgress\n

\n

\nBefore COVID-19, Peru was making steady progress against HIV and TB. For almost a decade there were year-on-year increases in the UNAIDS 95-95-95 HIV testing and treatment criteria. As of 2021, 82% of people living with HIV knew their status, 80% of people who knew their HIV status were receiving lifesaving antiretroviral therapy and 68% of people on treatment had a suppressed viral load. AIDS-related deaths fell by 80% between 2002 and 2021. TB incidence and mortality rates were on a downward trend over the long-term and, by 2019, the treatment success rate had risen to 83%.\n

\n

\n

\nChallenges\n

\n

\nPeru is one of the countries most affected by the COVID-19 pandemic in terms of the number of deaths as a percentage of the population. The virus also had a significant impact on HIV and TB progress. Case detection decreased, many people dropped out of treatment, contract tracing coverage reduced, case reporting was delayed, and many other interventions suffered as a result of strained health systems.\n

\n

\nThe current Global Fund grant is focused on supporting the recovery of the HIV and TB response in Peru. It supports actions such as scaling up active case-finding, getting people back on treatment, bringing health services closer to those in need, collaborating more closely with civil society organizations and restoring health systems to pre-pandemic levels. These activities are being undertaken with a particular focus on migrants and other vulnerable populations.\n

\n

\nEven before the pandemic, there were several challenges in Peru's fight against HIV and TB. Stigma and discrimination continue to present barriers to testing and treatment. Transgender women and gay men and other men who have sex with men are disproportionately affected by HIV (prevalence rates in 2020 were 31% and 10% respectively, compared to 0.3% among the general population). The health system can be complex to navigate, which can lead to people discontinuing treatment. These challenges are being addressed by the national multisectoral health policy, with which our current grant aligns.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$21.3 million has been signed for Peru for 2022-2025. The goal of the grant is to support reductions in the HIV and TB burden by ensuring access to quality and timely health services. In addition to the activities outlined above, our investment supports efforts to decentralize health services and outreach to people through primary health care, leverage technology to better manage data, strengthen the compatibility of TB and HIV information systems, as well as a spectrum of other interventions to support access to health for all in Peru.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669375736, + "_created": 1669375609, + "_id": "d166e3a03034635c63000019" + }, + { + "iso3": "GTM", + "summary": "
\n

\nThe Global Fund has three core grants currently active in Guatemala. Our investments aim to support the country in making gains against HIV, accelerating progress against tuberculosis (TB) and achieving malaria elimination, which the country has in its sights.\n

\n

\nProgress\n

\n

\nStraddling the land bridge between the Pacific Ocean and the Caribbean Sea, Guatemala has both the highest population and largest economy in Central America. In 1996, after 36 years of internal conflict, the government signed a peace agreement. Economic stability followed and, while this success has not translated into significant reductions in poverty and inequality, progress has nonetheless been made in the fight against HIV, TB and malaria.\n

\n

\nHIV incidence and mortality have fallen by over two-thirds since the turn of the century, and there have been steady increases in the UNAIDS 95-95-95 HIV testing and treatment target criteria. TB deaths have fallen by almost 20% since 2002, and the TB treatment success rate has remained consistently high, and in 2019 was 85%. Strong gains have been made against malaria. Very few malaria deaths are now reported, new cases have fallen by over 90% and the country is working toward eliminating the disease.\n

\n

\n

\nChallenges\n

\n

\nThe main epidemiological corridor of HIV cases in Guatemala, including the areas of highest prevalence, corresponds to the Pan-American Highway that crosses Central America. Our current grant supports prioritizing geographic areas for prevention interventions along the corridor and providing differentiated service packages to better serve the key and vulnerable populations that are concentrated there. Some parts of Guatemala have high rates of violence that are fueled by the protection of drug routes. This can affect the implementation of programs for all three diseases, particularly in remote areas.\n

\n

\nEnsuring that people living with HIV stay on antiretroviral therapy and adhere to treatment is an ongoing challenge. Around 20% of people living with HIV who started antiretroviral therapy in 2018 were not on treatment 12 months later. The current grant supports strengthening retention and adherence through active follow-up of cases, decentralization of treatment, making access to antiretroviral therapy easier, and strengthening local surveillance.\n

\n

\nKey and vulnerable populations in Guatemala experience stigma and discrimination in many contexts, including health, education, work, family, community and justice, according to national studies. Eliminating these structural barriers – particularly in health care facilities – strengthening quality of care and promoting demand for services for key populations are critical to reaching epidemic control for both HIV and TB in Guatemala.\n

\n

\nAlthough Guatemala’s TB burden status is classified as low, indigenous peoples are disproportionately affected by the disease, accounting for around one-third of people with TB in 2019. Significant disparities still exist in access to health care in rural and indigenous communities, many of which report experiencing discrimination and barriers to access due to language differences.\n

\n

\nContact tracing for TB and preventive treatment coverage are low. In 2020, only 23% of contacts in a household where someone was diagnosed with TB were screened for the disease. Drug-resistant TB is an ongoing threat, particularly as only slightly more than half of people with drug-resistant TB were successfully treated in 2019. The increasing use of GeneXpert machines and rollout of new oral treatment regimens are helping to step up the fight against drug-resistant TB in Guatemala.\n

\n

\nIn Guatemala, as in other countries, malaria transmission is generally associated with poverty and a lack of economic and social development opportunities. The areas with the highest prevalence of malaria are also areas of greater vulnerability, marginalization, and limited access to basic services. Over 90% of people with malaria live in the departments of Escuintla, Alta Verapaz, Petén, Izabal and Suchitepequez. The current Global Fund grant supports actions with a national scope, but also has a strong focus on areas where the risk of transmission is highest. It is designed to support vulnerable groups such as migrant workers who sleep outside or in factories; rural, indigenous, and socially and economically disadvantaged communities; factory workers; school-age children and women.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV grant of up to US$26.8 million has been signed for Guatemala for 2021-2023. Our investment is geared toward reducing new HIV infections using differentiated and innovative combined prevention interventions. It also aims to reduce AIDS-related deaths by supporting early detection and adequate case management and retention, tracking patients lost to follow-up, and ensuring a comprehensive, functional care system.\n

\n

\nWe continue to support the TB response in Guatemala through a grant of up to US$4.3 million for 2022-2025. The grant supports Guatemala in its goal of strengthening early detection and diagnosis for both TB and drug-resistant TB, harnessing patient-centered support to improve treatment success, reducing the TB burden for people living with HIV (and the HIV burden for those with TB) and improving the compliance of TB prevention activities.\n

\n

\nA malaria grant of up to US$4.7 million was signed for 2022-2024. Our investment supports the country in its efforts to eliminate malaria as a public health threat in the coming years.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669377182, + "_created": 1669376644, + "_id": "d204d7dc306534492100034a" + }, + { + "iso3": "DOM", + "summary": "
\n

\nThe Global Fund has two core grants currently active in the Dominican Republic, with funding totaling up to US$17.8 million signed for 2022-2024. Our investment aims to support the country’s continued progress in reducing new HIV infections and deaths, particularly for key populations, who bear a disproportionate burden of the virus.\n

\n

\nProgress\n

\n

\nIn recent years, the Dominican Republic has established aggressive health care policies to combat the HIV epidemic. This has contributed to progress against the disease: Mortality fell from 73 per 100,000 people in 2002 (when the Global Fund was founded) to 27 per 100,000 in 2019. Incidence rates also reduced over the same period, from 80 per 100,000 people to 32 per 100,000. In 2021, 85% of people living with HIV knew their status, compared to 54% only six years earlier.\n

\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in the Dominican Republic disproportionately affects key populations: transgender people, female sex workers and gay men and other men who have sex with men. Since 1995, it has been illegal to discriminate against people living with HIV. However, enforcement is uneven and inconsistent. Stigma and discrimination (including in health care settings) against people living with HIV and key populations presents an ongoing barrier to testing and treatment. As part of the current Global Fund grant, a new non-discrimination policy for use in the health sector is being drafted, and its implementation will be monitored.\n

\n

\nPeople of Haitian nationality aged 15-49 living in the Dominican Republic (Haitian migrants) are harder to reach with prevention activities than general and key populations, and they have been identified as being at higher risk of HIV infection. Estimates indicate that, of people living with HIV in the Dominican Republic, nearly 40% of them are of Haitian descent. Additionally, the percentages of Haitian migrants who know their HIV status or receive treatment are significantly lower than the rest of the population.\n

\n

\nAlong with key populations, Haitian migrants in the provinces of Azua, Dajabón, Barahona, Elias Piña, El Seibo, Independencia, Monte Cristi, Pedernales, Samaná, San Juan and Santiago Rodríguez are a priority for the Dominican Republic’s HIV response.\n

\n

\nGlobal Fund investments aim to address the key challenges in the fight against HIV in the Dominican Republic by linking people from key and vulnerable populations to HIV prevention and care services, and strengthening adherence in both these contexts. Investments also support integrating HIV services with other health services at the first level of care to broaden their reach and capacity, and promote social contracting with the aim of making HIV prevention services more sustainable.\n

\n

\n

\nGlobal Fund investments\n

\n

\nWe continue to support the HIV response in the Dominican Republic through two HIV grants of up to a combined US$17.8 million for 2022-2024. The grant supports the country in its goal to reduce new infections among key and vulnerable populations and to increase the life expectancy of people living with HIV in a sustainable manner.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669377674, + "_created": 1669377413, + "_id": "d27a23596531324af7000271" + }, + { + "iso3": "PNG", + "summary": "
\n

\nThe Global Fund has two core grants currently active in Papua New Guinea, with funding totaling up to US$82 million signed for 2021-2023. Our investment aims to support stronger gains against HIV and tuberculosis (TB) and help counter a recent resurgence in malaria, with the aim of soon eliminating transmission of the disease in selected areas.\n

\n

\nProgress\n

\n

\nPapua New Guinea (PNG) occupies the eastern half of the island of New Guinea in the western Pacific, as well as another 600 smaller islands and atolls. It is one of the most rural countries in the world – only about 13% of its people live in urban centers – which presents unique challenges in the fight against HIV, TB and malaria. Nonetheless, progress against the three diseases has been made in some crucial respects.\n

\n

\nAIDS-related deaths have fallen significantly. In 2019, the HIV mortality rate was more than 70% lower than at its peak in 2006. TB deaths have also declined: Mortality fell from 109 per 100,000 people in 2002 (when the Global Fund was founded) to 46 per 100,000 in 2019.\n

\n

\nPNG’s high malaria transmission level makes it challenging to gain traction in the fight against the disease. Since 2004, the National Malaria Control Program has been supported by the Global Fund, which has enabled repeat national distribution campaigns of long-lasting insecticidal nets and supported the scale-up of malaria rapid diagnostic tests and artemisinin-based combination therapy in health facilities. After almost two decades of malaria control efforts, the country aims to eliminate malaria transmission in selected areas in the coming years.\n

\n

\n

\nChallenges\n

\n

\nAlmost 90% of Papua New Guineans live in rural areas where there are limited health facilities, and many people must travel considerable distances to access services. The country’s mountainous, forested landscape, isolated rural communities and the transient nature of the population can make it difficult for people to continue or complete treatment. People stopping their medication and the consequential development of drug-resistant strains of disease poses one of the greatest challenges to controlling HIV and TB in PNG.\n

\n

\nThe HIV epidemic in PNG is concentrated among eight of its 22 provinces, accounting for around two-thirds of the country’s burden of the virus. Key populations, such as sex workers, transgender women and gay men and other men who have sex with men face stigma, discrimination and violence from police, clients and others in their communities on a regular basis. This can deter them from seeking or accessing health care services, including getting tested for HIV or other sexually transmitted infections.\n

\n

\nVertical transmission rates in PNG remain high, and financial gaps and health system challenges have had a significant effect on HIV interventions. Given the varied geographic, social and structural challenges to PNG’s HIV programming, Global Fund support is focused on reaching the right people in the right way and place, and at the right time.\n

\n

\nTB continues to be a major public health threat in PNG. The country appears on the World Health Organization’s global lists of the 30 highest-burden countries for drug-sensitive TB and drug-resistant TB. It has one of the highest rates of TB incidence globally. Scaling up case detection and diagnosis (particularly in hard-to-reach areas) and finding innovative ways to support people with TB in adhering to treatment (such as text message reminders and using drones to supply TB drugs in remote areas) are key elements of the country's strategic response to TB.\n

\n

\nPNG is a high malaria burden country. Around 60% of the country’s 9 million people live in areas below 1,200 meters in altitude, where malaria transmission is endemic and perennial. Most people live in densely populated coastal lowlands and islands, and it is often difficult to deliver malaria services to many of them.\n

\n

\nPNG’s current malaria programming aims to counter a recent resurgence of the disease by maintaining high coverage of quality long-lasting insecticidal nets and supporting improvements in treatment-seeking behavior and in the quality of case management. The program also aims to initiate an ambitious burden reduction campaign in the provinces of the New Guinea Islands Region (one of four regions in PNG) in preparation for setting up a cross-border elimination corridor with neighboring provinces of the Solomon Islands.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$45 million has been signed for PNG for 2021-2023. For HIV, our investment supports (in addition to a host of interventions to boost the technical and managerial elements of the HIV and TB programs) targeted activities to increase the percentage of people who know their HIV status to 95% and the percentage of people on antiretroviral treatment to over 90% in the coming years.\n

\n

\nFor TB, the grant is geared toward providing universal drug susceptibility tests to 95% of people with TB and increasing treatment success rates to at least 85%, in addition to a range of other strategic objectives designed to accelerate progress toward ending TB as a public health threat.\n

\n

\nWe continue to support the malaria response in PNG through a grant of up to US$37.5 million for 2021-2023. The grant supports the country in its goal to reduce morbidity by 65%, mortality by 95% and eliminate malaria transmission in selected areas by 2025.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669378818, + "_created": 1669378458, + "_id": "d319b665633435e568000093" + }, + { + "iso3": "HND", + "summary": "
\n

\nThe Global Fund has two core grants currently active in Honduras, with funding totaling up to US$23.1 million signed for 2021-2025. Our investment aims to support the country in continuing its hard-won progress against HIV and tuberculosis (TB) and consolidating its significant gains against malaria by achieving malaria-free certification in the coming years.\n

\n

\nProgress\n

\n

\nHonduras is the second-largest country in Central America, with a population of 10 million people and coastlines on the Pacific Ocean and the Caribbean Sea. The nation has undertaken a social and economic development agenda that has helped to drive progress against HIV, TB and malaria.\n

\n

\nAIDS-related deaths have fallen significantly. HIV mortality fell from 40 per 100,000 people in 2002 (when the Global Fund was founded) to 7 per 100,000 in 2020. Incidence rates also reduced over the same period, from 26 per 100,000 people to 7 per 100,000. Incidence for TB has also seen a large long-term decline, falling by almost two-thirds between 2002 and 2019. The TB treatment success rate has remained consistently high, and in 2019 was 89%.\n

\n

\nThe malaria elimination program has achieved great success. In 2019, only 319 cases of malaria were recorded in all of Honduras – a massive decline compared to the 35,000 cases the country recorded just two decades before. No deaths from malaria were reported in 2020, and the country is now working toward obtaining malaria-free certification from the World Health Organization (WHO) by 2025.\n

\n

\n

\nChallenges\n

\n

\nThe HIV epidemic in Honduras disproportionately affects female sex workers, transgender people, and gay men and other men who have sex with men. Despite progress in ensuring legal protections against discrimination based on sexual orientation and gender identity, there is a continued focus on addressing inequality, stigma and violence for key populations. The Garifuna, an ethnic group of the Americas comprising descendants of Island Carib, Arawak, and West African peoples, also have rates of HIV prevalence many multiples higher than the general population.\n

\n

\nGlobal Fund investment supports HIV programming for the most at-risk groups in Honduras. There is a particularly strong focus on the scale-up of combined prevention packages to key and vulnerable populations, improving adherence to antiretroviral treatment regimens for people in key populations living with HIV and expanding the scope and reach of community-based HIV diagnosis services.\n

\n

\nRegarding TB, studies on delays in people with TB seeking timely care show that late detection is a key factor in TB death. In 2020, the average time between diagnosis and death was 28 days. Given that delayed diagnosis worsens illness severity, prolongs patient suffering, enables transmission within the community and increases the risk of death, current TB programming is working to better the understanding of the factors that influence delays, whether at the patient or health care level.\n

\n

\nOur current TB investment is focused on addressing several challenges in Honduras’ epidemic response, namely optimizing TB detection and treatment, implementing WHO’s “End TB Strategy,” improving institutional response capacity of health units and the laboratory network, strengthening drug-resistant TB detection and management, improving quality of care for people in prisons with TB, and integrating prevention as well as the control interventions for TB and HIV to improve co-infection management.\n

\n

\nDespite the impressive progress made against malaria in Honduras, there are still a handful of areas in the country where it remains a threat. In 2019, 81% of malaria cases were detected in five out of 60 municipalities located in four departments. Most of these departments are characterized by difficult geographical access, violence (drug trafficking, social conflicts), migration (both internal and external with Nicaragua), the presence of crops creating permanent breeding sites, and the presence of the Misquita, Tawakas, Pech and Garifuna peoples. These indigenous and afrodescendant groups have distinct dialects and sociocultural differences regarding treatment-seeking behaviors.\n

\n

\nHonduras’ malaria plan follows a five-pronged approach designed to strengthen early detection, diagnosis, and treatment, especially in highly vulnerable groups; strengthen case investigation; expand targeted interventions such as long-lasting insecticidal nets and indoor residual spraying; improve case reporting and notification; and enhance the use and application of the national integrated health information system.\n

\n

\n

\nGlobal Fund investments\n

\n

\nAn HIV and TB grant of up to US$19.1 million has been signed for Honduras for 2022-2025. For HIV, our investment supports the country in its goal to reduce the estimated number of people living with HIV who die from AIDS-related causes by at least 50% and lower the HIV prevalence in gay men and other men who have sex with men to 5% or less, as well as supporting other strategies focused on strengthening detection, diagnosis and treatment adherence.\n

\n

\nFor TB, the grant supports efforts to detect at least 85% of TB patients by 2025, (as per WHO guidelines), reduce TB mortality by 50% by 2025 (compared to 2015) and optimize TB prevention and control activities for vulnerable populations.\n

\n

\nWe continue to support the strong malaria response in Honduras through a grant of up to US$4 million for 2021-2023. Our investment backs interventions designed to eliminate indigenous malaria transmission in the coming years and achieve malaria-free certification from WHO by 2025.\n

\n

\nLast updated October 2022.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1669379134, + "_created": 1669378952, + "_id": "d364f0f93063618a59000182" + }, + { + "iso3": "MDA", + "summary": "
\n

\nThe Global Fund has one core* grant currently active in Moldova: a consolidated HIV and TB grant signed for 2021-2023. Our investment aims to support the country in accelerating gains against HIV and continuing its progress in driving down TB infections and deaths.\n

\n

\nProgress\n

\n

\nMoldova’s approach to fighting HIV and TB has been shaped by an evolving understanding of how to best address the country’s epidemic and by using learnings gained since the first national HIV and TB programs began. This adaptability has helped deliver progress against both diseases.\n

\n

\nSteady increases in the UNAIDS 95-95-95 HIV testing and treatment target criteria have been achieved: As of 2022, 67% of people living with HIV knew their status, 49% of people living with HIV were receiving lifesaving antiretroviral therapy (up from 9% in 2010), and 43% of people on treatment had a suppressed viral load. Although challenges in addressing the country’s TB burden remain, TB deaths and cases have significantly reduced over the past two decades, falling by 74% and 43% respectively by between 2002 to 2021.\n

\n

\n

\nChallenges\n

\n

\nThe HIV burden in Moldova is among the highest in Europe. People who inject drugs, female sex workers and gay men and other men who have sex with men experience an HIV burden many multiples higher than the general population. Reaching these key populations with effective interventions is a key focus of HIV programming, which aims to address human rights and gender-related barriers to reporting, testing and treatment.\n

\n

\nResistance to anti-TB drugs is the largest challenge and the main obstacle in effectively addressing the TB epidemic in Moldova. Data from 2019 showed drug-resistant TB in 27% of people newly diagnosed with TB, and in 56% of people with TB who had previously been treated for the disease.\n

\n

\nThere are large geographical variations in the country’s HIV and TB burdens. The eastern region of the country has the highest TB notification, drug-resistant TB and HIV/TB co-infection rates compared to other zones. Incidence of both HIV and TB is higher on the Left Bank of the Nistru river (an administrative area that meets the border of Ukraine) than the Right Bank.\n

\n

\nHealth authorities in Moldova and the Global Fund partnership are undertaking efforts to improve active case-finding and early detection of TB and HIV, increase TB contact investigations and enhance treatment outcomes and compliance with treatment programs.\n

\n

\n

\nGlobal Fund investments\n

\n

\nA consolidated HIV and TB grant was signed for Moldova for 2021-2023. The goal of the program is to reduce the human suffering and socioeconomic burden of HIV and TB in the country, and promote people-centered, gender-sensitive, rights-based environments and systems to support effective and sustainable responses to both diseases.\n

\n

\nLast updated October 2023.\n

\n

\n*Core grants include standalone or multicomponent HIV, TB, malaria and/or RSSH grants. Catalytic and/or C19RM investments may be active within the country. However, as these can fluctuate periodically, they are not included in this summary.\n

\n
", + "summary_de": null, + "summary_fr": null, + "_by": "273e9f943366388292000025", + "_mby": "273e9f943366388292000025", + "_modified": 1698750933, + "_created": 1669379251, + "_id": "d39292796362650ff60001fa" + } +] diff --git a/src/utils/filtering/documents.ts b/src/utils/filtering/documents.ts new file mode 100644 index 0000000..a310cf6 --- /dev/null +++ b/src/utils/filtering/documents.ts @@ -0,0 +1,53 @@ +import _ from 'lodash'; +import filtering from '../../config/filtering/index.json'; + +const MAPPING = { + geography: 'geography/code', + type: 'documentType/parent/name', + search: `contains(documentType/parent/name,) OR contains(title,)`, +}; + +export function filterDocuments( + params: Record, + urlParams: string, +): string { + let str = ''; + + const geographies = _.filter( + _.get(params, 'geographies', '').split(','), + (o: string) => o.length > 0, + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ + filtering.in + }(${geographies.join(filtering.multi_param_separator)})`; + } + + const types = _.filter( + _.get(params, 'types', '').split(','), + (o: string) => o.length > 0, + ).map((type: string) => `'${type}'`); + if (types.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.type}${ + filtering.in + }(${types.join(filtering.multi_param_separator)})`; + } + + const search = _.get(params, 'q', ''); + if (search.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( + //g, + `'${search}'`, + )}`; + } + + if (str.length > 0) { + if (urlParams) { + str = urlParams.replace('', str); + } + } else if (urlParams) { + str = urlParams.replace('', ''); + } + + return str; +} diff --git a/src/utils/filtering/donors.ts b/src/utils/filtering/donors.ts new file mode 100644 index 0000000..1a9b212 --- /dev/null +++ b/src/utils/filtering/donors.ts @@ -0,0 +1,64 @@ +import _ from 'lodash'; +import filtering from '../../config/filtering/index.json'; + +const MAPPING = { + geography: 'donor/geography/code', + donor: 'name', + donorType: 'type/name', + search: `contains(type/name,) OR contains(name,)`, +}; + +export function filterDonors( + params: Record, + urlParams: string, +): string { + let str = ''; + + const geographies = _.filter( + _.get(params, 'geographies', '').split(','), + (o: string) => o.length > 0, + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ + filtering.in + }(${geographies.join(filtering.multi_param_separator)})`; + } + + const donors = _.filter( + _.get(params, 'donors', '').split(','), + (o: string) => o.length > 0, + ).map((donor: string) => `'${donor}'`); + if (donors.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donor}${ + filtering.in + }(${donors.join(filtering.multi_param_separator)})`; + } + + const donorTypes = _.filter( + _.get(params, 'donorTypes', '').split(','), + (o: string) => o.length > 0, + ).map((donorType: string) => `'${donorType}'`); + if (donorTypes.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donorType}${ + filtering.in + }(${donors.join(filtering.multi_param_separator)})`; + } + + const search = _.get(params, 'q', ''); + if (search.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( + //g, + `'${search}'`, + )}`; + } + + if (str.length > 0) { + if (urlParams) { + str = urlParams.replace('', ` AND ${str}`); + } + } else if (urlParams) { + str = urlParams.replace('', ''); + } + + return str; +} diff --git a/src/utils/filtering/eligibility.ts b/src/utils/filtering/eligibility.ts new file mode 100644 index 0000000..d935832 --- /dev/null +++ b/src/utils/filtering/eligibility.ts @@ -0,0 +1,52 @@ +import _ from 'lodash'; +import filtering from '../../config/filtering/index.json'; + +const MAPPING = { + geography: 'geography/code', + year: 'eligibilityYear', +}; + +export function filterEligibility( + params: Record, + urlParams: string, +): string { + let str = ''; + + const geographies = _.filter( + _.get(params, 'geographies', '').split(','), + (o: string) => o.length > 0, + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ + filtering.in + }(${geographies.join(filtering.multi_param_separator)})`; + } + + const years = _.filter( + _.get(params, 'years', '').split(','), + (o: string) => o.length > 0, + ).map((year: string) => `'${year}'`); + if (years.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.year}${ + filtering.in + }(${years.join(filtering.multi_param_separator)})`; + } + + // const search = _.get(params, 'q', ''); + // if (search.length > 0) { + // str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( + // //g, + // `'${search}'`, + // )}`; + // } + + if (str.length > 0) { + if (urlParams) { + str = urlParams.replace('', ` AND ${str}`); + } + } else if (urlParams) { + str = urlParams.replace('/', ''); + } + + return str; +} diff --git a/src/utils/filtering/financialIndicators.ts b/src/utils/filtering/financialIndicators.ts new file mode 100644 index 0000000..2978b12 --- /dev/null +++ b/src/utils/filtering/financialIndicators.ts @@ -0,0 +1,129 @@ +import _ from 'lodash'; +import filtering from '../../config/filtering/index.json'; + +const MAPPING = { + geography: ['geography/code', 'implementationPeriod/grant/geography/code'], + donor: 'donor/name', + donorType: 'donor/type/name', + period: 'periodCovered', + donorGeography: 'donor/geography/code', + year: 'implementationPeriod/periodFrom', + yearTo: 'implementationPeriod/periodTo', + grantIP: 'implementationPeriod/code', + search: `(contains(donor/type/name,) OR contains(donor/name,) OR contains(periodCovered,))`, +}; + +export function filterFinancialIndicators( + params: Record, + urlParams: string, +): string { + let str = ''; + + const geographies = _.filter( + _.get(params, 'geographies', '').split(','), + (o: string) => o.length > 0, + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + if (MAPPING.geography instanceof Array) { + str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography + .map( + m => + `${m}${filtering.in}(${geographies.join( + filtering.multi_param_separator, + )})`, + ) + .join(' OR ')})`; + } + } + + const donors = _.filter( + _.get(params, 'donors', '').split(','), + (o: string) => o.length > 0, + ).map((donor: string) => `'${donor}'`); + if (donors.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donor}${ + filtering.in + }(${donors.join(filtering.multi_param_separator)})`; + } + + const donorTypes = _.filter( + _.get(params, 'donorTypes', '').split(','), + (o: string) => o.length > 0, + ).map((donorType: string) => `'${donorType}'`); + if (donorTypes.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donorType}${ + filtering.in + }(${donors.join(filtering.multi_param_separator)})`; + } + + const donorGeographies = _.filter( + _.get(params, 'donorGeographies', '').split(','), + (o: string) => o.length > 0, + ).map((donorGeography: string) => `'${donorGeography}'`); + if (donorGeographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donorGeography}${ + filtering.in + }(${donorGeographies.join(filtering.multi_param_separator)})`; + } + + const periods = _.filter( + _.get(params, 'periods', '').split(','), + (o: string) => o.length > 0, + ).map((period: string) => `'${period}'`); + if (periods.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.period}${ + filtering.in + }(${periods.join(filtering.multi_param_separator)})`; + } + + const years = _.filter( + [ + ..._.get(params, 'years', '').split(','), + ..._.get(params, 'cycle', '').split(','), + ], + (o: string) => o.length > 0, + ).map((year: string) => `'${year}'`); + if (years.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.year}${ + filtering.in + }(${years.join(filtering.multi_param_separator)})`; + } + + const yearsTo = _.filter( + _.get(params, 'yearsTo', '').split(','), + (o: string) => o.length > 0, + ).map((yearTo: string) => `'${yearTo}'`); + if (yearsTo.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.yearTo}${ + filtering.in + }(${yearsTo.join(filtering.multi_param_separator)})`; + } + + const grantIPs = _.filter( + _.get(params, 'grantIP', '').split(','), + (o: string) => o.length > 0, + ).map((grantIP: string) => `'${grantIP}'`); + if (grantIPs.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.grantIP}${ + filtering.in + }(${grantIPs.join(filtering.multi_param_separator)})`; + } + + const search = _.get(params, 'q', ''); + if (search.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( + //g, + `'${search}'`, + )}`; + } + + if (str.length > 0) { + if (urlParams) { + str = urlParams.replace('', ` AND ${str}`); + } + } else if (urlParams) { + str = urlParams.replace('', ''); + } + + return str; +} diff --git a/src/utils/filtering/fundingRequests.ts b/src/utils/filtering/fundingRequests.ts new file mode 100644 index 0000000..226c302 --- /dev/null +++ b/src/utils/filtering/fundingRequests.ts @@ -0,0 +1,87 @@ +import _ from 'lodash'; +import filtering from '../../config/filtering/index.json'; + +const MAPPING = { + geography: 'geography/code', + component: 'activityArea/name', + period: 'periodFrom', + trpWindow: 'window', + portfolioCategory: 'differentiationCategory', +}; + +export function filterFundingRequests( + params: Record, + urlParams: string, +): string { + let str = ''; + + const geographies = _.filter( + _.get(params, 'geographies', '').split(','), + (o: string) => o.length > 0, + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ + filtering.in + }(${geographies.join(filtering.multi_param_separator)})`; + } + + const components = _.filter( + _.get(params, 'components', '').split(','), + (o: string) => o.length > 0, + ).map((component: string) => `'${component}'`); + if (components.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.component.replace( + //g, + components.join(filtering.multi_param_separator), + )}`; + } + + const periods = _.filter( + _.get(params, 'periods', '').split(','), + (o: string) => o.length > 0, + ).map((period: string) => period); + if (periods.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.period}${ + filtering.in + }(${periods.join(filtering.multi_param_separator)})`; + } + + const trpWindows = _.filter( + _.get(params, 'trpWindows', '').split(','), + (o: string) => o.length > 0, + ).map((trpWindow: string) => `'${trpWindow}'`); + if (trpWindows.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.trpWindow}${ + filtering.in + }(${trpWindows.join(filtering.multi_param_separator)})`; + } + + const portfolioCategories = _.filter( + _.get(params, 'portfolioCategories', '').split(','), + (o: string) => o.length > 0, + ).map((portfolioCategory: string) => `'${portfolioCategory}'`); + if (portfolioCategories.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.portfolioCategory}${ + filtering.in + }(${portfolioCategories.join(filtering.multi_param_separator)})`; + } + + // const search = _.get(params, 'q', ''); + // if (search.length > 0) { + // str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( + // //g, + // `'${search}'`, + // )}`; + // } + + if (str.length > 0) { + if (urlParams) { + str = urlParams.replace('', ` AND ${str}`); + str = str.replace('= AND ', '='); + } + } else if (urlParams) { + str = urlParams.replace('', '').replace('$filter=&', ''); + } + + return str; +} diff --git a/src/utils/filtering/grants.ts b/src/utils/filtering/grants.ts new file mode 100644 index 0000000..a946512 --- /dev/null +++ b/src/utils/filtering/grants.ts @@ -0,0 +1,92 @@ +import _ from 'lodash'; +import filtering from '../../config/filtering/index.json'; + +const MAPPING = { + geography: 'geography/code', + status: 'status/statusName', + component: 'activityArea/name', + principalRecipient: 'principalRecipient/name', + startDate: 'periodStartDate', + endDate: 'periodEndDate', + search: + 'contains(geography/name,) OR contains(activityArea/name,) OR contains(principalRecipient/name,) OR contains(status/statusName,)', +}; + +export function filterGrants( + params: Record, + urlParams: string, +): string { + let str = ''; + + const geographies = _.filter( + _.get(params, 'geographies', '').split(','), + (o: string) => o.length > 0, + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ + filtering.in + }(${geographies.join(filtering.multi_param_separator)})`; + } + + const statuses = _.filter( + _.get(params, 'statuses', '').split(','), + (o: string) => o.length > 0, + ).map((status: string) => `'${status}'`); + if (statuses.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.status}${ + filtering.in + }(${statuses.join(filtering.multi_param_separator)})`; + } + + const components = _.filter( + _.get(params, 'components', '').split(','), + (o: string) => o.length > 0, + ).map((component: string) => `'${component}'`); + if (components.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.component}${ + filtering.in + }(${components.join(filtering.multi_param_separator)})`; + } + + const principalRecipients = _.filter( + _.get(params, 'principalRecipients', '').split(','), + (o: string) => o.length > 0, + ).map((principalRecipient: string) => `'${principalRecipient}'`); + if (principalRecipients.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.principalRecipient}${ + filtering.in + }(${principalRecipients.join(filtering.multi_param_separator)})`; + } + + const startDate = _.get(params, 'startDate', ''); + if (startDate.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.startDate}${ + filtering.gte + }'${startDate}'`; + } + + const endDate = _.get(params, 'endDate', ''); + if (endDate.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.endDate}${ + filtering.lte + }'${endDate}'`; + } + + const search = _.get(params, 'q', ''); + if (search.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( + //g, + `'${search}'`, + )}`; + } + + if (str.length > 0) { + if (urlParams) { + str = urlParams.replace('', str); + } + } else if (urlParams) { + str = urlParams.replace('$filter=&', ''); + } + + return str; +} diff --git a/src/utils/filtering/programmaticIndicators.ts b/src/utils/filtering/programmaticIndicators.ts new file mode 100644 index 0000000..a7738b3 --- /dev/null +++ b/src/utils/filtering/programmaticIndicators.ts @@ -0,0 +1,65 @@ +import _ from 'lodash'; +import filtering from '../../config/filtering/index.json'; + +const MAPPING = { + geography: 'geography/code', + component: 'activityArea/name', + year: 'resultValueYear', + search: + 'contains(geography/code,) OR contains(activityArea/name,)', +}; + +export function filterProgrammaticIndicators( + params: Record, + urlParams: string, +): string { + let str = ''; + + const geographies = _.filter( + _.get(params, 'geographies', '').split(','), + (o: string) => o.length > 0, + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ + filtering.in + }(${geographies.join(filtering.multi_param_separator)})`; + } + + const components = _.filter( + _.get(params, 'components', '').split(','), + (o: string) => o.length > 0, + ).map((component: string) => `'${component}'`); + if (components.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.component}${ + filtering.in + }(${components.join(filtering.multi_param_separator)})`; + } + + const years = _.filter( + _.get(params, 'years', '').split(','), + (o: string) => o.length > 0, + ).map((year: string) => year); + if (years.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.year}${ + filtering.in + }(${years.join(filtering.multi_param_separator)})`; + } + + const search = _.get(params, 'q', ''); + if (search.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( + //g, + `'${search}'`, + )}`; + } + + if (str.length > 0) { + if (urlParams) { + str = urlParams.replace('', ` AND ${str}`); + } + } else if (urlParams) { + str = urlParams.replace('', ''); + } + + return str; +} From 353dab8f6998e573d75819e0f4305b6916a70d14 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 21 May 2024 16:14:31 +0300 Subject: [PATCH 02/44] feat: grant targets & results --- src/config/mapping/grants/targetsResults.json | 16 +++ src/controllers/grants.controller.ts | 126 ++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/config/mapping/grants/targetsResults.json diff --git a/src/config/mapping/grants/targetsResults.json b/src/config/mapping/grants/targetsResults.json new file mode 100644 index 0000000..c5745af --- /dev/null +++ b/src/config/mapping/grants/targetsResults.json @@ -0,0 +1,16 @@ +{ + "dataPath": "value", + "module": "activityArea.name", + "name": "indicatorName", + "reversed": "isReversed", + "geoCoverage": "geographicCoverage", + "cumulation": "aggregationType", + "baselineValue": "baselineValuePercentage", + "baselineYear": "baselineValueYear", + "baselineSource": "valueSource", + "year": "targetValueYear", + "target": "targetValuePercentage", + "result": "resultValuePercentage", + "achievement": "performance", + "urlParams": "?$filter=programmaticDataSet eq 'IMPLEMENTATION_PERIOD_TARGETS_RESULTS' AND implementationPeriod/code eq '' AND valueType eq ''&$expand=activityArea($select=name)&$select=activityArea,indicatorName,isReversed,geographicCoverage,aggregationType,baselineValuePercentage,baselineValueYear,valueSource,targetValueYear,targetValuePercentage,resultValuePercentage,performance" +} diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index c1a641f..c1eff68 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -25,6 +25,7 @@ import grantPeriodsMap from '../config/mapping/grants/grantPeriods.json'; import GrantsRadialMapping from '../config/mapping/grants/grantsRadial.json'; import grantsMap from '../config/mapping/grants/index.json'; import GrantsListMapping from '../config/mapping/grants/list.json'; +import GrantTargetsResultsMapping from '../config/mapping/grants/targetsResults.json'; import grantsUtils from '../config/mapping/grants/utils.json'; import urls from '../config/urls/index.json'; import { @@ -586,6 +587,131 @@ export class GrantsController { .catch(handleDataApiError); } + @get('/grant/{id}/{ip}/targets-results') + @response(200) + async grantTargetsResults( + @param.path.string('id') id: string, + @param.path.string('ip') ip: number, + ) { + const type = _.get(this.req.query, 'type', 'Impact indicator') as string; + const url = `${ + urls.PROGRAMMATIC_INDICATORS + }/${GrantTargetsResultsMapping.urlParams + .replace('', `${id}P0${ip}`) + .replace('', type)}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const raw = _.get(resp.data, GrantTargetsResultsMapping.dataPath, []); + + const groupedByModule = _.groupBy( + raw, + GrantTargetsResultsMapping.module, + ); + + const data: any[] = []; + + let years: string[] = []; + + _.map(groupedByModule, (moduleItems, key) => { + const item: any = { + name: key, + _children: [], + }; + + const groupedByName = _.groupBy( + moduleItems, + GrantTargetsResultsMapping.name, + ); + + _.map(groupedByName, (nameItems, key2) => { + const subItem: any = { + name: key2, + _children: [], + }; + + _.map(nameItems, item => { + const groupedByYear = _.groupBy( + nameItems, + GrantTargetsResultsMapping.year, + ); + years = [...years, ...Object.keys(groupedByYear)]; + let itempush = { + reversed: + _.get(item, GrantTargetsResultsMapping.reversed, false) === + true + ? 'Yes' + : 'No', + geoCoverage: _.get( + item, + GrantTargetsResultsMapping.geoCoverage, + '', + ), + cumulation: _.get( + item, + GrantTargetsResultsMapping.cumulation, + '', + ), + baselineValue: _.get( + item, + GrantTargetsResultsMapping.baselineValue, + '', + ), + baselineYear: _.get( + item, + GrantTargetsResultsMapping.baselineYear, + '', + ), + baselineSource: _.get( + item, + GrantTargetsResultsMapping.baselineSource, + '', + ), + }; + _.forEach(groupedByYear, (yearItems, key3) => { + itempush = { + ...itempush, + [key3]: yearItems.map((yearItem: any) => { + const target = _.get( + yearItem, + GrantTargetsResultsMapping.target, + '', + ); + const result = _.get( + yearItem, + GrantTargetsResultsMapping.result, + '', + ); + const achievement = _.get( + yearItem, + GrantTargetsResultsMapping.achievement, + '', + ); + return { + target: target ? `T:${target}%` : '', + result: result ? `T:${result}%` : '', + achievement: achievement ? `${achievement}%` : '', + }; + }), + }; + }); + subItem._children.push(itempush); + }); + + item._children.push(subItem); + }); + + data.push(item); + }); + + years = _.uniq(years); + + return {data, years}; + }) + .catch(handleDataApiError); + } + // v2 @get('/grants') From 183914d47b594bd4fed969396b0e7347752592f2 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 22 May 2024 17:06:02 +0300 Subject: [PATCH 03/44] feat: cycles filtering --- .../allocations/cumulative-by-cycles.json | 8 +++ src/config/mapping/allocations/cycles.json | 8 +-- src/config/mapping/budgets/cycles.json | 6 +- src/config/mapping/disbursements/cycles.json | 6 ++ .../mapping/disbursements/lineChart.json | 2 + src/config/mapping/expenditures/cycles.json | 6 ++ .../mapping/fundingrequests/cycles.json | 6 ++ src/config/mapping/globalsearch/index.json | 58 +++++++++---------- .../mapping/pledgescontributions/cycles.json | 6 ++ src/config/mapping/results/cycles.json | 6 ++ src/controllers/allocations.controller.ts | 56 +++++++++++++++--- src/controllers/budgets.controller.ts | 35 +++++++---- src/controllers/disbursements.controller.ts | 56 ++++++++++++++++++ src/controllers/expenditures.controller.ts | 34 +++++++++++ src/controllers/fundingrequests.controller.ts | 30 ++++++++++ .../pledgescontributions.controller.ts | 44 ++++++++++++++ src/controllers/results.controller.ts | 36 ++++++++++++ src/utils/filtering/grants.ts | 2 +- 18 files changed, 349 insertions(+), 56 deletions(-) create mode 100644 src/config/mapping/allocations/cumulative-by-cycles.json create mode 100644 src/config/mapping/disbursements/cycles.json create mode 100644 src/config/mapping/expenditures/cycles.json create mode 100644 src/config/mapping/fundingrequests/cycles.json create mode 100644 src/config/mapping/pledgescontributions/cycles.json create mode 100644 src/config/mapping/results/cycles.json diff --git a/src/config/mapping/allocations/cumulative-by-cycles.json b/src/config/mapping/allocations/cumulative-by-cycles.json new file mode 100644 index 0000000..e1f10d1 --- /dev/null +++ b/src/config/mapping/allocations/cumulative-by-cycles.json @@ -0,0 +1,8 @@ +{ + "dataPath": "value", + "value": "value", + "cycle": "periodCovered", + "component": "activityArea.name", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#D9D9D9"], + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((periodCovered,activityArea/name),aggregate(actualAmount with sum as value))" +} diff --git a/src/config/mapping/allocations/cycles.json b/src/config/mapping/allocations/cycles.json index e1f10d1..8dcf187 100644 --- a/src/config/mapping/allocations/cycles.json +++ b/src/config/mapping/allocations/cycles.json @@ -1,8 +1,6 @@ { "dataPath": "value", - "value": "value", - "cycle": "periodCovered", - "component": "activityArea.name", - "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#D9D9D9"], - "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((periodCovered,activityArea/name),aggregate(actualAmount with sum as value))" + "cycleFrom": "periodCovered", + "cycleTo": "", + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((periodCovered))&$orderby=periodCovered asc" } diff --git a/src/config/mapping/budgets/cycles.json b/src/config/mapping/budgets/cycles.json index 843c091..9790b3c 100644 --- a/src/config/mapping/budgets/cycles.json +++ b/src/config/mapping/budgets/cycles.json @@ -1,6 +1,6 @@ { "dataPath": "value", - "from": "implementationPeriod/periodFrom", - "to": "implementationPeriod/periodTo", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/perioFrom,implementationPeriod/periodTo))" + "cycleFrom": "implementationPeriod.periodFrom", + "cycleTo": "implementationPeriod.periodTo", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" } diff --git a/src/config/mapping/disbursements/cycles.json b/src/config/mapping/disbursements/cycles.json new file mode 100644 index 0000000..af3bfb8 --- /dev/null +++ b/src/config/mapping/disbursements/cycles.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "cycleFrom": "implementationPeriod.periodFrom", + "cycleTo": "implementationPeriod.periodTo", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" +} diff --git a/src/config/mapping/disbursements/lineChart.json b/src/config/mapping/disbursements/lineChart.json index f5b6f46..aa0df34 100644 --- a/src/config/mapping/disbursements/lineChart.json +++ b/src/config/mapping/disbursements/lineChart.json @@ -1,8 +1,10 @@ { "dataPath": "value", + "count": "@odata.count", "cycle": "periodFrom", "value": "value", "line": "activityArea.name", "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,activityArea/name),aggregate(actualAmount with sum as value))", + "activitiesCountUrlParams": "?$count=true&$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/grant/code))", "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD"] } diff --git a/src/config/mapping/expenditures/cycles.json b/src/config/mapping/expenditures/cycles.json new file mode 100644 index 0000000..8d2e63d --- /dev/null +++ b/src/config/mapping/expenditures/cycles.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "cycleFrom": "implementationPeriod.periodFrom", + "cycleTo": "implementationPeriod.periodTo", + "urlParams": "?$apply=filter(indicatorName eq 'Expenditure: Module-Intervention - Reference Rate' AND isLatestReported eq true)/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" +} diff --git a/src/config/mapping/fundingrequests/cycles.json b/src/config/mapping/fundingrequests/cycles.json new file mode 100644 index 0000000..01859e1 --- /dev/null +++ b/src/config/mapping/fundingrequests/cycles.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "cycleFrom": "implementationPeriod.periodFrom", + "cycleTo": "implementationPeriod.periodTo", + "urlParams": "?$apply=groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" +} diff --git a/src/config/mapping/globalsearch/index.json b/src/config/mapping/globalsearch/index.json index 4a9569f..5bae3e2 100644 --- a/src/config/mapping/globalsearch/index.json +++ b/src/config/mapping/globalsearch/index.json @@ -24,6 +24,35 @@ ], "options": [] }, + { + "name": "Grant(s)", + "type": "Grant", + "link": "/grant/", + "url": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$select=code,status&$expand=status(select=statusName)&$filter=()&$orderby=status/statusName asc", + "itemname": "", + "filterFields": [ + "geography/code", + "geography/name", + "activityArea/parent/parent/name", + "title", + "code", + "principalRecipient/name", + "principalRecipient/shortName", + "status/statusName" + ], + "filterTemplate": "contains(,)", + "order": ["asc", "asc"], + "mappings": [ + "value[]", + { + "code": "code", + "type": "status/statusName", + "order": "status/statusName", + "order1": "code" + } + ], + "options": [] + }, { "name": "Partner(s)", "type": "Partner", @@ -68,35 +97,6 @@ ], "options": [] }, - { - "name": "Grant(s)", - "type": "Grant", - "link": "/grant/", - "url": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$select=code,status&$expand=status(select=statusName)&$filter=()&$orderby=status/statusName asc", - "itemname": "", - "filterFields": [ - "geography/code", - "geography/name", - "activityArea/parent/parent/name", - "title", - "code", - "principalRecipient/name", - "principalRecipient/shortName", - "status/statusName" - ], - "filterTemplate": "contains(,)", - "order": ["asc", "asc"], - "mappings": [ - "value[]", - { - "code": "code", - "type": "status/statusName", - "order": "status/statusName", - "order1": "code" - } - ], - "options": [] - }, { "name": "Result(s)", "type": "Result", diff --git a/src/config/mapping/pledgescontributions/cycles.json b/src/config/mapping/pledgescontributions/cycles.json new file mode 100644 index 0000000..ed67ab1 --- /dev/null +++ b/src/config/mapping/pledgescontributions/cycles.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "cycleFrom": "periodCovered", + "cycleTo": "", + "urlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc" +} diff --git a/src/config/mapping/results/cycles.json b/src/config/mapping/results/cycles.json new file mode 100644 index 0000000..740c363 --- /dev/null +++ b/src/config/mapping/results/cycles.json @@ -0,0 +1,6 @@ +{ + "dataPath": "value", + "cycleFrom": "resultValueYear", + "cycleTo": "", + "urlParams": "?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((resultValueYear))&$orderby=resultValueYear asc" +} diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index c30006e..f352116 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -13,6 +13,7 @@ import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; +import AllocationCumulativeByCyclesFieldsMapping from '../config/mapping/allocations/cumulative-by-cycles.json'; import AllocationCyclesFieldsMapping from '../config/mapping/allocations/cycles.json'; import AllocationsDrilldownFieldsMapping from '../config/mapping/allocations/drilldown.json'; import AllocationsGeomapFieldsMapping from '../config/mapping/allocations/geomap.json'; @@ -99,7 +100,7 @@ export class AllocationsController { async cumulativeByCycles() { let filterString = filterFinancialIndicators( this.req.query, - AllocationCyclesFieldsMapping.urlParams, + AllocationCumulativeByCyclesFieldsMapping.urlParams, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -108,7 +109,7 @@ export class AllocationsController { .then((resp: AxiosResponse) => { const raw = _.get( resp.data, - AllocationCyclesFieldsMapping.dataPath, + AllocationCumulativeByCyclesFieldsMapping.dataPath, [], ); @@ -122,24 +123,28 @@ export class AllocationsController { const groupedByCycle = _.groupBy( raw, - AllocationCyclesFieldsMapping.cycle, + AllocationCumulativeByCyclesFieldsMapping.cycle, ); const cycles = Object.keys(groupedByCycle); const groupedByComponent = _.groupBy( raw, - AllocationCyclesFieldsMapping.component, + AllocationCumulativeByCyclesFieldsMapping.component, ); _.forEach(groupedByComponent, (component, componentKey) => { const values: number[] = []; _.forEach(cycles, cycle => { const cycleData = _.find(component, { - [AllocationCyclesFieldsMapping.cycle]: cycle, + [AllocationCumulativeByCyclesFieldsMapping.cycle]: cycle, }); values.push( - _.get(cycleData, AllocationCyclesFieldsMapping.value, 0), + _.get( + cycleData, + AllocationCumulativeByCyclesFieldsMapping.value, + 0, + ), ); }); @@ -148,7 +153,7 @@ export class AllocationsController { values, itemStyle: { color: _.get( - AllocationCyclesFieldsMapping.colors, + AllocationCumulativeByCyclesFieldsMapping.colors, data.length + 1, ), }, @@ -161,7 +166,7 @@ export class AllocationsController { _.sumBy(data, `values[${index}]`), ), itemStyle: { - color: _.get(AllocationCyclesFieldsMapping.colors, 0), + color: _.get(AllocationCumulativeByCyclesFieldsMapping.colors, 0), }, }); @@ -436,6 +441,41 @@ export class AllocationsController { return getAllocationsData(url); } + @get('/allocations/cycles') + @response(200) + async cycles() { + return axios + .get( + `${urls.FINANCIAL_INDICATORS}${AllocationCyclesFieldsMapping.urlParams}`, + ) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + AllocationCyclesFieldsMapping.dataPath, + [], + ); + + const data = _.map(rawData, (item, index) => { + const from = _.get(item, AllocationCyclesFieldsMapping.cycleFrom, ''); + const to = _.get(item, AllocationCyclesFieldsMapping.cycleTo, ''); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: `Cycle ${index + 1}`, + value, + }; + }); + + return {data}; + }) + .catch(handleDataApiError); + } + // v2 @get('/allocations') diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 4fe9cc6..3140556 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -304,19 +304,34 @@ export class BudgetsController { @get('/budgets/cycles') @response(200) async cycles() { - const url = `${urls.FINANCIAL_INDICATORS}${BudgetsCyclesMapping.urlParams}`; - return axios - .get(url) + .get(`${urls.FINANCIAL_INDICATORS}${BudgetsCyclesMapping.urlParams}`) .then((resp: AxiosResponse) => { - return _.get(resp.data, BudgetsCyclesMapping.dataPath, []).map( - (item: any) => - `${_.get(item, BudgetsCyclesMapping.from, '')}-${_.get( - item, - BudgetsCyclesMapping.to, - '', - )}`, + const rawData = _.get(resp.data, BudgetsCyclesMapping.dataPath, []); + + const data = _.map( + _.filter( + rawData, + item => _.get(item, BudgetsCyclesMapping.cycleFrom, null) !== null, + ), + (item, index) => { + const from = _.get(item, BudgetsCyclesMapping.cycleFrom, ''); + const to = _.get(item, BudgetsCyclesMapping.cycleTo, ''); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: `Cycle ${index + 1}`, + value, + }; + }, ); + + return {data}; }) .catch(handleDataApiError); } diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index 77e4821..73bdd14 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -14,6 +14,7 @@ import querystring from 'querystring'; import filteringGrants from '../config/filtering/grants.json'; import filtering from '../config/filtering/index.json'; import BarChartFieldsMapping from '../config/mapping/disbursements/barChart.json'; +import DisbursementsCyclesMapping from '../config/mapping/disbursements/cycles.json'; import GeomapFieldsMapping from '../config/mapping/disbursements/geomap.json'; import GrantCommittedTimeCycleFieldsMapping from '../config/mapping/disbursements/grantCommittedTimeCycle.json'; import GrantDetailTimeCycleFieldsMapping from '../config/mapping/disbursements/grantDetailTimeCycle.json'; @@ -270,7 +271,19 @@ export class DisbursementsController { this.req.query, LineChartFieldsMapping.urlParams, ); + const filterString2 = filterFinancialIndicators( + this.req.query, + LineChartFieldsMapping.activitiesCountUrlParams, + ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + + const activitiesCount = await axios + .get(url2) + .then((resp: AxiosResponse) => + _.get(resp.data, LineChartFieldsMapping.count, 0), + ) + .catch(() => 0); return axios .get(url) @@ -296,6 +309,7 @@ export class DisbursementsController { }, })), xAxisKeys: Object.keys(years), + activitiesCount, }; }) .catch(handleDataApiError); @@ -353,6 +367,48 @@ export class DisbursementsController { .catch(handleDataApiError); } + @get('/disbursements/cycles') + @response(200) + async cycles() { + return axios + .get( + `${urls.FINANCIAL_INDICATORS}${DisbursementsCyclesMapping.urlParams}`, + ) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + DisbursementsCyclesMapping.dataPath, + [], + ); + + const data = _.map( + _.filter( + rawData, + item => + _.get(item, DisbursementsCyclesMapping.cycleFrom, null) !== null, + ), + (item, index) => { + const from = _.get(item, DisbursementsCyclesMapping.cycleFrom, ''); + const to = _.get(item, DisbursementsCyclesMapping.cycleTo, ''); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: `Cycle ${index + 1}`, + value, + }; + }, + ); + + return {data}; + }) + .catch(handleDataApiError); + } + // v2 // Time/Cycle diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index 8c531ab..c703dfd 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -3,6 +3,7 @@ import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import ExpendituresBarChartMapping from '../config/mapping/expenditures/bar.json'; +import ExpendituresCyclesMapping from '../config/mapping/expenditures/cycles.json'; import ExpendituresHeatmapMapping from '../config/mapping/expenditures/heatmap.json'; import ExpendituresTableMapping from '../config/mapping/expenditures/table.json'; import urls from '../config/urls/index.json'; @@ -312,4 +313,37 @@ export class ExpendituresController { }) .catch(handleDataApiError); } + + @get('/expenditures/cycles') + @response(200) + async cycles() { + return axios + .get(`${urls.FINANCIAL_INDICATORS}${ExpendituresCyclesMapping.urlParams}`) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + ExpendituresCyclesMapping.dataPath, + [], + ); + + const data = _.map(rawData, (item, index) => { + const from = _.get(item, ExpendituresCyclesMapping.cycleFrom, ''); + const to = _.get(item, ExpendituresCyclesMapping.cycleTo, ''); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: `Cycle ${index + 1}`, + value, + }; + }); + + return {data}; + }) + .catch(handleDataApiError); + } } diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index e95a73f..954fa05 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -6,6 +6,7 @@ import {mapTransform} from 'map-transform'; import moment from 'moment'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; +import CyclesMapping from '../config/mapping/fundingrequests/cycles.json'; import Table2FieldsMapping from '../config/mapping/fundingrequests/table-2.json'; import TableFieldsMapping from '../config/mapping/fundingrequests/table.json'; import urls from '../config/urls/index.json'; @@ -89,6 +90,35 @@ export class FundingRequestsController { .catch(handleDataApiError); } + @get('/funding-requests/cycles') + @response(200) + async cycles() { + return axios + .get(`${urls.FINANCIAL_INDICATORS}${CyclesMapping.urlParams}`) + .then((resp: AxiosResponse) => { + const rawData = _.get(resp.data, CyclesMapping.dataPath, []); + + const data = _.map(rawData, (item, index) => { + const from = _.get(item, CyclesMapping.cycleFrom, ''); + const to = _.get(item, CyclesMapping.cycleTo, ''); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: `Cycle ${index + 1}`, + value, + }; + }); + + return {data}; + }) + .catch(handleDataApiError); + } + // v2 @get('/funding-requests/table') diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index 534aaf6..9df9249 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -12,6 +12,7 @@ import _, {orderBy} from 'lodash'; import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; import PledgesContributionsBarFieldsMapping from '../config/mapping/pledgescontributions/bar.json'; +import PledgesContributionsCyclesFieldsMapping from '../config/mapping/pledgescontributions/cycles.json'; import PledgesContributionsGeoFieldsMapping from '../config/mapping/pledgescontributions/geo.json'; import PledgesContributionsStatsFieldsMapping from '../config/mapping/pledgescontributions/stats.json'; import PledgesContributionsSunburstFieldsMapping from '../config/mapping/pledgescontributions/sunburst.json'; @@ -431,6 +432,49 @@ export class PledgescontributionsController { return getBarData([url1, url2]); } + @get('/pledges-contributions/cycles') + @response(200) + async cycles() { + return axios + .get( + `${urls.FINANCIAL_INDICATORS}${PledgesContributionsCyclesFieldsMapping.urlParams}`, + ) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + PledgesContributionsCyclesFieldsMapping.dataPath, + [], + ); + + const data = _.map(rawData, (item, index) => { + const from = _.get( + item, + PledgesContributionsCyclesFieldsMapping.cycleFrom, + '', + ); + const to = _.get( + item, + PledgesContributionsCyclesFieldsMapping.cycleTo, + '', + ); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: `Cycle ${index + 1}`, + value, + }; + }); + + return {data}; + }) + .catch(handleDataApiError); + } + // v2 @get('/pledges-contributions/time-cycle') diff --git a/src/controllers/results.controller.ts b/src/controllers/results.controller.ts index 801c30b..eb16411 100644 --- a/src/controllers/results.controller.ts +++ b/src/controllers/results.controller.ts @@ -10,6 +10,7 @@ import { import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import {mapTransform} from 'map-transform'; +import ResultsCyclesMappingFields from '../config/mapping/results/cycles.json'; import resultsMap from '../config/mapping/results/index.json'; import ResultsTableLocationMappingFields from '../config/mapping/results/location-table.json'; import ResultsPolylineMappingFields from '../config/mapping/results/polyline.json'; @@ -261,6 +262,41 @@ export class ResultsController { .catch(handleDataApiError); } + @get('/results/cycles') + @response(200) + async cycles() { + return axios + .get( + `${urls.PROGRAMMATIC_INDICATORS}${ResultsCyclesMappingFields.urlParams}`, + ) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + ResultsCyclesMappingFields.dataPath, + [], + ); + + const data = _.map(rawData, (item, index) => { + const from = _.get(item, ResultsCyclesMappingFields.cycleFrom, ''); + const to = _.get(item, ResultsCyclesMappingFields.cycleTo, ''); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: value, + value, + }; + }); + + return {data}; + }) + .catch(handleDataApiError); + } + // v2 @get('/results') diff --git a/src/utils/filtering/grants.ts b/src/utils/filtering/grants.ts index a946512..d37a16c 100644 --- a/src/utils/filtering/grants.ts +++ b/src/utils/filtering/grants.ts @@ -9,7 +9,7 @@ const MAPPING = { startDate: 'periodStartDate', endDate: 'periodEndDate', search: - 'contains(geography/name,) OR contains(activityArea/name,) OR contains(principalRecipient/name,) OR contains(status/statusName,)', + 'contains(geography/name,) OR contains(activityArea/name,) OR contains(principalRecipient/name,) OR contains(status/statusName,) OR contains(code,)', }; export function filterGrants( From ade7a4d80e9217b63c70e56c177d9a826da744f7 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 28 May 2024 15:53:55 +0300 Subject: [PATCH 04/44] feat: filtering logic --- src/config/mapping/budgets/breakdown.json | 4 +- src/config/mapping/budgets/metrics.json | 4 +- src/config/mapping/budgets/sankey.json | 6 +- src/config/mapping/budgets/table.json | 6 +- src/config/mapping/budgets/treemap.json | 4 +- .../mapping/disbursements/barChart.json | 4 +- .../mapping/disbursements/lineChart.json | 4 +- src/config/mapping/disbursements/table.json | 4 +- src/config/mapping/eligibility/table.json | 2 +- src/config/mapping/expenditures/bar.json | 6 +- src/config/mapping/expenditures/heatmap.json | 2 +- src/config/mapping/expenditures/table.json | 6 +- .../filter-options/principal-recipients.json | 6 +- .../filter-options/results-components.json | 7 + src/config/mapping/filter-options/status.json | 5 + src/config/urls/index.json | 6 +- src/controllers/budgets.controller.ts | 83 +- src/controllers/disbursements.controller.ts | 50 +- src/controllers/expenditures.controller.ts | 51 +- src/controllers/filteroptions.controller.ts | 413 +++++---- .../pledgescontributions.controller.ts | 6 +- src/interfaces/filters.ts | 4 +- src/static-assets/locations.json | 854 ++++++++++++++++++ .../data-themes/getDatasetFilterOptions.ts | 2 +- src/utils/filtering/donors.ts | 10 +- src/utils/filtering/eligibility.ts | 47 +- src/utils/filtering/financialIndicators.ts | 71 +- src/utils/filtering/fundingRequests.ts | 39 +- src/utils/filtering/geographies.ts | 28 + src/utils/filtering/grants.ts | 39 +- src/utils/filtering/programmaticIndicators.ts | 25 +- 31 files changed, 1507 insertions(+), 291 deletions(-) create mode 100644 src/config/mapping/filter-options/results-components.json create mode 100644 src/config/mapping/filter-options/status.json create mode 100644 src/static-assets/locations.json create mode 100644 src/utils/filtering/geographies.ts diff --git a/src/config/mapping/budgets/breakdown.json b/src/config/mapping/budgets/breakdown.json index 18a3009..20ea375 100644 --- a/src/config/mapping/budgets/breakdown.json +++ b/src/config/mapping/budgets/breakdown.json @@ -1,7 +1,7 @@ { "dataPath": "value", - "name": "activityAreaGroup.parent.parent.name", + "name": ".parent.parent.name", "value": "value", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/parent/name),aggregate(plannedAmount with sum as value))", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/parent/name),aggregate(plannedAmount with sum as value))", "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#F3F5F4"] } diff --git a/src/config/mapping/budgets/metrics.json b/src/config/mapping/budgets/metrics.json index 2a7307e..cd88850 100644 --- a/src/config/mapping/budgets/metrics.json +++ b/src/config/mapping/budgets/metrics.json @@ -11,6 +11,6 @@ "budgetValue": "plannedCumulative", "disbursementValue": "actualCumulative", "cashBalanceValue": "actual", - "urlParams": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))", - "urlParamsOrganisations": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName,implementationPeriod/grant/principalRecipient/type/name,implementationPeriod/grant/principalRecipient/name),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))" + "urlParams": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))", + "urlParamsOrganisations": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName,implementationPeriod/grant/principalRecipient/type/name,implementationPeriod/grant/principalRecipient/name),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))" } diff --git a/src/config/mapping/budgets/sankey.json b/src/config/mapping/budgets/sankey.json index 94fef84..a060d46 100644 --- a/src/config/mapping/budgets/sankey.json +++ b/src/config/mapping/budgets/sankey.json @@ -1,9 +1,9 @@ { "dataPath": "value", - "level1Field": "activityAreaGroup.parent.name", - "level2Field": "activityAreaGroup.name", + "level1Field": ".parent.name", + "level2Field": ".name", "valueField": "value", "cycle": "periodFrom", "nodeColors": ["#252C34", "#252C34", "#252C34"], - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/name,activityAreaGroup/name),aggregate(plannedAmount with sum as value))" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/name,/name),aggregate(plannedAmount with sum as value))" } diff --git a/src/config/mapping/budgets/table.json b/src/config/mapping/budgets/table.json index b1fdf46..0e1b4f6 100644 --- a/src/config/mapping/budgets/table.json +++ b/src/config/mapping/budgets/table.json @@ -1,9 +1,9 @@ { "dataPath": "value", - "parentField": "activityAreaGroup.parent.name", - "childrenField": "activityAreaGroup.name", + "parentField": ".parent.name", + "childrenField": ".name", "valueField": "value", "cycle": "periodFrom", "countField": "count", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/name,activityAreaGroup/name),aggregate(plannedAmount with sum as value,implementationPeriod/grantId with countdistinct as count))" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/name,/name),aggregate(plannedAmount with sum as value,implementationPeriod/grantId with countdistinct as count))" } diff --git a/src/config/mapping/budgets/treemap.json b/src/config/mapping/budgets/treemap.json index 605947d..b7b2355 100644 --- a/src/config/mapping/budgets/treemap.json +++ b/src/config/mapping/budgets/treemap.json @@ -1,9 +1,9 @@ { "dataPath": "value", "cycle": "periodFrom", - "name": "activityAreaGroup.parent.parent.name", + "name": ".parent.parent.name", "value": "value", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((activityAreaGroup/parent/parent/name),aggregate(plannedAmount with sum as value))", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/parent/name),aggregate(plannedAmount with sum as value))", "textbgcolors": [ { "color": "#0A2840", diff --git a/src/config/mapping/disbursements/barChart.json b/src/config/mapping/disbursements/barChart.json index 65b6061..b346db4 100644 --- a/src/config/mapping/disbursements/barChart.json +++ b/src/config/mapping/disbursements/barChart.json @@ -1,8 +1,8 @@ { "dataPath": "value", - "name": "activityArea.name", + "name": ".name", "value": "value", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((activityArea/name),aggregate(actualAmount with sum as value))&$orderby=value desc", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((/name),aggregate(actualAmount with sum as value))&$orderby=value desc", "biggerBarColor": "#00B5AE", "barColor": "#013E77" } diff --git a/src/config/mapping/disbursements/lineChart.json b/src/config/mapping/disbursements/lineChart.json index aa0df34..fc9a4f2 100644 --- a/src/config/mapping/disbursements/lineChart.json +++ b/src/config/mapping/disbursements/lineChart.json @@ -3,8 +3,8 @@ "count": "@odata.count", "cycle": "periodFrom", "value": "value", - "line": "activityArea.name", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,activityArea/name),aggregate(actualAmount with sum as value))", + "line": ".name", + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,/name),aggregate(actualAmount with sum as value))", "activitiesCountUrlParams": "?$count=true&$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/grant/code))", "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD"] } diff --git a/src/config/mapping/disbursements/table.json b/src/config/mapping/disbursements/table.json index 949a060..7e61a53 100644 --- a/src/config/mapping/disbursements/table.json +++ b/src/config/mapping/disbursements/table.json @@ -4,8 +4,8 @@ "signedIndicator": "Total Signed Amount - Reference Rate", "commitmentIndicator": "Total Commitment Amount - Reference Rate", "disbursementIndicator": "Total Disbursed Amount - Reference Rate", - "component": "activityArea.name", + "component": ".name", "grants": "count", "valueField": "value", - "urlParams": "?$apply=filter(indicatorName in ('Total Signed Amount - Reference Rate','Total Commitment Amount - Reference Rate','Total Disbursed Amount - Reference Rate'))/groupby((activityArea/name,indicatorName),aggregate(actualAmount with sum as value,implementationPeriod/grantId with countdistinct as count))&$orderby=value desc" + "urlParams": "?$apply=filter(indicatorName in ('Total Signed Amount - Reference Rate','Total Commitment Amount - Reference Rate','Total Disbursed Amount - Reference Rate'))/groupby((/name,indicatorName),aggregate(actualAmount with sum as value,implementationPeriod/grantId with countdistinct as count))&$orderby=value desc" } diff --git a/src/config/mapping/eligibility/table.json b/src/config/mapping/eligibility/table.json index 6f435ac..09ee1a4 100644 --- a/src/config/mapping/eligibility/table.json +++ b/src/config/mapping/eligibility/table.json @@ -11,5 +11,5 @@ "notEligible": "Not Eligible", "transitionFunding": "Transition Funding" }, - "urlParams": "?$apply=/groupby((geography/name,eligibilityYear,activityArea/name,isEligible,incomeLevel,diseaseBurden))&$orderby=geography/name,activityArea/name" + "urlParams": "?$apply=filter()/groupby((geography/name,eligibilityYear,activityArea/name,isEligible,incomeLevel,diseaseBurden))&$orderby=geography/name,activityArea/name" } diff --git a/src/config/mapping/expenditures/bar.json b/src/config/mapping/expenditures/bar.json index 4ed090d..7a076c3 100644 --- a/src/config/mapping/expenditures/bar.json +++ b/src/config/mapping/expenditures/bar.json @@ -1,8 +1,8 @@ { "dataPath": "value", - "name": "activityArea.parent.parent.name", - "itemName": "activityArea.parent.name", + "name": ".parent.parent.name", + "itemName": ".parent.name", "indicatorName": "indicatorName", "value": "actualCumulative", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,activityArea/parent/name,activityArea/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative))" + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/parent/name,/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative))" } diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index 9d12de3..5dddb53 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -7,6 +7,6 @@ "fields": { "principalRecipient": "implementationPeriod/grant/principalRecipient/name", "principalRecipientType": "implementationPeriod/grant/principalRecipient/type/name", - "component": "implementationPeriod/grant/activityArea/name" + "component": "/parent/parent/name" } } diff --git a/src/config/mapping/expenditures/table.json b/src/config/mapping/expenditures/table.json index 8a2a030..5e1e1aa 100644 --- a/src/config/mapping/expenditures/table.json +++ b/src/config/mapping/expenditures/table.json @@ -1,9 +1,9 @@ { "dataPath": "value", - "name": "activityArea.parent.parent.name", - "itemName": "activityArea.parent.name", + "name": ".parent.parent.name", + "itemName": ".parent.name", "indicatorName": "indicatorName", "cumulativeExpenditureValue": "actualCumulative", "periodExpenditureValue": "actual", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,activityArea/parent/name,activityArea/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/parent/name,/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" } diff --git a/src/config/mapping/filter-options/principal-recipients.json b/src/config/mapping/filter-options/principal-recipients.json index 50642d1..7d02cbf 100644 --- a/src/config/mapping/filter-options/principal-recipients.json +++ b/src/config/mapping/filter-options/principal-recipients.json @@ -1,5 +1,7 @@ { "dataPath": "value", - "label": "name", - "value": "name" + "label": "principalRecipient.name", + "value": "principalRecipient.name", + "type": "principalRecipient.type.name", + "typeCode": "principalRecipient.type.code" } diff --git a/src/config/mapping/filter-options/results-components.json b/src/config/mapping/filter-options/results-components.json new file mode 100644 index 0000000..591da5d --- /dev/null +++ b/src/config/mapping/filter-options/results-components.json @@ -0,0 +1,7 @@ +{ + "dataPath": "value", + "label": "activityArea.name", + "value": "activityArea.name", + "subItemLabel": "indicatorName", + "subItemValue": "indicatorName" +} diff --git a/src/config/mapping/filter-options/status.json b/src/config/mapping/filter-options/status.json new file mode 100644 index 0000000..98fd413 --- /dev/null +++ b/src/config/mapping/filter-options/status.json @@ -0,0 +1,5 @@ +{ + "dataPath": "value", + "label": "statusName", + "value": "statusName" +} diff --git a/src/config/urls/index.json b/src/config/urls/index.json index 6950421..85a7a5f 100644 --- a/src/config/urls/index.json +++ b/src/config/urls/index.json @@ -34,9 +34,11 @@ "multicountriescountriesdata": "https://fetch.theglobalfund.org/v3.4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", "FILTER_OPTIONS_GEOGRAPHY": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", "FILTER_OPTIONS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=type eq 'Component'", - "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))", + "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc", "FILTER_OPTIONS_DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors?$apply=groupby((id,name,type/name))", - "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/OrganizationTypes?$filter=group eq 'PrincipalRecipients'&$expand=children", + "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$apply=groupby((principalRecipient/type/name,principalRecipient/type/code,principalRecipient/name))", + "FILTER_OPTIONS_RESULTS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allProgrammaticIndicators?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((indicatorName,activityArea/name))", + "FILTER_OPTIONS_STATUS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Statuses", "FINANCIAL_INDICATORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators", "DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors", "PROGRAMMATIC_INDICATORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allProgrammaticIndicators", diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 3140556..9ad02a5 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -123,12 +123,15 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/sankey') + @get('/budgets/sankey/{componentField}') @response(200) - async sankey() { + async sankey(@param.path.string('componentField') componentField: string) { const filterString = filterFinancialIndicators( this.req.query, - BudgetsSankeyFieldsMapping.urlParams, + BudgetsSankeyFieldsMapping.urlParams.replace( + //g, + componentField, + ), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -154,7 +157,10 @@ export class BudgetsController { }; const groupedDataLevel1 = _.groupBy( rawData, - BudgetsSankeyFieldsMapping.level1Field, + BudgetsSankeyFieldsMapping.level1Field.replace( + '', + componentField, + ), ); _.forEach(groupedDataLevel1, (level1Data, level1) => { data.nodes.push({ @@ -171,7 +177,10 @@ export class BudgetsController { }); const groupedDataLevel2 = _.groupBy( level1Data, - BudgetsSankeyFieldsMapping.level2Field, + BudgetsSankeyFieldsMapping.level2Field.replace( + '', + componentField, + ), ); _.forEach(groupedDataLevel2, (level2Data, level2) => { const level2inLevel1 = _.find(data.nodes, { @@ -207,12 +216,15 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/treemap') + @get('/budgets/treemap/{componentField}') @response(200) - async treemap() { + async treemap(@param.path.string('componentField') componentField: string) { const filterString = filterFinancialIndicators( this.req.query, - BudgetsTreemapFieldsMapping.urlParams, + BudgetsTreemapFieldsMapping.urlParams.replace( + '', + componentField, + ), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -222,7 +234,14 @@ export class BudgetsController { return { data: _.get(resp.data, BudgetsTreemapFieldsMapping.dataPath, []).map( (item: any, index: number) => ({ - name: _.get(item, BudgetsTreemapFieldsMapping.name, ''), + name: _.get( + item, + BudgetsTreemapFieldsMapping.name.replace( + '', + componentField, + ), + '', + ), value: _.get(item, BudgetsTreemapFieldsMapping.value, 0), itemStyle: { color: _.get( @@ -247,12 +266,15 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/table') + @get('/budgets/table/{componentField}') @response(200) - async table() { + async table(@param.path.string('componentField') componentField: string) { const filterString = filterFinancialIndicators( this.req.query, - BudgetsTableFieldsMapping.urlParams, + BudgetsTableFieldsMapping.urlParams.replace( + //g, + componentField, + ), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -266,7 +288,10 @@ export class BudgetsController { ); const groupedByParent = _.groupBy( rawData, - BudgetsTableFieldsMapping.parentField, + BudgetsTableFieldsMapping.parentField.replace( + '', + componentField, + ), ); const data: { @@ -283,7 +308,14 @@ export class BudgetsController { _.forEach(groupedByParent, (parentData, parent) => { const children = parentData.map((child: any) => { return { - name: _.get(child, BudgetsTableFieldsMapping.childrenField, ''), + name: _.get( + child, + BudgetsTableFieldsMapping.childrenField.replace( + '', + componentField, + ), + '', + ), grants: _.get(child, BudgetsTableFieldsMapping.countField, 0), amount: _.get(child, BudgetsTableFieldsMapping.valueField, 0), }; @@ -336,9 +368,12 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/breakdown/{cycle}') + @get('/budgets/breakdown/{cycle}/{componentField}') @response(200) - async breakdown(@param.path.string('cycle') cycle: string) { + async breakdown( + @param.path.string('cycle') cycle: string, + @param.path.string('componentField') componentField: string, + ) { const years = cycle.split('-'); const filterString = filterFinancialIndicators( { @@ -346,7 +381,10 @@ export class BudgetsController { years: years[0], yearsTo: years[1], }, - BudgetsBreakdownFieldsMapping.urlParams, + BudgetsBreakdownFieldsMapping.urlParams.replace( + //g, + componentField, + ), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -360,9 +398,16 @@ export class BudgetsController { ); const total = _.sumBy(raw, BudgetsBreakdownFieldsMapping.value); const data = raw.map((item: any) => ({ - name: _.get(item, BudgetsRadialFieldsMapping.name, ''), + name: _.get( + item, + BudgetsBreakdownFieldsMapping.name.replace( + '', + componentField, + ), + '', + ), value: - (_.get(item, BudgetsRadialFieldsMapping.value, 0) / total) * 100, + (_.get(item, BudgetsBreakdownFieldsMapping.value, 0) / total) * 100, color: '', })); return { diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index 73bdd14..97cb82e 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -1,6 +1,7 @@ import {inject} from '@loopback/core'; import { get, + param, Request, response, ResponseObject, @@ -234,12 +235,15 @@ export class DisbursementsController { .catch(handleDataApiError); } - @get('/disbursements/bar-chart') + @get('/disbursements/bar-chart/{componentField}') @response(200) - async barChart() { + async barChart(@param.path.string('componentField') componentField: string) { const filterString = filterFinancialIndicators( this.req.query, - BarChartFieldsMapping.urlParams, + BarChartFieldsMapping.urlParams.replace( + '', + componentField, + ), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -250,7 +254,14 @@ export class DisbursementsController { return { data: raw.map((item: any, index: number) => ({ - name: _.get(item, BarChartFieldsMapping.name, ''), + name: _.get( + item, + BarChartFieldsMapping.name.replace( + '', + componentField, + ), + '', + ), value: _.get(item, BarChartFieldsMapping.value, 0), itemStyle: { color: @@ -264,12 +275,15 @@ export class DisbursementsController { .catch(handleDataApiError); } - @get('/disbursements/line-chart') + @get('/disbursements/line-chart/{componentField}') @response(200) - async lineChart() { + async lineChart(@param.path.string('componentField') componentField: string) { const filterString = filterFinancialIndicators( this.req.query, - LineChartFieldsMapping.urlParams, + LineChartFieldsMapping.urlParams.replace( + '', + componentField, + ), ); const filterString2 = filterFinancialIndicators( this.req.query, @@ -289,7 +303,13 @@ export class DisbursementsController { .get(url) .then((resp: AxiosResponse) => { const raw = _.get(resp.data, LineChartFieldsMapping.dataPath, []); - const groupedByLine = _.groupBy(raw, LineChartFieldsMapping.line); + const groupedByLine = _.groupBy( + raw, + LineChartFieldsMapping.line.replace( + '', + componentField, + ), + ); const lines = Object.keys(groupedByLine); const years = _.groupBy(raw, LineChartFieldsMapping.cycle); return { @@ -315,12 +335,12 @@ export class DisbursementsController { .catch(handleDataApiError); } - @get('/disbursements/table') + @get('/disbursements/table/{componentField}') @response(200) - async table() { + async table(@param.path.string('componentField') componentField: string) { const filterString = filterFinancialIndicators( this.req.query, - TableFieldsMapping.urlParams, + TableFieldsMapping.urlParams.replace('', componentField), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -329,7 +349,13 @@ export class DisbursementsController { .then((resp: AxiosResponse) => { const raw = _.get(resp.data, TableFieldsMapping.dataPath, []); - const groupedByComponent = _.groupBy(raw, TableFieldsMapping.component); + const groupedByComponent = _.groupBy( + raw, + TableFieldsMapping.component.replace( + '', + componentField, + ), + ); return { data: _.map(groupedByComponent, (items, component) => { diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index c703dfd..adf2651 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -13,11 +13,12 @@ import {filterFinancialIndicators} from '../utils/filtering/financialIndicators' export class ExpendituresController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - @get('/expenditures/heatmap/{row}/{column}') + @get('/expenditures/heatmap/{row}/{column}/{componentField}') @response(200) async heatmap( @param.path.string('row') row: string, @param.path.string('column') column: string, + @param.path.string('componentField') componentField: string, ) { let filterString = ExpendituresHeatmapMapping.urlParams; let rowField = ''; @@ -58,7 +59,7 @@ export class ExpendituresController { ExpendituresHeatmapMapping, `fields["${column}"]`, ExpendituresHeatmapMapping.fields.component, - ); + ).replace(//g, componentField); } filterString = filterString.replace( '', @@ -228,12 +229,17 @@ export class ExpendituresController { .catch(handleDataApiError); } - @get('/expenditures/expandable-bar') + @get('/expenditures/expandable-bar/{componentField}') @response(200) - async expandableBar() { + async expandableBar( + @param.path.string('componentField') componentField: string, + ) { const filterString = filterFinancialIndicators( this.req.query, - ExpendituresBarChartMapping.urlParams, + ExpendituresBarChartMapping.urlParams.replace( + //g, + componentField, + ), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -242,12 +248,21 @@ export class ExpendituresController { .then((resp: AxiosResponse) => { const raw = _.get(resp.data, ExpendituresBarChartMapping.dataPath, []); - const groupedByName = _.groupBy(raw, ExpendituresBarChartMapping.name); + const groupedByName = _.groupBy( + raw, + ExpendituresBarChartMapping.name.replace( + //g, + componentField, + ), + ); const data = _.map(groupedByName, (value, key) => { const groupedByItem = _.groupBy( value, - ExpendituresBarChartMapping.itemName, + ExpendituresBarChartMapping.itemName.replace( + //g, + componentField, + ), ); return { name: key, @@ -264,12 +279,15 @@ export class ExpendituresController { .catch(handleDataApiError); } - @get('/expenditures/table') + @get('/expenditures/table/{componentField}') @response(200) - async table() { + async table(@param.path.string('componentField') componentField: string) { const filterString = filterFinancialIndicators( this.req.query, - ExpendituresTableMapping.urlParams, + ExpendituresTableMapping.urlParams.replace( + //g, + componentField, + ), ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -278,12 +296,21 @@ export class ExpendituresController { .then((resp: AxiosResponse) => { const raw = _.get(resp.data, ExpendituresTableMapping.dataPath, []); - const groupedByName = _.groupBy(raw, ExpendituresTableMapping.name); + const groupedByName = _.groupBy( + raw, + ExpendituresTableMapping.name.replace( + //g, + componentField, + ), + ); const data = _.map(groupedByName, (value, key) => { const groupedByItem = _.groupBy( value, - ExpendituresTableMapping.itemName, + ExpendituresTableMapping.itemName.replace( + //g, + componentField, + ), ); return { name: key, diff --git a/src/controllers/filteroptions.controller.ts b/src/controllers/filteroptions.controller.ts index aff991b..2c924de 100644 --- a/src/controllers/filteroptions.controller.ts +++ b/src/controllers/filteroptions.controller.ts @@ -11,7 +11,10 @@ import _ from 'lodash'; import ComponentMapping from '../config/mapping/filter-options/components.json'; import DonorMapping from '../config/mapping/filter-options/donors.json'; import GeographyMapping from '../config/mapping/filter-options/geography.json'; +import PrincipalRecipientMapping from '../config/mapping/filter-options/principal-recipients.json'; import ReplenishmentPeriodMapping from '../config/mapping/filter-options/replenishment-periods.json'; +import ResultsComponentMapping from '../config/mapping/filter-options/results-components.json'; +import StatusMapping from '../config/mapping/filter-options/status.json'; import mappingComponents from '../config/mapping/filteroptions/components.json'; import mappingDonors from '../config/mapping/filteroptions/donors.json'; import mappingLocations from '../config/mapping/filteroptions/locations.json'; @@ -37,23 +40,23 @@ const FILTER_OPTIONS_RESPONSE: ResponseObject = { items: { type: 'object', properties: { - label: {type: 'string'}, + name: {type: 'string'}, value: {type: 'string'}, - subOptions: { + options: { type: 'array', items: { type: 'object', properties: { - label: {type: 'string'}, + name: {type: 'string'}, value: {type: 'string'}, - subOptions: { + options: { type: 'array', items: { type: 'object', properties: { - label: {type: 'string'}, + name: {type: 'string'}, value: {type: 'string'}, - subOptions: { + options: { type: 'array', items: {}, }, @@ -89,26 +92,26 @@ export class FilteroptionsController { const data: FilterGroupOption[] = []; rawData.forEach((item1: any) => { - const subOptions = _.get(item1, GeographyMapping.children, []); + const options = _.get(item1, GeographyMapping.children, []); data.push({ - label: _.get(item1, GeographyMapping.label, ''), + name: _.get(item1, GeographyMapping.label, ''), value: _.get(item1, GeographyMapping.value, ''), extraInfo: { isDonor: _.get(item1, GeographyMapping.isDonor, false), isRecipient: _.get(item1, GeographyMapping.isRecipient, false), level: _.get(item1, GeographyMapping.level, undefined), }, - subOptions: - subOptions && subOptions.length > 0 + options: + options && options.length > 0 ? _.orderBy( - subOptions.map((item2: any) => { - const item2SubOptions = _.get( + options.map((item2: any) => { + const item2options = _.get( item2, GeographyMapping.children, [], ); return { - label: _.get(item2, GeographyMapping.label, ''), + name: _.get(item2, GeographyMapping.label, ''), value: _.get(item2, GeographyMapping.value, ''), extraInfo: { isDonor: _.get( @@ -127,17 +130,17 @@ export class FilteroptionsController { undefined, ), }, - subOptions: - item2SubOptions && item2SubOptions.length > 0 + options: + item2options && item2options.length > 0 ? _.orderBy( - item2SubOptions.map((item3: any) => { - const item3SubOptions = _.get( + item2options.map((item3: any) => { + const item3options = _.get( item3, GeographyMapping.children, [], ); return { - label: _.get( + name: _.get( item3, GeographyMapping.label, '', @@ -164,56 +167,53 @@ export class FilteroptionsController { undefined, ), }, - subOptions: - item3SubOptions && - item3SubOptions.length > 0 + options: + item3options && item3options.length > 0 ? _.orderBy( - item3SubOptions.map( - (item4: any) => { - return { - label: _.get( + item3options.map((item4: any) => { + return { + name: _.get( + item4, + GeographyMapping.label, + '', + ), + value: _.get( + item4, + GeographyMapping.value, + '', + ), + extraInfo: { + isDonor: _.get( item4, - GeographyMapping.label, - '', + GeographyMapping.isDonor, + false, ), - value: _.get( + isRecipient: _.get( item4, - GeographyMapping.value, - '', + GeographyMapping.isRecipient, + false, ), - extraInfo: { - isDonor: _.get( - item4, - GeographyMapping.isDonor, - false, - ), - isRecipient: _.get( - item4, - GeographyMapping.isRecipient, - false, - ), - level: _.get( - item4, - GeographyMapping.level, - undefined, - ), - }, - }; - }, - ), - 'label', + level: _.get( + item4, + GeographyMapping.level, + undefined, + ), + }, + }; + }), + 'name', 'asc', ) : undefined, }; }), - 'label', + 'name', 'asc', ) : undefined, }; }), - 'label', + 'name', 'asc', ) : undefined, @@ -221,8 +221,11 @@ export class FilteroptionsController { }); return { - name: 'Geography', - options: _.orderBy(data, 'label', 'asc'), + data: { + id: 'geography', + name: 'Geography', + options: _.orderBy(data, 'name', 'asc'), + }, }; }) .catch(handleDataApiError); @@ -239,11 +242,18 @@ export class FilteroptionsController { const rawData = _.get(resp.data, ComponentMapping.dataPath, []); return { - name: 'Component', - options: rawData.map((item: any) => ({ - label: _.get(item, ComponentMapping.label, ''), - value: _.get(item, ComponentMapping.value, ''), - })), + data: { + id: 'component', + name: 'Component', + options: _.orderBy( + rawData.map((item: any) => ({ + name: _.get(item, ComponentMapping.label, ''), + value: _.get(item, ComponentMapping.value, ''), + })), + 'name', + 'asc', + ), + }, }; }) .catch(handleDataApiError); @@ -264,15 +274,18 @@ export class FilteroptionsController { ); return { - name: 'Replenishment period', - options: _.orderBy( - rawData.map((item: any) => ({ - label: _.get(item, ReplenishmentPeriodMapping.label, ''), - value: _.get(item, ReplenishmentPeriodMapping.value, ''), - })), - 'label', - 'asc', - ), + data: { + id: 'replenishmentPeriod', + name: 'Replenishment period', + options: _.orderBy( + rawData.map((item: any) => ({ + name: _.get(item, ReplenishmentPeriodMapping.label, ''), + value: _.get(item, ReplenishmentPeriodMapping.value, ''), + })), + 'label', + 'asc', + ), + }, }; }) .catch(handleDataApiError); @@ -292,10 +305,10 @@ export class FilteroptionsController { _.map(_.groupBy(rawData, DonorMapping.type), (donors, type) => { const typeOptions: FilterGroupOption = { - label: type, + name: type, value: type, - subOptions: donors.map((donor: any) => ({ - label: donor[DonorMapping.label], + options: donors.map((donor: any) => ({ + name: donor[DonorMapping.label], value: donor[DonorMapping.value], })), }; @@ -304,8 +317,11 @@ export class FilteroptionsController { }); return { - name: 'Donor', - options: _.orderBy(options, 'label', 'asc'), + data: { + id: 'donor', + name: 'Donor', + options: _.orderBy(options, 'name', 'asc'), + }, }; }) .catch(handleDataApiError); @@ -318,7 +334,102 @@ export class FilteroptionsController { return axios .get(url) - .then((resp: AxiosResponse) => {}) + .then((resp: AxiosResponse) => { + const rawData = _.get( + resp.data, + PrincipalRecipientMapping.dataPath, + [], + ); + + const options: FilterGroupOption[] = []; + + _.map( + _.groupBy(rawData, PrincipalRecipientMapping.type), + (prs, type) => { + const typeOptions: FilterGroupOption = { + name: type, + value: _.get(prs[0], PrincipalRecipientMapping.typeCode, ''), + options: prs.map((pr: any) => ({ + name: _.get(pr, PrincipalRecipientMapping.label, ''), + value: _.get(pr, PrincipalRecipientMapping.value, ''), + })), + }; + + options.push(typeOptions); + }, + ); + + return { + data: { + id: 'principalRecipient', + name: 'Principal Recipient', + options: _.orderBy(options, 'name', 'asc'), + }, + }; + }) + .catch(handleDataApiError); + } + + @get('/filter-options/status') + @response(200) + async filterOptionsStatus() { + const url = urls.FILTER_OPTIONS_STATUS; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get(resp.data, StatusMapping.dataPath, []); + + const options: FilterGroupOption[] = rawData.map((item: any) => ({ + name: _.get(item, StatusMapping.label, ''), + value: _.get(item, StatusMapping.value, ''), + })); + + return { + data: { + id: 'status', + name: 'Status', + options: _.orderBy(options, 'name', 'asc'), + }, + }; + }) + .catch(handleDataApiError); + } + + @get('/filter-options/results/components') + @response(200) + async filterOptionsResultComponents() { + const url = urls.FILTER_OPTIONS_RESULTS_COMPONENTS; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get(resp.data, ResultsComponentMapping.dataPath, []); + + const groupedByComponent = _.groupBy( + rawData, + ResultsComponentMapping.label, + ); + + return { + data: { + id: 'component', + name: 'Components & Indicators', + options: _.orderBy( + _.map(groupedByComponent, (items, key) => ({ + name: key, + value: _.get(items[0], ResultsComponentMapping.value, ''), + options: items.map((item: any) => ({ + name: _.get(item, ResultsComponentMapping.subItemLabel, ''), + value: _.get(item, ResultsComponentMapping.subItemValue, ''), + })), + })), + 'name', + 'asc', + ), + }, + }; + }) .catch(handleDataApiError); } @@ -336,33 +447,33 @@ export class FilteroptionsController { const data: FilterGroupOption[] = []; rawData.forEach((item1: any) => { - const subOptions = _.get(item1, mappingLocations.children, []); + const options = _.get(item1, mappingLocations.children, []); data.push({ - label: _.get(item1, mappingLocations.label, ''), + name: _.get(item1, mappingLocations.label, ''), value: _.get(item1, mappingLocations.value, ''), - subOptions: - subOptions && subOptions.length > 0 + options: + options && options.length > 0 ? _.orderBy( - subOptions.map((item2: any) => { - const item2SubOptions = _.get( + options.map((item2: any) => { + const item2options = _.get( item2, mappingLocations.children, [], ); return { - label: _.get(item2, mappingLocations.label, ''), + name: _.get(item2, mappingLocations.label, ''), value: _.get(item2, mappingLocations.value, ''), - subOptions: - item2SubOptions && item2SubOptions.length > 0 + options: + item2options && item2options.length > 0 ? _.orderBy( - item2SubOptions.map((item3: any) => { - const item3SubOptions = _.get( + item2options.map((item3: any) => { + const item3options = _.get( item3, mappingLocations.children, [], ); return { - label: _.get( + name: _.get( item3, mappingLocations.label, '', @@ -372,26 +483,23 @@ export class FilteroptionsController { mappingLocations.value, '', ), - subOptions: - item3SubOptions && - item3SubOptions.length > 0 + options: + item3options && item3options.length > 0 ? _.orderBy( - item3SubOptions.map( - (item4: any) => { - return { - label: _.get( - item4, - mappingLocations.label, - '', - ), - value: _.get( - item4, - mappingLocations.value, - '', - ), - }; - }, - ), + item3options.map((item4: any) => { + return { + name: _.get( + item4, + mappingLocations.label, + '', + ), + value: _.get( + item4, + mappingLocations.value, + '', + ), + }; + }), 'label', 'asc', ) @@ -423,28 +531,26 @@ export class FilteroptionsController { mcRawData.forEach((item: any) => { data.forEach((region: FilterGroupOption) => { - const fRegion = _.find(region.subOptions, { + const fRegion = _.find(region.options, { value: _.get(item, mappingMulticountries.regionCode), }); if (fRegion) { - region.subOptions?.push({ - label: _.get(item, mappingMulticountries.label, ''), + region.options?.push({ + name: _.get(item, mappingMulticountries.label, ''), value: _.get(item, mappingMulticountries.value, ''), }); } else { - region.subOptions?.forEach( - (subRegion: FilterGroupOption) => { - const fSubRegion = _.find(subRegion.subOptions, { - value: _.get(item, mappingMulticountries.regionCode), + region.options?.forEach((subRegion: FilterGroupOption) => { + const fSubRegion = _.find(subRegion.options, { + value: _.get(item, mappingMulticountries.regionCode), + }); + if (fSubRegion) { + subRegion.options?.push({ + name: _.get(item, mappingMulticountries.label, ''), + value: _.get(item, mappingMulticountries.value, ''), }); - if (fSubRegion) { - subRegion.subOptions?.push({ - label: _.get(item, mappingMulticountries.label, ''), - value: _.get(item, mappingMulticountries.value, ''), - }); - } - }, - ); + } + }); } }); }); @@ -478,7 +584,7 @@ export class FilteroptionsController { return { name: 'Components', options: rawData.map((item: any) => ({ - label: _.get(item, mappingComponents.label, ''), + name: _.get(item, mappingComponents.label, ''), value: _.get(item, mappingComponents.value, ''), })), }; @@ -486,7 +592,7 @@ export class FilteroptionsController { .catch(handleDataApiError); } - @get('/filter-options/partner-types') + @get('/v2/filter-options/partner-types') @response(200, FILTER_OPTIONS_RESPONSE) partnerTypes(): object { const url = urls.filteroptionspartnertypes; @@ -508,11 +614,11 @@ export class FilteroptionsController { groupedByPartnerType[partnerType], mappingPartnertypes.partnerSubType, ); - const subOptions: FilterGroupOption[] = []; + const options: FilterGroupOption[] = []; Object.keys(groupedBySubPartnerType).forEach( (subPartnerType: string) => { - subOptions.push({ - label: + options.push({ + name: subPartnerType && subPartnerType !== 'null' ? subPartnerType : 'Not Classified', @@ -521,10 +627,10 @@ export class FilteroptionsController { mappingPartnertypes.partnerSubTypeId, '', ), - subOptions: _.orderBy( + options: _.orderBy( groupedBySubPartnerType[subPartnerType].map( (partner: any) => ({ - label: _.get(partner, mappingPartnertypes.partner, ''), + name: _.get(partner, mappingPartnertypes.partner, ''), value: _.get(partner, mappingPartnertypes.partnerId, ''), }), ), @@ -535,7 +641,7 @@ export class FilteroptionsController { }, ); options.push({ - label: + name: partnerType && partnerType !== 'null' ? partnerType : 'Not Classified', @@ -544,7 +650,7 @@ export class FilteroptionsController { mappingPartnertypes.partnerTypeId, '', ), - subOptions: _.orderBy(subOptions, 'label', 'asc'), + options: _.orderBy(options, 'label', 'asc'), }); }); @@ -556,7 +662,7 @@ export class FilteroptionsController { .catch(handleDataApiError); } - @get('/filter-options/status') + @get('/v2/filter-options/status') @response(200, FILTER_OPTIONS_RESPONSE) status(): object { const url = urls.filteroptionsstatus; @@ -570,7 +676,7 @@ export class FilteroptionsController { name: 'Grant status', options: _.orderBy( rawData.map((item: any) => ({ - label: _.get(item, mappingStatus.label, ''), + name: _.get(item, mappingStatus.label, ''), value: _.get(item, mappingStatus.value, ''), })), 'label', @@ -599,7 +705,7 @@ export class FilteroptionsController { name: 'Replenishment periods', options: _.orderBy( rawData.map((item: any) => ({ - label: _.get(item, mappingReplenishmentperiods.label, ''), + name: _.get(item, mappingReplenishmentperiods.label, ''), value: _.get(item, mappingReplenishmentperiods.value, ''), })), 'label', @@ -625,44 +731,40 @@ export class FilteroptionsController { rawData.forEach((item: any) => { const type: FilterGroupOption = { - label: _.get(item, mappingDonors.label, ''), + name: _.get(item, mappingDonors.label, ''), value: _.get(item, mappingDonors.value, ''), - subOptions: [], + options: [], }; _.get(item, mappingDonors.children, []).forEach((child: any) => { if (_.get(child, mappingDonors.children, []).length > 0) { const subType: FilterGroupOption = { - label: _.get(child, mappingDonors.label, ''), + name: _.get(child, mappingDonors.label, ''), value: _.get(child, mappingDonors.value, ''), - subOptions: [], + options: [], }; _.get(child, mappingDonors.children, []).forEach( (gchild: any) => { - subType.subOptions?.push({ - label: _.get(gchild, mappingDonors.label, ''), + subType.options?.push({ + name: _.get(gchild, mappingDonors.label, ''), value: _.get(gchild, mappingDonors.value, ''), }); }, ); - subType.subOptions = _.orderBy( - subType.subOptions, - 'label', - 'asc', - ); - type.subOptions?.push(subType); + subType.options = _.orderBy(subType.options, 'label', 'asc'); + type.options?.push(subType); } else { - type.subOptions?.push({ - label: _.get(child, mappingDonors.label, ''), + type.options?.push({ + name: _.get(child, mappingDonors.label, ''), value: _.get(child, mappingDonors.value, ''), }); } }); - type.subOptions = _.orderBy(type.subOptions, 'label', 'asc'); + type.options = _.orderBy(type.options, 'label', 'asc'); if (keyword.length > 0) { - type.subOptions = _.filter(type.subOptions, (option: any) => { + type.options = _.filter(type.options, (option: any) => { let allKeywordsFound = true; keywords.forEach((key: string) => { if ( @@ -675,21 +777,18 @@ export class FilteroptionsController { }) as FilterGroupOption[]; } - const subOptionsWithSubOptions: FilterGroupOption[] = _.orderBy( - _.filter(type.subOptions, o => o.subOptions) as FilterGroupOption[], + const optionsWithoptions: FilterGroupOption[] = _.orderBy( + _.filter(type.options, o => o.options) as FilterGroupOption[], 'label', 'asc', ); - const subOptionsWithOutSubOptions: FilterGroupOption[] = _.orderBy( - _.filter(type.subOptions, o => !o.subOptions), + const optionsWithOutoptions: FilterGroupOption[] = _.orderBy( + _.filter(type.options, o => !o.options), 'label', 'asc', ); - type.subOptions = [ - ...subOptionsWithSubOptions, - ...subOptionsWithOutSubOptions, - ]; + type.options = [...optionsWithoptions, ...optionsWithOutoptions]; options.push(type); }); diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index 9df9249..687bcc5 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -166,7 +166,7 @@ export class PledgescontributionsController { })), }; - return data; + return {data}; }) .catch(handleDataApiError); } @@ -677,8 +677,8 @@ export class PledgescontributionsController { }); let subType = ''; donorFilterOptions.forEach((option: FilterGroupOption) => { - if (_.find(option.subOptions, {label: donor})) { - subType = option.label; + if (_.find(option.options, {label: donor})) { + subType = option.name; } }); const multiCoordinates = diff --git a/src/interfaces/filters.ts b/src/interfaces/filters.ts index 26d315e..2afbf6f 100644 --- a/src/interfaces/filters.ts +++ b/src/interfaces/filters.ts @@ -1,7 +1,7 @@ export interface FilterGroupOption { - label: string; + name: string; value: string; - subOptions?: FilterGroupOption[]; + options?: FilterGroupOption[]; extraInfo?: { [key: string]: any; }; diff --git a/src/static-assets/locations.json b/src/static-assets/locations.json new file mode 100644 index 0000000..92e3292 --- /dev/null +++ b/src/static-assets/locations.json @@ -0,0 +1,854 @@ +[ + { + "name": "World", + "value": "QMZ", + "items": [ + { + "name": "Multicountries", + "value": "Multicountries", + "items": [ + { + "name": "Multicountry HIV Caribbean PCC Consortium", + "value": "MCLACPCC" + }, + { + "name": "Multicountry Americas (Andean)", + "value": "MCANDEAN" + }, + { + "name": "Multicountry Americas (CRN+)", + "value": "MCCRN" + }, + { + "name": "Multicountry East Asia and Pacific APN", + "value": "MCAPN" + }, + { + "name": "Multicountry Americas COPRECOS", + "value": "MCCOPRECOS" + }, + { + "name": "Multicountry HIV EECA APH", + "value": "MCEECAAPH" + }, + { + "name": "Multicountry SEAF RMCC", + "value": "MCSEAF" + }, + { + "name": "Multicountry HIV Latin America ALEP", + "value": "MCALEP" + }, + { + "name": "Multicountry HIV SEA AFAO", + "value": "MCSEAAFAO" + }, + { + "name": "Multicountry MENA Key Populations", + "value": "MCMENA" + }, + { + "name": "Multicountry MENA HRA", + "value": "MCMENAHRA" + }, + { + "name": "Multicountry EECA ECOM", + "value": "MCECOM" + }, + { + "name": "Multicountry EECA ECUO", + "value": "MCECUO" + }, + { + "name": "The Lutheran World Federation", + "value": "MCLWF" + }, + { + "name": "Multicountry East Asia and Pacific KPRA SCF", + "value": "MCKPRA" + }, + { + "name": "Multicountry EECA EHRN", + "value": "MCEHRN" + }, + { + "name": "Multicountry EECA PAS", + "value": "MCPAS" + }, + { + "name": "Multicountry EECA IHAU", + "value": "MCIHAU" + }, + { + "name": "Multicountry Africa (RMCC)", + "value": "MCRMCC" + } + ] + } + ] + }, + { + "name": "Africa", + "value": "QPA", + "items": [ + { + "name": "Eastern Africa", + "value": "QPB", + "items": [ + { + "name": "Burundi", + "value": "BDI" + }, + { + "name": "Comoros", + "value": "COM" + }, + { + "name": "Djibouti", + "value": "DJI" + }, + { + "name": "Eritrea", + "value": "ERI" + }, + { + "name": "Ethiopia", + "value": "ETH" + }, + { + "name": "Kenya", + "value": "KEN" + }, + { + "name": "Madagascar", + "value": "MDG" + }, + { + "name": "Malawi", + "value": "MWI" + }, + { + "name": "Mauritius", + "value": "MUS" + }, + { + "name": "Mozambique", + "value": "MOZ" + }, + { + "name": "Rwanda", + "value": "RWA" + }, + { + "name": "Somalia", + "value": "SOM" + }, + { + "name": "South Sudan", + "value": "SSD" + }, + { + "name": "Tanzania (United Republic)", + "value": "TZA" + }, + { + "name": "Uganda", + "value": "UGA" + }, + { + "name": "Zambia", + "value": "ZMB" + }, + { + "name": "Zanzibar", + "value": "QNB" + }, + { + "name": "Zimbabwe", + "value": "ZWE" + } + ] + }, + { + "name": "Middle Africa", + "value": "QPC", + "items": [ + { + "name": "Angola", + "value": "AGO" + }, + { + "name": "Cameroon", + "value": "CMR" + }, + { + "name": "Central African Republic", + "value": "CAF" + }, + { + "name": "Chad", + "value": "TCD" + }, + { + "name": "Congo", + "value": "COG" + }, + { + "name": "Congo (Democratic Republic)", + "value": "COD" + }, + { + "name": "Equatorial Guinea", + "value": "GNQ" + }, + { + "name": "Gabon", + "value": "GAB" + }, + { + "name": "Sao Tome and Principe", + "value": "STP" + } + ] + }, + { + "name": "Northern Africa", + "value": "QPD", + "items": [ + { + "name": "Algeria", + "value": "DZA" + }, + { + "name": "Egypt", + "value": "EGY" + }, + { + "name": "Morocco", + "value": "MAR" + }, + { + "name": "Sudan", + "value": "SDN" + }, + { + "name": "Tunisia", + "value": "TUN" + } + ] + }, + { + "name": "Southern Africa", + "value": "QPE", + "items": [ + { + "name": "Botswana", + "value": "BWA" + }, + { + "name": "Eswatini", + "value": "SWZ" + }, + { + "name": "Lesotho", + "value": "LSO" + }, + { + "name": "Namibia", + "value": "NAM" + }, + { + "name": "South Africa", + "value": "ZAF" + } + ] + }, + { + "name": "Western Africa", + "value": "QPF", + "items": [ + { + "name": "Benin", + "value": "BEN" + }, + { + "name": "Burkina Faso", + "value": "BFA" + }, + { + "name": "Cabo Verde", + "value": "CPV" + }, + { + "name": "Côte d'Ivoire", + "value": "CIV" + }, + { + "name": "Gambia", + "value": "GMB" + }, + { + "name": "Ghana", + "value": "GHA" + }, + { + "name": "Guinea", + "value": "GIN" + }, + { + "name": "Guinea-Bissau", + "value": "GNB" + }, + { + "name": "Liberia", + "value": "LBR" + }, + { + "name": "Mali", + "value": "MLI" + }, + { + "name": "Mauritania", + "value": "MRT" + }, + { + "name": "Niger", + "value": "NER" + }, + { + "name": "Nigeria", + "value": "NGA" + }, + { + "name": "Senegal", + "value": "SEN" + }, + { + "name": "Sierra Leone", + "value": "SLE" + }, + { + "name": "Togo", + "value": "TGO" + } + ] + }, + { + "name": "Multicountries", + "value": "Multicountries", + "items": [ + { + "name": "Multicountry Africa ECSA-HC", + "value": "MCECSA-HC" + }, + { + "name": "Multicountry Eastern Africa ANECCA", + "value": "MCANECCA" + }, + { + "name": "Multicountry Southern Africa ARASA", + "value": "MCARASA-ENDA" + }, + { + "name": "Multicountry Southern Africa E8", + "value": "MCE8" + }, + { + "name": "Multicountry Southern Africa HIVOS", + "value": "MCHIVOS" + }, + { + "name": "Multicountry Southern Africa MOSASWA", + "value": "MCMOSASWA" + }, + { + "name": "Multicountry Southern Africa SADC", + "value": "MCSADC" + }, + { + "name": "Multicountry Southern Africa TIMS", + "value": "MCTIMS" + }, + { + "name": "Multicountry Southern Africa WHC", + "value": "MCWHC" + }, + { + "name": "Multicountry TB WC Africa NTP/SRL", + "value": "MCNTPSRL" + } + ] + } + ] + }, + { + "name": "Americas", + "value": "QRA", + "items": [ + { + "name": "Caribbean", + "value": "QRB", + "items": [ + { + "name": "Cuba", + "value": "CUB" + }, + { + "name": "Dominican Republic", + "value": "DOM" + }, + { + "name": "Haiti", + "value": "HTI" + }, + { + "name": "Jamaica", + "value": "JAM" + } + ] + }, + { + "name": "Central America", + "value": "QRC", + "items": [ + { + "name": "Belize", + "value": "BLZ" + }, + { + "name": "Costa Rica", + "value": "CRI" + }, + { + "name": "El Salvador", + "value": "SLV" + }, + { + "name": "Guatemala", + "value": "GTM" + }, + { + "name": "Honduras", + "value": "HND" + }, + { + "name": "Mexico", + "value": "MEX" + }, + { + "name": "Nicaragua", + "value": "NIC" + }, + { + "name": "Panama", + "value": "PAN" + } + ] + }, + { + "name": "South America", + "value": "QRD", + "items": [ + { + "name": "Argentina", + "value": "ARG" + }, + { + "name": "Bolivia (Plurinational State)", + "value": "BOL" + }, + { + "name": "Brazil", + "value": "BRA" + }, + { + "name": "Chile", + "value": "CHL" + }, + { + "name": "Colombia", + "value": "COL" + }, + { + "name": "Ecuador", + "value": "ECU" + }, + { + "name": "Guyana", + "value": "GUY" + }, + { + "name": "Paraguay", + "value": "PRY" + }, + { + "name": "Peru", + "value": "PER" + }, + { + "name": "Suriname", + "value": "SUR" + }, + { + "name": "Uruguay", + "value": "URY" + }, + { + "name": "Venezuela", + "value": "VEN" + } + ] + }, + { + "name": "Multicountries", + "value": "Multicountries", + "items": [ + { + "name": "Multicountry Americas CVC-COIN", + "value": "MCCVC/COIN" + }, + { + "name": "Multicountry Americas EMMIE", + "value": "MCEMMIE" + }, + { + "name": "Multicountry Americas ICW", + "value": "MCICW" + }, + { + "name": "Multicountry Americas ORAS-CONHU", + "value": "MCORAS-CONHU" + }, + { + "name": "Multicountry Americas REDLACTRANS", + "value": "MCREDLACTRANS" + }, + { + "name": "Multicountry Americas REDTRASEX", + "value": "MCREDTRASEX" + }, + { + "name": "Multicountry Caribbean CARICOM-PANCAP", + "value": "MCCARICOM/PANCAP" + }, + { + "name": "Multicountry South-Eastern Asia HIV", + "value": "MCSAHIV" + }, + { + "name": "Multicountry TB LAC PIH", + "value": "MCPIH" + } + ] + } + ] + }, + { + "name": "Asia", + "value": "QSA", + "items": [ + { + "name": "Central Asia", + "value": "QSB", + "items": [ + { + "name": "Kazakhstan", + "value": "KAZ" + }, + { + "name": "Kyrgyzstan", + "value": "KGZ" + }, + { + "name": "Tajikistan", + "value": "TJK" + }, + { + "name": "Turkmenistan", + "value": "TKM" + }, + { + "name": "Uzbekistan", + "value": "UZB" + } + ] + }, + { + "name": "Eastern Asia", + "value": "QSC", + "items": [ + { + "name": "China", + "value": "CHN" + }, + { + "name": "Korea (Democratic Peoples Republic)", + "value": "PRK" + }, + { + "name": "Mongolia", + "value": "MNG" + } + ] + }, + { + "name": "South-Eastern Asia", + "value": "QSE", + "items": [ + { + "name": "Cambodia", + "value": "KHM" + }, + { + "name": "Indonesia", + "value": "IDN" + }, + { + "name": "Lao (Peoples Democratic Republic)", + "value": "LAO" + }, + { + "name": "Malaysia", + "value": "MYS" + }, + { + "name": "Myanmar", + "value": "MMR" + }, + { + "name": "Philippines", + "value": "PHL" + }, + { + "name": "Thailand", + "value": "THA" + }, + { + "name": "Timor-Leste", + "value": "TLS" + }, + { + "name": "Viet Nam", + "value": "VNM" + } + ] + }, + { + "name": "Southern Asia", + "value": "QSD", + "items": [ + { + "name": "Afghanistan", + "value": "AFG" + }, + { + "name": "Bangladesh", + "value": "BGD" + }, + { + "name": "Bhutan", + "value": "BTN" + }, + { + "name": "India", + "value": "IND" + }, + { + "name": "Iran (Islamic Republic)", + "value": "IRN" + }, + { + "name": "Maldives", + "value": "MDV" + }, + { + "name": "Nepal", + "value": "NPL" + }, + { + "name": "Pakistan", + "value": "PAK" + }, + { + "name": "Sri Lanka", + "value": "LKA" + } + ] + }, + { + "name": "Western Asia", + "value": "QSF", + "items": [ + { + "name": "Armenia", + "value": "ARM" + }, + { + "name": "Azerbaijan", + "value": "AZE" + }, + { + "name": "Georgia", + "value": "GEO" + }, + { + "name": "Iraq", + "value": "IRQ" + }, + { + "name": "Jordan", + "value": "JOR" + }, + { + "name": "Palestine", + "value": "PSE" + }, + { + "name": "Syrian Arab Republic", + "value": "SYR" + }, + { + "name": "Türkiye", + "value": "TUR" + }, + { + "name": "Yemen", + "value": "YEM" + } + ] + }, + { + "name": "Multicountries", + "value": "Multicountries", + "items": [ + { + "name": "Multicountry Asia IHAA", + "value": "MCIHAA" + }, + { + "name": "Multicountry TB Asia TEAM", + "value": "MCASIATEAM" + }, + { + "name": "Multicountry TB Asia UNDP", + "value": "MCASIAUNDP" + } + ] + } + ] + }, + { + "name": "Europe", + "value": "QTA", + "items": [ + { + "name": "Eastern Europe", + "value": "QTB", + "items": [ + { + "name": "Belarus", + "value": "BLR" + }, + { + "name": "Bulgaria", + "value": "BGR" + }, + { + "name": "Moldova", + "value": "MDA" + }, + { + "name": "Romania", + "value": "ROU" + }, + { + "name": "Russian Federation", + "value": "RUS" + }, + { + "name": "Ukraine", + "value": "UKR" + } + ] + }, + { + "name": "Northern Europe", + "value": "QTC", + "items": [ + { + "name": "Estonia", + "value": "EST" + } + ] + }, + { + "name": "Southern Europe", + "value": "QTD", + "items": [ + { + "name": "Albania", + "value": "ALB" + }, + { + "name": "Bosnia and Herzegovina", + "value": "BIH" + }, + { + "name": "Croatia", + "value": "HRV" + }, + { + "name": "Kosovo", + "value": "QNA" + }, + { + "name": "Montenegro", + "value": "MNE" + }, + { + "name": "North Macedonia", + "value": "MKD" + }, + { + "name": "Serbia", + "value": "SRB" + } + ] + } + ] + }, + { + "name": "Oceania", + "value": "QUA", + "items": [ + { + "name": "Melanesia", + "value": "QUC", + "items": [ + { + "name": "Fiji", + "value": "FJI" + }, + { + "name": "Papua New Guinea", + "value": "PNG" + }, + { + "name": "Solomon Islands", + "value": "SLB" + } + ] + }, + { + "name": "Multicountries", + "value": "Multicountries", + "items": [ + { + "name": "Multicountry Western Pacific", + "value": "MCWP" + } + ] + } + ] + } +] diff --git a/src/utils/data-themes/getDatasetFilterOptions.ts b/src/utils/data-themes/getDatasetFilterOptions.ts index c948976..8269010 100644 --- a/src/utils/data-themes/getDatasetFilterOptions.ts +++ b/src/utils/data-themes/getDatasetFilterOptions.ts @@ -26,7 +26,7 @@ export function getDatasetFilterOptions(dataset: any): FilterGroup[] { name, options: _.orderBy( _.uniq(options).map((o: string) => ({ - label: o, + name: o, value: o, })), 'label', diff --git a/src/utils/filtering/donors.ts b/src/utils/filtering/donors.ts index 1a9b212..cbab808 100644 --- a/src/utils/filtering/donors.ts +++ b/src/utils/filtering/donors.ts @@ -52,13 +52,19 @@ export function filterDonors( )}`; } + let res = urlParams; + if (str.length > 0) { + str = str.replace(/&/g, '%26'); if (urlParams) { str = urlParams.replace('', ` AND ${str}`); } } else if (urlParams) { - str = urlParams.replace('', ''); + res = res.replace('$filter=&', ''); + res = res.replace('filter()/', ''); + res = res.replace('/', ''); + res = res.replace('', ''); } - return str; + return res; } diff --git a/src/utils/filtering/eligibility.ts b/src/utils/filtering/eligibility.ts index d935832..1636547 100644 --- a/src/utils/filtering/eligibility.ts +++ b/src/utils/filtering/eligibility.ts @@ -1,8 +1,10 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; +import {getGeographyValues} from './geographies'; const MAPPING = { - geography: 'geography/code', + geography: ['geography/name', 'geography/code'], + component: 'activityArea/name', year: 'eligibilityYear', }; @@ -12,14 +14,33 @@ export function filterEligibility( ): string { let str = ''; - const geographies = _.filter( + const geos = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ).map((geography: string) => `'${geography}'`); - if (geographies.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ + ); + const geographies = geos.map((geography: string) => `'${geography}'`); + if (geos.length > 0) { + const values: string[] = [...geographies, ...getGeographyValues(geos)]; + if (MAPPING.geography instanceof Array) { + str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography + .map( + m => + `${m}${filtering.in}(${values.join( + filtering.multi_param_separator, + )})`, + ) + .join(' OR ')})`; + } + } + + const components = _.filter( + _.get(params, 'components', '').split(','), + (o: string) => o.length > 0, + ).map((component: string) => `'${component}'`); + if (components.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.component}${ filtering.in - }(${geographies.join(filtering.multi_param_separator)})`; + }(${components.join(filtering.multi_param_separator)})`; } const years = _.filter( @@ -40,13 +61,21 @@ export function filterEligibility( // )}`; // } + let res = urlParams; + if (str.length > 0) { + str = str.replace(/&/g, '%26'); if (urlParams) { - str = urlParams.replace('', ` AND ${str}`); + res = res.replace('filter()', `filter(${str})`); + res = res.replace('', ` AND ${str}`); + res = res.replace('= AND ', '='); } } else if (urlParams) { - str = urlParams.replace('/', ''); + res = res.replace('$filter=&', ''); + res = res.replace('filter()/', ''); + res = res.replace('/', ''); + res = res.replace('', ''); } - return str; + return res; } diff --git a/src/utils/filtering/financialIndicators.ts b/src/utils/filtering/financialIndicators.ts index 2978b12..ec66b2c 100644 --- a/src/utils/filtering/financialIndicators.ts +++ b/src/utils/filtering/financialIndicators.ts @@ -1,15 +1,24 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; +import {getGeographyValues} from './geographies'; const MAPPING = { - geography: ['geography/code', 'implementationPeriod/grant/geography/code'], + geography: [ + 'geography/code', + 'geography/name', + 'implementationPeriod/grant/geography/code', + 'implementationPeriod/grant/geography/name', + ], donor: 'donor/name', donorType: 'donor/type/name', + principalRecipient: 'implementationPeriod/grant/principalRecipient/name', + principalRecipientType: + 'implementationPeriod/grant/principalRecipient/type/name', period: 'periodCovered', - donorGeography: 'donor/geography/code', year: 'implementationPeriod/periodFrom', yearTo: 'implementationPeriod/periodTo', grantIP: 'implementationPeriod/code', + status: 'implementationPeriod/status/statusName', search: `(contains(donor/type/name,) OR contains(donor/name,) OR contains(periodCovered,))`, }; @@ -19,16 +28,18 @@ export function filterFinancialIndicators( ): string { let str = ''; - const geographies = _.filter( + const geos = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ).map((geography: string) => `'${geography}'`); - if (geographies.length > 0) { + ); + const geographies = geos.map((geography: string) => `'${geography}'`); + if (geos.length > 0) { + const values: string[] = [...geographies, ...getGeographyValues(geos)]; if (MAPPING.geography instanceof Array) { str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography .map( m => - `${m}${filtering.in}(${geographies.join( + `${m}${filtering.in}(${values.join( filtering.multi_param_separator, )})`, ) @@ -53,17 +64,27 @@ export function filterFinancialIndicators( if (donorTypes.length > 0) { str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donorType}${ filtering.in - }(${donors.join(filtering.multi_param_separator)})`; + }(${donorTypes.join(filtering.multi_param_separator)})`; + } + + const principalRecipients = _.filter( + _.get(params, 'principalRecipients', '').split(','), + (o: string) => o.length > 0, + ).map((principalRecipient: string) => `'${principalRecipient}'`); + if (principalRecipients.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.principalRecipient}${ + filtering.in + }(${principalRecipients.join(filtering.multi_param_separator)})`; } - const donorGeographies = _.filter( - _.get(params, 'donorGeographies', '').split(','), + const principalRecipientTypes = _.filter( + _.get(params, 'principalRecipientTypes', '').split(','), (o: string) => o.length > 0, - ).map((donorGeography: string) => `'${donorGeography}'`); - if (donorGeographies.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donorGeography}${ + ).map((principalRecipientType: string) => `'${principalRecipientType}'`); + if (principalRecipientTypes.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.principalRecipientType}${ filtering.in - }(${donorGeographies.join(filtering.multi_param_separator)})`; + }(${principalRecipientTypes.join(filtering.multi_param_separator)})`; } const periods = _.filter( @@ -109,6 +130,16 @@ export function filterFinancialIndicators( }(${grantIPs.join(filtering.multi_param_separator)})`; } + const statuses = _.filter( + _.get(params, 'status', '').split(','), + (o: string) => o.length > 0, + ).map((status: string) => `'${status}'`); + if (statuses.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.status}${ + filtering.in + }(${statuses.join(filtering.multi_param_separator)})`; + } + const search = _.get(params, 'q', ''); if (search.length > 0) { str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( @@ -117,13 +148,21 @@ export function filterFinancialIndicators( )}`; } + let res = urlParams; + if (str.length > 0) { + str = str.replace(/&/g, '%26'); if (urlParams) { - str = urlParams.replace('', ` AND ${str}`); + res = res.replace('filter()', `filter(${str})`); + res = res.replace('', ` AND ${str}`); + res = res.replace('= AND ', '='); } } else if (urlParams) { - str = urlParams.replace('', ''); + res = res.replace('$filter=&', ''); + res = res.replace('filter()/', ''); + res = res.replace('/', ''); + res = res.replace('', ''); } - return str; + return res; } diff --git a/src/utils/filtering/fundingRequests.ts b/src/utils/filtering/fundingRequests.ts index 226c302..2a456be 100644 --- a/src/utils/filtering/fundingRequests.ts +++ b/src/utils/filtering/fundingRequests.ts @@ -1,8 +1,9 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; +import {getGeographyValues} from './geographies'; const MAPPING = { - geography: 'geography/code', + geography: ['geography/name', 'geography/code'], component: 'activityArea/name', period: 'periodFrom', trpWindow: 'window', @@ -15,14 +16,23 @@ export function filterFundingRequests( ): string { let str = ''; - const geographies = _.filter( + const geos = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ).map((geography: string) => `'${geography}'`); - if (geographies.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ - filtering.in - }(${geographies.join(filtering.multi_param_separator)})`; + ); + const geographies = geos.map((geography: string) => `'${geography}'`); + if (geos.length > 0) { + const values: string[] = [...geographies, ...getGeographyValues(geos)]; + if (MAPPING.geography instanceof Array) { + str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography + .map( + m => + `${m}${filtering.in}(${values.join( + filtering.multi_param_separator, + )})`, + ) + .join(' OR ')})`; + } } const components = _.filter( @@ -74,14 +84,21 @@ export function filterFundingRequests( // )}`; // } + let res = urlParams; + if (str.length > 0) { + str = str.replace(/&/g, '%26'); if (urlParams) { - str = urlParams.replace('', ` AND ${str}`); - str = str.replace('= AND ', '='); + res = res.replace('filter()', `filter(${str})`); + res = res.replace('', ` AND ${str}`); + res = res.replace('= AND ', '='); } } else if (urlParams) { - str = urlParams.replace('', '').replace('$filter=&', ''); + res = res.replace('$filter=&', ''); + res = res.replace('filter()/', ''); + res = res.replace('/', ''); + res = res.replace('', ''); } - return str; + return res; } diff --git a/src/utils/filtering/geographies.ts b/src/utils/filtering/geographies.ts new file mode 100644 index 0000000..04440fd --- /dev/null +++ b/src/utils/filtering/geographies.ts @@ -0,0 +1,28 @@ +import _ from 'lodash'; +import locations from '../../static-assets/locations.json'; + +export function getGeographyValues(geos: string[]) { + const values: string[] = []; + locations.forEach(region => { + const fRegion = _.find(geos, (g: string) => g === region.name); + if (fRegion) { + region.items.forEach(item => { + if (item.items) { + item.items.forEach(subItem => { + values.push(`'${subItem.value}'`); + }); + } + }); + } else { + region.items.forEach(item => { + const fItem = _.find(geos, (g: string) => g === item.name); + if (fItem) { + item.items.forEach(subItem => { + values.push(`'${subItem.value}'`); + }); + } + }); + } + }); + return values; +} diff --git a/src/utils/filtering/grants.ts b/src/utils/filtering/grants.ts index d37a16c..8965b28 100644 --- a/src/utils/filtering/grants.ts +++ b/src/utils/filtering/grants.ts @@ -1,8 +1,9 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; +import {getGeographyValues} from './geographies'; const MAPPING = { - geography: 'geography/code', + geography: ['geography/name', 'geography/code'], status: 'status/statusName', component: 'activityArea/name', principalRecipient: 'principalRecipient/name', @@ -18,14 +19,23 @@ export function filterGrants( ): string { let str = ''; - const geographies = _.filter( + const geos = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ).map((geography: string) => `'${geography}'`); - if (geographies.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ - filtering.in - }(${geographies.join(filtering.multi_param_separator)})`; + ); + const geographies = geos.map((geography: string) => `'${geography}'`); + if (geos.length > 0) { + const values: string[] = [...geographies, ...getGeographyValues(geos)]; + if (MAPPING.geography instanceof Array) { + str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography + .map( + m => + `${m}${filtering.in}(${values.join( + filtering.multi_param_separator, + )})`, + ) + .join(' OR ')})`; + } } const statuses = _.filter( @@ -80,13 +90,22 @@ export function filterGrants( )}`; } + let res = urlParams; + if (str.length > 0) { + str = str.replace(/&/g, '%26'); if (urlParams) { - str = urlParams.replace('', str); + res = res.replace('filter()', `filter(${str})`); + res = res.replace('', ` AND ${str}`); + + res = res.replace('= AND ', '='); } } else if (urlParams) { - str = urlParams.replace('$filter=&', ''); + res = res.replace('$filter=&', ''); + res = res.replace('filter()/', ''); + res = res.replace('/', ''); + res = res.replace('', ''); } - return str; + return res; } diff --git a/src/utils/filtering/programmaticIndicators.ts b/src/utils/filtering/programmaticIndicators.ts index a7738b3..21b7e8c 100644 --- a/src/utils/filtering/programmaticIndicators.ts +++ b/src/utils/filtering/programmaticIndicators.ts @@ -1,8 +1,9 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; +import {getGeographyValues} from './geographies'; const MAPPING = { - geography: 'geography/code', + geography: ['geography/name', 'geography/code'], component: 'activityArea/name', year: 'resultValueYear', search: @@ -15,14 +16,23 @@ export function filterProgrammaticIndicators( ): string { let str = ''; - const geographies = _.filter( + const geos = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ).map((geography: string) => `'${geography}'`); - if (geographies.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ - filtering.in - }(${geographies.join(filtering.multi_param_separator)})`; + ); + const geographies = geos.map((geography: string) => `'${geography}'`); + if (geos.length > 0) { + const values: string[] = [...geographies, ...getGeographyValues(geos)]; + if (MAPPING.geography instanceof Array) { + str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography + .map( + m => + `${m}${filtering.in}(${values.join( + filtering.multi_param_separator, + )})`, + ) + .join(' OR ')})`; + } } const components = _.filter( @@ -54,6 +64,7 @@ export function filterProgrammaticIndicators( } if (str.length > 0) { + str = str.replace(/&/g, '%26'); if (urlParams) { str = urlParams.replace('', ` AND ${str}`); } From 4cc01f544edf762781f39df1a0391d260dd3168a Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 3 Jun 2024 16:20:26 +0300 Subject: [PATCH 05/44] chore: remove global search unused categories --- src/controllers/global-search.controller.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/controllers/global-search.controller.ts b/src/controllers/global-search.controller.ts index bfc599b..2305f16 100644 --- a/src/controllers/global-search.controller.ts +++ b/src/controllers/global-search.controller.ts @@ -77,11 +77,14 @@ export class GlobalSearchController { message: '"q" param is required.', }; } - const keywords = keyword.split(' '); - const calls = _.filter( + const categories = _.filter( globalSearchMapping.categories, - (category: any) => category.url.length > 0, - ).map((category: any) => { + (category: any) => + category.type === 'Country' || category.type === 'Grant', + // (category: any) => category.url.length > 0, + ); + const keywords = keyword.split(' '); + const calls = categories.map((category: any) => { const call = category.url .replace( '', @@ -99,7 +102,7 @@ export class GlobalSearchController { .then( axios.spread((...responses) => { const results: SearchResultsTabModel[] = []; - globalSearchMapping.categories.forEach((cat: any, index: number) => { + categories.forEach((cat: any, index: number) => { const mapper = mapTransform(cat.mappings); const categoryResults = cat.url.length > 0 From 79b6bac16b683edc34a4ca4c3f498106ca167c7b Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 3 Jun 2024 16:20:44 +0300 Subject: [PATCH 06/44] feat: eligibility years endpoint --- src/controllers/eligibility.controller.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/controllers/eligibility.controller.ts b/src/controllers/eligibility.controller.ts index 0c95ac6..ea44080 100644 --- a/src/controllers/eligibility.controller.ts +++ b/src/controllers/eligibility.controller.ts @@ -99,6 +99,27 @@ export class EligibilityController { // v3 + @get('/eligibility/years') + @response(200, ELIGIBILITY_RESPONSE) + eligibilityYears(): object { + const url = `${urls.ELIGIBILITY}/?${EligibilityYearsFieldsMapping.aggregation}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + return { + data: _.get( + resp.data, + EligibilityYearsFieldsMapping.dataPath, + [], + ).map((item: any) => + _.get(item, EligibilityYearsFieldsMapping.year, ''), + ), + }; + }) + .catch(handleDataApiError); + } + @get('/eligibility/stats/{year}') @response(200) async eligibilityStats(@param.path.string('year') year: string) { @@ -400,7 +421,7 @@ export class EligibilityController { @get('/eligibility/years') @response(200, ELIGIBILITY_RESPONSE) - eligibilityYears(): object { + eligibilityYearsV2(): object { const url = `${urls.eligibility}/?${EligibilityYearsFieldsMapping.aggregation}`; return axios From 5f738e1fb4e4c108acdbbb1efbb201a79a00a369 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 3 Jun 2024 16:54:28 +0300 Subject: [PATCH 07/44] feat: grant list order by --- src/config/mapping/grants/list.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/mapping/grants/list.json b/src/config/mapping/grants/list.json index a6c7aa4..2e6b6cc 100644 --- a/src/config/mapping/grants/list.json +++ b/src/config/mapping/grants/list.json @@ -17,5 +17,5 @@ } ], "count": "@odata.count", - "urlParams": "?$filter=&$count=true&$expand=status($select=statusName),geography($select=name),activityArea($select=name),principalRecipient($select=name),narratives($select=narrativeType,narrativeLanguage,narrativeText)&$select=code,status,geography,activityArea,principalRecipient,periodStartDate,periodEndDate,narratives,totalSignedAmount_ReferenceRate,totalCommitmentAmount_ReferenceRate,totalDisbursedAmount_ReferenceRate&$orderby=status/statusName asc,periodStartDate desc" + "urlParams": "?$filter=&$count=true&$expand=status($select=statusName),geography($select=name),activityArea($select=name),principalRecipient($select=name),narratives($select=narrativeType,narrativeLanguage,narrativeText)&$select=code,status,geography,activityArea,principalRecipient,periodStartDate,periodEndDate,narratives,totalSignedAmount_ReferenceRate,totalCommitmentAmount_ReferenceRate,totalDisbursedAmount_ReferenceRate&$orderby=status/statusName asc,geography/name asc,code asc" } From a5fce5bedb2e74f891003aac51abf646eb4720e5 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 4 Jun 2024 13:04:41 +0300 Subject: [PATCH 08/44] feat: documents table type hierarchy --- src/config/mapping/documents/list.json | 3 +- src/controllers/documents.controller.ts | 37 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/config/mapping/documents/list.json b/src/config/mapping/documents/list.json index 019cb1b..5d2de89 100644 --- a/src/config/mapping/documents/list.json +++ b/src/config/mapping/documents/list.json @@ -1,8 +1,9 @@ { "dataPath": "value", "type": "documentType.parent.name", + "subType": "documentType.name", "title": "title", "url": "url", "geography": "geography.name", - "urlParams": "?$apply=filter()/groupby((geography/name,documentType/index,documentType/parent/name,title,url))&$orderby=documentType/index asc,title asc" + "urlParams": "?$apply=filter()/groupby((geography/name,documentType/index,documentType/parent/name,documentType/name,title,url))&$orderby=documentType/index asc,title asc" } diff --git a/src/controllers/documents.controller.ts b/src/controllers/documents.controller.ts index e58f598..780e588 100644 --- a/src/controllers/documents.controller.ts +++ b/src/controllers/documents.controller.ts @@ -80,20 +80,33 @@ export class DocumentsController { ); return { - data: _.map(groupedByLocation, (value, key) => { - const groupedByType = _.groupBy(value, DocumentsListMapping.type); + data: _.map(groupedByLocation, (locationObj, location) => { + const groupedByType = _.groupBy( + locationObj, + DocumentsListMapping.type, + ); return { - name: key, - documents: Object.keys(value).length, - _children: _.map(groupedByType, (value1, key) => ({ - name: key, - documents: Object.keys(value1).length, - _children: _.map(value1, value2 => ({ - name: _.get(value2, DocumentsListMapping.title, ''), - documents: _.get(value2, DocumentsListMapping.url, ''), - })), - })), + name: location, + documents: Object.keys(locationObj).length, + _children: _.map(groupedByType, (typeObj, type) => { + const groupedBySubType = _.groupBy( + typeObj, + DocumentsListMapping.subType, + ); + return { + name: type, + documents: Object.keys(typeObj).length, + _children: _.map(groupedBySubType, (subTypeObj, subType) => ({ + name: subType, + documents: Object.keys(subTypeObj).length, + _children: _.map(subTypeObj, doc => ({ + name: _.get(doc, DocumentsListMapping.title, ''), + documents: _.get(doc, DocumentsListMapping.url, ''), + })), + })), + }; + }), }; }), }; From 6e46c0bc92e3e3c0ca7622c42a534c8d7a319f7d Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 4 Jun 2024 19:38:39 +0300 Subject: [PATCH 09/44] feat: donors hierarchy --- .../mapping/pledgescontributions/bar.json | 3 +- .../pledgescontributions/sunburst.json | 5 +- .../pledgescontributions.controller.ts | 342 ++++++++++++------ 3 files changed, 244 insertions(+), 106 deletions(-) diff --git a/src/config/mapping/pledgescontributions/bar.json b/src/config/mapping/pledgescontributions/bar.json index fb99ce7..acf9ea2 100644 --- a/src/config/mapping/pledgescontributions/bar.json +++ b/src/config/mapping/pledgescontributions/bar.json @@ -8,10 +8,11 @@ "contributionsUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Contribution - Reference Rate')/groupby((periodCovered),aggregate(actualAmount with sum as value))&$orderby=periodCovered asc", "donorBarType": "donor.type.name", "donorBarDonor": "donor.name", + "donorBarType2": "donor.type.parent.name", "donorBarIndicatorField": "indicatorName", "donorBarIndicatorPledge": "Pledge - Reference Rate", "donorBarIndicatorContribution": "Contribution - Reference Rate", "donorBarIndicatorPledgeAmount": "plannedAmount", "donorBarIndicatorContributionAmount": "actualAmount", - "donorBarUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((donor/name,donor/type/name,indicatorName),aggregate(plannedAmount with sum as plannedAmount,actualAmount with sum as actualAmount))" + "donorBarUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((donor/name,donor/type/name,donor/type/parent/name,indicatorName),aggregate(plannedAmount with sum as plannedAmount,actualAmount with sum as actualAmount))" } diff --git a/src/config/mapping/pledgescontributions/sunburst.json b/src/config/mapping/pledgescontributions/sunburst.json index 3520224..37b8116 100644 --- a/src/config/mapping/pledgescontributions/sunburst.json +++ b/src/config/mapping/pledgescontributions/sunburst.json @@ -2,10 +2,11 @@ "dataPath": "value", "type": "donor.type.name", "donor": "donor.name", + "type2": "donor.type.parent.name", "indicatorField": "indicatorName", "indicatorPledge": "Pledge - Reference Rate", "indicatorContribution": "Contribution - Reference Rate", "amount": "amount", - "pledgeUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Pledge - Reference Rate')/groupby((donor/name,donor/type/name),aggregate(plannedAmount with sum as amount))", - "contributionUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Contribution - Reference Rate')/groupby((donor/name,donor/type/name),aggregate(actualAmount with sum as amount))" + "pledgeUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Pledge - Reference Rate')/groupby((donor/name,donor/type/name,donor/type/parent/name),aggregate(plannedAmount with sum as amount))", + "contributionUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName eq 'Contribution - Reference Rate')/groupby((donor/name,donor/type/name,donor/type/parent/name),aggregate(actualAmount with sum as amount))" } diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index 687bcc5..34bc3e5 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -189,11 +189,6 @@ export class PledgescontributionsController { [], ); - const groupedByIndicator = _.groupBy( - rawData, - PledgesContributionsBarFieldsMapping.donorBarType, - ); - const data: { name: string; value: number; @@ -205,45 +200,93 @@ export class PledgescontributionsController { }[]; }[] = []; - _.forEach(groupedByIndicator, (value, key) => { - const groupedByDonor = _.groupBy( - value, - PledgesContributionsBarFieldsMapping.donorBarDonor, - ); - data.push({ - name: key, - value: _.sumBy( - _.filter(value, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, - }), - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + const formatted: any[] = []; + + rawData.forEach((item: any) => { + const obj = { + indicatorName: _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarIndicatorField, + '', ), - value1: _.sumBy( - _.filter(value, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, - }), + actualAmount: _.get( + item, PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, + 0, ), - items: _.map(groupedByDonor, (donorValue, donorKey) => ({ - name: donorKey, - value: _.sumBy( - _.filter(donorValue, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, - }), - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, - ), - value1: _.sumBy( - _.filter(donorValue, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, - }), - PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, - ), - })), - }); + plannedAmount: _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + 0, + ), + donorType: '', + donorSubType: '', + donor: _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarDonor, + '', + ), + }; + if ( + _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType2, + null, + ) !== null + ) { + obj.donorType = _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType2, + '', + ); + obj.donorSubType = _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType, + '', + ); + } else { + obj.donorType = _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType, + '', + ); + } + formatted.push(obj); + }); + + const groupedByDonorType = _.groupBy(formatted, 'donorType'); + + _.forEach(groupedByDonorType, (value, key) => { + const groupedByDonorSubType = _.groupBy(value, 'donorSubType'); + const obj = { + name: key, + value: _.sumBy(value, 'actualAmount'), + value1: _.sumBy(value, 'plannedAmount'), + items: _.map( + groupedByDonorSubType, + (donorSubTypeValue, donorSubTypeKey) => { + const groupedByDonor = _.groupBy(donorSubTypeValue, 'donor'); + return { + name: donorSubTypeKey, + value: _.sumBy(donorSubTypeValue, 'actualAmount'), + value1: _.sumBy(donorSubTypeValue, 'plannedAmount'), + items: _.map(groupedByDonor, (donorValue, donorKey) => ({ + name: donorKey, + value: _.sumBy(donorValue, 'actualAmount'), + value1: _.sumBy(donorValue, 'plannedAmount'), + })), + }; + }, + ), + }; + const nullObj = _.find(obj.items, {name: ''}); + if (nullObj) { + nullObj.items.forEach((item: any) => { + obj.items.push(item); + }); + obj.items = _.filter(obj.items, item => item.name !== ''); + } + data.push(obj); }); return {data}; @@ -270,11 +313,6 @@ export class PledgescontributionsController { [], ); - const groupedByDonorType = _.groupBy( - rawData, - PledgesContributionsSunburstFieldsMapping.type, - ); - const data: { name: string; value: number; @@ -284,25 +322,80 @@ export class PledgescontributionsController { }[]; }[] = []; + const formatted: any[] = []; + + rawData.forEach((item: any) => { + const obj = { + value: _.get( + item, + PledgesContributionsSunburstFieldsMapping.amount, + 0, + ), + donorType: '', + donorSubType: '', + donor: _.get( + item, + PledgesContributionsSunburstFieldsMapping.donor, + '', + ), + }; + if ( + _.get( + item, + PledgesContributionsSunburstFieldsMapping.type2, + null, + ) !== null + ) { + obj.donorType = _.get( + item, + PledgesContributionsSunburstFieldsMapping.type2, + '', + ); + obj.donorSubType = _.get( + item, + PledgesContributionsSunburstFieldsMapping.type, + '', + ); + } else { + obj.donorType = _.get( + item, + PledgesContributionsSunburstFieldsMapping.type, + '', + ); + } + formatted.push(obj); + }); + + const groupedByDonorType = _.groupBy(formatted, 'donorType'); + _.forEach(groupedByDonorType, (value, key) => { - const groupedByDonor = _.groupBy( - value, - PledgesContributionsSunburstFieldsMapping.donor, - ); - data.push({ + const groupedByDonorSubType = _.groupBy(value, 'donorSubType'); + const obj = { name: key, - value: _.sumBy( - value, - PledgesContributionsSunburstFieldsMapping.amount, + value: _.sumBy(value, 'value'), + children: _.map( + groupedByDonorSubType, + (donorSubTypeValue, donorSubTypeKey) => { + const groupedByDonor = _.groupBy(donorSubTypeValue, 'donor'); + return { + name: donorSubTypeKey, + value: _.sumBy(donorSubTypeValue, 'value'), + children: _.map(groupedByDonor, (donorValue, donorKey) => ({ + name: donorKey, + value: _.sumBy(donorValue, 'value'), + })), + }; + }, ), - children: _.map(groupedByDonor, (donorValue, donorKey) => ({ - name: donorKey, - value: _.sumBy( - donorValue, - PledgesContributionsSunburstFieldsMapping.amount, - ), - })), - }); + }; + const nullObj = _.find(obj.children, {name: ''}); + if (nullObj) { + nullObj.children.forEach((item: any) => { + obj.children.push(item); + }); + obj.children = _.filter(obj.children, item => item.name !== ''); + } + data.push(obj); }); return {data}; @@ -328,11 +421,6 @@ export class PledgescontributionsController { [], ); - const groupedByIndicator = _.groupBy( - rawData, - PledgesContributionsBarFieldsMapping.donorBarType, - ); - const data: { name: string; pledge: number; @@ -344,45 +432,93 @@ export class PledgescontributionsController { }[]; }[] = []; - _.forEach(groupedByIndicator, (value, key) => { - const groupedByDonor = _.groupBy( - value, - PledgesContributionsBarFieldsMapping.donorBarDonor, - ); - data.push({ - name: key, - pledge: _.sumBy( - _.filter(value, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, - }), - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + const formatted: any[] = []; + + rawData.forEach((item: any) => { + const obj = { + indicatorName: _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarIndicatorField, + '', ), - contribution: _.sumBy( - _.filter(value, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, - }), + actualAmount: _.get( + item, PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, + 0, ), - _children: _.map(groupedByDonor, (donorValue, donorKey) => ({ - name: donorKey, - pledge: _.sumBy( - _.filter(donorValue, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledge, - }), - PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, - ), - contribution: _.sumBy( - _.filter(donorValue, { - [PledgesContributionsBarFieldsMapping.donorBarIndicatorField]: - PledgesContributionsBarFieldsMapping.donorBarIndicatorContribution, - }), - PledgesContributionsBarFieldsMapping.donorBarIndicatorContributionAmount, - ), - })), - }); + plannedAmount: _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarIndicatorPledgeAmount, + 0, + ), + donorType: '', + donorSubType: '', + donor: _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarDonor, + '', + ), + }; + if ( + _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType2, + null, + ) !== null + ) { + obj.donorType = _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType2, + '', + ); + obj.donorSubType = _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType, + '', + ); + } else { + obj.donorType = _.get( + item, + PledgesContributionsBarFieldsMapping.donorBarType, + '', + ); + } + formatted.push(obj); + }); + + const groupedByDonorType = _.groupBy(formatted, 'donorType'); + + _.forEach(groupedByDonorType, (value, key) => { + const groupedByDonorSubType = _.groupBy(value, 'donorSubType'); + const obj = { + name: key, + pledge: _.sumBy(value, 'actualAmount'), + contribution: _.sumBy(value, 'plannedAmount'), + _children: _.map( + groupedByDonorSubType, + (donorSubTypeValue, donorSubTypeKey) => { + const groupedByDonor = _.groupBy(donorSubTypeValue, 'donor'); + return { + name: donorSubTypeKey, + pledge: _.sumBy(donorSubTypeValue, 'actualAmount'), + contribution: _.sumBy(donorSubTypeValue, 'plannedAmount'), + _children: _.map(groupedByDonor, (donorValue, donorKey) => ({ + name: donorKey, + pledge: _.sumBy(donorValue, 'actualAmount'), + contribution: _.sumBy(donorValue, 'plannedAmount'), + })), + }; + }, + ), + }; + const nullObj = _.find(obj._children, {name: ''}); + if (nullObj) { + nullObj._children.forEach((item: any) => { + obj._children.push(item); + }); + obj._children = _.filter(obj._children, item => item.name !== ''); + } + data.push(obj); }); return {data}; From 725f1d17034aa30320883d9a7c8613ae2fe6df48 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 11 Jun 2024 16:33:07 +0300 Subject: [PATCH 10/44] feat: feedback changes --- src/config/mapping/budgets/sankey.json | 7 +- src/config/mapping/budgets/table.json | 8 +- src/config/mapping/expenditures/heatmap.json | 1 + src/controllers/allocations.controller.ts | 28 ++--- src/controllers/budgets.controller.ts | 126 ++++++++++++------- src/controllers/expenditures.controller.ts | 14 ++- 6 files changed, 110 insertions(+), 74 deletions(-) diff --git a/src/config/mapping/budgets/sankey.json b/src/config/mapping/budgets/sankey.json index a060d46..babdc02 100644 --- a/src/config/mapping/budgets/sankey.json +++ b/src/config/mapping/budgets/sankey.json @@ -1,9 +1,10 @@ { "dataPath": "value", - "level1Field": ".parent.name", - "level2Field": ".name", + "level1Field": "financialCategory.parent.parent.name", + "level2Field": "financialCategory.parent.name", + "level3Field": "financialCategory.name", "valueField": "value", "cycle": "periodFrom", "nodeColors": ["#252C34", "#252C34", "#252C34"], - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/name,/name),aggregate(plannedAmount with sum as value))" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((financialCategory/name,financialCategory/parent/name,financialCategory/parent/parent/name),aggregate(plannedAmount with sum as value))" } diff --git a/src/config/mapping/budgets/table.json b/src/config/mapping/budgets/table.json index 0e1b4f6..82daa20 100644 --- a/src/config/mapping/budgets/table.json +++ b/src/config/mapping/budgets/table.json @@ -1,9 +1,9 @@ { "dataPath": "value", - "parentField": ".parent.name", - "childrenField": ".name", + "level1Field": "financialCategory.parent.parent.name", + "level2Field": "financialCategory.parent.name", + "level3Field": "financialCategory.name", "valueField": "value", "cycle": "periodFrom", - "countField": "count", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/name,/name),aggregate(plannedAmount with sum as value,implementationPeriod/grantId with countdistinct as count))" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((financialCategory/name,financialCategory/parent/name,financialCategory/parent/parent/name),aggregate(plannedAmount with sum as value))" } diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index 5dddb53..be7c26a 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -6,6 +6,7 @@ "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true)/groupby((,),aggregate(actualAmountCumulative with sum as value1,plannedAmountCumulative with sum as value2))", "fields": { "principalRecipient": "implementationPeriod/grant/principalRecipient/name", + "principalRecipientSubType": "implementationPeriod/grant/principalRecipient/parent/name", "principalRecipientType": "implementationPeriod/grant/principalRecipient/type/name", "component": "/parent/parent/name" } diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index f352116..e78ba66 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -417,28 +417,16 @@ export class AllocationsController { async allocationsRadialChartInLocation( @param.path.string('countryCode') countryCode: string, ) { - let filterString = AllocationRadialFieldsMapping.urlParams; - if (this.req.query.cycle && countryCode) { - filterString = filterString.replace( - '', - ` AND ${AllocationRadialFieldsMapping.cycle} eq '${this.req.query.cycle}' AND ${AllocationRadialFieldsMapping.countryCode} eq '${countryCode}'`, - ); - } else if (this.req.query.cycle && !countryCode) { - filterString = filterString.replace( - '', - ` AND ${AllocationRadialFieldsMapping.cycle} eq '${this.req.query.cycle}'`, - ); - } else if (!this.req.query.cycle && countryCode) { - filterString = filterString.replace( - '', - ` AND ${AllocationRadialFieldsMapping.countryCode} eq '${countryCode}'`, - ); - } else { - filterString = filterString.replace('', ''); - } + let filterString = filterFinancialIndicators( + {...this.req.query, geographies: countryCode}, + AllocationRadialFieldsMapping.urlParams, + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; - return getAllocationsData(url); + const data = await getAllocationsData(url); + + return {data}; } @get('/allocations/cycles') diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 9ad02a5..8ae5626 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -123,15 +123,12 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/sankey/{componentField}') + @get('/budgets/sankey') @response(200) - async sankey(@param.path.string('componentField') componentField: string) { + async sankey() { const filterString = filterFinancialIndicators( this.req.query, - BudgetsSankeyFieldsMapping.urlParams.replace( - //g, - componentField, - ), + BudgetsSankeyFieldsMapping.urlParams, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -157,10 +154,7 @@ export class BudgetsController { }; const groupedDataLevel1 = _.groupBy( rawData, - BudgetsSankeyFieldsMapping.level1Field.replace( - '', - componentField, - ), + BudgetsSankeyFieldsMapping.level1Field, ); _.forEach(groupedDataLevel1, (level1Data, level1) => { data.nodes.push({ @@ -175,12 +169,10 @@ export class BudgetsController { target: level1, value: _.sumBy(level1Data, BudgetsSankeyFieldsMapping.valueField), }); + const groupedDataLevel2 = _.groupBy( level1Data, - BudgetsSankeyFieldsMapping.level2Field.replace( - '', - componentField, - ), + BudgetsSankeyFieldsMapping.level2Field, ); _.forEach(groupedDataLevel2, (level2Data, level2) => { const level2inLevel1 = _.find(data.nodes, { @@ -199,6 +191,32 @@ export class BudgetsController { target: level2inLevel1 ? `${level2}1` : level2, value: _.sumBy(level2Data, BudgetsSankeyFieldsMapping.valueField), }); + + const groupedDataLevel3 = _.groupBy( + level2Data, + BudgetsSankeyFieldsMapping.level3Field, + ); + _.forEach(groupedDataLevel3, (level3Data, level3) => { + const level3inLevel2 = _.find(data.nodes, { + name: level3, + level: 2, + }); + data.nodes.push({ + name: level3inLevel2 ? `${level3}1` : level3, + level: 3, + itemStyle: { + color: BudgetsSankeyFieldsMapping.nodeColors[2], + }, + }); + data.links.push({ + source: level2, + target: level3inLevel2 ? `${level3}1` : level3, + value: _.sumBy( + level3Data, + BudgetsSankeyFieldsMapping.valueField, + ), + }); + }); }); }); data.nodes = _.uniqBy(data.nodes, 'name'); @@ -266,15 +284,12 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/table/{componentField}') + @get('/budgets/table') @response(200) - async table(@param.path.string('componentField') componentField: string) { + async table() { const filterString = filterFinancialIndicators( this.req.query, - BudgetsTableFieldsMapping.urlParams.replace( - //g, - componentField, - ), + BudgetsTableFieldsMapping.urlParams, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -286,45 +301,64 @@ export class BudgetsController { BudgetsTableFieldsMapping.dataPath, [], ); - const groupedByParent = _.groupBy( + const groupedByLevel1 = _.groupBy( rawData, - BudgetsTableFieldsMapping.parentField.replace( - '', - componentField, - ), + BudgetsTableFieldsMapping.level1Field, ); const data: { name: string; - grants: number; amount: number; _children: { name: string; - grants: number; amount: number; + _children: { + name: string; + amount: number; + }[]; }[]; }[] = []; - _.forEach(groupedByParent, (parentData, parent) => { - const children = parentData.map((child: any) => { - return { - name: _.get( - child, - BudgetsTableFieldsMapping.childrenField.replace( - '', - componentField, - ), - '', - ), - grants: _.get(child, BudgetsTableFieldsMapping.countField, 0), - amount: _.get(child, BudgetsTableFieldsMapping.valueField, 0), - }; - }); + _.forEach(groupedByLevel1, (level1Data, level1) => { + const grouepdByLevel2 = _.groupBy( + level1Data, + BudgetsTableFieldsMapping.level2Field, + ); + const level1Amount = _.sumBy( + level1Data, + BudgetsTableFieldsMapping.valueField, + ); + const level1Children = _.map( + grouepdByLevel2, + (level2Data, level2) => { + const groupedByLevel3 = _.groupBy( + level2Data, + BudgetsTableFieldsMapping.level3Field, + ); + const level2Amount = _.sumBy( + level2Data, + BudgetsTableFieldsMapping.valueField, + ); + return { + name: level2, + amount: level2Amount, + _children: _.map(groupedByLevel3, (level3Data, level3) => { + const level3Amount = _.sumBy( + level3Data, + BudgetsTableFieldsMapping.valueField, + ); + return { + name: level3, + amount: level3Amount, + }; + }), + }; + }, + ); data.push({ - name: parent, - grants: _.sumBy(children, 'grants'), - amount: _.sumBy(children, 'amount'), - _children: children, + name: level1, + amount: level1Amount, + _children: level1Children, }); }); diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index adf2651..5813370 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -23,6 +23,7 @@ export class ExpendituresController { let filterString = ExpendituresHeatmapMapping.urlParams; let rowField = ''; let subRowField = ''; + let subSubRowField = ''; let columnField = ''; let subColumnField = ''; if (row.split(',').length > 1) { @@ -36,6 +37,13 @@ export class ExpendituresController { `fields["${row.split(',')[1]}"]`, ExpendituresHeatmapMapping.fields.principalRecipient, ); + if (row.split(',').length > 2) { + subSubRowField = _.get( + ExpendituresHeatmapMapping, + `fields["${row.split(',')[2]}"]`, + ExpendituresHeatmapMapping.fields.principalRecipient, + ); + } } else { rowField = _.get( ExpendituresHeatmapMapping, @@ -61,9 +69,12 @@ export class ExpendituresController { ExpendituresHeatmapMapping.fields.component, ).replace(//g, componentField); } + const rowFieldArray = [rowField, subRowField, subSubRowField].filter( + item => item.length > 0, + ); filterString = filterString.replace( '', - [rowField, subRowField].join(',').replace(/(^,)|(,$)/g, ''), + rowFieldArray.join(',').replace(/(^,)|(,$)/g, ''), ); filterString = filterString.replace( '', @@ -75,6 +86,7 @@ export class ExpendituresController { rowField = rowField.replace(/\//g, '.'); subRowField = subRowField.replace(/\//g, '.'); + subSubRowField = subSubRowField.replace(/\//g, '.'); columnField = columnField.replace(/\//g, '.'); subColumnField = subColumnField.replace(/\//g, '.'); From 03210d9f0b95f8fd40400d408dc7ff5e3b1e3df0 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 12 Jun 2024 15:40:43 +0300 Subject: [PATCH 11/44] feat: data additions for chart tooltips --- src/config/mapping/allocations/radial.json | 3 ++- src/controllers/allocations.controller.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/config/mapping/allocations/radial.json b/src/config/mapping/allocations/radial.json index 317a317..2832b38 100644 --- a/src/config/mapping/allocations/radial.json +++ b/src/config/mapping/allocations/radial.json @@ -5,7 +5,8 @@ "tooltipItem": "geography.parent.parent.name", "value": "value", "countryCode": "geography/code", - "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((activityArea/name),aggregate(actualAmount with sum as value))", + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((activityArea/name,geography/parent/parent/name),aggregate(actualAmount with sum as value))", + "urlParamsLocation": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((activityArea/name),aggregate(actualAmount with sum as value))", "countriesCountUrlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((geography/name))", "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD"] } diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index e78ba66..8533fa9 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -81,6 +81,9 @@ async function getAllocationsData(url: string) { items: groupedByName[name].map((item: any) => ({ name: _.get(item, AllocationRadialFieldsMapping.tooltipItem, ''), value: _.get(item, AllocationRadialFieldsMapping.value, 0), + percentage: + (_.get(item, AllocationRadialFieldsMapping.value, 0) / value) * + 100, })), }, }); @@ -419,7 +422,7 @@ export class AllocationsController { ) { let filterString = filterFinancialIndicators( {...this.req.query, geographies: countryCode}, - AllocationRadialFieldsMapping.urlParams, + AllocationRadialFieldsMapping.urlParamsLocation, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; From 28a6d07845dd518476d1bedadca13670ca332327 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 12 Jun 2024 15:41:02 +0300 Subject: [PATCH 12/44] chore: add more line chart colors --- src/config/mapping/disbursements/lineChart.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/mapping/disbursements/lineChart.json b/src/config/mapping/disbursements/lineChart.json index fc9a4f2..d845a53 100644 --- a/src/config/mapping/disbursements/lineChart.json +++ b/src/config/mapping/disbursements/lineChart.json @@ -6,5 +6,5 @@ "line": ".name", "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,/name),aggregate(actualAmount with sum as value))", "activitiesCountUrlParams": "?$count=true&$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/grant/code))", - "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD"] + "colors": ["#0A2840", "#DEE1E7", "#013E77", "#00B5AE", "#C3EDFD", "#D9D9D9"] } From a4ec9203ef9ab93734734cec773acd0ffa7e3733 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 12 Jun 2024 15:41:17 +0300 Subject: [PATCH 13/44] feat: revamp expenditures heatmap logic --- src/controllers/expenditures.controller.ts | 215 ++++++++------------- 1 file changed, 84 insertions(+), 131 deletions(-) diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index 5813370..5156410 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -94,147 +94,100 @@ export class ExpendituresController { .get(url) .then((resp: AxiosResponse) => { const raw = _.get(resp.data, ExpendituresHeatmapMapping.dataPath, []); - const data = _.filter( - raw.map((item: any) => { - const budget = _.get(item, ExpendituresHeatmapMapping.budget, 0); - const expenditure = _.get( - item, - ExpendituresHeatmapMapping.expenditure, - 0, - ); - return { - row: subRowField - ? _.get(item, subRowField, '') - : _.get(item, rowField), - parentRow: subRowField ? _.get(item, rowField) : undefined, - column: subColumnField - ? _.get(item, subColumnField, '') - : _.get(item, columnField), - parentColumn: subColumnField - ? _.get(item, columnField) - : undefined, - value: expenditure, - budget, - percentage: - budget && budget > 0 - ? Math.round((expenditure / budget) * 100) - : 120, - }; - }), - (item: any) => item.row && item.column, - ); - const rows = _.uniq(data.map((item: any) => item.row)); - const columns = _.uniq(data.map((item: any) => item.column)); - const parentRows = _.uniq( - _.filter( - data.map((item: any) => item.parentRow), - (item: any) => item, - ), - ); - const parentColumns = _.uniq( - _.filter( - data.map((item: any) => item.parentColumn), - (item: any) => item, - ), + let data: { + row: string; + column: string; + budget: number; + value: number; + percentage: number; + parentRow?: string; + parentColumn?: string; + }[] = []; + + const columnDimensionArray = [columnField, subColumnField].filter( + item => item.length > 0, ); + const rowDimensionArray = [ + rowField, + subRowField, + subSubRowField, + ].filter(item => item.length > 0); - if (parentRows.length > 0) { - parentRows.forEach((parentRow: any) => { - columns.forEach((column: any) => { - const expenditure = _.sumBy( - data.filter( - (item: any) => - item.parentRow === parentRow && item.column === column, - ), - 'value', + rowDimensionArray.forEach((rowDimensionValue, rowIndex) => { + columnDimensionArray.forEach((columnDimensionValue, columnIndex) => { + raw.forEach((item: any) => { + const row = _.get(item, rowDimensionValue, ''); + const column = _.get(item, columnDimensionValue, ''); + const expenditureAmount = _.get( + item, + ExpendituresHeatmapMapping.expenditure, + 0, ); - const budget = _.sumBy( - data.filter( - (item: any) => - item.parentRow === parentRow && item.column === column, - ), - 'budget', + const budgetAmount = _.get( + item, + ExpendituresHeatmapMapping.budget, + 0, ); - data.push({ - row: parentRow, - column, - value: expenditure, - budget, - percentage: - budget && budget > 0 - ? Math.round((expenditure / budget) * 100) - : 120, - }); - }); - }); - } - if (parentColumns.length > 0) { - console.log(1.1, parentColumns.length); - parentColumns.forEach((parentColumn: any) => { - rows.forEach((row: any) => { - const expenditure = _.sumBy( - data.filter( - (item: any) => - item.parentColumn === parentColumn && item.row === row, - ), - 'value', + const fItemIndex = _.findIndex( + data, + dataItem => dataItem.row === row && dataItem.column === column, ); - const budget = _.sumBy( - data.filter( - (item: any) => - item.parentColumn === parentColumn && item.row === row, - ), - 'budget', - ); - data.push({ - row, - column: parentColumn, - value: expenditure, - budget, - percentage: - budget && budget > 0 - ? Math.round((expenditure / budget) * 100) - : 120, - }); - }); - }); - } - if (parentRows.length > 0 && parentColumns.length > 0) { - console.log(1.2, parentRows.length, parentColumns.length); - parentRows.forEach((parentRow: any) => { - parentColumns.forEach((parentColumn: any) => { - const expenditure = _.sumBy( - data.filter( - (item: any) => - item.parentRow === parentRow && - item.parentColumn === parentColumn, - ), - 'value', - ); - const budget = _.sumBy( - data.filter( - (item: any) => - item.parentRow === parentRow && - item.parentColumn === parentColumn, - ), - 'budget', - ); - data.push({ - row: parentRow, - column: parentColumn, - value: expenditure, - budget, - percentage: - budget && budget > 0 - ? Math.round((expenditure / budget) * 100) - : 120, - }); + if (fItemIndex > -1) { + data[fItemIndex].budget += budgetAmount; + data[fItemIndex].value += expenditureAmount; + data[fItemIndex].percentage = + data[fItemIndex].budget > 0 + ? (data[fItemIndex].value / data[fItemIndex].budget) * 100 + : 120; + data[fItemIndex].percentage = Math.round( + data[fItemIndex].percentage, + ); + if (data[fItemIndex].percentage > 120) { + data[fItemIndex].percentage = 120; + } + } else if (row && column) { + let parentRow = + rowIndex > 0 + ? _.get(item, rowDimensionArray[rowIndex - 1]) + : undefined; + if ( + (parentRow === 'null' || parentRow === null) && + rowIndex > 0 + ) { + parentRow = _.get(item, rowDimensionArray[rowIndex - 2]); + } + if (parentRow === 'null' || parentRow === null) { + parentRow = undefined; + } + let parentColumn = + columnIndex > 0 + ? _.get(item, columnDimensionArray[columnIndex - 1]) + : undefined; + if (parentColumn === 'null' || parentColumn === null) { + parentColumn = undefined; + } + data.push({ + row, + parentRow, + column, + parentColumn, + budget: budgetAmount, + value: expenditureAmount, + percentage: + budgetAmount && budgetAmount > 0 + ? Math.round((expenditureAmount / budgetAmount) * 100) + : 120, + }); + if (data[data.length - 1].percentage > 120) { + data[data.length - 1].percentage = 120; + } + } }); }); - } + }); return {data}; }) From 1d27806db6dd8548e68e3852258b88f2877a16f1 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 13 Jun 2024 12:35:30 +0300 Subject: [PATCH 14/44] feat: filtering adjustments --- src/controllers/allocations.controller.ts | 19 +++++- src/controllers/budgets.controller.ts | 61 +++++++++++++------ src/controllers/disbursements.controller.ts | 10 +++ src/controllers/expenditures.controller.ts | 11 +++- .../pledgescontributions.controller.ts | 23 ++++++- src/utils/filtering/financialIndicators.ts | 40 +++++++----- 6 files changed, 128 insertions(+), 36 deletions(-) diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index 8533fa9..07f6ec0 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -104,6 +104,8 @@ export class AllocationsController { let filterString = filterFinancialIndicators( this.req.query, AllocationCumulativeByCyclesFieldsMapping.urlParams, + 'geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -187,6 +189,8 @@ export class AllocationsController { const filterString = filterFinancialIndicators( this.req.query, AllocationSunburstFieldsMapping.urlParams, + 'geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -229,7 +233,12 @@ export class AllocationsController { urlParams = AllocationTreemapFieldsMapping.urlParams[1]; } } - const filterString = filterFinancialIndicators(this.req.query, urlParams); + const filterString = filterFinancialIndicators( + this.req.query, + urlParams, + 'geography/name', + 'activityArea/name', + ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; return axios @@ -301,6 +310,8 @@ export class AllocationsController { const filterString = filterFinancialIndicators( this.req.query, AllocationTableFieldsMapping.urlParams, + 'geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -395,10 +406,14 @@ export class AllocationsController { let filterString = filterFinancialIndicators( this.req.query, AllocationRadialFieldsMapping.urlParams, + 'geography/name', + 'activityArea/name', ); let filterString2 = filterFinancialIndicators( this.req.query, AllocationRadialFieldsMapping.countriesCountUrlParams, + 'geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -423,6 +438,8 @@ export class AllocationsController { let filterString = filterFinancialIndicators( {...this.req.query, geographies: countryCode}, AllocationRadialFieldsMapping.urlParamsLocation, + 'geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 8ae5626..1a947ee 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -104,6 +104,8 @@ export class BudgetsController { const filterString = filterFinancialIndicators( this.req.query, BudgetsRadialFieldsMapping.urlParams, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -129,6 +131,8 @@ export class BudgetsController { const filterString = filterFinancialIndicators( this.req.query, BudgetsSankeyFieldsMapping.urlParams, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -243,6 +247,8 @@ export class BudgetsController { '', componentField, ), + 'implementationPeriod/grant/geography/name', + `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -290,6 +296,8 @@ export class BudgetsController { const filterString = filterFinancialIndicators( this.req.query, BudgetsTableFieldsMapping.urlParams, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -375,26 +383,31 @@ export class BudgetsController { .then((resp: AxiosResponse) => { const rawData = _.get(resp.data, BudgetsCyclesMapping.dataPath, []); - const data = _.map( - _.filter( - rawData, - item => _.get(item, BudgetsCyclesMapping.cycleFrom, null) !== null, - ), - (item, index) => { - const from = _.get(item, BudgetsCyclesMapping.cycleFrom, ''); - const to = _.get(item, BudgetsCyclesMapping.cycleTo, ''); + const data = _.orderBy( + _.map( + _.filter( + rawData, + item => + _.get(item, BudgetsCyclesMapping.cycleFrom, null) !== null, + ), + (item, index) => { + const from = _.get(item, BudgetsCyclesMapping.cycleFrom, ''); + const to = _.get(item, BudgetsCyclesMapping.cycleTo, ''); - let value = from; + let value = from; - if (from && to) { - value = `${from} - ${to}`; - } + if (from && to) { + value = `${from} - ${to}`; + } - return { - name: `Cycle ${index + 1}`, - value, - }; - }, + return { + name: `Cycle ${index + 1}`, + value, + }; + }, + ), + item => parseInt(item.value.toString().split(' - ')[0], 10), + 'desc', ); return {data}; @@ -419,6 +432,8 @@ export class BudgetsController { //g, componentField, ), + 'implementationPeriod/grant/geography/name', + `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -461,10 +476,14 @@ export class BudgetsController { const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -627,10 +646,14 @@ export class BudgetsController { const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -761,10 +784,14 @@ export class BudgetsController { const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index 97cb82e..c5fe927 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -190,6 +190,8 @@ export class DisbursementsController { const filterString = filterFinancialIndicators( this.req.query, FinancialInsightsStatsMapping.urlParams, + 'implementationPeriod/grant/geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -244,6 +246,8 @@ export class DisbursementsController { '', componentField, ), + 'implementationPeriod/grant/geography/name', + `${componentField}/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -284,10 +288,14 @@ export class DisbursementsController { '', componentField, ), + 'implementationPeriod/grant/geography/name', + `${componentField}/name`, ); const filterString2 = filterFinancialIndicators( this.req.query, LineChartFieldsMapping.activitiesCountUrlParams, + 'implementationPeriod/grant/geography/name', + `${componentField}/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -341,6 +349,8 @@ export class DisbursementsController { const filterString = filterFinancialIndicators( this.req.query, TableFieldsMapping.urlParams.replace('', componentField), + 'implementationPeriod/grant/geography/name', + `${componentField}/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index 5156410..e2d1ce3 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -80,7 +80,12 @@ export class ExpendituresController { '', [columnField, subColumnField].join(',').replace(/(^,)|(,$)/g, ''), ); - filterString = filterFinancialIndicators(this.req.query, filterString); + filterString = filterFinancialIndicators( + this.req.query, + filterString, + 'implementationPeriod/grant/geography/name', + `${componentField}/parent/parent/name`, + ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -205,6 +210,8 @@ export class ExpendituresController { //g, componentField, ), + 'implementationPeriod/grant/geography/name', + `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -253,6 +260,8 @@ export class ExpendituresController { //g, componentField, ), + 'implementationPeriod/grant/geography/name', + `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index 34bc3e5..fe095e2 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -112,10 +112,14 @@ export class PledgescontributionsController { const filterString1 = filterFinancialIndicators( this.req.query, PledgesContributionsStatsFieldsMapping.totalValuesUrlParams, + 'donor/geography/name', + 'activityArea/name', ); const filterString2 = filterFinancialIndicators( this.req.query, PledgesContributionsStatsFieldsMapping.donorTypesCountUrlParams, + 'donor/geography/name', + 'activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -177,6 +181,8 @@ export class PledgescontributionsController { const filterString = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.donorBarUrlParams, + 'donor/geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -301,7 +307,12 @@ export class PledgescontributionsController { type === 'pledge' ? PledgesContributionsSunburstFieldsMapping.pledgeUrlParams : PledgesContributionsSunburstFieldsMapping.contributionUrlParams; - const filterString = filterFinancialIndicators(this.req.query, urlParams); + const filterString = filterFinancialIndicators( + this.req.query, + urlParams, + 'donor/geography/name', + 'activityArea/name', + ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; return axios @@ -409,6 +420,8 @@ export class PledgescontributionsController { const filterString = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.donorBarUrlParams, + 'donor/geography/name', + 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -532,10 +545,14 @@ export class PledgescontributionsController { let filterString1 = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.pledgesUrlParams, + 'donor/geography/name', + 'activityArea/name', ); let filterString2 = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.contributionsUrlParams, + 'donor/geography/name', + 'activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -554,6 +571,8 @@ export class PledgescontributionsController { geographies: countryCode, }, PledgesContributionsBarFieldsMapping.pledgesUrlParams, + 'donor/geography/name', + 'activityArea/name', ); let filterString2 = filterFinancialIndicators( { @@ -561,6 +580,8 @@ export class PledgescontributionsController { geographies: countryCode, }, PledgesContributionsBarFieldsMapping.contributionsUrlParams, + 'donor/geography/name', + 'activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; diff --git a/src/utils/filtering/financialIndicators.ts b/src/utils/filtering/financialIndicators.ts index ec66b2c..dab5a00 100644 --- a/src/utils/filtering/financialIndicators.ts +++ b/src/utils/filtering/financialIndicators.ts @@ -1,6 +1,5 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; -import {getGeographyValues} from './geographies'; const MAPPING = { geography: [ @@ -9,6 +8,12 @@ const MAPPING = { 'implementationPeriod/grant/geography/code', 'implementationPeriod/grant/geography/name', ], + component: [ + 'activityArea/name', + 'activityAreaGroup/name', + 'activityArea/parent/parent/name', + 'activityAreaGroup/parent/parent/name', + ], donor: 'donor/name', donorType: 'donor/type/name', principalRecipient: 'implementationPeriod/grant/principalRecipient/name', @@ -25,26 +30,29 @@ const MAPPING = { export function filterFinancialIndicators( params: Record, urlParams: string, + geographyMapping: string, + componentMapping: string, ): string { let str = ''; - const geos = _.filter( + const geographies = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ); - const geographies = geos.map((geography: string) => `'${geography}'`); - if (geos.length > 0) { - const values: string[] = [...geographies, ...getGeographyValues(geos)]; - if (MAPPING.geography instanceof Array) { - str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography - .map( - m => - `${m}${filtering.in}(${values.join( - filtering.multi_param_separator, - )})`, - ) - .join(' OR ')})`; - } + ).map((geography: string) => `'${geography}'`); + if (geographies.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${geographyMapping}${ + filtering.in + }(${geographies.join(filtering.multi_param_separator)})`; + } + + const components = _.filter( + _.get(params, 'components', '').split(','), + (o: string) => o.length > 0, + ).map((component: string) => `'${component}'`); + if (components.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${componentMapping}${ + filtering.in + }(${components.join(filtering.multi_param_separator)})`; } const donors = _.filter( From bddd8b9fcd732bb012eae072c26c319acfb7d780 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 13 Jun 2024 13:43:41 +0300 Subject: [PATCH 15/44] feat: adjustments --- src/config/mapping/location/pieCharts.json | 8 +++---- src/controllers/allocations.controller.ts | 2 +- src/controllers/budgets.controller.ts | 5 ++++- src/controllers/disbursements.controller.ts | 5 ++++- src/controllers/location.controller.ts | 12 ++++++++--- src/utils/filtering/financialIndicators.ts | 24 +++++++++++++++------ 6 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/config/mapping/location/pieCharts.json b/src/config/mapping/location/pieCharts.json index 7584690..f485c86 100644 --- a/src/config/mapping/location/pieCharts.json +++ b/src/config/mapping/location/pieCharts.json @@ -1,11 +1,11 @@ { "dataPath": "value", "count": "count", - "pie1Field": "activityArea.name", - "pie2Field": "principalRecipient.name", + "pie1Field": "implementationPeriod.grant.activityArea.name", + "pie2Field": "implementationPeriod.grant.principalRecipient.name", "pie3Field": "financialDataSet", "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#F3F5F4"], - "pie1UrlParams": "?$apply=filter(geography/code eq '')/groupby((activityArea/name),aggregate(id with countdistinct as count))", - "pie2UrlParams": "?$apply=filter(geography/code eq '')/groupby((principalRecipient/name),aggregate(id with countdistinct as count))", + "pie1UrlParams": "?$apply=filter(implementationPeriod/grant/geography/code eq '' AND financialDataSet in ('Disbursement_ReferenceRate'))/groupby((implementationPeriod/grant/activityArea/name),aggregate(actualAmount with sum as count))", + "pie2UrlParams": "?$apply=filter(implementationPeriod/grant/geography/code eq '' AND financialDataSet in ('Disbursement_ReferenceRate'))/groupby((implementationPeriod/grant/principalRecipient/name),aggregate(actualAmount with sum as count))", "pie3UrlParams": "?$apply=filter(implementationPeriod/grant/geography/code eq '' AND financialDataSet in ('Disbursement_ReferenceRate', 'Commitment_ReferenceRate'))/groupby((financialDataSet),aggregate(actualAmount with sum as count))" } diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index 07f6ec0..6eb9b7e 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -438,7 +438,7 @@ export class AllocationsController { let filterString = filterFinancialIndicators( {...this.req.query, geographies: countryCode}, AllocationRadialFieldsMapping.urlParamsLocation, - 'geography/name', + ['geography/name', 'geography/code'], 'activityArea/name', ); diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 1a947ee..643e412 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -131,7 +131,10 @@ export class BudgetsController { const filterString = filterFinancialIndicators( this.req.query, BudgetsSankeyFieldsMapping.urlParams, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index c5fe927..2bd4b31 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -288,7 +288,10 @@ export class DisbursementsController { '', componentField, ), - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], `${componentField}/name`, ); const filterString2 = filterFinancialIndicators( diff --git a/src/controllers/location.controller.ts b/src/controllers/location.controller.ts index 1ce7723..c951a79 100644 --- a/src/controllers/location.controller.ts +++ b/src/controllers/location.controller.ts @@ -47,8 +47,8 @@ export class LocationController { filterString1 = filterString1.replace('', ''); filterString2 = filterString2.replace('', ''); filterString3 = filterString3.replace('', ''); - const url1 = `${urls.GRANTS}/${filterString1}`; - const url2 = `${urls.GRANTS}/${filterString2}`; + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; const url3 = `${urls.FINANCIAL_INDICATORS}/${filterString3}`; return axios @@ -75,6 +75,7 @@ export class LocationController { const dataPie1 = rawPie1.map((item: any, index: number) => ({ name: _.get(item, PieChartsMapping.pie1Field, ''), value: (_.get(item, PieChartsMapping.count, 0) * 100) / totalPie1, + amount: _.get(item, PieChartsMapping.count, 0), itemStyle: { color: PieChartsMapping.colors[index % PieChartsMapping.colors.length], @@ -85,6 +86,7 @@ export class LocationController { const dataPie2 = rawPie2.map((item: any, index: number) => ({ name: _.get(item, PieChartsMapping.pie2Field, ''), value: (_.get(item, PieChartsMapping.count, 0) * 100) / totalPie2, + amount: _.get(item, PieChartsMapping.count, 0), itemStyle: { color: PieChartsMapping.colors[index % PieChartsMapping.colors.length], @@ -93,8 +95,12 @@ export class LocationController { const totalPie3 = _.sumBy(rawPie3, PieChartsMapping.count); const dataPie3 = rawPie3.map((item: any, index: number) => ({ - name: _.get(item, PieChartsMapping.pie3Field, ''), + name: _.get(item, PieChartsMapping.pie3Field, '').replace( + '_ReferenceRate', + '', + ), value: (_.get(item, PieChartsMapping.count, 0) * 100) / totalPie3, + amount: _.get(item, PieChartsMapping.count, 0), itemStyle: { color: PieChartsMapping.colors[index % PieChartsMapping.colors.length], diff --git a/src/utils/filtering/financialIndicators.ts b/src/utils/filtering/financialIndicators.ts index dab5a00..c85f2cf 100644 --- a/src/utils/filtering/financialIndicators.ts +++ b/src/utils/filtering/financialIndicators.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; +import {getGeographyValues} from './geographies'; const MAPPING = { geography: [ @@ -30,19 +31,28 @@ const MAPPING = { export function filterFinancialIndicators( params: Record, urlParams: string, - geographyMapping: string, + geographyMapping: string | string[], componentMapping: string, ): string { let str = ''; - const geographies = _.filter( + const geos = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ).map((geography: string) => `'${geography}'`); - if (geographies.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${geographyMapping}${ - filtering.in - }(${geographies.join(filtering.multi_param_separator)})`; + ); + const geographies = geos.map((geography: string) => `'${geography}'`); + if (geos.length > 0) { + const values: string[] = [...geographies, ...getGeographyValues(geos)]; + const geoMapping = + geographyMapping instanceof Array ? geographyMapping : [geographyMapping]; + str += `${str.length > 0 ? ' AND ' : ''}(${geoMapping + .map( + m => + `${m}${filtering.in}(${values.join( + filtering.multi_param_separator, + )})`, + ) + .join(' OR ')})`; } const components = _.filter( From 0b3f9a548accc2f19b6900bc9ac18b901bf71ed9 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 13 Jun 2024 15:00:19 +0300 Subject: [PATCH 16/44] chore: geographies order --- src/controllers/location.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/location.controller.ts b/src/controllers/location.controller.ts index c951a79..5161175 100644 --- a/src/controllers/location.controller.ts +++ b/src/controllers/location.controller.ts @@ -316,7 +316,7 @@ export class LocationController { data = _.orderBy(data, ['name'], ['asc']); - data = [world, ...data]; + data = [...data, world]; return {data}; }) From 0f83a41393e35c025a26bf319a317a2052dd1578 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 13 Jun 2024 16:16:14 +0300 Subject: [PATCH 17/44] chore: incremental --- src/utils/filtering/grants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/filtering/grants.ts b/src/utils/filtering/grants.ts index 8965b28..a7d7972 100644 --- a/src/utils/filtering/grants.ts +++ b/src/utils/filtering/grants.ts @@ -39,7 +39,7 @@ export function filterGrants( } const statuses = _.filter( - _.get(params, 'statuses', '').split(','), + _.get(params, 'status', '').split(','), (o: string) => o.length > 0, ).map((status: string) => `'${status}'`); if (statuses.length > 0) { From f4f1c2fe6bbc096a9cf82367af94d108cebd5378 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 18 Jun 2024 12:40:53 +0300 Subject: [PATCH 18/44] feat: financial charts grouped/ungrouped comps --- src/config/mapping/budgets/breakdown.json | 7 +- src/config/mapping/budgets/treemap.json | 7 +- .../mapping/disbursements/barChart.json | 8 +- .../mapping/disbursements/lineChart.json | 8 +- src/config/mapping/disbursements/table.json | 7 +- src/config/mapping/expenditures/heatmap.json | 4 +- src/controllers/budgets.controller.ts | 284 ++++++++--- src/controllers/disbursements.controller.ts | 479 +++++++++++++----- src/controllers/documents.controller.ts | 2 - src/controllers/expenditures.controller.ts | 40 +- 10 files changed, 642 insertions(+), 204 deletions(-) diff --git a/src/config/mapping/budgets/breakdown.json b/src/config/mapping/budgets/breakdown.json index 20ea375..1a37349 100644 --- a/src/config/mapping/budgets/breakdown.json +++ b/src/config/mapping/budgets/breakdown.json @@ -1,7 +1,8 @@ { "dataPath": "value", - "name": ".parent.parent.name", + "name": "implementationPeriod.grant..name", "value": "value", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/parent/name),aggregate(plannedAmount with sum as value))", - "colors": ["#0A2840", "#013E77", "#00B5AE", "#C3EDFD", "#F3F5F4"] + "urlParams1": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/grant//name),aggregate(plannedAmount with sum as value))", + "urlParams2": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/grant//parent/name),aggregate(plannedAmount with sum as value))", + "colors": ["#0A2840", "#013E77", "#00B5AE", "#10708F", "#C3EDFD", "#F3F5F4"] } diff --git a/src/config/mapping/budgets/treemap.json b/src/config/mapping/budgets/treemap.json index b7b2355..0da529a 100644 --- a/src/config/mapping/budgets/treemap.json +++ b/src/config/mapping/budgets/treemap.json @@ -1,9 +1,12 @@ { "dataPath": "value", "cycle": "periodFrom", - "name": ".parent.parent.name", + "name": "implementationPeriod.grant..name", "value": "value", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/parent/parent/name),aggregate(plannedAmount with sum as value))", + "urlParams1": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/grant//name),aggregate(plannedAmount with sum as value))", + "urlParams2": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/grant//parent/name),aggregate(plannedAmount with sum as value))", + "url1Items": ["Malaria", "Tuberculosis"], + "url2Items": ["HIV/AIDS", "Other"], "textbgcolors": [ { "color": "#0A2840", diff --git a/src/config/mapping/disbursements/barChart.json b/src/config/mapping/disbursements/barChart.json index b346db4..14e3da5 100644 --- a/src/config/mapping/disbursements/barChart.json +++ b/src/config/mapping/disbursements/barChart.json @@ -1,8 +1,10 @@ { "dataPath": "value", - "name": ".name", + "name": "implementationPeriod.grant..name", "value": "value", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((/name),aggregate(actualAmount with sum as value))&$orderby=value desc", - "biggerBarColor": "#00B5AE", + "urlParams1": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/grant//name),aggregate(actualAmount with sum as value))&$orderby=value desc", + "urlParams2": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/grant//parent/name),aggregate(actualAmount with sum as value))&$orderby=value desc", + "url1Items": ["Malaria", "Tuberculosis"], + "url2Items": ["HIV/AIDS", "Other"], "barColor": "#013E77" } diff --git a/src/config/mapping/disbursements/lineChart.json b/src/config/mapping/disbursements/lineChart.json index d845a53..75e3630 100644 --- a/src/config/mapping/disbursements/lineChart.json +++ b/src/config/mapping/disbursements/lineChart.json @@ -3,8 +3,10 @@ "count": "@odata.count", "cycle": "periodFrom", "value": "value", - "line": ".name", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,/name),aggregate(actualAmount with sum as value))", - "activitiesCountUrlParams": "?$count=true&$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/grant/code))", + "line": "implementationPeriod.grant..name", + "urlParams1": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,implementationPeriod/grant//name),aggregate(actualAmount with sum as value))", + "urlParams2": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((periodFrom,implementationPeriod/grant//parent/name),aggregate(actualAmount with sum as value))", + "url1Items": ["Malaria", "Tuberculosis"], + "url2Items": ["HIV/AIDS", "Other"], "colors": ["#0A2840", "#DEE1E7", "#013E77", "#00B5AE", "#C3EDFD", "#D9D9D9"] } diff --git a/src/config/mapping/disbursements/table.json b/src/config/mapping/disbursements/table.json index 7e61a53..0d5979b 100644 --- a/src/config/mapping/disbursements/table.json +++ b/src/config/mapping/disbursements/table.json @@ -4,8 +4,11 @@ "signedIndicator": "Total Signed Amount - Reference Rate", "commitmentIndicator": "Total Commitment Amount - Reference Rate", "disbursementIndicator": "Total Disbursed Amount - Reference Rate", - "component": ".name", + "component": "implementationPeriod.grant..name", "grants": "count", "valueField": "value", - "urlParams": "?$apply=filter(indicatorName in ('Total Signed Amount - Reference Rate','Total Commitment Amount - Reference Rate','Total Disbursed Amount - Reference Rate'))/groupby((/name,indicatorName),aggregate(actualAmount with sum as value,implementationPeriod/grantId with countdistinct as count))&$orderby=value desc" + "urlParams1": "?$apply=filter(indicatorName in ('Total Signed Amount - Reference Rate','Total Commitment Amount - Reference Rate','Total Disbursed Amount - Reference Rate'))/groupby((implementationPeriod/grant//name,indicatorName),aggregate(actualAmount with sum as value,implementationPeriod/grantId with countdistinct as count))&$orderby=value desc", + "urlParams2": "?$apply=filter(indicatorName in ('Total Signed Amount - Reference Rate','Total Commitment Amount - Reference Rate','Total Disbursed Amount - Reference Rate'))/groupby((implementationPeriod/grant//parent/name,indicatorName),aggregate(actualAmount with sum as value,implementationPeriod/grantId with countdistinct as count))&$orderby=value desc", + "url1Items": ["Malaria", "Tuberculosis"], + "url2Items": ["HIV/AIDS", "Other"] } diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index be7c26a..ece4b82 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -4,10 +4,12 @@ "expenditure": "value1", "cycle": "periodCovered", "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true)/groupby((,),aggregate(actualAmountCumulative with sum as value1,plannedAmountCumulative with sum as value2))", + "url1Items": ["Malaria", "Tuberculosis"], + "url2Items": ["HIV/AIDS", "Other"], "fields": { "principalRecipient": "implementationPeriod/grant/principalRecipient/name", "principalRecipientSubType": "implementationPeriod/grant/principalRecipient/parent/name", "principalRecipientType": "implementationPeriod/grant/principalRecipient/type/name", - "component": "/parent/parent/name" + "component": "implementationPeriod/grant//name" } } diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 643e412..20393c6 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -244,52 +244,134 @@ export class BudgetsController { @get('/budgets/treemap/{componentField}') @response(200) async treemap(@param.path.string('componentField') componentField: string) { - const filterString = filterFinancialIndicators( + const filterString1 = filterFinancialIndicators( this.req.query, - BudgetsTreemapFieldsMapping.urlParams.replace( + BudgetsTreemapFieldsMapping.urlParams1.replace( '', componentField, ), 'implementationPeriod/grant/geography/name', - `${componentField}/parent/parent/name`, + `implementationPeriod/grant/${componentField}/name`, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const nameField1 = BudgetsTreemapFieldsMapping.name.replace( + '', + componentField, + ); + + let filterString2 = ''; + let url2 = ''; + const nameField2 = BudgetsTreemapFieldsMapping.name.replace( + '', + `${componentField}.parent`, ); - const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + if (componentField === 'activityAreaGroup') { + filterString2 = filterFinancialIndicators( + this.req.query, + BudgetsTreemapFieldsMapping.urlParams2.replace( + '', + componentField, + ), + 'implementationPeriod/grant/geography/name', + `implementationPeriod/grant/${componentField}/parent/name`, + ); + url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + } return axios - .get(url) - .then((resp: AxiosResponse) => { - return { - data: _.get(resp.data, BudgetsTreemapFieldsMapping.dataPath, []).map( - (item: any, index: number) => ({ - name: _.get( - item, - BudgetsTreemapFieldsMapping.name.replace( - '', - componentField, - ), - '', - ), + .all( + _.filter([url1, url2], url => url !== '').map((url: string) => + axios.get(url), + ), + ) + .then( + axios.spread((...responses) => { + const rawData1 = _.get( + responses[0], + `data.${BudgetsTreemapFieldsMapping.dataPath}`, + [], + ); + const rawData2 = _.get( + responses[1], + `data.${BudgetsTreemapFieldsMapping.dataPath}`, + [], + ); + + const data: { + name: string; + value: number; + itemStyle: { + color: string; + }; + label: { + normal: { + color: string; + }; + }; + }[] = []; + + (componentField === 'activityAreaGroup' + ? _.filter( + rawData1, + item => + BudgetsTreemapFieldsMapping.url1Items.indexOf( + _.get(item, nameField1, ''), + ) !== -1, + ) + : rawData1 + ).forEach((item: any) => { + data.push({ + name: _.get(item, nameField1, ''), value: _.get(item, BudgetsTreemapFieldsMapping.value, 0), itemStyle: { - color: _.get( - BudgetsTreemapFieldsMapping.textbgcolors, - `[${index}].color`, - '', - ), + color: '', }, label: { normal: { - color: _.get( - BudgetsTreemapFieldsMapping.textbgcolors, - `[${index}].textcolor`, - '', - ), + color: '', }, }, - }), - ), - }; - }) + }); + }); + + _.filter( + rawData2, + item => + BudgetsTreemapFieldsMapping.url2Items.indexOf( + _.get(item, nameField2, ''), + ) !== -1, + ).forEach(item => { + data.push({ + name: _.get(item, nameField2, ''), + value: _.get(item, BudgetsTreemapFieldsMapping.value, 0), + itemStyle: { + color: '', + }, + label: { + normal: { + color: '', + }, + }, + }); + }); + + _.orderBy(data, 'value', 'desc').forEach((item, index) => { + item.itemStyle.color = _.get( + BudgetsTreemapFieldsMapping.textbgcolors, + `[${index}].color`, + '', + ); + item.label.normal.color = _.get( + BudgetsTreemapFieldsMapping.textbgcolors, + `[${index}].textcolor`, + '', + ); + }); + + return {data}; + }), + ) .catch(handleDataApiError); } @@ -425,50 +507,124 @@ export class BudgetsController { @param.path.string('componentField') componentField: string, ) { const years = cycle.split('-'); - const filterString = filterFinancialIndicators( + + const filterString1 = filterFinancialIndicators( { ...this.req.query, years: years[0], yearsTo: years[1], }, - BudgetsBreakdownFieldsMapping.urlParams.replace( - //g, + BudgetsBreakdownFieldsMapping.urlParams1.replace( + '', componentField, ), 'implementationPeriod/grant/geography/name', - `${componentField}/parent/parent/name`, + `implementationPeriod/grant/${componentField}/name`, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const nameField1 = BudgetsBreakdownFieldsMapping.name.replace( + '', + componentField, ); - const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + let filterString2 = ''; + let url2 = ''; + const nameField2 = BudgetsBreakdownFieldsMapping.name.replace( + '', + `${componentField}.parent`, + ); + + if (componentField === 'activityAreaGroup') { + filterString2 = filterFinancialIndicators( + { + ...this.req.query, + years: years[0], + yearsTo: years[1], + }, + BudgetsBreakdownFieldsMapping.urlParams2.replace( + '', + componentField, + ), + 'implementationPeriod/grant/geography/name', + `implementationPeriod/grant/${componentField}/parent/name`, + ); + url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + } return axios - .get(url) - .then((resp: AxiosResponse) => { - const raw = _.get( - resp.data, - BudgetsBreakdownFieldsMapping.dataPath, - [], - ); - const total = _.sumBy(raw, BudgetsBreakdownFieldsMapping.value); - const data = raw.map((item: any) => ({ - name: _.get( - item, - BudgetsBreakdownFieldsMapping.name.replace( - '', - componentField, - ), - '', - ), - value: - (_.get(item, BudgetsBreakdownFieldsMapping.value, 0) / total) * 100, - color: '', - })); - return { - data: _.orderBy(data, 'value', 'desc').map((item, index) => { - item.color = _.get(BudgetsBreakdownFieldsMapping.colors, index, ''); - return item; - }), - }; - }) + .all( + _.filter([url1, url2], url => url !== '').map((url: string) => + axios.get(url), + ), + ) + .then( + axios.spread((...responses) => { + const rawData1 = _.get( + responses[0], + `data.${BudgetsTreemapFieldsMapping.dataPath}`, + [], + ); + const rawData2 = _.get( + responses[1], + `data.${BudgetsTreemapFieldsMapping.dataPath}`, + [], + ); + + const data: { + name: string; + value: number; + color: string; + }[] = []; + + (componentField === 'activityAreaGroup' + ? _.filter( + rawData1, + item => + BudgetsTreemapFieldsMapping.url1Items.indexOf( + _.get(item, nameField1, ''), + ) !== -1, + ) + : rawData1 + ).forEach((item: any) => { + data.push({ + name: _.get(item, nameField1, ''), + value: _.get(item, BudgetsTreemapFieldsMapping.value, 0), + color: '', + }); + }); + + _.filter( + rawData2, + item => + BudgetsTreemapFieldsMapping.url2Items.indexOf( + _.get(item, nameField2, ''), + ) !== -1, + ).forEach(item => { + data.push({ + name: _.get(item, nameField2, ''), + value: _.get(item, BudgetsTreemapFieldsMapping.value, 0), + color: '', + }); + }); + + const total = _.sumBy(data, 'value'); + + data.forEach((item, index) => { + item.value = (item.value / total) * 100; + }); + + return { + data: _.orderBy(data, 'value', 'desc').map((item, index) => { + item.color = _.get( + BudgetsBreakdownFieldsMapping.colors, + index, + '', + ); + return item; + }), + }; + }), + ) .catch(handleDataApiError); } diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index 2bd4b31..9539103 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -240,51 +240,119 @@ export class DisbursementsController { @get('/disbursements/bar-chart/{componentField}') @response(200) async barChart(@param.path.string('componentField') componentField: string) { - const filterString = filterFinancialIndicators( + const filterString1 = filterFinancialIndicators( this.req.query, - BarChartFieldsMapping.urlParams.replace( + BarChartFieldsMapping.urlParams1.replace( '', componentField, ), - 'implementationPeriod/grant/geography/name', - `${componentField}/name`, + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], + `implementationPeriod/grant/${componentField}/name`, + ); + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const nameField1 = BarChartFieldsMapping.name.replace( + '', + componentField, + ); + + let filterString2 = ''; + let url2 = ''; + const nameField2 = BarChartFieldsMapping.name.replace( + '', + `${componentField}.parent`, ); - const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + if (componentField === 'activityAreaGroup') { + filterString2 = filterFinancialIndicators( + this.req.query, + BarChartFieldsMapping.urlParams2.replace( + '', + componentField, + ), + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], + `implementationPeriod/grant/${componentField}/parent/name`, + ); + url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + } return axios - .get(url) - .then((resp: AxiosResponse) => { - const raw = _.get(resp.data, BarChartFieldsMapping.dataPath, []); + .all( + _.filter([url1, url2], url => url !== '').map((url: string) => + axios.get(url), + ), + ) + .then( + axios.spread((...responses) => { + const rawData1 = _.get( + responses[0], + `data.${BarChartFieldsMapping.dataPath}`, + [], + ); + const rawData2 = _.get( + responses[1], + `data.${BarChartFieldsMapping.dataPath}`, + [], + ); - return { - data: raw.map((item: any, index: number) => ({ - name: _.get( - item, - BarChartFieldsMapping.name.replace( - '', - componentField, - ), - '', - ), - value: _.get(item, BarChartFieldsMapping.value, 0), - itemStyle: { - color: - index === 0 - ? BarChartFieldsMapping.biggerBarColor - : BarChartFieldsMapping.barColor, - }, - })), - }; - }) + const data: { + name: string; + value: number; + itemStyle: {color: string}; + }[] = []; + + (componentField === 'activityAreaGroup' + ? _.filter( + rawData1, + item => + BarChartFieldsMapping.url1Items.indexOf( + _.get(item, nameField1, ''), + ) !== -1, + ) + : rawData1 + ).forEach((item: any) => { + data.push({ + name: _.get(item, nameField1, ''), + value: _.get(item, BarChartFieldsMapping.value, 0), + itemStyle: { + color: BarChartFieldsMapping.barColor, + }, + }); + }); + + _.filter( + rawData2, + item => + BarChartFieldsMapping.url2Items.indexOf( + _.get(item, nameField2, ''), + ) !== -1, + ).forEach((item: any) => { + data.push({ + name: _.get(item, nameField2, ''), + value: _.get(item, BarChartFieldsMapping.value, 0), + itemStyle: { + color: BarChartFieldsMapping.barColor, + }, + }); + }); + + return {data: _.orderBy(data, 'value', 'desc')}; + }), + ) .catch(handleDataApiError); } @get('/disbursements/line-chart/{componentField}') @response(200) async lineChart(@param.path.string('componentField') componentField: string) { - const filterString = filterFinancialIndicators( + const filterString1 = filterFinancialIndicators( this.req.query, - LineChartFieldsMapping.urlParams.replace( + LineChartFieldsMapping.urlParams1.replace( '', componentField, ), @@ -292,117 +360,284 @@ export class DisbursementsController { 'implementationPeriod/grant/geography/name', 'implementationPeriod/grant/geography/code', ], - `${componentField}/name`, + `implementationPeriod/grant/${componentField}/name`, ); - const filterString2 = filterFinancialIndicators( - this.req.query, - LineChartFieldsMapping.activitiesCountUrlParams, - 'implementationPeriod/grant/geography/name', - `${componentField}/name`, + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const nameField1 = LineChartFieldsMapping.line.replace( + '', + componentField, ); - const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; - const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; - const activitiesCount = await axios - .get(url2) - .then((resp: AxiosResponse) => - _.get(resp.data, LineChartFieldsMapping.count, 0), - ) - .catch(() => 0); + let filterString2 = ''; + let url2 = ''; + const nameField2 = LineChartFieldsMapping.line.replace( + '', + `${componentField}.parent`, + ); + + if (componentField === 'activityAreaGroup') { + filterString2 = filterFinancialIndicators( + this.req.query, + LineChartFieldsMapping.urlParams2.replace( + '', + componentField, + ), + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], + `implementationPeriod/grant/${componentField}/parent/name`, + ); + url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + } return axios - .get(url) - .then((resp: AxiosResponse) => { - const raw = _.get(resp.data, LineChartFieldsMapping.dataPath, []); - const groupedByLine = _.groupBy( - raw, - LineChartFieldsMapping.line.replace( - '', - componentField, - ), - ); - const lines = Object.keys(groupedByLine); - const years = _.groupBy(raw, LineChartFieldsMapping.cycle); - return { - data: _.map(groupedByLine, (items, line) => ({ - name: line, - data: _.orderBy( - items.map((item: any) => item[LineChartFieldsMapping.value]), - 'x', - 'asc', - ), - itemStyle: { - color: _.get( - LineChartFieldsMapping.colors, - `[${lines.indexOf(line)}]`, - '', - ), - }, - })), - xAxisKeys: Object.keys(years), - activitiesCount, - }; - }) + .all( + _.filter([url1, url2], url => url !== '').map((url: string) => + axios.get(url), + ), + ) + .then( + axios.spread((...responses) => { + const rawData1 = _.get( + responses[0], + `data.${LineChartFieldsMapping.dataPath}`, + [], + ); + const rawData2 = _.get( + responses[1], + `data.${LineChartFieldsMapping.dataPath}`, + [], + ); + + const data: { + name: string; + data: number[]; + itemStyle: {color: string}; + }[] = []; + + const groupedByLine1 = _.groupBy(rawData1, nameField1); + const lines1 = Object.keys(groupedByLine1); + const years1 = Object.keys( + _.groupBy(rawData1, LineChartFieldsMapping.cycle), + ); + + const groupedByLine2 = _.groupBy(rawData2, nameField2); + const lines2 = Object.keys(groupedByLine2); + const years2 = Object.keys( + _.groupBy(rawData2, LineChartFieldsMapping.cycle), + ); + + const years = _.uniq([...years1, ...years2]); + + (componentField === 'activityAreaGroup' + ? _.filter( + lines1, + item => LineChartFieldsMapping.url1Items.indexOf(item) !== -1, + ) + : lines1 + ).forEach(line => { + const items = groupedByLine1[line]; + data.push({ + name: line, + data: years.map(year => { + const value = _.get( + _.find(items, { + [LineChartFieldsMapping.cycle]: year, + }), + LineChartFieldsMapping.value, + null, + ); + return value; + }), + itemStyle: { + color: '', + }, + }); + }); + + _.filter( + lines2, + item => LineChartFieldsMapping.url2Items.indexOf(item) !== -1, + ).forEach(line => { + const items = groupedByLine2[line]; + data.push({ + name: line, + data: years.map(year => { + const value = _.get( + _.find(items, { + [LineChartFieldsMapping.cycle]: year, + }), + LineChartFieldsMapping.value, + null, + ); + return value; + }), + itemStyle: { + color: '', + }, + }); + }); + + _.orderBy(data, 'name', 'asc').forEach((item, index) => { + item.itemStyle.color = _.get( + LineChartFieldsMapping.colors, + `[${index}]`, + '', + ); + }); + + return { + data, + xAxisKeys: years, + }; + }), + ) .catch(handleDataApiError); } @get('/disbursements/table/{componentField}') @response(200) async table(@param.path.string('componentField') componentField: string) { - const filterString = filterFinancialIndicators( + const filterString1 = filterFinancialIndicators( this.req.query, - TableFieldsMapping.urlParams.replace('', componentField), - 'implementationPeriod/grant/geography/name', - `${componentField}/name`, + TableFieldsMapping.urlParams1.replace('', componentField), + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], + `implementationPeriod/grant/${componentField}/name`, ); - const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; + const nameField1 = TableFieldsMapping.component.replace( + '', + componentField, + ); + + let filterString2 = ''; + let url2 = ''; + const nameField2 = TableFieldsMapping.component.replace( + '', + `${componentField}.parent`, + ); + + if (componentField === 'activityAreaGroup') { + filterString2 = filterFinancialIndicators( + this.req.query, + TableFieldsMapping.urlParams2.replace( + '', + componentField, + ), + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], + `implementationPeriod/grant/${componentField}/parent/name`, + ); + url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; + } return axios - .get(url) - .then((resp: AxiosResponse) => { - const raw = _.get(resp.data, TableFieldsMapping.dataPath, []); + .all( + _.filter([url1, url2], url => url !== '').map((url: string) => + axios.get(url), + ), + ) + .then( + axios.spread((...responses) => { + const rawData1 = _.get( + responses[0], + `data.${TableFieldsMapping.dataPath}`, + [], + ); + const rawData2 = _.get( + responses[1], + `data.${TableFieldsMapping.dataPath}`, + [], + ); - const groupedByComponent = _.groupBy( - raw, - TableFieldsMapping.component.replace( - '', - componentField, - ), - ); + const groupedByComponent1 = _.groupBy(rawData1, nameField1); + const groupedByComponent2 = _.groupBy(rawData2, nameField2); - return { - data: _.map(groupedByComponent, (items, component) => { - return { - component, - grants: _.get(items[0], TableFieldsMapping.grants, 0), - signed: _.get( - _.find(items, { - [TableFieldsMapping.indicatorField]: - TableFieldsMapping.signedIndicator, - }), - TableFieldsMapping.valueField, - 0, - ), - committed: _.get( - _.find(items, { - [TableFieldsMapping.indicatorField]: - TableFieldsMapping.commitmentIndicator, + return { + data: _.orderBy( + [ + ...(componentField === 'activityAreaGroup' + ? _.filter(groupedByComponent1, (_, component) => { + return ( + TableFieldsMapping.url1Items.indexOf(component) !== -1 + ); + }) + : _.map(groupedByComponent1) + ).map(items => { + return { + component: _.get(items[0], nameField1, ''), + grants: _.get(items[0], TableFieldsMapping.grants, 0), + signed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.signedIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + committed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.commitmentIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + disbursed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.disbursementIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + }; }), - TableFieldsMapping.valueField, - 0, - ), - disbursed: _.get( - _.find(items, { - [TableFieldsMapping.indicatorField]: - TableFieldsMapping.disbursementIndicator, + ..._.filter(groupedByComponent2, (_, component) => { + return TableFieldsMapping.url2Items.indexOf(component) !== -1; + }).map(items => { + return { + component: _.get(items[0], nameField2, ''), + grants: _.get(items[0], TableFieldsMapping.grants, 0), + signed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.signedIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + committed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.commitmentIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + disbursed: _.get( + _.find(items, { + [TableFieldsMapping.indicatorField]: + TableFieldsMapping.disbursementIndicator, + }), + TableFieldsMapping.valueField, + 0, + ), + }; }), - TableFieldsMapping.valueField, - 0, - ), - }; - }), - }; - }) + ], + 'disbursed', + 'desc', + ), + }; + }), + ) .catch(handleDataApiError); } diff --git a/src/controllers/documents.controller.ts b/src/controllers/documents.controller.ts index 780e588..d71a689 100644 --- a/src/controllers/documents.controller.ts +++ b/src/controllers/documents.controller.ts @@ -126,8 +126,6 @@ export class DocumentsController { ); const url = `${urls.documents}/?${docsUtils.defaultSelect}${docsUtils.defaultOrderBy}${filterString}`; - console.log(url); - return axios .get(url) .then((resp: AxiosResponse) => { diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index e2d1ce3..338e678 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -19,6 +19,39 @@ export class ExpendituresController { @param.path.string('row') row: string, @param.path.string('column') column: string, @param.path.string('componentField') componentField: string, + ) { + const ungrouped = await this.heatmaplocal(row, column, 'activityArea'); + if (componentField === 'activityAreaGroup') { + const grouped = await this.heatmaplocal( + row, + column, + 'activityAreaGroup/parent', + ); + return { + data: [ + ..._.filter( + ungrouped.data, + item => + ExpendituresHeatmapMapping.url1Items.indexOf(item.column) > -1, + ), + ..._.filter( + grouped.data, + item => + ExpendituresHeatmapMapping.url2Items.indexOf(item.column) > -1, + ), + ], + }; + } else { + return ungrouped; + } + } + + @get('/expenditures/heatmap/{row}/{column}/{componentField}/local') + @response(200) + async heatmaplocal( + @param.path.string('row') row: string, + @param.path.string('column') column: string, + @param.path.string('componentField') componentField: string, ) { let filterString = ExpendituresHeatmapMapping.urlParams; let rowField = ''; @@ -83,8 +116,11 @@ export class ExpendituresController { filterString = filterFinancialIndicators( this.req.query, filterString, - 'implementationPeriod/grant/geography/name', - `${componentField}/parent/parent/name`, + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], + `implementationPeriod/grant/${componentField}/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; From c7a570c6bdb150465234228d585fb56cd0781bf0 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 20 Jun 2024 13:12:38 +0300 Subject: [PATCH 19/44] feat: implementation review alignments --- src/config/mapping/allocations/cycles.json | 2 +- src/config/mapping/budgets/cycles.json | 2 +- src/config/mapping/disbursements/cycles.json | 2 +- src/config/mapping/expenditures/cycles.json | 2 +- src/config/mapping/expenditures/heatmap.json | 4 +- .../filter-options/principal-recipients.json | 6 ++- .../mapping/fundingrequests/cycles.json | 6 +-- src/config/mapping/grants/grant.json | 3 +- .../mapping/pledgescontributions/bar.json | 2 +- .../mapping/pledgescontributions/cycles.json | 2 +- src/config/mapping/results/cycles.json | 2 +- src/config/urls/index.json | 2 +- src/controllers/allocations.controller.ts | 12 +++-- src/controllers/budgets.controller.ts | 12 ++++- src/controllers/disbursements.controller.ts | 15 ++++-- src/controllers/expenditures.controller.ts | 10 +++- src/controllers/filteroptions.controller.ts | 49 +++++++++++++++---- src/controllers/fundingrequests.controller.ts | 12 ++++- src/controllers/grants.controller.ts | 6 ++- .../pledgescontributions.controller.ts | 12 +++-- src/controllers/results.controller.ts | 10 ++-- src/utils/filtering/financialIndicators.ts | 6 ++- src/utils/filtering/fundingRequests.ts | 2 +- 23 files changed, 134 insertions(+), 47 deletions(-) diff --git a/src/config/mapping/allocations/cycles.json b/src/config/mapping/allocations/cycles.json index 8dcf187..a31a747 100644 --- a/src/config/mapping/allocations/cycles.json +++ b/src/config/mapping/allocations/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "periodCovered", "cycleTo": "", - "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((periodCovered))&$orderby=periodCovered asc" + "urlParams": "?$apply=filter(indicatorName eq 'Communicated Allocation - Reference Rate' AND financialDataSet eq 'CommunicatedAllocation_ReferenceRate')/groupby((periodCovered))&$orderby=periodCovered asc" } diff --git a/src/config/mapping/budgets/cycles.json b/src/config/mapping/budgets/cycles.json index 9790b3c..e60ffe2 100644 --- a/src/config/mapping/budgets/cycles.json +++ b/src/config/mapping/budgets/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "implementationPeriod.periodFrom", "cycleTo": "implementationPeriod.periodTo", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" } diff --git a/src/config/mapping/disbursements/cycles.json b/src/config/mapping/disbursements/cycles.json index af3bfb8..1b0b603 100644 --- a/src/config/mapping/disbursements/cycles.json +++ b/src/config/mapping/disbursements/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "implementationPeriod.periodFrom", "cycleTo": "implementationPeriod.periodTo", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" } diff --git a/src/config/mapping/expenditures/cycles.json b/src/config/mapping/expenditures/cycles.json index 8d2e63d..8f4a90e 100644 --- a/src/config/mapping/expenditures/cycles.json +++ b/src/config/mapping/expenditures/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "implementationPeriod.periodFrom", "cycleTo": "implementationPeriod.periodTo", - "urlParams": "?$apply=filter(indicatorName eq 'Expenditure: Module-Intervention - Reference Rate' AND isLatestReported eq true)/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" + "urlParams": "?$apply=filter(indicatorName eq 'Expenditure: Module-Intervention - Reference Rate' AND isLatestReported eq true)/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" } diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index ece4b82..1ddfd3c 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -8,8 +8,8 @@ "url2Items": ["HIV/AIDS", "Other"], "fields": { "principalRecipient": "implementationPeriod/grant/principalRecipient/name", - "principalRecipientSubType": "implementationPeriod/grant/principalRecipient/parent/name", - "principalRecipientType": "implementationPeriod/grant/principalRecipient/type/name", + "principalRecipientSubType": "implementationPeriod/grant/principalRecipient/type/name", + "principalRecipientType": "implementationPeriod/grant/principalRecipient/type/parent/name", "component": "implementationPeriod/grant//name" } } diff --git a/src/config/mapping/filter-options/principal-recipients.json b/src/config/mapping/filter-options/principal-recipients.json index 7d02cbf..8173805 100644 --- a/src/config/mapping/filter-options/principal-recipients.json +++ b/src/config/mapping/filter-options/principal-recipients.json @@ -2,6 +2,8 @@ "dataPath": "value", "label": "principalRecipient.name", "value": "principalRecipient.name", - "type": "principalRecipient.type.name", - "typeCode": "principalRecipient.type.code" + "subType": "principalRecipient.type.name", + "subTypeCode": "principalRecipient.type.code", + "type": "principalRecipient.type.parent.name", + "typeCode": "principalRecipient.type.parent.code" } diff --git a/src/config/mapping/fundingrequests/cycles.json b/src/config/mapping/fundingrequests/cycles.json index 01859e1..9ecb9d4 100644 --- a/src/config/mapping/fundingrequests/cycles.json +++ b/src/config/mapping/fundingrequests/cycles.json @@ -1,6 +1,6 @@ { "dataPath": "value", - "cycleFrom": "implementationPeriod.periodFrom", - "cycleTo": "implementationPeriod.periodTo", - "urlParams": "?$apply=groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" + "cycleFrom": "periodFrom", + "cycleTo": "periodTo", + "urlParams": "?$apply=filter()/groupby((periodFrom,periodTo))&$orderby=periodFrom asc" } diff --git a/src/config/mapping/grants/grant.json b/src/config/mapping/grants/grant.json index 7ac6c99..a0f93ba 100644 --- a/src/config/mapping/grants/grant.json +++ b/src/config/mapping/grants/grant.json @@ -7,7 +7,8 @@ "lang": "narrativeLanguage" }, "implementationPeriodArray": "implementationPeriods", - "implementationPeriodFrom": "implementationPeriodFrom", + "implementationPeriodFrom": "periodFrom", + "implementationPeriodTo": "periodTo", "implementationPeriodTitle": "title", "countryCode": "geography.code", "countryName": "geography.name", diff --git a/src/config/mapping/pledgescontributions/bar.json b/src/config/mapping/pledgescontributions/bar.json index acf9ea2..93949c6 100644 --- a/src/config/mapping/pledgescontributions/bar.json +++ b/src/config/mapping/pledgescontributions/bar.json @@ -14,5 +14,5 @@ "donorBarIndicatorContribution": "Contribution - Reference Rate", "donorBarIndicatorPledgeAmount": "plannedAmount", "donorBarIndicatorContributionAmount": "actualAmount", - "donorBarUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((donor/name,donor/type/name,donor/type/parent/name,indicatorName),aggregate(plannedAmount with sum as plannedAmount,actualAmount with sum as actualAmount))" + "donorBarUrlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions' AND indicatorName in ('Pledge - Reference Rate','Contribution - Reference Rate'))/groupby((donor/name,donor/type/name,donor/type/parent/name,indicatorName),aggregate(plannedAmount with sum as plannedAmount,actualAmount with sum as actualAmount))" } diff --git a/src/config/mapping/pledgescontributions/cycles.json b/src/config/mapping/pledgescontributions/cycles.json index ed67ab1..29d9a1d 100644 --- a/src/config/mapping/pledgescontributions/cycles.json +++ b/src/config/mapping/pledgescontributions/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "periodCovered", "cycleTo": "", - "urlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc" + "urlParams": "?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc" } diff --git a/src/config/mapping/results/cycles.json b/src/config/mapping/results/cycles.json index 740c363..ff80fdf 100644 --- a/src/config/mapping/results/cycles.json +++ b/src/config/mapping/results/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "resultValueYear", "cycleTo": "", - "urlParams": "?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((resultValueYear))&$orderby=resultValueYear asc" + "urlParams": "?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((resultValueYear))&$orderby=resultValueYear asc" } diff --git a/src/config/urls/index.json b/src/config/urls/index.json index 85a7a5f..01ca2e1 100644 --- a/src/config/urls/index.json +++ b/src/config/urls/index.json @@ -36,7 +36,7 @@ "FILTER_OPTIONS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=type eq 'Component'", "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc", "FILTER_OPTIONS_DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors?$apply=groupby((id,name,type/name))", - "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$apply=groupby((principalRecipient/type/name,principalRecipient/type/code,principalRecipient/name))", + "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$apply=groupby((principalRecipient/type/parent/name,principalRecipient/type/parent/code,principalRecipient/type/name,principalRecipient/type/code,principalRecipient/name))", "FILTER_OPTIONS_RESULTS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allProgrammaticIndicators?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((indicatorName,activityArea/name))", "FILTER_OPTIONS_STATUS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Statuses", "FINANCIAL_INDICATORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators", diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index 6eb9b7e..e2b33a2 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -452,10 +452,16 @@ export class AllocationsController { @get('/allocations/cycles') @response(200) async cycles() { + const filterString = filterFinancialIndicators( + this.req.query, + AllocationCyclesFieldsMapping.urlParams, + 'geography/code', + 'activityArea/name', + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + return axios - .get( - `${urls.FINANCIAL_INDICATORS}${AllocationCyclesFieldsMapping.urlParams}`, - ) + .get(url) .then((resp: AxiosResponse) => { const rawData = _.get( resp.data, diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 20393c6..d68fed3 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -463,8 +463,16 @@ export class BudgetsController { @get('/budgets/cycles') @response(200) async cycles() { + const filterString = filterFinancialIndicators( + this.req.query, + BudgetsCyclesMapping.urlParams, + 'implementationPeriod/grant/geography/code', + 'implementationPeriod/grant/activityArea/name', + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + return axios - .get(`${urls.FINANCIAL_INDICATORS}${BudgetsCyclesMapping.urlParams}`) + .get(url) .then((resp: AxiosResponse) => { const rawData = _.get(resp.data, BudgetsCyclesMapping.dataPath, []); @@ -492,7 +500,7 @@ export class BudgetsController { }, ), item => parseInt(item.value.toString().split(' - ')[0], 10), - 'desc', + 'asc', ); return {data}; diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index 9539103..b9a4670 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -644,10 +644,19 @@ export class DisbursementsController { @get('/disbursements/cycles') @response(200) async cycles() { + const filterString = filterFinancialIndicators( + this.req.query, + DisbursementsCyclesMapping.urlParams, + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], + 'activityArea/name', + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + return axios - .get( - `${urls.FINANCIAL_INDICATORS}${DisbursementsCyclesMapping.urlParams}`, - ) + .get(url) .then((resp: AxiosResponse) => { const rawData = _.get( resp.data, diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index 338e678..be12355 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -354,8 +354,16 @@ export class ExpendituresController { @get('/expenditures/cycles') @response(200) async cycles() { + const filterString = filterFinancialIndicators( + this.req.query, + ExpendituresCyclesMapping.urlParams, + 'implementationPeriod/grant/geography/code', + 'implementationPeriod/grant/activityArea/name', + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + return axios - .get(`${urls.FINANCIAL_INDICATORS}${ExpendituresCyclesMapping.urlParams}`) + .get(url) .then((resp: AxiosResponse) => { const rawData = _.get( resp.data, diff --git a/src/controllers/filteroptions.controller.ts b/src/controllers/filteroptions.controller.ts index 2c924de..5904a0e 100644 --- a/src/controllers/filteroptions.controller.ts +++ b/src/controllers/filteroptions.controller.ts @@ -343,21 +343,50 @@ export class FilteroptionsController { const options: FilterGroupOption[] = []; - _.map( - _.groupBy(rawData, PrincipalRecipientMapping.type), - (prs, type) => { + const groupedByType = _.groupBy( + rawData, + PrincipalRecipientMapping.type, + ); + + _.forEach(groupedByType, (values, type) => { + if (type !== 'null') { const typeOptions: FilterGroupOption = { name: type, - value: _.get(prs[0], PrincipalRecipientMapping.typeCode, ''), - options: prs.map((pr: any) => ({ - name: _.get(pr, PrincipalRecipientMapping.label, ''), - value: _.get(pr, PrincipalRecipientMapping.value, ''), - })), + value: _.get(values[0], PrincipalRecipientMapping.typeCode, ''), + options: [], }; + const groupedBySubType = _.groupBy( + values, + PrincipalRecipientMapping.subType, + ); + + _.forEach(groupedBySubType, (subValues, subType) => { + const subTypeOptions: FilterGroupOption = { + name: subType, + value: _.get( + subValues[0], + PrincipalRecipientMapping.subTypeCode, + '', + ), + options: _.orderBy( + subValues.map((subValue: any) => ({ + name: _.get(subValue, PrincipalRecipientMapping.label, ''), + value: _.get(subValue, PrincipalRecipientMapping.value, ''), + })), + 'name', + 'asc', + ), + }; + + typeOptions.options?.push(subTypeOptions); + }); + + typeOptions.options = _.orderBy(typeOptions.options, 'name', 'asc'); + options.push(typeOptions); - }, - ); + } + }); return { data: { diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 954fa05..6ca9b3e 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -85,7 +85,9 @@ export class FundingRequestsController { @param.path.string('countryCode') countryCode: string, ) { return axios - .get(`http://localhost:4200/funding-requests?geographies=${countryCode}`) + .get( + `http://localhost:4200/funding-requests?geographies=${countryCode}&periods=${this.req.query.periods}`, + ) .then((resp: AxiosResponse) => resp.data) .catch(handleDataApiError); } @@ -93,8 +95,14 @@ export class FundingRequestsController { @get('/funding-requests/cycles') @response(200) async cycles() { + const filterString = filterFundingRequests( + this.req.query, + CyclesMapping.urlParams, + ); + const url = `${urls.FUNDING_REQUESTS}/${filterString}`; + return axios - .get(`${urls.FINANCIAL_INDICATORS}${CyclesMapping.urlParams}`) + .get(url) .then((resp: AxiosResponse) => { const rawData = _.get(resp.data, CyclesMapping.dataPath, []); diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index c1eff68..a13c8e8 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -145,7 +145,11 @@ export class GrantsController { ['asc'], ).map((p, index: number) => ({ code: index + 1, - name: `Implementation Period ${index + 1}`, + name: `Implementation Period ${_.get( + p, + GrantMapping.implementationPeriodFrom, + '', + )}-${_.get(p, GrantMapping.implementationPeriodTo, '')}`, title: _.get(p, GrantMapping.implementationPeriodTitle, ''), })), countryName: _.get(raw, GrantMapping.countryName, ''), diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index fe095e2..dd98fff 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -592,10 +592,16 @@ export class PledgescontributionsController { @get('/pledges-contributions/cycles') @response(200) async cycles() { + const filterString = filterFinancialIndicators( + this.req.query, + PledgesContributionsCyclesFieldsMapping.urlParams, + ['donor/geography/name', 'donor/geography/code'], + 'activityArea/name', + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + return axios - .get( - `${urls.FINANCIAL_INDICATORS}${PledgesContributionsCyclesFieldsMapping.urlParams}`, - ) + .get(url) .then((resp: AxiosResponse) => { const rawData = _.get( resp.data, diff --git a/src/controllers/results.controller.ts b/src/controllers/results.controller.ts index eb16411..abf19d6 100644 --- a/src/controllers/results.controller.ts +++ b/src/controllers/results.controller.ts @@ -265,10 +265,14 @@ export class ResultsController { @get('/results/cycles') @response(200) async cycles() { + const filterString = filterProgrammaticIndicators( + this.req.query, + ResultsCyclesMappingFields.urlParams, + ); + const url = `${urls.PROGRAMMATIC_INDICATORS}/${filterString}`; + return axios - .get( - `${urls.PROGRAMMATIC_INDICATORS}${ResultsCyclesMappingFields.urlParams}`, - ) + .get(url) .then((resp: AxiosResponse) => { const rawData = _.get( resp.data, diff --git a/src/utils/filtering/financialIndicators.ts b/src/utils/filtering/financialIndicators.ts index c85f2cf..819ff49 100644 --- a/src/utils/filtering/financialIndicators.ts +++ b/src/utils/filtering/financialIndicators.ts @@ -18,8 +18,10 @@ const MAPPING = { donor: 'donor/name', donorType: 'donor/type/name', principalRecipient: 'implementationPeriod/grant/principalRecipient/name', - principalRecipientType: + principalRecipientSubType: 'implementationPeriod/grant/principalRecipient/type/name', + principalRecipientType: + 'implementationPeriod/grant/principalRecipient/type/parent/name', period: 'periodCovered', year: 'implementationPeriod/periodFrom', yearTo: 'implementationPeriod/periodTo', @@ -108,7 +110,7 @@ export function filterFinancialIndicators( const periods = _.filter( _.get(params, 'periods', '').split(','), (o: string) => o.length > 0, - ).map((period: string) => `'${period}'`); + ).map((period: string) => `'${period.replace(/ /g, '')}'`); if (periods.length > 0) { str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.period}${ filtering.in diff --git a/src/utils/filtering/fundingRequests.ts b/src/utils/filtering/fundingRequests.ts index 2a456be..a0fad46 100644 --- a/src/utils/filtering/fundingRequests.ts +++ b/src/utils/filtering/fundingRequests.ts @@ -49,7 +49,7 @@ export function filterFundingRequests( const periods = _.filter( _.get(params, 'periods', '').split(','), (o: string) => o.length > 0, - ).map((period: string) => period); + ).map((period: string) => `'${period}'`); if (periods.length > 0) { str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.period}${ filtering.in From ae89a98e434fc976398a579de7fe7dadf7d04834 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Fri, 21 Jun 2024 02:48:04 +0300 Subject: [PATCH 20/44] feat: cycles filtering --- src/utils/filtering/eligibility.ts | 11 +++++++++++ src/utils/filtering/financialIndicators.ts | 8 ++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/utils/filtering/eligibility.ts b/src/utils/filtering/eligibility.ts index 1636547..6ce1b21 100644 --- a/src/utils/filtering/eligibility.ts +++ b/src/utils/filtering/eligibility.ts @@ -6,6 +6,7 @@ const MAPPING = { geography: ['geography/name', 'geography/code'], component: 'activityArea/name', year: 'eligibilityYear', + cycle: 'fundingStream', }; export function filterEligibility( @@ -53,6 +54,16 @@ export function filterEligibility( }(${years.join(filtering.multi_param_separator)})`; } + const cycles = _.filter( + _.get(params, 'cycles', '').split(','), + (o: string) => o.length > 0, + ).map((cycle: string) => `'${cycle}'`); + if (cycles.length > 0) { + str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.cycle}${ + filtering.in + }(${cycles.join(filtering.multi_param_separator)})`; + } + // const search = _.get(params, 'q', ''); // if (search.length > 0) { // str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.search.replace( diff --git a/src/utils/filtering/financialIndicators.ts b/src/utils/filtering/financialIndicators.ts index 819ff49..04657ff 100644 --- a/src/utils/filtering/financialIndicators.ts +++ b/src/utils/filtering/financialIndicators.ts @@ -23,6 +23,7 @@ const MAPPING = { principalRecipientType: 'implementationPeriod/grant/principalRecipient/type/parent/name', period: 'periodCovered', + cycle: 'periodCovered', year: 'implementationPeriod/periodFrom', yearTo: 'implementationPeriod/periodTo', grantIP: 'implementationPeriod/code', @@ -108,9 +109,12 @@ export function filterFinancialIndicators( } const periods = _.filter( - _.get(params, 'periods', '').split(','), + [ + ..._.get(params, 'periods', '').split(','), + ..._.get(params, 'cycles', '').split(','), + ], (o: string) => o.length > 0, - ).map((period: string) => `'${period.replace(/ /g, '')}'`); + ).map((period: string) => `'${period.replace(/ /g, '').replace(' - ', '')}'`); if (periods.length > 0) { str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.period}${ filtering.in From 6697d372206e004ede5d324953d231d035337c0f Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 8 Jul 2024 13:10:56 +0300 Subject: [PATCH 21/44] feat: location endpoint return location is donor --- src/config/mapping/location/info.json | 1 + src/controllers/location.controller.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/config/mapping/location/info.json b/src/config/mapping/location/info.json index d717b9c..adce975 100644 --- a/src/config/mapping/location/info.json +++ b/src/config/mapping/location/info.json @@ -3,6 +3,7 @@ "name": "[0].name", "regionName": "[0].parent.name", "level": "[0].level", + "isDonor": "[0].isDonor", "countryLevel": "Country", "multicountryLevel": "Multicountry", "FPMSalutation": "[0].fundPortfolioManager.salutation", diff --git a/src/controllers/location.controller.ts b/src/controllers/location.controller.ts index 5161175..7923544 100644 --- a/src/controllers/location.controller.ts +++ b/src/controllers/location.controller.ts @@ -392,6 +392,7 @@ export class LocationController { name: _.get(raw, LocationInfoMapping.name, ''), region: _.get(raw, LocationInfoMapping.regionName, ''), description: _.get(description, 'summary', ''), + isDonor: _.get(raw, LocationInfoMapping.isDonor, false), FPMName: [ _.get(rawFPM, LocationInfoMapping.FPMSalutation, ''), _.get(rawFPM, LocationInfoMapping.FPMFirstName, ''), From 701409dce39f76643e2c4266d91d9980e1b5c1ee Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 8 Jul 2024 13:11:15 +0300 Subject: [PATCH 22/44] feat: funding requests table dates --- src/controllers/fundingrequests.controller.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 6ca9b3e..2b8c83f 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -43,22 +43,28 @@ export class FundingRequestsController { _children: items.map((item: any) => { return { components: item.components, - submissionDate: moment(item.submissionDate).format( - 'D MMMM YYYY', - ), + submissionDate: item.submissionDate + ? moment(item.submissionDate).format('D MMMM YYYY') + : '--', approach: item.approach, trpWindow: item.trpWindow, trpOutcome: item.trpOutcome, portfolioCategorization: item.portfolioCategorization, _children: item.items.map((subitem: any) => { return { - boardApproval: subitem.boardApproval, - gacMeeting: moment(item.gacMeeting).format('MMMM YYYY'), - grant: subitem.grant.code, - startingDate: moment(subitem.startDate).format( - 'DD-MM-YYYY', - ), - endingDate: moment(subitem.endDate).format('DD-MM-YYYY'), + boardApproval: subitem.boardApproval + ? moment(subitem.boardApproval).format('MMMM YYYY') + : '--', + gacMeeting: item.gacMeeting + ? moment(item.gacMeeting).format('MMMM YYYY') + : '--', + grant: subitem.grant, + startingDate: subitem.startDate + ? moment(subitem.startDate).format('DD-MM-YYYY') + : '--', + endingDate: subitem.endDate + ? moment(subitem.endDate).format('DD-MM-YYYY') + : '--', principalRecipient: subitem.principalRecipient, }; }), From 4c8d71c5816756090cd1b1a701d9c600cfc52ba2 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 8 Jul 2024 13:11:34 +0300 Subject: [PATCH 23/44] fix: inverted pledge & contribution values --- .../pledgescontributions.controller.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index dd98fff..b664433 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -266,20 +266,20 @@ export class PledgescontributionsController { const groupedByDonorSubType = _.groupBy(value, 'donorSubType'); const obj = { name: key, - value: _.sumBy(value, 'actualAmount'), - value1: _.sumBy(value, 'plannedAmount'), + value: _.sumBy(value, 'plannedAmount'), + value1: _.sumBy(value, 'actualAmount'), items: _.map( groupedByDonorSubType, (donorSubTypeValue, donorSubTypeKey) => { const groupedByDonor = _.groupBy(donorSubTypeValue, 'donor'); return { name: donorSubTypeKey, - value: _.sumBy(donorSubTypeValue, 'actualAmount'), - value1: _.sumBy(donorSubTypeValue, 'plannedAmount'), + value: _.sumBy(donorSubTypeValue, 'plannedAmount'), + value1: _.sumBy(donorSubTypeValue, 'actualAmount'), items: _.map(groupedByDonor, (donorValue, donorKey) => ({ name: donorKey, - value: _.sumBy(donorValue, 'actualAmount'), - value1: _.sumBy(donorValue, 'plannedAmount'), + value: _.sumBy(donorValue, 'plannedAmount'), + value1: _.sumBy(donorValue, 'actualAmount'), })), }; }, @@ -505,20 +505,20 @@ export class PledgescontributionsController { const groupedByDonorSubType = _.groupBy(value, 'donorSubType'); const obj = { name: key, - pledge: _.sumBy(value, 'actualAmount'), - contribution: _.sumBy(value, 'plannedAmount'), + pledge: _.sumBy(value, 'plannedAmount'), + contribution: _.sumBy(value, 'actualAmount'), _children: _.map( groupedByDonorSubType, (donorSubTypeValue, donorSubTypeKey) => { const groupedByDonor = _.groupBy(donorSubTypeValue, 'donor'); return { name: donorSubTypeKey, - pledge: _.sumBy(donorSubTypeValue, 'actualAmount'), - contribution: _.sumBy(donorSubTypeValue, 'plannedAmount'), + pledge: _.sumBy(donorSubTypeValue, 'plannedAmount'), + contribution: _.sumBy(donorSubTypeValue, 'actualAmount'), _children: _.map(groupedByDonor, (donorValue, donorKey) => ({ name: donorKey, - pledge: _.sumBy(donorValue, 'actualAmount'), - contribution: _.sumBy(donorValue, 'plannedAmount'), + pledge: _.sumBy(donorValue, 'plannedAmount'), + contribution: _.sumBy(donorValue, 'actualAmount'), })), }; }, From da33989d8322117162d9afa84b978d95e4cddb0e Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 8 Jul 2024 15:09:18 +0300 Subject: [PATCH 24/44] fix: allocations geo filtering --- src/controllers/allocations.controller.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index e2b33a2..bfcdbf8 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -104,7 +104,7 @@ export class AllocationsController { let filterString = filterFinancialIndicators( this.req.query, AllocationCumulativeByCyclesFieldsMapping.urlParams, - 'geography/name', + ['geography/name', 'geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -189,7 +189,7 @@ export class AllocationsController { const filterString = filterFinancialIndicators( this.req.query, AllocationSunburstFieldsMapping.urlParams, - 'geography/name', + ['geography/name', 'geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -236,7 +236,7 @@ export class AllocationsController { const filterString = filterFinancialIndicators( this.req.query, urlParams, - 'geography/name', + ['geography/name', 'geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -310,7 +310,7 @@ export class AllocationsController { const filterString = filterFinancialIndicators( this.req.query, AllocationTableFieldsMapping.urlParams, - 'geography/name', + ['geography/name', 'geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -406,13 +406,13 @@ export class AllocationsController { let filterString = filterFinancialIndicators( this.req.query, AllocationRadialFieldsMapping.urlParams, - 'geography/name', + ['geography/name', 'geography/code'], 'activityArea/name', ); let filterString2 = filterFinancialIndicators( this.req.query, AllocationRadialFieldsMapping.countriesCountUrlParams, - 'geography/name', + ['geography/name', 'geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; From e39d9d6a190aab1872911e6ed16647f8d93da10c Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 8 Jul 2024 16:12:19 +0300 Subject: [PATCH 25/44] fix: grant implementation period filtering --- src/config/mapping/grants/grantOverview.json | 1 + src/controllers/grants.controller.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/mapping/grants/grantOverview.json b/src/config/mapping/grants/grantOverview.json index 011b7e9..2fcd348 100644 --- a/src/config/mapping/grants/grantOverview.json +++ b/src/config/mapping/grants/grantOverview.json @@ -16,6 +16,7 @@ {"label": "Signed", "value": "Total Signed Amount - Reference Rate"} ] }, + "code": "code", "status": "status.statusName", "narratives": "narratives", "narrative": { diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index a13c8e8..d501172 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -207,7 +207,9 @@ export class GrantsController { [GrantOverviewMapping.implementationPeriodFrom], ['asc'], ); - const period = periods[ip - 1]; + const period = _.find(periods, { + [GrantOverviewMapping.implementationPeriod.code]: `${id}P0${ip}`, + }); const data: { status: string; goals: string[]; From 6fa1152ef6d2ac0e48b8c4be25c2d0ed6b8ed6eb Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 8 Jul 2024 17:50:34 +0300 Subject: [PATCH 26/44] chore: incremental --- src/controllers/allocations.controller.ts | 22 +++++++++++++++------- src/controllers/eligibility.controller.ts | 10 +++++----- src/controllers/results.controller.ts | 8 ++++---- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index bfcdbf8..bd5247a 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -78,13 +78,21 @@ async function getAllocationsData(url: string) { ), }, tooltip: { - items: groupedByName[name].map((item: any) => ({ - name: _.get(item, AllocationRadialFieldsMapping.tooltipItem, ''), - value: _.get(item, AllocationRadialFieldsMapping.value, 0), - percentage: - (_.get(item, AllocationRadialFieldsMapping.value, 0) / value) * - 100, - })), + items: _.filter( + groupedByName[name].map((item: any) => ({ + name: _.get( + item, + AllocationRadialFieldsMapping.tooltipItem, + '', + ), + value: _.get(item, AllocationRadialFieldsMapping.value, 0), + percentage: + (_.get(item, AllocationRadialFieldsMapping.value, 0) / + value) * + 100, + })), + item => item.name !== 'GLOBAL_FUND_HIERARCHY', + ), }, }); }); diff --git a/src/controllers/eligibility.controller.ts b/src/controllers/eligibility.controller.ts index ea44080..a80b298 100644 --- a/src/controllers/eligibility.controller.ts +++ b/src/controllers/eligibility.controller.ts @@ -341,7 +341,7 @@ export class EligibilityController { // v2 - @get('/eligibility') + @get('/v2/eligibility') @response(200, ELIGIBILITY_RESPONSE) eligibility(): object { const aggregateByField = @@ -419,7 +419,7 @@ export class EligibilityController { .catch(handleDataApiError); } - @get('/eligibility/years') + @get('/v2/eligibility/years') @response(200, ELIGIBILITY_RESPONSE) eligibilityYearsV2(): object { const url = `${urls.eligibility}/?${EligibilityYearsFieldsMapping.aggregation}`; @@ -440,7 +440,7 @@ export class EligibilityController { .catch(handleDataApiError); } - @get('/eligibility/country') + @get('/v2/eligibility/country') @response(200, ELIGIBILITY_COUNTRY_RESPONSE) eligibilityCountry(): object { if (_.get(this.req.query, 'locations', '').length === 0) { @@ -835,7 +835,7 @@ export class EligibilityController { .catch(handleDataApiError); } - @get('/eligibility/status/codelist') + @get('/v2/eligibility/status/codelist') @response(200) eligibilityStatusCodelist(): object { const keys = Object.keys(ScatterplotFieldsMapping.statusValues); @@ -848,7 +848,7 @@ export class EligibilityController { }; } - @get('/eligibility/disease-burden/codelist') + @get('/v2/eligibility/disease-burden/codelist') @response(200) eligibilityDiseaseBurdenCodelist(): object { return { diff --git a/src/controllers/results.controller.ts b/src/controllers/results.controller.ts index abf19d6..4ea10af 100644 --- a/src/controllers/results.controller.ts +++ b/src/controllers/results.controller.ts @@ -296,14 +296,14 @@ export class ResultsController { }; }); - return {data}; + return {data: _.orderBy(data, 'value', 'desc')}; }) .catch(handleDataApiError); } // v2 - @get('/results') + @get('/v2/results') @response(200, RESULTS_RESPONSE) results(): object { const mapper = mapTransform(resultsMap); @@ -347,7 +347,7 @@ export class ResultsController { .catch(handleDataApiError); } - @get('/results/years') + @get('/v2/results/years') @response(200, RESULTS_RESPONSE) resultYears(): object { const url = `${urls.results}/?${ResultsYearsMappingFields.aggregation}`; @@ -364,7 +364,7 @@ export class ResultsController { .catch(handleDataApiError); } - @get('/results-stats') + @get('/v2/results-stats') @response(200, RESULT_STATS_RESPONSE) resultStats(): object { const filterString = getFilterStringForStats( From db64aa42792a14a839eed70b13382f8ad2f44eb4 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 9 Jul 2024 13:26:37 +0300 Subject: [PATCH 27/44] feat: adjustments --- src/config/mapping/budgets/metrics.json | 5 +- src/config/mapping/expenditures/bar.json | 8 +- src/config/mapping/expenditures/heatmap.json | 2 +- src/config/mapping/expenditures/table.json | 6 +- src/config/urls/index.json | 64 +++---- src/controllers/budgets.controller.ts | 162 ++++++++++++++++-- src/controllers/expenditures.controller.ts | 2 +- .../pledgescontributions.controller.ts | 18 +- 8 files changed, 203 insertions(+), 64 deletions(-) diff --git a/src/config/mapping/budgets/metrics.json b/src/config/mapping/budgets/metrics.json index cd88850..7d88eb4 100644 --- a/src/config/mapping/budgets/metrics.json +++ b/src/config/mapping/budgets/metrics.json @@ -6,11 +6,12 @@ "disbursementIndicatorName": "Total Disbursement to Last Expenditure Reporting Period - Reference Rate", "cashBalanceIndicatorName": "Opening Cash Balance - Reference Rate", "organisationName": "implementationPeriod.grant.principalRecipient.name", - "organisationType": "implementationPeriod.grant.principalRecipient.type.name", + "organisationSubType": "implementationPeriod.grant.principalRecipient.type.name", + "organisationType": "implementationPeriod.grant.principalRecipient.type.parent.name", "expenditureValue": "actualCumulative", "budgetValue": "plannedCumulative", "disbursementValue": "actualCumulative", "cashBalanceValue": "actual", "urlParams": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))", - "urlParamsOrganisations": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName,implementationPeriod/grant/principalRecipient/type/name,implementationPeriod/grant/principalRecipient/name),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))" + "urlParamsOrganisations": "?$apply=filter(financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((indicatorName,implementationPeriod/grant/principalRecipient/type/parent/name,implementationPeriod/grant/principalRecipient/type/name,implementationPeriod/grant/principalRecipient/name),aggregate(actualAmount with sum as actual,actualAmountCumulative with sum as actualCumulative,plannedAmountCumulative with sum as plannedCumulative))" } diff --git a/src/config/mapping/expenditures/bar.json b/src/config/mapping/expenditures/bar.json index 7a076c3..98cf95c 100644 --- a/src/config/mapping/expenditures/bar.json +++ b/src/config/mapping/expenditures/bar.json @@ -1,8 +1,8 @@ { "dataPath": "value", - "name": ".parent.parent.name", - "itemName": ".parent.name", + "name": ".parent.name", + "itemName": ".name", "indicatorName": "indicatorName", - "value": "actualCumulative", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/parent/name,/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative))" + "value": "actual", + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmount with sum as actual))" } diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index 1ddfd3c..a3b2698 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -3,7 +3,7 @@ "budget": "value2", "expenditure": "value1", "cycle": "periodCovered", - "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true)/groupby((,),aggregate(actualAmountCumulative with sum as value1,plannedAmountCumulative with sum as value2))", + "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true)/groupby((,),aggregate(actualAmount with sum as value1,plannedAmount with sum as value2))", "url1Items": ["Malaria", "Tuberculosis"], "url2Items": ["HIV/AIDS", "Other"], "fields": { diff --git a/src/config/mapping/expenditures/table.json b/src/config/mapping/expenditures/table.json index 5e1e1aa..e87edaa 100644 --- a/src/config/mapping/expenditures/table.json +++ b/src/config/mapping/expenditures/table.json @@ -1,9 +1,9 @@ { "dataPath": "value", - "name": ".parent.parent.name", - "itemName": ".parent.name", + "name": ".parent.name", + "itemName": ".name", "indicatorName": "indicatorName", "cumulativeExpenditureValue": "actualCumulative", "periodExpenditureValue": "actual", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/parent/name,/parent/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" } diff --git a/src/config/urls/index.json b/src/config/urls/index.json index 01ca2e1..db952b1 100644 --- a/src/config/urls/index.json +++ b/src/config/urls/index.json @@ -1,37 +1,37 @@ { - "grants": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreements/?$count=true&", - "grantsNoCount": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreements", - "results": "https://fetch.theglobalfund.org/v3.4/odata/VReportingResults", - "documents": "https://fetch.theglobalfund.org/v3.4/odata/VProgramDocuments", - "disbursements": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreementDisbursements", - "commitments": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementCommitments", - "vcommitments": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreementCommitments", - "grantDetailGrants": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementImplementationPeriods", - "grantPeriods": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementImplementationPeriods", - "vgrantPeriods": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreementImplementationPeriods", - "grantCycles": "https://fetch.theglobalfund.org/v3.4/odata/grantCycles", - "grantIPGoalsObjectives": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementImplementationPeriodGoalsAndObjectives", - "grantDetailDisbursements": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementDisbursements", - "budgets": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementImplementationPeriodDetailedBudgets", - "allocations": "https://fetch.theglobalfund.org/v3.4/odata/Allocations", - "eligibility": "https://fetch.theglobalfund.org/v3.4/odata/VEligibility", + "grants": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements/?$count=true&", + "grantsNoCount": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements", + "results": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VReportingResults", + "documents": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VProgramDocuments", + "disbursements": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementDisbursements", + "commitments": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementCommitments", + "vcommitments": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementCommitments", + "grantDetailGrants": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriods", + "grantPeriods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriods", + "vgrantPeriods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementImplementationPeriods", + "grantCycles": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/grantCycles", + "grantIPGoalsObjectives": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriodGoalsAndObjectives", + "grantDetailDisbursements": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementDisbursements", + "budgets": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriodDetailedBudgets", + "allocations": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Allocations", + "eligibility": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VEligibility", "geojson": "https://data.theglobalfund.org/static/simple.geo.json", - "countrycontactinfo": "https://fetch.theglobalfund.org/v3.4/odata/VContacts", - "pledgescontributions": "https://fetch.theglobalfund.org/v3.4/odata/PledgesAndContributions", - "performancerating": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementProgressUpdates", - "performanceframework": "https://fetch.theglobalfund.org/v3.4/odata/GrantAgreementImplementationPeriodPerformanceFrameworks", - "fundingrequests": "https://fetch.theglobalfund.org/v3.4/odata/FundingRequests", - "multicountries": "https://fetch.theglobalfund.org/v3.4/odata/MultiCountries", - "indicators": "https://fetch.theglobalfund.org/v3.4/odata/VNationalHealthAndDevelopmentIndicators", - "filteroptionslocations": "https://fetch.theglobalfund.org/v3.4/odata/GeographicAreas?$select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId&$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members)))&$filter=geographicAreaName eq 'World'", - "filteroptionsmulticountries": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreements?$apply=filter(multiCountryName%20ne%20null)/groupby((MultiCountryName,GeographicAreaCode_ISO3),aggregate(GrantAgreementId%20with%20countdistinct%20as%20count))", - "filteroptionscomponents": "https://fetch.theglobalfund.org/v3.4/odata/Components", - "filteroptionspartnertypes": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreements?$apply=groupby((principalRecipientClassificationName,principalRecipientClassificationId,principalRecipientSubClassificationName,principalRecipientSubClassificationId,principalRecipientName,principalRecipientId))", - "filteroptionsstatus": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreements?$apply=groupby((grantAgreementStatusTypeName))", - "filteroptionsstatus1": "https://fetch.theglobalfund.org/v3.4/odata/VGrantAgreementImplementationPeriods?$apply=groupby((implementationPeriodStatusTypeName))", - "filteroptionsdonors": "https://fetch.theglobalfund.org/v3.4/odata/donors/?$filter=donorId%20eq%20c06eaea9-c81b-442b-b403-dc348f3734eb%20or%20donorId%20eq%2025188dfc-7567-4e08-b8d0-088a6b83f238%20or%20donorId%20eq%20641c05fa-129d-4b57-a213-b5f6852ddc5f%20or%20donorid%20eq%20cc30fc59-b2fa-49e3-aa5a-762727daa68c&$expand=members($expand=members($expand=members($expand=members)))", - "filteroptionsreplenishmentperiods": "https://fetch.theglobalfund.org/v3.4/odata/replenishmentperiods", - "multicountriescountriesdata": "https://fetch.theglobalfund.org/v3.4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", + "countrycontactinfo": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VContacts", + "pledgescontributions": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/PledgesAndContributions", + "performancerating": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementProgressUpdates", + "performanceframework": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriodPerformanceFrameworks", + "fundingrequests": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/FundingRequests", + "multicountries": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/MultiCountries", + "indicators": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VNationalHealthAndDevelopmentIndicators", + "filteroptionslocations": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GeographicAreas?$select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId&$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members)))&$filter=geographicAreaName eq 'World'", + "filteroptionsmulticountries": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements?$apply=filter(multiCountryName%20ne%20null)/groupby((MultiCountryName,GeographicAreaCode_ISO3),aggregate(GrantAgreementId%20with%20countdistinct%20as%20count))", + "filteroptionscomponents": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Components", + "filteroptionspartnertypes": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements?$apply=groupby((principalRecipientClassificationName,principalRecipientClassificationId,principalRecipientSubClassificationName,principalRecipientSubClassificationId,principalRecipientName,principalRecipientId))", + "filteroptionsstatus": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements?$apply=groupby((grantAgreementStatusTypeName))", + "filteroptionsstatus1": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementImplementationPeriods?$apply=groupby((implementationPeriodStatusTypeName))", + "filteroptionsdonors": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/donors/?$filter=donorId%20eq%20c06eaea9-c81b-442b-b403-dc348f3734eb%20or%20donorId%20eq%2025188dfc-7567-4e08-b8d0-088a6b83f238%20or%20donorId%20eq%20641c05fa-129d-4b57-a213-b5f6852ddc5f%20or%20donorid%20eq%20cc30fc59-b2fa-49e3-aa5a-762727daa68c&$expand=members($expand=members($expand=members($expand=members)))", + "filteroptionsreplenishmentperiods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/replenishmentperiods", + "multicountriescountriesdata": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", "FILTER_OPTIONS_GEOGRAPHY": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", "FILTER_OPTIONS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=type eq 'Component'", "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc", diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index d68fed3..a2feb0b 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -736,9 +736,9 @@ export class BudgetsController { ); const totalValue = disbursementValue + cashBalanceValue; const utilization = (totalValue / budgetValue) * 100; - const groupedByOrganisations = _.groupBy( + const groupedBySubOrganisations = _.groupBy( value, - BudgetsMetricsFieldsMapping.organisationName, + BudgetsMetricsFieldsMapping.organisationSubType, ); return { level: 0, @@ -746,7 +746,7 @@ export class BudgetsController { value: utilization, color: '#013E77', items: _.orderBy( - _.map(groupedByOrganisations, (value2, key2) => { + _.map(groupedBySubOrganisations, (value2, key2) => { const disbursement = _.filter( value2, (item: any) => @@ -779,12 +779,66 @@ export class BudgetsController { ); const totalValue = disbursementValue + cashBalanceValue; const utilization = (totalValue / budgetValue) * 100; + const groupedByOrganisations = _.groupBy( + value2, + BudgetsMetricsFieldsMapping.organisationName, + ); return { level: 1, name: key2, value: utilization, color: '#013E77', - items: [], + items: _.orderBy( + _.map(groupedByOrganisations, (value3, key3) => { + const disbursement = _.filter( + value3, + (item: any) => + item[ + BudgetsMetricsFieldsMapping.indicatorNameField + ] === + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + ); + const cashBalance = _.filter( + value3, + (item: any) => + item[ + BudgetsMetricsFieldsMapping.indicatorNameField + ] === + BudgetsMetricsFieldsMapping.cashBalanceIndicatorName, + ); + const budget = _.filter( + value3, + (item: any) => + item[ + BudgetsMetricsFieldsMapping.indicatorNameField + ] === + BudgetsMetricsFieldsMapping.budgetIndicatorName, + ); + const disbursementValue = _.sumBy( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + ); + const cashBalanceValue = _.sumBy( + cashBalance, + BudgetsMetricsFieldsMapping.cashBalanceValue, + ); + const budgetValue = _.sumBy( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + ); + const totalValue = disbursementValue + cashBalanceValue; + const utilization = (totalValue / budgetValue) * 100; + return { + level: 2, + name: key3, + value: utilization, + color: '#013E77', + items: [], + }; + }), + 'name', + 'asc', + ), }; }), 'name', @@ -885,9 +939,9 @@ export class BudgetsController { BudgetsMetricsFieldsMapping.budgetValue, ); const absorption = (expenditureValue / budgetValue) * 100; - const groupedByOrganisations = _.groupBy( + const groupedBySubOrganisations = _.groupBy( value, - BudgetsMetricsFieldsMapping.organisationName, + BudgetsMetricsFieldsMapping.organisationSubType, ); return { level: 0, @@ -895,7 +949,7 @@ export class BudgetsController { value: absorption, color: '#00B5AE', items: _.orderBy( - _.map(groupedByOrganisations, (value2, key2) => { + _.map(groupedBySubOrganisations, (value2, key2) => { const expenditure = _.filter( value2, (item: any) => @@ -917,12 +971,54 @@ export class BudgetsController { BudgetsMetricsFieldsMapping.budgetValue, ); const absorption = (expenditureValue / budgetValue) * 100; + const groupedByOrganisations = _.groupBy( + value2, + BudgetsMetricsFieldsMapping.organisationName, + ); return { level: 1, name: key2, value: absorption, color: '#00B5AE', - items: [], + items: _.orderBy( + _.map(groupedByOrganisations, (value3, key3) => { + const expenditure = _.filter( + value3, + (item: any) => + item[ + BudgetsMetricsFieldsMapping.indicatorNameField + ] === + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + ); + const budget = _.filter( + value3, + (item: any) => + item[ + BudgetsMetricsFieldsMapping.indicatorNameField + ] === + BudgetsMetricsFieldsMapping.budgetIndicatorName, + ); + const expenditureValue = _.sumBy( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + ); + const budgetValue = _.sumBy( + budget, + BudgetsMetricsFieldsMapping.budgetValue, + ); + const absorption = + (expenditureValue / budgetValue) * 100; + return { + level: 2, + name: key3, + value: absorption, + color: '#00B5AE', + items: [], + }; + }), + 'name', + 'asc', + ), }; }), 'name', @@ -1023,9 +1119,9 @@ export class BudgetsController { BudgetsMetricsFieldsMapping.disbursementValue, ); const utilization = (expenditureValue / disbursementValue) * 100; - const groupedByOrganisations = _.groupBy( + const groupedBySubOrganisations = _.groupBy( value, - BudgetsMetricsFieldsMapping.organisationName, + BudgetsMetricsFieldsMapping.organisationSubType, ); return { level: 0, @@ -1033,7 +1129,7 @@ export class BudgetsController { value: utilization, color: '#0A2840', items: _.orderBy( - _.map(groupedByOrganisations, (value2, key2) => { + _.map(groupedBySubOrganisations, (value2, key2) => { const expenditure = _.filter( value2, (item: any) => @@ -1056,12 +1152,54 @@ export class BudgetsController { ); const utilization = (expenditureValue / disbursementValue) * 100; + const groupedByOrganisations = _.groupBy( + value2, + BudgetsMetricsFieldsMapping.organisationName, + ); return { level: 1, name: key2, value: utilization, color: '#0A2840', - items: [], + items: _.orderBy( + _.map(groupedByOrganisations, (value3, key3) => { + const expenditure = _.filter( + value3, + (item: any) => + item[ + BudgetsMetricsFieldsMapping.indicatorNameField + ] === + BudgetsMetricsFieldsMapping.expenditureIndicatorName, + ); + const disbursement = _.filter( + value3, + (item: any) => + item[ + BudgetsMetricsFieldsMapping.indicatorNameField + ] === + BudgetsMetricsFieldsMapping.disbursementIndicatorName, + ); + const expenditureValue = _.sumBy( + expenditure, + BudgetsMetricsFieldsMapping.expenditureValue, + ); + const disbursementValue = _.sumBy( + disbursement, + BudgetsMetricsFieldsMapping.disbursementValue, + ); + const utilization = + (expenditureValue / disbursementValue) * 100; + return { + level: 2, + name: key3, + value: utilization, + color: '#0A2840', + items: [], + }; + }), + 'name', + 'asc', + ), }; }), 'name', diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index be12355..f49a3c3 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -282,7 +282,7 @@ export class ExpendituresController { }; }); - return {data}; + return {data: _.filter(data, item => item.name !== 'null')}; }) .catch(handleDataApiError); } diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index b664433..35d82a1 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -112,13 +112,13 @@ export class PledgescontributionsController { const filterString1 = filterFinancialIndicators( this.req.query, PledgesContributionsStatsFieldsMapping.totalValuesUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); const filterString2 = filterFinancialIndicators( this.req.query, PledgesContributionsStatsFieldsMapping.donorTypesCountUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -181,7 +181,7 @@ export class PledgescontributionsController { const filterString = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.donorBarUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -310,7 +310,7 @@ export class PledgescontributionsController { const filterString = filterFinancialIndicators( this.req.query, urlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -420,7 +420,7 @@ export class PledgescontributionsController { const filterString = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.donorBarUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -545,13 +545,13 @@ export class PledgescontributionsController { let filterString1 = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.pledgesUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); let filterString2 = filterFinancialIndicators( this.req.query, PledgesContributionsBarFieldsMapping.contributionsUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -571,7 +571,7 @@ export class PledgescontributionsController { geographies: countryCode, }, PledgesContributionsBarFieldsMapping.pledgesUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); let filterString2 = filterFinancialIndicators( @@ -580,7 +580,7 @@ export class PledgescontributionsController { geographies: countryCode, }, PledgesContributionsBarFieldsMapping.contributionsUrlParams, - 'donor/geography/name', + ['donor/geography/name', 'donor/geography/code'], 'activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; From 526d2aea1490224c73336e68e1441a17a2e00996 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 9 Jul 2024 14:20:45 +0300 Subject: [PATCH 28/44] feat: funding requests component filtering --- .../mapping/fundingrequests/table-2.json | 2 +- src/controllers/fundingrequests.controller.ts | 33 +++++++++++++------ src/utils/filtering/fundingRequests.ts | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/config/mapping/fundingrequests/table-2.json b/src/config/mapping/fundingrequests/table-2.json index 1631983..bb79c4c 100644 --- a/src/config/mapping/fundingrequests/table-2.json +++ b/src/config/mapping/fundingrequests/table-2.json @@ -24,5 +24,5 @@ ] } ], - "urlParams": "?$filter=&$expand=geography,implementationPeriods($expand=milestones,grant($expand=principalRecipient))" + "urlParams": "?$filter=&$select=name,submissionDate,reviewApproach,window,reviewOutcome,reviewDate,differentiationCategory&$expand=geography($select=name),implementationPeriods($select=periodStartDate,periodEndDate;$expand=milestones($select=date),grant($select=code;$expand=principalRecipient($select=name)))" } diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 2b8c83f..044fb41 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -36,7 +36,11 @@ export class FundingRequestsController { const groupedByGeo = _.groupBy(data, Table2FieldsMapping.groupby); - return { + let result: { + data: any; + signedCount?: any; + submittedCount?: any; + } = { data: _.map(groupedByGeo, (items, key) => { return { components: key, @@ -72,15 +76,24 @@ export class FundingRequestsController { }), }; }), - submittedCount: _.map(groupedByGeo, (items, key) => ({ - name: key, - count: items.length, - })), - signedCount: _.map(groupedByGeo, (items, key) => ({ - name: key, - count: _.filter(items, (item: any) => item.items.length > 0).length, - })), }; + + if (this.req.query.withCounts) { + result = { + ...result, + submittedCount: _.map(groupedByGeo, (items, key) => ({ + name: key, + count: items.length, + })), + signedCount: _.map(groupedByGeo, (items, key) => ({ + name: key, + count: _.filter(items, (item: any) => item.items.length > 0) + .length, + })), + }; + } + + return result; }) .catch(handleDataApiError); } @@ -92,7 +105,7 @@ export class FundingRequestsController { ) { return axios .get( - `http://localhost:4200/funding-requests?geographies=${countryCode}&periods=${this.req.query.periods}`, + `http://localhost:4200/funding-requests?withCounts=1&geographies=${countryCode}&periods=${this.req.query.periods}`, ) .then((resp: AxiosResponse) => resp.data) .catch(handleDataApiError); diff --git a/src/utils/filtering/fundingRequests.ts b/src/utils/filtering/fundingRequests.ts index a0fad46..c45cfc5 100644 --- a/src/utils/filtering/fundingRequests.ts +++ b/src/utils/filtering/fundingRequests.ts @@ -4,7 +4,7 @@ import {getGeographyValues} from './geographies'; const MAPPING = { geography: ['geography/name', 'geography/code'], - component: 'activityArea/name', + component: 'activityAreas/any(tag:tag/name in ())', period: 'periodFrom', trpWindow: 'window', portfolioCategory: 'differentiationCategory', From 7c0ba002cd379dafe6b46cab7b28e6aed0587bfd Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 9 Jul 2024 17:09:26 +0300 Subject: [PATCH 29/44] feat: grant targets & results adjustments --- src/config/mapping/grants/targetsResults.json | 4 ++- src/controllers/grants.controller.ts | 31 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/config/mapping/grants/targetsResults.json b/src/config/mapping/grants/targetsResults.json index c5745af..e82c011 100644 --- a/src/config/mapping/grants/targetsResults.json +++ b/src/config/mapping/grants/targetsResults.json @@ -8,9 +8,11 @@ "baselineValue": "baselineValuePercentage", "baselineYear": "baselineValueYear", "baselineSource": "valueSource", + "startDate": "startDate", + "endDate": "endDate", "year": "targetValueYear", "target": "targetValuePercentage", "result": "resultValuePercentage", "achievement": "performance", - "urlParams": "?$filter=programmaticDataSet eq 'IMPLEMENTATION_PERIOD_TARGETS_RESULTS' AND implementationPeriod/code eq '' AND valueType eq ''&$expand=activityArea($select=name)&$select=activityArea,indicatorName,isReversed,geographicCoverage,aggregationType,baselineValuePercentage,baselineValueYear,valueSource,targetValueYear,targetValuePercentage,resultValuePercentage,performance" + "urlParams": "?$filter=programmaticDataSet eq 'IMPLEMENTATION_PERIOD_TARGETS_RESULTS' AND implementationPeriod/code eq '' AND valueType eq ''&$expand=activityArea($select=name)&$select=activityArea,indicatorName,isReversed,geographicCoverage,startDate,baselineValuePercentage,baselineValueYear,valueSource,targetValueYear,targetValuePercentage,resultValuePercentage,performance" } diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index d501172..473eee8 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -506,6 +506,9 @@ export class GrantsController { data.nodes.push({ name: category1, level: 1, + itemStyle: { + color: '#252C34', + }, }); data.links.push({ source: 'Total budget', @@ -530,6 +533,9 @@ export class GrantsController { data.nodes.push({ name, level: 2, + itemStyle: { + color: '#252C34', + }, }); data.links.push({ source: category1, @@ -564,6 +570,9 @@ export class GrantsController { data.nodes.push({ name, level: 3, + itemStyle: { + color: '#252C34', + }, }); data.links.push({ @@ -620,6 +629,14 @@ export class GrantsController { let years: string[] = []; + if (type === 'Coverage / Output indicator') { + _.forEach(raw, (item: any) => { + item.targetValueYear = moment( + _.get(item, GrantTargetsResultsMapping.startDate, ''), + ).get('year'); + }); + } + _.map(groupedByModule, (moduleItems, key) => { const item: any = { name: key, @@ -643,6 +660,10 @@ export class GrantsController { GrantTargetsResultsMapping.year, ); years = [...years, ...Object.keys(groupedByYear)]; + years = _.filter( + _.uniq(years), + year => year !== 'null' && year !== 'NaN', + ); let itempush = { reversed: _.get(item, GrantTargetsResultsMapping.reversed, false) === @@ -711,9 +732,13 @@ export class GrantsController { data.push(item); }); - years = _.uniq(years); - - return {data, years}; + return { + data: + type === 'Coverage / Output indicator' + ? data + : _.get(data, '[0]._children', []), + years, + }; }) .catch(handleDataApiError); } From 5a6ff2c7a8f61492f335ed358027f808e5649232 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 9 Jul 2024 18:26:08 +0300 Subject: [PATCH 30/44] fix: continent, region filtering --- src/controllers/budgets.controller.ts | 40 ++++++++++++++++----- src/controllers/disbursements.controller.ts | 5 ++- src/controllers/expenditures.controller.ts | 10 ++++-- src/utils/filtering/documents.ts | 22 ++++++++---- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index a2feb0b..b37bc75 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -250,7 +250,10 @@ export class BudgetsController { '', componentField, ), - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -381,7 +384,10 @@ export class BudgetsController { const filterString = filterFinancialIndicators( this.req.query, BudgetsTableFieldsMapping.urlParams, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -526,7 +532,10 @@ export class BudgetsController { '', componentField, ), - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -643,7 +652,10 @@ export class BudgetsController { const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'implementationPeriod/grant/activityArea/name', ); const filterString2 = filterFinancialIndicators( @@ -867,13 +879,19 @@ export class BudgetsController { const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'implementationPeriod/grant/activityArea/name', ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'implementationPeriod/grant/activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -1047,13 +1065,19 @@ export class BudgetsController { const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'implementationPeriod/grant/activityArea/name', ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'implementationPeriod/grant/activityArea/name', ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index b9a4670..c7390e9 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -190,7 +190,10 @@ export class DisbursementsController { const filterString = filterFinancialIndicators( this.req.query, FinancialInsightsStatsMapping.urlParams, - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index f49a3c3..f90caa6 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -246,7 +246,10 @@ export class ExpendituresController { //g, componentField, ), - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -296,7 +299,10 @@ export class ExpendituresController { //g, componentField, ), - 'implementationPeriod/grant/geography/name', + [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ], `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/utils/filtering/documents.ts b/src/utils/filtering/documents.ts index a310cf6..01e05a4 100644 --- a/src/utils/filtering/documents.ts +++ b/src/utils/filtering/documents.ts @@ -1,8 +1,9 @@ import _ from 'lodash'; import filtering from '../../config/filtering/index.json'; +import {getGeographyValues} from './geographies'; const MAPPING = { - geography: 'geography/code', + geography: ['geography/code', 'geography/name'], type: 'documentType/parent/name', search: `contains(documentType/parent/name,) OR contains(title,)`, }; @@ -13,14 +14,21 @@ export function filterDocuments( ): string { let str = ''; - const geographies = _.filter( + const geos = _.filter( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, - ).map((geography: string) => `'${geography}'`); - if (geographies.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.geography}${ - filtering.in - }(${geographies.join(filtering.multi_param_separator)})`; + ); + const geographies = geos.map((geography: string) => `'${geography}'`); + if (geos.length > 0) { + const values: string[] = [...geographies, ...getGeographyValues(geos)]; + str += `${str.length > 0 ? ' AND ' : ''}(${MAPPING.geography + .map( + m => + `${m}${filtering.in}(${values.join( + filtering.multi_param_separator, + )})`, + ) + .join(' OR ')})`; } const types = _.filter( From 314866ad85ba38f6f188b0e03a5d9fe4ab4bfcb1 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Tue, 9 Jul 2024 19:26:50 +0300 Subject: [PATCH 31/44] chore: remove legacy code & assets --- src/controllers/allocations.controller.ts | 726 +--- src/controllers/budgets.controller.ts | 830 +--- .../data-themes/raw-data.controller.ts | 175 - src/controllers/disbursements.controller.ts | 3645 +---------------- src/controllers/documents.controller.ts | 215 +- src/controllers/eligibility.controller.ts | 611 +-- src/controllers/filteroptions.controller.ts | 434 +- src/controllers/fundingrequests.controller.ts | 147 - src/controllers/grants.controller.ts | 355 +- src/controllers/index.ts | 4 - src/controllers/location.controller.ts | 321 +- src/controllers/partner.controller.ts | 75 - .../performanceframework.controller.ts | 327 -- .../performancerating.controller.ts | 97 - .../pledgescontributions.controller.ts | 816 +--- src/controllers/results.controller.ts | 171 +- src/interfaces/allocations.ts | 22 - src/interfaces/budgetsFlow.ts | 18 - src/interfaces/budgetsTimeCycle.ts | 3 - src/interfaces/budgetsTreemap.ts | 15 - src/interfaces/disbursementsTimeCycle.ts | 3 - src/interfaces/disbursementsTreemap.ts | 22 - src/interfaces/documentsTable.ts | 10 - src/interfaces/eligibilityDot.ts | 7 - src/interfaces/eligibilityScatterplot.ts | 9 - src/interfaces/grantDetail.ts | 36 - src/interfaces/grantList.ts | 14 - src/interfaces/performanceFrameworkNetwork.ts | 69 - src/interfaces/performanceRating.ts | 3 - src/interfaces/pledgesContributions.ts | 19 - src/interfaces/resultList.ts | 21 - src/interfaces/search.ts | 11 - src/interfaces/simpleTable.ts | 4 - src/static-assets/countries.json | 2026 --------- src/utils/data-themes/formatRawData.ts | 24 - .../data-themes/getDatasetFilterOptions.ts | 40 - .../filtering/allocations/getFilterString.ts | 82 - .../budgets/getDrilldownFilterString.ts | 104 - .../filtering/budgets/getFilterString.ts | 106 - .../disbursements/getFilterString.ts | 130 - .../grantDetailGetFilterString.ts | 84 - .../multicountries/getFilterString.ts | 90 - .../filtering/documents/getFilterString.ts | 68 - .../filtering/eligibility/getFilterString.ts | 82 - .../fundingrequests/getFilterString.ts | 90 - src/utils/filtering/globalsearch/index.ts | 23 - src/utils/filtering/grants/getFilterString.ts | 131 - .../filtering/partner/getFilterString.ts | 101 - .../performanceframework/getFilterString.ts | 7 - .../performancerating/getFilterString.ts | 55 - .../pledges-contributions/getFilterString.ts | 75 - .../filtering/results/getFilterString.ts | 100 - src/utils/formatFinancialValue.ts | 6 - .../performanceframework/formatPFData.ts | 369 -- .../performanceframework/getTimeframes.ts | 39 - .../pledgescontributions/getD2HCoordinates.ts | 25 - 56 files changed, 13 insertions(+), 13079 deletions(-) delete mode 100644 src/controllers/data-themes/raw-data.controller.ts delete mode 100644 src/controllers/partner.controller.ts delete mode 100644 src/controllers/performanceframework.controller.ts delete mode 100644 src/controllers/performancerating.controller.ts delete mode 100644 src/interfaces/allocations.ts delete mode 100644 src/interfaces/budgetsFlow.ts delete mode 100644 src/interfaces/budgetsTimeCycle.ts delete mode 100644 src/interfaces/budgetsTreemap.ts delete mode 100644 src/interfaces/disbursementsTimeCycle.ts delete mode 100644 src/interfaces/disbursementsTreemap.ts delete mode 100644 src/interfaces/documentsTable.ts delete mode 100644 src/interfaces/eligibilityDot.ts delete mode 100644 src/interfaces/eligibilityScatterplot.ts delete mode 100644 src/interfaces/grantDetail.ts delete mode 100644 src/interfaces/performanceFrameworkNetwork.ts delete mode 100644 src/interfaces/performanceRating.ts delete mode 100644 src/interfaces/pledgesContributions.ts delete mode 100644 src/interfaces/resultList.ts delete mode 100644 src/interfaces/search.ts delete mode 100644 src/interfaces/simpleTable.ts delete mode 100644 src/static-assets/countries.json delete mode 100644 src/utils/data-themes/formatRawData.ts delete mode 100644 src/utils/data-themes/getDatasetFilterOptions.ts delete mode 100644 src/utils/filtering/allocations/getFilterString.ts delete mode 100644 src/utils/filtering/budgets/getDrilldownFilterString.ts delete mode 100644 src/utils/filtering/budgets/getFilterString.ts delete mode 100644 src/utils/filtering/disbursements/getFilterString.ts delete mode 100644 src/utils/filtering/disbursements/grantDetailGetFilterString.ts delete mode 100644 src/utils/filtering/disbursements/multicountries/getFilterString.ts delete mode 100644 src/utils/filtering/documents/getFilterString.ts delete mode 100644 src/utils/filtering/eligibility/getFilterString.ts delete mode 100644 src/utils/filtering/fundingrequests/getFilterString.ts delete mode 100644 src/utils/filtering/globalsearch/index.ts delete mode 100644 src/utils/filtering/grants/getFilterString.ts delete mode 100644 src/utils/filtering/partner/getFilterString.ts delete mode 100644 src/utils/filtering/performanceframework/getFilterString.ts delete mode 100644 src/utils/filtering/performancerating/getFilterString.ts delete mode 100644 src/utils/filtering/pledges-contributions/getFilterString.ts delete mode 100644 src/utils/filtering/results/getFilterString.ts delete mode 100644 src/utils/formatFinancialValue.ts delete mode 100644 src/utils/performanceframework/formatPFData.ts delete mode 100644 src/utils/performanceframework/getTimeframes.ts delete mode 100644 src/utils/pledgescontributions/getD2HCoordinates.ts diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index bd5247a..b1be246 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -1,59 +1,16 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; -import center from '@turf/center'; -import {points, Position} from '@turf/helpers'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; -import querystring from 'querystring'; -import filtering from '../config/filtering/index.json'; import AllocationCumulativeByCyclesFieldsMapping from '../config/mapping/allocations/cumulative-by-cycles.json'; import AllocationCyclesFieldsMapping from '../config/mapping/allocations/cycles.json'; -import AllocationsDrilldownFieldsMapping from '../config/mapping/allocations/drilldown.json'; -import AllocationsGeomapFieldsMapping from '../config/mapping/allocations/geomap.json'; -import AllocationsFieldsMapping from '../config/mapping/allocations/index.json'; -import AllocationsPeriodsFieldsMapping from '../config/mapping/allocations/periods.json'; import AllocationRadialFieldsMapping from '../config/mapping/allocations/radial.json'; import AllocationSunburstFieldsMapping from '../config/mapping/allocations/sunburst.json'; import AllocationTableFieldsMapping from '../config/mapping/allocations/table.json'; import AllocationTreemapFieldsMapping from '../config/mapping/allocations/treemap.json'; import urls from '../config/urls/index.json'; -import {AllocationsTreemapDataItem} from '../interfaces/allocations'; -import {SimpleTableRow} from '../interfaces/simpleTable'; -import staticCountries from '../static-assets/countries.json'; import {handleDataApiError} from '../utils/dataApiError'; -import {getFilterString} from '../utils/filtering/allocations/getFilterString'; import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; -import {formatFinancialValue} from '../utils/formatFinancialValue'; - -const ALLOCATIONS_RESPONSE: ResponseObject = { - description: 'Allocations Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'AllocationsResponse', - properties: { - data: { - type: 'object', - properties: { - total: {type: 'number'}, - values: {type: 'array', items: {type: 'number'}}, - keys: {type: 'array', items: {type: 'string'}}, - colors: {type: 'array', items: {type: 'string'}}, - }, - }, - }, - }, - }, - }, -}; async function getAllocationsData(url: string) { return axios @@ -104,8 +61,6 @@ async function getAllocationsData(url: string) { export class AllocationsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/allocations/cumulative-by-cycles') @response(200) async cumulativeByCycles() { @@ -497,683 +452,4 @@ export class AllocationsController { }) .catch(handleDataApiError); } - - // v2 - - @get('/allocations') - @response(200, ALLOCATIONS_RESPONSE) - allocations(): object { - const filterString = getFilterString( - this.req.query, - AllocationsFieldsMapping.allocationsAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.allocations}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.orderBy( - _.get(resp.data, AllocationsFieldsMapping.dataPath, []), - AllocationsFieldsMapping.amount, - 'desc', - ); - return { - total: _.sumBy(rawData, 'amount'), - values: rawData.map((item: any) => - _.get(item, AllocationsFieldsMapping.amount), - ), - keys: rawData.map((item: any) => - _.get(item, AllocationsFieldsMapping.component), - ), - colors: rawData.map((item: any) => - _.get( - AllocationsFieldsMapping.componentColors, - _.get(item, AllocationsFieldsMapping.component), - '', - ), - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/allocations/periods') - @response(200, ALLOCATIONS_RESPONSE) - allocationsPeriods(): object { - const filterString = getFilterString( - this.req.query, - AllocationsPeriodsFieldsMapping.aggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.allocations}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - return { - data: _.get( - resp.data, - AllocationsPeriodsFieldsMapping.dataPath, - [], - ).map( - (item: any) => - `${_.get( - item, - AllocationsPeriodsFieldsMapping.periodStart, - '', - )} - ${_.get( - item, - AllocationsPeriodsFieldsMapping.periodEnd, - '', - )}`, - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/allocations/drilldown') - @response(200, ALLOCATIONS_RESPONSE) - allocationsDrilldown(): object { - const filterString = getFilterString( - this.req.query, - AllocationsDrilldownFieldsMapping.aggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.allocations}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get( - resp.data, - AllocationsDrilldownFieldsMapping.dataPath, - [], - ); - const data: AllocationsTreemapDataItem[] = []; - const groupedByComponent = _.groupBy( - rawData, - AllocationsDrilldownFieldsMapping.component, - ); - Object.keys(groupedByComponent).forEach((component: string) => { - const value = _.sumBy( - groupedByComponent[component], - AllocationsDrilldownFieldsMapping.amount, - ); - data.push({ - name: component, - value, - formattedValue: formatFinancialValue(value), - color: '#DFE3E5', - _children: _.orderBy( - groupedByComponent[component].map((item: any) => ({ - name: - _.get( - item, - AllocationsDrilldownFieldsMapping.multicountry, - null, - ) ?? - _.get( - item, - AllocationsDrilldownFieldsMapping.locationName, - '', - ), - value: _.get(item, AllocationsDrilldownFieldsMapping.amount, 0), - formattedValue: formatFinancialValue( - _.get(item, AllocationsDrilldownFieldsMapping.amount, 0), - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: - _.get( - item, - AllocationsDrilldownFieldsMapping.multicountry, - null, - ) ?? - _.get( - item, - AllocationsDrilldownFieldsMapping.locationName, - '', - ), - value: _.get( - item, - AllocationsDrilldownFieldsMapping.amount, - 0, - ), - }, - ], - value: _.get( - item, - AllocationsDrilldownFieldsMapping.amount, - 0, - ), - }, - })), - 'value', - 'desc', - ), - tooltip: { - header: component, - value, - componentsStats: [ - { - name: component, - value, - }, - ], - }, - }); - }); - return { - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/allocations/geomap') - @response(200, ALLOCATIONS_RESPONSE) - geomap(): object { - const filterString = getFilterString( - this.req.query, - AllocationsGeomapFieldsMapping.aggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.allocations}/?${params}${filterString}`; - - return axios - .all([axios.get(url), axios.get(urls.geojson)]) - .then( - axios.spread((...responses) => { - const geoJSONData = responses[1].data.features; - const data: any = []; - const groupedDataByLocation = _.groupBy( - responses[0].data.value, - AllocationsGeomapFieldsMapping.locationCode, - ); - Object.keys(groupedDataByLocation).forEach((iso3: string) => { - const dataItems = groupedDataByLocation[iso3]; - const locationComponents: any = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: _.get(item, AllocationsGeomapFieldsMapping.component, ''), - value: item[AllocationsGeomapFieldsMapping.amount], - }); - }); - data.push({ - code: iso3, - components: locationComponents, - value: _.sumBy(locationComponents, 'value'), - }); - }); - const maxValue: number = _.max(data.map((d: any) => d.value)) ?? 0; - let interval = 0; - if (maxValue) { - interval = maxValue / 13; - } - const intervals: number[] = []; - for (let i = 0; i < 13; i++) { - intervals.push(interval * i); - } - const features = geoJSONData.map((feature: any) => { - const fItem = _.find(data, {code: feature.id}); - let itemValue = 0; - if (fItem) { - if ( - (fItem.value < maxValue || fItem.value === maxValue) && - (fItem.value >= intervals[11] || fItem.value === intervals[11]) - ) { - itemValue = 12; - } - if ( - (fItem.value < intervals[11] || - fItem.value === intervals[11]) && - (fItem.value >= intervals[10] || fItem.value === intervals[10]) - ) { - itemValue = 11; - } - if ( - (fItem.value < intervals[10] || - fItem.value === intervals[10]) && - (fItem.value >= intervals[9] || fItem.value === intervals[9]) - ) { - itemValue = 10; - } - if ( - (fItem.value < intervals[9] || fItem.value === intervals[9]) && - (fItem.value >= intervals[8] || fItem.value === intervals[8]) - ) { - itemValue = 9; - } - if ( - (fItem.value < intervals[8] || fItem.value === intervals[8]) && - (fItem.value >= intervals[7] || fItem.value === intervals[7]) - ) { - itemValue = 8; - } - if ( - (fItem.value < intervals[7] || fItem.value === intervals[7]) && - (fItem.value >= intervals[6] || fItem.value === intervals[6]) - ) { - itemValue = 7; - } - if ( - (fItem.value < intervals[6] || fItem.value === intervals[6]) && - (fItem.value >= intervals[5] || fItem.value === intervals[5]) - ) { - itemValue = 6; - } - if ( - (fItem.value < intervals[5] || fItem.value === intervals[5]) && - (fItem.value >= intervals[4] || fItem.value === intervals[4]) - ) { - itemValue = 5; - } - if ( - (fItem.value < intervals[4] || fItem.value === intervals[4]) && - (fItem.value >= intervals[3] || fItem.value === intervals[3]) - ) { - itemValue = 4; - } - if ( - (fItem.value < intervals[3] || fItem.value === intervals[3]) && - (fItem.value >= intervals[2] || fItem.value === intervals[2]) - ) { - itemValue = 3; - } - if ( - (fItem.value < intervals[2] || fItem.value === intervals[2]) && - (fItem.value >= intervals[1] || fItem.value === intervals[1]) - ) { - itemValue = 2; - } - if ( - (fItem.value < intervals[1] || fItem.value === intervals[1]) && - (fItem.value >= intervals[0] || fItem.value === intervals[0]) - ) { - itemValue = 1; - } - } - return { - ...feature, - properties: { - ...feature.properties, - value: itemValue, - iso_a3: feature.id, - data: fItem - ? { - components: fItem.components, - value: fItem.value, - } - : {}, - }, - }; - }); - return { - count: features.length, - data: features, - maxValue, - }; - }), - ) - .catch(handleDataApiError); - } - - @get('/allocations/geomap/multicountries') - @response(200, ALLOCATIONS_RESPONSE) - geomapMulticountries(): object { - const filterString = getFilterString( - this.req.query, - AllocationsGeomapFieldsMapping.aggregationMulticountry, - 'multiCountryName ne null', - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.allocations}/?${params}${filterString}`; - - return axios - .all([axios.get(url), axios.get(urls.multicountriescountriesdata)]) - .then( - axios.spread((...responses) => { - const rawData = _.get( - responses[0].data, - AllocationsGeomapFieldsMapping.dataPath, - [], - ); - const mcGeoData = _.get( - responses[1].data, - AllocationsGeomapFieldsMapping.dataPath, - [], - ); - const data: any = []; - const groupedByMulticountry = _.groupBy( - rawData, - AllocationsGeomapFieldsMapping.multicountry, - ); - Object.keys(groupedByMulticountry).forEach((mc: string) => { - const fMCGeoItem = _.find( - mcGeoData, - (mcGeoItem: any) => - _.get( - mcGeoItem, - AllocationsGeomapFieldsMapping.multicountry, - '', - ) === mc, - ); - let latitude = 0; - let longitude = 0; - if (fMCGeoItem) { - const coordinates: Position[] = []; - const composition = _.get( - fMCGeoItem, - AllocationsGeomapFieldsMapping.multiCountryComposition, - [], - ); - composition.forEach((item: any) => { - const iso3 = _.get( - item, - AllocationsGeomapFieldsMapping.multiCountryCompositionItem, - '', - ); - const fCountry = _.find(staticCountries, {iso3: iso3}); - if (fCountry) { - coordinates.push([fCountry.longitude, fCountry.latitude]); - } - }); - if (coordinates.length > 0) { - const lonlat = center(points(coordinates)); - longitude = lonlat.geometry.coordinates[0]; - latitude = lonlat.geometry.coordinates[1]; - } - } - data.push({ - id: mc, - code: mc.replace(/\//g, '|'), - geoName: mc, - components: groupedByMulticountry[mc].map((item: any) => ({ - name: _.get( - item, - AllocationsGeomapFieldsMapping.multicountryComponent, - '', - ), - value: _.get(item, AllocationsGeomapFieldsMapping.amount, 0), - })), - latitude: latitude, - longitude: longitude, - value: _.sumBy( - groupedByMulticountry[mc], - AllocationsGeomapFieldsMapping.amount, - ), - }); - }); - return { - pins: data, - }; - }), - ) - .catch(handleDataApiError); - } - - @get('/v2/allocations/table') - @response(200, ALLOCATIONS_RESPONSE) - allocationsTableV2(): object { - const filterString = getFilterString(this.req.query); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const aggregateByField = - this.req.query.aggregateBy && - this.req.query.aggregateBy.toString().length > 0 - ? this.req.query.aggregateBy - : AllocationsFieldsMapping.allocationsTableAggregateByFields[0]; - const nonAggregateByField = ( - this.req.query.nonAggregateBy - ? this.req.query.nonAggregateBy - : aggregateByField === - AllocationsFieldsMapping.allocationsTableAggregateByFields[0] - ? AllocationsFieldsMapping.allocationsTableAggregateByFields[1] - : AllocationsFieldsMapping.allocationsTableAggregateByFields[0] - ).toString(); - const url = `${urls.allocations}/?${params}${filterString}&${AllocationsFieldsMapping.allocationsTableExpand}`; - const sortBy = this.req.query.sortBy; - const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'asc'; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, AllocationsFieldsMapping.dataPath, []); - - let data: SimpleTableRow[] = []; - - let groupedBy1 = _.groupBy(rawData, aggregateByField); - - _.filter( - Object.keys(groupedBy1), - compKey => compKey !== 'undefined' && compKey !== 'null', - ).forEach(compKey => { - const groupedBy2 = _.groupBy( - groupedBy1[compKey], - nonAggregateByField, - ); - const subData: SimpleTableRow[] = []; - _.filter( - Object.keys(groupedBy2), - countryKey => countryKey !== 'undefined' && countryKey !== 'null', - ).forEach(countryKey => { - let item = { - name: countryKey, - }; - _.orderBy( - groupedBy2[countryKey], - AllocationsFieldsMapping.periodStart, - 'desc', - ).forEach(countryItem => { - item = { - ...item, - [`${_.get( - countryItem, - AllocationsFieldsMapping.periodStart, - '', - )}-${_.get( - countryItem, - AllocationsFieldsMapping.periodEnd, - '', - )}`]: _.get( - countryItem, - AllocationsFieldsMapping.amountTable, - 0, - ), - }; - }); - subData.push(item); - }); - let item: SimpleTableRow = { - name: compKey, - }; - const groupedByPeriods = _.groupBy( - groupedBy1[compKey], - AllocationsFieldsMapping.periodStart, - ); - _.sortBy(Object.keys(groupedByPeriods)) - .reverse() - .forEach(period => { - item = { - ...item, - [`${_.get( - groupedByPeriods[period][0], - AllocationsFieldsMapping.periodStart, - '', - )}-${_.get( - groupedByPeriods[period][0], - AllocationsFieldsMapping.periodEnd, - '', - )}`]: _.sumBy( - groupedByPeriods[period], - AllocationsFieldsMapping.amountTable, - ), - }; - }); - item = { - ...item, - children: subData, - }; - data.push(item); - }); - - groupedBy1 = _.groupBy( - rawData, - aggregateByField === - AllocationsFieldsMapping.allocationsTableAggregateByFields[0] - ? AllocationsFieldsMapping.multicountry - : aggregateByField, - ); - _.filter( - Object.keys(groupedBy1), - compKey => compKey !== 'undefined' && compKey !== 'null', - ).forEach(compKey => { - const groupedBy2 = _.groupBy( - groupedBy1[compKey], - nonAggregateByField === - AllocationsFieldsMapping.allocationsTableAggregateByFields[1] - ? nonAggregateByField - : AllocationsFieldsMapping.multicountry, - ); - const subData: SimpleTableRow[] = []; - _.filter( - Object.keys(groupedBy2), - countryKey => countryKey !== 'undefined' && countryKey !== 'null', - ).forEach(countryKey => { - let item = { - name: countryKey, - }; - _.orderBy( - groupedBy2[countryKey], - AllocationsFieldsMapping.periodStart, - 'desc', - ).forEach(countryItem => { - item = { - ...item, - [`${_.get( - countryItem, - AllocationsFieldsMapping.periodStart, - '', - )}-${_.get( - countryItem, - AllocationsFieldsMapping.periodEnd, - '', - )}`]: _.get( - countryItem, - AllocationsFieldsMapping.amountTable, - 0, - ), - }; - }); - subData.push(item); - }); - let item: SimpleTableRow = { - name: compKey, - }; - const groupedByPeriods = _.groupBy( - groupedBy1[compKey], - AllocationsFieldsMapping.periodStart, - ); - _.sortBy(Object.keys(groupedByPeriods)) - .reverse() - .forEach(period => { - item = { - ...item, - [`${_.get( - groupedByPeriods[period][0], - AllocationsFieldsMapping.periodStart, - '', - )}-${_.get( - groupedByPeriods[period][0], - AllocationsFieldsMapping.periodEnd, - '', - )}`]: _.sumBy( - groupedByPeriods[period], - AllocationsFieldsMapping.amountTable, - ), - }; - }); - const fItemIndex = _.findIndex(data, {name: item.name}); - if (fItemIndex > -1) { - data[fItemIndex] = { - ...data[fItemIndex], - children: [...(data[fItemIndex].children || []), ...subData], - }; - } else { - item = { - ...item, - children: subData, - }; - data.push(item); - } - }); - - data.forEach((item, index) => { - data[index].children = _.orderBy( - data[index].children, - instance => instance[sortByValue] || '', - sortByDirection, - ); - }); - - data = _.orderBy( - data, - instance => instance[sortByValue] || '', - sortByDirection, - ); - - return {count: data.length, data}; - }) - .catch(handleDataApiError); - } } diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index b37bc75..f766245 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -1,103 +1,22 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; -import center from '@turf/center'; -import {points, Position} from '@turf/helpers'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; -import querystring from 'querystring'; -import filtering from '../config/filtering/index.json'; import BudgetsBreakdownFieldsMapping from '../config/mapping/budgets/breakdown.json'; import BudgetsCyclesMapping from '../config/mapping/budgets/cycles.json'; -import BudgetsFlowFieldsMapping from '../config/mapping/budgets/flow.json'; -import BudgetsFlowDrilldownFieldsMapping from '../config/mapping/budgets/flowDrilldown.json'; -import BudgetsGeomapFieldsMapping from '../config/mapping/budgets/geomap.json'; import BudgetsMetricsFieldsMapping from '../config/mapping/budgets/metrics.json'; import BudgetsRadialFieldsMapping from '../config/mapping/budgets/radial.json'; import BudgetsSankeyFieldsMapping from '../config/mapping/budgets/sankey.json'; import BudgetsTableFieldsMapping from '../config/mapping/budgets/table.json'; -import BudgetsTimeCycleFieldsMapping from '../config/mapping/budgets/timeCycle.json'; import BudgetsTreemapFieldsMapping from '../config/mapping/budgets/treemap.json'; import urls from '../config/urls/index.json'; import {BudgetSankeyChartData} from '../interfaces/budgetSankey'; -import {BudgetsFlowData} from '../interfaces/budgetsFlow'; -import {BudgetsTimeCycleData} from '../interfaces/budgetsTimeCycle'; -import {BudgetsTreemapDataItem} from '../interfaces/budgetsTreemap'; -import staticCountries from '../static-assets/countries.json'; import {handleDataApiError} from '../utils/dataApiError'; -import {getDrilldownFilterString} from '../utils/filtering/budgets/getDrilldownFilterString'; -import {getFilterString} from '../utils/filtering/budgets/getFilterString'; import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; -import {formatFinancialValue} from '../utils/formatFinancialValue'; - -const BUDGETS_FLOW_RESPONSE: ResponseObject = { - description: 'Budgets Flow Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'BudgetsFlowResponse', - properties: { - totalBudget: {type: 'number'}, - data: { - type: 'object', - properties: { - nodes: { - type: 'array', - item: { - type: 'object', - properties: { - id: {type: 'string'}, - filterStr: {type: 'string'}, - }, - }, - }, - links: { - type: 'array', - item: { - type: 'object', - properties: { - source: {type: 'string'}, - target: {type: 'string'}, - amount: {type: 'number'}, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; -const BUDGETS_TIME_CYCLE_RESPONSE: ResponseObject = { - description: 'Budgets Time Cycle Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'BudgetsTimeCycleResponse', - properties: { - data: { - type: 'object', - }, - }, - }, - }, - }, -}; export class BudgetsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/budgets/radial') @response(200) async radial() { @@ -1244,751 +1163,4 @@ export class BudgetsController { ) .catch(handleDataApiError); } - - // v2 - - @get('/budgets/flow') - @response(200, BUDGETS_FLOW_RESPONSE) - flow(): object { - const filterString = getFilterString( - this.req.query, - BudgetsFlowFieldsMapping.budgetsFlowAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.budgets}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, BudgetsFlowFieldsMapping.dataPath, []); - let formattedRawData = []; - if (_.get(rawData, `[0].${BudgetsFlowFieldsMapping.level1}`, null)) { - formattedRawData = rawData.map((item: any) => ({ - amount: _.get(item, BudgetsFlowFieldsMapping.amount, 0), - count: _.get(item, BudgetsFlowFieldsMapping.count, 0), - level1: _.get(item, BudgetsFlowFieldsMapping.level1, ''), - level2: _.get(item, BudgetsFlowFieldsMapping.level2, ''), - costCategory: _.get(item, BudgetsFlowFieldsMapping.costCategory, '') - .replace(/\(/g, '- ') - .replace(/\)/g, ''), - rawCostCategory: _.get( - item, - BudgetsFlowFieldsMapping.costCategory, - '', - ), - component: _.get(item, BudgetsFlowFieldsMapping.component, ''), - })); - } - const totalBudget = _.sumBy(formattedRawData, 'amount'); - - const data: BudgetsFlowData = {nodes: [], links: []}; - - // 4th column - const costCategoryGroupBy = _.groupBy(formattedRawData, 'costCategory'); - Object.keys(costCategoryGroupBy).forEach(costCategory => { - const groupedByTotalBudget = _.sumBy( - costCategoryGroupBy[costCategory], - 'amount', - ); - const groupedByComponents = _.groupBy( - costCategoryGroupBy[costCategory], - 'component', - ); - data.nodes.push({ - id: costCategory, - filterStr: `budgetCategory/budgetCategoryName eq '${costCategoryGroupBy[costCategory][0].rawCostCategory}'`, - components: _.sortBy(Object.keys(groupedByComponents)).map( - componentKey => { - const compValue = _.sumBy( - groupedByComponents[componentKey], - 'amount', - ); - const compCount = _.sumBy( - groupedByComponents[componentKey], - 'count', - ); - return { - id: componentKey, - color: _.get( - BudgetsFlowFieldsMapping.componentColors, - componentKey, - '', - ), - value: compValue, - count: compCount, - height: (compValue * 100) / groupedByTotalBudget, - }; - }, - ), - }); - }); - - // 3rd column - const level2GroupBy = _.groupBy(formattedRawData, 'level2'); - Object.keys(level2GroupBy).forEach(level2 => { - const groupedByTotalBudget = _.sumBy(level2GroupBy[level2], 'amount'); - const groupedByComponents = _.groupBy( - level2GroupBy[level2], - 'component', - ); - data.nodes.push({ - id: level2, - filterStr: `budgetCategory/budgetCategoryParent/budgetCategoryName eq '${level2}'`, - components: _.sortBy(Object.keys(groupedByComponents)).map( - componentKey => { - const compValue = _.sumBy( - groupedByComponents[componentKey], - 'amount', - ); - const compCount = _.sumBy( - groupedByComponents[componentKey], - 'count', - ); - return { - id: componentKey, - color: _.get( - BudgetsFlowFieldsMapping.componentColors, - componentKey, - '', - ), - value: compValue, - count: compCount, - height: (compValue * 100) / groupedByTotalBudget, - }; - }, - ), - }); - level2GroupBy[level2].forEach(item => { - const foundIndex = _.findIndex( - data.links, - l => l.source === level2 && l.target === item.costCategory, - ); - if (foundIndex === -1) { - if (level2 !== item.costCategory) { - data.links.push({ - source: level2, - target: item.costCategory, - value: item.amount, - }); - } - } else { - data.links[foundIndex].value += item.amount; - } - }); - }); - - // 2nd column - const level1GroupBy = _.groupBy(formattedRawData, 'level1'); - Object.keys(level1GroupBy).forEach(level1 => { - const groupedByTotalBudget = _.sumBy(level1GroupBy[level1], 'amount'); - const groupedByComponents = _.groupBy( - level1GroupBy[level1], - 'component', - ); - data.nodes.push({ - id: level1, - filterStr: `budgetCategory/budgetCategoryParent/budgetCategoryParent/budgetCategoryName eq '${level1}'`, - components: _.sortBy(Object.keys(groupedByComponents)).map( - componentKey => { - const compValue = _.sumBy( - groupedByComponents[componentKey], - 'amount', - ); - const compCount = _.sumBy( - groupedByComponents[componentKey], - 'count', - ); - return { - id: componentKey, - color: _.get( - BudgetsFlowFieldsMapping.componentColors, - componentKey, - '', - ), - value: compValue, - count: compCount, - height: (compValue * 100) / groupedByTotalBudget, - }; - }, - ), - }); - level1GroupBy[level1].forEach(item => { - const foundIndex = _.findIndex( - data.links, - l => l.source === level1 && l.target === item.level2, - ); - if (foundIndex === -1) { - data.links.push({ - source: level1, - target: item.level2, - value: item.amount, - }); - } else { - data.links[foundIndex].value += item.amount; - } - }); - data.links.push({ - source: 'Budgets', - target: level1, - value: _.sumBy(level1GroupBy[level1], 'amount'), - }); - }); - - const groupedByComponents = _.groupBy(formattedRawData, 'component'); - - // 1st column - data.nodes.push({ - id: 'Budgets', - filterStr: 'activityArea/activityAreaParent/activityAreaName ne null', - components: _.sortBy(Object.keys(groupedByComponents)).map( - componentKey => { - const compValue = _.sumBy( - groupedByComponents[componentKey], - 'amount', - ); - const compCount = _.sumBy( - groupedByComponents[componentKey], - 'count', - ); - return { - id: componentKey, - color: _.get( - BudgetsFlowFieldsMapping.componentColors, - componentKey, - '', - ), - value: compValue, - count: compCount, - height: (compValue * 100) / totalBudget, - }; - }, - ), - }); - - data.nodes = _.uniqBy(data.nodes, 'id'); - data.nodes = _.sortBy(data.nodes, 'id'); - data.links = _.sortBy(data.links, ['source', 'target']); - - return { - ...data, - totalBudget, - }; - }) - .catch(handleDataApiError); - } - - @get('/budgets/time-cycle') - @response(200, BUDGETS_TIME_CYCLE_RESPONSE) - timeCycle(): object { - const filterString = getFilterString( - this.req.query, - BudgetsTimeCycleFieldsMapping.budgetsTimeCycleAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.budgets}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - let rawData = _.get( - resp.data, - BudgetsTimeCycleFieldsMapping.dataPath, - [], - ); - if (rawData.length > 0) { - if ( - _.get(rawData[0], BudgetsTimeCycleFieldsMapping.year, '').length > 4 - ) { - rawData = _.filter( - rawData, - (item: any) => item[BudgetsTimeCycleFieldsMapping.year], - ).map((item: any) => ({ - ...item, - [BudgetsTimeCycleFieldsMapping.year]: item[ - BudgetsTimeCycleFieldsMapping.year - ].slice(0, 4), - })); - } - } - const returnData: BudgetsTimeCycleData = {data: []}; - const groupedYears = _.groupBy( - rawData, - BudgetsTimeCycleFieldsMapping.year, - ); - Object.keys(groupedYears).forEach(yKey => { - const instance = groupedYears[yKey]; - let components = {}; - const groupedYComponents = _.groupBy( - instance, - BudgetsTimeCycleFieldsMapping.component, - ); - Object.keys(groupedYComponents).forEach(ycKey => { - components = { - ...components, - [ycKey]: _.sumBy( - groupedYComponents[ycKey], - BudgetsTimeCycleFieldsMapping.amount, - ), - [`${ycKey}Color`]: _.get( - BudgetsTimeCycleFieldsMapping.componentColors, - ycKey, - '#000', - ), - }; - }); - returnData.data.push({ - year: yKey, - ...components, - amount: _.sumBy(instance, BudgetsTimeCycleFieldsMapping.amount), - }); - }); - return returnData; - }) - .catch(handleDataApiError); - } - - @get('/budgets/drilldown') - @response(200, BUDGETS_FLOW_RESPONSE) - flowDrilldown(): object { - if (!this.req.query.levelParam) { - return { - count: 0, - data: [], - message: '"levelParam" parameters are required.', - }; - } - const filterString = getDrilldownFilterString( - this.req.query, - BudgetsFlowDrilldownFieldsMapping.aggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.budgets}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, BudgetsFlowDrilldownFieldsMapping.dataPath, []), - BudgetsFlowDrilldownFieldsMapping.component, - ); - const data: BudgetsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const children: BudgetsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - children.push({ - name: _.get(item, BudgetsFlowDrilldownFieldsMapping.child, ''), - value: item[BudgetsFlowDrilldownFieldsMapping.amount], - formattedValue: formatFinancialValue( - item[BudgetsFlowDrilldownFieldsMapping.amount], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: _.get( - item, - BudgetsFlowDrilldownFieldsMapping.child, - '', - ), - value: item[BudgetsFlowDrilldownFieldsMapping.amount], - }, - ], - value: item[BudgetsFlowDrilldownFieldsMapping.amount], - }, - }); - }); - const value = _.sumBy(children, 'value'); - data.push({ - name: component, - color: '#DFE3E5', - value, - formattedValue: formatFinancialValue(value), - _children: _.orderBy(children, 'value', 'desc'), - tooltip: { - header: component, - value, - componentsStats: [ - { - name: component, - value: _.sumBy(children, 'value'), - }, - ], - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/budgets/drilldown/2') - @response(200, BUDGETS_FLOW_RESPONSE) - flowDrilldownLevel2(): object { - if (!this.req.query.levelParam || !this.req.query.activityAreaName) { - return { - count: 0, - data: [], - message: '"levelParam" and "activityAreaName" parameters are required.', - }; - } - const filterString = getDrilldownFilterString( - this.req.query, - BudgetsFlowDrilldownFieldsMapping.aggregation2, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.budgets}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get( - resp.data, - BudgetsFlowDrilldownFieldsMapping.dataPath, - [], - ); - const totalValue = _.sumBy( - rawData, - BudgetsFlowDrilldownFieldsMapping.amount, - ); - const areaName = this.req.query.activityAreaName as string; - const data: BudgetsTreemapDataItem[] = [ - { - name: areaName, - color: '#DFE3E5', - value: totalValue, - formattedValue: formatFinancialValue(totalValue), - _children: _.orderBy( - rawData.map((item: any) => ({ - name: _.get(item, BudgetsFlowDrilldownFieldsMapping.grant, ''), - value: item[BudgetsFlowDrilldownFieldsMapping.amount], - formattedValue: formatFinancialValue( - item[BudgetsFlowDrilldownFieldsMapping.amount], - ), - color: '#595C70', - tooltip: { - header: areaName, - componentsStats: [ - { - name: _.get( - item, - BudgetsFlowDrilldownFieldsMapping.grant, - '', - ), - value: item[BudgetsFlowDrilldownFieldsMapping.amount], - }, - ], - value: item[BudgetsFlowDrilldownFieldsMapping.amount], - }, - })), - 'value', - 'desc', - ), - tooltip: { - header: areaName, - value: totalValue, - componentsStats: [ - { - name: areaName, - value: totalValue, - }, - ], - }, - }, - ]; - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/budgets/geomap') - @response(200, BUDGETS_FLOW_RESPONSE) - geomap(): object { - const filterString = getFilterString( - this.req.query, - BudgetsGeomapFieldsMapping.aggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.budgets}/?${params}${filterString}`; - - return axios - .all([axios.get(url), axios.get(urls.geojson)]) - .then( - axios.spread((...responses) => { - const geoJSONData = responses[1].data.features; - const data: any = []; - const groupedDataByLocation = _.groupBy( - responses[0].data.value, - BudgetsGeomapFieldsMapping.locationCode, - ); - Object.keys(groupedDataByLocation).forEach((iso3: string) => { - const dataItems = groupedDataByLocation[iso3]; - const locationComponents: any = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: _.get(item, BudgetsGeomapFieldsMapping.component, ''), - value: item[BudgetsGeomapFieldsMapping.amount], - }); - }); - data.push({ - code: iso3, - components: locationComponents, - value: _.sumBy(locationComponents, 'value'), - }); - }); - const maxValue: number = _.max(data.map((d: any) => d.value)) ?? 0; - let interval = 0; - if (maxValue) { - interval = maxValue / 13; - } - const intervals: number[] = []; - for (let i = 0; i < 13; i++) { - intervals.push(interval * i); - } - const features = geoJSONData.map((feature: any) => { - const fItem = _.find(data, {code: feature.id}); - let itemValue = 0; - if (fItem) { - if ( - (fItem.value < maxValue || fItem.value === maxValue) && - (fItem.value >= intervals[11] || fItem.value === intervals[11]) - ) { - itemValue = 12; - } - if ( - (fItem.value < intervals[11] || - fItem.value === intervals[11]) && - (fItem.value >= intervals[10] || fItem.value === intervals[10]) - ) { - itemValue = 11; - } - if ( - (fItem.value < intervals[10] || - fItem.value === intervals[10]) && - (fItem.value >= intervals[9] || fItem.value === intervals[9]) - ) { - itemValue = 10; - } - if ( - (fItem.value < intervals[9] || fItem.value === intervals[9]) && - (fItem.value >= intervals[8] || fItem.value === intervals[8]) - ) { - itemValue = 9; - } - if ( - (fItem.value < intervals[8] || fItem.value === intervals[8]) && - (fItem.value >= intervals[7] || fItem.value === intervals[7]) - ) { - itemValue = 8; - } - if ( - (fItem.value < intervals[7] || fItem.value === intervals[7]) && - (fItem.value >= intervals[6] || fItem.value === intervals[6]) - ) { - itemValue = 7; - } - if ( - (fItem.value < intervals[6] || fItem.value === intervals[6]) && - (fItem.value >= intervals[5] || fItem.value === intervals[5]) - ) { - itemValue = 6; - } - if ( - (fItem.value < intervals[5] || fItem.value === intervals[5]) && - (fItem.value >= intervals[4] || fItem.value === intervals[4]) - ) { - itemValue = 5; - } - if ( - (fItem.value < intervals[4] || fItem.value === intervals[4]) && - (fItem.value >= intervals[3] || fItem.value === intervals[3]) - ) { - itemValue = 4; - } - if ( - (fItem.value < intervals[3] || fItem.value === intervals[3]) && - (fItem.value >= intervals[2] || fItem.value === intervals[2]) - ) { - itemValue = 3; - } - if ( - (fItem.value < intervals[2] || fItem.value === intervals[2]) && - (fItem.value >= intervals[1] || fItem.value === intervals[1]) - ) { - itemValue = 2; - } - if ( - (fItem.value < intervals[1] || fItem.value === intervals[1]) && - (fItem.value >= intervals[0] || fItem.value === intervals[0]) - ) { - itemValue = 1; - } - } - return { - ...feature, - properties: { - ...feature.properties, - value: itemValue, - iso_a3: feature.id, - data: fItem - ? { - components: fItem.components, - value: fItem.value, - } - : {}, - }, - }; - }); - return { - count: features.length, - data: features, - maxValue, - }; - }), - ) - .catch(handleDataApiError); - } - - @get('/budgets/geomap/multicountries') - @response(200, BUDGETS_FLOW_RESPONSE) - geomapMulticountries(): object { - const filterString = getFilterString( - this.req.query, - BudgetsGeomapFieldsMapping.aggregationMulticountry, - 'grantAgreementImplementationPeriod/grantAgreement/multiCountry/multiCountryName ne null', - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.budgets}/?${params}${filterString}`; - - return axios - .all([axios.get(url), axios.get(urls.multicountriescountriesdata)]) - .then( - axios.spread((...responses) => { - const rawData = _.get( - responses[0].data, - BudgetsGeomapFieldsMapping.dataPath, - [], - ); - const mcGeoData = _.get( - responses[1].data, - BudgetsGeomapFieldsMapping.dataPath, - [], - ); - const data: any = []; - const groupedByMulticountry = _.groupBy( - rawData, - BudgetsGeomapFieldsMapping.multicountry, - ); - Object.keys(groupedByMulticountry).forEach((mc: string) => { - const fMCGeoItem = _.find( - mcGeoData, - (mcGeoItem: any) => - _.get( - mcGeoItem, - BudgetsGeomapFieldsMapping.geodatamulticountry, - '', - ) === mc, - ); - let latitude = 0; - let longitude = 0; - if (fMCGeoItem) { - const coordinates: Position[] = []; - const composition = _.get( - fMCGeoItem, - BudgetsGeomapFieldsMapping.multiCountryComposition, - [], - ); - composition.forEach((item: any) => { - const iso3 = _.get( - item, - BudgetsGeomapFieldsMapping.multiCountryCompositionItem, - '', - ); - const fCountry = _.find(staticCountries, {iso3: iso3}); - if (fCountry) { - coordinates.push([fCountry.longitude, fCountry.latitude]); - } - }); - if (coordinates.length > 0) { - const lonlat = center(points(coordinates)); - longitude = lonlat.geometry.coordinates[0]; - latitude = lonlat.geometry.coordinates[1]; - } - } - data.push({ - id: mc, - code: mc.replace(/\//g, '|'), - geoName: mc, - components: groupedByMulticountry[mc].map((item: any) => ({ - name: _.get( - item, - BudgetsGeomapFieldsMapping.multicountryComponent, - '', - ), - value: _.get(item, BudgetsGeomapFieldsMapping.amount, 0), - })), - latitude: latitude, - longitude: longitude, - value: _.sumBy( - groupedByMulticountry[mc], - BudgetsGeomapFieldsMapping.amount, - ), - }); - }); - return { - pins: data, - }; - }), - ) - .catch(handleDataApiError); - } } diff --git a/src/controllers/data-themes/raw-data.controller.ts b/src/controllers/data-themes/raw-data.controller.ts deleted file mode 100644 index 8d965e3..0000000 --- a/src/controllers/data-themes/raw-data.controller.ts +++ /dev/null @@ -1,175 +0,0 @@ -import {inject} from '@loopback/core'; -import {get, Request, response, RestBindings} from '@loopback/rest'; -import axios, {AxiosResponse} from 'axios'; -import _ from 'lodash'; -import {mapTransform} from 'map-transform'; -import allocations from '../../config/mapping/data-themes/raw-data/allocations.json'; -import budgets from '../../config/mapping/data-themes/raw-data/budgets.json'; -import eligibility from '../../config/mapping/data-themes/raw-data/eligibility.json'; -import generic from '../../config/mapping/data-themes/raw-data/generic.json'; -import grants from '../../config/mapping/data-themes/raw-data/grants.json'; -import investmentCommitted from '../../config/mapping/data-themes/raw-data/investment-committed.json'; -import investmentDisbursed from '../../config/mapping/data-themes/raw-data/investment-disbursed.json'; -import investmentSigned from '../../config/mapping/data-themes/raw-data/investment-signed.json'; -import pledgesContributions from '../../config/mapping/data-themes/raw-data/pledges-contributions.json'; -import urls from '../../config/urls/index.json'; -import {formatRawData} from '../../utils/data-themes/formatRawData'; -import {getDatasetFilterOptions} from '../../utils/data-themes/getDatasetFilterOptions'; -import {handleDataApiError} from '../../utils/dataApiError'; - -export class DataThemesRawDataController { - constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - - @get('/data-themes/raw-data/investment-signed') - @response(200) - investmentSigned(): object { - return axios - .get(`${urls.vgrantPeriods}/?${investmentSigned.select}&${generic.rows}`) - .then((res: AxiosResponse) => { - const data = _.get(res.data, investmentSigned.dataPath, []).map( - formatRawData, - ); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } - - @get('/data-themes/raw-data/investment-committed') - @response(200) - investmentCommitted(): object { - return axios - .get( - `${urls.vcommitments}/?${investmentCommitted.select}&${generic.rows}`, - ) - .then((res: AxiosResponse) => { - const data = _.get(res.data, investmentCommitted.dataPath, []).map( - formatRawData, - ); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } - - @get('/data-themes/raw-data/investment-disbursed') - @response(200) - investmentDisbursed(): object { - return axios - .get( - `${urls.disbursements}/?${investmentDisbursed.select}&${generic.rows}`, - ) - .then((res: AxiosResponse) => { - const data = _.get(res.data, investmentDisbursed.dataPath, []).map( - formatRawData, - ); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } - - @get('/data-themes/raw-data/budgets') - @response(200) - budgets(): object { - const mapper = mapTransform(budgets.mapping); - return axios - .get(`${urls.budgets}/?${budgets.expand}&${generic.rows}`) - .then((res: AxiosResponse) => { - const data = (mapper(res.data) as never[]).map(formatRawData); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } - - @get('/data-themes/raw-data/pledges-contributions') - @response(200) - pledgesContributions(): object { - const mapper = mapTransform(pledgesContributions.mapping); - return axios - .get( - `${urls.pledgescontributions}/?${pledgesContributions.expand}&${generic.rows}`, - ) - .then((res: AxiosResponse) => { - const data = (mapper(res.data) as never[]).map(formatRawData); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } - - @get('/data-themes/raw-data/allocations') - @response(200) - allocations(): object { - const mapper = mapTransform(allocations.mapping); - return axios - .get(`${urls.allocations}/?${allocations.expand}&${generic.rows}`) - .then((res: AxiosResponse) => { - const data = (mapper(res.data) as never[]).map(formatRawData); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } - - @get('/data-themes/raw-data/grants') - @response(200) - grants(): object { - return axios - .get(`${urls.grantsNoCount}/?${grants.select}&${generic.rows}`) - .then((res: AxiosResponse) => { - const data = _.get(res.data, grants.dataPath, []).map(formatRawData); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } - - @get('/data-themes/raw-data/eligibility') - @response(200) - eligibility(): object { - return axios - .get(`${urls.eligibility}/?${eligibility.select}&${generic.rows}`) - .then((res: AxiosResponse) => { - const data = _.get(res.data, eligibility.dataPath, []).map( - formatRawData, - ); - const filterOptions = getDatasetFilterOptions(data); - return { - data, - filterOptions, - count: data.length, - }; - }) - .catch(handleDataApiError); - } -} diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index c7390e9..990a825 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -1,189 +1,19 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; -import center from '@turf/center'; -import {points, Position} from '@turf/helpers'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; -import querystring from 'querystring'; -import filteringGrants from '../config/filtering/grants.json'; -import filtering from '../config/filtering/index.json'; import BarChartFieldsMapping from '../config/mapping/disbursements/barChart.json'; import DisbursementsCyclesMapping from '../config/mapping/disbursements/cycles.json'; -import GeomapFieldsMapping from '../config/mapping/disbursements/geomap.json'; -import GrantCommittedTimeCycleFieldsMapping from '../config/mapping/disbursements/grantCommittedTimeCycle.json'; -import GrantDetailTimeCycleFieldsMapping from '../config/mapping/disbursements/grantDetailTimeCycle.json'; -import GrantDetailTreemapFieldsMapping from '../config/mapping/disbursements/grantDetailTreemap.json'; import LineChartFieldsMapping from '../config/mapping/disbursements/lineChart.json'; import TableFieldsMapping from '../config/mapping/disbursements/table.json'; -import TimeCycleFieldsMapping from '../config/mapping/disbursements/timeCycle.json'; -import TimeCycleDrilldownFieldsMapping from '../config/mapping/disbursements/timeCycleDrilldown.json'; -import TimeCycleDrilldownFieldsMapping2 from '../config/mapping/disbursements/timeCycleDrilldown2.json'; -import TreemapFieldsMapping from '../config/mapping/disbursements/treemap.json'; import FinancialInsightsStatsMapping from '../config/mapping/financialInsightsStats.json'; import urls from '../config/urls/index.json'; -import {BudgetsTreemapDataItem} from '../interfaces/budgetsTreemap'; -import {DisbursementsTreemapDataItem} from '../interfaces/disbursementsTreemap'; -import staticCountries from '../static-assets/countries.json'; import {handleDataApiError} from '../utils/dataApiError'; -import {getFilterString} from '../utils/filtering/disbursements/getFilterString'; -import { - grantDetailGetFilterString, - grantDetailTreemapGetFilterString, -} from '../utils/filtering/disbursements/grantDetailGetFilterString'; -import {getGeoMultiCountriesFilterString} from '../utils/filtering/disbursements/multicountries/getFilterString'; import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; -import {formatFinancialValue} from '../utils/formatFinancialValue'; - -const DISBURSEMENTS_TIME_CYCLE_RESPONSE: ResponseObject = { - description: 'Disbursements Time Cycle Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'DisbursementsTimeCycleResponse', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - year: {type: 'string'}, - disbursed: {type: 'number'}, - cumulative: {type: 'number'}, - disbursedChildren: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - color: {type: 'string'}, - value: {type: 'number'}, - }, - }, - }, - cumulativeChildren: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - color: {type: 'string'}, - value: {type: 'number'}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; -const DISBURSEMENTS_TREEMAP_RESPONSE: ResponseObject = { - description: 'Disbursements Treemap Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'DisbursementsTreemapResponse', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'number'}, - color: {type: 'string'}, - formattedValue: {type: 'string'}, - tooltip: { - type: 'object', - properties: { - header: {type: 'string'}, - componentsStats: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - count: {type: 'number'}, - investment: {type: 'number'}, - }, - }, - }, - totalInvestments: { - type: 'object', - properties: { - committed: {type: 'number'}, - disbursed: {type: 'number'}, - signed: {type: 'number'}, - }, - }, - percValue: {type: 'string'}, - }, - }, - _children: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'number'}, - color: {type: 'string'}, - formattedValue: {type: 'string'}, - tooltip: { - type: 'object', - properties: { - header: {type: 'string'}, - componentsStats: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - count: {type: 'number'}, - investment: {type: 'number'}, - }, - }, - }, - totalInvestments: { - type: 'object', - properties: { - committed: {type: 'number'}, - disbursed: {type: 'number'}, - signed: {type: 'number'}, - }, - }, - percValue: {type: 'string'}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; export class DisbursementsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/financial-insights/stats') @response(200) async financialInsightsStats() { @@ -694,3477 +524,4 @@ export class DisbursementsController { }) .catch(handleDataApiError); } - - // v2 - - // Time/Cycle - - @get('/disbursements/time-cycle') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycle(): object { - const filterString = getFilterString( - this.req.query, - TimeCycleFieldsMapping.disbursementsTimeCycleAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.disbursements}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - let apiData = _.get(resp.data, TimeCycleFieldsMapping.dataPath, []); - if (apiData.length > 0) { - if (_.get(apiData[0], TimeCycleFieldsMapping.year, '').length > 4) { - apiData = _.filter( - apiData, - (item: any) => item[TimeCycleFieldsMapping.year], - ).map((item: any) => ({ - ...item, - [TimeCycleFieldsMapping.year]: item[ - TimeCycleFieldsMapping.year - ].slice(0, 4), - })); - } - } - const groupedDataByYear = _.groupBy( - apiData, - TimeCycleFieldsMapping.year, - ); - const data: any = []; - Object.keys(groupedDataByYear).forEach((year: string) => { - const dataItems = groupedDataByYear[year]; - const yearComponents: any = []; - _.orderBy(dataItems, TimeCycleFieldsMapping.year, 'asc').forEach( - (item: any) => { - const value = parseInt( - _.get(item, TimeCycleFieldsMapping.disbursed, 0), - 10, - ); - if (!isNaN(value)) { - const name = _.get(item, TimeCycleFieldsMapping.component, ''); - const prevYearComponent = _.get( - data, - `[${data.length - 1}].cumulativeChildren`, - [], - ); - yearComponents.push({ - name, - disbursed: value, - cumulative: - _.get(_.find(prevYearComponent, {name}), 'value', 0) + - value, - }); - } - }, - ); - const disbursed = _.sumBy(yearComponents, 'disbursed'); - const cumulative = _.sumBy(yearComponents, 'cumulative'); - data.push({ - year, - disbursed, - cumulative, - disbursedChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.disbursed, - }), - ), - cumulativeChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.cumulative, - }), - ), - }); - if ( - data.length > 1 && - data[data.length - 1].cumulativeChildren.length < - data[data.length - 2].cumulativeChildren.length - ) { - const prev = data[data.length - 2]; - const current = data[data.length - 1]; - const temp = _.filter( - prev.cumulativeChildren, - (item: any) => - _.findIndex(current.cumulativeChildren, { - name: item.name, - }) === -1, - ); - data[data.length - 1].cumulativeChildren = [ - ...temp, - ...data[data.length - 1].cumulativeChildren, - ]; - data[data.length - 1].cumulative += _.sumBy(temp, 'value'); - data[data.length - 1].cumulativeChildren = _.orderBy( - data[data.length - 1].cumulativeChildren, - 'name', - 'asc', - ); - } - }); - return { - count: data.length, - data: data, - }; - }) - .catch(handleDataApiError); - } - - @get('/signed/time-cycle') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleSigned(): object { - const filterString = getFilterString( - this.req.query, - TimeCycleFieldsMapping.signedTimeCycleAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.vgrantPeriods}/?${params}${filterString - .replace('filter', '$filter=') - .replace(')/', ')')}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - let apiData = _.get(resp.data, TimeCycleFieldsMapping.dataPath, []); - if (apiData.length > 0) { - apiData = _.filter( - apiData, - (item: any) => item[TimeCycleFieldsMapping.signedYear], - ).map((item: any) => ({ - ...item, - [TimeCycleFieldsMapping.signedYear]: item[ - TimeCycleFieldsMapping.signedYear - ].slice(0, 4), - })); - } - const groupedDataByYear = _.groupBy( - apiData, - TimeCycleFieldsMapping.signedYear, - ); - const data: any = []; - Object.keys(groupedDataByYear).forEach((year: string) => { - const dataItems = groupedDataByYear[year]; - const groupedYearCompsData = _.groupBy( - dataItems, - TimeCycleFieldsMapping.component, - ); - const groupedYearComps = Object.keys(groupedYearCompsData).map( - (component: string) => ({ - [TimeCycleFieldsMapping.component]: component, - [TimeCycleFieldsMapping.signed]: _.sumBy( - groupedYearCompsData[component], - TimeCycleFieldsMapping.signed, - ), - }), - ); - const yearComponents: any = []; - groupedYearComps.forEach((item: any) => { - const value = parseInt( - _.get(item, TimeCycleFieldsMapping.signed, 0), - 10, - ); - if (value) { - const name = _.get(item, TimeCycleFieldsMapping.component, ''); - const prevYearComponent = _.get( - data, - `[${data.length - 1}].cumulativeChildren`, - [], - ); - yearComponents.push({ - name, - disbursed: value, - cumulative: - _.get(_.find(prevYearComponent, {name}), 'value', 0) + value, - }); - } - }); - const disbursed = _.sumBy(yearComponents, 'disbursed'); - const cumulative = _.sumBy(yearComponents, 'cumulative'); - data.push({ - year, - disbursed, - cumulative, - disbursedChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.disbursed, - }), - ), - cumulativeChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.cumulative, - }), - ), - }); - if ( - data.length > 1 && - data[data.length - 1].cumulativeChildren.length < - data[data.length - 2].cumulativeChildren.length - ) { - const prev = data[data.length - 2]; - const current = data[data.length - 1]; - const temp = _.filter( - prev.cumulativeChildren, - (item: any) => - _.findIndex(current.cumulativeChildren, { - name: item.name, - }) === -1, - ); - data[data.length - 1].cumulativeChildren = [ - ...temp, - ...data[data.length - 1].cumulativeChildren, - ]; - data[data.length - 1].cumulative += _.sumBy(temp, 'value'); - data[data.length - 1].cumulativeChildren = _.orderBy( - data[data.length - 1].cumulativeChildren, - 'name', - 'asc', - ); - } - }); - return { - count: data.length, - data: data, - }; - }) - .catch(handleDataApiError); - } - - @get('/commitment/time-cycle') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleCommitment(): object { - const filterString = getFilterString( - this.req.query, - TimeCycleFieldsMapping.commitmentTimeCycleAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.vcommitments}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - let apiData = _.get(resp.data, TimeCycleFieldsMapping.dataPath, []); - if (apiData.length > 0) { - if ( - _.get(apiData[0], TimeCycleFieldsMapping.committedYear, '').length > - 4 - ) { - apiData = _.filter( - apiData, - (item: any) => item[TimeCycleFieldsMapping.committedYear], - ).map((item: any) => ({ - ...item, - [TimeCycleFieldsMapping.committedYear]: item[ - TimeCycleFieldsMapping.committedYear - ].slice(0, 4), - })); - } - } - const groupedDataByYear = _.groupBy( - apiData, - TimeCycleFieldsMapping.committedYear, - ); - const data: any = []; - Object.keys(groupedDataByYear).forEach((year: string) => { - const dataItems = groupedDataByYear[year]; - const yearComponents: any = []; - _.orderBy( - dataItems, - TimeCycleFieldsMapping.committedYear, - 'asc', - ).forEach((item: any) => { - const value = parseInt( - _.get(item, TimeCycleFieldsMapping.committed, 0), - 10, - ); - if (value) { - const name = _.get(item, TimeCycleFieldsMapping.component, ''); - const prevYearComponent = _.get( - data, - `[${data.length - 1}].cumulativeChildren`, - [], - ); - yearComponents.push({ - name, - disbursed: value, - cumulative: - _.get(_.find(prevYearComponent, {name}), 'value', 0) + value, - }); - } - }); - const disbursed = _.sumBy(yearComponents, 'disbursed'); - const cumulative = _.sumBy(yearComponents, 'cumulative'); - - data.push({ - year, - disbursed, - cumulative, - disbursedChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.disbursed, - }), - ), - cumulativeChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.cumulative, - }), - ), - }); - - if ( - data.length > 1 && - data[data.length - 1].cumulativeChildren.length < - data[data.length - 2].cumulativeChildren.length - ) { - const prev = data[data.length - 2]; - const current = data[data.length - 1]; - const temp = _.filter( - prev.cumulativeChildren, - (item: any) => - _.findIndex(current.cumulativeChildren, { - name: item.name, - }) === -1, - ); - data[data.length - 1].cumulativeChildren = [ - ...temp, - ...data[data.length - 1].cumulativeChildren, - ]; - data[data.length - 1].cumulative += _.sumBy(temp, 'value'); - data[data.length - 1].cumulativeChildren = _.orderBy( - data[data.length - 1].cumulativeChildren, - 'name', - 'asc', - ); - } - }); - return { - count: data.length, - data: data, - }; - }) - .catch(handleDataApiError); - } - - @get('/disbursements/time-cycle/drilldown') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleDrilldown(): object { - const filterString = getFilterString( - this.req.query, - TimeCycleDrilldownFieldsMapping.disbursementsTimeCycleDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.disbursements}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get( - resp.data, - TimeCycleDrilldownFieldsMapping.dataPath, - [], - ); - const groupedDataByComponent = _.groupBy( - apiData, - TimeCycleDrilldownFieldsMapping.component, - ); - const data: BudgetsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: BudgetsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - componentLocations.push({ - name: item[TimeCycleDrilldownFieldsMapping.locationName], - value: item[TimeCycleDrilldownFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[TimeCycleDrilldownFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: item[TimeCycleDrilldownFieldsMapping.locationName], - value: item[TimeCycleDrilldownFieldsMapping.disbursed], - }, - ], - value: item[TimeCycleDrilldownFieldsMapping.disbursed], - }, - }); - }); - const disbursed = _.sumBy(componentLocations, 'value'); - data.push({ - name: component, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(componentLocations, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - value: _.sumBy(componentLocations, 'value'), - }, - ], - value: _.sumBy(componentLocations, 'value'), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/signed/time-cycle/drilldown') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleDrilldownSigned(): object { - const query = { - ...this.req.query, - signedBarPeriod: this.req.query.barPeriod, - }; - // @ts-ignore - delete query.barPeriod; - const filterString = getFilterString( - query, - TimeCycleDrilldownFieldsMapping.signedTimeCycleDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.vgrantPeriods}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get( - resp.data, - TimeCycleDrilldownFieldsMapping.dataPath, - [], - ); - const groupedDataByComponent = _.groupBy( - apiData, - TimeCycleDrilldownFieldsMapping.component, - ); - const data: BudgetsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: BudgetsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - componentLocations.push({ - name: item[TimeCycleDrilldownFieldsMapping.locationName], - value: item[TimeCycleDrilldownFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[TimeCycleDrilldownFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: item[TimeCycleDrilldownFieldsMapping.locationName], - value: item[TimeCycleDrilldownFieldsMapping.signed], - }, - ], - value: item[TimeCycleDrilldownFieldsMapping.signed], - }, - }); - }); - const signed = _.sumBy(componentLocations, 'value'); - data.push({ - name: component, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(componentLocations, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - value: _.sumBy(componentLocations, 'value'), - }, - ], - value: _.sumBy(componentLocations, 'value'), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/commitment/time-cycle/drilldown') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleDrilldownCommitment(): object { - const query = { - ...this.req.query, - committedBarPeriod: this.req.query.barPeriod, - }; - // @ts-ignore - delete query.barPeriod; - const filterString = getFilterString( - query, - TimeCycleDrilldownFieldsMapping.commitmentTimeCycleDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.vcommitments}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get( - resp.data, - TimeCycleDrilldownFieldsMapping.dataPath, - [], - ); - const groupedDataByComponent = _.groupBy( - apiData, - TimeCycleDrilldownFieldsMapping.component, - ); - const data: BudgetsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: BudgetsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - componentLocations.push({ - name: item[TimeCycleDrilldownFieldsMapping.locationName], - value: item[TimeCycleDrilldownFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[TimeCycleDrilldownFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: item[TimeCycleDrilldownFieldsMapping.locationName], - value: item[TimeCycleDrilldownFieldsMapping.committed], - }, - ], - value: item[TimeCycleDrilldownFieldsMapping.committed], - }, - }); - }); - const committed = _.sumBy(componentLocations, 'value'); - data.push({ - name: component, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(componentLocations, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - value: _.sumBy(componentLocations, 'value'), - }, - ], - value: _.sumBy(componentLocations, 'value'), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/disbursements/time-cycle/drilldown/2') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleDrilldown2(): object { - const filterString = getFilterString( - this.req.query, - TimeCycleDrilldownFieldsMapping2.disbursementsTimeCycleDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.disbursements}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get( - resp.data, - TimeCycleDrilldownFieldsMapping2.dataPath, - [], - ); - const groupedData = _.groupBy( - apiData, - TimeCycleDrilldownFieldsMapping2.locationName, - ); - const data: BudgetsTreemapDataItem[] = []; - Object.keys(groupedData).forEach((key: string) => { - const dataItems = groupedData[key]; - const items: BudgetsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - items.push({ - name: item[TimeCycleDrilldownFieldsMapping2.grantCode], - value: item[TimeCycleDrilldownFieldsMapping2.disbursed], - formattedValue: formatFinancialValue( - item[TimeCycleDrilldownFieldsMapping2.disbursed], - ), - color: '#595C70', - tooltip: { - header: - item[TimeCycleDrilldownFieldsMapping2.grantName] || - item[TimeCycleDrilldownFieldsMapping2.grantCode], - componentsStats: [ - { - name: item[TimeCycleDrilldownFieldsMapping2.grantCode], - value: item[TimeCycleDrilldownFieldsMapping2.disbursed], - }, - ], - value: item[TimeCycleDrilldownFieldsMapping.disbursed], - }, - }); - }); - const disbursed = _.sumBy(items, 'value'); - data.push({ - name: key, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(items, 'value', 'desc'), - tooltip: { - header: key, - componentsStats: [ - { - name: key, - value: _.sumBy(items, 'value'), - }, - ], - value: _.sumBy(items, 'value'), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/signed/time-cycle/drilldown/2') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleDrilldownSigned2(): object { - const query = { - ...this.req.query, - signedBarPeriod: this.req.query.barPeriod, - }; - // @ts-ignore - delete query.barPeriod; - const filterString = getFilterString( - query, - TimeCycleDrilldownFieldsMapping2.signedTimeCycleDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.vgrantPeriods}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get( - resp.data, - TimeCycleDrilldownFieldsMapping2.dataPath, - [], - ); - const groupedData = _.groupBy( - apiData, - TimeCycleDrilldownFieldsMapping2.locationName, - ); - const data: BudgetsTreemapDataItem[] = []; - Object.keys(groupedData).forEach((key: string) => { - const dataItems = groupedData[key]; - const items: BudgetsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - items.push({ - name: item[TimeCycleDrilldownFieldsMapping2.grantCode], - value: item[TimeCycleDrilldownFieldsMapping2.signed], - formattedValue: formatFinancialValue( - item[TimeCycleDrilldownFieldsMapping2.signed], - ), - color: '#595C70', - tooltip: { - header: - item[TimeCycleDrilldownFieldsMapping2.grantName] || - item[TimeCycleDrilldownFieldsMapping2.grantCode], - componentsStats: [ - { - name: item[TimeCycleDrilldownFieldsMapping2.grantCode], - value: item[TimeCycleDrilldownFieldsMapping2.signed], - }, - ], - value: item[TimeCycleDrilldownFieldsMapping.signed], - }, - }); - }); - const signed = _.sumBy(items, 'value'); - data.push({ - name: key, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(items, 'value', 'desc'), - tooltip: { - header: key, - componentsStats: [ - { - name: key, - value: _.sumBy(items, 'value'), - }, - ], - value: _.sumBy(items, 'value'), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/commitment/time-cycle/drilldown/2') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - timeCycleDrilldownCommitment2(): object { - const query = { - ...this.req.query, - committedBarPeriod: this.req.query.barPeriod, - }; - // @ts-ignore - delete query.barPeriod; - const filterString = getFilterString( - query, - TimeCycleDrilldownFieldsMapping2.commitmentTimeCycleDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.vcommitments}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get( - resp.data, - TimeCycleDrilldownFieldsMapping2.dataPath, - [], - ); - const groupedData = _.groupBy( - apiData, - TimeCycleDrilldownFieldsMapping2.locationName, - ); - const data: BudgetsTreemapDataItem[] = []; - Object.keys(groupedData).forEach((key: string) => { - const dataItems = groupedData[key]; - const items: BudgetsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - items.push({ - name: item[TimeCycleDrilldownFieldsMapping2.grantCode], - value: item[TimeCycleDrilldownFieldsMapping2.committed], - formattedValue: formatFinancialValue( - item[TimeCycleDrilldownFieldsMapping2.committed], - ), - color: '#595C70', - tooltip: { - header: - item[TimeCycleDrilldownFieldsMapping2.grantName] || - item[TimeCycleDrilldownFieldsMapping2.grantCode], - componentsStats: [ - { - name: item[TimeCycleDrilldownFieldsMapping2.grantCode], - value: item[TimeCycleDrilldownFieldsMapping2.committed], - }, - ], - value: item[TimeCycleDrilldownFieldsMapping.committed], - }, - }); - }); - const committed = _.sumBy(items, 'value'); - data.push({ - name: key, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(items, 'value', 'desc'), - tooltip: { - header: key, - componentsStats: [ - { - name: key, - value: _.sumBy(items, 'value'), - }, - ], - value: _.sumBy(items, 'value'), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - // Treemap - - @get('/disbursements/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - treemap(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.disbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - const sortBy = this.req.query.sortBy; - let sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'value'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'desc'; - - switch (sortByValue) { - case 'grants': - sortByValue = 'tooltip.componentsStats[0].count'; - break; - case 'signed': - sortByValue = 'tooltip.totalInvestments.signed'; - break; - case 'committed': - sortByValue = 'tooltip.totalInvestments.committed'; - break; - case 'disbursed': - sortByValue = 'tooltip.totalInvestments.disbursed'; - break; - default: - break; - } - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - let locationName = item[TreemapFieldsMapping.locationName]; - let locationCode = item[TreemapFieldsMapping.locationCode]; - if (item[TreemapFieldsMapping.multicountry] !== null) { - locationName = item[TreemapFieldsMapping.multicountry]; - locationCode = item[TreemapFieldsMapping.multicountry]; - } - componentLocations.push({ - name: locationName, - code: locationCode, - value: item[TreemapFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: locationName, - count: item.count, - investment: item[TreemapFieldsMapping.disbursed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const disbursed = _.sumBy(componentLocations, 'value'); - const committed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.committed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy( - componentLocations, - sortByValue, - sortByDirection.toLowerCase(), - ), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentLocations, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentLocations, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - componentLocations, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, sortByValue, sortByDirection.toLowerCase()), - }; - }) - .catch(handleDataApiError); - } - - @get('/signed/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - treemapSigned(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.disbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - const sortBy = this.req.query.sortBy; - let sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'value'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'desc'; - - switch (sortByValue) { - case 'grants': - sortByValue = 'tooltip.componentsStats[0].count'; - break; - case 'signed': - sortByValue = 'tooltip.totalInvestments.signed'; - break; - case 'committed': - sortByValue = 'tooltip.totalInvestments.committed'; - break; - case 'disbursed': - sortByValue = 'tooltip.totalInvestments.disbursed'; - break; - default: - break; - } - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - let locationName = item[TreemapFieldsMapping.locationName]; - let locationCode = item[TreemapFieldsMapping.locationCode]; - if (item[TreemapFieldsMapping.multicountry] !== null) { - locationName = item[TreemapFieldsMapping.multicountry]; - locationCode = item[TreemapFieldsMapping.multicountry]; - } - componentLocations.push({ - name: locationName, - code: locationCode, - value: item[TreemapFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: locationName, - count: item.count, - investment: item[TreemapFieldsMapping.signed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const signed = _.sumBy(componentLocations, 'value'); - const committed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy( - componentLocations, - sortByValue, - sortByDirection, - ), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentLocations, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentLocations, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, sortByValue, sortByDirection), - }; - }) - .catch(handleDataApiError); - } - - @get('/commitment/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - treemapCommitment(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.disbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - const sortBy = this.req.query.sortBy; - let sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'value'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'desc'; - - switch (sortByValue) { - case 'grants': - sortByValue = 'tooltip.componentsStats[0].count'; - break; - case 'signed': - sortByValue = 'tooltip.totalInvestments.signed'; - break; - case 'committed': - sortByValue = 'tooltip.totalInvestments.committed'; - break; - case 'disbursed': - sortByValue = 'tooltip.totalInvestments.disbursed'; - break; - default: - break; - } - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - let locationName = item[TreemapFieldsMapping.locationName]; - let locationCode = item[TreemapFieldsMapping.locationCode]; - if (item[TreemapFieldsMapping.multicountry] !== null) { - locationName = item[TreemapFieldsMapping.multicountry]; - locationCode = item[TreemapFieldsMapping.multicountry]; - } - componentLocations.push({ - name: locationName, - code: locationCode, - value: item[TreemapFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: locationName, - count: item.count, - investment: item[TreemapFieldsMapping.committed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const committed = _.sumBy(componentLocations, 'value'); - const signed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.signed', - ); - const disbursed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy( - componentLocations, - sortByValue, - sortByDirection, - ), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentLocations, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentLocations, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, sortByValue, sortByDirection), - }; - }) - .catch(handleDataApiError); - } - - @get('/disbursements/treemap/drilldown') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - treemapDrilldown(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.disbursementsTreemapDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const component = ( - _.get(this.req.query, 'components', '') as string - ).split(',')[0]; - const componentLabel = ` - ${component}`; - const groupedDataByCountry = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.locationName, - ); - const groupedDataByMulticountry = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.multicountry, - ); - const data: DisbursementsTreemapDataItem[] = []; - const countryKeys = Object.keys(groupedDataByCountry); - const multicountryKeys = _.filter( - Object.keys(groupedDataByMulticountry), - (key: string) => key !== 'null', - ); - if (multicountryKeys.length === 0) { - countryKeys.forEach((location: string) => { - const dataItems = groupedDataByCountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.disbursed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const disbursed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - locationComponents, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } else if (countryKeys.length === 1 && multicountryKeys.length > 0) { - multicountryKeys.forEach((location: string) => { - const dataItems = groupedDataByMulticountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.disbursed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const disbursed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - locationComponents, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } else { - countryKeys.forEach((location: string) => { - const dataItems = groupedDataByCountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.disbursed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const disbursed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - locationComponents, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - multicountryKeys.forEach((location: string) => { - const dataItems = groupedDataByMulticountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: 1, - investment: item[TreemapFieldsMapping.disbursed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const disbursed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - locationComponents, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/signed/treemap/drilldown') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - treemapDrilldownSigned(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.disbursementsTreemapDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const component = ( - _.get(this.req.query, 'components', '') as string - ).split(',')[0]; - const componentLabel = ` - ${component}`; - const groupedDataByCountry = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.locationName, - ); - const groupedDataByMulticountry = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.multicountry, - ); - const data: DisbursementsTreemapDataItem[] = []; - const countryKeys = Object.keys(groupedDataByCountry); - const multicountryKeys = _.filter( - Object.keys(groupedDataByMulticountry), - (key: string) => key !== 'null', - ); - if (multicountryKeys.length === 0) { - countryKeys.forEach((location: string) => { - const dataItems = groupedDataByCountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.signed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const signed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } else if (countryKeys.length === 1 && multicountryKeys.length > 0) { - multicountryKeys.forEach((location: string) => { - const dataItems = groupedDataByMulticountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.signed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const signed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } else { - countryKeys.forEach((location: string) => { - const dataItems = groupedDataByCountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.signed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const signed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - multicountryKeys.forEach((location: string) => { - const dataItems = groupedDataByMulticountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.signed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const signed = _.sumBy(locationComponents, 'value'); - const committed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/commitment/treemap/drilldown') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - treemapDrilldownCommitment(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.disbursementsTreemapDrilldownAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const component = ( - _.get(this.req.query, 'components', '') as string - ).split(',')[0]; - const componentLabel = ` - ${component}`; - const groupedDataByCountry = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.locationName, - ); - const groupedDataByMulticountry = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.multicountry, - ); - const data: DisbursementsTreemapDataItem[] = []; - const countryKeys = Object.keys(groupedDataByCountry); - const multicountryKeys = _.filter( - Object.keys(groupedDataByMulticountry), - (key: string) => key !== 'null', - ); - if (multicountryKeys.length === 0) { - countryKeys.forEach((location: string) => { - const dataItems = groupedDataByCountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.committed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const committed = _.sumBy(locationComponents, 'value'); - const signed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } else if (countryKeys.length === 1 && multicountryKeys.length > 0) { - multicountryKeys.forEach((location: string) => { - const dataItems = groupedDataByMulticountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.committed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const committed = _.sumBy(locationComponents, 'value'); - const signed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } else { - countryKeys.forEach((location: string) => { - const dataItems = groupedDataByCountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.committed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const committed = _.sumBy(locationComponents, 'value'); - const signed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - multicountryKeys.forEach((location: string) => { - const dataItems = groupedDataByMulticountry[location]; - const locationComponents: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[TreemapFieldsMapping.grantCode], - value: item[TreemapFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: item[TreemapFieldsMapping.grantName], - componentsStats: [ - { - name: item[TreemapFieldsMapping.grantCode], - count: item.count, - investment: item[TreemapFieldsMapping.committed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const committed = _.sumBy(locationComponents, 'value'); - const signed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - locationComponents, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: `${location}${component ? componentLabel : ''}`, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(locationComponents, 'value', 'desc'), - tooltip: { - header: location, - componentsStats: [ - { - name: component ?? location, - count: _.sumBy( - locationComponents, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(locationComponents, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - } - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - // Geomap - - @get('/disbursements/geomap') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - geomap(): object { - let aggregationField = GeomapFieldsMapping.disbursed; - if ( - this.req.query.aggregationField && - typeof this.req.query.aggregationField === 'string' && - _.get(GeomapFieldsMapping, this.req.query.aggregationField, null) - ) { - aggregationField = _.get( - GeomapFieldsMapping, - this.req.query.aggregationField, - GeomapFieldsMapping.disbursed, - ); - } - const filterString = getFilterString( - this.req.query, - GeomapFieldsMapping.disbursementsGeomapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - - return axios - .all([axios.get(url), axios.get(urls.geojson)]) - .then( - axios.spread((...responses) => { - const geoJSONData = responses[1].data.features; - // const geoJSONData = geojson.features; - const groupedDataByLocation = _.groupBy( - responses[0].data.value, - GeomapFieldsMapping.locationCode, - ); - const data: any = []; - Object.keys(groupedDataByLocation).forEach((iso3: string) => { - const dataItems = groupedDataByLocation[iso3]; - const locationComponents: any = []; - dataItems.forEach((item: any) => { - locationComponents.push({ - name: item[GeomapFieldsMapping.component], - activitiesCount: item.count, - value: item[aggregationField], - }); - }); - const disbursed = _.sumBy(dataItems, GeomapFieldsMapping.disbursed); - const committed = _.sumBy(dataItems, GeomapFieldsMapping.committed); - const signed = _.sumBy(dataItems, GeomapFieldsMapping.signed); - data.push({ - code: iso3, - components: locationComponents, - disbursed, - committed, - signed, - }); - }); - const maxValue: number = - _.max(data.map((d: any) => d[aggregationField])) ?? 0; - let interval = 0; - if (maxValue) { - interval = maxValue / 13; - } - const intervals: number[] = []; - for (let i = 0; i < 13; i++) { - intervals.push(interval * i); - } - const features = geoJSONData.map((feature: any) => { - const fItem = _.find(data, {code: feature.id}); - let itemValue = 0; - if (fItem) { - const fItemValue = fItem[aggregationField]; - if ( - (fItemValue < maxValue || fItemValue === maxValue) && - (fItemValue >= intervals[11] || fItemValue === intervals[11]) - ) { - itemValue = 12; - } - if ( - (fItemValue < intervals[11] || fItemValue === intervals[11]) && - (fItemValue >= intervals[10] || fItemValue === intervals[10]) - ) { - itemValue = 11; - } - if ( - (fItemValue < intervals[10] || fItemValue === intervals[10]) && - (fItemValue >= intervals[9] || fItemValue === intervals[9]) - ) { - itemValue = 10; - } - if ( - (fItemValue < intervals[9] || fItemValue === intervals[9]) && - (fItemValue >= intervals[8] || fItemValue === intervals[8]) - ) { - itemValue = 9; - } - if ( - (fItemValue < intervals[8] || fItemValue === intervals[8]) && - (fItemValue >= intervals[7] || fItemValue === intervals[7]) - ) { - itemValue = 8; - } - if ( - (fItemValue < intervals[7] || fItemValue === intervals[7]) && - (fItemValue >= intervals[6] || fItemValue === intervals[6]) - ) { - itemValue = 7; - } - if ( - (fItemValue < intervals[6] || fItemValue === intervals[6]) && - (fItemValue >= intervals[5] || fItemValue === intervals[5]) - ) { - itemValue = 6; - } - if ( - (fItemValue < intervals[5] || fItemValue === intervals[5]) && - (fItemValue >= intervals[4] || fItemValue === intervals[4]) - ) { - itemValue = 5; - } - if ( - (fItemValue < intervals[4] || fItemValue === intervals[4]) && - (fItemValue >= intervals[3] || fItemValue === intervals[3]) - ) { - itemValue = 4; - } - if ( - (fItemValue < intervals[3] || fItemValue === intervals[3]) && - (fItemValue >= intervals[2] || fItemValue === intervals[2]) - ) { - itemValue = 3; - } - if ( - (fItemValue < intervals[2] || fItemValue === intervals[2]) && - (fItemValue >= intervals[1] || fItemValue === intervals[1]) - ) { - itemValue = 2; - } - if ( - (fItemValue < intervals[1] || fItemValue === intervals[1]) && - (fItemValue >= intervals[0] || fItemValue === intervals[0]) - ) { - itemValue = 1; - } - } - return { - ...feature, - properties: { - ...feature.properties, - value: itemValue, - iso_a3: feature.id, - data: fItem - ? { - components: fItem.components, - disbursed: fItem.disbursed, - committed: fItem.committed, - signed: fItem.signed, - } - : {}, - }, - }; - }); - return { - count: features.length, - data: features, - maxValue, - }; - }), - ) - .catch(handleDataApiError); - } - - @get('/disbursements/geomap/multicountries') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - geomapMulticountries(): object { - let aggregationField = GeomapFieldsMapping.disbursed; - if ( - this.req.query.aggregationField && - typeof this.req.query.aggregationField === 'string' && - _.get(GeomapFieldsMapping, this.req.query.aggregationField, null) - ) { - aggregationField = _.get( - GeomapFieldsMapping, - this.req.query.aggregationField, - GeomapFieldsMapping.disbursed, - ); - } - const filterString = getGeoMultiCountriesFilterString( - this.req.query, - GeomapFieldsMapping.disbursementsMulticountryGeomapAggregation, - 'GrantAgreement/MultiCountryId ne null', - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantPeriods}/?${params}${filterString}`; - - return axios - .all([axios.get(url), axios.get(urls.multicountriescountriesdata)]) - .then( - axios.spread((...responses) => { - const rawData = _.get( - responses[0].data, - GeomapFieldsMapping.dataPath, - [], - ); - const mcGeoData = _.get( - responses[1].data, - GeomapFieldsMapping.dataPath, - [], - ); - const data: any = []; - const groupedByMulticountry = _.groupBy( - rawData, - GeomapFieldsMapping.multicountry, - ); - Object.keys(groupedByMulticountry).forEach((mc: string) => { - const fMCGeoItem = _.find( - mcGeoData, - (mcGeoItem: any) => - _.get( - mcGeoItem, - GeomapFieldsMapping.geoDataMulticountry, - '', - ) === mc, - ); - let latitude = 0; - let longitude = 0; - if (fMCGeoItem) { - const coordinates: Position[] = []; - const composition = _.get( - fMCGeoItem, - GeomapFieldsMapping.multiCountryComposition, - [], - ); - composition.forEach((item: any) => { - const iso3 = _.get( - item, - GeomapFieldsMapping.multiCountryCompositionItem, - '', - ); - const fCountry = _.find(staticCountries, {iso3: iso3}); - if (fCountry) { - coordinates.push([fCountry.longitude, fCountry.latitude]); - } - }); - if (coordinates.length > 0) { - const lonlat = center(points(coordinates)); - longitude = lonlat.geometry.coordinates[0]; - latitude = lonlat.geometry.coordinates[1]; - } - } - data.push({ - id: mc, - code: mc.replace(/\//g, '|'), - geoName: mc, - components: groupedByMulticountry[mc].map((item: any) => ({ - name: _.get( - item, - GeomapFieldsMapping.multicountryComponent, - '', - ), - activitiesCount: _.get(item, GeomapFieldsMapping.count, 0), - value: _.get(item, aggregationField, 0), - })), - disbursed: _.sumBy( - groupedByMulticountry[mc], - GeomapFieldsMapping.disbursed, - ), - committed: _.sumBy( - groupedByMulticountry[mc], - GeomapFieldsMapping.committed, - ), - signed: _.sumBy( - groupedByMulticountry[mc], - GeomapFieldsMapping.signed, - ), - latitude: latitude, - longitude: longitude, - }); - }); - return { - pins: data, - }; - }), - ) - .catch(handleDataApiError); - } - - // Location page - - @get('/location/disbursements/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - locationDetailTreemap(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.locationDisbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentGrants: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - let grantName = item[TreemapFieldsMapping.grantName]; - let grantCode = item[TreemapFieldsMapping.grantCode]; - if (item[TreemapFieldsMapping.multicountry] !== null) { - grantName = item[TreemapFieldsMapping.multicountry]; - grantCode = item[TreemapFieldsMapping.multicountry]; - } - componentGrants.push({ - name: `${grantName} | ${grantCode}`, - code: grantCode, - value: item[TreemapFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: grantName, - count: item.count, - investment: item[TreemapFieldsMapping.disbursed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const disbursed = _.sumBy(componentGrants, 'value'); - const committed = _.sumBy( - componentGrants, - 'tooltip.totalInvestments.committed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(componentGrants, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentGrants, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentGrants, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - componentGrants, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/location/signed/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - locationDetailTreemapSigned(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.locationDisbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentGrants: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - let grantName = item[TreemapFieldsMapping.grantName]; - let grantCode = item[TreemapFieldsMapping.grantCode]; - if (item[TreemapFieldsMapping.multicountry] !== null) { - grantName = item[TreemapFieldsMapping.multicountry]; - grantCode = item[TreemapFieldsMapping.multicountry]; - } - componentGrants.push({ - name: `${grantName} | ${grantCode}`, - code: grantCode, - value: item[TreemapFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: grantName, - count: item.count, - investment: item[TreemapFieldsMapping.signed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const signed = _.sumBy(componentGrants, 'value'); - const committed = _.sumBy( - componentGrants, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - componentGrants, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(componentGrants, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentGrants, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentGrants, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/location/commitment/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - locationDetailTreemapCommitment(): object { - const filterString = getFilterString( - this.req.query, - TreemapFieldsMapping.locationDisbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantsNoCount}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, TreemapFieldsMapping.dataPath, []), - TreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentGrants: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - let grantName = item[TreemapFieldsMapping.grantName]; - let grantCode = item[TreemapFieldsMapping.grantCode]; - if (item[TreemapFieldsMapping.multicountry] !== null) { - grantName = item[TreemapFieldsMapping.multicountry]; - grantCode = item[TreemapFieldsMapping.multicountry]; - } - componentGrants.push({ - name: `${grantName} | ${grantCode}`, - code: grantCode, - value: item[TreemapFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[TreemapFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: grantName, - count: item.count, - investment: item[TreemapFieldsMapping.committed], - }, - ], - totalInvestments: { - committed: item[TreemapFieldsMapping.committed], - disbursed: item[TreemapFieldsMapping.disbursed], - signed: item[TreemapFieldsMapping.signed], - }, - percValue: ( - (item[TreemapFieldsMapping.disbursed] * 100) / - item[TreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const committed = _.sumBy(componentGrants, 'value'); - const signed = _.sumBy( - componentGrants, - 'tooltip.totalInvestments.signed', - ); - const disbursed = _.sumBy( - componentGrants, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(componentGrants, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentGrants, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentGrants, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed, - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - // Grant page - - @get('/grant/disbursements/time-cycle') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - grantDetailTimeCycle(): object { - const filterString = grantDetailGetFilterString( - this.req.query, - GrantDetailTimeCycleFieldsMapping.disbursementsTimeCycleAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantDetailDisbursements}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - let apiData = _.get( - resp.data, - GrantDetailTimeCycleFieldsMapping.dataPath, - [], - ); - if (apiData.length > 0) { - if ( - _.get(apiData[0], GrantDetailTimeCycleFieldsMapping.year, '') - .length > 4 - ) { - apiData = _.filter( - apiData, - (item: any) => item[GrantDetailTimeCycleFieldsMapping.year], - ).map((item: any) => ({ - ...item, - [GrantDetailTimeCycleFieldsMapping.year]: item[ - GrantDetailTimeCycleFieldsMapping.year - ].slice(0, 4), - })); - } - } - const groupedDataByYear = _.groupBy( - apiData, - GrantDetailTimeCycleFieldsMapping.year, - ); - const data: any = []; - Object.keys(groupedDataByYear).forEach((year: string) => { - const dataItems = groupedDataByYear[year]; - const yearComponents: any = []; - _.orderBy( - dataItems, - GrantDetailTimeCycleFieldsMapping.year, - 'asc', - ).forEach((item: any) => { - const value = parseInt( - _.get(item, GrantDetailTimeCycleFieldsMapping.disbursed, 0), - 10, - ); - if (value) { - const name = _.get( - item, - GrantDetailTimeCycleFieldsMapping.component, - '', - ); - const prevYearComponent = _.get( - data, - `[${data.length - 1}].cumulativeChildren`, - [], - ); - yearComponents.push({ - name, - disbursed: value, - cumulative: - _.get(_.find(prevYearComponent, {name}), 'value', 0) + value, - }); - } - }); - const disbursed = _.sumBy(yearComponents, 'disbursed'); - const cumulative = _.sumBy(yearComponents, 'cumulative'); - - data.push({ - year, - disbursed, - cumulative, - disbursedChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - GrantDetailTimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.disbursed, - }), - ), - cumulativeChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - GrantDetailTimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.cumulative, - }), - ), - }); - }); - return { - count: data.length, - data: data, - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/commitment/time-cycle') - @response(200, DISBURSEMENTS_TIME_CYCLE_RESPONSE) - grantDetailTimeCycleCommitment(): object { - const filterString = getFilterString( - this.req.query, - GrantCommittedTimeCycleFieldsMapping.aggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.commitments}/?${params}${filterString - .replace( - filteringGrants.IPnumber, - GrantCommittedTimeCycleFieldsMapping.IPnumber, - ) - .replace( - filteringGrants.grantId, - GrantCommittedTimeCycleFieldsMapping.grantId, - )}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - let apiData = _.get(resp.data, TimeCycleFieldsMapping.dataPath, []); - if (apiData.length > 0) { - if ( - _.get(apiData[0], TimeCycleFieldsMapping.committedYear, '').length > - 4 - ) { - apiData = _.filter( - apiData, - (item: any) => item[TimeCycleFieldsMapping.committedYear], - ).map((item: any) => ({ - ...item, - [TimeCycleFieldsMapping.committedYear]: item[ - TimeCycleFieldsMapping.committedYear - ].slice(0, 4), - })); - } - } - const groupedDataByYear = _.groupBy( - apiData, - TimeCycleFieldsMapping.committedYear, - ); - const data: any = []; - Object.keys(groupedDataByYear).forEach((year: string) => { - const dataItems = groupedDataByYear[year]; - const yearComponents: any = []; - _.orderBy( - dataItems, - TimeCycleFieldsMapping.committedYear, - 'asc', - ).forEach((item: any) => { - const value = parseInt( - _.get(item, TimeCycleFieldsMapping.committed, 0), - 10, - ); - if (value) { - const name = _.get(item, TimeCycleFieldsMapping.component, ''); - const prevYearComponent = _.get( - data, - `[${data.length - 1}].cumulativeChildren`, - [], - ); - yearComponents.push({ - name, - disbursed: value, - cumulative: - _.get(_.find(prevYearComponent, {name}), 'value', 0) + value, - }); - } - }); - const disbursed = _.sumBy(yearComponents, 'disbursed'); - const cumulative = _.sumBy(yearComponents, 'cumulative'); - - data.push({ - year, - disbursed, - cumulative, - disbursedChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.disbursed, - }), - ), - cumulativeChildren: _.orderBy(yearComponents, 'name', 'asc').map( - (yc: any) => ({ - name: yc.name, - color: _.get( - TimeCycleFieldsMapping.componentColors, - yc.name, - '', - ), - value: yc.cumulative, - }), - ), - }); - }); - return { - count: data.length, - data: data, - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/disbursements/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - grantDetailTreemap(): object { - const filterString = grantDetailTreemapGetFilterString( - this.req.query, - GrantDetailTreemapFieldsMapping.disbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantDetailGrants}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, GrantDetailTreemapFieldsMapping.dataPath, []), - GrantDetailTreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - componentLocations.push({ - name: _.get( - item, - GrantDetailTreemapFieldsMapping.locationName, - '', - ), - value: item[GrantDetailTreemapFieldsMapping.disbursed], - formattedValue: formatFinancialValue( - item[GrantDetailTreemapFieldsMapping.disbursed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: _.get( - item, - GrantDetailTreemapFieldsMapping.locationName, - '', - ), - count: item.count, - investment: item[GrantDetailTreemapFieldsMapping.disbursed], - }, - ], - totalInvestments: { - committed: item[GrantDetailTreemapFieldsMapping.committed], - disbursed: item[GrantDetailTreemapFieldsMapping.disbursed], - signed: item[GrantDetailTreemapFieldsMapping.signed], - }, - percValue: ( - (item[GrantDetailTreemapFieldsMapping.disbursed] * 100) / - item[GrantDetailTreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const disbursed = _.sumBy(componentLocations, 'value'); - const committed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.committed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: disbursed, - formattedValue: formatFinancialValue(disbursed), - _children: _.orderBy(componentLocations, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentLocations, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentLocations, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - componentLocations, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/signed/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - grantDetailTreemapSigned(): object { - const filterString = grantDetailTreemapGetFilterString( - this.req.query, - GrantDetailTreemapFieldsMapping.disbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantDetailGrants}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, GrantDetailTreemapFieldsMapping.dataPath, []), - GrantDetailTreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - componentLocations.push({ - name: _.get( - item, - GrantDetailTreemapFieldsMapping.locationName, - '', - ), - value: item[GrantDetailTreemapFieldsMapping.signed], - formattedValue: formatFinancialValue( - item[GrantDetailTreemapFieldsMapping.signed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: _.get( - item, - GrantDetailTreemapFieldsMapping.locationName, - '', - ), - count: item.count, - investment: item[GrantDetailTreemapFieldsMapping.signed], - }, - ], - totalInvestments: { - committed: item[GrantDetailTreemapFieldsMapping.committed], - disbursed: item[GrantDetailTreemapFieldsMapping.disbursed], - signed: item[GrantDetailTreemapFieldsMapping.signed], - }, - percValue: ( - (item[GrantDetailTreemapFieldsMapping.disbursed] * 100) / - item[GrantDetailTreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const signed = _.sumBy(componentLocations, 'value'); - const committed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.committed', - ); - const disbursed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: signed, - formattedValue: formatFinancialValue(signed), - _children: _.orderBy(componentLocations, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentLocations, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentLocations, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - componentLocations, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/commitment/treemap') - @response(200, DISBURSEMENTS_TREEMAP_RESPONSE) - grantDetailTreemapCommitment(): object { - const filterString = grantDetailTreemapGetFilterString( - this.req.query, - GrantDetailTreemapFieldsMapping.disbursementsTreemapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grantDetailGrants}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const groupedDataByComponent = _.groupBy( - _.get(resp.data, GrantDetailTreemapFieldsMapping.dataPath, []), - GrantDetailTreemapFieldsMapping.component, - ); - const data: DisbursementsTreemapDataItem[] = []; - Object.keys(groupedDataByComponent).forEach((component: string) => { - const dataItems = groupedDataByComponent[component]; - const componentLocations: DisbursementsTreemapDataItem[] = []; - dataItems.forEach((item: any) => { - componentLocations.push({ - name: _.get( - item, - GrantDetailTreemapFieldsMapping.locationName, - '', - ), - value: item[GrantDetailTreemapFieldsMapping.committed], - formattedValue: formatFinancialValue( - item[GrantDetailTreemapFieldsMapping.committed], - ), - color: '#595C70', - tooltip: { - header: component, - componentsStats: [ - { - name: _.get( - item, - GrantDetailTreemapFieldsMapping.locationName, - '', - ), - count: item.count, - investment: item[GrantDetailTreemapFieldsMapping.committed], - }, - ], - totalInvestments: { - committed: item[GrantDetailTreemapFieldsMapping.committed], - disbursed: item[GrantDetailTreemapFieldsMapping.disbursed], - signed: item[GrantDetailTreemapFieldsMapping.signed], - }, - percValue: ( - (item[GrantDetailTreemapFieldsMapping.disbursed] * 100) / - item[GrantDetailTreemapFieldsMapping.committed] - ).toString(), - }, - }); - }); - const committed = _.sumBy(componentLocations, 'value'); - const signed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.signed', - ); - const disbursed = _.sumBy( - componentLocations, - 'tooltip.totalInvestments.disbursed', - ); - data.push({ - name: component, - color: '#DFE3E5', - value: committed, - formattedValue: formatFinancialValue(committed), - _children: _.orderBy(componentLocations, 'value', 'desc'), - tooltip: { - header: component, - componentsStats: [ - { - name: component, - count: _.sumBy( - componentLocations, - 'tooltip.componentsStats[0].count', - ), - investment: _.sumBy(componentLocations, 'value'), - }, - ], - totalInvestments: { - committed, - disbursed, - signed: _.sumBy( - componentLocations, - 'tooltip.totalInvestments.signed', - ), - }, - percValue: ((disbursed * 100) / committed).toString(), - }, - }); - }); - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } } diff --git a/src/controllers/documents.controller.ts b/src/controllers/documents.controller.ts index d71a689..649ee29 100644 --- a/src/controllers/documents.controller.ts +++ b/src/controllers/documents.controller.ts @@ -1,65 +1,15 @@ import {inject} from '@loopback/core'; -import { - get, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; +import {get, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; -import {mapTransform} from 'map-transform'; -import docsMap from '../config/mapping/documents/index.json'; import DocumentsListMapping from '../config/mapping/documents/list.json'; -import docsUtils from '../config/mapping/documents/utils.json'; import urls from '../config/urls/index.json'; -import {DocumentsTableRow} from '../interfaces/documentsTable'; import {handleDataApiError} from '../utils/dataApiError'; import {filterDocuments} from '../utils/filtering/documents'; -import {getFilterString} from '../utils/filtering/documents/getFilterString'; - -const RESULTS_RESPONSE: ResponseObject = { - description: 'Results Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'Results Response', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - id: {type: 'string'}, - title: {type: 'string'}, - value: {type: 'number'}, - component: {type: 'string'}, - geoLocations: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'number'}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; export class DocumentsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/documents') @response(200) async documents() { @@ -113,167 +63,4 @@ export class DocumentsController { }) .catch(handleDataApiError); } - - // v2 - - @get('/v2/documents') - @response(200, RESULTS_RESPONSE) - documentsV2(): object { - const mapper = mapTransform(docsMap); - const filterString = getFilterString( - this.req.query, - docsUtils.defaultFilter, - ); - const url = `${urls.documents}/?${docsUtils.defaultSelect}${docsUtils.defaultOrderBy}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const mappedData = mapper(resp.data) as never[]; - const data: DocumentsTableRow[] = []; - - const countryDocs = _.filter( - mappedData, - (doc: any) => - doc.country && - (!doc.organizationId || - doc.organizationId === '00000000-0000-0000-0000-000000000000'), - ); - const multicountryDocs = _.filter( - mappedData, - (doc: any) => - doc.organizationId && - doc.organizationId !== '00000000-0000-0000-0000-000000000000' && - doc.organizationName, - ); - const groupedByCountry = _.groupBy(countryDocs, 'country'); - Object.keys(groupedByCountry).forEach((country: string) => { - const groupedByCategory = _.groupBy( - groupedByCountry[country], - 'category', - ); - const docCategories: any[] = []; - _.orderBy(Object.keys(groupedByCategory), undefined, 'asc').forEach( - (category: string) => { - docCategories.push({ - name: category, - count: groupedByCategory[category].length, - docs: groupedByCategory[category].map((item: any) => { - let title = ''; - if (item.processName) title = `${title} ${item.processName}`; - if (item.component) title = `${title} ${item.component}`; - if (item.processYear && item.processWindow) { - title = `${title} - ${item.processYear} ${item.processWindow}`; - } else if (item.processYear) { - title = `${title} - ${item.processYear}`; - } else if (item.processWindow) { - title = `${title} - ${item.processWindow}`; - } - if (item.fileLanguage) { - title = `${title} - ${item.fileLanguage}`; - } - return { - title, - link: item.fileURL, - }; - }), - }); - }, - ); - data.push({ - name: country, - count: groupedByCountry[country].length, - docCategories, - }); - }); - const groupedByMulticountry = _.groupBy( - multicountryDocs, - 'organizationName', - ); - _.orderBy(Object.keys(groupedByMulticountry), undefined, 'asc').forEach( - (country: string) => { - const groupedByCategory = _.groupBy( - groupedByMulticountry[country], - 'category', - ); - const docCategories: any[] = []; - Object.keys(groupedByCategory).forEach((category: string) => { - docCategories.push({ - name: category, - count: groupedByCategory[category].length, - docs: groupedByCategory[category].map((item: any) => { - let title = ''; - if (item.processName) title = `${title} ${item.processName}`; - if (item.component) title = `${title} ${item.component}`; - if (item.processYear && item.processWindow) { - title = `${title} - ${item.processYear} ${item.processWindow}`; - } else if (item.processYear) { - title = `${title} - ${item.processYear}`; - } else if (item.processWindow) { - title = `${title} - ${item.processWindow}`; - } - if (item.fileLanguage) { - title = `${title} - ${item.fileLanguage}`; - } - return { - title, - link: item.fileURL, - }; - }), - }); - }); - data.push({ - name: country, - count: groupedByMulticountry[country].length, - docCategories, - }); - }, - ); - return { - count: data.length, - data: _.orderBy(data, 'name', 'asc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/grant-documents') - @response(200, RESULTS_RESPONSE) - grantDocuments(): object { - const mapper = mapTransform(docsMap); - const filterString = getFilterString(this.req.query); - const url = `${urls.documents}/?${docsUtils.defaultSelect}${docsUtils.defaultOrderBy}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const mappedData = mapper(resp.data) as never[]; - const data: DocumentsTableRow[] = []; - - const groupedByCategory = _.groupBy(mappedData, 'category'); - _.orderBy(Object.keys(groupedByCategory), undefined, 'asc').forEach( - (category: string) => { - data.push({ - name: category, - count: groupedByCategory[category].length, - docs: _.orderBy( - groupedByCategory[category].map((item: any) => ({ - title: `${item.processName}${ - item.fileIndex ? ` - ${item.fileIndex}` : '' - }`, - link: item.fileURL, - })), - 'title', - 'asc', - ), - }); - }, - ); - return { - count: data.length, - data, - }; - }) - .catch(handleDataApiError); - } } diff --git a/src/controllers/eligibility.controller.ts b/src/controllers/eligibility.controller.ts index a80b298..b27a200 100644 --- a/src/controllers/eligibility.controller.ts +++ b/src/controllers/eligibility.controller.ts @@ -1,106 +1,20 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; -import querystring from 'querystring'; -import filtering from '../config/filtering/index.json'; -import EligibilityFieldsMapping from '../config/mapping/eligibility/dotsChart.json'; import EligibilityHeatmap from '../config/mapping/eligibility/heatmap.json'; -import ScatterplotFieldsMapping from '../config/mapping/eligibility/scatterplot.json'; import EligibilityStatsMapping from '../config/mapping/eligibility/stats.json'; import EligibilityTableMapping from '../config/mapping/eligibility/table.json'; import EligibilityYearsFieldsMapping from '../config/mapping/eligibility/years.json'; import urls from '../config/urls/index.json'; -import {EligibilityDotDataItem} from '../interfaces/eligibilityDot'; -import {EligibilityScatterplotDataItem} from '../interfaces/eligibilityScatterplot'; import {handleDataApiError} from '../utils/dataApiError'; import {filterEligibility} from '../utils/filtering/eligibility'; -import {getFilterString} from '../utils/filtering/eligibility/getFilterString'; - -const ELIGIBILITY_RESPONSE: ResponseObject = { - description: 'Eligibility Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'EligibilityResponse', - properties: { - count: {type: 'number'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - items: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - status: {type: 'string'}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; -const ELIGIBILITY_COUNTRY_RESPONSE: ResponseObject = { - description: 'Eligibility Country Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'EligibilityCountryResponse', - properties: { - count: {type: 'number'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - id: {type: 'string'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - x: {type: 'string'}, - y: {type: 'string'}, - eligibility: {type: 'string'}, - incomeLevel: {type: 'string'}, - diseaseBurden: {type: 'string'}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; export class EligibilityController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/eligibility/years') - @response(200, ELIGIBILITY_RESPONSE) + @response(200) eligibilityYears(): object { const url = `${urls.ELIGIBILITY}/?${EligibilityYearsFieldsMapping.aggregation}`; @@ -338,525 +252,4 @@ export class EligibilityController { }) .catch(handleDataApiError); } - - // v2 - - @get('/v2/eligibility') - @response(200, ELIGIBILITY_RESPONSE) - eligibility(): object { - const aggregateByField = - this.req.query.aggregateBy ?? - EligibilityFieldsMapping.aggregateByFields[0]; - const nonAggregateByField = ( - this.req.query.nonAggregateBy - ? this.req.query.nonAggregateBy - : this.req.query.aggregateBy === - EligibilityFieldsMapping.aggregateByFields[0] - ? EligibilityFieldsMapping.aggregateByFields[1] - : EligibilityFieldsMapping.aggregateByFields[0] - ).toString(); - const filterString = getFilterString(this.req.query); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.eligibility}/?${params}${filterString}&${EligibilityFieldsMapping.defaultSelect}`; - const sortBy = this.req.query.sortBy; - const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'asc'; - - let outSortByValue = 'name'; - let inSortByValue = 'name'; - - if (sortByValue === 'status') { - outSortByValue = 'name'; - inSortByValue = 'status'; - } - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get(resp.data, EligibilityFieldsMapping.dataPath, []); - const aggregatedData = _.groupBy(apiData, aggregateByField); - const data: EligibilityDotDataItem[] = []; - - Object.keys(aggregatedData).forEach((key: string) => { - data.push({ - name: key, - items: _.orderBy( - aggregatedData[key].map(item => ({ - name: _.get(item, nonAggregateByField, ''), - status: _.get( - EligibilityFieldsMapping, - _.get(item, EligibilityFieldsMapping.status, '') - .toLowerCase() - .trim(), - _.get(item, EligibilityFieldsMapping.status, ''), - ), - })), - inSortByValue, - sortByDirection, - ), - }); - }); - - return { - count: data.length, - data: _.orderBy( - data, - outSortByValue, - inSortByValue === 'status' ? 'asc' : sortByDirection, - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/eligibility/years') - @response(200, ELIGIBILITY_RESPONSE) - eligibilityYearsV2(): object { - const url = `${urls.eligibility}/?${EligibilityYearsFieldsMapping.aggregation}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - return { - data: _.get( - resp.data, - EligibilityYearsFieldsMapping.dataPath, - [], - ).map((item: any) => - _.get(item, EligibilityYearsFieldsMapping.year, ''), - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/eligibility/country') - @response(200, ELIGIBILITY_COUNTRY_RESPONSE) - eligibilityCountry(): object { - if (_.get(this.req.query, 'locations', '').length === 0) { - return { - count: 0, - data: [], - }; - } - const filterString = getFilterString(this.req.query); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.eligibility}/?${params}${filterString}&${ScatterplotFieldsMapping.defaultSelect}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get(resp.data, ScatterplotFieldsMapping.dataPath, []); - const aggregatedData = _.groupBy( - apiData, - ScatterplotFieldsMapping.aggregateByField, - ); - const aggregatedDataByYear = _.groupBy( - apiData, - ScatterplotFieldsMapping.year, - ); - const years: number[] = _.sortBy( - _.uniq( - Object.keys(_.groupBy(apiData, ScatterplotFieldsMapping.year)), - ).map((key: string) => parseInt(key, 10)), - ); - years.push(years[years.length - 1] + 1); - years.unshift(years[0] - 1); - const data: {id: string; data: EligibilityScatterplotDataItem[]}[] = []; - - _.orderBy(Object.keys(aggregatedData), undefined, 'asc').forEach( - (key: string) => { - data.push({ - id: key, - data: _.orderBy( - aggregatedData[key], - ScatterplotFieldsMapping.year, - 'asc', - ).map(item => ({ - y: key, - x: _.get(item, ScatterplotFieldsMapping.year, ''), - eligibility: _.get( - ScatterplotFieldsMapping, - _.get(item, ScatterplotFieldsMapping.status, '') - .toLowerCase() - .trim(), - _.get(item, ScatterplotFieldsMapping.status, ''), - ), - incomeLevel: - _.get(item, ScatterplotFieldsMapping.incomeLevel, null) === - null - ? 0 - : _.findIndex( - ScatterplotFieldsMapping.incomeLevels, - (incomeLevel: string) => - incomeLevel === - _.get( - item, - ScatterplotFieldsMapping.incomeLevel, - 'None', - ), - ), - diseaseBurden: - _.get(item, ScatterplotFieldsMapping.diseaseBurden, null) === - null - ? 0 - : _.findIndex( - ScatterplotFieldsMapping.diseaseBurdens, - (diseaseBurden: string) => - diseaseBurden === - _.get( - item, - ScatterplotFieldsMapping.diseaseBurden, - 'None', - ), - ), - })), - }); - }, - ); - - data.forEach((item: any, index: number) => { - years.forEach((year: number, yearindex: number) => { - if (!_.find(item.data, {x: year})) { - let fItemWithData = _.get( - aggregatedDataByYear, - `${year}[0]`, - null, - ); - if (yearindex === 0) { - fItemWithData = _.get( - aggregatedDataByYear, - `${years[1]}[0]`, - null, - ); - } - const incomeLevel: number = - _.get( - fItemWithData, - ScatterplotFieldsMapping.incomeLevel, - null, - ) === null - ? 0 - : _.findIndex( - ScatterplotFieldsMapping.incomeLevels, - (il: string) => - il === - _.get( - fItemWithData, - ScatterplotFieldsMapping.incomeLevel, - 'None', - ), - ); - data[index].data.push({ - y: item.data[0].y, - x: year, - diseaseBurden: 0, - incomeLevel, - eligibility: 'Not Eligible', - invisible: true, - }); - } - }); - data[index].data = _.orderBy(data[index].data, 'x', 'asc'); - }); - - if (this.req.query.view && this.req.query.view === 'table') { - const sortBy = this.req.query.sortBy; - let sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'x'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'asc'; - - switch (sortByValue) { - case 'year': - sortByValue = 'x'; - break; - case 'component': - sortByValue = 'y'; - break; - case 'incomeLevel': - sortByValue = 'incomeLevel'; - break; - case 'diseaseBurden': - sortByValue = 'diseaseBurden'; - break; - case 'status': - sortByValue = 'eligibility'; - break; - default: - break; - } - - let tableData: EligibilityScatterplotDataItem[] = []; - data.forEach(comp => { - tableData = [...tableData, ...comp.data]; - }); - - tableData = _.orderBy(tableData, sortByValue, sortByDirection); - - return { - count: tableData.length, - data: tableData, - }; - } - - data.unshift({ - id: 'dummy1', - data: years.map((year: number) => ({ - x: year, - diseaseBurden: 0, - incomeLevel: 0, - eligibility: 'Not Eligible', - y: 'dummy1', - invisible: true, - })), - }); - - data.push({ - id: 'dummy2', - data: years.map((year: number, index: number) => { - let fItemWithData = _.get(aggregatedDataByYear, `${year}[0]`, null); - if (index === 0) { - fItemWithData = _.get( - aggregatedDataByYear, - `${years[1]}[0]`, - null, - ); - } - const incomeLevel: number = - _.get( - fItemWithData, - ScatterplotFieldsMapping.incomeLevel, - null, - ) === null - ? 0 - : _.findIndex( - ScatterplotFieldsMapping.incomeLevels, - (il: string) => - il === - _.get( - fItemWithData, - ScatterplotFieldsMapping.incomeLevel, - 'None', - ), - ); - return { - x: year, - y: 'dummy2', - diseaseBurden: 0, - incomeLevel, - eligibility: 'Not Eligible', - invisible: true, - }; - }), - }); - - return { - count: data.length, - data, - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/eligibility/table') - @response(200) - eligibilityTableV2(): object { - const aggregateByField = - this.req.query.aggregateBy && - this.req.query.aggregateBy.toString().length > 0 - ? this.req.query.aggregateBy - : EligibilityFieldsMapping.aggregateByFields[0]; - const nonAggregateByField = ( - this.req.query.nonAggregateBy - ? this.req.query.nonAggregateBy - : aggregateByField === EligibilityFieldsMapping.aggregateByFields[0] - ? EligibilityFieldsMapping.aggregateByFields[1] - : EligibilityFieldsMapping.aggregateByFields[0] - ).toString(); - const filterString = getFilterString(this.req.query); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.eligibility}/?${params}${filterString}&${ScatterplotFieldsMapping.defaultSelect}`; - const sortBy = this.req.query.sortBy; - const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'asc'; - - const isLocationPage = - _.get(this.req.query, 'locations', '').toString().split(',').length === 1; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const apiData = _.get(resp.data, ScatterplotFieldsMapping.dataPath, []); - - const data: any = []; - - const aggregatedData = _.groupBy(apiData, aggregateByField); - - Object.keys(aggregatedData).forEach(aggrKey => { - const yearsData = _.groupBy( - aggregatedData[aggrKey], - ScatterplotFieldsMapping.year, - ); - data.push({ - level1: aggrKey, - children: _.orderBy( - Object.keys(yearsData), - undefined, - sortByValue === 'level1' && isLocationPage - ? sortByDirection - : 'desc', - ).map(year => { - let incomeLevelIndex: number = - _.get( - yearsData[year][0], - ScatterplotFieldsMapping.incomeLevel, - null, - ) === null - ? 0 - : _.findIndex( - ScatterplotFieldsMapping.incomeLevels, - (incomeLevel: string) => - incomeLevel === - _.get( - yearsData[year][0], - ScatterplotFieldsMapping.incomeLevel, - 'None', - ), - ); - let incomeLevel: string = _.get( - ScatterplotFieldsMapping.incomeLevels, - incomeLevelIndex, - 'None', - ); - const groupedByNonAggregate = _.groupBy( - yearsData[year], - nonAggregateByField, - ); - return { - level1: year, - incomeLevel: - aggregateByField === - EligibilityFieldsMapping.aggregateByFields[1] - ? incomeLevel - : '', - children: _.orderBy( - Object.keys(groupedByNonAggregate).map(nonAggrKey => { - incomeLevelIndex = - _.get( - groupedByNonAggregate[nonAggrKey][0], - ScatterplotFieldsMapping.incomeLevel, - null, - ) === null - ? 0 - : _.findIndex( - ScatterplotFieldsMapping.incomeLevels, - (incomeLevel: string) => - incomeLevel === - _.get( - yearsData[year][0], - ScatterplotFieldsMapping.incomeLevel, - 'None', - ), - ); - incomeLevel = _.get( - ScatterplotFieldsMapping.incomeLevels, - incomeLevelIndex, - 'None', - ); - return { - level2: nonAggrKey, - eligibilityStatus: _.get( - ScatterplotFieldsMapping.statusValues, - _.get( - groupedByNonAggregate[nonAggrKey][0], - ScatterplotFieldsMapping.status, - '', - ), - _.get( - groupedByNonAggregate[nonAggrKey][0], - ScatterplotFieldsMapping.status, - '', - ), - ), - diseaseBurden: _.get( - groupedByNonAggregate[nonAggrKey][0], - ScatterplotFieldsMapping.diseaseBurden, - '', - ), - incomeLevel: - aggregateByField === - EligibilityFieldsMapping.aggregateByFields[0] - ? incomeLevel - : '', - }; - }), - sortByValue, - sortByDirection, - ), - }; - }), - }); - }); - - return { - count: data.length, - data: _.orderBy(data, sortByValue, sortByDirection), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/eligibility/status/codelist') - @response(200) - eligibilityStatusCodelist(): object { - const keys = Object.keys(ScatterplotFieldsMapping.statusValues); - return { - count: keys.length, - data: keys.map(key => ({ - value: key, - label: _.get(ScatterplotFieldsMapping.statusValues, `[${key}]`, key), - })), - }; - } - - @get('/v2/eligibility/disease-burden/codelist') - @response(200) - eligibilityDiseaseBurdenCodelist(): object { - return { - count: ScatterplotFieldsMapping.diseaseBurdens.length, - data: ScatterplotFieldsMapping.diseaseBurdens.map(db => ({ - value: db, - label: db, - })), - }; - } } diff --git a/src/controllers/filteroptions.controller.ts b/src/controllers/filteroptions.controller.ts index 5904a0e..5667b2c 100644 --- a/src/controllers/filteroptions.controller.ts +++ b/src/controllers/filteroptions.controller.ts @@ -1,11 +1,5 @@ import {inject} from '@loopback/core'; -import { - get, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; +import {get, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import ComponentMapping from '../config/mapping/filter-options/components.json'; @@ -15,71 +9,13 @@ import PrincipalRecipientMapping from '../config/mapping/filter-options/principa import ReplenishmentPeriodMapping from '../config/mapping/filter-options/replenishment-periods.json'; import ResultsComponentMapping from '../config/mapping/filter-options/results-components.json'; import StatusMapping from '../config/mapping/filter-options/status.json'; -import mappingComponents from '../config/mapping/filteroptions/components.json'; -import mappingDonors from '../config/mapping/filteroptions/donors.json'; -import mappingLocations from '../config/mapping/filteroptions/locations.json'; -import mappingMulticountries from '../config/mapping/filteroptions/multicountries.json'; -import mappingPartnertypes from '../config/mapping/filteroptions/partnertypes.json'; -import mappingReplenishmentperiods from '../config/mapping/filteroptions/replenishmentperiods.json'; -import mappingStatus from '../config/mapping/filteroptions/status.json'; import urls from '../config/urls/index.json'; import {FilterGroupOption} from '../interfaces/filters'; import {handleDataApiError} from '../utils/dataApiError'; -const FILTER_OPTIONS_RESPONSE: ResponseObject = { - description: 'Filter Options Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'FilterOptionsResponse', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'string'}, - options: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'string'}, - options: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'string'}, - options: { - type: 'array', - items: {}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; - export class FilteroptionsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/filter-options/geography') @response(200) async filterOptionsGeography() { @@ -461,372 +397,4 @@ export class FilteroptionsController { }) .catch(handleDataApiError); } - - // v2 - - @get('/v2/filter-options/locations') - @response(200, FILTER_OPTIONS_RESPONSE) - locations(): object { - const url = urls.filteroptionslocations; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, mappingLocations.dataPath, []); - const data: FilterGroupOption[] = []; - - rawData.forEach((item1: any) => { - const options = _.get(item1, mappingLocations.children, []); - data.push({ - name: _.get(item1, mappingLocations.label, ''), - value: _.get(item1, mappingLocations.value, ''), - options: - options && options.length > 0 - ? _.orderBy( - options.map((item2: any) => { - const item2options = _.get( - item2, - mappingLocations.children, - [], - ); - return { - name: _.get(item2, mappingLocations.label, ''), - value: _.get(item2, mappingLocations.value, ''), - options: - item2options && item2options.length > 0 - ? _.orderBy( - item2options.map((item3: any) => { - const item3options = _.get( - item3, - mappingLocations.children, - [], - ); - return { - name: _.get( - item3, - mappingLocations.label, - '', - ), - value: _.get( - item3, - mappingLocations.value, - '', - ), - options: - item3options && item3options.length > 0 - ? _.orderBy( - item3options.map((item4: any) => { - return { - name: _.get( - item4, - mappingLocations.label, - '', - ), - value: _.get( - item4, - mappingLocations.value, - '', - ), - }; - }), - 'label', - 'asc', - ) - : undefined, - }; - }), - 'label', - 'asc', - ) - : undefined, - }; - }), - 'label', - 'asc', - ) - : undefined, - }); - }); - - if (urls.filteroptionsmulticountries) { - return axios - .get(urls.filteroptionsmulticountries) - .then((resp2: AxiosResponse) => { - const mcRawData = _.get( - resp2.data, - mappingMulticountries.dataPath, - [], - ); - - mcRawData.forEach((item: any) => { - data.forEach((region: FilterGroupOption) => { - const fRegion = _.find(region.options, { - value: _.get(item, mappingMulticountries.regionCode), - }); - if (fRegion) { - region.options?.push({ - name: _.get(item, mappingMulticountries.label, ''), - value: _.get(item, mappingMulticountries.value, ''), - }); - } else { - region.options?.forEach((subRegion: FilterGroupOption) => { - const fSubRegion = _.find(subRegion.options, { - value: _.get(item, mappingMulticountries.regionCode), - }); - if (fSubRegion) { - subRegion.options?.push({ - name: _.get(item, mappingMulticountries.label, ''), - value: _.get(item, mappingMulticountries.value, ''), - }); - } - }); - } - }); - }); - - return { - name: 'Locations', - options: _.orderBy(data, 'label', 'asc'), - }; - }) - .catch(handleDataApiError); - } else { - return { - name: 'Locations', - options: _.orderBy(data, 'label', 'asc'), - }; - } - }) - .catch(handleDataApiError); - } - - @get('/v2/filter-options/components') - @response(200, FILTER_OPTIONS_RESPONSE) - components(): object { - const url = urls.filteroptionscomponents; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, mappingComponents.dataPath, []); - - return { - name: 'Components', - options: rawData.map((item: any) => ({ - name: _.get(item, mappingComponents.label, ''), - value: _.get(item, mappingComponents.value, ''), - })), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/filter-options/partner-types') - @response(200, FILTER_OPTIONS_RESPONSE) - partnerTypes(): object { - const url = urls.filteroptionspartnertypes; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, mappingPartnertypes.dataPath, []); - - const groupedByPartnerType = _.groupBy( - rawData, - mappingPartnertypes.partnerType, - ); - - const options: FilterGroupOption[] = []; - - Object.keys(groupedByPartnerType).forEach((partnerType: string) => { - const groupedBySubPartnerType = _.groupBy( - groupedByPartnerType[partnerType], - mappingPartnertypes.partnerSubType, - ); - const options: FilterGroupOption[] = []; - Object.keys(groupedBySubPartnerType).forEach( - (subPartnerType: string) => { - options.push({ - name: - subPartnerType && subPartnerType !== 'null' - ? subPartnerType - : 'Not Classified', - value: _.get( - groupedBySubPartnerType[subPartnerType][0], - mappingPartnertypes.partnerSubTypeId, - '', - ), - options: _.orderBy( - groupedBySubPartnerType[subPartnerType].map( - (partner: any) => ({ - name: _.get(partner, mappingPartnertypes.partner, ''), - value: _.get(partner, mappingPartnertypes.partnerId, ''), - }), - ), - 'label', - 'asc', - ), - }); - }, - ); - options.push({ - name: - partnerType && partnerType !== 'null' - ? partnerType - : 'Not Classified', - value: _.get( - groupedByPartnerType[partnerType][0], - mappingPartnertypes.partnerTypeId, - '', - ), - options: _.orderBy(options, 'label', 'asc'), - }); - }); - - return { - name: 'Partner types', - options: _.orderBy(options, 'label', 'asc'), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/filter-options/status') - @response(200, FILTER_OPTIONS_RESPONSE) - status(): object { - const url = urls.filteroptionsstatus; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, mappingStatus.dataPath, []); - - return { - name: 'Grant status', - options: _.orderBy( - rawData.map((item: any) => ({ - name: _.get(item, mappingStatus.label, ''), - value: _.get(item, mappingStatus.value, ''), - })), - 'label', - 'asc', - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/filter-options/replenishment-periods') - @response(200, FILTER_OPTIONS_RESPONSE) - replenishmentPeriods(): object { - const url = urls.filteroptionsreplenishmentperiods; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get( - resp.data, - mappingReplenishmentperiods.dataPath, - [], - ); - - return { - name: 'Replenishment periods', - options: _.orderBy( - rawData.map((item: any) => ({ - name: _.get(item, mappingReplenishmentperiods.label, ''), - value: _.get(item, mappingReplenishmentperiods.value, ''), - })), - 'label', - 'asc', - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/filter-options/donors') - @response(200, FILTER_OPTIONS_RESPONSE) - donors(): object { - const keyword = (this.req.query.q ?? '').toString().trim(); - const keywords = keyword.split(' '); - const url = urls.filteroptionsdonors; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, mappingDonors.dataPath, []); - const options: FilterGroupOption[] = []; - - rawData.forEach((item: any) => { - const type: FilterGroupOption = { - name: _.get(item, mappingDonors.label, ''), - value: _.get(item, mappingDonors.value, ''), - options: [], - }; - - _.get(item, mappingDonors.children, []).forEach((child: any) => { - if (_.get(child, mappingDonors.children, []).length > 0) { - const subType: FilterGroupOption = { - name: _.get(child, mappingDonors.label, ''), - value: _.get(child, mappingDonors.value, ''), - options: [], - }; - _.get(child, mappingDonors.children, []).forEach( - (gchild: any) => { - subType.options?.push({ - name: _.get(gchild, mappingDonors.label, ''), - value: _.get(gchild, mappingDonors.value, ''), - }); - }, - ); - subType.options = _.orderBy(subType.options, 'label', 'asc'); - type.options?.push(subType); - } else { - type.options?.push({ - name: _.get(child, mappingDonors.label, ''), - value: _.get(child, mappingDonors.value, ''), - }); - } - }); - - type.options = _.orderBy(type.options, 'label', 'asc'); - - if (keyword.length > 0) { - type.options = _.filter(type.options, (option: any) => { - let allKeywordsFound = true; - keywords.forEach((key: string) => { - if ( - option.label.toLowerCase().indexOf(key.toLowerCase()) === -1 - ) { - allKeywordsFound = false; - } - }); - return allKeywordsFound; - }) as FilterGroupOption[]; - } - - const optionsWithoptions: FilterGroupOption[] = _.orderBy( - _.filter(type.options, o => o.options) as FilterGroupOption[], - 'label', - 'asc', - ); - const optionsWithOutoptions: FilterGroupOption[] = _.orderBy( - _.filter(type.options, o => !o.options), - 'label', - 'asc', - ); - - type.options = [...optionsWithoptions, ...optionsWithOutoptions]; - - options.push(type); - }); - - return { - name: 'Donors', - options: _.orderBy(options, 'label', 'asc'), - }; - }) - .catch(handleDataApiError); - } } diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 044fb41..87e6b32 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -4,21 +4,15 @@ import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import {mapTransform} from 'map-transform'; import moment from 'moment'; -import querystring from 'querystring'; -import filtering from '../config/filtering/index.json'; import CyclesMapping from '../config/mapping/fundingrequests/cycles.json'; import Table2FieldsMapping from '../config/mapping/fundingrequests/table-2.json'; -import TableFieldsMapping from '../config/mapping/fundingrequests/table.json'; import urls from '../config/urls/index.json'; import {handleDataApiError} from '../utils/dataApiError'; import {filterFundingRequests} from '../utils/filtering/fundingRequests'; -import {getFilterString} from '../utils/filtering/fundingrequests/getFilterString'; export class FundingRequestsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/funding-requests') @response(200) async fundingRequests() { @@ -145,145 +139,4 @@ export class FundingRequestsController { }) .catch(handleDataApiError); } - - // v2 - - @get('/funding-requests/table') - @response(200) - fundingRequestsLocationTable(): object { - const filterString = getFilterString(this.req.query); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.fundingrequests}/?${params}${filterString}&${TableFieldsMapping.defaultExpand}`; - const sortBy = this.req.query.sortBy; - const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'asc'; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const mapper = mapTransform(TableFieldsMapping.tableMap); - const apiData = mapper(resp.data) as never[]; - - const data: any = []; - - const aggregatedData = _.groupBy( - apiData, - (item: any) => item.country || item.multicountry, - ); - - Object.keys(aggregatedData).forEach((key: string) => { - data.push({ - name: key, - children: _.orderBy( - aggregatedData[key].map((item: any) => { - const rawDate = moment(item.submissionDate); - const date = rawDate.format('D MMMM YYYY'); - return { - id: item.name, - date: date === 'Invalid date' ? null : date, - component: item.components - .map((c: any) => c.component) - .join(', '), - approach: item.approach, - window: item.trpwindow, - outcome: item.trpoutcome, - portfolioCategory: item.portfolioCategory, - rawDate: date === 'Invalid date' ? 0 : rawDate, - children: item.items.map((subitem: any) => ({ - gac: moment(subitem.gacmeeting).format('MMM YY'), - // board: moment(subitem.boardApproval).format('MMM YY'), - grant: subitem.IPGrantNumber, - start: moment(subitem.IPStartDate).format('DD-MM-YYYY'), - end: moment(subitem.IPEndDate).format('DD-MM-YYYY'), - component: subitem.component, - ip: subitem.IPNumber, - })), - documents: item.documents, - }; - }), - ['rawDate', 'component'], - ['desc', 'asc'], - ), - }); - }); - - return { - count: data.length, - data: _.orderBy(data, sortByValue, sortByDirection), - }; - }) - .catch(handleDataApiError); - } - - @get('/funding-requests/portfolio-categories/codelist') - @response(200) - fundingRequestsPortfolioCategoriesCodelist(): object { - const url = `${urls.fundingrequests}/?${TableFieldsMapping.portfolioCategoriesCodelistAggregation}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - return { - data: _.filter( - _.get(resp.data, TableFieldsMapping.dataPath, []).map( - (item: any) => ({ - label: _.get( - item, - `[${TableFieldsMapping.portfolioCategoryAggregationField}]`, - '', - ), - value: _.get( - item, - `[${TableFieldsMapping.portfolioCategoryAggregationField}]`, - '', - ), - }), - ), - (item: any) => item.value !== null && item.value !== 'null', - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/funding-requests/trp-window/codelist') - @response(200) - trpWindowCodelist(): object { - const url = `${urls.fundingrequests}/?${TableFieldsMapping.trpWindowCodelistAggregation}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - return { - data: _.filter( - _.get(resp.data, TableFieldsMapping.dataPath, []).map( - (item: any) => ({ - label: _.get( - item, - `[${TableFieldsMapping.trpWindowAggregationField}]`, - '', - ), - value: _.get( - item, - `[${TableFieldsMapping.trpWindowAggregationField}]`, - '', - ), - }), - ), - (item: any) => item.value !== null && item.value !== 'null', - ), - }; - }) - .catch(handleDataApiError); - } } diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index 473eee8..c1d5749 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -1,12 +1,5 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import {mapTransform} from 'map-transform'; @@ -15,70 +8,18 @@ import querystring from 'querystring'; import filtering from '../config/filtering/index.json'; import {getPage} from '../config/filtering/utils'; import GrantMapping from '../config/mapping/grants/grant.json'; -import grantDetailMap from '../config/mapping/grants/grantDetail.json'; -import grantDetailUtils from '../config/mapping/grants/grantDetail.utils.json'; import GrantImplementationMapping from '../config/mapping/grants/grantImplementation.json'; import GrantOverviewMapping from '../config/mapping/grants/grantOverview.json'; -import grantPeriodGoalsObjectivesMap from '../config/mapping/grants/grantPeriodGoalsObjectives.json'; -import grantPeriodInfoMap from '../config/mapping/grants/grantPeriodInfo.json'; -import grantPeriodsMap from '../config/mapping/grants/grantPeriods.json'; -import GrantsRadialMapping from '../config/mapping/grants/grantsRadial.json'; -import grantsMap from '../config/mapping/grants/index.json'; import GrantsListMapping from '../config/mapping/grants/list.json'; import GrantTargetsResultsMapping from '../config/mapping/grants/targetsResults.json'; -import grantsUtils from '../config/mapping/grants/utils.json'; import urls from '../config/urls/index.json'; -import { - GrantDetailInformation, - GrantDetailPeriod, - GrantDetailPeriodInformation, -} from '../interfaces/grantDetail'; -import { - GrantListItemModel, - GrantListItemModelV2, -} from '../interfaces/grantList'; +import {GrantListItemModel} from '../interfaces/grantList'; import {handleDataApiError} from '../utils/dataApiError'; import {filterGrants} from '../utils/filtering/grants'; -import {getFilterString} from '../utils/filtering/grants/getFilterString'; -import {getFilterString as getFilterStringPF} from '../utils/filtering/performancerating/getFilterString'; - -const GRANTS_RESPONSE: ResponseObject = { - description: 'Grants Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'GrantsResponse', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - id: {type: 'string'}, - title: {type: 'string'}, - status: {type: 'string'}, - component: {type: 'string'}, - geoLocation: {type: 'string'}, - rating: {type: 'string'}, - disbursed: {type: 'number'}, - committed: {type: 'number'}, - signed: {type: 'number'}, - }, - }, - }, - }, - }, - }, - }, -}; export class GrantsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/grants/{page}/{pageSize}') @response(200) async grants( @@ -742,296 +683,4 @@ export class GrantsController { }) .catch(handleDataApiError); } - - // v2 - - @get('/grants') - @response(200, GRANTS_RESPONSE) - grantsV2(): object { - const mapper = mapTransform(grantsMap); - const page = (this.req.query.page ?? '1').toString(); - const pageSize = (this.req.query.pageSize ?? '10').toString(); - const orderBy = this.req.query.orderBy ?? grantsUtils.defaultOrderBy; - const filterString = getFilterString(this.req.query); - const params = querystring.stringify( - { - ...getPage(filtering.page, parseInt(page, 10), parseInt(pageSize, 10)), - [filtering.page_size]: pageSize, - }, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.grants}${filterString}${filtering.orderby}${ - filtering.param_assign_operator - }${orderBy}${parseInt(pageSize, 10) > 0 ? `&${params}` : ''}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const res: GrantListItemModelV2[] = mapper(resp.data) as never[]; - return { - count: resp.data[grantsUtils.countPath], - data: res, - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/detail') - @response(200, GRANTS_RESPONSE) - grantDetail(): object { - const grantNumber = _.get(this.req.query, 'grantNumber', null); - if (!grantNumber) { - return { - data: {}, - message: '"grantNumber" parameter is required.', - }; - } - const mapper = mapTransform(grantDetailMap); - const url = `${urls.grantsNoCount}/?$top=1&$filter=${grantDetailUtils.grantNumber} eq '${grantNumber}'`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const res: GrantDetailInformation[] = mapper(resp.data) as never[]; - return { - data: res, - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/periods') - @response(200, GRANTS_RESPONSE) - grantDetailPeriods(): object { - const grantNumber = _.get(this.req.query, 'grantNumber', null); - if (!grantNumber) { - return { - data: {}, - message: '"grantNumber" parameter is required.', - }; - } - const mapper = mapTransform(grantPeriodsMap); - const url = `${urls.grantPeriods}/?${grantDetailUtils.defaultSelectFields}${grantDetailUtils.defaultSort}$filter=${grantDetailUtils.periodGrantNumber} eq '${grantNumber}'`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const res: GrantDetailPeriod[] = mapper(resp.data) as never[]; - return { - data: res.map((period: GrantDetailPeriod) => ({ - ...period, - startDate: period.startDate.split('T')[0], - endDate: period.endDate.split('T')[0], - })), - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/period/info') - @response(200, GRANTS_RESPONSE) - grantDetailPeriodInfo(): object { - const grantNumber = _.get(this.req.query, 'grantNumber', null); - const IPnumber = _.get(this.req.query, 'IPnumber', null); - if (!grantNumber && !IPnumber) { - return { - data: [], - message: '"grantId" and "IPnumber" parameters is required.', - }; - } - const mapper = mapTransform(grantPeriodInfoMap); - const financialUrl = `${urls.grantPeriods}/?${grantDetailUtils.periodInfoSelectFields}$filter=${grantDetailUtils.periodGrantNumber} eq '${grantNumber}' and ${grantDetailUtils.periodNumber} eq ${IPnumber}`; - const ratingUrl = `${urls.performancerating}/?${grantDetailUtils.periodInfoRatingSelectFields}${grantDetailUtils.periodInfoRatingPageSize}${grantDetailUtils.periodInfoRatingExpand}${grantDetailUtils.periodInfoRatingSort}$filter=${grantDetailUtils.periodInfoRatingGrantNumber} eq '${grantNumber}' and ${grantDetailUtils.periodInfoRatingPeriodNumber} eq ${IPnumber}${grantDetailUtils.periodInfoRatingExtraFilter}`; - - return axios - .all([axios.get(financialUrl), axios.get(ratingUrl)]) - .then( - axios.spread((...responses) => { - const respData = [ - { - ..._.get( - responses[0].data, - grantDetailUtils.periodInfoDataPath, - {}, - ), - ..._.get( - responses[1].data, - grantDetailUtils.periodInfoDataPath, - {}, - ), - }, - ]; - const res: GrantDetailPeriodInformation[] = mapper( - respData, - ) as never[]; - return { - data: res, - }; - }), - ) - .catch(handleDataApiError); - } - - @get('/grants/radial') - @response(200, GRANTS_RESPONSE) - grantsRadial(): object { - const filterString = getFilterString(this.req.query); - const filterStringPF = getFilterStringPF(this.req.query); - const grantsUrl = `${urls.grantsNoCount}/?${filterString}${GrantsRadialMapping.grantAgreementsSelect}`; - const periodsUrl = `${urls.vgrantPeriods}/?${filterString}${GrantsRadialMapping.implementationPeriodsSelect}`; - const ipRatingUrl = `${urls.performancerating}/?${filterStringPF}${GrantsRadialMapping.ipRatingDefaultExpand}${GrantsRadialMapping.ipRatingDefaultOrderBy}`; - - return axios - .all([ - axios.get(periodsUrl), - axios.get(grantsUrl), - axios.get(ipRatingUrl), - ]) - .then( - axios.spread((...responses) => { - const periodsData = _.get( - responses[0].data, - GrantsRadialMapping.dataPath, - [], - ); - const grantsData = _.get( - responses[1].data, - GrantsRadialMapping.dataPath, - [], - ); - const ipRatingData = _.get( - responses[2].data, - GrantsRadialMapping.dataPath, - [], - ); - const groupedGrants = _.groupBy( - periodsData, - GrantsRadialMapping.name, - ); - const results: any[] = []; - Object.keys(groupedGrants).forEach(grant => { - const items = groupedGrants[grant]; - const fGrant = _.find(grantsData, { - grantAgreementNumber: grant, - }); - results.push({ - title: _.get(fGrant, GrantsRadialMapping.title, ''), - name: _.get(items[0], GrantsRadialMapping.name, ''), - years: [ - parseInt( - _.get(items[0], GrantsRadialMapping.start, '').slice(0, 4), - 10, - ), - parseInt( - _.get(items[0], GrantsRadialMapping.end, '').slice(0, 4), - 10, - ), - ], - value: _.sumBy(items, GrantsRadialMapping.value), - component: _.get(items[0], GrantsRadialMapping.component, ''), - status: _.get(items[0], GrantsRadialMapping.status, ''), - rating: _.get(fGrant, GrantsRadialMapping.rating, 'None'), - implementationPeriods: _.sortBy( - items.map(item => { - const fRatingData = _.find( - ipRatingData, - (ipRatingDataItem: any) => { - return ( - _.get( - ipRatingDataItem, - GrantsRadialMapping.ipRatingGrantNumber, - null, - ) === _.get(items[0], GrantsRadialMapping.name, '') && - _.get( - ipRatingDataItem, - GrantsRadialMapping.ipRatingPeriodNumber, - null, - ) === _.get(item, GrantsRadialMapping.ipNumber, '') && - _.get( - ipRatingDataItem, - GrantsRadialMapping.ipRatingValue, - null, - ) !== null - ); - }, - ); - return { - name: _.get(item, GrantsRadialMapping.ipNumber, ''), - years: [ - parseInt( - _.get(item, GrantsRadialMapping.ipStart, '').slice( - 0, - 4, - ), - 10, - ), - parseInt( - _.get(item, GrantsRadialMapping.ipEnd, '').slice(0, 4), - 10, - ), - ], - value: _.get(item, GrantsRadialMapping.value, ''), - status: _.get(item, GrantsRadialMapping.ipStatus, ''), - rating: _.get( - fRatingData, - GrantsRadialMapping.ipRatingValue, - 'None', - ), - }; - }), - 'name', - ), - }); - }); - return { - data: results, - }; - }), - ) - .catch(handleDataApiError); - } - - @get('/grant-cycles') - @response(200) - grantCycles(): object { - return axios - .get(urls.grantCycles) - .then(resp => { - return { - data: resp.data.value - .map((item: any) => item.grantCycleCoveragePeriod) - .reverse(), - }; - }) - .catch(handleDataApiError); - } - - @get('/grant/goals-objectives') - @response(200) - grantGoalsObjectives(): object { - const grantNumber = _.get(this.req.query, 'grantNumber', null); - const IPnumber = _.get(this.req.query, 'IPnumber', null); - if (!grantNumber && !IPnumber) { - return { - data: [], - message: '"grantId" and "IPnumber" parameters is required.', - }; - } - const mapper = mapTransform(grantPeriodGoalsObjectivesMap); - const url = `${urls.grantIPGoalsObjectives}/?$filter=${grantDetailUtils.periodInfoRatingGrantNumber} eq '${grantNumber}' AND ${grantDetailUtils.periodInfoRatingPeriodNumber} eq ${IPnumber}&${grantDetailUtils.goalsObjectivesSort}`; - - return axios - .get(url) - .then(resp => { - const res = mapper(resp.data) as never[]; - return { - data: res, - }; - }) - .catch(handleDataApiError); - } } diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 9f3e68f..99e7ec1 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -1,6 +1,5 @@ export * from './allocations.controller'; export * from './budgets.controller'; -export * from './data-themes/raw-data.controller'; export * from './disbursements.controller'; export * from './documents.controller'; export * from './eligibility.controller'; @@ -10,9 +9,6 @@ export * from './fundingrequests.controller'; export * from './global-search.controller'; export * from './grants.controller'; export * from './location.controller'; -export * from './partner.controller'; -export * from './performanceframework.controller'; -export * from './performancerating.controller'; export * from './ping.controller'; export * from './pledgescontributions.controller'; export * from './results.controller'; diff --git a/src/controllers/location.controller.ts b/src/controllers/location.controller.ts index 7923544..93d6f4a 100644 --- a/src/controllers/location.controller.ts +++ b/src/controllers/location.controller.ts @@ -1,43 +1,19 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios from 'axios'; import _ from 'lodash'; import CoordinatingMehanismContactsMapping from '../config/mapping/location/coordinating-mechanism-contacts.json'; import GeographiesMapping from '../config/mapping/location/geographies.json'; -import locationMappingFields from '../config/mapping/location/index.json'; import LocationInfoMapping from '../config/mapping/location/info.json'; import PieChartsMapping from '../config/mapping/location/pieCharts.json'; import urls from '../config/urls/index.json'; import {GeographyModel} from '../interfaces/geographies'; import CountryDescriptions from '../static-assets/country-descriptions.json'; import {handleDataApiError} from '../utils/dataApiError'; -import {getFilterString} from '../utils/filtering/grants/getFilterString'; - -const LOCATION_INFO_RESPONSE: ResponseObject = { - description: 'Location Information Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'LocationInfoResponse', - properties: {}, - }, - }, - }, -}; export class LocationController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/location/{code}/grants/pie-charts') @response(200) async locationGrantsPieCharts(@param.path.string('code') code: string) { @@ -512,299 +488,4 @@ export class LocationController { }) .catch(handleDataApiError); } - - // v2 - - @get('/location/detail') - @response(200, LOCATION_INFO_RESPONSE) - locationDetail(): object { - const locations = _.get(this.req.query, 'locations', '') as string; - if (locations.length === 0) { - return { - data: {}, - message: '"locations" parameter is required.', - }; - } - const location = locations.split(',')[0]; - const multicountriesUrl = `${urls.multicountries}/?${locationMappingFields[ - location.length > 3 - ? 'countriesFilterString' - : 'multiCountriesFilterString' - ].replace('', location as string)}`; - const filterString = getFilterString( - this.req.query, - locationMappingFields.locationFinancialAggregation, - ); - const financialUrl = `${urls.grantsNoCount}/?${filterString}`; - const prevPrincipalRecipientsFilterString = getFilterString( - { - ...this.req.query, - status: locationMappingFields.prevPrincipalRecipientStatusFilter, - }, - locationMappingFields.principalRecipientAggregation, - ); - const currPrincipalRecipientsFilterString = getFilterString( - { - ...this.req.query, - status: locationMappingFields.currPrincipalRecipientStatusFilter, - }, - locationMappingFields.principalRecipientAggregation, - ); - const prevPrincipalRecipientsUrl = `${urls.grantsNoCount}/?${prevPrincipalRecipientsFilterString}`; - const currPrincipalRecipientsUrl = `${urls.grantsNoCount}/?${currPrincipalRecipientsFilterString}`; - const contactsUrl = `${ - urls.countrycontactinfo - }/?${locationMappingFields.contactsFilterString.replace( - //g, - location, - )}&$top=1000`; - - return axios - .all([ - axios.get(multicountriesUrl), - axios.get(financialUrl), - axios.get(prevPrincipalRecipientsUrl), - axios.get(currPrincipalRecipientsUrl), - axios.get(contactsUrl), - ]) - .then( - axios.spread((...responses) => { - const multicountriesResp = _.get( - responses[0].data, - locationMappingFields.multiCountriesDataPath, - [], - ); - const countriesResp = _.get( - responses[0].data, - locationMappingFields.countriesDataPath, - [], - ); - const locationFinancialResp = _.get( - responses[1].data, - locationMappingFields.locationFinancialDataPath, - { - locationName: '', - multiCountryName: '', - disbursed: 0, - committed: 0, - signed: 0, - portfolioManager: '', - portfolioManagerEmail: '', - geoId: '', - multiCountryId: '', - }, - ); - const prevPrincipalRecipientsResp = _.get( - responses[2].data, - locationMappingFields.multiCountriesDataPath, - [], - ); - const currPrincipalRecipientsResp = _.get( - responses[3].data, - locationMappingFields.multiCountriesDataPath, - [], - ); - const contactsResp = _.get( - responses[4].data, - locationMappingFields.contactsDataPath, - [], - ); - - const formattedContactsResp = contactsResp.map((c: any) => ({ - orgName: _.get( - c, - locationMappingFields.contactOrganisationName, - '', - ), - url: _.get(c, locationMappingFields.contactOrganisationUrl, ''), - name: _.get(c, locationMappingFields.contactName, ''), - surname: _.get(c, locationMappingFields.contactSurname, ''), - role: _.get(c, locationMappingFields.contactRole, ''), - salutation: _.get(c, locationMappingFields.contactSalutation, ''), - position: _.get(c, locationMappingFields.contactPosition, ''), - email: _.get(c, locationMappingFields.contactEmail, ''), - })); - - const contacts: any[] = []; - const groupedBy = _.groupBy(formattedContactsResp, 'orgName'); - - Object.keys(groupedBy).forEach((key: string) => { - let url = groupedBy[key][0].url; - if (url && url.indexOf('http') !== 0) { - url = `http://${url}`; - } - contacts.push({ - name: key, - url, - items: groupedBy[key] - .map((item: any) => ({ - name: `${item.salutation} ${item.name} ${( - item.surname ?? '' - ).toUpperCase()}`, - role: item.role, - position: item.position, - email: item.email, - })) - .sort( - (a, b) => - _.get( - locationMappingFields.contactRoleOrder, - `[${a.role}]`, - 0, - ) - - _.get( - locationMappingFields.contactRoleOrder, - `[${b.role}]`, - 0, - ), - ), - }); - }); - - return { - data: [ - { - id: _.get( - locationFinancialResp, - location.length > 3 - ? locationMappingFields.multiCountryId - : locationMappingFields.geoId, - '', - ), - locationName: _.get( - locationFinancialResp, - location.length > 3 - ? locationMappingFields.multiCountryName - : locationMappingFields.locationName, - '', - ), - disbursed: _.get( - locationFinancialResp, - locationMappingFields.disbursed, - 0, - ), - committed: _.get( - locationFinancialResp, - locationMappingFields.committed, - 0, - ), - signed: _.get( - locationFinancialResp, - locationMappingFields.signed, - 0, - ), - portfolioManager: _.get( - locationFinancialResp, - locationMappingFields.portfolioManager, - '', - ), - portfolioManagerEmail: _.get( - locationFinancialResp, - locationMappingFields.portfolioManagerEmail, - '', - ), - multicountries: - location.length > 3 - ? [] - : _.orderBy( - multicountriesResp.map((mc: any) => ({ - name: _.get( - mc, - locationMappingFields.multiCountryName, - '', - ), - code: _.get( - mc, - locationMappingFields.multiCountryName, - '', - ).replace(/\//g, '|'), - })), - 'name', - 'asc', - ), - countries: - location.length > 3 - ? _.orderBy( - countriesResp.map((loc: any) => ({ - name: _.get( - loc, - locationMappingFields.countryName, - '', - ), - code: _.get( - loc, - locationMappingFields.countryCode, - '', - ), - })), - 'name', - 'asc', - ) - : [], - prevPrincipalRecipients: _.filter( - prevPrincipalRecipientsResp.map((pr: any) => { - const fullName = _.get( - pr, - locationMappingFields.principalRecipientName, - '', - ); - const shortName = _.get( - pr, - locationMappingFields.principalRecipientShortName, - '', - ); - const id = _.get( - pr, - locationMappingFields.principalRecipientId, - '', - ); - - return { - code: id, - name: `${fullName}${shortName ? ` (${shortName})` : ''}`, - }; - }), - (pr: any) => - pr.code && - !_.find(currPrincipalRecipientsResp, { - [locationMappingFields.principalRecipientId]: pr.code, - }), - ), - currPrincipalRecipients: _.filter( - currPrincipalRecipientsResp.map((pr: any) => { - const fullName = _.get( - pr, - locationMappingFields.principalRecipientName, - '', - ); - const shortName = _.get( - pr, - locationMappingFields.principalRecipientShortName, - '', - ); - const id = _.get( - pr, - locationMappingFields.principalRecipientId, - '', - ); - - return { - code: id, - name: `${fullName}${shortName ? ` (${shortName})` : ''}`, - }; - }), - (pr: any) => pr.code, - ), - coordinatingMechanismContacts: _.orderBy( - contacts, - 'name', - 'asc', - ), - }, - ], - }; - }), - ) - .catch(handleDataApiError); - } } diff --git a/src/controllers/partner.controller.ts b/src/controllers/partner.controller.ts deleted file mode 100644 index a43258d..0000000 --- a/src/controllers/partner.controller.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {inject} from '@loopback/core'; -import { - get, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; -import axios, {AxiosResponse} from 'axios'; -import _ from 'lodash'; -import partnerMappingFields from '../config/mapping/partner/index.json'; -import urls from '../config/urls/index.json'; -import {handleDataApiError} from '../utils/dataApiError'; -import {getFilterString} from '../utils/filtering/partner/getFilterString'; - -const PARTNER_INFO_RESPONSE: ResponseObject = { - description: 'Partner Information Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'PartnerInfoResponse', - properties: {}, - }, - }, - }, -}; - -export class PartnerController { - constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - - @get('/partner/detail') - @response(200, PARTNER_INFO_RESPONSE) - partnerDetail(): object { - const partners = _.get(this.req.query, 'partners', '') as string; - if (partners.length === 0) { - return { - data: {}, - message: '"partners" parameter is required.', - }; - } - const filterString = getFilterString( - this.req.query, - partnerMappingFields.url, - ); - const url = `${urls.grantsNoCount}/?${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, partnerMappingFields.dataPath, []); - let partnerName = ''; - if (_.get(rawData, partnerMappingFields.partnerShortName, '')) { - partnerName = _.get( - rawData, - partnerMappingFields.partnerShortName, - '', - ); - } - if (_.get(rawData, partnerMappingFields.partnerLongName, '')) { - partnerName += `${partnerName ? ' | ' : ''}${_.get( - rawData, - partnerMappingFields.partnerLongName, - '', - )}`; - } - return { - data: { - partnerName, - }, - }; - }) - .catch(handleDataApiError); - } -} diff --git a/src/controllers/performanceframework.controller.ts b/src/controllers/performanceframework.controller.ts deleted file mode 100644 index 5b43044..0000000 --- a/src/controllers/performanceframework.controller.ts +++ /dev/null @@ -1,327 +0,0 @@ -import {inject} from '@loopback/core'; -import { - get, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; -import axios, {AxiosResponse} from 'axios'; -import _ from 'lodash'; -import {mapTransform} from 'map-transform'; -import moment from 'moment'; -import querystring from 'querystring'; -import filtering from '../config/filtering/index.json'; -import performanceframeworkMappingUtilsExpand from '../config/mapping/performanceframework/expand.json'; -import performanceframeworkExpandMap from '../config/mapping/performanceframework/expandMap.json'; -import performanceframeworkMap from '../config/mapping/performanceframework/index.json'; -import performanceframeworkMappingUtils from '../config/mapping/performanceframework/utils.json'; -import urls from '../config/urls/index.json'; -import { - PFIndicator, - PFIndicatorResult, - PFIndicatorResultDisaggregationGroup, - PFIndicatorResultIntervention, - PFIndicatorResultInterventionValue, -} from '../interfaces/performanceFrameworkNetwork'; -import {handleDataApiError} from '../utils/dataApiError'; -import {getFilterString} from '../utils/filtering/performanceframework/getFilterString'; -import { - formatPFData, - getAchievementRateLegendValues, - getColorBasedOnValue, -} from '../utils/performanceframework/formatPFData'; -import {getTimeframeGroups} from '../utils/performanceframework/getTimeframes'; - -const PERFORMANCE_FRAMEWORK_RESPONSE: ResponseObject = { - description: 'Performance Framework Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'PerformanceFrameworkResponse', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - year: {type: 'string'}, - rating: {type: 'number'}, - }, - }, - }, - }, - }, - }, - }, -}; - -export class PerformanceframeworkController { - constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - - @get('/performance-framework') - @response(200, PERFORMANCE_FRAMEWORK_RESPONSE) - performancerating(): object { - if (!this.req.query.grantId || !this.req.query.IPnumber) { - return { - data: [], - message: '"grantId" and "IPnumber" parameters are required.', - }; - } - const filterString = getFilterString( - this.req.query, - `${performanceframeworkMappingUtils.defaultSelect}${performanceframeworkMappingUtils.defaultExpand}${performanceframeworkMappingUtils.defaultFilter}`, - ); - const mapper = mapTransform(performanceframeworkMap); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.performanceframework}/?${filterString}&${params}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const mappedData = mapper(resp.data) as never[]; - if (mappedData.length === 0) { - return { - data: { - nodes: [], - links: [], - }, - periods: [], - timeframes: [], - }; - } - const timeframes = getTimeframeGroups(mappedData); - - const timeframeIndexParam = this.req.query.timeframeIndex - ? parseInt(this.req.query.timeframeIndex.toString(), 10) - : 0; - let selectedTimeframes = []; - - if ( - timeframeIndexParam < 0 || - timeframeIndexParam > timeframes.length - 1 - ) { - selectedTimeframes = [ - { - raw: timeframes[0].start, - formatted: timeframes[0].startFormatted, - number: new Date(timeframes[0].start).getTime(), - }, - { - raw: timeframes[0].end, - formatted: timeframes[0].endFormatted, - number: new Date(timeframes[0].end).getTime(), - }, - ]; - } else { - selectedTimeframes = [ - { - raw: timeframes[timeframeIndexParam].start, - formatted: timeframes[timeframeIndexParam].startFormatted, - number: new Date(timeframes[timeframeIndexParam].start).getTime(), - }, - { - raw: timeframes[timeframeIndexParam].end, - formatted: timeframes[timeframeIndexParam].endFormatted, - number: new Date(timeframes[timeframeIndexParam].end).getTime(), - }, - ]; - } - - const data = formatPFData(mappedData, selectedTimeframes); - - const periods: string[][] = timeframes.map((group: any) => [ - group.startFormatted, - group.endFormatted, - ]); - - return { - data, - periods, - timeframes, - }; - }) - .catch(handleDataApiError); - } - - @get('/performance-framework/expand') - @response(200, PERFORMANCE_FRAMEWORK_RESPONSE) - performanceratingexpand(): object { - if ( - !this.req.query.indicatorSet || - !this.req.query.moduleName || - !this.req.query.grantId || - !this.req.query.IPnumber - ) { - return { - data: [], - message: 'Invalid parameters', - }; - } - const filterString = getFilterString( - this.req.query, - `${performanceframeworkMappingUtilsExpand.defaultSelect}${performanceframeworkMappingUtilsExpand.defaultExpand}${performanceframeworkMappingUtilsExpand.defaultFilter}`, - ); - const mapper = mapTransform(performanceframeworkExpandMap); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.performanceframework}/?${filterString}&${params}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const mappedData = mapper(resp.data) as never[]; - - const groupedByIndicatorName = _.groupBy(mappedData, 'indicatorName'); - - const indicators: PFIndicator[] = []; - const interventions: PFIndicatorResultIntervention[] = []; - Object.keys(groupedByIndicatorName).forEach((indicatorName: string) => { - const instance = groupedByIndicatorName[indicatorName]; - const groupedByStartDate = _.groupBy(instance, 'startDate'); - const results: PFIndicatorResult[] = []; - const sortedDates = _.filter( - Object.keys(groupedByStartDate), - (date: string) => date !== 'undefined' && date !== 'null', - ) - .sort( - (a: string, b: string) => - moment(a).valueOf() - moment(b).valueOf(), - ) - .reverse(); - sortedDates.forEach((startDate: string) => { - if (startDate !== 'undefined' && startDate !== 'null') { - const dateInstance = groupedByStartDate[startDate]; - - const baselineInstance: any = - _.find(instance, { - valueType: 'Baseline', - }) ?? {}; - const targetInstance: any = - _.find(dateInstance, {valueType: 'Target'}) ?? {}; - const resultInstance: any = - _.find(dateInstance, {valueType: 'Result'}) ?? {}; - - const disaggregations: PFIndicatorResultDisaggregationGroup[] = - baselineInstance.disaggregationGroup - ? [ - { - name: baselineInstance.disaggregationGroup, - values: [ - { - category: baselineInstance.disaggregationValue, - baseline: { - numerator: baselineInstance.valueNumerator, - denominator: baselineInstance.valueDenominator, - percentage: baselineInstance.valuePercentage, - }, - reported: { - numerator: resultInstance.valueNumerator, - denominator: resultInstance.valueDenominator, - percentage: resultInstance.valuePercentage, - }, - }, - ], - }, - ] - : []; - const achievementRate = - this.req.query.moduleName === 'Process indicator / WPTM' - ? resultInstance.valueNumerator || - targetInstance.valueNumerator - : resultInstance.valueAchievementRate || - targetInstance.valueAchievementRate; - - results.push({ - type: 'Percentage', - baseline: !_.isEmpty(baselineInstance) - ? baselineInstance.valuePercentage - ? `${baselineInstance.valuePercentage}%` - : null - : null, - target: !_.isEmpty(targetInstance) - ? targetInstance.valuePercentage - ? `${targetInstance.valuePercentage}%` - : null - : null, - result: !_.isEmpty(resultInstance) - ? resultInstance.valuePercentage - ? `${resultInstance.valuePercentage}%` - : null - : null, - achievementRate, - color: getColorBasedOnValue( - achievementRate, - getAchievementRateLegendValues(), - resultInstance.isIndicatorReversed || - targetInstance.isIndicatorReversed, - this.req.query.moduleName === 'Process indicator / WPTM', - resultInstance, - ), - period: `${( - resultInstance.startDate || targetInstance.startDate - ).slice(0, 10)}:${( - resultInstance.endDate || targetInstance.endDate - ).slice(0, 10)}`, - isReversed: - resultInstance.isIndicatorReversed || - targetInstance.isIndicatorReversed - ? 'Yes' - : 'No', - aggregationType: - resultInstance.indicatorAggregationTypeName || - targetInstance.indicatorAggregationTypeName, - coverage: - resultInstance.geographicCoverage || - targetInstance.geographicCoverage, - disaggregations, - }); - } - }); - indicators.push({ - name: indicatorName, - results, - }); - const instanceInterventions = _.filter( - instance, - (item: any) => item.intervention && item.valueText, - ); - if (instanceInterventions.length > 0) { - interventions.push({ - name: indicatorName, - values: _.uniqBy( - instanceInterventions.map((item: any) => ({ - name: item.intervention, - achievementRate: item.valueAchievementRate, - valueText: item.valueText, - })), - (item: PFIndicatorResultInterventionValue) => - `${item.name}-${item.achievementRate}-${item.valueText}`, - ), - }); - } - }); - - return { - indicators, - interventions, - }; - }) - .catch(handleDataApiError); - } -} diff --git a/src/controllers/performancerating.controller.ts b/src/controllers/performancerating.controller.ts deleted file mode 100644 index 659a3ab..0000000 --- a/src/controllers/performancerating.controller.ts +++ /dev/null @@ -1,97 +0,0 @@ -import {inject} from '@loopback/core'; -import { - get, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; -import axios, {AxiosResponse} from 'axios'; -import _ from 'lodash'; -import querystring from 'querystring'; -import filtering from '../config/filtering/index.json'; -import performanceratingMapping from '../config/mapping/performancerating/index.json'; -import urls from '../config/urls/index.json'; -import {handleDataApiError} from '../utils/dataApiError'; - -const PERFORMANCE_RATING_RESPONSE: ResponseObject = { - description: 'Performance Rating Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'PerformanceRatingResponse', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - year: {type: 'string'}, - rating: {type: 'number'}, - }, - }, - }, - }, - }, - }, - }, -}; - -export class PerformanceratingController { - constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - - @get('/performance-rating') - @response(200, PERFORMANCE_RATING_RESPONSE) - performancerating(): object { - if (!this.req.query.grantId && !this.req.query.IPnumber) { - return { - data: [], - message: '"grantId" and "IPnumber" parameters is required.', - }; - } - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.performancerating}/?${performanceratingMapping.defaultSelect}${performanceratingMapping.defaultOrderBy}${performanceratingMapping.defaultExpand}&$filter=performanceRating/performanceRatingCode ne null and grantAgreementImplementationPeriod/grantAgreement/grantAgreementNumber eq ${this.req.query.grantId} and grantAgreementImplementationPeriod/implementationPeriodNumber eq ${this.req.query.IPnumber}&${filtering.default_q_param}${params}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, performanceratingMapping.dataPath, []); - const data: Record[] = []; - - rawData.forEach((item: any) => { - const dates = [ - new Date(_.get(item, performanceratingMapping.startDate, null)), - new Date(_.get(item, performanceratingMapping.endDate, null)), - ]; - data.push({ - year: `${dates[0].toLocaleString('default', { - month: 'short', - })} ${dates[0].getUTCFullYear()} - ${dates[1].toLocaleString( - 'default', - {month: 'short'}, - )} ${dates[1].getUTCFullYear()}`, - rating: _.get( - performanceratingMapping.ratingValues, - _.get(item, performanceratingMapping.rating, 'N/A'), - 0, - ), - }); - }); - - return { - count: data.length, - data, - }; - }) - .catch(handleDataApiError); - } -} diff --git a/src/controllers/pledgescontributions.controller.ts b/src/controllers/pledgescontributions.controller.ts index 35d82a1..9905612 100644 --- a/src/controllers/pledgescontributions.controller.ts +++ b/src/controllers/pledgescontributions.controller.ts @@ -1,57 +1,14 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; -import _, {orderBy} from 'lodash'; -import querystring from 'querystring'; -import filtering from '../config/filtering/index.json'; +import _ from 'lodash'; import PledgesContributionsBarFieldsMapping from '../config/mapping/pledgescontributions/bar.json'; import PledgesContributionsCyclesFieldsMapping from '../config/mapping/pledgescontributions/cycles.json'; -import PledgesContributionsGeoFieldsMapping from '../config/mapping/pledgescontributions/geo.json'; import PledgesContributionsStatsFieldsMapping from '../config/mapping/pledgescontributions/stats.json'; import PledgesContributionsSunburstFieldsMapping from '../config/mapping/pledgescontributions/sunburst.json'; -import PledgesContributionsTimeCycleFieldsMapping from '../config/mapping/pledgescontributions/timeCycle.json'; -import PledgesContributionsTimeCycleDrilldownFieldsMapping from '../config/mapping/pledgescontributions/timeCycleDrilldown.json'; import urls from '../config/urls/index.json'; -import {BudgetsTreemapDataItem} from '../interfaces/budgetsTreemap'; -import {FilterGroupOption} from '../interfaces/filters'; -import {PledgesContributionsTreemapDataItem} from '../interfaces/pledgesContributions'; import {handleDataApiError} from '../utils/dataApiError'; import {filterFinancialIndicators} from '../utils/filtering/financialIndicators'; -import {getFilterString} from '../utils/filtering/pledges-contributions/getFilterString'; -import {formatFinancialValue} from '../utils/formatFinancialValue'; -import {getD2HCoordinates} from '../utils/pledgescontributions/getD2HCoordinates'; - -const PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE: ResponseObject = { - description: 'Pledges and Contributions time-cycle Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'PledgesContributionsTimeCycleResponse', - properties: { - count: {type: 'number'}, - data: { - type: 'array', - properties: { - year: {type: 'string'}, - pledge: {type: 'number'}, - contribution: {type: 'number'}, - pledgeColor: {type: 'string'}, - contributionColor: {type: 'string'}, - }, - }, - }, - }, - }, - }, -}; async function getBarData(urls: string[]) { return axios @@ -104,8 +61,6 @@ async function getBarData(urls: string[]) { export class PledgescontributionsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/pledges-contributions/stats') @response(200) async stats() { @@ -637,771 +592,4 @@ export class PledgescontributionsController { }) .catch(handleDataApiError); } - - // v2 - - @get('/pledges-contributions/time-cycle') - @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) - timeCycle(): object { - const filterString = getFilterString( - this.req.query, - PledgesContributionsTimeCycleFieldsMapping.pledgescontributionsTimeCycleAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.pledgescontributions}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get( - resp.data, - PledgesContributionsTimeCycleFieldsMapping.dataPath, - [], - ); - const groupByYear = _.groupBy( - rawData, - PledgesContributionsTimeCycleFieldsMapping.year, - ); - const data: Record[] = []; - - _.orderBy(Object.keys(groupByYear), undefined, 'asc').forEach( - (year: string) => { - const pledge = _.find( - groupByYear[year], - (item: any) => - item[PledgesContributionsTimeCycleFieldsMapping.indicator] === - 'Pledge', - ); - const contribution = _.find( - groupByYear[year], - (item: any) => - item[PledgesContributionsTimeCycleFieldsMapping.indicator] === - 'Contribution', - ); - data.push({ - year, - pledge: _.get( - pledge, - PledgesContributionsTimeCycleFieldsMapping.amount, - 0, - ), - contribution: _.get( - contribution, - PledgesContributionsTimeCycleFieldsMapping.amount, - 0, - ), - pledgeColor: '#BFCFEE', - contributionColor: '#252C34', - }); - }, - ); - - return { - count: data.length, - data, - }; - }) - .catch(handleDataApiError); - } - - @get('/pledges-contributions/geomap') - @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) - geomap(): object { - const filterString = getFilterString( - this.req.query, - PledgesContributionsGeoFieldsMapping.pledgescontributionsGeoMapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const valueType = ( - this.req.query.valueType ?? PledgesContributionsGeoFieldsMapping.pledge - ).toString(); - const url = `${urls.pledgescontributions}/?${params}${filterString}`; - const sortBy = this.req.query.sortBy; - const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; - const sortByDirection: any = - sortBy && sortBy.toString().split(' ').length > 1 - ? sortBy.toString().split(' ')[1].toLowerCase() - : 'asc'; - - let pinsSortByValue = 'geoName'; - let featuresSortByValue = 'properties.name'; - - if (sortByValue === 'value') { - pinsSortByValue = 'amounts[0].value'; - featuresSortByValue = 'properties.data.amounts[0].value'; - } - - return axios - .all([ - axios.get(url), - axios.get( - PledgesContributionsGeoFieldsMapping.inAppDonorsFilterOptionsURL, - ), - axios.get(urls.geojson), - axios.get(PledgesContributionsGeoFieldsMapping.d2hspatialshapesURL), - ]) - .then( - axios.spread((...responses) => { - const geoJSONData = responses[2].data.features; - const donorFilterOptions = responses[1].data.options; - const D2HDonorCoordinateData: any[] = []; - - responses[3].data.value[0].members.forEach((member: any) => { - member.donorSpatialShapes.forEach((shape: any) => { - D2HDonorCoordinateData.push(shape); - }); - }); - - const rawData = _.get( - responses[0].data, - PledgesContributionsGeoFieldsMapping.dataPath, - [], - ); - const donorCountries = _.groupBy( - rawData, - PledgesContributionsGeoFieldsMapping.countryDonors, - ); - const publicSectorCountries: any[] = []; - const nonCountrySectorDonors: any[] = []; - - Object.keys(donorCountries).forEach((iso3: string) => { - if (iso3 !== 'undefined') { - const items = donorCountries[iso3]; - const pledges = _.filter(items, { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.pledge, - }); - const contributions = _.filter(items, { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.contribution, - }); - publicSectorCountries.push({ - code: iso3, - geoName: items[0].donor.geographicArea.geographicAreaName, - id: items[0].donorId, - amounts: [ - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? { - label: 'Pledge', - value: _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ), - } - : { - label: 'Contribution', - value: _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - }, - ], - }); - } else { - const nonPublicDonors = _.groupBy( - donorCountries[iso3], - PledgesContributionsGeoFieldsMapping.nonCountryDonors, - ); - Object.keys(nonPublicDonors).forEach( - (donor: string, index: number) => { - const donorData = nonPublicDonors[donor][0]; - const lat = _.get( - donorData, - PledgesContributionsGeoFieldsMapping.donorLat, - null, - ); - const long = _.get( - donorData, - PledgesContributionsGeoFieldsMapping.donorLong, - null, - ); - if (lat && long) { - const pledges = _.filter(nonPublicDonors[donor], { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.pledge, - }); - const contributions = _.filter(nonPublicDonors[donor], { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.contribution, - }); - let subType = ''; - donorFilterOptions.forEach((option: FilterGroupOption) => { - if (_.find(option.options, {label: donor})) { - subType = option.name; - } - }); - const multiCoordinates = - subType === 'Debt2Health' - ? getD2HCoordinates(donor, D2HDonorCoordinateData) - : null; - nonCountrySectorDonors.push({ - code: donor, - geoName: donor, - id: _.get( - donorData, - PledgesContributionsGeoFieldsMapping.donorId, - ), - latitude: parseFloat( - multiCoordinates ? multiCoordinates[0][0] : lat, - ), - longitude: parseFloat( - multiCoordinates ? multiCoordinates[0][1] : long, - ), - amounts: [ - valueType === - PledgesContributionsGeoFieldsMapping.pledge - ? { - label: 'Pledge', - value: _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ), - } - : { - label: 'Contribution', - value: _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - }, - ], - subType, - d2hCoordinates: multiCoordinates, - intId: index, - }); - } - }, - ); - } - }); - - const maxValue: number = - _.max( - publicSectorCountries.map((d: any) => - _.get(_.find(d.amounts, {label: valueType}), 'value', 0), - ), - ) ?? 0; - let interval = 0; - if (maxValue) { - interval = maxValue / 13; - } - const intervals: number[] = []; - for (let i = 0; i < 13; i++) { - intervals.push(interval * i); - } - const features = geoJSONData.map((feature: any) => { - const fItem = _.find(publicSectorCountries, { - code: feature.id, - }); - let itemValue = 0; - if (fItem) { - const fItemValue = _.get( - _.find(fItem.amounts, {label: valueType}), - 'value', - 0, - ); - if ( - (fItemValue < maxValue || fItemValue === maxValue) && - (fItemValue >= intervals[11] || fItemValue === intervals[11]) - ) { - itemValue = 12; - } - if ( - (fItemValue < intervals[11] || fItemValue === intervals[11]) && - (fItemValue >= intervals[10] || fItemValue === intervals[10]) - ) { - itemValue = 11; - } - if ( - (fItemValue < intervals[10] || fItemValue === intervals[10]) && - (fItemValue >= intervals[9] || fItemValue === intervals[9]) - ) { - itemValue = 10; - } - if ( - (fItemValue < intervals[9] || fItemValue === intervals[9]) && - (fItemValue >= intervals[8] || fItemValue === intervals[8]) - ) { - itemValue = 9; - } - if ( - (fItemValue < intervals[8] || fItemValue === intervals[8]) && - (fItemValue >= intervals[7] || fItemValue === intervals[7]) - ) { - itemValue = 8; - } - if ( - (fItemValue < intervals[7] || fItemValue === intervals[7]) && - (fItemValue >= intervals[6] || fItemValue === intervals[6]) - ) { - itemValue = 7; - } - if ( - (fItemValue < intervals[6] || fItemValue === intervals[6]) && - (fItemValue >= intervals[5] || fItemValue === intervals[5]) - ) { - itemValue = 6; - } - if ( - (fItemValue < intervals[5] || fItemValue === intervals[5]) && - (fItemValue >= intervals[4] || fItemValue === intervals[4]) - ) { - itemValue = 5; - } - if ( - (fItemValue < intervals[4] || fItemValue === intervals[4]) && - (fItemValue >= intervals[3] || fItemValue === intervals[3]) - ) { - itemValue = 4; - } - if ( - (fItemValue < intervals[3] || fItemValue === intervals[3]) && - (fItemValue >= intervals[2] || fItemValue === intervals[2]) - ) { - itemValue = 3; - } - if ( - (fItemValue < intervals[2] || fItemValue === intervals[2]) && - (fItemValue >= intervals[1] || fItemValue === intervals[1]) - ) { - itemValue = 2; - } - if ( - (fItemValue < intervals[1] || fItemValue === intervals[1]) && - (fItemValue >= intervals[0] || fItemValue === intervals[0]) - ) { - itemValue = 1; - } - } - return { - ...feature, - properties: { - ...feature.properties, - iso_a3: feature.id, - value: itemValue, - data: fItem ? fItem : {}, - }, - }; - }); - - return { - maxValue, - layers: orderBy(features, featuresSortByValue, sortByDirection), - pins: orderBy( - nonCountrySectorDonors, - pinsSortByValue, - sortByDirection, - ), - }; - }), - ) - .catch(handleDataApiError); - } - - @get('/pledges-contributions/time-cycle/drilldown') - @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) - timeCycleDrilldown(): object { - const valueType = - (_.get(this.req.query, 'levelParam', '') as string) - .split('-') - .indexOf('pledge') > -1 - ? 'pledge' - : 'contribution'; - const filterString = getFilterString( - this.req.query, - PledgesContributionsTimeCycleDrilldownFieldsMapping.aggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const url = `${urls.pledgescontributions}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.filter( - _.get( - resp.data, - PledgesContributionsTimeCycleDrilldownFieldsMapping.dataPath, - [], - ), - { - [PledgesContributionsTimeCycleDrilldownFieldsMapping.indicator]: - PledgesContributionsTimeCycleDrilldownFieldsMapping[valueType], - }, - ); - const levelComponent = _.get( - rawData, - `[0].${PledgesContributionsTimeCycleDrilldownFieldsMapping.year}`, - '', - ); - const value = _.sumBy( - rawData, - PledgesContributionsTimeCycleDrilldownFieldsMapping.amount, - ); - const data: PledgesContributionsTreemapDataItem[] = [ - { - name: levelComponent, - value, - formattedValue: formatFinancialValue(value), - color: '#DFE3E5', - _children: _.orderBy( - rawData.map((item: any) => ({ - name: _.get( - item, - PledgesContributionsTimeCycleDrilldownFieldsMapping.donor, - '', - ), - value: _.get( - item, - PledgesContributionsTimeCycleDrilldownFieldsMapping.amount, - 0, - ), - formattedValue: formatFinancialValue( - _.get( - item, - PledgesContributionsTimeCycleDrilldownFieldsMapping.amount, - 0, - ), - ), - color: '#595C70', - tooltip: { - header: levelComponent, - componentsStats: [ - { - name: _.get( - item, - PledgesContributionsTimeCycleDrilldownFieldsMapping.donor, - '', - ), - value: _.get( - item, - PledgesContributionsTimeCycleDrilldownFieldsMapping.amount, - 0, - ), - }, - ], - value: _.get( - item, - PledgesContributionsTimeCycleDrilldownFieldsMapping.amount, - 0, - ), - }, - })), - 'value', - 'desc', - ), - tooltip: { - header: levelComponent, - value, - componentsStats: [ - { - name: levelComponent, - value, - }, - ], - }, - }, - ]; - return { - data, - }; - }) - .catch(handleDataApiError); - } - - @get('/pledges-contributions/treemap') - @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) - treemap(): object { - const filterString = getFilterString( - this.req.query, - PledgesContributionsGeoFieldsMapping.pledgescontributionsGeoMapAggregation, - ); - const params = querystring.stringify( - {}, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); - const valueType = ( - this.req.query.valueType ?? PledgesContributionsGeoFieldsMapping.pledge - ).toString(); - const url = `${urls.pledgescontributions}/?${params}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get( - resp.data, - PledgesContributionsGeoFieldsMapping.dataPath, - [], - ); - - const donorCountries = _.groupBy( - rawData, - PledgesContributionsGeoFieldsMapping.countryDonors, - ); - const publicSectorCountries: BudgetsTreemapDataItem[] = []; - const nonCountrySectorDonors: BudgetsTreemapDataItem[] = []; - - Object.keys(donorCountries).forEach((iso3: string) => { - if (iso3 !== 'undefined') { - const items = donorCountries[iso3]; - const pledges = _.filter(items, { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.pledge, - }); - const contributions = _.filter(items, { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.contribution, - }); - publicSectorCountries.push({ - // code: items[0].donorId, - name: items[0].donor.geographicArea.geographicAreaName, - value: - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - formattedValue: formatFinancialValue( - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - ), - color: '#DFE3E5', - tooltip: { - header: items[0].donor.geographicArea.geographicAreaName, - componentsStats: [ - { - name: valueType, - value: - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - }, - ], - value: - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - }, - }); - } else { - const nonPublicDonors = _.groupBy( - donorCountries[iso3], - PledgesContributionsGeoFieldsMapping.nonCountryDonors, - ); - Object.keys(nonPublicDonors).forEach((donor: string) => { - // const donorData = nonPublicDonors[donor][0]; - - const pledges = _.filter(nonPublicDonors[donor], { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.pledge, - }); - const contributions = _.filter(nonPublicDonors[donor], { - [PledgesContributionsGeoFieldsMapping.indicator]: - PledgesContributionsGeoFieldsMapping.contribution, - }); - nonCountrySectorDonors.push({ - // code: _.get( - // donorData, - // PledgesContributionsGeoFieldsMapping.donorId, - // ), - name: donor, - value: - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - formattedValue: formatFinancialValue( - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - ), - color: '#DFE3E5', - tooltip: { - header: donor, - componentsStats: [ - { - name: valueType, - value: - valueType === - PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - }, - ], - value: - valueType === PledgesContributionsGeoFieldsMapping.pledge - ? _.sumBy( - pledges, - PledgesContributionsGeoFieldsMapping.amount, - ) - : _.sumBy( - contributions, - PledgesContributionsGeoFieldsMapping.amount, - ), - }, - }); - }); - } - }); - - const data = [...publicSectorCountries, ...nonCountrySectorDonors]; - - return { - count: data.length, - data: _.orderBy(data, 'value', 'desc'), - }; - }) - .catch(handleDataApiError); - } - - // @get('/pledges-contributions/table') - // @response(200, PLEDGES_AND_CONTRIBUTIONS_TIME_CYCLE_RESPONSE) - // table(): object { - // const aggregation = - // PledgesContributionsTableFieldsMapping.aggregations[ - // this.req.query.aggregateBy === - // PledgesContributionsTableFieldsMapping.aggregations[0].key - // ? 0 - // : 1 - // ].value; - // const aggregationKey = - // this.req.query.aggregateBy === - // PledgesContributionsTableFieldsMapping.aggregations[1].key - // ? PledgesContributionsTableFieldsMapping.aggregations[1].key - // : PledgesContributionsTableFieldsMapping.aggregations[0].key; - // const filterString = getFilterString(this.req.query, aggregation); - // const params = querystring.stringify( - // {}, - // '&', - // filtering.param_assign_operator, - // { - // encodeURIComponent: (str: string) => str, - // }, - // ); - // const url = `${urls.pledgescontributions}/?${params}${filterString}`; - // const sortBy = this.req.query.sortBy; - // const sortByValue = sortBy ? sortBy.toString().split(' ')[0] : 'name'; - // const sortByDirection: any = - // sortBy && sortBy.toString().split(' ').length > 1 - // ? sortBy.toString().split(' ')[1].toLowerCase() - // : 'asc'; - - // return axios - // .get(url) - // .then((resp: AxiosResponse) => { - // const rawData = _.get( - // resp.data, - // PledgesContributionsTableFieldsMapping.dataPath, - // [], - // ); - // const groupedByData = _.groupBy( - // rawData, - // _.get( - // PledgesContributionsTableFieldsMapping, - // aggregationKey, - // PledgesContributionsTableFieldsMapping.Donor, - // ), - // ); - // const data: SimpleTableRow[] = []; - // Object.keys(groupedByData).forEach(key => { - // data.push({ - // name: key, - // Pledges: _.sumBy( - // _.filter( - // groupedByData[key], - // dataItem => - // _.get( - // dataItem, - // PledgesContributionsTableFieldsMapping.indicator, - // '', - // ) === PledgesContributionsTableFieldsMapping.pledge, - // ), - // PledgesContributionsTableFieldsMapping.amount, - // ), - // Contributions: _.sumBy( - // _.filter( - // groupedByData[key], - // dataItem => - // _.get( - // dataItem, - // PledgesContributionsTableFieldsMapping.indicator, - // '', - // ) === PledgesContributionsTableFieldsMapping.contribution, - // ), - // PledgesContributionsTableFieldsMapping.amount, - // ), - // }); - // }); - - // return { - // count: data.length, - // data: _.orderBy(data, sortByValue, sortByDirection), - // }; - // }) - // .catch(handleDataApiError); - // } } diff --git a/src/controllers/results.controller.ts b/src/controllers/results.controller.ts index 4ea10af..d427fac 100644 --- a/src/controllers/results.controller.ts +++ b/src/controllers/results.controller.ts @@ -1,100 +1,19 @@ import {inject} from '@loopback/core'; -import { - get, - param, - Request, - response, - ResponseObject, - RestBindings, -} from '@loopback/rest'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; -import {mapTransform} from 'map-transform'; import ResultsCyclesMappingFields from '../config/mapping/results/cycles.json'; -import resultsMap from '../config/mapping/results/index.json'; import ResultsTableLocationMappingFields from '../config/mapping/results/location-table.json'; import ResultsPolylineMappingFields from '../config/mapping/results/polyline.json'; import ResultsStatsMappingFields from '../config/mapping/results/stats-home.json'; -import resultStatsMap from '../config/mapping/results/stats.json'; import ResultsTableMappingFields from '../config/mapping/results/table.json'; -import resultsUtils from '../config/mapping/results/utils.json'; -import ResultsYearsMappingFields from '../config/mapping/results/years.json'; import urls from '../config/urls/index.json'; -import {ResultListItemModel} from '../interfaces/resultList'; import {handleDataApiError} from '../utils/dataApiError'; import {filterProgrammaticIndicators} from '../utils/filtering/programmaticIndicators'; -import { - getFilterString, - getFilterStringForStats, -} from '../utils/filtering/results/getFilterString'; - -const RESULTS_RESPONSE: ResponseObject = { - description: 'Results Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'Results Response', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - id: {type: 'string'}, - title: {type: 'string'}, - value: {type: 'number'}, - component: {type: 'string'}, - geoLocations: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'number'}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; -const RESULT_STATS_RESPONSE: ResponseObject = { - description: 'Result Stats Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'ResultStatsResponse', - properties: { - count: {type: 'integer'}, - data: { - type: 'array', - items: { - type: 'object', - properties: { - name: {type: 'string'}, - value: {type: 'number'}, - description: {type: 'string'}, - }, - }, - }, - }, - }, - }, - }, -}; export class ResultsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - // v3 - @get('/results/polyline/{cycle}') @response(200) async polyline(@param.path.string('cycle') cycle: string) { @@ -300,92 +219,4 @@ export class ResultsController { }) .catch(handleDataApiError); } - - // v2 - - @get('/v2/results') - @response(200, RESULTS_RESPONSE) - results(): object { - const mapper = mapTransform(resultsMap); - const filterString = getFilterString(this.req.query); - const url = `${urls.results}/?${resultsUtils.defaultSelect}${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const mappedData = mapper(resp.data) as never[]; - const data: ResultListItemModel[] = []; - // @ts-ignore - const groupedByIndicator = _.groupBy(mappedData, 'title'); - - Object.keys(groupedByIndicator).forEach((indicator: string) => { - data.push({ - id: _.get(groupedByIndicator[indicator], '[0].id', ''), - title: _.get(groupedByIndicator[indicator], '[0].title', ''), - value: _.sumBy(groupedByIndicator[indicator], 'value'), - component: _.get( - groupedByIndicator[indicator], - '[0].component', - '', - ), - geoLocations: _.orderBy( - groupedByIndicator[indicator].map((item: any) => ({ - name: item.country, - value: item.value, - })), - 'name', - 'asc', - ), - }); - }); - - return { - count: data.length, - data, - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/results/years') - @response(200, RESULTS_RESPONSE) - resultYears(): object { - const url = `${urls.results}/?${ResultsYearsMappingFields.aggregation}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - return { - data: _.get(resp.data, ResultsYearsMappingFields.dataPath, []).map( - (item: any) => _.get(item, ResultsYearsMappingFields.year, ''), - ), - }; - }) - .catch(handleDataApiError); - } - - @get('/v2/results-stats') - @response(200, RESULT_STATS_RESPONSE) - resultStats(): object { - const filterString = getFilterStringForStats( - this.req.query, - resultStatsMap.ResultStatsAggregation, - ); - const url = `${urls.results}/?${filterString}`; - - return axios - .get(url) - .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, resultStatsMap.dataPath, []); - return { - count: rawData.length, - data: rawData.map((item: any) => ({ - name: _.get(item, resultStatsMap.name, ''), - value: _.get(item, resultStatsMap.value, ''), - description: _.get(item, resultStatsMap.description, ''), - })), - }; - }) - .catch(handleDataApiError); - } } diff --git a/src/interfaces/allocations.ts b/src/interfaces/allocations.ts deleted file mode 100644 index e057114..0000000 --- a/src/interfaces/allocations.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface Allocations { - total: number; // total investment value - keys: string[]; // component names - colors: string[]; // component colors - values: number[]; // component values -} - -export interface AllocationsTreemapDataItem { - name: string; - value: number; - formattedValue: string; - color: string; - _children?: AllocationsTreemapDataItem[]; - tooltip: { - header: string; - componentsStats: { - name: string; - value: number; - }[]; - value: number; - }; -} diff --git a/src/interfaces/budgetsFlow.ts b/src/interfaces/budgetsFlow.ts deleted file mode 100644 index ecf6663..0000000 --- a/src/interfaces/budgetsFlow.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface BudgetsFlowData { - nodes: { - id: string; - filterStr: string; - components?: { - id: string; - color: string; - value: number; - count: number; - height: number; - }[]; - }[]; - links: { - value: number; - source: string; - target: string; - }[]; -} diff --git a/src/interfaces/budgetsTimeCycle.ts b/src/interfaces/budgetsTimeCycle.ts deleted file mode 100644 index c52619a..0000000 --- a/src/interfaces/budgetsTimeCycle.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface BudgetsTimeCycleData { - data: Record[]; -} diff --git a/src/interfaces/budgetsTreemap.ts b/src/interfaces/budgetsTreemap.ts deleted file mode 100644 index 3357b71..0000000 --- a/src/interfaces/budgetsTreemap.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface BudgetsTreemapDataItem { - name: string; - value: number; - formattedValue: string; - color: string; - _children?: BudgetsTreemapDataItem[]; - tooltip: { - header: string; - componentsStats: { - name: string; - value: number; - }[]; - value: number; - }; -} diff --git a/src/interfaces/disbursementsTimeCycle.ts b/src/interfaces/disbursementsTimeCycle.ts deleted file mode 100644 index 416066c..0000000 --- a/src/interfaces/disbursementsTimeCycle.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface DisbursementsTimeCycleData { - data: Record[]; -} diff --git a/src/interfaces/disbursementsTreemap.ts b/src/interfaces/disbursementsTreemap.ts deleted file mode 100644 index 31995ca..0000000 --- a/src/interfaces/disbursementsTreemap.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface DisbursementsTreemapDataItem { - name: string; - code?: string; - value: number; - formattedValue: string; - color: string; - _children?: DisbursementsTreemapDataItem[]; - tooltip: { - header: string; - componentsStats: { - name: string; - count: number; - investment: number; - }[]; - totalInvestments: { - committed: number; - disbursed: number; - signed: number; - }; - percValue: string; - }; -} diff --git a/src/interfaces/documentsTable.ts b/src/interfaces/documentsTable.ts deleted file mode 100644 index 1b3ccde..0000000 --- a/src/interfaces/documentsTable.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface DocumentsTableRow { - name: string; - link?: string; - count?: number; - docCategories?: DocumentsTableRow[]; - docs?: { - title: string; - link: string; - }[]; -} diff --git a/src/interfaces/eligibilityDot.ts b/src/interfaces/eligibilityDot.ts deleted file mode 100644 index 734c652..0000000 --- a/src/interfaces/eligibilityDot.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface EligibilityDotDataItem { - name: string; - items: { - name: string; - status: 'Eligible' | 'Not Eligible' | 'Transition Funding'; - }[]; -} diff --git a/src/interfaces/eligibilityScatterplot.ts b/src/interfaces/eligibilityScatterplot.ts deleted file mode 100644 index 79c6ace..0000000 --- a/src/interfaces/eligibilityScatterplot.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface EligibilityScatterplotDataItem { - x: number; - y: string; - incomeLevel: number; - diseaseBurden: number; - allocationCycleName?: string | null; - eligibility: 'Eligible' | 'Not Eligible' | 'Transition Funding'; - invisible?: boolean; -} diff --git a/src/interfaces/grantDetail.ts b/src/interfaces/grantDetail.ts deleted file mode 100644 index c4824a8..0000000 --- a/src/interfaces/grantDetail.ts +++ /dev/null @@ -1,36 +0,0 @@ -export interface GrantDetailInformation { - title: string; - code: string; - rating: string; - status: string; - location: string; - component: string; - description: string; - investments: { - disbursed: number; - committed: number; - signed: number; - }; - manager: { - name: string; - email: string; - }; - principalRecipient: { - code: string; - name: string; - shortName: string; - }; -} - -export interface GrantDetailPeriod { - number: number; - startDate: string; - endDate: string; -} - -export interface GrantDetailPeriodInformation { - disbursed: number; - committed: number; - signed: number; - rating: string; -} diff --git a/src/interfaces/grantList.ts b/src/interfaces/grantList.ts index fc871ef..e26e26b 100644 --- a/src/interfaces/grantList.ts +++ b/src/interfaces/grantList.ts @@ -1,17 +1,3 @@ -export interface GrantListItemModelV2 { - id: string; - title: string; - status: string; - component: string; - geoLocation: string; - rating: string | null; - disbursed: number; - committed: number; - signed: number; - recipientName: string; - recipientShortName: string; -} - export interface GrantListItemModel { code: string; title: string; diff --git a/src/interfaces/performanceFrameworkNetwork.ts b/src/interfaces/performanceFrameworkNetwork.ts deleted file mode 100644 index b54b528..0000000 --- a/src/interfaces/performanceFrameworkNetwork.ts +++ /dev/null @@ -1,69 +0,0 @@ -// import {InputLink, InputNode} from '@nivo/network'; - -// export interface PerformanceFrameworkNetworkData { -// nodes: InputNode[]; -// links: InputLink[]; -// } - -export interface PFIndicatorResultInterventionValue { - name: string; - achievementRate: number | null; - valueText: string; -} - -export interface PFIndicatorResultIntervention { - name: string; - values: PFIndicatorResultInterventionValue[]; -} - -export interface PFIndicatorResultDisaggregationValue { - numerator: number | null; - denominator: number | null; - percentage: number | null; -} - -export interface PFIndicatorResultDisaggregation { - category: string; - baseline: PFIndicatorResultDisaggregationValue; - reported: PFIndicatorResultDisaggregationValue; -} - -export interface PFIndicatorResultDisaggregationGroup { - name: string; - values: PFIndicatorResultDisaggregation[]; -} - -export interface PFIndicatorResult { - type: string; - baseline: string | number | null; - target: string | number | null; - result: string | number | null; - achievementRate: number | string | null; - color: string; - period: string; - isReversed: string; - aggregationType: string; - coverage: string; - disaggregations: PFIndicatorResultDisaggregationGroup[]; -} - -export interface PFIndicator { - name: string; - results: PFIndicatorResult[]; -} - -export interface PFModule { - name: string; - indicators: PFIndicator[]; - interventions: PFIndicatorResultIntervention[]; -} - -export interface PFIndicatorSet { - name: string; - modules: PFModule[]; -} - -export interface PerformanceFrameworkExpandedViewProps { - indicators: PFIndicator[]; - interventions: PFIndicatorResultIntervention[]; -} diff --git a/src/interfaces/performanceRating.ts b/src/interfaces/performanceRating.ts deleted file mode 100644 index cf111e7..0000000 --- a/src/interfaces/performanceRating.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface PerformanceRatingData { - data: Record[]; -} diff --git a/src/interfaces/pledgesContributions.ts b/src/interfaces/pledgesContributions.ts deleted file mode 100644 index 47327e0..0000000 --- a/src/interfaces/pledgesContributions.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface PledgesContributionsData { - data: Record[]; -} - -export interface PledgesContributionsTreemapDataItem { - name: string; - value: number; - formattedValue: string; - color: string; - _children?: PledgesContributionsTreemapDataItem[]; - tooltip: { - header: string; - componentsStats: { - name: string; - value: number; - }[]; - value: number; - }; -} diff --git a/src/interfaces/resultList.ts b/src/interfaces/resultList.ts deleted file mode 100644 index c9147f3..0000000 --- a/src/interfaces/resultList.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface ResultListItemModel { - id: string; - title: string; - value: number; - component: string; - geoLocations: { - name: string; - value: number; - }[]; -} - -export interface ResultsInfoContentStats { - name: string; - value: number; - description: string; -} - -export interface ResultsInfoContent { - description: string; - stats: ResultsInfoContentStats[]; -} diff --git a/src/interfaces/search.ts b/src/interfaces/search.ts deleted file mode 100644 index d66d6f8..0000000 --- a/src/interfaces/search.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface SearchResultItem { - link: string; - label: string; - value: string; - type?: string; -} - -export interface SearchResultsTab { - name: string; - results: SearchResultItem[]; -} diff --git a/src/interfaces/simpleTable.ts b/src/interfaces/simpleTable.ts deleted file mode 100644 index 2131203..0000000 --- a/src/interfaces/simpleTable.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface SimpleTableRow { - [key: string]: any; - children?: SimpleTableRow[]; -} diff --git a/src/static-assets/countries.json b/src/static-assets/countries.json deleted file mode 100644 index 612d4b2..0000000 --- a/src/static-assets/countries.json +++ /dev/null @@ -1,2026 +0,0 @@ -[ - { - "country": "Afghanistan", - "iso2": "AF", - "iso3": "AFG", - "numeric_code": 4, - "latitude": 33, - "longitude": 65 - }, - { - "country": "Albania", - "iso2": "AL", - "iso3": "ALB", - "numeric_code": 8, - "latitude": 41.14244989, - "longitude": 20.04983396 - }, - { - "country": "Algeria", - "iso2": "DZ", - "iso3": "DZA", - "numeric_code": 12, - "latitude": 28, - "longitude": 3 - }, - { - "country": "American Samoa", - "iso2": "AS", - "iso3": "ASM", - "numeric_code": 16, - "latitude": -14.3333, - "longitude": -170 - }, - { - "country": "Andorra", - "iso2": "AD", - "iso3": "AND", - "numeric_code": 20, - "latitude": 42.5, - "longitude": 1.6 - }, - { - "country": "Angola", - "iso2": "AO", - "iso3": "AGO", - "numeric_code": 24, - "latitude": -12.5, - "longitude": 18.5 - }, - { - "country": "Anguilla", - "iso2": "AI", - "iso3": "AIA", - "numeric_code": 660, - "latitude": 18.25, - "longitude": -63.1667 - }, - { - "country": "Antarctica", - "iso2": "AQ", - "iso3": "ATA", - "numeric_code": 10, - "latitude": -90, - "longitude": 0 - }, - { - "country": "Antigua and Barbuda", - "iso2": "AG", - "iso3": "ATG", - "numeric_code": 28, - "latitude": 17.05, - "longitude": -61.8 - }, - { - "country": "Argentina", - "iso2": "AR", - "iso3": "ARG", - "numeric_code": 32, - "latitude": -34, - "longitude": -64 - }, - { - "country": "Armenia", - "iso2": "AM", - "iso3": "ARM", - "numeric_code": 51, - "latitude": 40, - "longitude": 45 - }, - { - "country": "Aruba", - "iso2": "AW", - "iso3": "ABW", - "numeric_code": 533, - "latitude": 12.5, - "longitude": -69.9667 - }, - { - "country": "Australia", - "iso2": "AU", - "iso3": "AUS", - "numeric_code": 36, - "latitude": -27, - "longitude": 133 - }, - { - "country": "Austria", - "iso2": "AT", - "iso3": "AUT", - "numeric_code": 40, - "latitude": 47.3333, - "longitude": 13.3333 - }, - { - "country": "Azerbaijan", - "iso2": "AZ", - "iso3": "AZE", - "numeric_code": 31, - "latitude": 40.5, - "longitude": 47.5 - }, - { - "country": "Bahamas", - "iso2": "BS", - "iso3": "BHS", - "numeric_code": 44, - "latitude": 24.25, - "longitude": -76 - }, - { - "country": "Bahrain", - "iso2": "BH", - "iso3": "BHR", - "numeric_code": 48, - "latitude": 26, - "longitude": 50.55 - }, - { - "country": "Bangladesh", - "iso2": "BD", - "iso3": "BGD", - "numeric_code": 50, - "latitude": 24, - "longitude": 90 - }, - { - "country": "Barbados", - "iso2": "BB", - "iso3": "BRB", - "numeric_code": 52, - "latitude": 13.1667, - "longitude": -59.5333 - }, - { - "country": "Belarus", - "iso2": "BY", - "iso3": "BLR", - "numeric_code": 112, - "latitude": 53, - "longitude": 28 - }, - { - "country": "Belgium", - "iso2": "BE", - "iso3": "BEL", - "numeric_code": 56, - "latitude": 50.8333, - "longitude": 4 - }, - { - "country": "Belize", - "iso2": "BZ", - "iso3": "BLZ", - "numeric_code": 84, - "latitude": 17.25, - "longitude": -88.75 - }, - { - "country": "Benin", - "iso2": "BJ", - "iso3": "BEN", - "numeric_code": 204, - "latitude": 9.5, - "longitude": 2.25 - }, - { - "country": "Bermuda", - "iso2": "BM", - "iso3": "BMU", - "numeric_code": 60, - "latitude": 32.3333, - "longitude": -64.75 - }, - { - "country": "Bhutan", - "iso2": "BT", - "iso3": "BTN", - "numeric_code": 64, - "latitude": 27.5, - "longitude": 90.5 - }, - { - "country": "Bolivia (Plurinational State)", - "iso2": "BO", - "iso3": "BOL", - "numeric_code": 68, - "latitude": -17, - "longitude": -65 - }, - { - "country": "Bosnia and Herzegovina", - "iso2": "BA", - "iso3": "BIH", - "numeric_code": 70, - "latitude": 44, - "longitude": 18 - }, - { - "country": "Botswana", - "iso2": "BW", - "iso3": "BWA", - "numeric_code": 72, - "latitude": -22, - "longitude": 24 - }, - { - "country": "Bouvet Island", - "iso2": "BV", - "iso3": "BVT", - "numeric_code": 74, - "latitude": -54.4333, - "longitude": 3.4 - }, - { - "country": "Brazil", - "iso2": "BR", - "iso3": "BRA", - "numeric_code": 76, - "latitude": -10, - "longitude": -55 - }, - { - "country": "British Indian Ocean Territory", - "iso2": "IO", - "iso3": "IOT", - "numeric_code": 86, - "latitude": -6, - "longitude": 71.5 - }, - { - "country": "Brunei Darussalam", - "iso2": "BN", - "iso3": "BRN", - "numeric_code": 96, - "latitude": 4.5, - "longitude": 114.6667 - }, - { - "country": "Brunei", - "iso2": "BN", - "iso3": "BRN", - "numeric_code": 96, - "latitude": 4.5, - "longitude": 114.6667 - }, - { - "country": "Bulgaria", - "iso2": "BG", - "iso3": "BGR", - "numeric_code": 100, - "latitude": 43, - "longitude": 25 - }, - { - "country": "Burkina Faso", - "iso2": "BF", - "iso3": "BFA", - "numeric_code": 854, - "latitude": 13, - "longitude": -2 - }, - { - "country": "Burundi", - "iso2": "BI", - "iso3": "BDI", - "numeric_code": 108, - "latitude": -3.5, - "longitude": 30 - }, - { - "country": "Cambodia", - "iso2": "KH", - "iso3": "KHM", - "numeric_code": 116, - "latitude": 13, - "longitude": 105 - }, - { - "country": "Cameroon", - "iso2": "CM", - "iso3": "CMR", - "numeric_code": 120, - "latitude": 5.69109849, - "longitude": 12.73964156 - }, - { - "country": "Canada", - "iso2": "CA", - "iso3": "CAN", - "numeric_code": 124, - "latitude": 60, - "longitude": -95 - }, - { - "country": "Cabo Verde", - "iso2": "CV", - "iso3": "CPV", - "numeric_code": 132, - "latitude": 15.066604, - "longitude": -23.606085 - }, - { - "country": "Cayman Islands", - "iso2": "KY", - "iso3": "CYM", - "numeric_code": 136, - "latitude": 19.5, - "longitude": -80.5 - }, - { - "country": "Central African Republic", - "iso2": "CF", - "iso3": "CAF", - "numeric_code": 140, - "latitude": 7, - "longitude": 21 - }, - { - "country": "Chad", - "iso2": "TD", - "iso3": "TCD", - "numeric_code": 148, - "latitude": 15, - "longitude": 19 - }, - { - "country": "Chile", - "iso2": "CL", - "iso3": "CHL", - "numeric_code": 152, - "latitude": -30, - "longitude": -71 - }, - { - "country": "China", - "iso2": "CN", - "iso3": "CHN", - "numeric_code": 156, - "latitude": 35, - "longitude": 105 - }, - { - "country": "Christmas Island", - "iso2": "CX", - "iso3": "CXR", - "numeric_code": 162, - "latitude": -10.5, - "longitude": 105.6667 - }, - { - "country": "Cocos (Keeling) Islands", - "iso2": "CC", - "iso3": "CCK", - "numeric_code": 166, - "latitude": -12.5, - "longitude": 96.8333 - }, - { - "country": "Colombia", - "iso2": "CO", - "iso3": "COL", - "numeric_code": 170, - "latitude": 4, - "longitude": -72 - }, - { - "country": "Comoros", - "iso2": "KM", - "iso3": "COM", - "numeric_code": 174, - "latitude": -11.63934, - "longitude": 43.327497 - }, - { - "country": "Congo", - "iso2": "CG", - "iso3": "COG", - "numeric_code": 178, - "latitude": -0.83787463, - "longitude": 15.21965762 - }, - { - "country": "Congo (Democratic Republic)", - "iso2": "CD", - "iso3": "COD", - "numeric_code": 180, - "latitude": 0, - "longitude": 25 - }, - { - "country": "Cook Islands", - "iso2": "CK", - "iso3": "COK", - "numeric_code": 184, - "latitude": -21.238164, - "longitude": -159.77231 - }, - { - "country": "Costa Rica", - "iso2": "CR", - "iso3": "CRI", - "numeric_code": 188, - "latitude": 10, - "longitude": -84 - }, - { - "country": "Côte d'Ivoire", - "iso2": "CI", - "iso3": "CIV", - "numeric_code": 384, - "latitude": 8, - "longitude": -5 - }, - { - "country": "Croatia", - "iso2": "HR", - "iso3": "HRV", - "numeric_code": 191, - "latitude": 45.1667, - "longitude": 15.5 - }, - { - "country": "Cuba", - "iso2": "CU", - "iso3": "CUB", - "numeric_code": 192, - "latitude": 21.5, - "longitude": -80 - }, - { - "country": "Cyprus", - "iso2": "CY", - "iso3": "CYP", - "numeric_code": 196, - "latitude": 35, - "longitude": 33 - }, - { - "country": "Czech Republic", - "iso2": "CZ", - "iso3": "CZE", - "numeric_code": 203, - "latitude": 49.75, - "longitude": 15.5 - }, - { - "country": "Denmark", - "iso2": "DK", - "iso3": "DNK", - "numeric_code": 208, - "latitude": 56, - "longitude": 10 - }, - { - "country": "Djibouti", - "iso2": "DJ", - "iso3": "DJI", - "numeric_code": 262, - "latitude": 11.898011, - "longitude": 42.754048 - }, - { - "country": "Dominica", - "iso2": "DM", - "iso3": "DMA", - "numeric_code": 212, - "latitude": 15.4167, - "longitude": -61.3333 - }, - { - "country": "Dominican Republic", - "iso2": "DO", - "iso3": "DOM", - "numeric_code": 214, - "latitude": 19, - "longitude": -70.6667 - }, - { - "country": "Ecuador", - "iso2": "EC", - "iso3": "ECU", - "numeric_code": 218, - "latitude": -1.42381612, - "longitude": -78.75201922 - }, - { - "country": "Egypt", - "iso2": "EG", - "iso3": "EGY", - "numeric_code": 818, - "latitude": 27, - "longitude": 30 - }, - { - "country": "El Salvador", - "iso2": "SV", - "iso3": "SLV", - "numeric_code": 222, - "latitude": 13.8333, - "longitude": -88.9167 - }, - { - "country": "Equatorial Guinea", - "iso2": "GQ", - "iso3": "GNQ", - "numeric_code": 226, - "latitude": 1.70555135, - "longitude": 10.34137924 - }, - { - "country": "Eritrea", - "iso2": "ER", - "iso3": "ERI", - "numeric_code": 232, - "latitude": 15.807628, - "longitude": 38.488815 - }, - { - "country": "Estonia", - "iso2": "EE", - "iso3": "EST", - "numeric_code": 233, - "latitude": 59, - "longitude": 26 - }, - { - "country": "Ethiopia", - "iso2": "ET", - "iso3": "ETH", - "numeric_code": 231, - "latitude": 8, - "longitude": 38 - }, - { - "country": "Falkland Islands (Malvinas)", - "iso2": "FK", - "iso3": "FLK", - "numeric_code": 238, - "latitude": -51.75, - "longitude": -59 - }, - { - "country": "Faroe Islands", - "iso2": "FO", - "iso3": "FRO", - "numeric_code": 234, - "latitude": 62, - "longitude": -7 - }, - { - "country": "Fiji", - "iso2": "FJ", - "iso3": "FJI", - "numeric_code": 242, - "latitude": -17.783501, - "longitude": 178.158457 - }, - { - "country": "Finland", - "iso2": "FI", - "iso3": "FIN", - "numeric_code": 246, - "latitude": 64, - "longitude": 26 - }, - { - "country": "France", - "iso2": "FR", - "iso3": "FRA", - "numeric_code": 250, - "latitude": 46, - "longitude": 2 - }, - { - "country": "French Guiana", - "iso2": "GF", - "iso3": "GUF", - "numeric_code": 254, - "latitude": 4, - "longitude": -53 - }, - { - "country": "French Polynesia", - "iso2": "PF", - "iso3": "PYF", - "numeric_code": 258, - "latitude": -15, - "longitude": -140 - }, - { - "country": "French Southern Territories", - "iso2": "TF", - "iso3": "ATF", - "numeric_code": 260, - "latitude": -43, - "longitude": 67 - }, - { - "country": "Gabon", - "iso2": "GA", - "iso3": "GAB", - "numeric_code": 266, - "latitude": -1, - "longitude": 11.75 - }, - { - "country": "Gambia", - "iso2": "GM", - "iso3": "GMB", - "numeric_code": 270, - "latitude": 13.4667, - "longitude": -16.5667 - }, - { - "country": "Georgia", - "iso2": "GE", - "iso3": "GEO", - "numeric_code": 268, - "latitude": 42, - "longitude": 43.5 - }, - { - "country": "Germany", - "iso2": "DE", - "iso3": "DEU", - "numeric_code": 276, - "latitude": 51, - "longitude": 9 - }, - { - "country": "Ghana", - "iso2": "GH", - "iso3": "GHA", - "numeric_code": 288, - "latitude": 7.95345644, - "longitude": -1.21676566 - }, - { - "country": "Gibraltar", - "iso2": "GI", - "iso3": "GIB", - "numeric_code": 292, - "latitude": 36.1833, - "longitude": -5.3667 - }, - { - "country": "Greece", - "iso2": "GR", - "iso3": "GRC", - "numeric_code": 300, - "latitude": 39, - "longitude": 22 - }, - { - "country": "Greenland", - "iso2": "GL", - "iso3": "GRL", - "numeric_code": 304, - "latitude": 72, - "longitude": -40 - }, - { - "country": "Grenada", - "iso2": "GD", - "iso3": "GRD", - "numeric_code": 308, - "latitude": 12.1167, - "longitude": -61.6667 - }, - { - "country": "Guadeloupe", - "iso2": "GP", - "iso3": "GLP", - "numeric_code": 312, - "latitude": 16.25, - "longitude": -61.5833 - }, - { - "country": "Guam", - "iso2": "GU", - "iso3": "GUM", - "numeric_code": 316, - "latitude": 13.4667, - "longitude": 144.7833 - }, - { - "country": "Guatemala", - "iso2": "GT", - "iso3": "GTM", - "numeric_code": 320, - "latitude": 15.5, - "longitude": -90.25 - }, - { - "country": "Guernsey", - "iso2": "GG", - "iso3": "GGY", - "numeric_code": 831, - "latitude": 49.5, - "longitude": -2.56 - }, - { - "country": "Guinea", - "iso2": "GN", - "iso3": "GIN", - "numeric_code": 324, - "latitude": 11, - "longitude": -10 - }, - { - "country": "Guinea-Bissau", - "iso2": "GW", - "iso3": "GNB", - "numeric_code": 624, - "latitude": 12, - "longitude": -15 - }, - { - "country": "Guyana", - "iso2": "GY", - "iso3": "GUY", - "numeric_code": 328, - "latitude": 5, - "longitude": -59 - }, - { - "country": "Haiti", - "iso2": "HT", - "iso3": "HTI", - "numeric_code": 332, - "latitude": 19, - "longitude": -72.4167 - }, - { - "country": "Heard Island and McDonald Islands", - "iso2": "HM", - "iso3": "HMD", - "numeric_code": 334, - "latitude": -53.1, - "longitude": 72.5167 - }, - { - "country": "Holy See (Vatican City State)", - "iso2": "VA", - "iso3": "VAT", - "numeric_code": 336, - "latitude": 41.9, - "longitude": 12.45 - }, - { - "country": "Honduras", - "iso2": "HN", - "iso3": "HND", - "numeric_code": 340, - "latitude": 15, - "longitude": -86.5 - }, - { - "country": "Hong Kong", - "iso2": "HK", - "iso3": "HKG", - "numeric_code": 344, - "latitude": 22.25, - "longitude": 114.1667 - }, - { - "country": "Hungary", - "iso2": "HU", - "iso3": "HUN", - "numeric_code": 348, - "latitude": 47, - "longitude": 20 - }, - { - "country": "Iceland", - "iso2": "IS", - "iso3": "ISL", - "numeric_code": 352, - "latitude": 65, - "longitude": -18 - }, - { - "country": "India", - "iso2": "IN", - "iso3": "IND", - "numeric_code": 356, - "latitude": 20, - "longitude": 77 - }, - { - "country": "Indonesia", - "iso2": "ID", - "iso3": "IDN", - "numeric_code": 360, - "latitude": -5, - "longitude": 120 - }, - { - "country": "Iran (Islamic Republic)", - "iso2": "IR", - "iso3": "IRN", - "numeric_code": 364, - "latitude": 32, - "longitude": 53 - }, - { - "country": "Iraq", - "iso2": "IQ", - "iso3": "IRQ", - "numeric_code": 368, - "latitude": 33, - "longitude": 44 - }, - { - "country": "Ireland", - "iso2": "IE", - "iso3": "IRL", - "numeric_code": 372, - "latitude": 53, - "longitude": -8 - }, - { - "country": "Isle of Man", - "iso2": "IM", - "iso3": "IMN", - "numeric_code": 833, - "latitude": 54.23, - "longitude": -4.55 - }, - { - "country": "Israel", - "iso2": "IL", - "iso3": "ISR", - "numeric_code": 376, - "latitude": 31.5, - "longitude": 34.75 - }, - { - "country": "Italy", - "iso2": "IT", - "iso3": "ITA", - "numeric_code": 380, - "latitude": 42.8333, - "longitude": 12.8333 - }, - { - "country": "Jamaica", - "iso2": "JM", - "iso3": "JAM", - "numeric_code": 388, - "latitude": 18.25, - "longitude": -77.5 - }, - { - "country": "Japan", - "iso2": "JP", - "iso3": "JPN", - "numeric_code": 392, - "latitude": 36, - "longitude": 138 - }, - { - "country": "Jersey", - "iso2": "JE", - "iso3": "JEY", - "numeric_code": 832, - "latitude": 49.21, - "longitude": -2.13 - }, - { - "country": "Jordan", - "iso2": "JO", - "iso3": "JOR", - "numeric_code": 400, - "latitude": 30.875089, - "longitude": 36.455614 - }, - { - "country": "Kazakhstan", - "iso2": "KZ", - "iso3": "KAZ", - "numeric_code": 398, - "latitude": 48, - "longitude": 68 - }, - { - "country": "Kenya", - "iso2": "KE", - "iso3": "KEN", - "numeric_code": 404, - "latitude": 1, - "longitude": 38 - }, - { - "country": "Kiribati", - "iso2": "KI", - "iso3": "KIR", - "numeric_code": 296, - "latitude": 1.812568, - "longitude": -157.333235 - }, - { - "country": "Korea (Democratic Peoples Republic)", - "iso2": "KP", - "iso3": "PRK", - "numeric_code": 408, - "latitude": 40, - "longitude": 127 - }, - { - "country": "Korea, Republic of", - "iso2": "KR", - "iso3": "KOR", - "numeric_code": 410, - "latitude": 37, - "longitude": 127.5 - }, - { - "country": "Kosovo", - "iso2": "QN", - "iso3": "QNA", - "numeric_code": 0, - "latitude": 42.620466, - "longitude": 20.833336 - }, - { - "country": "South Korea", - "iso2": "KR", - "iso3": "KOR", - "numeric_code": 410, - "latitude": 37, - "longitude": 127.5 - }, - { - "country": "Kuwait", - "iso2": "KW", - "iso3": "KWT", - "numeric_code": 414, - "latitude": 29.3375, - "longitude": 47.6581 - }, - { - "country": "Kyrgyzstan", - "iso2": "KG", - "iso3": "KGZ", - "numeric_code": 417, - "latitude": 41, - "longitude": 75 - }, - { - "country": "Lao (Peoples Democratic Republic)", - "iso2": "LA", - "iso3": "LAO", - "numeric_code": 418, - "latitude": 18, - "longitude": 105 - }, - { - "country": "Latvia", - "iso2": "LV", - "iso3": "LVA", - "numeric_code": 428, - "latitude": 57, - "longitude": 25 - }, - { - "country": "Lebanon", - "iso2": "LB", - "iso3": "LBN", - "numeric_code": 422, - "latitude": 33.8333, - "longitude": 35.8333 - }, - { - "country": "Lesotho", - "iso2": "LS", - "iso3": "LSO", - "numeric_code": 426, - "latitude": -29.58003188, - "longitude": 28.22723131 - }, - { - "country": "Liberia", - "iso2": "LR", - "iso3": "LBR", - "numeric_code": 430, - "latitude": 6.5, - "longitude": -9.5 - }, - { - "country": "Libyan Arab Jamahiriya", - "iso2": "LY", - "iso3": "LBY", - "numeric_code": 434, - "latitude": 25, - "longitude": 17 - }, - { - "country": "Libya", - "iso2": "LY", - "iso3": "LBY", - "numeric_code": 434, - "latitude": 25, - "longitude": 17 - }, - { - "country": "Liechtenstein", - "iso2": "LI", - "iso3": "LIE", - "numeric_code": 438, - "latitude": 47.1667, - "longitude": 9.5333 - }, - { - "country": "Lithuania", - "iso2": "LT", - "iso3": "LTU", - "numeric_code": 440, - "latitude": 56, - "longitude": 24 - }, - { - "country": "Luxembourg", - "iso2": "LU", - "iso3": "LUX", - "numeric_code": 442, - "latitude": 49.75, - "longitude": 6.1667 - }, - { - "country": "Macao", - "iso2": "MO", - "iso3": "MAC", - "numeric_code": 446, - "latitude": 22.1667, - "longitude": 113.55 - }, - { - "country": "North Macedonia", - "iso2": "MK", - "iso3": "MKD", - "numeric_code": 807, - "latitude": 41.8333, - "longitude": 22 - }, - { - "country": "Madagascar", - "iso2": "MG", - "iso3": "MDG", - "numeric_code": 450, - "latitude": -20, - "longitude": 47 - }, - { - "country": "Malawi", - "iso2": "MW", - "iso3": "MWI", - "numeric_code": 454, - "latitude": -13.5, - "longitude": 34 - }, - { - "country": "Malaysia", - "iso2": "MY", - "iso3": "MYS", - "numeric_code": 458, - "latitude": 2.5, - "longitude": 112.5 - }, - { - "country": "Maldives", - "iso2": "MV", - "iso3": "MDV", - "numeric_code": 462, - "latitude": -0.66491, - "longitude": 73.11832 - }, - { - "country": "Mali", - "iso2": "ML", - "iso3": "MLI", - "numeric_code": 466, - "latitude": 17, - "longitude": -4 - }, - { - "country": "Malta", - "iso2": "MT", - "iso3": "MLT", - "numeric_code": 470, - "latitude": 35.8333, - "longitude": 14.5833 - }, - { - "country": "Marshall Islands", - "iso2": "MH", - "iso3": "MHL", - "numeric_code": 584, - "latitude": 7.273925, - "longitude": 168.818013 - }, - { - "country": "Martinique", - "iso2": "MQ", - "iso3": "MTQ", - "numeric_code": 474, - "latitude": 14.6667, - "longitude": -61 - }, - { - "country": "Mauritania", - "iso2": "MR", - "iso3": "MRT", - "numeric_code": 478, - "latitude": 20, - "longitude": -12 - }, - { - "country": "Mauritius", - "iso2": "MU", - "iso3": "MUS", - "numeric_code": 480, - "latitude": -20.2833, - "longitude": 57.55 - }, - { - "country": "Mayotte", - "iso2": "YT", - "iso3": "MYT", - "numeric_code": 175, - "latitude": -12.8333, - "longitude": 45.1667 - }, - { - "country": "Mexico", - "iso2": "MX", - "iso3": "MEX", - "numeric_code": 484, - "latitude": 23, - "longitude": -102 - }, - { - "country": "Micronesia, Federated States of", - "iso2": "FM", - "iso3": "FSM", - "numeric_code": 583, - "latitude": 6.869474, - "longitude": 158.232959 - }, - { - "country": "Moldova", - "iso2": "MD", - "iso3": "MDA", - "numeric_code": 498, - "latitude": 47, - "longitude": 29 - }, - { - "country": "Monaco", - "iso2": "MC", - "iso3": "MCO", - "numeric_code": 492, - "latitude": 43.7333, - "longitude": 7.4 - }, - { - "country": "Mongolia", - "iso2": "MN", - "iso3": "MNG", - "numeric_code": 496, - "latitude": 46, - "longitude": 105 - }, - { - "country": "Montenegro", - "iso2": "ME", - "iso3": "MNE", - "numeric_code": 499, - "latitude": 42.78890259, - "longitude": 19.23883939 - }, - { - "country": "Montserrat", - "iso2": "MS", - "iso3": "MSR", - "numeric_code": 500, - "latitude": 16.75, - "longitude": -62.2 - }, - { - "country": "Morocco", - "iso2": "MA", - "iso3": "MAR", - "numeric_code": 504, - "latitude": 32, - "longitude": -5 - }, - { - "country": "Mozambique", - "iso2": "MZ", - "iso3": "MOZ", - "numeric_code": 508, - "latitude": -18.25, - "longitude": 35 - }, - { - "country": "Myanmar", - "iso2": "MM", - "iso3": "MMR", - "numeric_code": 104, - "latitude": 22, - "longitude": 98 - }, - { - "country": "Namibia", - "iso2": "NA", - "iso3": "NAM", - "numeric_code": 516, - "latitude": -22, - "longitude": 17 - }, - { - "country": "Nauru", - "iso2": "NR", - "iso3": "NRU", - "numeric_code": 520, - "latitude": -0.534665, - "longitude": 166.937828 - }, - { - "country": "Nepal", - "iso2": "NP", - "iso3": "NPL", - "numeric_code": 524, - "latitude": 28.24891365, - "longitude": 83.9158264 - }, - { - "country": "Netherlands", - "iso2": "NL", - "iso3": "NLD", - "numeric_code": 528, - "latitude": 52.5, - "longitude": 5.75 - }, - { - "country": "Netherlands Antilles", - "iso2": "AN", - "iso3": "ANT", - "numeric_code": 530, - "latitude": 12.25, - "longitude": -68.75 - }, - { - "country": "New Caledonia", - "iso2": "NC", - "iso3": "NCL", - "numeric_code": 540, - "latitude": -21.5, - "longitude": 165.5 - }, - { - "country": "New Zealand", - "iso2": "NZ", - "iso3": "NZL", - "numeric_code": 554, - "latitude": -41, - "longitude": 174 - }, - { - "country": "Nicaragua", - "iso2": "NI", - "iso3": "NIC", - "numeric_code": 558, - "latitude": 13, - "longitude": -85 - }, - { - "country": "Niger", - "iso2": "NE", - "iso3": "NER", - "numeric_code": 562, - "latitude": 16, - "longitude": 8 - }, - { - "country": "Nigeria", - "iso2": "NG", - "iso3": "NGA", - "numeric_code": 566, - "latitude": 10, - "longitude": 8 - }, - { - "country": "Niue", - "iso2": "NU", - "iso3": "NIU", - "numeric_code": 570, - "latitude": -19.048223, - "longitude": -169.85019 - }, - { - "country": "Norfolk Island", - "iso2": "NF", - "iso3": "NFK", - "numeric_code": 574, - "latitude": -29.0333, - "longitude": 167.95 - }, - { - "country": "Northern Mariana Islands", - "iso2": "MP", - "iso3": "MNP", - "numeric_code": 580, - "latitude": 15.2, - "longitude": 145.75 - }, - { - "country": "Norway", - "iso2": "NO", - "iso3": "NOR", - "numeric_code": 578, - "latitude": 62, - "longitude": 10 - }, - { - "country": "Oman", - "iso2": "OM", - "iso3": "OMN", - "numeric_code": 512, - "latitude": 21, - "longitude": 57 - }, - { - "country": "Pakistan", - "iso2": "PK", - "iso3": "PAK", - "numeric_code": 586, - "latitude": 30, - "longitude": 70 - }, - { - "country": "Palau", - "iso2": "PW", - "iso3": "PLW", - "numeric_code": 585, - "latitude": 7.484079, - "longitude": 134.574043 - }, - { - "country": "Palestine", - "iso2": "PS", - "iso3": "PSE", - "numeric_code": 275, - "latitude": 32, - "longitude": 35.25 - }, - { - "country": "Panama", - "iso2": "PA", - "iso3": "PAN", - "numeric_code": 591, - "latitude": 9, - "longitude": -80 - }, - { - "country": "Papua New Guinea", - "iso2": "PG", - "iso3": "PNG", - "numeric_code": 598, - "latitude": -6, - "longitude": 147 - }, - { - "country": "Paraguay", - "iso2": "PY", - "iso3": "PRY", - "numeric_code": 600, - "latitude": -23, - "longitude": -58 - }, - { - "country": "Peru", - "iso2": "PE", - "iso3": "PER", - "numeric_code": 604, - "latitude": -10, - "longitude": -76 - }, - { - "country": "Philippines", - "iso2": "PH", - "iso3": "PHL", - "numeric_code": 608, - "latitude": 13, - "longitude": 122 - }, - { - "country": "Pitcairn", - "iso2": "PN", - "iso3": "PCN", - "numeric_code": 612, - "latitude": -24.7, - "longitude": -127.4 - }, - { - "country": "Poland", - "iso2": "PL", - "iso3": "POL", - "numeric_code": 616, - "latitude": 52, - "longitude": 20 - }, - { - "country": "Portugal", - "iso2": "PT", - "iso3": "PRT", - "numeric_code": 620, - "latitude": 39.5, - "longitude": -8 - }, - { - "country": "Puerto Rico", - "iso2": "PR", - "iso3": "PRI", - "numeric_code": 630, - "latitude": 18.25, - "longitude": -66.5 - }, - { - "country": "Qatar", - "iso2": "QA", - "iso3": "QAT", - "numeric_code": 634, - "latitude": 25.5, - "longitude": 51.25 - }, - { - "country": "Réunion", - "iso2": "RE", - "iso3": "REU", - "numeric_code": 638, - "latitude": -21.1, - "longitude": 55.6 - }, - { - "country": "Romania", - "iso2": "RO", - "iso3": "ROU", - "numeric_code": 642, - "latitude": 46, - "longitude": 25 - }, - { - "country": "Russian Federation", - "iso2": "RU", - "iso3": "RUS", - "numeric_code": 643, - "latitude": 60, - "longitude": 100 - }, - { - "country": "Rwanda", - "iso2": "RW", - "iso3": "RWA", - "numeric_code": 646, - "latitude": -2, - "longitude": 30 - }, - { - "country": "Saint Helena, Ascension and Tristan da Cunha", - "iso2": "SH", - "iso3": "SHN", - "numeric_code": 654, - "latitude": -15.9333, - "longitude": -5.7 - }, - { - "country": "Saint Kitts and Nevis", - "iso2": "KN", - "iso3": "KNA", - "numeric_code": 659, - "latitude": 17.3333, - "longitude": -62.75 - }, - { - "country": "Saint Lucia", - "iso2": "LC", - "iso3": "LCA", - "numeric_code": 662, - "latitude": 13.8833, - "longitude": -61.1333 - }, - { - "country": "Saint Pierre and Miquelon", - "iso2": "PM", - "iso3": "SPM", - "numeric_code": 666, - "latitude": 46.8333, - "longitude": -56.3333 - }, - { - "country": "Saint Vincent and the Grenadines", - "iso2": "VC", - "iso3": "VCT", - "numeric_code": 670, - "latitude": 13.25, - "longitude": -61.2 - }, - { - "country": "Saint Vincent & the Grenadines", - "iso2": "VC", - "iso3": "VCT", - "numeric_code": 670, - "latitude": 13.25, - "longitude": -61.2 - }, - { - "country": "St. Vincent and the Grenadines", - "iso2": "VC", - "iso3": "VCT", - "numeric_code": 670, - "latitude": 13.25, - "longitude": -61.2 - }, - { - "country": "Samoa", - "iso2": "WS", - "iso3": "WSM", - "numeric_code": 882, - "latitude": -13.596923, - "longitude": -172.370771 - }, - { - "country": "San Marino", - "iso2": "SM", - "iso3": "SMR", - "numeric_code": 674, - "latitude": 43.7667, - "longitude": 12.4167 - }, - { - "country": "Sao Tome and Principe", - "iso2": "ST", - "iso3": "STP", - "numeric_code": 678, - "latitude": 1, - "longitude": 7 - }, - { - "country": "Saudi Arabia", - "iso2": "SA", - "iso3": "SAU", - "numeric_code": 682, - "latitude": 25, - "longitude": 45 - }, - { - "country": "Senegal", - "iso2": "SN", - "iso3": "SEN", - "numeric_code": 686, - "latitude": 15.182353, - "longitude": -14.575589 - }, - { - "country": "Serbia", - "iso2": "RS", - "iso3": "SRB", - "numeric_code": 688, - "latitude": 44, - "longitude": 21 - }, - { - "country": "Seychelles", - "iso2": "SC", - "iso3": "SYC", - "numeric_code": 690, - "latitude": -4.5833, - "longitude": 55.6667 - }, - { - "country": "Sierra Leone", - "iso2": "SL", - "iso3": "SLE", - "numeric_code": 694, - "latitude": 8.5, - "longitude": -11.5 - }, - { - "country": "Singapore", - "iso2": "SG", - "iso3": "SGP", - "numeric_code": 702, - "latitude": 1.3667, - "longitude": 103.8 - }, - { - "country": "Slovakia", - "iso2": "SK", - "iso3": "SVK", - "numeric_code": 703, - "latitude": 48.6667, - "longitude": 19.5 - }, - { - "country": "Slovenia", - "iso2": "SI", - "iso3": "SVN", - "numeric_code": 705, - "latitude": 46, - "longitude": 15 - }, - { - "country": "Solomon Islands", - "iso2": "SB", - "iso3": "SLB", - "numeric_code": 90, - "latitude": -8, - "longitude": 159 - }, - { - "country": "Somalia", - "iso2": "SO", - "iso3": "SOM", - "numeric_code": 706, - "latitude": 10, - "longitude": 49 - }, - { - "country": "South Africa", - "iso2": "ZA", - "iso3": "ZAF", - "numeric_code": 710, - "latitude": -29, - "longitude": 24 - }, - { - "country": "South Georgia and the South Sandwich Islands", - "iso2": "GS", - "iso3": "SGS", - "numeric_code": 239, - "latitude": -54.5, - "longitude": -37 - }, - { - "country": "Spain", - "iso2": "ES", - "iso3": "ESP", - "numeric_code": 724, - "latitude": 40, - "longitude": -4 - }, - { - "country": "Sri Lanka", - "iso2": "LK", - "iso3": "LKA", - "numeric_code": 144, - "latitude": 7, - "longitude": 81 - }, - { - "country": "Sudan", - "iso2": "SD", - "iso3": "SDN", - "numeric_code": 736, - "latitude": 15, - "longitude": 30 - }, - { - "country": "South Sudan", - "iso2": "SD", - "iso3": "SSD", - "numeric_code": 736, - "latitude": 6.628957, - "longitude": 31.25367 - }, - { - "country": "Suriname", - "iso2": "SR", - "iso3": "SUR", - "numeric_code": 740, - "latitude": 4, - "longitude": -56 - }, - { - "country": "Svalbard and Jan Mayen", - "iso2": "SJ", - "iso3": "SJM", - "numeric_code": 744, - "latitude": 78, - "longitude": 20 - }, - { - "country": "Eswatini", - "iso2": "SZ", - "iso3": "SWZ", - "numeric_code": 748, - "latitude": -26.5, - "longitude": 31.5 - }, - { - "country": "Sweden", - "iso2": "SE", - "iso3": "SWE", - "numeric_code": 752, - "latitude": 62, - "longitude": 15 - }, - { - "country": "Switzerland", - "iso2": "CH", - "iso3": "CHE", - "numeric_code": 756, - "latitude": 47, - "longitude": 8 - }, - { - "country": "Syrian Arab Republic", - "iso2": "SY", - "iso3": "SYR", - "numeric_code": 760, - "latitude": 35, - "longitude": 38 - }, - { - "country": "Taiwan, Province of China", - "iso2": "TW", - "iso3": "TWN", - "numeric_code": 158, - "latitude": 23.5, - "longitude": 121 - }, - { - "country": "Taiwan", - "iso2": "TW", - "iso3": "TWN", - "numeric_code": 158, - "latitude": 23.5, - "longitude": 121 - }, - { - "country": "Tajikistan", - "iso2": "TJ", - "iso3": "TJK", - "numeric_code": 762, - "latitude": 39, - "longitude": 71 - }, - { - "country": "Tanzania (United Republic)", - "iso2": "TZ", - "iso3": "TZA", - "numeric_code": 834, - "latitude": -6, - "longitude": 35 - }, - { - "country": "Thailand", - "iso2": "TH", - "iso3": "THA", - "numeric_code": 764, - "latitude": 15, - "longitude": 100 - }, - { - "country": "Timor-Leste", - "iso2": "TL", - "iso3": "TLS", - "numeric_code": 626, - "latitude": -8.55, - "longitude": 125.5167 - }, - { - "country": "Togo", - "iso2": "TG", - "iso3": "TGO", - "numeric_code": 768, - "latitude": 8.52531356, - "longitude": 0.96232845 - }, - { - "country": "Tokelau", - "iso2": "TK", - "iso3": "TKL", - "numeric_code": 772, - "latitude": -9, - "longitude": -172 - }, - { - "country": "Tonga", - "iso2": "TO", - "iso3": "TON", - "numeric_code": 776, - "latitude": -21.188987, - "longitude": -175.190833 - }, - { - "country": "Trinidad and Tobago", - "iso2": "TT", - "iso3": "TTO", - "numeric_code": 780, - "latitude": 11, - "longitude": -61 - }, - { - "country": "Trinidad & Tobago", - "iso2": "TT", - "iso3": "TTO", - "numeric_code": 780, - "latitude": 11, - "longitude": -61 - }, - { - "country": "Tunisia", - "iso2": "TN", - "iso3": "TUN", - "numeric_code": 788, - "latitude": 34, - "longitude": 9 - }, - { - "country": "Turkey", - "iso2": "TR", - "iso3": "TUR", - "numeric_code": 792, - "latitude": 39, - "longitude": 35 - }, - { - "country": "Turkmenistan", - "iso2": "TM", - "iso3": "TKM", - "numeric_code": 795, - "latitude": 40, - "longitude": 60 - }, - { - "country": "Turks and Caicos Islands", - "iso2": "TC", - "iso3": "TCA", - "numeric_code": 796, - "latitude": 21.75, - "longitude": -71.5833 - }, - { - "country": "Tuvalu", - "iso2": "TV", - "iso3": "TUV", - "numeric_code": 798, - "latitude": -8.503343, - "longitude": 179.194139 - }, - { - "country": "Uganda", - "iso2": "UG", - "iso3": "UGA", - "numeric_code": 800, - "latitude": 1, - "longitude": 32 - }, - { - "country": "Ukraine", - "iso2": "UA", - "iso3": "UKR", - "numeric_code": 804, - "latitude": 49, - "longitude": 32 - }, - { - "country": "United Arab Emirates", - "iso2": "AE", - "iso3": "ARE", - "numeric_code": 784, - "latitude": 24, - "longitude": 54 - }, - { - "country": "United Kingdom", - "iso2": "GB", - "iso3": "GBR", - "numeric_code": 826, - "latitude": 54, - "longitude": -2 - }, - { - "country": "United States", - "iso2": "US", - "iso3": "USA", - "numeric_code": 840, - "latitude": 38, - "longitude": -97 - }, - { - "country": "United States Minor Outlying Islands", - "iso2": "UM", - "iso3": "UMI", - "numeric_code": 581, - "latitude": 19.2833, - "longitude": 166.6 - }, - { - "country": "Uruguay", - "iso2": "UY", - "iso3": "URY", - "numeric_code": 858, - "latitude": -33, - "longitude": -56 - }, - { - "country": "Uzbekistan", - "iso2": "UZ", - "iso3": "UZB", - "numeric_code": 860, - "latitude": 41, - "longitude": 64 - }, - { - "country": "Vanuatu", - "iso2": "VU", - "iso3": "VUT", - "numeric_code": 548, - "latitude": -15.51274, - "longitude": 166.956285 - }, - { - "country": "Venezuela", - "iso2": "VE", - "iso3": "VEN", - "numeric_code": 862, - "latitude": 8, - "longitude": -66 - }, - { - "country": "Viet Nam", - "iso2": "VN", - "iso3": "VNM", - "numeric_code": 704, - "latitude": 13.314024, - "longitude": 108.768746 - }, - { - "country": "Virgin Islands, British", - "iso2": "VG", - "iso3": "VGB", - "numeric_code": 92, - "latitude": 18.5, - "longitude": -64.5 - }, - { - "country": "Virgin Islands, U.S.", - "iso2": "VI", - "iso3": "VIR", - "numeric_code": 850, - "latitude": 18.3333, - "longitude": -64.8333 - }, - { - "country": "Wallis and Futuna", - "iso2": "WF", - "iso3": "WLF", - "numeric_code": 876, - "latitude": -13.3, - "longitude": -176.2 - }, - { - "country": "Western Sahara", - "iso2": "EH", - "iso3": "ESH", - "numeric_code": 732, - "latitude": 24.5, - "longitude": -13 - }, - { - "country": "Yemen", - "iso2": "YE", - "iso3": "YEM", - "numeric_code": 887, - "latitude": 15, - "longitude": 48 - }, - { - "country": "Zambia", - "iso2": "ZM", - "iso3": "ZMB", - "numeric_code": 894, - "latitude": -15.095562, - "longitude": 26.645935 - }, - { - "country": "Zanzibar", - "iso2": "", - "iso3": "QNB", - "numeric_code": 0, - "latitude": -6.144212, - "longitude": 39.378279 - }, - { - "country": "Zimbabwe", - "iso2": "ZW", - "iso3": "ZWE", - "numeric_code": 716, - "latitude": -19.00420419, - "longitude": 29.8514412 - } -] diff --git a/src/utils/data-themes/formatRawData.ts b/src/utils/data-themes/formatRawData.ts deleted file mode 100644 index 4d5be0b..0000000 --- a/src/utils/data-themes/formatRawData.ts +++ /dev/null @@ -1,24 +0,0 @@ -function formatDateString(item: string) { - if ( - typeof item === 'string' && - new Date(item).toString() !== 'Invalid Date' - ) { - return item.split('T')[0]; - } - return item; -} - -export function formatRawData(item: any) { - const keys = Object.keys(item); - let parsedItem = {}; - keys.forEach((key: string) => { - parsedItem = { - ...parsedItem, - [key]: - item[key] === undefined || item[key] === null - ? '' - : formatDateString(item[key]), - }; - }); - return parsedItem; -} diff --git a/src/utils/data-themes/getDatasetFilterOptions.ts b/src/utils/data-themes/getDatasetFilterOptions.ts deleted file mode 100644 index 8269010..0000000 --- a/src/utils/data-themes/getDatasetFilterOptions.ts +++ /dev/null @@ -1,40 +0,0 @@ -import _ from 'lodash'; -import {FilterGroup} from '../../interfaces/filters'; - -export function getDatasetFilterOptions(dataset: any): FilterGroup[] { - const filterOptions: FilterGroup[] = []; - const itemKeys = _.filter(Object.keys(dataset[0]), key => { - return ( - key !== 'id' && - !key.toLowerCase().includes('amount') && - !key.toLowerCase().includes('date') && - !key.toLowerCase().includes('number') && - !key.toLowerCase().includes('title') - ); - }); - - itemKeys.forEach(key => { - const options = _.filter( - Object.keys(_.groupBy(dataset, key)), - (optionKey: string) => - optionKey !== 'undefined' && optionKey !== 'null' && optionKey !== '', - ); - const name = key; - - if (options.length > 0) { - filterOptions.push({ - name, - options: _.orderBy( - _.uniq(options).map((o: string) => ({ - name: o, - value: o, - })), - 'label', - 'asc', - ), - }); - } - }); - - return filterOptions; -} diff --git a/src/utils/filtering/allocations/getFilterString.ts b/src/utils/filtering/allocations/getFilterString.ts deleted file mode 100644 index 8872ed9..0000000 --- a/src/utils/filtering/allocations/getFilterString.ts +++ /dev/null @@ -1,82 +0,0 @@ -import _ from 'lodash'; -import filteringAllocations from '../../../config/filtering/allocations.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString( - params: any, - aggregationString?: string, - extraFilterString?: string, -) { - let str = extraFilterString ?? ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringAllocations.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringAllocations.multicountry}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringAllocations.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const periods = _.filter( - _.get(params, 'periods', '').split(','), - (period: string) => period.length > 0, - ).map((period: string) => period); - if (periods.length > 0) { - const startPeriods = periods.map((period: string) => - period.split('-')[0].trim(), - ); - const endPeriods = periods.map((period: string) => - period.split('-')[1].trim(), - ); - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringAllocations.periodStart - }${filtering.in}(${startPeriods.join(filtering.multi_param_separator)})`; - str += `${str.length > 0 ? ' AND ' : ''}${filteringAllocations.periodEnd}${ - filtering.in - }(${endPeriods.join(filtering.multi_param_separator)})`; - } - - str += `${ - str.length > 0 && _.get(params, 'levelParam', '').length > 0 ? ' AND ' : '' - }${_.get(params, 'levelParam', '')}`; - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${ - str.length > 0 ? ' AND ' : '' - }${filteringAllocations.search.replace(//g, `'${search}'`)}`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/budgets/getDrilldownFilterString.ts b/src/utils/filtering/budgets/getDrilldownFilterString.ts deleted file mode 100644 index 4ccc9bd..0000000 --- a/src/utils/filtering/budgets/getDrilldownFilterString.ts +++ /dev/null @@ -1,104 +0,0 @@ -import _ from 'lodash'; -import filteringBudgets from '../../../config/filtering/budgets.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getDrilldownFilterString( - params: any, - aggregationString?: string, -) { - let str = ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringBudgets.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringBudgets.multicountry}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const statuses = _.filter( - _.get(params, 'status', '').split(','), - (stat: string) => stat.length > 0, - ).map((stat: string) => `'${stat}'`); - if (statuses.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.status}${ - filtering.in - }(${statuses.join(filtering.multi_param_separator)})`; - } - - const partners = _.filter( - _.get(params, 'partners', '').split(','), - (partner: string) => partner.length > 0, - ).map((partner: string) => `'${partner}'`); - if (partners.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.partner}${ - filtering.in - }(${partners.join(filtering.multi_param_separator)})`; - } - - const partnerTypes = _.filter( - _.get(params, 'partnerTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.partner_type}${ - filtering.in - }(${partnerTypes.join(filtering.multi_param_separator)})`; - } - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.grantId}${ - filtering.eq - }${grantId}`; - } - - const IPnumber = _.get(params, 'IPnumber', null); - if (IPnumber) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.IPnumber}${ - filtering.eq - }${IPnumber}`; - } - - const activityAreaName = _.get(params, 'activityAreaName', null); - if (activityAreaName) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringBudgets.activityAreaName - }${filtering.eq}'${activityAreaName}'`; - } - - str += `${str.length > 0 ? ' AND ' : ''}${_.get(params, 'levelParam', '')}`; - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/budgets/getFilterString.ts b/src/utils/filtering/budgets/getFilterString.ts deleted file mode 100644 index a89710c..0000000 --- a/src/utils/filtering/budgets/getFilterString.ts +++ /dev/null @@ -1,106 +0,0 @@ -import _ from 'lodash'; -import filteringBudgets from '../../../config/filtering/budgets.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString( - params: any, - aggregationString?: string, - extraFilterString?: string, -) { - let str = extraFilterString ?? ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringBudgets.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringBudgets.multicountry}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const statuses = _.filter( - _.get(params, 'status', '').split(','), - (stat: string) => stat.length > 0, - ).map((stat: string) => `'${stat}'`); - if (statuses.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.status}${ - filtering.in - }(${statuses.join(filtering.multi_param_separator)})`; - } - - const partners = _.filter( - _.get(params, 'partners', '').split(','), - (partner: string) => partner.length > 0, - ).map((partner: string) => `'${partner}'`); - if (partners.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.partner}${ - filtering.in - }(${partners.join(filtering.multi_param_separator)})`; - } - - const partnerSubTypes = _.filter( - _.get(params, 'partnerSubTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerSubTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringBudgets.partner_sub_type - }${filtering.in}(${partnerSubTypes.join(filtering.multi_param_separator)})`; - } - - const partnerTypes = _.filter( - _.get(params, 'partnerTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.partner_type}${ - filtering.in - }(${partnerTypes.join(filtering.multi_param_separator)})`; - } - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.grantId}${ - filtering.eq - }${grantId}`; - } - - const IPnumber = _.get(params, 'IPnumber', null); - if (IPnumber) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringBudgets.IPnumber}${ - filtering.eq - }${IPnumber}`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/disbursements/getFilterString.ts b/src/utils/filtering/disbursements/getFilterString.ts deleted file mode 100644 index 65e9649..0000000 --- a/src/utils/filtering/disbursements/getFilterString.ts +++ /dev/null @@ -1,130 +0,0 @@ -import _ from 'lodash'; -import filteringDisbursements from '../../../config/filtering/disbursements.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString(params: any, aggregationString?: string) { - let str = ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringDisbursements.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringDisbursements.multicountry}${ - filtering.in - }(${locations.join(filtering.multi_param_separator)}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringDisbursements.component - }${filtering.in}(${components.join(filtering.multi_param_separator)})`; - } - - const statuses = _.filter( - _.get(params, 'status', '').split(','), - (stat: string) => stat.length > 0, - ).map((stat: string) => `'${stat}'`); - if (statuses.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDisbursements.status}${ - filtering.in - }(${statuses.join(filtering.multi_param_separator)})`; - } - - const partners = _.filter( - _.get(params, 'partners', '').split(','), - (partner: string) => partner.length > 0, - ).map((partner: string) => `'${partner}'`); - if (partners.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDisbursements.partner}${ - filtering.in - }(${partners.join(filtering.multi_param_separator)})`; - } - - const partnerSubTypes = _.filter( - _.get(params, 'partnerSubTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerSubTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringDisbursements.partner_sub_type - }${filtering.in}(${partnerSubTypes.join(filtering.multi_param_separator)})`; - } - - const partnerTypes = _.filter( - _.get(params, 'partnerTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringDisbursements.partner_type - }${filtering.in}(${partnerTypes.join(filtering.multi_param_separator)})`; - } - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDisbursements.grantId}${ - filtering.eq - }${grantId}`; - } - - const IPnumber = _.get(params, 'IPnumber', null); - if (IPnumber) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDisbursements.IPnumber}${ - filtering.eq - }${IPnumber}`; - } - - const barPeriod = _.get(params, 'barPeriod', null); - if (barPeriod) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringDisbursements.barPeriod - }${filtering.eq}${barPeriod}`; - } - const signedBarPeriod = _.get(params, 'signedBarPeriod', null); - if (signedBarPeriod) { - str += `${ - str.length > 0 ? ' AND ' : '' - }${filteringDisbursements.signedBarPeriod - .replace('', `${signedBarPeriod}-01-01`) - .replace('', `${parseInt(signedBarPeriod, 10) + 1}-01-01`)}`; - } - const committedBarPeriod = _.get(params, 'committedBarPeriod', null); - if (committedBarPeriod) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringDisbursements.committedBarPeriod - }${filtering.eq}${committedBarPeriod}`; - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${ - str.length > 0 ? ' AND ' : '' - }${filteringDisbursements.search.replace(//g, `'${search}'`)}`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/disbursements/grantDetailGetFilterString.ts b/src/utils/filtering/disbursements/grantDetailGetFilterString.ts deleted file mode 100644 index 5263df4..0000000 --- a/src/utils/filtering/disbursements/grantDetailGetFilterString.ts +++ /dev/null @@ -1,84 +0,0 @@ -import _ from 'lodash'; -import filteringGrantDetailDisbursements from '../../../config/filtering/grantDetailDisbursements.json'; -import filteringGrantDetailTreemapDisbursements from '../../../config/filtering/grantDetailTreemapDisbursements.json'; -import filtering from '../../../config/filtering/index.json'; - -export function grantDetailGetFilterString( - params: any, - aggregationString?: string, -) { - let str = ''; - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringGrantDetailDisbursements.grantId - }${filtering.eq}${grantId}`; - } - - const IPnumber = _.get(params, 'IPnumber', null); - if (IPnumber) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringGrantDetailDisbursements.IPnumber - }${filtering.eq}${IPnumber}`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} - -export function grantDetailTreemapGetFilterString( - params: any, - aggregationString?: string, -) { - let str = ''; - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringGrantDetailTreemapDisbursements.grantId - }${filtering.eq}${grantId}`; - } - - const IPnumber = _.get(params, 'IPnumber', null); - if (IPnumber) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringGrantDetailTreemapDisbursements.IPnumber - }${filtering.eq}${IPnumber}`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/disbursements/multicountries/getFilterString.ts b/src/utils/filtering/disbursements/multicountries/getFilterString.ts deleted file mode 100644 index 47c2a51..0000000 --- a/src/utils/filtering/disbursements/multicountries/getFilterString.ts +++ /dev/null @@ -1,90 +0,0 @@ -import _ from 'lodash'; -import filtering from '../../../../config/filtering/index.json'; -import filteringMulticountries from '../../../../config/filtering/multicountries.json'; - -export function getGeoMultiCountriesFilterString( - params: any, - aggregationString?: string, - extraFilterString?: string, -) { - let str = extraFilterString ?? ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `${filteringMulticountries.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )})`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringMulticountries.component - }${filtering.in}(${components.join(filtering.multi_param_separator)})`; - } - - const statuses = _.filter( - _.get(params, 'status', '').split(','), - (stat: string) => stat.length > 0, - ).map((stat: string) => `'${stat}'`); - if (statuses.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringMulticountries.status}${ - filtering.in - }(${statuses.join(filtering.multi_param_separator)})`; - } - - const partners = _.filter( - _.get(params, 'partners', '').split(','), - (partner: string) => partner.length > 0, - ).map((partner: string) => `'${partner}'`); - if (partners.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringMulticountries.partner}${ - filtering.in - }(${partners.join(filtering.multi_param_separator)})`; - } - - const partnerSubTypes = _.filter( - _.get(params, 'partnerSubTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerSubTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringMulticountries.partner_sub_type - }${filtering.in}(${partnerSubTypes.join(filtering.multi_param_separator)})`; - } - - const partnerTypes = _.filter( - _.get(params, 'partnerTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringMulticountries.partner_type - }${filtering.in}(${partnerTypes.join(filtering.multi_param_separator)})`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/documents/getFilterString.ts b/src/utils/filtering/documents/getFilterString.ts deleted file mode 100644 index fc819f9..0000000 --- a/src/utils/filtering/documents/getFilterString.ts +++ /dev/null @@ -1,68 +0,0 @@ -import _ from 'lodash'; -import filteringDocuments from '../../../config/filtering/documents.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString(params: any, defaultFilter?: string) { - let str = defaultFilter && !params.documentTypes ? defaultFilter : ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDocuments.country}${ - filtering.in - }(${locations.join(filtering.multi_param_separator)})`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDocuments.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDocuments.grantId}${ - filtering.eq - }${grantId}`; - } - - const multicountries = _.filter( - _.get(params, 'multicountries', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (multicountries.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDocuments.multicountry}${ - filtering.in - }(${multicountries.join(filtering.multi_param_separator)})`; - } - - const documentTypes = _.filter( - _.get(params, 'documentTypes', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (documentTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDocuments.documentType}${ - filtering.in - }(${documentTypes.join(filtering.multi_param_separator)})`; - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringDocuments.search.replace( - //g, - `'${search}'`, - )}`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}`; - } - - return str; -} diff --git a/src/utils/filtering/eligibility/getFilterString.ts b/src/utils/filtering/eligibility/getFilterString.ts deleted file mode 100644 index d1e4fac..0000000 --- a/src/utils/filtering/eligibility/getFilterString.ts +++ /dev/null @@ -1,82 +0,0 @@ -import _ from 'lodash'; -import filteringEligibility from '../../../config/filtering/eligibility.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString(params: any, defaultFilter?: string) { - let str = defaultFilter ?? ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringEligibility.country}${ - filtering.in - }(${locations.join(filtering.multi_param_separator)})`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringEligibility.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const periods = _.filter( - _.get(params, 'periods', '').split(','), - (period: string) => period.length > 0, - ).map((period: string) => period); - if (periods.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringEligibility.period}${ - filtering.in - }(${periods.join(filtering.multi_param_separator)})`; - } - - const cycles = _.filter( - _.get(params, 'cycles', '').split(','), - (cycle: string) => cycle.length > 0, - ).map((cycle: string) => (cycle !== 'null' ? `'${cycle}'` : cycle)); - if (cycles.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringEligibility.cycle}${ - filtering.in - }(${cycles.join(filtering.multi_param_separator)})`; - } - - const status = _.filter( - _.get(params, 'status', '').split(','), - (s: string) => s.length > 0, - ).map((s: string) => `'${s}'`); - if (status.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringEligibility.status}${ - filtering.in - }(${status.join(filtering.multi_param_separator)})`; - } - - const diseaseBurden = _.filter( - _.get(params, 'diseaseBurden', '').split(','), - (db: string) => db.length > 0, - ).map((db: string) => `'${db}'`); - if (diseaseBurden.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringEligibility.disease}${ - filtering.in - }(${diseaseBurden.join(filtering.multi_param_separator)})`; - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${ - str.length > 0 ? ' AND ' : '' - }${filteringEligibility.search.replace(//g, `'${search}'`)}`; - } - - if (str.length > 0) { - if (!defaultFilter) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - } - } - - return str; -} diff --git a/src/utils/filtering/fundingrequests/getFilterString.ts b/src/utils/filtering/fundingrequests/getFilterString.ts deleted file mode 100644 index 66353e7..0000000 --- a/src/utils/filtering/fundingrequests/getFilterString.ts +++ /dev/null @@ -1,90 +0,0 @@ -import _ from 'lodash'; -import filteringFunding from '../../../config/filtering/fundingrequests.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString(params: any, defaultFilter?: string) { - let str = defaultFilter ?? ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringFunding.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringFunding.multicountry}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${ - str.length > 0 ? ' AND ' : '' - }${filteringFunding.component.replace( - '', - components.join(filtering.multi_param_separator), - )}`; - } - - const periods = _.filter( - _.get(params, 'periods', '').split(','), - (period: string) => period.length > 0, - ).map((period: string) => period); - if (periods.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringFunding.cycle}${ - filtering.in - }(${periods.join(filtering.multi_param_separator)})`; - } - - const trpWindows = _.filter( - _.get(params, 'trpWindows', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (trpWindows.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringFunding.trpwindow}${ - filtering.in - }(${trpWindows.join(filtering.multi_param_separator)})`; - } - - const portfolioCategories = _.filter( - _.get(params, 'portfolioCategories', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (portfolioCategories.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringFunding.portfolioCategory - }${filtering.in}(${portfolioCategories.join( - filtering.multi_param_separator, - )})`; - } - - const cycles = _.filter( - _.get(params, 'cycles', '').split(','), - (cycle: string) => cycle.length > 0, - ).map((cycle: string) => `'${cycle}'`); - if (cycles.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringFunding.cycle}${ - filtering.in - }(${cycles.join(filtering.multi_param_separator)})`; - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringFunding.search.replace( - //g, - `'${search}'`, - )}`; - } - - if (str.length > 0) { - if (!defaultFilter) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - } - } - - return str; -} diff --git a/src/utils/filtering/globalsearch/index.ts b/src/utils/filtering/globalsearch/index.ts deleted file mode 100644 index 28d4403..0000000 --- a/src/utils/filtering/globalsearch/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import filteringUtils from '../../../config/filtering/index.json'; - -export function buildGlobalSearchFilterString( - fields: string[], - template: string, - keywords: string[], -): string { - const strArray: string[] = []; - - keywords.forEach((keyword: string) => { - const fieldStrArray: string[] = fields.map((field: string) => - template - .replace('', field) - .replace( - '', - `'${encodeURIComponent(keyword.replace(/'/g, "''"))}'`, - ), - ); - strArray.push(`(${fieldStrArray.join(` ${filteringUtils.or_operator} `)})`); - }); - - return strArray.join(` ${filteringUtils.and_operator} `); -} diff --git a/src/utils/filtering/grants/getFilterString.ts b/src/utils/filtering/grants/getFilterString.ts deleted file mode 100644 index 0474ebf..0000000 --- a/src/utils/filtering/grants/getFilterString.ts +++ /dev/null @@ -1,131 +0,0 @@ -import _ from 'lodash'; -import filteringGrants from '../../../config/filtering/grants.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString(params: any, aggregationString?: string) { - let str = ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringGrants.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringGrants.multicountry}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const statuses = _.filter( - _.get(params, 'status', '').split(','), - (stat: string) => stat.length > 0, - ).map((stat: string) => `'${stat}'`); - if (statuses.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.status}${ - filtering.in - }(${statuses.join(filtering.multi_param_separator)})`; - } - - const partners = _.filter( - _.get(params, 'partners', '').split(','), - (partner: string) => partner.length > 0, - ).map((partner: string) => `'${partner}'`); - if (partners.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.partner}${ - filtering.in - }(${partners.join(filtering.multi_param_separator)})`; - } - - const partnerSubTypes = _.filter( - _.get(params, 'partnerSubTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerSubTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringGrants.partner_sub_type - }${filtering.in}(${partnerSubTypes.join(filtering.multi_param_separator)})`; - } - - const partnerTypes = _.filter( - _.get(params, 'partnerTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.partner_type}${ - filtering.in - }(${partnerTypes.join(filtering.multi_param_separator)})`; - } - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.grantId}${ - filtering.eq - }${grantId}`; - } - - const IPnumber = _.get(params, 'IPnumber', null); - if (IPnumber) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.IPnumber}${ - filtering.eq - }${IPnumber}`; - } - - const barPeriod = _.get(params, 'barPeriod', null); - if (barPeriod) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.barPeriod}${ - filtering.eq - }${barPeriod}`; - } - const signedBarPeriod = _.get(params, 'signedBarPeriod', null); - if (signedBarPeriod) { - str += `${ - str.length > 0 ? ' AND ' : '' - }${filteringGrants.signedBarPeriod - .replace('', `${signedBarPeriod}-01-01`) - .replace('', `${parseInt(signedBarPeriod, 10) + 1}-01-01`)}`; - } - const committedBarPeriod = _.get(params, 'committedBarPeriod', null); - if (committedBarPeriod) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringGrants.committedBarPeriod - }${filtering.eq}${committedBarPeriod}`; - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.search.replace( - '', - `'${search}'`, - )}`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - if (aggregationString) { - str = aggregationString.replace( - '', - `${str - .replace( - `${filtering.filter_operator}${filtering.param_assign_operator}`, - 'filter(', - ) - .replace('&', ')/')}`, - ); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/partner/getFilterString.ts b/src/utils/filtering/partner/getFilterString.ts deleted file mode 100644 index 9901960..0000000 --- a/src/utils/filtering/partner/getFilterString.ts +++ /dev/null @@ -1,101 +0,0 @@ -import _ from 'lodash'; -import filteringGrants from '../../../config/filtering/grants.json'; -import filtering from '../../../config/filtering/index.json'; - -export function getFilterString(params: any, aggregationString?: string) { - let str = ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringGrants.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringGrants.multicountry}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const statuses = _.filter( - _.get(params, 'status', '').split(','), - (stat: string) => stat.length > 0, - ).map((stat: string) => `'${stat}'`); - if (statuses.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.status}${ - filtering.in - }(${statuses.join(filtering.multi_param_separator)})`; - } - - const partners = _.filter( - _.get(params, 'partners', '').split(','), - (partner: string) => partner.length > 0, - ).map((partner: string) => `'${partner}'`); - if (partners.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.partner}${ - filtering.in - }(${partners.join(filtering.multi_param_separator)})`; - } - - const partnerSubTypes = _.filter( - _.get(params, 'partnerSubTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerSubTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringGrants.partner_sub_type - }${filtering.in}(${partnerSubTypes.join(filtering.multi_param_separator)})`; - } - - const partnerTypes = _.filter( - _.get(params, 'partnerTypes', '').split(','), - (type: string) => type.length > 0, - ).map((type: string) => `'${type}'`); - if (partnerTypes.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.partner_type}${ - filtering.in - }(${partnerTypes.join(filtering.multi_param_separator)})`; - } - - const grantId = _.get(params, 'grantId', null); - if (grantId) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.grantId}${ - filtering.eq - }${grantId}`; - } - - const barPeriod = _.get(params, 'barPeriod', null); - if (barPeriod) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.barPeriod}${ - filtering.eq - }${barPeriod}`; - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringGrants.search.replace( - '', - `'${search}'`, - )}`; - } - - if (str.length > 0) { - if (aggregationString) { - str = aggregationString.replace('', str); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/performanceframework/getFilterString.ts b/src/utils/filtering/performanceframework/getFilterString.ts deleted file mode 100644 index 3996aea..0000000 --- a/src/utils/filtering/performanceframework/getFilterString.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function getFilterString(params: any, aggregationString: string) { - return aggregationString - .replace('', `'${params.grantId}'`) - .replace('', params.IPnumber) - .replace('', `'${params.indicatorSet}'`) - .replace('', `'${params.moduleName}'`); -} diff --git a/src/utils/filtering/performancerating/getFilterString.ts b/src/utils/filtering/performancerating/getFilterString.ts deleted file mode 100644 index 2c044ce..0000000 --- a/src/utils/filtering/performancerating/getFilterString.ts +++ /dev/null @@ -1,55 +0,0 @@ -import _ from 'lodash'; -import filtering from '../../../config/filtering/index.json'; -import filteringPF from '../../../config/filtering/performancerating.json'; - -export function getFilterString(params: any) { - let str = ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `(${filteringPF.country}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}) OR ${filteringPF.multicountry}${filtering.in}(${locations.join( - filtering.multi_param_separator, - )}))`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringPF.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const statuses = _.filter( - _.get(params, 'status', '').split(','), - (stat: string) => stat.length > 0, - ).map((stat: string) => `'${stat}'`); - if (statuses.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringPF.status}${ - filtering.in - }(${statuses.join(filtering.multi_param_separator)})`; - } - - const partners = _.filter( - _.get(params, 'partners', '').split(','), - (partner: string) => partner.length > 0, - ).map((partner: string) => `'${partner}'`); - if (partners.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringPF.partner}${ - filtering.in - }(${partners.join(filtering.multi_param_separator)})`; - } - - if (str.length > 0) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - } - - return str; -} diff --git a/src/utils/filtering/pledges-contributions/getFilterString.ts b/src/utils/filtering/pledges-contributions/getFilterString.ts deleted file mode 100644 index 4e35619..0000000 --- a/src/utils/filtering/pledges-contributions/getFilterString.ts +++ /dev/null @@ -1,75 +0,0 @@ -import _ from 'lodash'; -import filtering from '../../../config/filtering/index.json'; -import filteringPledgesContributions from '../../../config/filtering/pledgescontributions.json'; - -export function getFilterString(params: any, aggregationString?: string) { - let str = ''; - - const donors = _.filter( - _.get(params, 'donors', '').split(','), - (loc: string) => loc.length > 0, - ).map((donor: string) => `'${donor}'`); - if (donors.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringPledgesContributions.donors - }${filtering.in}(${donors.join(filtering.multi_param_separator)})`; - } - // else { - // str += `${str.length > 0 ? ' AND ' : ''}${ - // filteringPledgesContributions.donorCategory - // }${filtering.in}(${ - // PledgesContributionsTimeCycleFieldsMapping.defaultDonorCategoryFilter - // })`; - // } - - const donorCategories = _.filter( - _.get(params, 'donorCategories', '').split(','), - (loc: string) => loc.length > 0, - ).map((donorCat: string) => `'${donorCat}'`); - if (donorCategories.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringPledgesContributions.donorCategory - }${filtering.in}(${donorCategories.join(filtering.multi_param_separator)})`; - } - - const periods = _.filter( - _.get(params, 'periods', '').split(','), - (period: string) => period.length > 0, - ).map((period: string) => `'${period}'`); - if (periods.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${ - filteringPledgesContributions.period - }${filtering.in}(${periods.join(filtering.multi_param_separator)})`; - } - - if (_.get(params, 'levelParam', '').length > 0) { - const lParam = _.get(params, 'levelParam', '').split('-'); - if (lParam.length === 1 || lParam.length > 2) { - str += `${str.length > 0 ? ' AND ' : ''}${_.get( - lParam, - '[0]', - '', - )}-${_.get(lParam, '[1]', '')}`; - } - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${ - str.length > 0 ? ' AND ' : '' - }${filteringPledgesContributions.search.replace( - //g, - `'${search}'`, - )}`; - } - - if (str.length > 0) { - if (aggregationString) { - str = aggregationString.replace('', ` AND ${str}`); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/filtering/results/getFilterString.ts b/src/utils/filtering/results/getFilterString.ts deleted file mode 100644 index 4a07e6f..0000000 --- a/src/utils/filtering/results/getFilterString.ts +++ /dev/null @@ -1,100 +0,0 @@ -import _ from 'lodash'; -import filtering from '../../../config/filtering/index.json'; -import filteringResults from '../../../config/filtering/results.json'; - -export function getFilterString(params: any, defaultFilter?: string) { - let str = defaultFilter ?? ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringResults.country}${ - filtering.in - }(${locations.join(filtering.multi_param_separator)})`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringResults.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const periods = _.filter( - _.get(params, 'periods', '').split(','), - (period: string) => period.length > 0, - ).map((period: string) => period); - if (periods.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringResults.period}${ - filtering.in - }(${periods.join(filtering.multi_param_separator)})`; - } - - const search = _.get(params, 'q', ''); - if (search.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringResults.search.replace( - '', - `'${search}'`, - )}`; - } - - if (str.length > 0) { - if (!defaultFilter) { - str = `${filtering.filter_operator}${filtering.param_assign_operator}${str}&`; - } - } - - return str; -} - -export function getFilterStringForStats( - params: any, - aggregationString?: string, -) { - let str = ''; - - const locations = _.filter( - _.get(params, 'locations', '').split(','), - (loc: string) => loc.length > 0, - ).map((loc: string) => `'${loc}'`); - if (locations.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringResults.country}${ - filtering.in - }(${locations.join(filtering.multi_param_separator)})`; - } - - const components = _.filter( - _.get(params, 'components', '').split(','), - (comp: string) => comp.length > 0, - ).map((comp: string) => `'${comp}'`); - if (components.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringResults.component}${ - filtering.in - }(${components.join(filtering.multi_param_separator)})`; - } - - const periods = _.filter( - _.get(params, 'periods', '').split(','), - (period: string) => period.length > 0, - ).map((period: string) => period); - if (periods.length > 0) { - str += `${str.length > 0 ? ' AND ' : ''}${filteringResults.period}${ - filtering.in - }(${periods.join(filtering.multi_param_separator)})`; - } - - if (str.length > 0) { - if (aggregationString) { - str = aggregationString.replace('', ` AND ${str}`); - } - } else if (aggregationString) { - str = aggregationString.replace('', ''); - } - - return str; -} diff --git a/src/utils/formatFinancialValue.ts b/src/utils/formatFinancialValue.ts deleted file mode 100644 index 9928c29..0000000 --- a/src/utils/formatFinancialValue.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function formatFinancialValue(value: number | bigint): string { - return `${value.toLocaleString('en-US', { - minimumFractionDigits: 0, - maximumFractionDigits: 0, - })} USD`; -} diff --git a/src/utils/performanceframework/formatPFData.ts b/src/utils/performanceframework/formatPFData.ts deleted file mode 100644 index 3732336..0000000 --- a/src/utils/performanceframework/formatPFData.ts +++ /dev/null @@ -1,369 +0,0 @@ -import _ from 'lodash'; -import performanceframeworkMappingUtils from '../../config/mapping/performanceframework/utils.json'; - -const rateColors = performanceframeworkMappingUtils.valueAchievementRateColors; - -const indicatorSetOrder = performanceframeworkMappingUtils.indicatorSetOrder; - -export function getColorBasedOnValue( - value: number, - rates: {value: number; color: string}[], - isReversed: boolean, - isWPTM: boolean, - result: any, -) { - if (value === null && !result) return '#fff'; - if (isWPTM) { - if (value === 1) return !isReversed ? rateColors[0] : rateColors[4]; - if (value === 2) return rateColors[2]; - if (value === 3) return !isReversed ? rateColors[4] : rateColors[0]; - return '#E2E2E2'; - } - if (rates.length === 6) { - if ( - (rates[0].value < value || rates[0].value === value) && - rates[1].value > value - ) { - return !isReversed ? rates[0].color : rates[4].color; - } - if ( - (rates[1].value < value || rates[1].value === value) && - rates[2].value > value - ) { - return !isReversed ? rates[1].color : rates[3].color; - } - if ( - (rates[2].value < value || rates[2].value === value) && - rates[3].value > value - ) { - return rates[2].color; - } - if ( - (rates[3].value < value || rates[3].value === value) && - rates[4].value > value - ) { - return !isReversed ? rates[3].color : rates[1].color; - } - if ( - (rates[4].value < value || rates[4].value === value) && - rates[5].value > value - ) { - return !isReversed ? rates[4].color : rates[1].color; - } - } - return '#E2E2E2'; -} - -export function getAchievementRateLegendValues() { - return [ - { - value: 0, - color: rateColors[0], - }, - { - value: 0.3, - color: rateColors[1], - }, - { - value: 0.6, - color: rateColors[2], - }, - { - value: 0.9, - color: rateColors[3], - }, - { - value: 1, - color: rateColors[4], - }, - { - value: 2, - color: '', - }, - ]; -} - -export function formatPFData( - rawData: { - indicatorSet: string; - indicatorName: string; - startDate: string; - endDate: string; - disaggregationGroup: string | null; - disaggregationValue: string | number | null; - valueType: string; - valueAchievementRate: string | number | null; - module: string; - intervention?: string; - }[], - selectedTimeframe: { - raw: string; - formatted: string; - number: number; - }[], -) { - const nodes: {id: string; [key: string]: any}[] = []; - const links: {source: string; target: string; [key: string]: any}[] = []; - - let data: any[] = []; - let filteredRawData = rawData; - - if (selectedTimeframe) { - nodes.push({ - id: `${selectedTimeframe[0].formatted} - ${selectedTimeframe[1].formatted}`, - radius: 6, - depth: 0, - color: '#262C34', - borderColor: '#ADB5BD', - }); - filteredRawData = _.filter(rawData, dataIns => { - if (dataIns.disaggregationValue && dataIns.disaggregationGroup) - return false; - if (dataIns.valueType === 'Baseline') return true; - const date1 = new Date(dataIns.startDate).getTime(); - const date2 = new Date(dataIns.endDate).getTime(); - if ( - (date1 === selectedTimeframe[0].number || - date1 < selectedTimeframe[0].number) && - (date2 === selectedTimeframe[1].number || - date2 > selectedTimeframe[1].number) - ) { - return true; - } - return false; - }); - } - - const groupedByIndicatorSet = _.groupBy(filteredRawData, 'indicatorSet'); - Object.keys(groupedByIndicatorSet).forEach(indicatorSetKey => { - const instance = groupedByIndicatorSet[indicatorSetKey].map( - (indicator: any) => { - if (!indicator.module) { - return { - ...indicator, - module: 'Other', - }; - } - return indicator; - }, - ); - const groupedByActivityAreaModule = _.groupBy(instance, 'module'); - const children: any[] = []; - Object.keys(groupedByActivityAreaModule).forEach(activityAreaModuleKey => { - // indicator names - const grandChildren: any[] = []; - const groupedByIndicatorName = _.groupBy( - groupedByActivityAreaModule[activityAreaModuleKey], - 'indicatorName', - ); - Object.keys(groupedByIndicatorName).forEach(indicatorNameKey => { - const targetInstance = _.find( - groupedByIndicatorName[indicatorNameKey], - { - valueType: 'Target', - }, - ); - const resultInstance = _.find( - groupedByIndicatorName[indicatorNameKey], - { - valueType: 'Result', - }, - ); - const baselineInstance = _.find( - groupedByIndicatorName[indicatorNameKey], - { - valueType: 'Baseline', - }, - ); - const targetAchievementRate = _.get( - targetInstance, - 'valueAchievementRate', - null, - ); - const resultAchievementRate = _.get( - resultInstance, - 'valueAchievementRate', - null, - ); - const baselineAchievementRate = _.get( - baselineInstance, - 'valueAchievementRate', - null, - ); - grandChildren.push({ - name: indicatorNameKey, - target: { - instance: targetInstance, - achievementRate: targetAchievementRate, - }, - result: { - instance: resultInstance, - achievementRate: resultAchievementRate, - }, - baseline: { - instance: baselineInstance, - achievementRate: baselineAchievementRate, - }, - }); - }); - // interventions - const groupedByActivityAreaIntervention = _.groupBy( - instance, - 'activityAreaIntervention.activityAreaName', - ); - const interventions: any[] = []; - Object.keys(groupedByActivityAreaIntervention).forEach( - interventionKey => { - if (interventionKey !== 'undefined') { - const groupedByIndicatorName2 = _.groupBy( - groupedByActivityAreaIntervention[interventionKey], - 'indicatorName', - ); - Object.keys(groupedByIndicatorName2).forEach(indicatorNameKey => { - interventions.push({ - name: interventionKey, - indicator: indicatorNameKey, - }); - }); - } - }, - ); - children.push({ - name: - activityAreaModuleKey !== 'undefined' - ? activityAreaModuleKey - : 'Other', - count: groupedByActivityAreaModule[activityAreaModuleKey].length, - children: _.sortBy(grandChildren, 'name'), - interventions: _.sortBy(interventions, 'name'), - }); - }); - - data.push({ - name: indicatorSetKey, - count: instance.length, - children: _.sortBy(children, 'name'), - }); - }); - - const achievementRatesLegendValues = getAchievementRateLegendValues(); - data = data - .sort( - (a, b) => - _.get(indicatorSetOrder, a.name, 0) - - _.get(indicatorSetOrder, b.name, 0), - ) - .map(set => ({ - ...set, - children: set.children.map((aaModule: any) => ({ - ...aaModule, - children: aaModule.children.map((indicator: any) => { - return { - ...indicator, - color: getColorBasedOnValue( - aaModule.name === 'Process indicator / WPTM' - ? indicator.result.valueNumerator || - indicator.target.valueNumerator - : indicator.result.achievementRate || - indicator.target.achievementRate, - achievementRatesLegendValues, - _.get(indicator.result, 'instance.isIndicatorReversed', false) || - _.get(indicator.target, 'instance.isIndicatorReversed', false), - aaModule.name === 'Process indicator / WPTM', - indicator.result.instance, - ), - target: { - ...indicator.target, - color: getColorBasedOnValue( - aaModule.name === 'Process indicator / WPTM' - ? indicator.target.valueNumerator - : indicator.target.achievementRate, - achievementRatesLegendValues, - _.get(indicator.target, 'instance.isIndicatorReversed', false), - aaModule.name === 'Process indicator / WPTM', - indicator.target.instance, - ), - }, - result: { - ...indicator.result, - color: getColorBasedOnValue( - aaModule.name === 'Process indicator / WPTM' - ? indicator.result.valueNumerator - : indicator.result.achievementRate, - achievementRatesLegendValues, - _.get(indicator.result, 'instance.isIndicatorReversed', false), - aaModule.name === 'Process indicator / WPTM', - indicator.result.instance, - ), - }, - baseline: { - ...indicator.baseline, - color: getColorBasedOnValue( - aaModule.name === 'Process indicator / WPTM' - ? indicator.baseline.valueNumerator - : indicator.baseline.achievementRate, - achievementRatesLegendValues, - _.get( - indicator.baseline, - 'instance.isIndicatorReversed', - false, - ), - aaModule.name === 'Process indicator / WPTM', - indicator.baseline.instance, - ), - }, - }; - }), - })), - })); - - data.forEach((indicatorSet: any) => { - nodes.push({ - id: indicatorSet.name, - radius: 6, - depth: 1, - color: '#ADB5BD', - borderColor: '#262C34', - }); - links.push({ - source: nodes[0].id, - target: indicatorSet.name, - distance: 70, - }); - indicatorSet.children.forEach((module: any) => { - const moduleId = `${module.name}|${indicatorSet.name}`; - nodes.push({ - id: moduleId, - radius: 6, - depth: 2, - color: '#fff', - borderColor: '#262C34', - }); - links.push({ - source: indicatorSet.name, - target: moduleId, - distance: 30, - }); - module.children.forEach((indicator: any) => { - nodes.push({ - id: indicator.name, - radius: 6, - depth: 3, - color: indicator.color, - borderColor: '#262C34', - }); - links.push({ - source: moduleId, - target: indicator.name, - distance: 20, - }); - }); - }); - }); - - return { - nodes: _.orderBy(_.uniqBy(nodes, 'id'), 'depth', 'asc'), - links, - // achievementRatesLegends: achievementRatesLegendValues, - }; -} diff --git a/src/utils/performanceframework/getTimeframes.ts b/src/utils/performanceframework/getTimeframes.ts deleted file mode 100644 index d717cfb..0000000 --- a/src/utils/performanceframework/getTimeframes.ts +++ /dev/null @@ -1,39 +0,0 @@ -import _ from 'lodash/'; -import moment from 'moment'; - -export function getTimeframes(rawData: any) { - let timeframes: any[] = []; - rawData.forEach((item: any) => { - if (item.startDate) timeframes.push(item.startDate); - if (item.endDate) timeframes.push(item.endDate); - }); - timeframes = _.uniq(timeframes); - timeframes = timeframes.map(tf => { - return { - raw: tf, - formatted: moment(tf).format('MMM, YYYY'), - number: new Date(tf).getTime(), - }; - }); - timeframes = _.sortBy(timeframes, 'number'); - return timeframes; -} - -export function getTimeframeGroups(rawData: any) { - let timeframes: any[] = []; - rawData.forEach((item: any) => { - if (item.startDate && item.endDate) { - timeframes.push({ - start: item.startDate, - startFormatted: moment(item.startDate).format('MMM, YYYY'), - end: item.endDate, - endFormatted: moment(item.endDate).format('MMM, YYYY'), - number: - new Date(item.startDate).getTime() + new Date(item.endDate).getTime(), - }); - } - }); - timeframes = _.uniqBy(timeframes, 'number'); - timeframes = _.orderBy(timeframes, 'number', 'asc'); - return timeframes; -} diff --git a/src/utils/pledgescontributions/getD2HCoordinates.ts b/src/utils/pledgescontributions/getD2HCoordinates.ts deleted file mode 100644 index df411fc..0000000 --- a/src/utils/pledgescontributions/getD2HCoordinates.ts +++ /dev/null @@ -1,25 +0,0 @@ -import _ from 'lodash'; - -export function getD2HCoordinates(name: string, coordinates: any) { - const result = []; - const countries = name.split('-').map(c => c.trim()); - const country1 = _.find( - coordinates, - c => _.get(c, 'spatialShape.description', '').indexOf(countries[1]) > -1, - ); - const country2 = _.find( - coordinates, - c => _.get(c, 'spatialShape.description', '').indexOf(countries[2]) > -1, - ); - if (country1 && country2) { - result.push([ - parseFloat(country1.spatialShape.latitude), - parseFloat(country1.spatialShape.longitude), - ]); - result.push([ - parseFloat(country2.spatialShape.latitude), - parseFloat(country2.spatialShape.longitude), - ]); - } - return result; -} From 867180be823466d4a0d3a714741a2c6fa3adcfe9 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 10 Jul 2024 10:34:26 +0300 Subject: [PATCH 32/44] fix: missing util file --- src/utils/filtering/globalsearch.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/utils/filtering/globalsearch.ts diff --git a/src/utils/filtering/globalsearch.ts b/src/utils/filtering/globalsearch.ts new file mode 100644 index 0000000..4b4d646 --- /dev/null +++ b/src/utils/filtering/globalsearch.ts @@ -0,0 +1,23 @@ +import filteringUtils from '../../config/filtering/index.json'; + +export function buildGlobalSearchFilterString( + fields: string[], + template: string, + keywords: string[], +): string { + const strArray: string[] = []; + + keywords.forEach((keyword: string) => { + const fieldStrArray: string[] = fields.map((field: string) => + template + .replace('', field) + .replace( + '', + `'${encodeURIComponent(keyword.replace(/'/g, "''"))}'`, + ), + ); + strArray.push(`(${fieldStrArray.join(` ${filteringUtils.or_operator} `)})`); + }); + + return strArray.join(` ${filteringUtils.and_operator} `); +} From 14c2a1dc76d74fc96e0c461c0b1072241f9da80e Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 10 Jul 2024 11:48:18 +0300 Subject: [PATCH 33/44] fix: funding requests table dates --- src/controllers/fundingrequests.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 87e6b32..3a148a7 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -57,10 +57,10 @@ export class FundingRequestsController { ? moment(item.gacMeeting).format('MMMM YYYY') : '--', grant: subitem.grant, - startingDate: subitem.startDate + startingDate: subitem.startingDate ? moment(subitem.startDate).format('DD-MM-YYYY') : '--', - endingDate: subitem.endDate + endingDate: subitem.endingDate ? moment(subitem.endDate).format('DD-MM-YYYY') : '--', principalRecipient: subitem.principalRecipient, From 91c05d8d899acddfdbcebe476ee6fd7af73b5672 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 10 Jul 2024 12:15:44 +0300 Subject: [PATCH 34/44] feat: standardize date format --- src/controllers/fundingrequests.controller.ts | 10 +++++----- src/controllers/grants.controller.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 3a148a7..7e0b86f 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -42,7 +42,7 @@ export class FundingRequestsController { return { components: item.components, submissionDate: item.submissionDate - ? moment(item.submissionDate).format('D MMMM YYYY') + ? moment(item.submissionDate).format('DD MMM YYYY') : '--', approach: item.approach, trpWindow: item.trpWindow, @@ -51,17 +51,17 @@ export class FundingRequestsController { _children: item.items.map((subitem: any) => { return { boardApproval: subitem.boardApproval - ? moment(subitem.boardApproval).format('MMMM YYYY') + ? moment(subitem.boardApproval).format('DD MMM YYYY') : '--', gacMeeting: item.gacMeeting - ? moment(item.gacMeeting).format('MMMM YYYY') + ? moment(item.gacMeeting).format('DD MMM YYYY') : '--', grant: subitem.grant, startingDate: subitem.startingDate - ? moment(subitem.startDate).format('DD-MM-YYYY') + ? moment(subitem.startDate).format('DD MMM YYYY') : '--', endingDate: subitem.endingDate - ? moment(subitem.endDate).format('DD-MM-YYYY') + ? moment(subitem.endDate).format('DD MMM YYYY') : '--', principalRecipient: subitem.principalRecipient, }; diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index c1d5749..727b86f 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -246,7 +246,7 @@ export class GrantsController { GrantOverviewMapping.implementationPeriod.boardApprovedDate, '', ), - ).format('DD/MM/YYYY - hh:mm A'); + ).format('DD MMM YYYY'); data.dates = [ moment( _.get( @@ -254,14 +254,14 @@ export class GrantsController { GrantOverviewMapping.implementationPeriod.startDate, '', ), - ).format('DD/MM/YYYY hh:mm A'), + ).format('DD MMM YYYY'), moment( _.get( period, GrantOverviewMapping.implementationPeriod.endDate, '', ), - ).format('DD/MM/YYYY hh:mm A'), + ).format('DD MMM YYYY'), ]; } return {data: [data]}; From 64854b7485687ffd69b6b8aa19c36a8629fc481d Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Wed, 10 Jul 2024 14:10:02 +0300 Subject: [PATCH 35/44] feat: budgets table modules & interventions var --- src/config/mapping/budgets/table.json | 5 ++- src/controllers/budgets.controller.ts | 63 +++++++++++++++++---------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/config/mapping/budgets/table.json b/src/config/mapping/budgets/table.json index 82daa20..b1009ae 100644 --- a/src/config/mapping/budgets/table.json +++ b/src/config/mapping/budgets/table.json @@ -5,5 +5,8 @@ "level3Field": "financialCategory.name", "valueField": "value", "cycle": "periodFrom", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((financialCategory/name,financialCategory/parent/name,financialCategory/parent/parent/name),aggregate(plannedAmount with sum as value))" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((financialCategory/name,financialCategory/parent/name,financialCategory/parent/parent/name),aggregate(plannedAmount with sum as value))", + "level1FieldVar2": ".parent.name", + "level2FieldVar2": ".name", + "urlParamsVar2": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((/name,/parent/name,/parent/parent/name),aggregate(plannedAmount with sum as value))" } diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index f766245..0ee462d 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -300,9 +300,29 @@ export class BudgetsController { @get('/budgets/table') @response(200) async table() { + let urlParams = BudgetsTableFieldsMapping.urlParams; + let level1Field = BudgetsTableFieldsMapping.level1Field; + let level2Field = BudgetsTableFieldsMapping.level2Field; + let level3Field: string | null = BudgetsTableFieldsMapping.level3Field; + if (this.req.query.var2) { + const var2 = this.req.query.var2.toString(); + urlParams = BudgetsTableFieldsMapping.urlParamsVar2.replace( + //g, + var2, + ); + level1Field = BudgetsTableFieldsMapping.level1FieldVar2.replace( + '', + var2, + ); + level2Field = BudgetsTableFieldsMapping.level2FieldVar2.replace( + '', + var2, + ); + level3Field = null; + } const filterString = filterFinancialIndicators( this.req.query, - BudgetsTableFieldsMapping.urlParams, + urlParams, [ 'implementationPeriod/grant/geography/name', 'implementationPeriod/grant/geography/code', @@ -319,10 +339,7 @@ export class BudgetsController { BudgetsTableFieldsMapping.dataPath, [], ); - const groupedByLevel1 = _.groupBy( - rawData, - BudgetsTableFieldsMapping.level1Field, - ); + const groupedByLevel1 = _.groupBy(rawData, level1Field); const data: { name: string; @@ -330,7 +347,7 @@ export class BudgetsController { _children: { name: string; amount: number; - _children: { + _children?: { name: string; amount: number; }[]; @@ -338,10 +355,7 @@ export class BudgetsController { }[] = []; _.forEach(groupedByLevel1, (level1Data, level1) => { - const grouepdByLevel2 = _.groupBy( - level1Data, - BudgetsTableFieldsMapping.level2Field, - ); + const grouepdByLevel2 = _.groupBy(level1Data, level2Field); const level1Amount = _.sumBy( level1Data, BudgetsTableFieldsMapping.valueField, @@ -349,10 +363,9 @@ export class BudgetsController { const level1Children = _.map( grouepdByLevel2, (level2Data, level2) => { - const groupedByLevel3 = _.groupBy( - level2Data, - BudgetsTableFieldsMapping.level3Field, - ); + const groupedByLevel3 = level3Field + ? _.groupBy(level2Data, level3Field) + : {}; const level2Amount = _.sumBy( level2Data, BudgetsTableFieldsMapping.valueField, @@ -360,16 +373,18 @@ export class BudgetsController { return { name: level2, amount: level2Amount, - _children: _.map(groupedByLevel3, (level3Data, level3) => { - const level3Amount = _.sumBy( - level3Data, - BudgetsTableFieldsMapping.valueField, - ); - return { - name: level3, - amount: level3Amount, - }; - }), + _children: level3Field + ? _.map(groupedByLevel3, (level3Data, level3) => { + const level3Amount = _.sumBy( + level3Data, + BudgetsTableFieldsMapping.valueField, + ); + return { + name: level3, + amount: level3Amount, + }; + }) + : undefined, }; }, ); From d2e22abaeeff74e65cc0dca15f5ebe11f6fdbbf3 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 11 Jul 2024 10:58:23 +0300 Subject: [PATCH 36/44] feat: geography grouping --- .../mapping/filter-options/geography.json | 2 + src/config/urls/index.json | 7 +- src/controllers/budgets.controller.ts | 232 +++++++++++++----- src/controllers/disbursements.controller.ts | 145 +++++++---- src/controllers/expenditures.controller.ts | 105 ++++++-- src/controllers/filteroptions.controller.ts | 27 +- src/utils/filtering/financialIndicators.ts | 6 +- 7 files changed, 392 insertions(+), 132 deletions(-) diff --git a/src/config/mapping/filter-options/geography.json b/src/config/mapping/filter-options/geography.json index 327299c..73618ff 100644 --- a/src/config/mapping/filter-options/geography.json +++ b/src/config/mapping/filter-options/geography.json @@ -1,5 +1,7 @@ { "dataPath": "value[0].children", + "dataPathBoardConstituencyView": "value", + "dataPathPortfolioView": "value", "label": "name", "value": "code", "children": "children", diff --git a/src/config/urls/index.json b/src/config/urls/index.json index db952b1..5e722fa 100644 --- a/src/config/urls/index.json +++ b/src/config/urls/index.json @@ -32,8 +32,11 @@ "filteroptionsdonors": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/donors/?$filter=donorId%20eq%20c06eaea9-c81b-442b-b403-dc348f3734eb%20or%20donorId%20eq%2025188dfc-7567-4e08-b8d0-088a6b83f238%20or%20donorId%20eq%20641c05fa-129d-4b57-a213-b5f6852ddc5f%20or%20donorid%20eq%20cc30fc59-b2fa-49e3-aa5a-762727daa68c&$expand=members($expand=members($expand=members($expand=members)))", "filteroptionsreplenishmentperiods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/replenishmentperiods", "multicountriescountriesdata": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", - "FILTER_OPTIONS_GEOGRAPHY": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", - "FILTER_OPTIONS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=type eq 'Component'", + "FILTER_OPTIONS_GEOGRAPHIES": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_GEOGRAPHIES_BOARD_CONSTITUENCY_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_BoardConstituencyView?&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_GEOGRAPHIES_PORTFOLIO_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_PortfolioView?&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_COMPONENTS_GROUPED": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=parentId eq null", + "FILTER_OPTIONS_COMPONENTS_UNGROUPED": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreas?$filter=type eq 'Component'", "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc", "FILTER_OPTIONS_DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors?$apply=groupby((id,name,type/name))", "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$apply=groupby((principalRecipient/type/parent/name,principalRecipient/type/parent/code,principalRecipient/type/name,principalRecipient/type/code,principalRecipient/name))", diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index 0ee462d..ac77ba2 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -20,10 +20,25 @@ export class BudgetsController { @get('/budgets/radial') @response(200) async radial() { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (this.req.query.geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (this.req.query.geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString = filterFinancialIndicators( this.req.query, BudgetsRadialFieldsMapping.urlParams, - 'implementationPeriod/grant/geography/name', + geographyMappings, 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -44,17 +59,32 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/sankey') + @get('/budgets/sankey/{componentField}/{geographyGrouping}') @response(200) - async sankey() { + async sankey( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString = filterFinancialIndicators( this.req.query, BudgetsSankeyFieldsMapping.urlParams, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], - 'implementationPeriod/grant/activityArea/name', + geographyMappings, + `implementationPeriod/grant/${componentField}/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -160,19 +190,34 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/treemap/{componentField}') + @get('/budgets/treemap/{componentField}/{geographyGrouping}') @response(200) - async treemap(@param.path.string('componentField') componentField: string) { + async treemap( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString1 = filterFinancialIndicators( this.req.query, BudgetsTreemapFieldsMapping.urlParams1.replace( '', componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -195,7 +240,7 @@ export class BudgetsController { '', componentField, ), - 'implementationPeriod/grant/geography/name', + geographyMappings, `implementationPeriod/grant/${componentField}/parent/name`, ); url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -297,9 +342,27 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/table') + @get('/budgets/table/{componentField}/{geographyGrouping}') @response(200) - async table() { + async table( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } let urlParams = BudgetsTableFieldsMapping.urlParams; let level1Field = BudgetsTableFieldsMapping.level1Field; let level2Field = BudgetsTableFieldsMapping.level2Field; @@ -323,10 +386,7 @@ export class BudgetsController { const filterString = filterFinancialIndicators( this.req.query, urlParams, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -448,12 +508,28 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/breakdown/{cycle}/{componentField}') + @get('/budgets/breakdown/{cycle}/{componentField}/{geographyGrouping}') @response(200) async breakdown( @param.path.string('cycle') cycle: string, @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const years = cycle.split('-'); const filterString1 = filterFinancialIndicators( @@ -466,10 +542,7 @@ export class BudgetsController { '', componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -496,7 +569,7 @@ export class BudgetsController { '', componentField, ), - 'implementationPeriod/grant/geography/name', + geographyMappings, `implementationPeriod/grant/${componentField}/parent/name`, ); url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -579,24 +652,39 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/utilization') + @get('/budgets/utilization/{componentField}/{geographyGrouping}') @response(200) - async utilization() { + async utilization( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { // (disbursement + cash balance) / budget + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], - 'implementationPeriod/grant/activityArea/name', + geographyMappings, + `implementationPeriod/grant/${componentField}/name`, ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/activityArea/name', + geographyMappings, + `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -806,27 +894,39 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/budgets/absorption') + @get('/budgets/absorption/{componentField}/{geographyGrouping}') @response(200) - async absorption() { + async absorption( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { // expenditure / budget + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], - 'implementationPeriod/grant/activityArea/name', + geographyMappings, + `implementationPeriod/grant/${componentField}/name`, ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], - 'implementationPeriod/grant/activityArea/name', + geographyMappings, + `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -992,27 +1092,39 @@ export class BudgetsController { .catch(handleDataApiError); } - @get('/disbursements/utilization') + @get('/disbursements/utilization/{componentField}/{geographyGrouping}') @response(200) - async disbursementsUtilization() { + async disbursementsUtilization( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { // expenditure / disbursement + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString1 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParams, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], - 'implementationPeriod/grant/activityArea/name', + geographyMappings, + `implementationPeriod/grant/${componentField}/name`, ); const filterString2 = filterFinancialIndicators( this.req.query, BudgetsMetricsFieldsMapping.urlParamsOrganisations, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], - 'implementationPeriod/grant/activityArea/name', + geographyMappings, + `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; const url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; diff --git a/src/controllers/disbursements.controller.ts b/src/controllers/disbursements.controller.ts index 990a825..2adb815 100644 --- a/src/controllers/disbursements.controller.ts +++ b/src/controllers/disbursements.controller.ts @@ -14,17 +14,32 @@ import {filterFinancialIndicators} from '../utils/filtering/financialIndicators' export class DisbursementsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - @get('/financial-insights/stats') + @get('/financial-insights/stats/{componentField}/{geographyGrouping}') @response(200) - async financialInsightsStats() { + async financialInsightsStats( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString = filterFinancialIndicators( this.req.query, FinancialInsightsStatsMapping.urlParams, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], - 'activityArea/name', + geographyMappings, + `${componentField}/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -70,19 +85,34 @@ export class DisbursementsController { .catch(handleDataApiError); } - @get('/disbursements/bar-chart/{componentField}') + @get('/disbursements/bar-chart/{componentField}/{geographyGrouping}') @response(200) - async barChart(@param.path.string('componentField') componentField: string) { + async barChart( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString1 = filterFinancialIndicators( this.req.query, BarChartFieldsMapping.urlParams1.replace( '', componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -105,10 +135,7 @@ export class DisbursementsController { '', componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/parent/name`, ); url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -180,19 +207,34 @@ export class DisbursementsController { .catch(handleDataApiError); } - @get('/disbursements/line-chart/{componentField}') + @get('/disbursements/line-chart/{componentField}/{geographyGrouping}') @response(200) - async lineChart(@param.path.string('componentField') componentField: string) { + async lineChart( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString1 = filterFinancialIndicators( this.req.query, LineChartFieldsMapping.urlParams1.replace( '', componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -215,10 +257,7 @@ export class DisbursementsController { '', componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/parent/name`, ); url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -329,16 +368,31 @@ export class DisbursementsController { .catch(handleDataApiError); } - @get('/disbursements/table/{componentField}') + @get('/disbursements/table/{componentField}/{geographyGrouping}') @response(200) - async table(@param.path.string('componentField') componentField: string) { + async table( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString1 = filterFinancialIndicators( this.req.query, TableFieldsMapping.urlParams1.replace('', componentField), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/name`, ); const url1 = `${urls.FINANCIAL_INDICATORS}/${filterString1}`; @@ -361,10 +415,7 @@ export class DisbursementsController { '', componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/parent/name`, ); url2 = `${urls.FINANCIAL_INDICATORS}/${filterString2}`; @@ -477,13 +528,25 @@ export class DisbursementsController { @get('/disbursements/cycles') @response(200) async cycles() { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (this.req.query.geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (this.req.query.geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString = filterFinancialIndicators( this.req.query, DisbursementsCyclesMapping.urlParams, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, 'activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index f90caa6..4586eac 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -13,19 +13,28 @@ import {filterFinancialIndicators} from '../utils/filtering/financialIndicators' export class ExpendituresController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - @get('/expenditures/heatmap/{row}/{column}/{componentField}') + @get( + '/expenditures/heatmap/{row}/{column}/{componentField}/{geographyGrouping}', + ) @response(200) async heatmap( @param.path.string('row') row: string, @param.path.string('column') column: string, @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, ) { - const ungrouped = await this.heatmaplocal(row, column, 'activityArea'); + const ungrouped = await this.heatmaplocal( + row, + column, + 'activityArea', + geographyGrouping, + ); if (componentField === 'activityAreaGroup') { const grouped = await this.heatmaplocal( row, column, 'activityAreaGroup/parent', + geographyGrouping, ); return { data: [ @@ -46,13 +55,31 @@ export class ExpendituresController { } } - @get('/expenditures/heatmap/{row}/{column}/{componentField}/local') + @get( + '/expenditures/heatmap/{row}/{column}/{componentField}/{geographyGrouping}/local', + ) @response(200) async heatmaplocal( @param.path.string('row') row: string, @param.path.string('column') column: string, @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } let filterString = ExpendituresHeatmapMapping.urlParams; let rowField = ''; let subRowField = ''; @@ -116,10 +143,7 @@ export class ExpendituresController { filterString = filterFinancialIndicators( this.req.query, filterString, - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `implementationPeriod/grant/${componentField}/name`, ); @@ -235,21 +259,34 @@ export class ExpendituresController { .catch(handleDataApiError); } - @get('/expenditures/expandable-bar/{componentField}') + @get('/expenditures/expandable-bar/{componentField}/{geographyGrouping}') @response(200) async expandableBar( @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString = filterFinancialIndicators( this.req.query, ExpendituresBarChartMapping.urlParams.replace( //g, componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -290,19 +327,34 @@ export class ExpendituresController { .catch(handleDataApiError); } - @get('/expenditures/table/{componentField}') + @get('/expenditures/table/{componentField}/{geographyGrouping}') @response(200) - async table(@param.path.string('componentField') componentField: string) { + async table( + @param.path.string('componentField') componentField: string, + @param.path.string('geographyGrouping') geographyGrouping: string, + ) { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString = filterFinancialIndicators( this.req.query, ExpendituresTableMapping.urlParams.replace( //g, componentField, ), - [ - 'implementationPeriod/grant/geography/name', - 'implementationPeriod/grant/geography/code', - ], + geographyMappings, `${componentField}/parent/parent/name`, ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; @@ -360,10 +412,25 @@ export class ExpendituresController { @get('/expenditures/cycles') @response(200) async cycles() { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (this.req.query.geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (this.req.query.geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } const filterString = filterFinancialIndicators( this.req.query, ExpendituresCyclesMapping.urlParams, - 'implementationPeriod/grant/geography/code', + geographyMappings, 'implementationPeriod/grant/activityArea/name', ); const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; diff --git a/src/controllers/filteroptions.controller.ts b/src/controllers/filteroptions.controller.ts index 5667b2c..e36f755 100644 --- a/src/controllers/filteroptions.controller.ts +++ b/src/controllers/filteroptions.controller.ts @@ -1,5 +1,5 @@ import {inject} from '@loopback/core'; -import {get, Request, response, RestBindings} from '@loopback/rest'; +import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; import ComponentMapping from '../config/mapping/filter-options/components.json'; @@ -16,15 +16,23 @@ import {handleDataApiError} from '../utils/dataApiError'; export class FilteroptionsController { constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - @get('/filter-options/geography') + @get('/filter-options/geography/{type}') @response(200) - async filterOptionsGeography() { - const url = urls.FILTER_OPTIONS_GEOGRAPHY; + async filterOptionsGeography(@param.path.string('type') type: string) { + let url = urls.FILTER_OPTIONS_GEOGRAPHIES; + let dataPath = GeographyMapping.dataPath; + if (type === 'Portfolio View') { + url = urls.FILTER_OPTIONS_GEOGRAPHIES_PORTFOLIO_VIEW; + dataPath = GeographyMapping.dataPathPortfolioView; + } else if (type === 'Board Constituency View') { + url = urls.FILTER_OPTIONS_GEOGRAPHIES_BOARD_CONSTITUENCY_VIEW; + dataPath = GeographyMapping.dataPathBoardConstituencyView; + } return axios .get(url) .then((resp: AxiosResponse) => { - const rawData = _.get(resp.data, GeographyMapping.dataPath, []); + const rawData = _.get(resp.data, dataPath, []); const data: FilterGroupOption[] = []; rawData.forEach((item1: any) => { @@ -167,10 +175,13 @@ export class FilteroptionsController { .catch(handleDataApiError); } - @get('/filter-options/components') + @get('/filter-options/components/{type}') @response(200) - async filterOptionsComponents() { - const url = urls.FILTER_OPTIONS_COMPONENTS; + async filterOptionsComponents(@param.path.string('type') type: string) { + const url = + type === 'grouped' + ? urls.FILTER_OPTIONS_COMPONENTS_GROUPED + : urls.FILTER_OPTIONS_COMPONENTS_UNGROUPED; return axios .get(url) diff --git a/src/utils/filtering/financialIndicators.ts b/src/utils/filtering/financialIndicators.ts index 04657ff..11c8e96 100644 --- a/src/utils/filtering/financialIndicators.ts +++ b/src/utils/filtering/financialIndicators.ts @@ -43,7 +43,9 @@ export function filterFinancialIndicators( _.get(params, 'geographies', '').split(','), (o: string) => o.length > 0, ); - const geographies = geos.map((geography: string) => `'${geography}'`); + const geographies = geos.map( + (geography: string) => `'${geography.replace(/'/g, "''")}'`, + ); if (geos.length > 0) { const values: string[] = [...geographies, ...getGeographyValues(geos)]; const geoMapping = @@ -71,7 +73,7 @@ export function filterFinancialIndicators( const donors = _.filter( _.get(params, 'donors', '').split(','), (o: string) => o.length > 0, - ).map((donor: string) => `'${donor}'`); + ).map((donor: string) => `'${donor.replace(/'/g, "''")}'`); if (donors.length > 0) { str += `${str.length > 0 ? ' AND ' : ''}${MAPPING.donor}${ filtering.in From b219db427ef3885d3847d3082cd3a5509ad57f64 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 11 Jul 2024 11:17:13 +0300 Subject: [PATCH 37/44] feat: change isLatestReported to isAnnualized --- src/config/mapping/expenditures/bar.json | 2 +- src/config/mapping/expenditures/cycles.json | 2 +- src/config/mapping/expenditures/heatmap.json | 2 +- src/config/mapping/expenditures/table.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/mapping/expenditures/bar.json b/src/config/mapping/expenditures/bar.json index 98cf95c..e5b5467 100644 --- a/src/config/mapping/expenditures/bar.json +++ b/src/config/mapping/expenditures/bar.json @@ -4,5 +4,5 @@ "itemName": ".name", "indicatorName": "indicatorName", "value": "actual", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmount with sum as actual))" + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isAnnualized eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmount with sum as actual))" } diff --git a/src/config/mapping/expenditures/cycles.json b/src/config/mapping/expenditures/cycles.json index 8f4a90e..7c2c295 100644 --- a/src/config/mapping/expenditures/cycles.json +++ b/src/config/mapping/expenditures/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "implementationPeriod.periodFrom", "cycleTo": "implementationPeriod.periodTo", - "urlParams": "?$apply=filter(indicatorName eq 'Expenditure: Module-Intervention - Reference Rate' AND isLatestReported eq true)/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" + "urlParams": "?$apply=filter(indicatorName eq 'Expenditure: Module-Intervention - Reference Rate' AND isAnnualized eq true)/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" } diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index a3b2698..191a5a9 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -3,7 +3,7 @@ "budget": "value2", "expenditure": "value1", "cycle": "periodCovered", - "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true)/groupby((,),aggregate(actualAmount with sum as value1,plannedAmount with sum as value2))", + "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isAnnualized eq true)/groupby((,),aggregate(actualAmount with sum as value1,plannedAmount with sum as value2))", "url1Items": ["Malaria", "Tuberculosis"], "url2Items": ["HIV/AIDS", "Other"], "fields": { diff --git a/src/config/mapping/expenditures/table.json b/src/config/mapping/expenditures/table.json index e87edaa..1ff5614 100644 --- a/src/config/mapping/expenditures/table.json +++ b/src/config/mapping/expenditures/table.json @@ -5,5 +5,5 @@ "indicatorName": "indicatorName", "cumulativeExpenditureValue": "actualCumulative", "periodExpenditureValue": "actual", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isAnnualized eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" } From 69f3d4509d72b188549be24ea3f38c79def9fa15 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 11 Jul 2024 16:20:07 +0300 Subject: [PATCH 38/44] feat: eligibility years array in table result --- src/controllers/eligibility.controller.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/eligibility.controller.ts b/src/controllers/eligibility.controller.ts index b27a200..34337e5 100644 --- a/src/controllers/eligibility.controller.ts +++ b/src/controllers/eligibility.controller.ts @@ -80,6 +80,8 @@ export class EligibilityController { EligibilityTableMapping.geography, ); + const years: string[] = []; + const data: { [key: string]: | string @@ -150,6 +152,7 @@ export class EligibilityController { ); _.forEach(componentGroupedByYear, (value, key) => { + years.push(key); let isEligible = _.get( value, `[0]["${EligibilityTableMapping.isEligible}"]`, @@ -184,7 +187,7 @@ export class EligibilityController { return item; }); - return {data}; + return {data, years: _.orderBy(_.uniq(years), [], ['desc'])}; }) .catch(handleDataApiError); } From 294d20f98567fe6c9fdee4e4586c7211d030d34c Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Thu, 11 Jul 2024 18:02:48 +0300 Subject: [PATCH 39/44] feat: expenditures opts --- .../mapping/expenditures/availability.json | 4 ++ src/config/mapping/expenditures/heatmap.json | 7 ++- src/controllers/expenditures.controller.ts | 57 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/config/mapping/expenditures/availability.json diff --git a/src/config/mapping/expenditures/availability.json b/src/config/mapping/expenditures/availability.json new file mode 100644 index 0000000..7495549 --- /dev/null +++ b/src/config/mapping/expenditures/availability.json @@ -0,0 +1,4 @@ +{ + "dataPath": "@odata.count", + "urlParams": "?$count=true&$top=0&$filter=financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isAnnualized eq true" +} diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index 191a5a9..a521e5f 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -10,6 +10,11 @@ "principalRecipient": "implementationPeriod/grant/principalRecipient/name", "principalRecipientSubType": "implementationPeriod/grant/principalRecipient/type/name", "principalRecipientType": "implementationPeriod/grant/principalRecipient/type/parent/name", - "component": "implementationPeriod/grant//name" + "component": "implementationPeriod/grant//name", + "module": "/parent/name", + "intervention": "/name", + "investmentLandscape1": "financialCategory/parent/parent/name", + "investmentLandscape2": "financialCategory/parent/name", + "costCategory": "financialCategory/name" } } diff --git a/src/controllers/expenditures.controller.ts b/src/controllers/expenditures.controller.ts index 4586eac..a3026d2 100644 --- a/src/controllers/expenditures.controller.ts +++ b/src/controllers/expenditures.controller.ts @@ -2,6 +2,7 @@ import {inject} from '@loopback/core'; import {get, param, Request, response, RestBindings} from '@loopback/rest'; import axios, {AxiosResponse} from 'axios'; import _ from 'lodash'; +import ExpendituresAvailabilityMapping from '../config/mapping/expenditures/availability.json'; import ExpendituresBarChartMapping from '../config/mapping/expenditures/bar.json'; import ExpendituresCyclesMapping from '../config/mapping/expenditures/cycles.json'; import ExpendituresHeatmapMapping from '../config/mapping/expenditures/heatmap.json'; @@ -81,6 +82,12 @@ export class ExpendituresController { ]; } let filterString = ExpendituresHeatmapMapping.urlParams; + if (row.indexOf('investmentLandscape1') > -1) { + filterString = filterString.replace( + 'Expenditure_Intervention_ReferenceRate', + 'Expenditure_InvestmentLandscape_ReferenceRate', + ); + } let rowField = ''; let subRowField = ''; let subSubRowField = ''; @@ -129,6 +136,17 @@ export class ExpendituresController { ExpendituresHeatmapMapping.fields.component, ).replace(//g, componentField); } + rowField = rowField.replace(//g, componentField); + subRowField = subRowField.replace(//g, componentField); + subSubRowField = subSubRowField.replace( + //g, + componentField, + ); + columnField = columnField.replace(//g, componentField); + subColumnField = subColumnField.replace( + //g, + componentField, + ); const rowFieldArray = [rowField, subRowField, subSubRowField].filter( item => item.length > 0, ); @@ -464,4 +482,43 @@ export class ExpendituresController { }) .catch(handleDataApiError); } + + @get('/has/expenditures') + @response(200) + async hasExpenditures() { + let geographyMappings = [ + 'implementationPeriod/grant/geography/name', + 'implementationPeriod/grant/geography/code', + ]; + if (this.req.query.geographyGrouping === 'Portfolio View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_PortfolioView/name', + 'implementationPeriod/grant/geography_PortfolioView/code', + ]; + } else if (this.req.query.geographyGrouping === 'Board Constituency View') { + geographyMappings = [ + 'implementationPeriod/grant/geography_BoardConstituencyView/name', + 'implementationPeriod/grant/geography_BoardConstituencyView/code', + ]; + } + const filterString = filterFinancialIndicators( + this.req.query, + ExpendituresAvailabilityMapping.urlParams, + geographyMappings, + 'implementationPeriod/grant/activityArea/name', + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + return { + data: { + hasExpenditures: + _.get(resp.data, ExpendituresAvailabilityMapping.dataPath, 0) > 0, + }, + }; + }) + .catch(handleDataApiError); + } } From a7f557aecf5aed2bdcfcb71a27e1afded1d39b06 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Fri, 12 Jul 2024 13:55:20 +0300 Subject: [PATCH 40/44] feat: general optimisations --- .../mapping/expenditures/availability.json | 2 +- src/config/mapping/expenditures/bar.json | 2 +- src/config/mapping/expenditures/cycles.json | 2 +- src/config/mapping/expenditures/heatmap.json | 2 +- src/config/mapping/expenditures/table.json | 2 +- src/config/urls/index.json | 4 +- src/controllers/allocations.controller.ts | 2 +- src/controllers/grants.controller.ts | 33 +- .../locations-board-constituency-view.json | 1510 +++++++++++++++++ .../locations-portfolio-view.json | 1466 ++++++++++++++++ src/utils/filtering/geographies.ts | 24 +- 11 files changed, 3024 insertions(+), 25 deletions(-) create mode 100644 src/static-assets/locations-board-constituency-view.json create mode 100644 src/static-assets/locations-portfolio-view.json diff --git a/src/config/mapping/expenditures/availability.json b/src/config/mapping/expenditures/availability.json index 7495549..4f020fb 100644 --- a/src/config/mapping/expenditures/availability.json +++ b/src/config/mapping/expenditures/availability.json @@ -1,4 +1,4 @@ { "dataPath": "@odata.count", - "urlParams": "?$count=true&$top=0&$filter=financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isAnnualized eq true" + "urlParams": "?$count=true&$top=0&$filter=financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true" } diff --git a/src/config/mapping/expenditures/bar.json b/src/config/mapping/expenditures/bar.json index e5b5467..41075a1 100644 --- a/src/config/mapping/expenditures/bar.json +++ b/src/config/mapping/expenditures/bar.json @@ -4,5 +4,5 @@ "itemName": ".name", "indicatorName": "indicatorName", "value": "actual", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isAnnualized eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmount with sum as actual))" + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmountCumulative with sum as actual))" } diff --git a/src/config/mapping/expenditures/cycles.json b/src/config/mapping/expenditures/cycles.json index 7c2c295..8f4a90e 100644 --- a/src/config/mapping/expenditures/cycles.json +++ b/src/config/mapping/expenditures/cycles.json @@ -2,5 +2,5 @@ "dataPath": "value", "cycleFrom": "implementationPeriod.periodFrom", "cycleTo": "implementationPeriod.periodTo", - "urlParams": "?$apply=filter(indicatorName eq 'Expenditure: Module-Intervention - Reference Rate' AND isAnnualized eq true)/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" + "urlParams": "?$apply=filter(indicatorName eq 'Expenditure: Module-Intervention - Reference Rate' AND isLatestReported eq true)/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" } diff --git a/src/config/mapping/expenditures/heatmap.json b/src/config/mapping/expenditures/heatmap.json index a521e5f..c6cfc8d 100644 --- a/src/config/mapping/expenditures/heatmap.json +++ b/src/config/mapping/expenditures/heatmap.json @@ -3,7 +3,7 @@ "budget": "value2", "expenditure": "value1", "cycle": "periodCovered", - "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isAnnualized eq true)/groupby((,),aggregate(actualAmount with sum as value1,plannedAmount with sum as value2))", + "urlParams": "?$apply=filter(financialDataSet eq 'Expenditure_Intervention_ReferenceRate' AND isLatestReported eq true)/groupby((,),aggregate(actualAmountCumulative with sum as value1,plannedAmountCumulative with sum as value2))", "url1Items": ["Malaria", "Tuberculosis"], "url2Items": ["HIV/AIDS", "Other"], "fields": { diff --git a/src/config/mapping/expenditures/table.json b/src/config/mapping/expenditures/table.json index 1ff5614..e87edaa 100644 --- a/src/config/mapping/expenditures/table.json +++ b/src/config/mapping/expenditures/table.json @@ -5,5 +5,5 @@ "indicatorName": "indicatorName", "cumulativeExpenditureValue": "actualCumulative", "periodExpenditureValue": "actual", - "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isAnnualized eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" + "urlParams": "?$apply=filter(indicatorName in ('Expenditure: Module-Intervention - Reference Rate') AND isLatestReported eq true)/groupby((indicatorName,/name,/parent/name),aggregate(actualAmountCumulative with sum as actualCumulative,actualAmount with sum as actual))" } diff --git a/src/config/urls/index.json b/src/config/urls/index.json index 5e722fa..cfc7ec9 100644 --- a/src/config/urls/index.json +++ b/src/config/urls/index.json @@ -33,8 +33,8 @@ "filteroptionsreplenishmentperiods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/replenishmentperiods", "multicountriescountriesdata": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", "FILTER_OPTIONS_GEOGRAPHIES": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", - "FILTER_OPTIONS_GEOGRAPHIES_BOARD_CONSTITUENCY_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_BoardConstituencyView?&$expand=children($expand=children($expand=children))", - "FILTER_OPTIONS_GEOGRAPHIES_PORTFOLIO_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_PortfolioView?&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_GEOGRAPHIES_BOARD_CONSTITUENCY_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_BoardConstituencyView?&$filter=parentId ne null AND children/any(c: c ne null)&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_GEOGRAPHIES_PORTFOLIO_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_PortfolioView?&$filter=parentId ne null AND children/any(c: c ne null)&$expand=children($expand=children($expand=children))", "FILTER_OPTIONS_COMPONENTS_GROUPED": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=parentId eq null", "FILTER_OPTIONS_COMPONENTS_UNGROUPED": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreas?$filter=type eq 'Component'", "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc", diff --git a/src/controllers/allocations.controller.ts b/src/controllers/allocations.controller.ts index b1be246..9029662 100644 --- a/src/controllers/allocations.controller.ts +++ b/src/controllers/allocations.controller.ts @@ -401,7 +401,7 @@ export class AllocationsController { let filterString = filterFinancialIndicators( {...this.req.query, geographies: countryCode}, AllocationRadialFieldsMapping.urlParamsLocation, - ['geography/name', 'geography/code'], + 'geography/code', 'activityArea/name', ); diff --git a/src/controllers/grants.controller.ts b/src/controllers/grants.controller.ts index 727b86f..e06cdae 100644 --- a/src/controllers/grants.controller.ts +++ b/src/controllers/grants.controller.ts @@ -27,22 +27,31 @@ export class GrantsController { @param.path.string('pageSize') pageSize: string, ) { const mapper = mapTransform(GrantsListMapping.map); - const params = querystring.stringify( - { - ...getPage(filtering.page, parseInt(page, 10), parseInt(pageSize, 10)), - [filtering.page_size]: pageSize, - }, - '&', - filtering.param_assign_operator, - { - encodeURIComponent: (str: string) => str, - }, - ); + const params = + pageSize === 'all' + ? '' + : querystring.stringify( + { + ...getPage( + filtering.page, + parseInt(page, 10), + parseInt(pageSize, 10), + ), + [filtering.page_size]: pageSize, + }, + '&', + filtering.param_assign_operator, + { + encodeURIComponent: (str: string) => str, + }, + ); const filterString = filterGrants( this.req.query, GrantsListMapping.urlParams, ); - const url = `${urls.GRANTS}${filterString}&${params}`; + const url = `${urls.GRANTS}${filterString}${ + params.length > 0 ? `&${params}` : params + }`; return axios .get(url) diff --git a/src/static-assets/locations-board-constituency-view.json b/src/static-assets/locations-board-constituency-view.json new file mode 100644 index 0000000..1daaef3 --- /dev/null +++ b/src/static-assets/locations-board-constituency-view.json @@ -0,0 +1,1510 @@ +[ + { + "name": "Eastern Europe and Central Asia (EECA)", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Albania", + "value": "ALB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Armenia", + "value": "ARM", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Azerbaijan", + "value": "AZE", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Belarus", + "value": "BLR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Bulgaria", + "value": "BGR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Georgia", + "value": "GEO", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kazakhstan", + "value": "KAZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kosovo", + "value": "QNA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kyrgyzstan", + "value": "KGZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Moldova", + "value": "MDA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Montenegro", + "value": "MNE", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry EECA ECOM", + "value": "MCECOM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA ECUO", + "value": "MCECUO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA EHRN", + "value": "MCEHRN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA IHAU", + "value": "MCIHAU", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA PAS", + "value": "MCPAS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry HIV EECA APH", + "value": "MCEECAAPH", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "North Macedonia", + "value": "MKD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Romania", + "value": "ROU", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Russian Federation", + "value": "RUS", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Serbia", + "value": "SRB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Tajikistan", + "value": "TJK", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Turkmenistan", + "value": "TKM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ukraine", + "value": "UKR", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Uzbekistan", + "value": "UZB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "Eastern Mediterranean Region", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Afghanistan", + "value": "AFG", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Djibouti", + "value": "DJI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Egypt", + "value": "EGY", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Iran (Islamic Republic)", + "value": "IRN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Iraq", + "value": "IRQ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Morocco", + "value": "MAR", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry MENA HRA", + "value": "MCMENAHRA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry MENA Key Populations", + "value": "MCMENA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Middle East MER", + "value": "MCMER", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Pakistan", + "value": "PAK", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Somalia", + "value": "SOM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sudan", + "value": "SDN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Tunisia", + "value": "TUN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "Eastern and Southern Africa (ESA)", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Angola", + "value": "AGO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Botswana", + "value": "BWA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Burundi", + "value": "BDI", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Comoros", + "value": "COM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Eritrea", + "value": "ERI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Eswatini", + "value": "SWZ", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ethiopia", + "value": "ETH", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kenya", + "value": "KEN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Lesotho", + "value": "LSO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Madagascar", + "value": "MDG", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Malawi", + "value": "MWI", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mauritius", + "value": "MUS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mozambique", + "value": "MOZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry Africa ECSA-HC", + "value": "MCECSA-HC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Eastern Africa ANECCA", + "value": "MCANECCA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Eastern Africa IGAD", + "value": "MCIGAD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Eastern Africa KANCO", + "value": "MCKANCO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa ARASA", + "value": "MCARASA-ENDA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa E8", + "value": "MCE8", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa HIVOS", + "value": "MCHIVOS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa MOSASWA", + "value": "MCMOSASWA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa SADC", + "value": "MCSADC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa TIMS", + "value": "MCTIMS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa WHC", + "value": "MCWHC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Namibia", + "value": "NAM", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Rwanda", + "value": "RWA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "South Africa", + "value": "ZAF", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "South Sudan", + "value": "SSD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Tanzania (United Republic)", + "value": "TZA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Uganda", + "value": "UGA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Zambia", + "value": "ZMB", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Zanzibar", + "value": "QNB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Subnational" + } + }, + { + "name": "Zimbabwe", + "value": "ZWE", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "Latin America and the Caribbean (LAC)", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Belize", + "value": "BLZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Bolivia (Plurinational State)", + "value": "BOL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Colombia", + "value": "COL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Costa Rica", + "value": "CRI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Cuba", + "value": "CUB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Dominican Republic", + "value": "DOM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ecuador", + "value": "ECU", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "El Salvador", + "value": "SLV", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guatemala", + "value": "GTM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guyana", + "value": "GUY", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Haiti", + "value": "HTI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Honduras", + "value": "HND", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Jamaica", + "value": "JAM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry Americas CVC-COIN", + "value": "MCCVC/COIN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas EMMIE", + "value": "MCEMMIE", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas ICW", + "value": "MCICW", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas ORAS-CONHU", + "value": "MCORAS-CONHU", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas REDLACTRANS", + "value": "MCREDLACTRANS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas REDTRASEX", + "value": "MCREDTRASEX", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Caribbean CARICOM-PANCAP", + "value": "MCCARICOM/PANCAP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Caribbean MCC", + "value": "MCC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Central Americas REDCA", + "value": "MCREDCA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry HIV Latin America ALEP", + "value": "MCALEP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry TB LAC PIH", + "value": "MCPIH", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Nicaragua", + "value": "NIC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Panama", + "value": "PAN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Paraguay", + "value": "PRY", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Peru", + "value": "PER", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Suriname", + "value": "SUR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Venezuela", + "value": "VEN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "South East Asia (SEA)", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Bangladesh", + "value": "BGD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Bhutan", + "value": "BTN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "India", + "value": "IND", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Indonesia", + "value": "IDN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Korea (Democratic Peoples Republic)", + "value": "PRK", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry Asia IHAA", + "value": "MCIHAA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry East Asia and Pacific RAI", + "value": "MCRAI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry HIV SEA AFAO", + "value": "MCSEAAFAO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry South Asia", + "value": "MCSA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry South-Eastern Asia AFAO", + "value": "MCAFAO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry TB Asia TEAM", + "value": "MCASIATEAM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry TB Asia UNDP", + "value": "MCASIAUNDP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Myanmar", + "value": "MMR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Nepal", + "value": "NPL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sri Lanka", + "value": "LKA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Thailand", + "value": "THA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Timor-Leste", + "value": "TLS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "West Pacific Region (WPR)", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Cambodia", + "value": "KHM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Fiji", + "value": "FJI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Lao (Peoples Democratic Republic)", + "value": "LAO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Malaysia", + "value": "MYS", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mongolia", + "value": "MNG", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry East Asia and Pacific APN", + "value": "MCAPN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry East Asia and Pacific HIVOS", + "value": "MCISEAN-HIVOS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Western Pacific", + "value": "MCWP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Papua New Guinea", + "value": "PNG", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Philippines", + "value": "PHL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Solomon Islands", + "value": "SLB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Viet Nam", + "value": "VNM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "Western and Central Africa (WCA)", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Algeria", + "value": "DZA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Benin", + "value": "BEN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Burkina Faso", + "value": "BFA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Cabo Verde", + "value": "CPV", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Cameroon", + "value": "CMR", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Central African Republic", + "value": "CAF", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Chad", + "value": "TCD", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Congo", + "value": "COG", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Congo (Democratic Republic)", + "value": "COD", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Côte d'Ivoire", + "value": "CIV", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Gabon", + "value": "GAB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Gambia", + "value": "GMB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ghana", + "value": "GHA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guinea", + "value": "GIN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guinea-Bissau", + "value": "GNB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Liberia", + "value": "LBR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mali", + "value": "MLI", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mauritania", + "value": "MRT", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry TB WC Africa NTP/SRL", + "value": "MCNTPSRL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry West Africa ALCO", + "value": "MCOCAL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry West Africa ITPC", + "value": "MCITPC-WA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Western Africa ANCS", + "value": "MCANCS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Western Africa HI", + "value": "MCHI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Niger", + "value": "NER", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Nigeria", + "value": "NGA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sao Tome and Principe", + "value": "STP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Senegal", + "value": "SEN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sierra Leone", + "value": "SLE", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Togo", + "value": "TGO", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + } + ] + } +] diff --git a/src/static-assets/locations-portfolio-view.json b/src/static-assets/locations-portfolio-view.json new file mode 100644 index 0000000..a83306b --- /dev/null +++ b/src/static-assets/locations-portfolio-view.json @@ -0,0 +1,1466 @@ +[ + { + "name": "Rest of Africa", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Algeria", + "value": "DZA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Angola", + "value": "AGO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Botswana", + "value": "BWA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Burundi", + "value": "BDI", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Comoros", + "value": "COM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Djibouti", + "value": "DJI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Egypt", + "value": "EGY", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Eritrea", + "value": "ERI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Eswatini", + "value": "SWZ", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ethiopia", + "value": "ETH", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kenya", + "value": "KEN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Lesotho", + "value": "LSO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Madagascar", + "value": "MDG", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Malawi", + "value": "MWI", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mauritius", + "value": "MUS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Morocco", + "value": "MAR", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mozambique", + "value": "MOZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry Africa ECSA-HC", + "value": "MCECSA-HC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Eastern Africa ANECCA", + "value": "MCANECCA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Eastern Africa IGAD", + "value": "MCIGAD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Eastern Africa KANCO", + "value": "MCKANCO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa ARASA", + "value": "MCARASA-ENDA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa E8", + "value": "MCE8", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa HIVOS", + "value": "MCHIVOS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa MOSASWA", + "value": "MCMOSASWA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa SADC", + "value": "MCSADC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa TIMS", + "value": "MCTIMS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Southern Africa WHC", + "value": "MCWHC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry West Africa ITPC", + "value": "MCITPC-WA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Namibia", + "value": "NAM", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Rwanda", + "value": "RWA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Somalia", + "value": "SOM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "South Africa", + "value": "ZAF", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "South Sudan", + "value": "SSD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sudan", + "value": "SDN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Tanzania (United Republic)", + "value": "TZA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Tunisia", + "value": "TUN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Uganda", + "value": "UGA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Zambia", + "value": "ZMB", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Zanzibar", + "value": "QNB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Subnational" + } + }, + { + "name": "Zimbabwe", + "value": "ZWE", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "Rest of the World", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Afghanistan", + "value": "AFG", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Albania", + "value": "ALB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Armenia", + "value": "ARM", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Azerbaijan", + "value": "AZE", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Bangladesh", + "value": "BGD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Belarus", + "value": "BLR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Belize", + "value": "BLZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Bhutan", + "value": "BTN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Bolivia (Plurinational State)", + "value": "BOL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Bulgaria", + "value": "BGR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Cambodia", + "value": "KHM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Colombia", + "value": "COL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Costa Rica", + "value": "CRI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Cuba", + "value": "CUB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Dominican Republic", + "value": "DOM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ecuador", + "value": "ECU", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "El Salvador", + "value": "SLV", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Fiji", + "value": "FJI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Georgia", + "value": "GEO", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guatemala", + "value": "GTM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guyana", + "value": "GUY", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Haiti", + "value": "HTI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Honduras", + "value": "HND", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "India", + "value": "IND", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Indonesia", + "value": "IDN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Iran (Islamic Republic)", + "value": "IRN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Iraq", + "value": "IRQ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Jamaica", + "value": "JAM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kazakhstan", + "value": "KAZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Korea (Democratic Peoples Republic)", + "value": "PRK", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kosovo", + "value": "QNA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Kyrgyzstan", + "value": "KGZ", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Lao (Peoples Democratic Republic)", + "value": "LAO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Malaysia", + "value": "MYS", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Moldova", + "value": "MDA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mongolia", + "value": "MNG", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Montenegro", + "value": "MNE", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry Americas CVC-COIN", + "value": "MCCVC/COIN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas EMMIE", + "value": "MCEMMIE", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas ICW", + "value": "MCICW", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas ORAS-CONHU", + "value": "MCORAS-CONHU", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas REDLACTRANS", + "value": "MCREDLACTRANS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Americas REDTRASEX", + "value": "MCREDTRASEX", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Asia IHAA", + "value": "MCIHAA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Caribbean CARICOM-PANCAP", + "value": "MCCARICOM/PANCAP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Caribbean MCC", + "value": "MCC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Central Americas REDCA", + "value": "MCREDCA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA ECOM", + "value": "MCECOM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA ECUO", + "value": "MCECUO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA EHRN", + "value": "MCEHRN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA IHAU", + "value": "MCIHAU", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry EECA PAS", + "value": "MCPAS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry East Asia and Pacific APN", + "value": "MCAPN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry East Asia and Pacific HIVOS", + "value": "MCISEAN-HIVOS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry East Asia and Pacific RAI", + "value": "MCRAI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry HIV EECA APH", + "value": "MCEECAAPH", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry HIV Latin America ALEP", + "value": "MCALEP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry HIV SEA AFAO", + "value": "MCSEAAFAO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry MENA HRA", + "value": "MCMENAHRA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry MENA Key Populations", + "value": "MCMENA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Middle East MER", + "value": "MCMER", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry South Asia", + "value": "MCSA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry South-Eastern Asia AFAO", + "value": "MCAFAO", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry TB Asia TEAM", + "value": "MCASIATEAM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry TB Asia UNDP", + "value": "MCASIAUNDP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry TB LAC PIH", + "value": "MCPIH", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Western Pacific", + "value": "MCWP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Myanmar", + "value": "MMR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Nepal", + "value": "NPL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Nicaragua", + "value": "NIC", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "North Macedonia", + "value": "MKD", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Pakistan", + "value": "PAK", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Panama", + "value": "PAN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Papua New Guinea", + "value": "PNG", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Paraguay", + "value": "PRY", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Peru", + "value": "PER", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Philippines", + "value": "PHL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Romania", + "value": "ROU", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Russian Federation", + "value": "RUS", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Serbia", + "value": "SRB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Solomon Islands", + "value": "SLB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sri Lanka", + "value": "LKA", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Suriname", + "value": "SUR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Tajikistan", + "value": "TJK", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Thailand", + "value": "THA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Timor-Leste", + "value": "TLS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Turkmenistan", + "value": "TKM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ukraine", + "value": "UKR", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Uzbekistan", + "value": "UZB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Venezuela", + "value": "VEN", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Viet Nam", + "value": "VNM", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + } + ] + }, + { + "name": "West and Central Africa", + "value": null, + "extraInfo": { + "isDonor": false, + "isRecipient": false, + "level": null + }, + "items": [ + { + "name": "Benin", + "value": "BEN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Burkina Faso", + "value": "BFA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Cabo Verde", + "value": "CPV", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Cameroon", + "value": "CMR", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Central African Republic", + "value": "CAF", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Chad", + "value": "TCD", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Congo", + "value": "COG", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Congo (Democratic Republic)", + "value": "COD", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Côte d'Ivoire", + "value": "CIV", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Gabon", + "value": "GAB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Gambia", + "value": "GMB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Ghana", + "value": "GHA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guinea", + "value": "GIN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Guinea-Bissau", + "value": "GNB", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Liberia", + "value": "LBR", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mali", + "value": "MLI", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Mauritania", + "value": "MRT", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Multicountry TB WC Africa NTP/SRL", + "value": "MCNTPSRL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry West Africa ALCO", + "value": "MCOCAL", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Western Africa ANCS", + "value": "MCANCS", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Multicountry Western Africa HI", + "value": "MCHI", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Multicountry" + } + }, + { + "name": "Niger", + "value": "NER", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Nigeria", + "value": "NGA", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sao Tome and Principe", + "value": "STP", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Senegal", + "value": "SEN", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Sierra Leone", + "value": "SLE", + "extraInfo": { + "isDonor": false, + "isRecipient": true, + "level": "Country" + } + }, + { + "name": "Togo", + "value": "TGO", + "extraInfo": { + "isDonor": true, + "isRecipient": true, + "level": "Country" + } + } + ] + } +] diff --git a/src/utils/filtering/geographies.ts b/src/utils/filtering/geographies.ts index 04440fd..a3aafd9 100644 --- a/src/utils/filtering/geographies.ts +++ b/src/utils/filtering/geographies.ts @@ -1,23 +1,37 @@ import _ from 'lodash'; -import locations from '../../static-assets/locations.json'; +import locationsBoardConstituencyView from '../../static-assets/locations-board-constituency-view.json'; +import locationsPortfolioView from '../../static-assets/locations-portfolio-view.json'; +import locationsStandardView from '../../static-assets/locations.json'; + +interface Item { + name: string; + value: string | null; + items?: Item[]; +} export function getGeographyValues(geos: string[]) { const values: string[] = []; - locations.forEach(region => { + [ + ...locationsStandardView, + ...locationsPortfolioView, + ...locationsBoardConstituencyView, + ].forEach((region: Item) => { const fRegion = _.find(geos, (g: string) => g === region.name); if (fRegion) { - region.items.forEach(item => { + region.items?.forEach(item => { if (item.items) { item.items.forEach(subItem => { values.push(`'${subItem.value}'`); }); + } else { + values.push(`'${item.value}'`); } }); } else { - region.items.forEach(item => { + region.items?.forEach(item => { const fItem = _.find(geos, (g: string) => g === item.name); if (fItem) { - item.items.forEach(subItem => { + item.items?.forEach(subItem => { values.push(`'${subItem.value}'`); }); } From 2e1d4cbaf047e86dd1ad18673f0772deb86a6647 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Fri, 12 Jul 2024 16:14:16 +0300 Subject: [PATCH 41/44] fix: typo in funding requests --- src/controllers/fundingrequests.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/fundingrequests.controller.ts b/src/controllers/fundingrequests.controller.ts index 7e0b86f..f61b57e 100644 --- a/src/controllers/fundingrequests.controller.ts +++ b/src/controllers/fundingrequests.controller.ts @@ -58,10 +58,10 @@ export class FundingRequestsController { : '--', grant: subitem.grant, startingDate: subitem.startingDate - ? moment(subitem.startDate).format('DD MMM YYYY') + ? moment(subitem.startingDate).format('DD MMM YYYY') : '--', endingDate: subitem.endingDate - ? moment(subitem.endDate).format('DD MMM YYYY') + ? moment(subitem.endingDate).format('DD MMM YYYY') : '--', principalRecipient: subitem.principalRecipient, }; From 943edd40f5a6d1c669298e778ab3426e2cef3dbc Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Fri, 12 Jul 2024 16:33:00 +0300 Subject: [PATCH 42/44] fix: concat bugfix --- src/controllers/location.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/location.controller.ts b/src/controllers/location.controller.ts index 93d6f4a..5ab3b42 100644 --- a/src/controllers/location.controller.ts +++ b/src/controllers/location.controller.ts @@ -240,7 +240,7 @@ export class LocationController { ); } if (mcSubSubOptions && mcSubSubOptions.length > 0) { - items.concat( + items = items.concat( _.orderBy( mcSubSubOptions.map((mc: any) => ({ name: _.get( From d870a9a3ff5fc977ceef70e6bc5fe8e0308820c2 Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Fri, 12 Jul 2024 16:48:58 +0300 Subject: [PATCH 43/44] feat: financial metrics cycles --- src/config/mapping/budgets/cycles.json | 3 +- src/controllers/budgets.controller.ts | 48 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/config/mapping/budgets/cycles.json b/src/config/mapping/budgets/cycles.json index e60ffe2..4e2eada 100644 --- a/src/config/mapping/budgets/cycles.json +++ b/src/config/mapping/budgets/cycles.json @@ -2,5 +2,6 @@ "dataPath": "value", "cycleFrom": "implementationPeriod.periodFrom", "cycleTo": "implementationPeriod.periodTo", - "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" + "urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'GrantBudget_ReferenceRate')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc", + "urlParamsMetrics": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'ImplementationPeriodFinancialMetricAmount')/groupby((implementationPeriod/periodFrom,implementationPeriod/periodTo))&$orderby=implementationPeriod/periodFrom asc" } diff --git a/src/controllers/budgets.controller.ts b/src/controllers/budgets.controller.ts index ac77ba2..e1c0d69 100644 --- a/src/controllers/budgets.controller.ts +++ b/src/controllers/budgets.controller.ts @@ -1290,4 +1290,52 @@ export class BudgetsController { ) .catch(handleDataApiError); } + + @get('/financial-metrics/cycles') + @response(200) + async metricsCycles() { + const filterString = filterFinancialIndicators( + this.req.query, + BudgetsCyclesMapping.urlParamsMetrics, + 'implementationPeriod/grant/geography/code', + 'implementationPeriod/grant/activityArea/name', + ); + const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`; + + return axios + .get(url) + .then((resp: AxiosResponse) => { + const rawData = _.get(resp.data, BudgetsCyclesMapping.dataPath, []); + + const data = _.orderBy( + _.map( + _.filter( + rawData, + item => + _.get(item, BudgetsCyclesMapping.cycleFrom, null) !== null, + ), + (item, index) => { + const from = _.get(item, BudgetsCyclesMapping.cycleFrom, ''); + const to = _.get(item, BudgetsCyclesMapping.cycleTo, ''); + + let value = from; + + if (from && to) { + value = `${from} - ${to}`; + } + + return { + name: `Cycle ${index + 1}`, + value, + }; + }, + ), + item => parseInt(item.value.toString().split(' - ')[0], 10), + 'asc', + ); + + return {data}; + }) + .catch(handleDataApiError); + } } From 62a5d716573359de529edb0e741080feb4e197ba Mon Sep 17 00:00:00 2001 From: Stefanos Hadjipetrou Date: Mon, 15 Jul 2024 17:22:38 +0300 Subject: [PATCH 44/44] chore: change api base url --- src/config/urls/index.json | 104 ++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/config/urls/index.json b/src/config/urls/index.json index cfc7ec9..e5e9044 100644 --- a/src/config/urls/index.json +++ b/src/config/urls/index.json @@ -1,55 +1,55 @@ { - "grants": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements/?$count=true&", - "grantsNoCount": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements", - "results": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VReportingResults", - "documents": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VProgramDocuments", - "disbursements": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementDisbursements", - "commitments": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementCommitments", - "vcommitments": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementCommitments", - "grantDetailGrants": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriods", - "grantPeriods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriods", - "vgrantPeriods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementImplementationPeriods", - "grantCycles": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/grantCycles", - "grantIPGoalsObjectives": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriodGoalsAndObjectives", - "grantDetailDisbursements": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementDisbursements", - "budgets": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriodDetailedBudgets", - "allocations": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Allocations", - "eligibility": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VEligibility", + "grants": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreements/?$count=true&", + "grantsNoCount": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreements", + "results": "https://fetch.theglobalfund.org/v4/odata/VReportingResults", + "documents": "https://fetch.theglobalfund.org/v4/odata/VProgramDocuments", + "disbursements": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreementDisbursements", + "commitments": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementCommitments", + "vcommitments": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreementCommitments", + "grantDetailGrants": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementImplementationPeriods", + "grantPeriods": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementImplementationPeriods", + "vgrantPeriods": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreementImplementationPeriods", + "grantCycles": "https://fetch.theglobalfund.org/v4/odata/grantCycles", + "grantIPGoalsObjectives": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementImplementationPeriodGoalsAndObjectives", + "grantDetailDisbursements": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementDisbursements", + "budgets": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementImplementationPeriodDetailedBudgets", + "allocations": "https://fetch.theglobalfund.org/v4/odata/Allocations", + "eligibility": "https://fetch.theglobalfund.org/v4/odata/VEligibility", "geojson": "https://data.theglobalfund.org/static/simple.geo.json", - "countrycontactinfo": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VContacts", - "pledgescontributions": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/PledgesAndContributions", - "performancerating": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementProgressUpdates", - "performanceframework": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GrantAgreementImplementationPeriodPerformanceFrameworks", - "fundingrequests": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/FundingRequests", - "multicountries": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/MultiCountries", - "indicators": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VNationalHealthAndDevelopmentIndicators", - "filteroptionslocations": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/GeographicAreas?$select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId&$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members)))&$filter=geographicAreaName eq 'World'", - "filteroptionsmulticountries": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements?$apply=filter(multiCountryName%20ne%20null)/groupby((MultiCountryName,GeographicAreaCode_ISO3),aggregate(GrantAgreementId%20with%20countdistinct%20as%20count))", - "filteroptionscomponents": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Components", - "filteroptionspartnertypes": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements?$apply=groupby((principalRecipientClassificationName,principalRecipientClassificationId,principalRecipientSubClassificationName,principalRecipientSubClassificationId,principalRecipientName,principalRecipientId))", - "filteroptionsstatus": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreements?$apply=groupby((grantAgreementStatusTypeName))", - "filteroptionsstatus1": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/VGrantAgreementImplementationPeriods?$apply=groupby((implementationPeriodStatusTypeName))", - "filteroptionsdonors": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/donors/?$filter=donorId%20eq%20c06eaea9-c81b-442b-b403-dc348f3734eb%20or%20donorId%20eq%2025188dfc-7567-4e08-b8d0-088a6b83f238%20or%20donorId%20eq%20641c05fa-129d-4b57-a213-b5f6852ddc5f%20or%20donorid%20eq%20cc30fc59-b2fa-49e3-aa5a-762727daa68c&$expand=members($expand=members($expand=members($expand=members)))", - "filteroptionsreplenishmentperiods": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/replenishmentperiods", - "multicountriescountriesdata": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", - "FILTER_OPTIONS_GEOGRAPHIES": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", - "FILTER_OPTIONS_GEOGRAPHIES_BOARD_CONSTITUENCY_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_BoardConstituencyView?&$filter=parentId ne null AND children/any(c: c ne null)&$expand=children($expand=children($expand=children))", - "FILTER_OPTIONS_GEOGRAPHIES_PORTFOLIO_VIEW": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies_PortfolioView?&$filter=parentId ne null AND children/any(c: c ne null)&$expand=children($expand=children($expand=children))", - "FILTER_OPTIONS_COMPONENTS_GROUPED": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreasGrouped?$filter=parentId eq null", - "FILTER_OPTIONS_COMPONENTS_UNGROUPED": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/ActivityAreas?$filter=type eq 'Component'", - "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc", - "FILTER_OPTIONS_DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors?$apply=groupby((id,name,type/name))", - "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants?$apply=groupby((principalRecipient/type/parent/name,principalRecipient/type/parent/code,principalRecipient/type/name,principalRecipient/type/code,principalRecipient/name))", - "FILTER_OPTIONS_RESULTS_COMPONENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allProgrammaticIndicators?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((indicatorName,activityArea/name))", - "FILTER_OPTIONS_STATUS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Statuses", - "FINANCIAL_INDICATORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allFinancialIndicators", - "DONORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Donors", - "PROGRAMMATIC_INDICATORS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/allProgrammaticIndicators", - "COORDINATING_MECHANISMS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/CoordinatingMechanisms", - "GRANTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Grants", - "HIERARCHICAL_GEOGRAPHIES": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", - "GEOGRAPHIES": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Geographies", - "FUNDING_REQUESTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/FundingRequests", - "DOCUMENTS": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Documents", - "ELIGIBILITY": "https://api-gf-api-gf-02.azurewebsites.net/v4/odata/Eligibility" + "countrycontactinfo": "https://fetch.theglobalfund.org/v4/odata/VContacts", + "pledgescontributions": "https://fetch.theglobalfund.org/v4/odata/PledgesAndContributions", + "performancerating": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementProgressUpdates", + "performanceframework": "https://fetch.theglobalfund.org/v4/odata/GrantAgreementImplementationPeriodPerformanceFrameworks", + "fundingrequests": "https://fetch.theglobalfund.org/v4/odata/FundingRequests", + "multicountries": "https://fetch.theglobalfund.org/v4/odata/MultiCountries", + "indicators": "https://fetch.theglobalfund.org/v4/odata/VNationalHealthAndDevelopmentIndicators", + "filteroptionslocations": "https://fetch.theglobalfund.org/v4/odata/GeographicAreas?$select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId&$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members($select=geographicAreaCode_ISO3,geographicAreaCode_ISO2,geographicAreaId,geographicAreaName,geographicAreaParentId;$expand=members)))&$filter=geographicAreaName eq 'World'", + "filteroptionsmulticountries": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreements?$apply=filter(multiCountryName%20ne%20null)/groupby((MultiCountryName,GeographicAreaCode_ISO3),aggregate(GrantAgreementId%20with%20countdistinct%20as%20count))", + "filteroptionscomponents": "https://fetch.theglobalfund.org/v4/odata/Components", + "filteroptionspartnertypes": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreements?$apply=groupby((principalRecipientClassificationName,principalRecipientClassificationId,principalRecipientSubClassificationName,principalRecipientSubClassificationId,principalRecipientName,principalRecipientId))", + "filteroptionsstatus": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreements?$apply=groupby((grantAgreementStatusTypeName))", + "filteroptionsstatus1": "https://fetch.theglobalfund.org/v4/odata/VGrantAgreementImplementationPeriods?$apply=groupby((implementationPeriodStatusTypeName))", + "filteroptionsdonors": "https://fetch.theglobalfund.org/v4/odata/donors/?$filter=donorId%20eq%20c06eaea9-c81b-442b-b403-dc348f3734eb%20or%20donorId%20eq%2025188dfc-7567-4e08-b8d0-088a6b83f238%20or%20donorId%20eq%20641c05fa-129d-4b57-a213-b5f6852ddc5f%20or%20donorid%20eq%20cc30fc59-b2fa-49e3-aa5a-762727daa68c&$expand=members($expand=members($expand=members($expand=members)))", + "filteroptionsreplenishmentperiods": "https://fetch.theglobalfund.org/v4/odata/replenishmentperiods", + "multicountriescountriesdata": "https://fetch.theglobalfund.org/v4/odata/MultiCountries?$expand=MultiCountryComposition($select=GeographicArea;$expand=GeographicArea($select=GeographicAreaCode_ISO3))&$select=MultiCountryName,MultiCountryComposition", + "FILTER_OPTIONS_GEOGRAPHIES": "https://fetch.theglobalfund.org/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_GEOGRAPHIES_BOARD_CONSTITUENCY_VIEW": "https://fetch.theglobalfund.org/v4/odata/Geographies_BoardConstituencyView?&$filter=parentId ne null AND children/any(c: c ne null)&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_GEOGRAPHIES_PORTFOLIO_VIEW": "https://fetch.theglobalfund.org/v4/odata/Geographies_PortfolioView?&$filter=parentId ne null AND children/any(c: c ne null)&$expand=children($expand=children($expand=children))", + "FILTER_OPTIONS_COMPONENTS_GROUPED": "https://fetch.theglobalfund.org/v4/odata/ActivityAreasGrouped?$filter=parentId eq null", + "FILTER_OPTIONS_COMPONENTS_UNGROUPED": "https://fetch.theglobalfund.org/v4/odata/ActivityAreas?$filter=type eq 'Component'", + "FILTER_OPTIONS_REPLENISHMENT_PERIODS": "https://fetch.theglobalfund.org/v4/odata/allFinancialIndicators?$apply=filter(financialDataSet eq 'Pledges_Contributions')/groupby((periodCovered))&$orderby=periodCovered asc", + "FILTER_OPTIONS_DONORS": "https://fetch.theglobalfund.org/v4/odata/Donors?$apply=groupby((id,name,type/name))", + "FILTER_OPTIONS_PRINCIPAL_RECIPIENTS": "https://fetch.theglobalfund.org/v4/odata/Grants?$apply=groupby((principalRecipient/type/parent/name,principalRecipient/type/parent/code,principalRecipient/type/name,principalRecipient/type/code,principalRecipient/name))", + "FILTER_OPTIONS_RESULTS_COMPONENTS": "https://fetch.theglobalfund.org/v4/odata/allProgrammaticIndicators?$apply=filter(programmaticDataset eq 'Annual_Results')/groupby((indicatorName,activityArea/name))", + "FILTER_OPTIONS_STATUS": "https://fetch.theglobalfund.org/v4/odata/Statuses", + "FINANCIAL_INDICATORS": "https://fetch.theglobalfund.org/v4/odata/allFinancialIndicators", + "DONORS": "https://fetch.theglobalfund.org/v4/odata/Donors", + "PROGRAMMATIC_INDICATORS": "https://fetch.theglobalfund.org/v4/odata/allProgrammaticIndicators", + "COORDINATING_MECHANISMS": "https://fetch.theglobalfund.org/v4/odata/CoordinatingMechanisms", + "GRANTS": "https://fetch.theglobalfund.org/v4/odata/Grants", + "HIERARCHICAL_GEOGRAPHIES": "https://fetch.theglobalfund.org/v4/odata/Geographies?$filter=level eq 'World'&$expand=children($expand=children($expand=children))", + "GEOGRAPHIES": "https://fetch.theglobalfund.org/v4/odata/Geographies", + "FUNDING_REQUESTS": "https://fetch.theglobalfund.org/v4/odata/FundingRequests", + "DOCUMENTS": "https://fetch.theglobalfund.org/v4/odata/Documents", + "ELIGIBILITY": "https://fetch.theglobalfund.org/v4/odata/Eligibility" }