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..7b1c5aaa 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 activity type and country", + }, 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), +});