Skip to content

Commit

Permalink
eudr dashboard detail
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeh committed Mar 11, 2024
1 parent 6170749 commit c396ca4
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ export class BigQueryAlertsQueryBuilder {
this.queryBuilder.limit(this.dto?.limit);

const [query, params] = this.queryBuilder.getQueryAndParameters();
console.log('query', query);
console.log('params', params);

return this.parseToBigQuery(query, params);
}
Expand Down
3 changes: 2 additions & 1 deletion api/src/modules/eudr-alerts/alerts.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { AlertsOutput } from 'modules/eudr-alerts/dto/alerts-output.dto';
import {
EUDRAlertDatabaseResult,
EUDRAlertDates,
GetAlertSummary,
IEUDRAlertsRepository,
} from 'modules/eudr-alerts/eudr.repositoty.interface';
import { GetEUDRAlertsDto } from 'modules/eudr-alerts/dto/get-alerts.dto';
Expand Down Expand Up @@ -62,6 +61,8 @@ export class AlertsRepository implements IEUDRAlertsRepository {
queryBuilder.addSelect('alertconfidence', 'alertConfidence');
queryBuilder.addSelect('year', 'alertYear');
queryBuilder.addSelect('alertcount', 'alertCount');
queryBuilder.addSelect('georegionid', 'geoRegionId');
queryBuilder.orderBy('alertdate', 'ASC');
return this.query(queryBuilder, dto);
}

Expand Down
72 changes: 72 additions & 0 deletions api/src/modules/eudr-alerts/dashboard/dashboard-detail.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ApiProperty } from '@nestjs/swagger';

export class EUDRDashBoardDetail {
@ApiProperty()
name: string;
@ApiProperty()
address: string;
@ApiProperty()
companyId: string;
@ApiProperty({
type: () => DashBoardDetailSourcingInformation,
isArray: true,
})
sourcingInformation: DashBoardDetailSourcingInformation[];
@ApiProperty({ type: () => DashBoardDetailAlerts, isArray: true })
alerts: DashBoardDetailAlerts[];
}

class DashBoardDetailSourcingInformation {
@ApiProperty()
materialName: string;
@ApiProperty()
hsCode: string;
@ApiProperty()
totalArea: number;
@ApiProperty()
totalVolume: number;
@ApiProperty({ type: () => ByVolume, isArray: true })
byVolume: ByVolume[];
@ApiProperty({ type: () => ByArea, isArray: true })
byArea: ByArea[];
}

class ByVolume {
@ApiProperty()
year: number;
@ApiProperty()
percentage: number;
@ApiProperty()
volume: number;
}

class ByArea {
@ApiProperty()
plotName: string;
@ApiProperty()
percentage: number;
@ApiProperty()
area: number;
@ApiProperty()
geoRegionId: string;
}

class DashBoardDetailAlerts {
@ApiProperty()
startAlertDate: Date;
@ApiProperty()
endAlertDate: number;
@ApiProperty()
totalAlerts: number;
@ApiProperty({ type: () => AlertValues, isArray: true })
values: AlertValues[];
}

class AlertValues {
@ApiProperty()
geoRegionId: string;
@ApiProperty()
alertCount: number;
@ApiProperty()
plotName: string;
}
161 changes: 159 additions & 2 deletions api/src/modules/eudr-alerts/dashboard/eudr-dashboard.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// supress typescript error
// eslint-disable-next-line @typescript-eslint/ban-types
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import { DataSource, SelectQueryBuilder } from 'typeorm';
import { DataSource, EntityManager, SelectQueryBuilder } from 'typeorm';
import {
EUDRAlertDatabaseResult,
IEUDRAlertsRepository,
Expand All @@ -18,7 +18,13 @@ import {
EUDRBreakDown,
EUDRDashboard,
EUDRDashBoardFields,
} from 'modules/eudr-alerts/dashboard/types';
} from 'modules/eudr-alerts/dashboard/dashboard.types';
import { GetEUDRAlertDatesDto } from '../dto/get-alerts.dto';
import { AdminRegionsService } from '../../admin-regions/admin-regions.service';
import { AlertsOutput } from '../dto/alerts-output.dto';

