Skip to content

Commit

Permalink
feat(api): cost surface: update costs
Browse files Browse the repository at this point in the history
  • Loading branch information
kgajowy committed May 24, 2021
1 parent 96c0f90 commit 5c175c4
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 26 deletions.
15 changes: 11 additions & 4 deletions api/src/modules/analysis/analysis.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { PlanningUnitsModule } from '../planning-units/planning-units.module';
import { ScenariosPlanningUnitModule } from '../scenarios-planning-unit/scenarios-planning-unit.module';
import { DbConnections } from '../../ormconfig.connections';

import { ScenariosPlanningUnitModule } from '../scenarios-planning-unit/scenarios-planning-unit.module';
import { AdjustCostSurface } from './entry-points/adjust-cost-surface';

import { AdjustPlanningUnits } from './entry-points/adjust-planning-units';
import { GetScenarioStatus } from './entry-points/get-scenario-status';

import { UpdateCostSurfaceService } from './providers/cost-surface/update-cost-surface.service';
import { ArePuidsAllowedAdapter } from './providers/shared/adapters/are-puids-allowed-adapter';
import { ArePuidsAllowedPort } from './providers/shared/are-puids-allowed.port';
Expand All @@ -14,12 +16,17 @@ import { ScenarioStatusService } from './providers/status/scenario-status.servic
import { RequestJobPort } from './providers/planning-units/request-job.port';
import { AsyncJobsAdapter } from './providers/planning-units/adapters/async-jobs-adapter';
import { CostSurfaceRepo } from './providers/cost-surface/cost-surface-repo';
import { BaseAppCostSurface } from './providers/cost-surface/adapters/base-app-cost-surface';
import { TypeormCostSurface } from './providers/cost-surface/adapters/typeorm-cost-surface';
import { QueueModule } from '../queue/queue.module';
import { queueName } from './queue-name';
import { ScenariosPuCostDataGeo } from './providers/cost-surface/adapters/scenarios-pu-cost-data.geo.entity';

