Skip to content

Commit

Permalink
feature(FeatureAmounts): Adds setting min/max on ComputeArea service …
Browse files Browse the repository at this point in the history
…for shapefiles too
  • Loading branch information
KevSanchez committed Nov 6, 2023
1 parent e94838f commit e012bd3
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 23 deletions.
15 changes: 12 additions & 3 deletions api/apps/api/src/modules/geo-features/geo-features.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export class GeoFeaturesService extends AppBaseService<
private readonly geoDataSource: DataSource,
@InjectRepository(GeoFeatureGeometry, DbConnections.geoprocessingDB)
private readonly geoFeaturesGeometriesRepository: Repository<GeoFeatureGeometry>,
@InjectEntityManager()
private readonly apiEntityManager: EntityManager,
@InjectEntityManager(DbConnections.geoprocessingDB)
private readonly geoEntityManager: EntityManager,
@InjectRepository(GeoFeature)
Expand Down Expand Up @@ -432,9 +434,9 @@ export class GeoFeaturesService extends AppBaseService<
);

await this.saveAmountRangeForFeatures(
[geoFeature.id],
apiQueryRunner.manager,
geoQueryRunner.manager,
[geoFeature.id],
);

await apiQueryRunner.commitTransaction();
Expand Down Expand Up @@ -887,10 +889,17 @@ export class GeoFeaturesService extends AppBaseService<
}

