Skip to content

Commit

Permalink
simplify custom project calculation flow
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeh committed Nov 29, 2024
1 parent 717a9c2 commit 1ec4ee5
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 72 deletions.
56 changes: 55 additions & 1 deletion api/src/modules/calculations/calculation.engine.ts
Original file line number Diff line number Diff line change
@@ -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),
};
}
}
5 changes: 2 additions & 3 deletions api/src/modules/calculations/cost.calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,15 @@ export class CostCalculator {
projectInput: ProjectInput,
baseSize: BaseSize,
baseIncrease: BaseIncrease,
revenueProfitCalculator: RevenueProfitCalculator,
sequestrationRateCalculator: SequestrationRateCalculator,
) {
this.projectInput = projectInput;
this.defaultProjectLength = projectInput.assumptions.defaultProjectLength;
this.startingPointScaling = projectInput.assumptions.startingPointScaling;
this.baseIncrease = baseIncrease;
this.baseSize = baseSize;
this.revenueProfitCalculator = new RevenueProfitCalculator(
this.projectInput,
);
this.revenueProfitCalculator = revenueProfitCalculator;
this.sequestrationRateCalculator = sequestrationRateCalculator;
}

Expand Down
9 changes: 5 additions & 4 deletions api/src/modules/calculations/revenue-profit.calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions api/src/modules/custom-projects/custom-projects.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -16,7 +16,7 @@ import { SaveCustomProjectEventHandler } from '@api/modules/custom-projects/even
],
providers: [
CustomProjectsService,
CustomProjectInputFactory,
CustomProjectFactory,
SaveCustomProjectEventHandler,
],
controllers: [CustomProjectsController],
Expand Down
72 changes: 12 additions & 60 deletions api/src/modules/custom-projects/custom-projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');
Expand All @@ -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<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,7 +33,7 @@ export type GeneralProjectInputs = {
};

@Injectable()
export class CustomProjectInputFactory {
export class CustomProjectFactory {
createProjectInput(
dto: CreateCustomProjectDto,
additionalBaseData: AdditionalBaseData,
Expand Down Expand Up @@ -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;
}
}

0 comments on commit 1ec4ee5

Please sign in to comment.