Skip to content

Commit

Permalink
feature(Feature Amounts): Copies amounts to (geo)FeatureAmounts table…
Browse files Browse the repository at this point in the history
… for shapefiles and legacy projects
  • Loading branch information
KevSanchez authored and hotzevzl committed Oct 26, 2023
1 parent 963fd17 commit 2ac1fc3
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ test(`custom feature csv upload`, async () => {
expect(result.body).toHaveLength(2);
await fixtures.ThenNewFeaturesAreCreated();
await fixtures.ThenNewFeaturesAmountsAreCreated();
await fixtures.ThenFeatureAmountPerPlanningUnitAreCreated();
await fixtures.ThenFeatureUploadRegistryIsCleared();
await fixtures.ThenProjectSourcesIsSetToLegacyProject();
});
Expand Down
51 changes: 46 additions & 5 deletions api/apps/api/test/upload-feature/upload-feature.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { HttpStatus } from '@nestjs/common';
import { GeoFeatureTag } from '@marxan-api/modules/geo-feature-tags/geo-feature-tag.api.entity';
import { tagMaxlength } from '@marxan-api/modules/geo-feature-tags/dto/update-geo-feature-tag.dto';
import { Project } from '@marxan-api/modules/projects/project.api.entity';
import { FeatureAmountsPerPlanningUnitEntity } from '@marxan/feature-amounts-per-planning-unit';

