Skip to content

Commit

Permalink
fix(ImpactTables): Fixes the way the Impact Tables are created and ag…
Browse files Browse the repository at this point in the history
…gregated
  • Loading branch information
KevSanchez committed Jan 29, 2025
1 parent 56d38ef commit 2b96d93
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 221 deletions.
44 changes: 26 additions & 18 deletions api/src/modules/impact/base-impact.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export class BaseImpactService {
treeOptions,
)
).map((entity: LOCATION_TYPES) => {
return { name: entity, children: [] };
return { id: entity, name: entity, children: [] };
});

default:
Expand Down Expand Up @@ -413,34 +413,39 @@ export class BaseImpactService {
// as using Math.max(...impactTable.map(...)) since the call stack will be exceeded because of no of arguments
const yearsWithData: Set<number> = new Set();

const indicatorEntityMap: ImpactDataTableAuxMap<RowsValues> = new Map();
const indicatorEntityYearMap: ImpactDataTableAuxMap<RowsValues> = new Map();

// Convert the flat structure on array to tree of Maps for easier access
for (const impactTableData of dataForImpactTable) {
let entityMap: Map<string, Map<number, RowsValues>> | undefined =
indicatorEntityMap.get(impactTableData.indicatorId);

if (!entityMap) {
entityMap = new Map();
indicatorEntityMap.set(impactTableData.indicatorId, entityMap);
let indicatorEntityMap: Map<string, Map<number, RowsValues>> | undefined =
indicatorEntityYearMap.get(impactTableData.indicatorId);

if (!indicatorEntityMap) {
indicatorEntityMap = new Map();
indicatorEntityYearMap.set(
impactTableData.indicatorId,
indicatorEntityMap,
);
}

let yearMap: Map<number, RowsValues> | undefined = entityMap.get(
impactTableData.name,
);
if (!yearMap) {
yearMap = new Map();
entityMap.set(impactTableData.name, yearMap);
let entityYearMap: Map<number, RowsValues> | undefined =
indicatorEntityMap.get(impactTableData.identifier);
if (!entityYearMap) {
entityYearMap = new Map();
indicatorEntityMap.set(impactTableData.identifier, entityYearMap);
}

yearMap.set(impactTableData.year, dataToRowsValuesFunc(impactTableData));
entityYearMap.set(
impactTableData.year,
dataToRowsValuesFunc(impactTableData),
);

yearsWithData.add(impactTableData.year);
}

const lastYearWithData: number = Math.max(...yearsWithData.values());

return [indicatorEntityMap, lastYearWithData];
return [indicatorEntityYearMap, lastYearWithData];
}

/**
Expand Down Expand Up @@ -498,12 +503,12 @@ export class BaseImpactService {
}

/**
* Small helper function to get the combined IndicatorId+EntityName+Year to facilitate pre processing of
* Small helper function to get the combined IndicatorId+EntityIdentifier+Year to facilitate pre processing of
* Impact Table Data before building the impact table
* @param data
*/
static getImpactTableDataKey(data: ImpactTableData): string {
return data.indicatorId + '-' + data.name + '-' + data.year;
return data.indicatorId + '-' + data.identifier + '-' + data.year;
}

static sortRowValueByYear(
Expand All @@ -514,6 +519,9 @@ export class BaseImpactService {
}
}

// Helper data structure that represents the aggregated data for each combination of Indicator, Entity and Year
// Indicator -> N Entities -> N Years -> RowsValues (data that represents the actual impact)
// Map<IndicatorId, Map<EntityIdentifier, Map<Year, RowsValues>>>
export type ImpactDataTableAuxMap<T extends AnyImpactTableRowsValues> = Map<
string,
Map<string, Map<number, T>>
Expand Down
109 changes: 48 additions & 61 deletions api/src/modules/impact/comparison/actual-vs-scenario.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,7 @@ export class ActualVsScenarioImpactService {
dto.sortingOrder,
);

const paginatedTable: any = BaseImpactService.paginateTable(
impactTable,
fetchSpecification,
);

return paginatedTable;
return BaseImpactService.paginateTable(impactTable, fetchSpecification);
}

private buildActualVsScenarioImpactTable(
Expand Down Expand Up @@ -131,15 +126,10 @@ export class ActualVsScenarioImpactService {
lastYearWithData,
);

// copy and populate tree skeleton for each indicator
const impactTableEntitySkeleton: ActualVsScenarioImpactTableRows[] =
this.buildActualVsScenarioImpactTableRowsSkeleton(entityTree);

for (const entity of impactTableEntitySkeleton) {
this.populateValuesRecursively(entity, entityMap, rangeOfYears);
}

impactTableDataByIndicator.rows = impactTableEntitySkeleton;
impactTableDataByIndicator.rows = entityTree.map(
(entity: ImpactTableEntityType) =>
this.buildImpactTableRecursively(entity, entityMap, rangeOfYears),
);

impactTableDataByIndicator.yearSum = this.calculateIndicatorSumByYear(
entityMap,
Expand Down Expand Up @@ -269,53 +259,65 @@ export class ActualVsScenarioImpactService {
}

/**
* @description Recursive function that populates and returns
* aggregated data of parent entity and all its children
* @description Constructs the Impact Table and populates its aggregated values recursively
* according to the given entity and its children
* @param entity contains the entity tree to that will be used to build the Impact Table
* @param entityYearMap contains the actual values data for all entities
*/
private populateValuesRecursively(
entity: ActualVsScenarioImpactTableRows,
entityDataMap: Map<
private buildImpactTableRecursively(
entity: ImpactTableEntityType,
entityYearMap: Map<
string,
Map<number, ActualVsScenarioImpactTableRowsValues>
>,
rangeOfYears: number[],
): ActualVsScenarioImpactTableRowsValues[] {
entity.values = [];
for (const year of rangeOfYears) {
const rowsValues: ActualVsScenarioImpactTableRowsValues = {
year: year,
value: 0,
comparedScenarioValue: 0,
absoluteDifference: 0,
percentageDifference: 0,
isProjected: false,
};
entity.values.push(rowsValues);
}
): ActualVsScenarioImpactTableRows {
const impactTableRow: ActualVsScenarioImpactTableRows = {
name: entity.name || '',
values: rangeOfYears.map(
(year: number) =>
({
year,
value: 0,
comparedScenarioValue: 0,
absoluteDifference: 0,
percentageDifference: 0,
isProjected: false,
} as ActualVsScenarioImpactTableRowsValues),
),
children:
entity.children?.length > 0
? entity.children.map((childEntity: ImpactTableEntityType) =>
this.buildImpactTableRecursively(
childEntity,
entityYearMap,
rangeOfYears,
),
)
: [],
};

const valuesToAggregate: ActualVsScenarioImpactTableRowsValues[][] = [];
const selfData:
| Map<number, ActualVsScenarioImpactTableRowsValues>
| undefined = entityDataMap.get(entity.name);
| undefined = entityYearMap.get(entity.id);

if (selfData) {
const sortedSelfData: ActualVsScenarioImpactTableRowsValues[] =
Array.from(selfData.values()).sort(
BaseImpactService.sortRowValueByYear,
);
valuesToAggregate.push(sortedSelfData);
}
entity.children.forEach((childEntity: ActualVsScenarioImpactTableRows) => {
//first aggregate data of child entity and then add returned value for parents aggregation
const childValues: ActualVsScenarioImpactTableRowsValues[] =
this.populateValuesRecursively(
childEntity,
entityDataMap,
rangeOfYears,
);
valuesToAggregate.push(childValues);
});

for (const [valueIndex, entityRowValue] of entity.values.entries()) {
for (const childEntity of impactTableRow.children) {
valuesToAggregate.push(childEntity.values);
}

for (const [
valueIndex,
entityRowValue,
] of impactTableRow.values.entries()) {
for (const valueToAggregate of valuesToAggregate) {
entityRowValue.value += valueToAggregate[valueIndex].value;
entityRowValue.comparedScenarioValue +=
Expand All @@ -340,22 +342,7 @@ export class ActualVsScenarioImpactService {
: percentageDifference;
}
}
return entity.values;
}

private buildActualVsScenarioImpactTableRowsSkeleton(
entities: ImpactTableEntityType[],
): ActualVsScenarioImpactTableRows[] {
return entities.map((item: ImpactTableEntityType) => {
return {
name: item.name || '',
children:
item.children?.length > 0
? this.buildActualVsScenarioImpactTableRowsSkeleton(item.children)
: [],
values: [],
};
});
return impactTableRow;
}

// For all indicators, entities are sorted by the value of the given sortingYear, in the order given by sortingOrder
Expand Down
109 changes: 46 additions & 63 deletions api/src/modules/impact/comparison/scenario-vs-scenario.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,7 @@ export class ScenarioVsScenarioImpactService {
dto.sortingOrder,
);

const paginatedTable: any = BaseImpactService.paginateTable(
impactTable,
fetchSpecification,
);

return paginatedTable;
return BaseImpactService.paginateTable(impactTable, fetchSpecification);
}

private buildImpactTable(
Expand Down Expand Up @@ -150,15 +145,10 @@ export class ScenarioVsScenarioImpactService {
lastYearWithData,
);

// copy and populate tree skeleton for each indicator
const impactTableEntitySkeleton: ScenarioVsScenarioImpactTableRows[] =
this.buildScenarioVsScenarioImpactTableRowsSkeleton(entityTree);

for (const entity of impactTableEntitySkeleton) {
this.populateValuesRecursively(entity, entityMap, rangeOfYears);
}

impactTableDataByIndicator.rows = impactTableEntitySkeleton;
impactTableDataByIndicator.rows = entityTree.map(
(entity: ImpactTableEntityType) =>
this.buildImpactTableRecursively(entity, entityMap, rangeOfYears),
);

impactTableDataByIndicator.yearSum = this.calculateIndicatorSumByYear(
entityMap,
Expand All @@ -179,34 +169,48 @@ export class ScenarioVsScenarioImpactService {
}

/**
* @description Recursive function that populates and returns
* aggregated data of parent entity and all its children
* @description Constructs the Impact Table and populates its aggregated values recursively
* according to the given entity and its children
* @param entity contains the entity tree to that will be used to build the Impact Table
* @param entityYearMap contains the actual values data for all entities
*/
private populateValuesRecursively(
entity: ScenarioVsScenarioImpactTableRows,
entityDataMap: Map<
private buildImpactTableRecursively(
entity: ImpactTableEntityType,
entityYearMap: Map<
string,
Map<number, ScenarioVsScenarioImpactTableRowsValues>
>,
rangeOfYears: number[],
): ScenarioVsScenarioImpactTableRowsValues[] {
entity.values = [];
for (const year of rangeOfYears) {
const rowsValues: ScenarioVsScenarioImpactTableRowsValues = {
year: year,
baseScenarioValue: 0,
comparedScenarioValue: 0,
absoluteDifference: 0,
percentageDifference: 0,
isProjected: false,
};
entity.values.push(rowsValues);
}
): ScenarioVsScenarioImpactTableRows {
const impactTableRow: ScenarioVsScenarioImpactTableRows = {
name: entity.name || '',
values: rangeOfYears.map(
(year: number) =>
({
year,
baseScenarioValue: 0,
comparedScenarioValue: 0,
absoluteDifference: 0,
percentageDifference: 0,
isProjected: false,
} as ScenarioVsScenarioImpactTableRowsValues),
),
children:
entity.children?.length > 0
? entity.children.map((childEntity: ImpactTableEntityType) =>
this.buildImpactTableRecursively(
childEntity,
entityYearMap,
rangeOfYears,
),
)
: [],
};

const valuesToAggregate: ScenarioVsScenarioImpactTableRowsValues[][] = [];
const selfData:
| Map<number, ScenarioVsScenarioImpactTableRowsValues>
| undefined = entityDataMap.get(entity.name);
| undefined = entityYearMap.get(entity.id);
if (selfData) {
const sortedSelfData: ScenarioVsScenarioImpactTableRowsValues[] =
Array.from(selfData.values()).sort(
Expand All @@ -215,20 +219,14 @@ export class ScenarioVsScenarioImpactService {
valuesToAggregate.push(sortedSelfData);
}

entity.children.forEach(
(childEntity: ScenarioVsScenarioImpactTableRows) => {
//first aggregate data of child entity and then add returned value for parents aggregation
const childValues: ScenarioVsScenarioImpactTableRowsValues[] =
this.populateValuesRecursively(
childEntity,
entityDataMap,
rangeOfYears,
);
valuesToAggregate.push(childValues);
},
);
for (const childEntity of impactTableRow.children) {
valuesToAggregate.push(childEntity.values);
}

for (const [valueIndex, entityRowValue] of entity.values.entries()) {
for (const [
valueIndex,
entityRowValue,
] of impactTableRow.values.entries()) {
for (const valueToAggregate of valuesToAggregate) {
entityRowValue.baseScenarioValue +=
valueToAggregate[valueIndex].baseScenarioValue;
Expand Down Expand Up @@ -257,22 +255,7 @@ export class ScenarioVsScenarioImpactService {
: percentageDifference;
}
}
return entity.values;
}

private buildScenarioVsScenarioImpactTableRowsSkeleton(
entities: ImpactTableEntityType[],
): ScenarioVsScenarioImpactTableRows[] {
return entities.map((item: ImpactTableEntityType) => {
return {
name: item.name || '',
children:
item.children?.length > 0
? this.buildScenarioVsScenarioImpactTableRowsSkeleton(item.children)
: [],
values: [],
};
});
return impactTableRow;
}

private static processTwoScenariosData(
Expand Down
Loading

0 comments on commit 2b96d93

Please sign in to comment.