Skip to content

Commit

Permalink
Merge pull request #27 from globalfund/feat/DE-1397
Browse files Browse the repository at this point in the history
DE-1397 | Investments by Disbursement Area new chart block
  • Loading branch information
stephanoshadjipetrou authored Sep 17, 2024
2 parents 2abf691 + 4fc88b8 commit 494a83c
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"dataPath": "value",
"level1Field": "financialCategory.parent.name",
"level2Field": "financialCategory.name",
"valueField": "value",
"cycle": "periodFrom",
"nodeColors": ["#252C34", "#252C34", "#252C34"],
"urlParams": "?$apply=filter(contains(indicatorName, 'reference') AND financialDataSet eq 'Disbursement_ReferenceRate'<filterString>)/groupby((financialCategory/name,financialCategory/parent/name,financialCategory/parent/parent/name),aggregate(actualAmount with sum as value))"
}
209 changes: 209 additions & 0 deletions src/controllers/disbursements.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import axios, {AxiosResponse} from 'axios';
import _ from 'lodash';
import BarChartFieldsMapping from '../config/mapping/disbursements/barChart.json';
import DisbursementsCyclesMapping from '../config/mapping/disbursements/cycles.json';
import HolisticGrantInvestmentsMapping from '../config/mapping/disbursements/holisticGrantInvestments.json';
import LineChartFieldsMapping from '../config/mapping/disbursements/lineChart.json';
import TableFieldsMapping from '../config/mapping/disbursements/table.json';
import FinancialInsightsStatsMapping from '../config/mapping/financialInsightsStats.json';
import urls from '../config/urls/index.json';
import {BudgetSankeyChartData} from '../interfaces/budgetSankey';
import CycleMapping from '../static-assets/cycle-mapping.json';
import {handleDataApiError} from '../utils/dataApiError';
import {filterFinancialIndicators} from '../utils/filtering/financialIndicators';
Expand Down Expand Up @@ -531,6 +533,213 @@ export class DisbursementsController {
.catch(handleDataApiError);
}

@get('/disbursements/hgi/sankey')
@response(200)
async holisticGrantInvestmentsSankey() {
const filterString = filterFinancialIndicators(
this.req.query,
HolisticGrantInvestmentsMapping.urlParams,
[
'implementationPeriod/grant/geography/name',
'implementationPeriod/grant/geography/code',
],
'implementationPeriod/grant/activityArea/name',
'disbursement',
);
const url = `${urls.FINANCIAL_INDICATORS}/${filterString}&t=t`;

return axios
.get(url)
.then((resp: AxiosResponse) => {
const rawData = _.get(
resp.data,
HolisticGrantInvestmentsMapping.dataPath,
[],
);

const data: BudgetSankeyChartData = {
nodes: [
{
name: 'Total Disbursed',
level: 0,
itemStyle: {
color: HolisticGrantInvestmentsMapping.nodeColors[0],
},
},
],
links: [],
};

const groupedDataLevel1 = _.groupBy(
rawData,
HolisticGrantInvestmentsMapping.level1Field,
);
_.forEach(groupedDataLevel1, (level1Data, level1) => {
data.nodes.push({
name: level1,
level: 1,
itemStyle: {
color: HolisticGrantInvestmentsMapping.nodeColors[1],
},
});
data.links.push({
source: data.nodes[0].name,
target: level1,
value: _.sumBy(
level1Data,
HolisticGrantInvestmentsMapping.valueField,
),
});

const groupedDataLevel2 = _.groupBy(
level1Data,
HolisticGrantInvestmentsMapping.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: HolisticGrantInvestmentsMapping.nodeColors[2],
},
});
data.links.push({
source: level1,
target: level2inLevel1 ? `${level2}1` : level2,
value: _.sumBy(
level2Data,
HolisticGrantInvestmentsMapping.valueField,
),
});
});
});
data.nodes = _.uniqBy(data.nodes, 'name');
data.nodes = _.orderBy(
_.map(data.nodes, node => {
return {
...node,
value: _.sumBy(
_.filter(data.links, {target: node.name}),
'value',
),
};
}),
'value',
'asc',
);
const rootNodeIndex = _.findIndex(data.nodes, {level: 0});
data.nodes[rootNodeIndex].value = _.sumBy(data.nodes, node =>
node.level === 1 && node.value ? node.value : 0,
);
data.nodes = _.orderBy(
data.nodes,
node => {
if (node.level === 0) {
return [node.value, node.value];
}
if (node.level === 1) {
const nodeLinks = _.filter(data.links, {source: node.name});
return [_.sumBy(nodeLinks, 'value'), node.value];
}
const source = _.find(data.links, {target: node.name})?.source;
return [_.find(data.nodes, {name: source})?.value, node.value];
},
['desc', 'desc'],
);
data.nodes = _.orderBy(data.nodes, 'level', 'asc');
data.links = _.orderBy(data.links, 'value', 'desc');
return {data};
})
.catch(handleDataApiError);
}

@get('/disbursements/hgi/table')
@response(200)
async holisticGrantInvestmentsTable() {
const filterString = filterFinancialIndicators(
this.req.query,
HolisticGrantInvestmentsMapping.urlParams,
[
'implementationPeriod/grant/geography/name',
'implementationPeriod/grant/geography/code',
],
'implementationPeriod/grant/activityArea/name',
'disbursement',
);
const url = `${urls.FINANCIAL_INDICATORS}/${filterString}`;

return axios
.get(url)
.then((resp: AxiosResponse) => {
const rawData = _.get(
resp.data,
HolisticGrantInvestmentsMapping.dataPath,
[],
);
const groupedByLevel1 = _.groupBy(
rawData,
HolisticGrantInvestmentsMapping.level1Field,
);

const data: {
name: string;
amount: number;
_children: {
name: string;
amount: number;
_children?: {
name: string;
amount: number;
}[];
}[];
}[] = [];

_.forEach(groupedByLevel1, (level1Data, level1) => {
const grouepdByLevel2 = _.groupBy(
level1Data,
HolisticGrantInvestmentsMapping.level2Field,
);
const level1Amount = _.sumBy(
level1Data,
HolisticGrantInvestmentsMapping.valueField,
);
const level1Children = _.map(
grouepdByLevel2,
(level2Data, level2) => {
const level2Amount = _.sumBy(
level2Data,
HolisticGrantInvestmentsMapping.valueField,
);
return {
name: level2,
amount: level2Amount,
};
},
);
data.push({
name: level1,
amount: level1Amount,
_children: level1Children,
});
});

return {
data: [
{
name: 'Total Disbursed',
amount: _.sumBy(data, 'amount'),
_children: data,
},
],
};
})
.catch(handleDataApiError);
}

@get('/disbursements/cycles')
@response(200)
async cycles() {
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/budgetSankey.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface BudgetSankeyChartNode {
name: string;
level: number;
value?: number;
itemStyle?: {
color: string;
};
Expand Down

0 comments on commit 494a83c

Please sign in to comment.