import { GeoRegion } from 'modules/geo-regions/geo-region.entity';
import { EUDRDashBoardDetail } from './dashboard-detail.types';

@Injectable()
export class EudrDashboardService {
Expand Down Expand Up @@ -392,4 +398,155 @@ export class EudrDashboardService {

return queryBuilder.getRawMany();
}

async buildDashboardDetail(
supplierId: string,
dto?: GetEUDRAlertDatesDto,
): Promise<EUDRDashBoardDetail> {
const result: any = {};
const sourcingInformation: any = {};
let supplier: Supplier;
const geoRegionMap: Map<string, { plotName: string }> = new Map();

return this.datasource.transaction(async (manager: EntityManager) => {
supplier = await manager
.getRepository(Supplier)
.findOneOrFail({ where: { id: supplierId } });
result.name = supplier.name;
result.address = supplier.address;
result.companyId = supplier.companyId;
result.sourcingInformation = sourcingInformation;
const sourcingData: {
materialId: string;
materialName: string;
hsCode: string;
countryName: string;
plotName: string;
geoRegionId: string;
plotArea: number;
volume: number;
year: number;
sourcingLocationId: string;
}[] = await manager
.createQueryBuilder(SourcingLocation, 'sl')
.select('m.name', 'materialName')
.addSelect('sl.locationCountryInput', 'countryName')
.addSelect('m.hsCodeId', 'hsCode')
.addSelect('m.id', 'materialId')
.leftJoin(Material, 'm', 'm.id = sl.materialId')
.where('sl.producerId = :producerId', { producerId: supplierId })
.distinct(true)
.getRawMany();

// TODO: we are assuming that each suppliers supplies only one material and for the same country

const country: AdminRegion = await manager
.getRepository(AdminRegion)
.findOneOrFail({
where: { name: sourcingData[0].countryName, level: 0 },
});

sourcingInformation.materialName = sourcingData[0].materialName;
sourcingInformation.hsCode = sourcingData[0].hsCode;
sourcingInformation.country = {
name: country.name,
isoA3: country.isoA3,
};

for (const material of sourcingData) {
const geoRegions: any[] = await manager
.createQueryBuilder(SourcingLocation, 'sl')
.select('gr.id', 'geoRegionId')
.addSelect('gr.name', 'plotName')
.addSelect('gr.totalArea', 'totalArea')
.distinct(true)
.leftJoin(GeoRegion, 'gr', 'gr.id = sl.geoRegionId')
.where('sl.materialId = :materialId', {
materialId: material.materialId,
})
.andWhere('sl.producerId = :supplierId', { supplierId })
.getRawMany();
const totalArea: number = geoRegions.reduce(
(acc: number, cur: any) => acc + parseInt(cur.totalArea),
0,
);
let sourcingRecords: SourcingRecord[] = [];
for (const geoRegion of geoRegions) {
if (!geoRegionMap.get(geoRegion.geoRegionId)) {
geoRegionMap.set(geoRegion.geoRegionId, {
plotName: geoRegion.plotName,
});
}
sourcingRecords = await manager
.createQueryBuilder(SourcingRecord, 'sr')
.leftJoin(SourcingLocation, 'sl', 'sr.sourcingLocationId = sl.id')
.leftJoin(GeoRegion, 'gr', 'gr.id = sl.geoRegionId')
.where('sl.geoRegionId = :geoRegionId', {
geoRegionId: geoRegion.geoRegionId,
})
.andWhere('sl.producerId = :supplierId', { supplierId: supplierId })
.andWhere('sl.materialId = :materialId', {
materialId: material.materialId,
})
.select([
'sr.year AS year',
'sr.tonnage AS volume',
'gr.name as plotName',
'gr.id as geoRegionId',
])
.getRawMany();
}

const totalVolume: number = sourcingRecords.reduce(
(acc: number, cur: any) => acc + parseInt(cur.volume),
0,
);

sourcingInformation.totalArea = totalArea;
sourcingInformation.totalVolume = totalVolume;
sourcingInformation.byArea = geoRegions.map((geoRegion: any) => ({
plotName: geoRegion.plotName,
geoRegionId: geoRegion.geoRegionId,
percentage: (geoRegion.totalArea / totalArea) * 100,
area: geoRegion.totalArea,
}));
sourcingInformation.byVolume = sourcingRecords.map((record: any) => ({
plotName: record.plotName,
geoRegionId: record.geoRegionId,
year: record.year,
percentage: (parseInt(record.volume) / totalVolume) * 100,
volume: parseInt(record.volume),
}));
}

const alertsOutput: AlertsOutput[] = await this.eudrRepository.getAlerts({
supplierIds: [supplierId],
startAlertDate: dto?.startAlertDate,
endAlertDate: dto?.endAlertDate,
});

const totalAlerts: number = alertsOutput.reduce(
(acc: number, cur: AlertsOutput) => acc + cur.alertCount,
0,
);
const startAlertDate: string = alertsOutput[0].alertDate.value.toString();
const endAlertDate: string =
alertsOutput[alertsOutput.length - 1].alertDate.value.toString();

const alerts = {
startADateDate: startAlertDate,
endAlertDate: endAlertDate,
totalAlerts,
values: alertsOutput.map((alert: AlertsOutput) => ({
geoRegionId: alert.geoRegionId,
alertCount: alert.alertCount,
plotName: geoRegionMap.get(alert.geoRegionId)!.plotName,
})),
};

result.alerts = alerts;

return result;
});
}
}
3 changes: 2 additions & 1 deletion api/src/modules/eudr-alerts/dto/alerts-output.dto.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
export type AlertsOutput = {
alertCount: boolean;
alertCount: number;
alertDate: {
value: Date | string;
};
year: number;
alertConfidence: 'low' | 'medium' | 'high' | 'very high';
geoRegionId: string;
};