async saveAmountRangeForFeatures(
apiEntityManager: EntityManager,
geoEntityManager: EntityManager,
featureIds: string[],
apiEntityManager?: EntityManager,
geoEntityManager?: EntityManager,
) {
apiEntityManager = apiEntityManager
? apiEntityManager
: this.apiEntityManager;
geoEntityManager = geoEntityManager
? geoEntityManager
: this.geoEntityManager;

this.logger.log(`Saving min and max amounts for new features...`);

const minAndMaxAmountsForFeatures = await geoEntityManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ export class FeatureAmountUploadService {

this.logger.log(`Saving min and max amounts for new features...`);
await this.geoFeaturesService.saveAmountRangeForFeatures(
newFeaturesFromCsvUpload.map((feature) => feature.id),
apiQueryRunner.manager,
geoQueryRunner.manager,
newFeaturesFromCsvUpload.map((feature) => feature.id),
);

this.logger.log(`Csv file upload process finished successfully`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,85 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { v4 } from 'uuid';
import { Project } from '../projects/project.api.entity';
import { ComputeArea } from './compute-area.service';
import { GeoFeature } from '@marxan-api/modules/geo-features/geo-feature.api.entity';
import { GeoFeaturesService } from '@marxan-api/modules/geo-features';

describe(ComputeArea, () => {
let fixtures: FixtureType<typeof getFixtures>;
beforeEach(async () => {
fixtures = await getFixtures();
});

it('saves amounts per planning unit when computation has not been made', async () => {
it('saves amounts per planning unit when computation has not been made and save min/max amount for the feature', async () => {
const { projectId, scenarioId } = fixtures.GivenProject();
const featureId = fixtures.GivenNoComputationHasBeenSaved();
fixtures.GivenMinMaxAmount(featureId, undefined, undefined);

await fixtures.WhenComputing(projectId, scenarioId, featureId);

await fixtures.ThenComputationsHasBeenSaved(projectId, featureId);
await fixtures.ThenMinMaxAmountWasSaved();
});

it('does not save saves amounts per planning unit when computation has already been made but saves min/max amount for the feature', async () => {
const { projectId, scenarioId } = fixtures.GivenProject();
const featureId = fixtures.GivenNoComputationHasBeenSaved();
fixtures.GivenMinMaxAmount(featureId, 1, undefined);
fixtures.GivenComputationAlreadySaved(projectId, featureId);

await fixtures.WhenComputing(projectId, scenarioId, featureId);

await fixtures.ThenComputationsWasNotDone();
await fixtures.ThenMinMaxAmountWasSaved();
});
it('does not save saves amounts per planning unit when computation has already been made and does not save min/max amount for the feature if already present', async () => {
const { projectId, scenarioId } = fixtures.GivenProject();
const featureId = fixtures.GivenNoComputationHasBeenSaved();
fixtures.GivenMinMaxAmount(featureId, 1, 10);
fixtures.GivenComputationAlreadySaved(projectId, featureId);

await fixtures.WhenComputing(projectId, scenarioId, featureId);

await fixtures.ThenComputationsWasNotDone();
await fixtures.ThenMinMaxAmountWasNotSaved();
});

it('does not save amount per planning unit when is a legacy project', async () => {
const { projectId, scenarioId } = await fixtures.GivenLegacyProject();
const featureId = fixtures.GivenNoComputationHasBeenSaved();

await fixtures.WhenComputing(projectId, scenarioId, featureId);
await fixtures.ThenComputationsHasNotBeenSaved(projectId, featureId);

await fixtures.ThenComputationsHasNotBeenSavedForLegacyProject(
projectId,
featureId,
);
await fixtures.ThenMinMaxAmountWasNotSaved();
});
});

const getFixtures = async () => {
const computeMarxanAmountPerPlanningUnitMock = jest.fn();
const findProjectMock = jest.fn();
const findGeoFeatureMock = jest.fn();
const saveAmountRangeForFeaturesMock = jest.fn();
const sandbox = await Test.createTestingModule({
imports: [],
providers: [
{
provide: getRepositoryToken(Project),
useValue: { find: findProjectMock },
},
{
provide: getRepositoryToken(GeoFeature),
useValue: { findOneOrFail: findGeoFeatureMock },
},
{
provide: GeoFeaturesService,
useValue: {
saveAmountRangeForFeatures: saveAmountRangeForFeaturesMock,
},
},
{
provide: FeatureAmountsPerPlanningUnitRepository,
useClass: MemoryFeatureAmountsPerPlanningUnitRepository,
Expand Down Expand Up @@ -99,6 +146,18 @@ const getFixtures = async () => {

return featureId;
},
GivenComputationAlreadySaved: (projectId: string, featureId: string) => {
featureAmountsPerPlanningUnitRepo.memory[projectId] = [
{ featureId, amount: 42, projectPuId: v4() },
];
},
GivenMinMaxAmount: (featureId: string, min?: number, max?: number) => {
findGeoFeatureMock.mockResolvedValueOnce({
id: featureId,
amountMin: min,
amountMax: max,
});
},
WhenComputing: (projectId: string, scenarioId: string, featureId: string) =>
sut.computeAreaPerPlanningUnitOfFeature(projectId, scenarioId, featureId),
ThenComputationsHasBeenSaved: async (
Expand All @@ -118,7 +177,10 @@ const getFixtures = async () => {
featureId,
});
},
ThenComputationsHasNotBeenSaved: async (
ThenComputationsWasNotDone: async () => {
expect(computeMarxanAmountPerPlanningUnitMock).not.toHaveBeenCalled();
},
ThenComputationsHasNotBeenSavedForLegacyProject: async (
projectId: string,
featureId: string,
) => {
Expand All @@ -131,5 +193,11 @@ const getFixtures = async () => {

expect(hasBeenSaved).toEqual(false);
},
ThenMinMaxAmountWasSaved: async () => {
expect(saveAmountRangeForFeaturesMock).toBeCalledTimes(1);
},
ThenMinMaxAmountWasNotSaved: async () => {
expect(saveAmountRangeForFeaturesMock).toBeCalledTimes(0);
},
};
};
47 changes: 33 additions & 14 deletions api/apps/api/src/modules/scenarios-features/compute-area.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Project } from '../projects/project.api.entity';
import { GeoFeaturesService } from '@marxan-api/modules/geo-features';
import { GeoFeature } from '@marxan-api/modules/geo-features/geo-feature.api.entity';

@Injectable()
export class ComputeArea {
Expand All @@ -15,7 +17,11 @@ export class ComputeArea {
private readonly featureAmountsPerPlanningUnit: FeatureAmountsPerPlanningUnitService,
@InjectRepository(Project)
private readonly projectsRepo: Repository<Project>,
@InjectRepository(GeoFeature)
private readonly geoFeatureRepo: Repository<GeoFeature>,
private readonly geoFeatureService: GeoFeaturesService,
) {}

public async computeAreaPerPlanningUnitOfFeature(
projectId: string,
scenarioId: string,
Expand All @@ -31,24 +37,37 @@ export class ComputeArea {
featureId,
);

if (alreadyComputed) return;
if (!alreadyComputed) {
const amountPerPlanningUnitOfFeature =
await this.featureAmountsPerPlanningUnit.computeMarxanAmountPerPlanningUnit(
featureId,
projectId,
);

const amountPerPlanningUnitOfFeature =
await this.featureAmountsPerPlanningUnit.computeMarxanAmountPerPlanningUnit(
featureId,
await this.featureAmountsPerPlanningUnitRepo.saveAmountPerPlanningUnitAndFeature(
projectId,
amountPerPlanningUnitOfFeature.map(
({ featureId, projectPuId, amount }) => ({
featureId,
projectPuId,
amount,
}),
),
);
}

return this.featureAmountsPerPlanningUnitRepo.saveAmountPerPlanningUnitAndFeature(
projectId,
amountPerPlanningUnitOfFeature.map(
({ featureId, projectPuId, amount }) => ({
featureId,
projectPuId,
amount,
}),
),
);
const minMaxAlreadyComputed = await this.areFeatureMinMaxSaved(featureId);

if (!minMaxAlreadyComputed) {
await this.geoFeatureService.saveAmountRangeForFeatures([featureId]);
}
}

private async areFeatureMinMaxSaved(featureId: string): Promise<boolean> {
const feature = await this.geoFeatureRepo.findOneOrFail({
where: { id: featureId },
});
return !!(feature.amountMin && feature.amountMax);
}

private async isLegacyProject(projectId: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { DbConnections } from '@marxan-api/ormconfig.connections';
import { ProjectsModule } from '@marxan-api/modules/projects/projects.module';
import { CqrsModule } from '@nestjs/cqrs';
import { ApiEventsModule } from '../api-events/api-events.module';
import { ApiEventsModule } from '@marxan-api/modules/api-events';
import { CreateFeaturesSaga } from './create-features.saga';
import { CreateFeaturesHandler } from './create-features.handler';
import { CopyDataProvider, CopyOperation, CopyQuery } from './copy';
Expand All @@ -27,12 +27,12 @@ import {
} from './stratification';
import { AccessControlModule } from '@marxan-api/modules/access-control';
import { ComputeArea } from './compute-area.service';
import { LegacyProjectImportRepositoryModule } from '../legacy-project-import/infra/legacy-project-import.repository.module';
import { FeatureAmountsPerPlanningUnitModule } from '@marxan/feature-amounts-per-planning-unit';
import { SplitFeatureConfigMapper } from '../scenarios/specification/split-feature-config.mapper';
import { FeatureHashModule } from '../features-hash/features-hash.module';
import { SplitCreateFeatures } from './split/split-create-features.service';
import { Project } from '../projects/project.api.entity';
import { GeoFeaturesModule } from '@marxan-api/modules/geo-features/geo-features.module';

@Module({
imports: [
Expand All @@ -52,6 +52,7 @@ import { Project } from '../projects/project.api.entity';
ApiEventsModule,
IntersectWithPuModule,
AccessControlModule,
GeoFeaturesModule,
],
providers: [
ScenarioFeaturesService,
Expand Down

0 comments on commit e012bd3

Please sign in to comment.