Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…ost-tool into feature/custom-project
  • Loading branch information
catalin-oancea committed Nov 23, 2024
2 parents 919b3e4 + aab9585 commit 6ba2e50
Show file tree
Hide file tree
Showing 93 changed files with 2,639 additions and 607 deletions.
6 changes: 6 additions & 0 deletions admin/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ import { ImplementationLaborCost } from "@shared/entities/cost-inputs/implementa
import { BaseSize } from "@shared/entities/base-size.entity.js";
import { BaseIncrease } from "@shared/entities/base-increase.entity.js";
import { ModelAssumptions } from "@shared/entities/model-assumptions.entity.js";
import { UserUploadCostInputs } from "@shared/entities/users/user-upload-cost-inputs.entity.js";
import { UserUploadRestorationInputs } from "@shared/entities/users/user-upload-restoration-inputs.entity.js";
import { UserUploadConservationInputs } from "@shared/entities/users/user-upload-conservation-inputs.entity.js";

// TODO: If we import the COMMON_DATABASE_ENTITIES from shared, we get an error where DataSouce is not set for a given entity
export const ADMINJS_ENTITIES = [
User,
UserUploadCostInputs,
UserUploadRestorationInputs,
UserUploadConservationInputs,
ApiEventsEntity,
Country,
ProjectSize,
Expand Down
35 changes: 34 additions & 1 deletion admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ import { ImplementationLaborCostResource } from "./resources/implementation-labo
import { BaseSizeResource } from "./resources/base-size/base-size.resource.js";
import { BaseIncreaseResource } from "./resources/base-increase/base-increase.resource.js";
import { ModelAssumptionResource } from "./resources/model-assumptions/model-assumptions.resource.js";
import { UserUploadCostInputs } from "@shared/entities/users/user-upload-cost-inputs.entity.js";
import { UserUploadConservationInputs } from "@shared/entities/users/user-upload-conservation-inputs.entity.js";
import { UserUploadRestorationInputs } from "@shared/entities/users/user-upload-restoration-inputs.entity.js";

AdminJS.registerAdapter({
Database: AdminJSTypeorm.Database,
Expand All @@ -59,6 +62,36 @@ const start = async () => {
componentLoader,
resources: [
UserResource,
{
resource: UserUploadCostInputs,
name: "UserUploadCostInputs",
options: {
navigation: {
name: "User Data",
icon: "File",
},
},
},
{
resource: UserUploadConservationInputs,
name: "UserUploadConservationInputs",
options: {
navigation: {
name: "User Data",
icon: "File",
},
},
},
{
resource: UserUploadRestorationInputs,
name: "UserUploadRestorationInputs",
options: {
navigation: {
name: "User Data",
icon: "File",
},
},
},
ProjectSizeResource,
FeasibilityAnalysisResource,
ConservationAndPlanningAdminResource,
Expand Down Expand Up @@ -119,7 +152,7 @@ const start = async () => {

app.listen(PORT, () => {
console.log(
`AdminJS started on http://localhost:${PORT}${admin.options.rootPath}`
`AdminJS started on http://localhost:${PORT}${admin.options.rootPath}`,
);
});
};
Expand Down
60 changes: 60 additions & 0 deletions api/src/modules/calculations/assumptions.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { In, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Injectable } from '@nestjs/common';
import { ModelAssumptions } from '@shared/entities/model-assumptions.entity';
import { GetOverridableAssumptionsDTO } from '@shared/dtos/custom-projects/get-overridable-assumptions.dto';
import { ACTIVITY } from '@shared/entities/activity.enum';
import { ECOSYSTEM } from '@shared/entities/ecosystem.enum';
import {
ACTIVITY_PROJECT_LENGTH_NAMES,
COMMON_OVERRIDABLE_ASSUMPTION_NAMES,
ECOSYSTEM_RESTORATION_RATE_NAMES,
} from '@shared/schemas/assumptions/assumptions.enums';

@Injectable()
export class AssumptionsRepository extends Repository<ModelAssumptions> {
map: Record<
ACTIVITY & ECOSYSTEM,
ECOSYSTEM_RESTORATION_RATE_NAMES & ACTIVITY_PROJECT_LENGTH_NAMES
> = {
[ACTIVITY.CONSERVATION]: ACTIVITY_PROJECT_LENGTH_NAMES.CONSERVATION,
[ACTIVITY.RESTORATION]: ACTIVITY_PROJECT_LENGTH_NAMES.RESTORATION,
[ECOSYSTEM.MANGROVE]: ECOSYSTEM_RESTORATION_RATE_NAMES.MANGROVE,
[ECOSYSTEM.SEAGRASS]: ECOSYSTEM_RESTORATION_RATE_NAMES.SEAGRASS,
[ECOSYSTEM.SALT_MARSH]: ECOSYSTEM_RESTORATION_RATE_NAMES.SALT_MARSH,
};
constructor(
@InjectRepository(ModelAssumptions)
private repo: Repository<ModelAssumptions>,
) {
super(repo.target, repo.manager, repo.queryRunner);
}

async getOverridableModelAssumptions(dto: GetOverridableAssumptionsDTO) {
const assumptions = await this.createQueryBuilder('model_assumptions')
.select(['name', 'unit', 'value'])
.where({
name: In(this.getAssumptionNamesByCountryAndEcosystem(dto)),
})
.orderBy('name', 'ASC')
.getRawMany();
if (assumptions.length !== 7) {
throw new Error('Not all required overridable assumptions were found');
}
return assumptions;
}

private getAssumptionNamesByCountryAndEcosystem(
dto: GetOverridableAssumptionsDTO,
): string[] {
const { ecosystem, activity } = dto;
const assumptions = [...COMMON_OVERRIDABLE_ASSUMPTION_NAMES] as string[];
assumptions.push(this.map[ecosystem]);
assumptions.push(this.map[activity]);
return assumptions;
}

async getAllModelAssumptions() {
// TODO: To be implemented. We probably don't want to retrieve by find() as we would need to have a constant-like object for the calculations
}
}
5 changes: 3 additions & 2 deletions api/src/modules/calculations/calculations.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DataRepository } from '@api/modules/calculations/data.repository';
import { ModelAssumptions } from '@shared/entities/model-assumptions.entity';
import { BaseIncrease } from '@shared/entities/base-increase.entity';
import { BaseSize } from '@shared/entities/base-size.entity';
import { AssumptionsRepository } from '@api/modules/calculations/assumptions.repository';

@Module({
imports: [
Expand All @@ -16,7 +17,7 @@ import { BaseSize } from '@shared/entities/base-size.entity';
BaseSize,
]),
],
providers: [CalculationEngine, DataRepository],
exports: [CalculationEngine, DataRepository],
providers: [CalculationEngine, DataRepository, AssumptionsRepository],
exports: [CalculationEngine, DataRepository, AssumptionsRepository],
})
export class CalculationsModule {}
6 changes: 4 additions & 2 deletions api/src/modules/calculations/conservation-cost.calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { DEFAULT_STUFF } from '@api/modules/custom-projects/project-config.inter
import { BaseIncrease } from '@shared/entities/base-increase.entity';
import { BaseSize } from '@shared/entities/base-size.entity';
import { SequestrationRatesCalculator } from '@api/modules/calculations/sequestration-rate.calculator';
import { RESTORATION_ACTIVITY_SUBTYPE } from '@shared/entities/projects.entity';
import { ACTIVITY } from '@shared/entities/activity.enum';
import {
ACTIVITY,
RESTORATION_ACTIVITY_SUBTYPE,
} from '@shared/entities/activity.enum';
import { RevenueProfitCalculator } from '@api/modules/calculations/revenue-profit.calculators';
import { Finance } from 'financejs';

Expand Down
9 changes: 5 additions & 4 deletions api/src/modules/calculations/cost.calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RestorationProjectInput } from '@api/modules/custom-projects/input-fact
import { BaseSize } from '@shared/entities/base-size.entity';
import { BaseIncrease } from '@shared/entities/base-increase.entity';
import {
CostInputs,
OverridableCostInputs,
PROJECT_DEVELOPMENT_TYPE,
} from '@api/modules/custom-projects/dto/project-cost-inputs.dto';

Expand All @@ -29,7 +29,7 @@ type CostPlanMap = {
// ) {}
// }

type CostPlans = Record<keyof CostInputs, CostPlanMap>;
type CostPlans = Record<keyof OverridableCostInputs, CostPlanMap>;

export enum COST_KEYS {
FEASIBILITY_ANALYSIS = 'feasibilityAnalysis',
Expand Down Expand Up @@ -165,8 +165,9 @@ export class CostCalculator {
const totalBaseCost = this.getTotalBaseCost(
COST_KEYS.COMMUNITY_REPRESENTATION,
);
const projectDevelopmentType =
this.projectInput.costInputs.otherCommunityCashFlow;
// TODO: TO avoid type crash, fix after cost calculator has all required inputs
const projectDevelopmentType = 'Development';
// this.projectInput.costInputs.otherCommunityCashFlow;
const initialCost =
projectDevelopmentType === PROJECT_DEVELOPMENT_TYPE.DEVELOPMENT
? 0
Expand Down
112 changes: 77 additions & 35 deletions api/src/modules/calculations/data.repository.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Repository } from 'typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { BaseDataView } from '@shared/entities/base-data.view';
import { InjectRepository } from '@nestjs/typeorm';
import { Injectable, NotFoundException } from '@nestjs/common';
import { ECOSYSTEM } from '@shared/entities/ecosystem.enum';
import { ACTIVITY } from '@shared/entities/activity.enum';
import { GetDefaultCostInputsDto } from '@shared/dtos/custom-projects/get-default-cost-inputs.dto';
import { CostInputs } from '@api/modules/custom-projects/dto/project-cost-inputs.dto';
import { ModelAssumptions } from '@shared/entities/model-assumptions.entity';
import {
ACTIVITY,
RESTORATION_ACTIVITY_SUBTYPE,
} from '@shared/entities/activity.enum';
import { GetOverridableCostInputs } from '@shared/dtos/custom-projects/get-overridable-cost-inputs.dto';
import { OverridableCostInputs } from '@api/modules/custom-projects/dto/project-cost-inputs.dto';
import { BaseSize } from '@shared/entities/base-size.entity';
import { BaseIncrease } from '@shared/entities/base-increase.entity';

Expand All @@ -17,6 +19,24 @@ export type CarbonInputs = {
emissionFactorSoc: BaseDataView['emissionFactorSoc'];
};

const COMMON_OVERRIDABLE_COST_INPUTS = [
'feasibilityAnalysis',
'conservationPlanningAndAdmin',
'dataCollectionAndFieldCost',
'communityRepresentation',
'blueCarbonProjectPlanning',
'establishingCarbonRights',
'validation',
'monitoring',
'maintenance',
'communityBenefitSharingFund',
'carbonStandardFees',
'baselineReassessment',
'mrv',
'longTermProjectOperatingCost',
'financingCost',
];

@Injectable()
export class DataRepository extends Repository<BaseDataView> {
constructor(
Expand Down Expand Up @@ -70,41 +90,25 @@ export class DataRepository extends Repository<BaseDataView> {
return defaultCarbonInputs;
}

async getDefaultCostInputs(
dto: GetDefaultCostInputsDto,
): Promise<CostInputs> {
async getOverridableCostInputs(
dto: GetOverridableCostInputs,
): Promise<OverridableCostInputs> {
const { countryCode, activity, ecosystem } = dto;
// The coming CostInput has a implementation labor property which does not exist in the BaseDataView entity, so we use a partial type to avoid the error
const costInputs: Partial<CostInputs> = await this.findOne({
where: { countryCode, activity, ecosystem },
select: [
'feasibilityAnalysis',
'conservationPlanningAndAdmin',
'dataCollectionAndFieldCost',
'communityRepresentation',
'blueCarbonProjectPlanning',
'establishingCarbonRights',
'validation',
'implementationLaborHybrid',
'monitoring',
'maintenance',
'communityBenefitSharingFund',
'carbonStandardFees',
'baselineReassessment',
'mrv',
'longTermProjectOperatingCost',
'financingCost',
'implementationLaborPlanting',
'implementationLaborHydrology',
'otherCommunityCashFlow',
],
const queryBuilder = this.createQueryBuilder().where({
countryCode,
activity,
ecosystem,
});

const selectQueryBuilder = this.buildSelect(queryBuilder, dto);

const costInputs = await selectQueryBuilder.getRawOne();
if (!costInputs) {
throw new NotFoundException(
`Could not find default Cost Inputs for country ${countryCode}, activity ${activity} and ecosystem ${ecosystem}`,
);
}
return costInputs as CostInputs;
return costInputs;
}

async getBaseIncreaseAndSize(params: {
Expand All @@ -128,7 +132,45 @@ export class DataRepository extends Repository<BaseDataView> {
return { baseSize, baseIncrease };
}

async getDefaultModelAssumptions() {
return this.repo.manager.getRepository(ModelAssumptions).find();
/**
* As of now, only implementation labor has to be dynamically selected based on the restoration activity, if the activity is Restoration
* If the activity is Conservation, the implementation labor should be null or 0
*/
private buildSelect(
queryBuilder: SelectQueryBuilder<BaseDataView>,
dto: GetOverridableCostInputs,
) {
let implementationLaborToSelect: string;
if (dto.activity === ACTIVITY.RESTORATION) {
switch (dto.restorationActivity) {
case RESTORATION_ACTIVITY_SUBTYPE.HYBRID:
implementationLaborToSelect = 'implementationLaborHybrid';
break;
case RESTORATION_ACTIVITY_SUBTYPE.PLANTING:
implementationLaborToSelect = 'implementationLaborPlanting';
break;
case RESTORATION_ACTIVITY_SUBTYPE.HYDROLOGY:
implementationLaborToSelect = 'implementationLaborHydrology';
break;
}
queryBuilder.select(
queryBuilder.alias + '.' + implementationLaborToSelect,
'implementationLabor',
);
}
// Set implementation labor to 0 if the activity is Conservation, since there is no implementation labor data for Conservation
if (dto.activity === ACTIVITY.CONSERVATION) {
queryBuilder.select('0', 'implementationLabor');
}
// Since we are using aliases and selecting columns that are not in the entity, the transformer does not get triggered
// So we manually parse the values to float
for (const name of COMMON_OVERRIDABLE_COST_INPUTS) {
queryBuilder.addSelect(
queryBuilder.alias + '.' + name + ' :: float',
name,
);
}

return queryBuilder;
}
}
7 changes: 5 additions & 2 deletions api/src/modules/calculations/sequestration-rate.calculator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ConservationProject } from '@api/modules/custom-projects/conservation.project';
import { ACTIVITY } from '@shared/entities/activity.enum';
import { RESTORATION_ACTIVITY_SUBTYPE } from '@shared/entities/projects.entity';
import {
ACTIVITY,
RESTORATION_ACTIVITY_SUBTYPE,
} from '@shared/entities/activity.enum';

import { Injectable } from '@nestjs/common';

@Injectable()
Expand Down
5 changes: 3 additions & 2 deletions api/src/modules/custom-projects/custom-projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ export class CustomProjectsController {
async getAssumptions(): Promise<ControllerResponse> {
return tsRestHandler(
customProjectContract.getDefaultAssumptions,
async () => {
const data = await this.customProjects.getDefaultAssumptions();
async ({ query }) => {
const data: any =
await this.customProjects.getDefaultAssumptions(query);
return { body: { data }, status: HttpStatus.OK };
},
);
Expand Down
Loading

0 comments on commit 6ba2e50

Please sign in to comment.