@Module({
imports: [
TypeOrmModule.forFeature(
[ScenariosPuCostDataGeo],
DbConnections.geoprocessingDB,
),
ScenariosPlanningUnitModule,
PlanningUnitsModule,
QueueModule.register({
Expand Down Expand Up @@ -52,7 +59,7 @@ import { queueName } from './queue-name';
},
{
provide: CostSurfaceRepo,
useClass: BaseAppCostSurface,
useClass: TypeormCostSurface,
},
],
exports: [AdjustCostSurface, AdjustPlanningUnits, GetScenarioStatus],
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ScenariosPlanningUnitGeoEntity } from '../../../../scenarios-planning-u
@Entity({
name: 'scenarios_pu_cost_data',
})
export class ScenariosPutCostDataGeo {
export class ScenariosPuCostDataGeo {
@PrimaryGeneratedColumn('uuid')
id!: string;

Expand All @@ -37,6 +37,6 @@ export class ScenariosPutCostDataGeo {
})
scenariosPlanningUnit?: ScenariosPlanningUnitGeoEntity | null;

@RelationId((spud: ScenariosPutCostDataGeo) => spud.scenariosPlanningUnit)
@RelationId((spud: ScenariosPuCostDataGeo) => spud.scenariosPlanningUnit)
scenariosPuDataId!: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { flatten, Injectable } from '@nestjs/common';
import { CostSurfaceRepo } from '../cost-surface-repo';
import { CostSurfaceInputDto } from '../../../entry-points/adjust-cost-surface-input';
import { InjectRepository } from '@nestjs/typeorm';
import { ScenariosPuCostDataGeo } from './scenarios-pu-cost-data.geo.entity';
import { DbConnections } from '../../../../../ormconfig.connections';
import { Repository } from 'typeorm';

type Success = true;

@Injectable()
export class TypeormCostSurface implements CostSurfaceRepo {
constructor(
@InjectRepository(ScenariosPuCostDataGeo, DbConnections.geoprocessingDB)
private readonly costs: Repository<ScenariosPuCostDataGeo>,
) {
//
}

async applyCostSurface(
_: string,
values: CostSurfaceInputDto['planningUnits'],
): Promise<Success> {
const pairs = values.map<[string, number]>((pair) => [pair.id, pair.cost]);
await this.costs.query(
`
UPDATE scenarios_pu_cost_data as spd
set "cost" = pucd.new_cost
from (values
${this.generateParametrizedValues(pairs)}
) as pucd(output_results_data_id, new_cost)
where pucd.output_results_data_id = spd.output_results_data_id
`,
flatten(pairs),
);
return true;
}

/**
*
* generates parametrized input for:
* ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid, 5000::float)
*
* in form of:
* ($1::uuid, $2::float),
* ($3::uuid, $4::float),
* ($5::uuid, $6::float),
* ...
*
*/
private generateParametrizedValues(pairs: [string, number][]): string {
return pairs
.map(
(_, index) =>
`($${(index + 1) * 2 - 1}::uuid, $${(index + 1) * 2}::float)`,
)
.join(',');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { INestApplication } from '@nestjs/common';
import { TypeormCostSurface } from '../../../src/modules/analysis/providers/cost-surface/adapters/typeorm-cost-surface';
import { bootstrapApplication } from '../../utils/api-application';
import { CostSurfaceUpdateWorld, createWorld } from './world';
import { CostSurfaceRepo } from '../../../src/modules/analysis/providers/cost-surface/cost-surface-repo';

let app: INestApplication;
let sut: TypeormCostSurface;
let world: CostSurfaceUpdateWorld;

beforeAll(async () => {
app = await bootstrapApplication();
world = await createWorld(app);
sut = app.get(CostSurfaceRepo);
});

afterAll(async () => {
await world.cleanup();
await app.close();
});

describe(`when updating some of the costs`, () => {
let puCostDataIds: string[];
beforeEach(async () => {
puCostDataIds = await world.GivenPuCostDataExists();
});

it(`applies new costs to given PU`, async () => {
const costOf9999Id = puCostDataIds[0];
const costOf1Id = puCostDataIds[1];
const sameCostId = puCostDataIds[2];

await sut.applyCostSurface(world.scenarioId, [
{
cost: 9999,
id: costOf9999Id,
},
{
cost: 1,
id: costOf1Id,
},
]);

const afterChanges = await world.GetPuCostsData(world.scenarioId);

expect(afterChanges).toContainEqual({
scenario_id: world.scenarioId,
cost: 9999,
pu_id: costOf9999Id,
});

expect(afterChanges).toContainEqual({
scenario_id: world.scenarioId,
cost: 1,
pu_id: costOf1Id,
});

expect(afterChanges).toContainEqual({
scenario_id: world.scenarioId,
cost: expect.any(Number),
pu_id: sameCostId,
});
});
});
80 changes: 80 additions & 0 deletions api/test/integration/cost-surface-repo/world.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { INestApplication } from '@nestjs/common';
import { ScenariosPlanningUnitGeoEntity } from '../../../src/modules/scenarios-planning-unit/entities/scenarios-planning-unit.geo.entity';
import { GivenScenarioPuDataExists } from '../../steps/given-scenario-pu-data-exists';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ScenariosPuCostDataGeo } from '../../../src/modules/analysis/providers/cost-surface/adapters/scenarios-pu-cost-data.geo.entity';
import { DbConnections } from '../../../src/ormconfig.connections';
import { In, Repository } from 'typeorm';
import { v4 } from 'uuid';

export interface CostSurfaceUpdateWorld {
cleanup: () => Promise<void>;
scenarioId: string;
planningUnitsIds: string[];
GivenPuCostDataExists: () => Promise<string[]>;
GetPuCostsData: (
scenarioId: string,
) => Promise<{ scenario_id: string; cost: number; pu_id: string }[]>;
}

export const createWorld = async (
app: INestApplication,
): Promise<CostSurfaceUpdateWorld> => {
const scenarioId = v4();
const puCostRepoToken = getRepositoryToken(
ScenariosPuCostDataGeo,
DbConnections.geoprocessingDB,
);
const puDataRepoToken = getRepositoryToken(
ScenariosPlanningUnitGeoEntity,
DbConnections.geoprocessingDB,
);
const puDataRepo: Repository<ScenariosPlanningUnitGeoEntity> = app.get(
puDataRepoToken,
);
const puCostDataRepo: Repository<ScenariosPuCostDataGeo> = app.get(
puCostRepoToken,
);
const scenarioPuData = await GivenScenarioPuDataExists(
puDataRepo,
scenarioId,
);

const puDataIds = scenarioPuData.rows.map((row) => row.id);
const puIds = scenarioPuData.rows.map((row) => row.puGeometryId);

return {
GetPuCostsData: async (
scenarioId: string,
): Promise<{ scenario_id: string; cost: number; pu_id: string }[]> =>
puCostDataRepo.query(`
select spud.scenario_id, spucd."cost", spucd.output_results_data_id as pu_id from scenarios_pu_data as spud join scenarios_pu_cost_data as spucd on (spud."id" = spucd.scenarios_pu_data_id)
where spud.scenario_id = '${scenarioId}'
`),
GivenPuCostDataExists: async () =>
puCostDataRepo
.save(
scenarioPuData.rows.map((scenarioPuData) =>
puCostDataRepo.create({
scenariosPuDataId: scenarioPuData.id,
cost: 300,
planningUnitId: scenarioPuData.puGeometryId,
scenariosPlanningUnit: scenarioPuData,
}),
),
)
.then((rows) => rows.map((row) => row.planningUnitId)),
planningUnitsIds: puIds,
scenarioId,
cleanup: async () => {
await puCostDataRepo.delete({
scenariosPlanningUnit: {
id: In(puDataIds),
},
});
await puDataRepo.delete({
scenarioId,
});
},
};
};

0 comments on commit 5c175c4

Please sign in to comment.