diff --git a/api/src/modules/calculations/calculation.engine.ts b/api/src/modules/calculations/calculation.engine.ts index ddce6740..5968d5d1 100644 --- a/api/src/modules/calculations/calculation.engine.ts +++ b/api/src/modules/calculations/calculation.engine.ts @@ -1,7 +1,61 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; +import { + CostCalculator, + ProjectInput, +} from '@api/modules/calculations/cost.calculator'; +import { BaseIncrease } from '@shared/entities/base-increase.entity'; +import { BaseSize } from '@shared/entities/base-size.entity'; +import { SequestrationRateCalculator } from '@api/modules/calculations/sequestration-rate.calculator'; +import { RevenueProfitCalculator } from '@api/modules/calculations/revenue-profit.calculator'; +import { + CustomProjectCostDetails, + CustomProjectSummary, + YearlyBreakdown, +} from '@shared/dtos/custom-projects/custom-project-output.dto'; + +export type CostOutput = { + costPlans: any; + summary: CustomProjectSummary; + yearlyBreakdown: YearlyBreakdown; + costDetails: { + total: CustomProjectCostDetails; + npv: CustomProjectCostDetails; + }; +}; @Injectable() export class CalculationEngine { - constructor(private readonly dataSource: DataSource) {} + constructor() {} + + calculateCostOutput(dto: { + projectInput: ProjectInput; + baseIncrease: BaseIncrease; + baseSize: BaseSize; + }): CostOutput { + const { projectInput, baseIncrease, baseSize } = dto; + const sequestrationRateCalculator = new SequestrationRateCalculator( + projectInput, + ); + const revenueProfitCalculator = new RevenueProfitCalculator( + projectInput, + sequestrationRateCalculator, + ); + + const costCalculator = new CostCalculator( + projectInput, + baseSize, + baseIncrease, + revenueProfitCalculator, + sequestrationRateCalculator, + ); + + const costPlans = costCalculator.initializeCostPlans(); + return { + costPlans, + summary: costCalculator.getSummary(costPlans), + yearlyBreakdown: costCalculator.getYearlyBreakdown(), + costDetails: costCalculator.getCostDetails(costPlans), + }; + } } diff --git a/api/src/modules/calculations/cost.calculator.ts b/api/src/modules/calculations/cost.calculator.ts index 2f087340..ffda4dcf 100644 --- a/api/src/modules/calculations/cost.calculator.ts +++ b/api/src/modules/calculations/cost.calculator.ts @@ -61,6 +61,7 @@ export class CostCalculator { projectInput: ProjectInput, baseSize: BaseSize, baseIncrease: BaseIncrease, + revenueProfitCalculator: RevenueProfitCalculator, sequestrationRateCalculator: SequestrationRateCalculator, ) { this.projectInput = projectInput; @@ -68,9 +69,7 @@ export class CostCalculator { this.startingPointScaling = projectInput.assumptions.startingPointScaling; this.baseIncrease = baseIncrease; this.baseSize = baseSize; - this.revenueProfitCalculator = new RevenueProfitCalculator( - this.projectInput, - ); + this.revenueProfitCalculator = revenueProfitCalculator; this.sequestrationRateCalculator = sequestrationRateCalculator; } diff --git a/api/src/modules/calculations/revenue-profit.calculator.ts b/api/src/modules/calculations/revenue-profit.calculator.ts index 03bb6497..29c006b3 100644 --- a/api/src/modules/calculations/revenue-profit.calculator.ts +++ b/api/src/modules/calculations/revenue-profit.calculator.ts @@ -10,14 +10,15 @@ export class RevenueProfitCalculator { defaultProjectLength: number; carbonPrice: number; carbonPriceIncrease: number; - constructor(projectInput: ProjectInput) { + constructor( + projectInput: ProjectInput, + sequestrationRateCalculator: SequestrationRateCalculator, + ) { this.projectLength = projectInput.assumptions.projectLength; this.defaultProjectLength = projectInput.assumptions.defaultProjectLength; this.carbonPrice = projectInput.assumptions.carbonPrice; this.carbonPriceIncrease = projectInput.assumptions.carbonPriceIncrease; - this.sequestrationCreditsCalculator = new SequestrationRateCalculator( - projectInput, - ); + this.sequestrationCreditsCalculator = sequestrationRateCalculator; } calculateEstimatedRevenuePlan(): CostPlanMap { diff --git a/api/src/modules/custom-projects/custom-projects.module.ts b/api/src/modules/custom-projects/custom-projects.module.ts index fab84c7c..7f7a4405 100644 --- a/api/src/modules/custom-projects/custom-projects.module.ts +++ b/api/src/modules/custom-projects/custom-projects.module.ts @@ -5,7 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CustomProject } from '@shared/entities/custom-project.entity'; import { CustomProjectsController } from './custom-projects.controller'; import { CalculationsModule } from '@api/modules/calculations/calculations.module'; -import { CustomProjectInputFactory } from '@api/modules/custom-projects/input-factory/custom-project-input.factory'; +import { CustomProjectFactory } from '@api/modules/custom-projects/input-factory/custom-project.factory'; import { SaveCustomProjectEventHandler } from '@api/modules/custom-projects/events/handlers/save-custom-project.handler'; @Module({ @@ -16,7 +16,7 @@ import { SaveCustomProjectEventHandler } from '@api/modules/custom-projects/even ], providers: [ CustomProjectsService, - CustomProjectInputFactory, + CustomProjectFactory, SaveCustomProjectEventHandler, ], controllers: [CustomProjectsController], diff --git a/api/src/modules/custom-projects/custom-projects.service.ts b/api/src/modules/custom-projects/custom-projects.service.ts index a8ab3b11..f1686288 100644 --- a/api/src/modules/custom-projects/custom-projects.service.ts +++ b/api/src/modules/custom-projects/custom-projects.service.ts @@ -9,15 +9,12 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CustomProject } from '@shared/entities/custom-project.entity'; import { CalculationEngine } from '@api/modules/calculations/calculation.engine'; -import { CustomProjectInputFactory } from '@api/modules/custom-projects/input-factory/custom-project-input.factory'; +import { CustomProjectFactory } from '@api/modules/custom-projects/input-factory/custom-project.factory'; import { GetOverridableCostInputs } from '@shared/dtos/custom-projects/get-overridable-cost-inputs.dto'; import { DataRepository } from '@api/modules/calculations/data.repository'; import { OverridableCostInputs } from '@api/modules/custom-projects/dto/project-cost-inputs.dto'; -import { CostCalculator } from '@api/modules/calculations/cost.calculator'; import { GetOverridableAssumptionsDTO } from '@shared/dtos/custom-projects/get-overridable-assumptions.dto'; import { AssumptionsRepository } from '@api/modules/calculations/assumptions.repository'; -import { SequestrationRateCalculator } from '@api/modules/calculations/sequestration-rate.calculator'; -import { CountriesService } from '@api/modules/countries/countries.service'; 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'; @@ -36,8 +33,7 @@ export class CustomProjectsService extends AppBaseService< public readonly calculationEngine: CalculationEngine, public readonly dataRepository: DataRepository, public readonly assumptionsRepository: AssumptionsRepository, - public readonly customProjectFactory: CustomProjectInputFactory, - private readonly countries: CountriesService, + public readonly customProjectFactory: CustomProjectFactory, private readonly eventBus: EventBus, ) { super(repo, 'customProject', 'customProjects'); @@ -55,70 +51,26 @@ export class CustomProjectsService extends AppBaseService< ecosystem, activity, }); - const country = await this.countries.getById(countryCode, { - fields: ['code', 'name'], - }); const projectInput = this.customProjectFactory.createProjectInput( dto, additionalBaseData, additionalAssumptions, ); - const sequestrationRateCalculator = new SequestrationRateCalculator( - projectInput, - ); - const calculator = new CostCalculator( + + const costOutput = this.calculationEngine.calculateCostOutput({ projectInput, - baseSize, baseIncrease, - sequestrationRateCalculator, - ); - - const costPlans = calculator.initializeCostPlans(); + baseSize, + }); - // TODO: the extended props are not defined in the entity, we could put them in the output but according to the design, they might need to be - // sortable, so we might need to define as first class properties in the entity - const projectOutput: CustomProject & { - abatementPotential: number; - } = { - projectName: dto.projectName, - abatementPotential: null, // We still dont know how to calculate this - country, - totalCostNPV: costPlans.totalCapexNPV + costPlans.totalOpexNPV, - totalCost: costPlans.totalCapex + costPlans.totalOpex, - projectSize: dto.projectSizeHa, - projectLength: dto.assumptions.projectLength, - ecosystem: dto.ecosystem, - activity: dto.activity, - output: { - lossRate: projectInput.lossRate, - carbonRevenuesToCover: projectInput.carbonRevenuesToCover, - initialCarbonPrice: projectInput.initialCarbonPriceAssumption, - emissionFactors: { - emissionFactor: projectInput.emissionFactor, - emissionFactorAgb: projectInput.emissionFactorAgb, - emissionFactorSoc: projectInput.emissionFactorSoc, - }, - totalProjectCost: { - total: { - total: costPlans.totalCapex + costPlans.totalOpex, - capex: costPlans.totalCapex, - opex: costPlans.totalOpex, - }, - npv: { - total: costPlans.totalCapexNPV + costPlans.totalOpexNPV, - capex: costPlans.totalCapexNPV, - opex: costPlans.totalOpexNPV, - }, - }, + const customProject = this.customProjectFactory.createProject( + dto, + projectInput, + costOutput, + ); - summary: calculator.getSummary(costPlans), - costDetails: calculator.getCostDetails(costPlans), - yearlyBreakdown: calculator.getYearlyBreakdown(), - }, - input: dto, - }; - return projectOutput; + return customProject; } async saveCustomProject(dto: CustomProject, user: User): Promise { diff --git a/api/src/modules/custom-projects/input-factory/conservation-project.input.ts b/api/src/modules/custom-projects/input-factory/conservation-project.input.ts index f7c3fa04..0433d3af 100644 --- a/api/src/modules/custom-projects/input-factory/conservation-project.input.ts +++ b/api/src/modules/custom-projects/input-factory/conservation-project.input.ts @@ -9,7 +9,7 @@ import { } from '@api/modules/custom-projects/dto/conservation-project-params.dto'; import { AdditionalBaseData } from '@api/modules/calculations/data.repository'; import { LOSS_RATE_USED } from '@shared/schemas/custom-projects/create-custom-project.schema'; -import { GeneralProjectInputs } from '@api/modules/custom-projects/input-factory/custom-project-input.factory'; +import { GeneralProjectInputs } from '@api/modules/custom-projects/input-factory/custom-project.factory'; import { ModelAssumptionsForCalculations, NonOverridableModelAssumptions, diff --git a/api/src/modules/custom-projects/input-factory/custom-project-input.factory.ts b/api/src/modules/custom-projects/input-factory/custom-project.factory.ts similarity index 62% rename from api/src/modules/custom-projects/input-factory/custom-project-input.factory.ts rename to api/src/modules/custom-projects/input-factory/custom-project.factory.ts index 9b959280..b4941594 100644 --- a/api/src/modules/custom-projects/input-factory/custom-project-input.factory.ts +++ b/api/src/modules/custom-projects/input-factory/custom-project.factory.ts @@ -10,6 +10,10 @@ import { NonOverridableModelAssumptions, } from '@api/modules/calculations/assumptions.repository'; import { BaseDataView } from '@shared/entities/base-data.view'; +import { CostOutput } from '@api/modules/calculations/calculation.engine'; +import { ProjectInput } from '@api/modules/calculations/cost.calculator'; +import { CustomProject } from '@shared/entities/custom-project.entity'; +import { Country } from '@shared/entities/country.entity'; export type ConservationProjectCarbonInputs = { lossRate: number; @@ -29,7 +33,7 @@ export type GeneralProjectInputs = { }; @Injectable() -export class CustomProjectInputFactory { +export class CustomProjectFactory { createProjectInput( dto: CreateCustomProjectDto, additionalBaseData: AdditionalBaseData, @@ -95,4 +99,50 @@ export class CustomProjectInputFactory { return conservationProjectInput; } + + createProject( + dto: CreateCustomProjectDto, + input: ProjectInput, + output: CostOutput, + ): CustomProject { + const { costPlans, summary, costDetails, yearlyBreakdown } = output; + const customProject = new CustomProject(); + customProject.projectName = dto.projectName; + customProject.country = { code: dto.countryCode } as Country; + customProject.totalCostNPV = + costPlans.totalCapexNPV + costPlans.totalOpexNPV; + customProject.totalCost = costPlans.totalCapex + costPlans.totalOpex; + customProject.projectSize = dto.projectSizeHa; + customProject.projectLength = dto.assumptions.projectLength; + customProject.ecosystem = dto.ecosystem; + customProject.activity = dto.activity; + customProject.output = { + lossRate: input.lossRate, + carbonRevenuesToCover: input.carbonRevenuesToCover, + initialCarbonPrice: input.initialCarbonPriceAssumption, + emissionFactors: { + emissionFactor: input.emissionFactor, + emissionFactorAgb: input.emissionFactorAgb, + emissionFactorSoc: input.emissionFactorSoc, + }, + totalProjectCost: { + total: { + total: costPlans.totalCapex + costPlans.totalOpex, + capex: costPlans.totalCapex, + opex: costPlans.totalOpex, + }, + npv: { + total: costPlans.totalCapexNPV + costPlans.totalOpexNPV, + capex: costPlans.totalCapexNPV, + opex: costPlans.totalOpexNPV, + }, + }, + summary, + costDetails, + yearlyBreakdown, + }; + customProject.input = dto; + + return customProject; + } }