Skip to content

Commit

Permalink
WIP: creating restoration.project.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeh committed Nov 5, 2024
1 parent 24e9bfa commit c42423b
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 15 deletions.
7 changes: 4 additions & 3 deletions api/src/modules/custom-projects/custom-projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ export class CustomProjectsService extends AppBaseService<
baseIncrease,
baseSize,
);
//return calculator.capexTotalCostPlan;
// TODO: Also define clearly which are the outputs that the consumer expects, that is, relevant for the custom project

return {
capexTotalCostPlan: calculator.capexTotalCostPlan,
opexTotalCostPlan: calculator.opexTotalCostPlan,
estimates: calculator.getCostEstimates(),
summary: calculator.getSummary(),
yearBreakdown: calculator.getYearlyCostBreakdown(),
};
}
}
20 changes: 20 additions & 0 deletions api/src/modules/custom-projects/project-config.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BaseDataView } from '@shared/entities/base-data.view';
import { ACTIVITY } from '@shared/entities/activity.enum';
import { RESTORATION_ACTIVITY_SUBTYPE } from '@shared/entities/projects.entity';

// TODO: This seems to be a mix of assumptions, base sizes and increases. Check with Data
export const DEFAULT_STUFF = {
Expand Down Expand Up @@ -37,3 +38,22 @@ export interface ConservationProjectConfig {
tier3EmissionFactorAGB?: number;
tier3EmissionFactorSOC?: number;
}

export interface RestorationProjectConfig {
name: string;
activity: ACTIVITY;
activitySubtype: RESTORATION_ACTIVITY_SUBTYPE;
ecosystem: string;
countryCode: string;
inputData: BaseDataView;
projectSizeHa: number;
plantingSuccessRate: number;
carbonPrice?: number;
carbonRevenuesToCover?: string;
projectSpecificLossRate?: number;
emissionFactorUsed: string;
tier3ProjectSpecificEmission?: string;
tier3ProjectSpecificEmissionOneFactor?: number;
tier3EmissionFactorAGB?: number;
tier3EmissionFactorSOC?: number;
}
152 changes: 152 additions & 0 deletions api/src/modules/custom-projects/restoration.project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { ACTIVITY } from '@shared/entities/activity.enum';
import {
RestorationProjectConfig,
DEFAULT_STUFF,
} from '@api/modules/custom-projects/project-config.interface';
import { BaseDataView } from '@shared/entities/base-data.view';
import { CostInputs } from '@api/modules/custom-projects/cost-inputs.interface';
import { ModelAssumptions } from '@shared/entities/model-assumptions.entity';
import { RESTORATION_ACTIVITY_SUBTYPE } from '@shared/entities/projects.entity';

export class RestorationProject {
name: string;
activity: ACTIVITY.RESTORATION;
ecosystem: string;
countryCode: string;
costInputs: CostInputs;
modelAssumptions?: ModelAssumptions;
startingPointScaling: number =
DEFAULT_STUFF.RESTORATION_STARTING_POINT_SCALING;
projectSizeHa: number;
plantingSuccessRate: number;
carbonPrice: number;
carbonRevenuesToCover: string;
carbonRevenuesWillNotCover: string;
discountRate: number = DEFAULT_STUFF.DISCOUNT_RATE;
verificationFrequency: number = DEFAULT_STUFF.VERIFICATION_FREQUENCY;
carbonPriceIncrease: number = DEFAULT_STUFF.CARBON_PRICE_INCREASE;
restorationRate: number = DEFAULT_STUFF.RESTORATION_RATE;
buffer: number = DEFAULT_STUFF.BUFFER;
soilOrganicCarbonReleaseLength: number =
DEFAULT_STUFF.SOIL_ORGANIC_CARBON_RELEASE_LENGTH;
restorationProjectLength: number = DEFAULT_STUFF.RESTORATION_PROJECT_LENGTH;
sequestrationRateUsed?: string;
projectSpecificSequestrationRate?: number;
restorationActivity?: string;
sequestrationRate: number;

constructor(projectConfig: RestorationProjectConfig) {
this.name = projectConfig.name;
this.ecosystem = projectConfig.ecosystem;
this.countryCode = projectConfig.countryCode;
this.projectSizeHa = Number(projectConfig.projectSizeHa);
this.carbonPrice = Number(projectConfig.carbonPrice) || 30;
this.plantingSuccessRate = projectConfig.plantingSuccessRate;
this.carbonRevenuesToCover = projectConfig.carbonRevenuesToCover || 'Opex';
this.carbonRevenuesWillNotCover =
this.carbonRevenuesToCover === 'Opex' ? 'Capex' : 'None';
this.restorationActivity = projectConfig.activitySubtype;
this.initializeCostInputs(projectConfig.inputData);
this.setImplementationLabor(projectConfig.inputData);
this.setSequestrationRate(projectConfig.inputData);
this.setPlantingSuccessRate();
}

private initializeCostInputs(baseData: BaseDataView): void {
this.costInputs = {
feasibilityAnalysis: Number(baseData.feasibilityAnalysis),
conservationPlanningAndAdmin: Number(
baseData.conservationPlanningAndAdmin,
),
dataCollectionAndFieldCost: Number(baseData.dataCollectionAndFieldCost),
communityRepresentation: Number(baseData.communityRepresentation),
blueCarbonProjectPlanning: Number(baseData.blueCarbonProjectPlanning),
establishingCarbonRights: Number(baseData.establishingCarbonRights),
validation: Number(baseData.validation),
monitoring: Number(baseData.monitoring),
maintenance: Number(baseData.maintenance),
maintenanceDuration: Number(baseData.maintenanceDuration),
communityBenefitSharingFund: Number(baseData.communityBenefitSharingFund),
carbonStandardFees: Number(baseData.carbonStandardFees),
baselineReassessment: Number(baseData.baselineReassessment),
mrv: Number(baseData.mrv),
longTermProjectOperating: Number(baseData.longTermProjectOperatingCost),
financingCost: Number(baseData.financingCost),
// This is set in the constructor
implementationLabor: 0,
projectSizeHa: Number(baseData.projectSizeHa),
projectDevelopmentType: baseData.otherCommunityCashFlow,
tier1SequestrationRate: baseData.tier1SequestrationRate,
tier2SequestrationRate: baseData.tier2SequestrationRate,
};
}

setImplementationLabor(baseData: BaseDataView): void {
switch (this.restorationActivity) {
case RESTORATION_ACTIVITY_SUBTYPE.HYBRID:
this.costInputs.implementationLabor =
baseData.implementationLaborHybrid;
break;
case RESTORATION_ACTIVITY_SUBTYPE.PLANTING:
this.costInputs.implementationLabor =
baseData.implementationLaborPlanting;
break;
case RESTORATION_ACTIVITY_SUBTYPE.HYDROLOGY:
this.costInputs.implementationLabor =
baseData.implementationLaborHydrology;
break;
}
}

public setSequestrationRate(baseData: BaseDataView): void {
if (this.activity !== ACTIVITY.RESTORATION) {
throw new Error(
'Sequestration rate can only be calculated for restoration projects.',
);
}

if (this.sequestrationRateUsed === 'Tier 1 - IPCC default value') {
this.sequestrationRate = Number(baseData.tier1SequestrationRate);
} else if (
this.sequestrationRateUsed === 'Tier 2 - Country-specific rate'
) {
if (this.ecosystem === 'Mangrove') {
this.sequestrationRate = Number(baseData.tier2SequestrationRate);
} else {
throw new Error(
'Country-specific sequestration rate is not available for this ecosystem.',
);
}
} else if (
this.sequestrationRateUsed === 'Tier 3 - Project-specific rate'
) {
if (this.projectSpecificSequestrationRate !== undefined) {
this.sequestrationRate = this.projectSpecificSequestrationRate;
} else {
throw new Error(
'Project-specific sequestration rate must be provided when "Tier 3 - Project-specific rate" is selected.',
);
}
} else {
throw new Error('Invalid sequestration rate option selected.');
}
}

// TODO: This is not actually setting any value, check with data
public setPlantingSuccessRate(): void {
if (this.activity !== ACTIVITY.RESTORATION) {
throw new Error(
'Planting success rate can only be set for restoration projects.',
);
}

if (
this.restorationActivity === RESTORATION_ACTIVITY_SUBTYPE.PLANTING &&
this.plantingSuccessRate === undefined
) {
throw new Error(
'Planting success rate must be provided when "Planting" is selected as the restoration activity.',
);
}
}
}
3 changes: 3 additions & 0 deletions data/src/restored_code/blue_carbon_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def __init__(self, activity, ecosystem, country, master_table, base_size, base_i
self.calculate_project_parameters()
self.restoration_plan = initialize_restoration_plan()


## TODO: Here we are setting sequestration rate but in get_sequestration_rate we check that the activity is not restoration
def get_project_parameters(self):
if self.activity == 'Restoration':
additional_parameters = {
Expand Down Expand Up @@ -189,6 +191,7 @@ def get_sequestration_rate(self):
'Project-specific sequestration rate must be provided when\n \'Tier 3 '
'- Project-specific rate\' is selected.')

## TODO: This is not actually setting any value
def get_planting_success_rate(self):
if self.activity != 'Restoration':
raise ValueError('Planting success rate can only be calculated for restoration projects.')
Expand Down
7 changes: 7 additions & 0 deletions data/src/restored_code/cost_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,27 @@ def __init__(self, project):
self.total_capex = sum(self.capex_cost_plan.values())
self.total_capex_NPV = calculate_npv(self.capex_cost_plan, self.project.discount_rate)
self.opex_cost_plan = self.calculate_opex_total()

self.total_opex = sum(self.opex_cost_plan.values())

self.total_opex_NPV = calculate_npv(self.opex_cost_plan, self.project.discount_rate)

self.total_NPV = self.total_capex_NPV + self.total_opex_NPV

self.estimated_revenue_plan = self.revenue_profit_calculator.calculate_est_revenue()
self.total_revenue = sum(self.estimated_revenue_plan.values())
self.total_revenue_NPV = calculate_npv(self.estimated_revenue_plan, self.project.discount_rate)
self.total_credits_plan = self.sequestration_credits_calculator.calculate_est_credits_issued()
self.credits_issued = sum(self.total_credits_plan.values())
self.cost_per_tCO2e = self.total_NPV / self.credits_issued

self.cost_per_ha = self.total_NPV / self.project.project_size_ha
if self.project.carbon_revenues_to_cover == 'Opex':
self.NPV_covering_cost = self.total_revenue_NPV - self.total_opex_NPV
else:
self.NPV_covering_cost = self.total_revenue_NPV - self.total_NPV


self.financing_cost = float(self.project.financing_cost) * float(self.total_capex)
if self.NPV_covering_cost < 0:
self.funding_gap_NPV = -self.NPV_covering_cost
Expand Down
1 change: 1 addition & 0 deletions data/src/restored_code/revenue_profit_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def calculate_annual_net_cash_flow(self, capex_total_cost_plan, opex_total_cost_
total_cost_plan = {k: cost_plans['capex_total'].get(k, 0) + cost_plans['opex_total'].get(k, 0) for k in
set(cost_plans['capex_total']) | set(cost_plans['opex_total'])}
annual_net_cash_flow = {
## Where is annual net cash flow defined?
year: estimated_revenue[year] + total_cost_plan.get(year, 0) if year != 0 else annual_net_cash_flow for year
in range(-4, self.project_length + 1)}
return annual_net_cash_flow
Expand Down
24 changes: 12 additions & 12 deletions data/src/restored_code/test_calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@
# tier_3_emission_factor_SOC=0.5
)

## Aqui ya tengo los parametros necesarios para calcular el proyecto
#print(Project.get_project_parameters())
# Aqui ya tengo los parametros necesarios para calcular el proyecto
print(Project.get_project_parameters())

# Project.set_additional_assumptions(
# # verification_frequency,
# # discount_rate,
# # carbon_price_increase,
# buffer=0.28,
# # baseline_reassessment_frequency,
# conservation_project_length=30,
# # restoration_project_length,
# # restoration_rate
# )
Project.set_additional_assumptions(
# verification_frequency,
# discount_rate,
# carbon_price_increase,
buffer=0.28,
# baseline_reassessment_frequency,
conservation_project_length=30,
# restoration_project_length,
# restoration_rate
)
#
# Project.override_cost_input(
# feasibility_analysis=30000
Expand Down
31 changes: 31 additions & 0 deletions data/src/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from data.src.restored_code.blue_carbon_project import BlueCarbonProject

# Example usage for a restoration project
Project = BlueCarbonProject(
activity="Conservation", # ['Restoration', 'Conservation']
ecosystem="Mangrove", # ['Mangrove', 'Seagrass', 'Salt marsh']
country="Indonesia", # [
#'United States', 'Indonesia', 'Australia', 'Caribbean', 'Kenya', 'Mexico',
# 'Colombia', 'India', 'China']
master_table=master_table,
base_size=base_size,
base_increase=base_increase,
carbon_price=20, # Default value 30
carbon_revenues_to_cover="Opex", # ['Opex', 'capex+Opex']
project_size_ha=10000,
# restoration_activity='Planting', # ['Planting', 'Hybrid', 'Hydrology']
# sequestration_rate_used='Tier 1 - IPCC default value', # ['Tier 1 - IPCC default value',
# 'Tier 2 - Country-specific rate', 'Tier 3 - Project-specific rate']
# project_specific_sequestration_rate=None,
# planting_success_rate=0.8, # Default value 0.8
loss_rate_used="project-specific", # ['National average', 'project-specific']
project_specific_loss_rate=-0.001,
emission_factor_used="Tier 2 - Country-specific emission factor",
# ['Tier 1 - Global emission factor', 'Tier 2 - Country-specific emission factor',
# 'Tier 3 - Project specific emission factor']
# tier_3_project_specific_emission="AGB and SOC separately",
# ['One emission factor', 'AGB and SOC separately']
# tier_3_project_specific_emission_one_factor=0.5,
# tier_3_emission_factor_AGB=0.5,
# tier_3_emission_factor_SOC=0.5
)

0 comments on commit c42423b

Please sign in to comment.