export type AlertGeometry = {
Expand Down
40 changes: 21 additions & 19 deletions api/src/modules/eudr-alerts/dto/get-alerts.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,49 @@ import {
IsUUID,
} from 'class-validator';

export class GetEUDRAlertsDto {
export class GetEUDRAlertDatesDto {
@ApiPropertyOptional()
@IsOptional()
@IsDate()
@Type(() => Date)
startAlertDate?: Date;

@ApiPropertyOptional()
@IsOptional()
@IsDate()
@Type(() => Date)
endAlertDate?: Date;
}

export class GetEUDRAlertsDto extends GetEUDRAlertDatesDto {
@ApiPropertyOptional()
@IsOptional()
@IsArray()
@IsUUID('4', { each: true })
supplierIds: string[];
supplierIds?: string[];

@ApiPropertyOptional()
@IsOptional()
@IsArray()
@IsUUID('4', { each: true })
geoRegionIds: string[];
geoRegionIds?: string[];

@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Type(() => Number)
startYear: number;
startYear?: number;

@ApiPropertyOptional()
@IsOptional()
@IsNumber()
@Type(() => Number)
endYear: number;
endYear?: number;

alertConfidence: 'high' | 'medium' | 'low';

@ApiPropertyOptional()
@IsOptional()
@IsDate()
@Type(() => Date)
startAlertDate: Date;

@ApiPropertyOptional()
@IsOptional()
@IsDate()
@Type(() => Date)
endAlertDate: Date;
alertConfidence?: 'high' | 'medium' | 'low';

@ApiPropertyOptional()
@IsOptional()
@IsInt()
limit: number = 1000;
limit?: number = 1000;
}
Loading

0 comments on commit c396ca4

Please sign in to comment.