From 6b16450fe3fa84cc62767478f19f658231ea71ea Mon Sep 17 00:00:00 2001 From: Alejandro Peralta Date: Thu, 5 Dec 2024 14:18:27 +0100 Subject: [PATCH] feat(api): Add CustomProject->getActivityTypesDefaults contract and implement the necessary stuff to make it work --- .../modules/calculations/data.repository.ts | 57 +++++++++++++++++++ .../custom-projects.controller.ts | 11 ++++ .../custom-projects.service.ts | 5 ++ .../custom-projects-setup.spec.ts | 16 +++++- shared/contracts/custom-projects.contract.ts | 11 ++++ .../get-activity-types-defaults.dto.ts | 6 ++ .../activity-types-defaults.schema.ts | 7 +++ 7 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 shared/dtos/custom-projects/get-activity-types-defaults.dto.ts create mode 100644 shared/schemas/custom-projects/activity-types-defaults.schema.ts diff --git a/api/src/modules/calculations/data.repository.ts b/api/src/modules/calculations/data.repository.ts index f217ba91..90a862c8 100644 --- a/api/src/modules/calculations/data.repository.ts +++ b/api/src/modules/calculations/data.repository.ts @@ -12,6 +12,7 @@ import { OverridableCostInputs } from '@api/modules/custom-projects/dto/project- import { BaseSize } from '@shared/entities/base-size.entity'; import { BaseIncrease } from '@shared/entities/base-increase.entity'; import { AssumptionsRepository } from '@api/modules/calculations/assumptions.repository'; +import { GetActivityTypesDefaults } from '@shared/dtos/custom-projects/get-activity-types-defaults.dto'; /** * Additional data that is required to perform calculations, which is not overridable by the user. Better naming and clustering of concepts would be great @@ -110,6 +111,62 @@ export class DataRepository extends Repository { return additionalBaseData; } + async getActivityTypesDefaults(dto: GetActivityTypesDefaults) { + const defaults = {}; + + [defaults[ACTIVITY.CONSERVATION], defaults[ACTIVITY.RESTORATION]] = + await Promise.all([ + this.getConservationActivityDefaults(dto), + this.getRestorationActivityDefaults(dto), + ]); + + return defaults; + } + + private async getConservationActivityDefaults(dto: GetActivityTypesDefaults) { + const { countryCode, ecosystem } = dto; + + const result = await this.findOne({ + where: { countryCode, ecosystem, activity: ACTIVITY.CONSERVATION }, + select: [ + 'ecosystemLossRate', + 'tier1EmissionFactor', + 'emissionFactorAgb', + 'emissionFactorSoc', + ], + }); + if (result === null) return null; + + return { + ecosystemLossRate: result.ecosystemLossRate, + emissionFactor: { + tier1: result.tier1EmissionFactor, + tier2: { + emissionFactorAgb: result.emissionFactorAgb, + emissionFactorSoc: result.emissionFactorSoc, + }, + }, + }; + } + + private async getRestorationActivityDefaults(dto: GetActivityTypesDefaults) { + const { countryCode, ecosystem } = dto; + + const result = await this.findOne({ + where: { countryCode, ecosystem, activity: ACTIVITY.RESTORATION }, + select: ['activity', 'tier1SequestrationRate', 'tier2SequestrationRate'], + }); + if (result === null) return null; + + return { + activity: result.activity, + sequestrationRate: { + tier1: result.tier1SequestrationRate, + tier2: result.tier2SequestrationRate, + }, + }; + } + async getOverridableCostInputs( dto: GetOverridableCostInputs, ): Promise { diff --git a/api/src/modules/custom-projects/custom-projects.controller.ts b/api/src/modules/custom-projects/custom-projects.controller.ts index 85a75d2a..f2be8736 100644 --- a/api/src/modules/custom-projects/custom-projects.controller.ts +++ b/api/src/modules/custom-projects/custom-projects.controller.ts @@ -25,6 +25,17 @@ export class CustomProjectsController { private readonly customProjects: CustomProjectsService, ) {} + @TsRestHandler(customProjectContract.getActivityTypesDefaults) + async getActivityTypeDefaults(): Promise { + return tsRestHandler( + customProjectContract.getActivityTypesDefaults, + async ({ query }) => { + const data = await this.customProjects.getActivityTypeDefaults(query); + return { body: { data }, status: HttpStatus.OK }; + }, + ); + } + @TsRestHandler(customProjectContract.getAvailableCountries) async getAvailableCountriesToCreateACustomProject(): Promise { return tsRestHandler( diff --git a/api/src/modules/custom-projects/custom-projects.service.ts b/api/src/modules/custom-projects/custom-projects.service.ts index 11c8cd31..fad558f1 100644 --- a/api/src/modules/custom-projects/custom-projects.service.ts +++ b/api/src/modules/custom-projects/custom-projects.service.ts @@ -19,6 +19,7 @@ import { User } from '@shared/entities/users/user.entity'; import { EventBus } from '@nestjs/cqrs'; import { SaveCustomProjectEvent } from '@api/modules/custom-projects/events/save-custom-project.event'; import { FetchSpecification } from 'nestjs-base-service'; +import { GetActivityTypesDefaults } from '@shared/dtos/custom-projects/get-activity-types-defaults.dto'; @Injectable() export class CustomProjectsService extends AppBaseService< @@ -87,6 +88,10 @@ export class CustomProjectsService extends AppBaseService< } } + async getActivityTypeDefaults(dto: GetActivityTypesDefaults) { + return this.dataRepository.getActivityTypesDefaults(dto); + } + async getDefaultCostInputs( dto: GetOverridableCostInputs, ): Promise { diff --git a/api/test/integration/custom-projects/custom-projects-setup.spec.ts b/api/test/integration/custom-projects/custom-projects-setup.spec.ts index 9feb9b9b..b301d4ca 100644 --- a/api/test/integration/custom-projects/custom-projects-setup.spec.ts +++ b/api/test/integration/custom-projects/custom-projects-setup.spec.ts @@ -20,7 +20,21 @@ describe('Create Custom Projects - Setup', () => { await testManager.close(); }); - describe('Get Overridable Model Assumptions', () => { + describe('Get Activity type defaults', () => { + test('Should return activity type defaults based on country and ecosystem', async () => { + const response = await testManager + .request() + .get(customProjectContract.getActivityTypesDefaults.path) + .query({ + countryCode: 'IND', + ecosystem: ECOSYSTEM.MANGROVE, + }); + + expect(response.status).toBe(200); + }); + }); + + describe.skip('Get Overridable Model Assumptions', () => { test('Should return overridable model assumptions based on ecosystem and activity', async () => { const response = await testManager .request() diff --git a/shared/contracts/custom-projects.contract.ts b/shared/contracts/custom-projects.contract.ts index 7f8aeb9f..c15416d2 100644 --- a/shared/contracts/custom-projects.contract.ts +++ b/shared/contracts/custom-projects.contract.ts @@ -12,12 +12,23 @@ import { OverridableCostInputs } from "@api/modules/custom-projects/dto/project- import { GetAssumptionsSchema } from "@shared/schemas/assumptions/get-assumptions.schema"; import { ModelAssumptions } from "@shared/entities/model-assumptions.entity"; import { generateEntityQuerySchema } from "@shared/schemas/query-param.schema"; +import { GetActivityTypesDefaultsSchema } from "@shared/schemas/custom-projects/activity-types-defaults.schema"; export const customProjecsQuerySchema = generateEntityQuerySchema(CustomProject); const contract = initContract(); export const customProjectContract = contract.router({ + getActivityTypesDefaults: { + method: "GET", + path: "/custom-projects/activity-types-defaults", + query: GetActivityTypesDefaultsSchema, + responses: { + 200: contract.type>(), + }, + summary: + "Get default values for fields based on the selected country and ecosystem", + }, getAvailableCountries: { method: "GET", path: "/custom-projects/available-countries", diff --git a/shared/dtos/custom-projects/get-activity-types-defaults.dto.ts b/shared/dtos/custom-projects/get-activity-types-defaults.dto.ts new file mode 100644 index 00000000..81f6e1a2 --- /dev/null +++ b/shared/dtos/custom-projects/get-activity-types-defaults.dto.ts @@ -0,0 +1,6 @@ +import { GetActivityTypesDefaultsSchema } from "@shared/schemas/custom-projects/activity-types-defaults.schema"; +import { z } from "zod"; + +export type GetActivityTypesDefaults = z.infer< + typeof GetActivityTypesDefaultsSchema +>; diff --git a/shared/schemas/custom-projects/activity-types-defaults.schema.ts b/shared/schemas/custom-projects/activity-types-defaults.schema.ts new file mode 100644 index 00000000..6b817ae7 --- /dev/null +++ b/shared/schemas/custom-projects/activity-types-defaults.schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; +import { ECOSYSTEM } from "@shared/entities/ecosystem.enum"; + +export const GetActivityTypesDefaultsSchema = z.object({ + countryCode: z.string().min(3).max(3), + ecosystem: z.nativeEnum(ECOSYSTEM), +});