export const getFixtures = async () => {
const app = await bootstrapApplication();
Expand Down Expand Up @@ -54,9 +55,18 @@ export const getFixtures = async () => {
getRepositoryToken(Project, DbConnections.default),
);

const featuresAmounsGeoDbRepository: Repository<GeoFeatureGeometry> = app.get(
getRepositoryToken(GeoFeatureGeometry, DbConnections.geoprocessingDB),
);
const featuresAmountsGeoDbRepository: Repository<GeoFeatureGeometry> =
app.get(
getRepositoryToken(GeoFeatureGeometry, DbConnections.geoprocessingDB),
);

const featureAmountsPerPlanningUnitRepo: Repository<FeatureAmountsPerPlanningUnitEntity> =
app.get(
getRepositoryToken(
FeatureAmountsPerPlanningUnitEntity,
DbConnections.geoprocessingDB,
),
);

const geoEntityManager: EntityManager = app.get(
getEntityManagerToken(DbConnections.geoprocessingDB),
Expand Down Expand Up @@ -322,13 +332,13 @@ export const getFixtures = async () => {
featureClassName: 'feat_28135ef',
},
});
const newFeature1Amounts = await featuresAmounsGeoDbRepository.find({
const newFeature1Amounts = await featuresAmountsGeoDbRepository.find({
where: { featureId: newFeatures1?.id },
order: {
amount: 'DESC',
},
});
const newFeature2Amounts = await featuresAmounsGeoDbRepository.find({
const newFeature2Amounts = await featuresAmountsGeoDbRepository.find({
where: { featureId: newFeatures2?.id },
});

Expand All @@ -342,6 +352,37 @@ export const getFixtures = async () => {
expect(newFeature2Amounts[1].amount).toBe(0);
expect(newFeature2Amounts[2].amount).toBe(0);
},
ThenFeatureAmountPerPlanningUnitAreCreated: async () => {
const feature1 = await featuresRepository.findOne({
where: {
featureClassName: 'feat_1d666bd',
},
});
const feature2 = await featuresRepository.findOne({
where: {
featureClassName: 'feat_28135ef',
},
});
const feature1Amounts = await featureAmountsPerPlanningUnitRepo.find({
where: { featureId: feature1?.id },
order: {
amount: 'DESC',
},
});
const feature2Amounts = await featureAmountsPerPlanningUnitRepo.find({
where: { featureId: feature2?.id },
});

expect(feature1Amounts).toHaveLength(3);
expect(feature2Amounts).toHaveLength(3);
expect(feature1Amounts[0].amount).toBe(4.245387225);
expect(feature1Amounts[1].amount).toBe(4.245387225);
expect(feature1Amounts[2].amount).toBe(3.245387225);

expect(feature2Amounts[0].amount).toBe(0);
expect(feature2Amounts[1].amount).toBe(0);
expect(feature2Amounts[2].amount).toBe(0);
},
ThenFeatureUploadRegistryIsCleared: async () => {
const featureImportRegistryRecord = await featureImportRegistry.findOne({
where: { projectId },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
PuvsprDatReader,
} from './file-readers/puvspr-dat.reader';
import { SpecDatReader, SpecDatRow } from './file-readers/spec-dat.reader';
import { FeatureAmountsPerPlanningUnitEntity } from '@marxan/feature-amounts-per-planning-unit';

type FeaturesData = {
id: string;
Expand Down Expand Up @@ -302,6 +303,7 @@ export class FeaturesLegacyProjectPieceImporter
};
});

this.logger.log(`Saving features API metadata...`);
await Promise.all(
featuresInsertValues.map((value) =>
apiEm
Expand All @@ -326,6 +328,7 @@ export class FeaturesLegacyProjectPieceImporter
projectPusGeomsMap,
);

this.logger.log(`Saving Geo feature entities...`);
await Promise.all(
chunk(
featuresDataInsertValues,
Expand All @@ -344,6 +347,30 @@ export class FeaturesLegacyProjectPieceImporter
),
);

this.logger.log(`Saving feature amounts...`);
await Promise.all(
chunk(
featuresDataInsertValues,
CHUNK_SIZE_FOR_BATCH_GEODB_OPERATIONS,
).map((values) => {
this.geoprocessingEntityManager
.createQueryBuilder()
.insert()
.into(FeatureAmountsPerPlanningUnitEntity)
.values(
values.map(({ amount, projectPuId, featureId }) => {
return { amount, projectPuId, featureId, projectId };
}),
)
.execute();
}),
);

await this.updateAmountMinMaxForAPIFeatures(
apiEm,
featuresData.map((feature) => feature.id),
);

return nonExistingPus;
},
);
Expand All @@ -355,4 +382,39 @@ export class FeaturesLegacyProjectPieceImporter
: undefined,
};
}

private async updateAmountMinMaxForAPIFeatures(
apiEntityManager: EntityManager,
featureIds: string[],
): Promise<void> {
this.logger.log(`Saving min and max amounts for new features...`);

const minAndMaxAmountsForFeatures = await this.geoprocessingEntityManager
.createQueryBuilder()
.select('feature_id', 'id')
.addSelect('MIN(amount)', 'amountMin')
.addSelect('MAX(amount)', 'amountMax')
.from('feature_amounts_per_planning_unit', 'fappu')
.where('fappu.feature_id IN (:...featureIds)', { featureIds })
.groupBy('fappu.feature_id')
.getRawMany();

const minMaxSqlValueStringForFeatures = minAndMaxAmountsForFeatures
.map(
(feature) =>
`(uuid('${feature.id}'), ${feature.amountMin}, ${feature.amountMax})`,
)
.join(', ');

const query = `
update features set
amount_min = minmax.min,
amount_max = minmax.max
from (
values
${minMaxSqlValueStringForFeatures}
) as minmax(feature_id, min, max)
where features.id = minmax.feature_id;`;
await apiEntityManager.query(query);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
GivenUserExists,
} from '../cloning/fixtures';
import { FakeLogger } from '@marxan-geoprocessing/utils/__mocks__/fake-logger';
import { FeatureAmountsPerPlanningUnitEntity } from '@marxan/feature-amounts-per-planning-unit';

let fixtures: FixtureType<typeof getFixtures>;

Expand Down Expand Up @@ -233,7 +234,11 @@ const getFixtures = async () => {
logging: false,
}),
TypeOrmModule.forFeature(
[ProjectsPuEntity, GeoFeatureGeometry],
[
ProjectsPuEntity,
GeoFeatureGeometry,
FeatureAmountsPerPlanningUnitEntity,
],
geoprocessingConnections.default,
),
TypeOrmModule.forRoot({
Expand Down Expand Up @@ -289,6 +294,8 @@ const getFixtures = async () => {
const featuresDataRepo = sandbox.get<Repository<GeoFeatureGeometry>>(
getRepositoryToken(GeoFeatureGeometry),
);
const featureAmountsPerPlanningUnitRepo: Repository<FeatureAmountsPerPlanningUnitEntity> =
sandbox.get(getRepositoryToken(FeatureAmountsPerPlanningUnitEntity));

const specDatFileType = LegacyProjectImportFileType.SpecDat;
const puvsprDatFileType = LegacyProjectImportFileType.PuvsprDat;
Expand Down Expand Up @@ -515,6 +522,21 @@ const getFixtures = async () => {
},
});

const featureAmounts = await featureAmountsPerPlanningUnitRepo.find({
where: { featureId: In(insertedFeaturesIds) },
});

const featuresMinMax = await apiEntityManager
.createQueryBuilder()
.select('id')
.addSelect('amount_min', 'amountMin')
.addSelect('amount_max', 'amountMax')
.from('features', 'f')
.where('f.id IN (:...featureIds)', {
featureIds: insertedFeaturesIds,
})
.execute();

expect(insertedFeaturesData).toHaveLength(
amountOfFeaturesData - amountOfNonExistingPuids,
);
Expand All @@ -526,6 +548,25 @@ const getFixtures = async () => {
pus.map(({ id }) => id).includes(projectPuId),
),
).toEqual(true);

expect(featureAmounts).toHaveLength(
amountOfFeaturesData - amountOfNonExistingPuids,
);
expect(
featureAmounts.every(
({ amount, projectPuId }) =>
amount === expectedAmount &&
projectPuId &&
pus.map(({ id }) => id).includes(projectPuId),
),
).toEqual(true);
expect(
featuresMinMax.every(
(featureMinMax: any) =>
featureMinMax.amountMin === expectedAmount &&
featureMinMax.amountMax === expectedAmount,
),
).toBeTruthy();
},
};
},
Expand Down

1 comment on commit 2ac1fc3

@vercel
Copy link

@vercel vercel bot commented on 2ac1fc3 Oct 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

marxan – ./

marxan23.vercel.app
marxan-git-develop-vizzuality1.vercel.app
marxan-vizzuality1.vercel.app

Please sign in to comment.