Skip to content

Commit

Permalink
first calculation feasibility analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeh committed Nov 18, 2024
1 parent 69982cb commit 11cedec
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 9 deletions.
82 changes: 75 additions & 7 deletions api/src/modules/calculations/cost.calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { RevenueProfitCalculator } from '@api/modules/calculations/revenue-profi
import { RestorationProjectInput } from '@api/modules/custom-projects/input-factory/restoration-project.input';
import { BaseSize } from '@shared/entities/base-size.entity';
import { BaseIncrease } from '@shared/entities/base-increase.entity';
import { CostInputs } from '@api/modules/custom-projects/dto/project-cost-inputs.dto';

type CostPlan = {
type CostPlanMap = {
[year: number]: number;
};

Expand All @@ -28,43 +29,110 @@ type CostPlan = {
// ) {}
// }

type CostPlans = Record<keyof CostInputs, CostPlanMap>;

export enum COST_KEYS {
FEASIBILITY_ANALYSIS = 'feasibilityAnalysis',
CONSERVATION_PLANNING_AND_ADMIN = 'conservationPlanningAndAdmin',
DATA_COLLECTION_AND_FIELD_COST = 'dataCollectionAndFieldCost',
COMMUNITY_REPRESENTATION = 'communityRepresentation',
BLUE_CARBON_PROJECT_PLANNING = 'blueCarbonProjectPlanning',
ESTABLISHING_CARBON_RIGHTS = 'establishingCarbonRights',
FINANCING_COST = 'financingCost',
VALIDATION = 'validation',
MONITORING = 'monitoring',
BASELINE_REASSESSMENT = 'baselineReassessment',
MRV = 'mrv',
LONG_TERM_PROJECT_OPERATING_COST = 'longTermProjectOperatingCost',
}

type ProjectInput = ConservationProjectInput | RestorationProjectInput;

export class CostCalculator {
projectInput: ProjectInput;
defaultProjectLength: number;
startingPointScaling: number;
baseSize: BaseSize;
baseIncrease: BaseIncrease;
capexTotalCostPlan: CostPlan;
opexTotalCostPlan: CostPlan;
capexTotalCostPlan: CostPlanMap;
opexTotalCostPlan: CostPlanMap;
costPlans: CostPlans;
constructor(
projectInput: ProjectInput,
defaultProjectLength: number,
startingPointScaling: number,
baseSize: BaseSize,
baseIncrease: BaseIncrease,
) {
this.projectInput = projectInput;
this.defaultProjectLength = defaultProjectLength;
this.startingPointScaling = startingPointScaling;
this.baseIncrease = baseIncrease;
this.baseSize = baseSize;
}

initializeCostPlans() {
this.capexTotalCostPlan = this.initializeCostPlan(
this.capexTotalCostPlan = this.initializeTotalCostPlan(
this.defaultProjectLength,
);
this.opexTotalCostPlan = this.initializeTotalCostPlan(
this.defaultProjectLength,
);
this.opexTotalCostPlan = this.initializeCostPlan(this.defaultProjectLength);
}

/**
* @description: Initialize the cost plan with the default project length, with 0 costs for each year
* @param defaultProjectLength
*/
private initializeCostPlan(defaultProjectLength: number): CostPlan {
const costPlan: CostPlan = {};
private initializeTotalCostPlan(defaultProjectLength: number): CostPlanMap {
const costPlan: CostPlanMap = {};
for (let i = 1; i <= defaultProjectLength; i++) {
costPlan[i] = 0;
}
return costPlan;
}

private createSimpleCostPlan(
totalBaseCost: number,
years = [-4, -3, -2, -1],
) {
const costPlan: CostPlanMap = {};
years.forEach((year) => {
costPlan[year] = totalBaseCost;
});
return costPlan;
}

private getTotalBaseCost(costType: COST_KEYS): number {
const baseCost = this.projectInput.costInputs[costType];
const increasedBy: number = this.baseIncrease[costType];
const sizeDifference =
this.projectInput.projectSizeHa - this.startingPointScaling;
const scalingFactor = Math.max(Math.round(sizeDifference / baseCost), 0);
const totalBaseCost = baseCost + increasedBy * scalingFactor * baseCost;
this.throwIfValueIsNotValid(totalBaseCost, costType);
return totalBaseCost;
}

private calculateFeasibilityAnalysisCosts() {
const totalBaseCost = this.getTotalBaseCost(COST_KEYS.FEASIBILITY_ANALYSIS);
const feasibilityAnalysisCostPlan = this.createSimpleCostPlan(
totalBaseCost,
[-4],
);
return feasibilityAnalysisCostPlan;
}

private throwIfValueIsNotValid(value: number, costKey: COST_KEYS): void {
if (typeof value !== 'number' || isNaN(value) || !isFinite(value)) {
throw new Error(`Invalid number: ${value} produced for ${costKey}`);
}
}

calculateCosts() {
// @ts-ignore
this.costPlans = {
feasibilityAnalysis: this.calculateFeasibilityAnalysisCosts(),
};
}
}
6 changes: 5 additions & 1 deletion api/src/modules/custom-projects/custom-projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,20 @@ export class CustomProjectsService extends AppBaseService<
// value is used regardless of the activity.

const DEFAULT_PROJECT_LENGTH = 40;
const CONSERVATION_STARTING_POINT_SCALING = 500;
const RESTORATION_STARTING_POINT_SCALING = 20000;

const calculator = new CostCalculator(
projectInput,
DEFAULT_PROJECT_LENGTH,
CONSERVATION_STARTING_POINT_SCALING,
baseSize,
baseIncrease,
);

calculator.initializeCostPlans();
return calculator;
calculator.calculateCosts();
return calculator.costPlans;
}

async getDefaultCostInputs(
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export class RestorationProjectInput {}
import { ConservationProjectInput } from '@api/modules/custom-projects/input-factory/conservation-project.input';

// TMP extends ConservationProjectInput, until we know how to implement the RestorationProjectInput
export class RestorationProjectInput extends ConservationProjectInput {}

0 comments on commit 11cedec

Please sign in to comment.