From e1c1693977e091c836c97e144c5d85ccba740058 Mon Sep 17 00:00:00 2001 From: Dyostiq <15174395+Dyostiq@users.noreply.github.com> Date: Thu, 26 Aug 2021 16:20:40 +0200 Subject: [PATCH] fix(api): keep only current specification data in the table for processing --- .../specification-activated.handler.ts | 23 +-- .../specification-processing.finished.ts | 8 ++ ...tivate-candidate-specification.fixtures.ts | 2 +- .../domain/__tests__/fixtures.ts | 1 + .../events/specification-activated.event.ts | 5 +- .../domain/scenario-specification.ts | 2 +- .../modules/scenario-specification/index.ts | 2 + .../copy/copy-operation.service.ts | 1 + .../copy/copy-query.service.ts | 25 ++-- .../create-features.handler.ts | 4 +- .../src/modules/scenarios-features/index.ts | 1 + .../move-data-from-preparation.command.ts | 10 ++ .../move-data-from-preparation.handler.ts | 77 ++++++++++ .../move-data-from-preparation.saga.ts | 23 +++ .../scenario-features.module.ts | 4 + .../split/split-operation.service.ts | 7 +- .../split/split-query.service.ts | 20 +-- ...ddSpecificationIdToScenarioFeaturesData.ts | 16 +++ ...PerparationTableForScenarioFeaturesData.ts | 20 +++ api/libs/features/src/index.ts | 1 + .../src/scenario-features-data.geo.entity.ts | 132 +---------------- ...cenario-features-preparation.geo.entity.ts | 133 ++++++++++++++++++ 22 files changed, 350 insertions(+), 167 deletions(-) create mode 100644 api/apps/api/src/modules/scenario-specification/adapters/specification-processing.finished.ts create mode 100644 api/apps/api/src/modules/scenarios-features/move-data-from-preparation.command.ts create mode 100644 api/apps/api/src/modules/scenarios-features/move-data-from-preparation.handler.ts create mode 100644 api/apps/api/src/modules/scenarios-features/move-data-from-preparation.saga.ts create mode 100644 api/apps/geoprocessing/src/migrations/geoprocessing/1629831194337-AddSpecificationIdToScenarioFeaturesData.ts create mode 100644 api/apps/geoprocessing/src/migrations/geoprocessing/1629896532969-AddPerparationTableForScenarioFeaturesData.ts create mode 100644 api/libs/features/src/scenario-features-preparation.geo.entity.ts diff --git a/api/apps/api/src/modules/scenario-specification/adapters/specification-activated.handler.ts b/api/apps/api/src/modules/scenario-specification/adapters/specification-activated.handler.ts index 8f2a63de90..39b0dc33c4 100644 --- a/api/apps/api/src/modules/scenario-specification/adapters/specification-activated.handler.ts +++ b/api/apps/api/src/modules/scenario-specification/adapters/specification-activated.handler.ts @@ -1,28 +1,17 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; -import { Repository } from 'typeorm'; -import { InjectRepository } from '@nestjs/typeorm'; import { API_EVENT_KINDS } from '@marxan/api-events'; import { ApiEventsService } from '@marxan-api/modules/api-events/api-events.service'; -import { SpecificationApiEntity } from '@marxan-api/modules/specification/adapters/specification.api.entity'; -import { SpecificationActivated } from '../domain'; +import { SpecificationProcessingFinished } from './specification-processing.finished'; -@EventsHandler(SpecificationActivated) +@EventsHandler(SpecificationProcessingFinished) export class SpecificationActivatedHandler - implements IEventHandler { - constructor( - private readonly apiEvents: ApiEventsService, - @InjectRepository(SpecificationApiEntity) - private readonly specifications: Repository, - ) {} + implements IEventHandler { + constructor(private readonly apiEvents: ApiEventsService) {} - async handle(event: SpecificationActivated) { - const specification = await this.specifications.findOne( - event.specificationId.value, - ); - if (!specification) return; + async handle(event: SpecificationProcessingFinished) { await this.apiEvents.create({ kind: API_EVENT_KINDS.scenario__specification__finished__v1__alpha1, - topic: specification.scenarioId, + topic: event.scenarioId, data: { ...event }, }); } diff --git a/api/apps/api/src/modules/scenario-specification/adapters/specification-processing.finished.ts b/api/apps/api/src/modules/scenario-specification/adapters/specification-processing.finished.ts new file mode 100644 index 0000000000..12d599c297 --- /dev/null +++ b/api/apps/api/src/modules/scenario-specification/adapters/specification-processing.finished.ts @@ -0,0 +1,8 @@ +import { IEvent } from '@nestjs/cqrs'; + +export class SpecificationProcessingFinished implements IEvent { + constructor( + public readonly scenarioId: string, + public readonly specificationId: string, + ) {} +} diff --git a/api/apps/api/src/modules/scenario-specification/application/__tests__/activate-candidate-specification.fixtures.ts b/api/apps/api/src/modules/scenario-specification/application/__tests__/activate-candidate-specification.fixtures.ts index 9c6c1e2151..d1dd26de88 100644 --- a/api/apps/api/src/modules/scenario-specification/application/__tests__/activate-candidate-specification.fixtures.ts +++ b/api/apps/api/src/modules/scenario-specification/application/__tests__/activate-candidate-specification.fixtures.ts @@ -82,7 +82,7 @@ export const getFixtures = async () => { }, ThenSpecificationIsActivated: () => { expect(events).toEqual([ - new SpecificationActivated(currentCandidateSpecificationId), + new SpecificationActivated(scenarioId, currentCandidateSpecificationId), ]); }, ThenSpecificationIsNotActivated: async ( diff --git a/api/apps/api/src/modules/scenario-specification/domain/__tests__/fixtures.ts b/api/apps/api/src/modules/scenario-specification/domain/__tests__/fixtures.ts index 07d60f2821..54c231669c 100644 --- a/api/apps/api/src/modules/scenario-specification/domain/__tests__/fixtures.ts +++ b/api/apps/api/src/modules/scenario-specification/domain/__tests__/fixtures.ts @@ -52,6 +52,7 @@ export const getFixtures = () => { expect(result.right).toEqual(void 0); expect(scenarioSpecification.getUncommittedEvents()).toEqual([ new SpecificationActivated( + scenarioSpecification.scenarioId, scenarioSpecification.currentActiveSpecification!, ), ]); diff --git a/api/apps/api/src/modules/scenario-specification/domain/events/specification-activated.event.ts b/api/apps/api/src/modules/scenario-specification/domain/events/specification-activated.event.ts index f21e921697..2de0045a5b 100644 --- a/api/apps/api/src/modules/scenario-specification/domain/events/specification-activated.event.ts +++ b/api/apps/api/src/modules/scenario-specification/domain/events/specification-activated.event.ts @@ -2,5 +2,8 @@ import { IEvent } from '@nestjs/cqrs'; import { SpecificationId } from '../specification.id'; export class SpecificationActivated implements IEvent { - constructor(public readonly specificationId: SpecificationId) {} + constructor( + public readonly scenarioId: string, + public readonly specificationId: SpecificationId, + ) {} } diff --git a/api/apps/api/src/modules/scenario-specification/domain/scenario-specification.ts b/api/apps/api/src/modules/scenario-specification/domain/scenario-specification.ts index 4f56377978..1d024a048e 100644 --- a/api/apps/api/src/modules/scenario-specification/domain/scenario-specification.ts +++ b/api/apps/api/src/modules/scenario-specification/domain/scenario-specification.ts @@ -46,7 +46,7 @@ export class ScenarioSpecification extends AggregateRoot { this.#active = this.#candidate; this.#candidate = undefined; - this.apply(new SpecificationActivated(this.#active)); + this.apply(new SpecificationActivated(this.scenarioId, this.#active)); return right(void 0); } diff --git a/api/apps/api/src/modules/scenario-specification/index.ts b/api/apps/api/src/modules/scenario-specification/index.ts index b818228be0..0eeca7bacc 100644 --- a/api/apps/api/src/modules/scenario-specification/index.ts +++ b/api/apps/api/src/modules/scenario-specification/index.ts @@ -1 +1,3 @@ export { ScenarioSpecificationModule } from './scenario-specification.module'; +export { SpecificationActivated } from './domain'; +export { SpecificationProcessingFinished } from './adapters/specification-processing.finished'; diff --git a/api/apps/api/src/modules/scenarios-features/copy/copy-operation.service.ts b/api/apps/api/src/modules/scenarios-features/copy/copy-operation.service.ts index f07a28535a..4858650138 100644 --- a/api/apps/api/src/modules/scenarios-features/copy/copy-operation.service.ts +++ b/api/apps/api/src/modules/scenarios-features/copy/copy-operation.service.ts @@ -20,6 +20,7 @@ export class CopyOperation { async copy(data: { scenarioId: string; + specificationId: string; input: FeatureConfigCopy; }): Promise<{ id: string }[]> { await this.events.create({ diff --git a/api/apps/api/src/modules/scenarios-features/copy/copy-query.service.ts b/api/apps/api/src/modules/scenarios-features/copy/copy-query.service.ts index c5693cfe09..b2e364a198 100644 --- a/api/apps/api/src/modules/scenarios-features/copy/copy-query.service.ts +++ b/api/apps/api/src/modules/scenarios-features/copy/copy-query.service.ts @@ -6,7 +6,11 @@ import { FeatureConfigCopy } from '@marxan-api/modules/specification'; @Injectable() export class CopyQuery { public prepareStatement( - command: { scenarioId: string; input: FeatureConfigCopy }, + command: { + scenarioId: string; + specificationId: string; + input: FeatureConfigCopy; + }, planningAreaLocation: { id: string; tableName: string } | undefined, protectedAreaFilterByIds: string[], project: Pick, @@ -14,6 +18,7 @@ export class CopyQuery { const parameters: (string | number)[] = []; const fields = { scenarioId: `$${parameters.push(command.scenarioId)}`, + specificationId: `$${parameters.push(command.specificationId)}`, fpf: isDefined(command.input.fpf) ? `$${parameters.push(command.input.fpf)}` : `NULL`, @@ -63,15 +68,17 @@ export class CopyQuery { ? `left join ${planningAreaLocation.tableName} as pa on pa.id = ${fields.planningAreaId}` : ``; const query = ` - insert into scenario_features_data as sfd (feature_class_id, - scenario_id, - fpf, - target, - prop, - total_area, - current_pa) + insert into scenario_features_preparation as sfp (feature_class_id, + scenario_id, + specification_id, + fpf, + target, + prop, + total_area, + current_pa) select fd.id, ${fields.scenarioId}, + ${fields.specificationId}, ${fields.fpf}, ${fields.target}, ${fields.prop}, @@ -88,7 +95,7 @@ export class CopyQuery { ${fields.bbox[3]}, 4326 ), fd.the_geom) - returning sfd.id as id; + returning sfp.id as id; `; return { parameters, query }; } diff --git a/api/apps/api/src/modules/scenarios-features/create-features.handler.ts b/api/apps/api/src/modules/scenarios-features/create-features.handler.ts index 5a80b26836..1423fa2e9b 100644 --- a/api/apps/api/src/modules/scenarios-features/create-features.handler.ts +++ b/api/apps/api/src/modules/scenarios-features/create-features.handler.ts @@ -31,6 +31,7 @@ export class CreateFeaturesHandler case SpecificationOperation.Copy: { const ids = await this.copyOperation.copy({ scenarioId: command.scenarioId, + specificationId: command.specificationId, input: command.input, }); this.eventBus.publish( @@ -43,7 +44,8 @@ export class CreateFeaturesHandler } case SpecificationOperation.Split: { const ids = await this.splitOperation.split({ - ...command, + scenarioId: command.scenarioId, + specificationId: command.specificationId, input: command.input, }); this.eventBus.publish( diff --git a/api/apps/api/src/modules/scenarios-features/index.ts b/api/apps/api/src/modules/scenarios-features/index.ts index 07d46b4635..de5594c932 100644 --- a/api/apps/api/src/modules/scenarios-features/index.ts +++ b/api/apps/api/src/modules/scenarios-features/index.ts @@ -2,3 +2,4 @@ export { ScenarioFeaturesModule } from './scenario-features.module'; export { ScenarioFeaturesService } from './scenario-features.service'; export { ScenariosFeaturesView } from './scenario-features.dto'; export { FeaturesCreated } from './features-created.event'; +export { SpecificationProcessingFinished } from '@marxan-api/modules/scenario-specification/adapters/specification-processing.finished'; diff --git a/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.command.ts b/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.command.ts new file mode 100644 index 0000000000..90f488ecc3 --- /dev/null +++ b/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.command.ts @@ -0,0 +1,10 @@ +import { Command } from '@nestjs-architects/typed-cqrs'; + +export class MoveDataFromPreparationCommand extends Command { + constructor( + public readonly scenarioId: string, + public readonly specificationId: string, + ) { + super(); + } +} diff --git a/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.handler.ts b/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.handler.ts new file mode 100644 index 0000000000..dd6ddb120b --- /dev/null +++ b/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.handler.ts @@ -0,0 +1,77 @@ +import { + CommandHandler, + EventBus, + IInferredCommandHandler, +} from '@nestjs/cqrs'; +import { EntityManager } from 'typeorm'; +import { InjectEntityManager } from '@nestjs/typeorm'; +import { + ScenarioFeaturesPreparation, + ScenarioFeaturesData, +} from '@marxan/features'; +import { DbConnections } from '@marxan-api/ormconfig.connections'; +import { SpecificationProcessingFinished } from '@marxan-api/modules/scenario-specification'; +import { MoveDataFromPreparationCommand } from './move-data-from-preparation.command'; + +@CommandHandler(MoveDataFromPreparationCommand) +export class MoveDataFromPreparationHandler + implements IInferredCommandHandler { + constructor( + @InjectEntityManager(DbConnections.geoprocessingDB) + private readonly entityManager: EntityManager, + private readonly eventBus: EventBus, + ) {} + + async execute(command: MoveDataFromPreparationCommand): Promise { + await this.entityManager.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.delete(ScenarioFeaturesData, { + scenarioId: command.scenarioId, + }); + await transactionalEntityManager.query( + ` + insert into scenario_features_data as sfd (id, + feature_class_id, + scenario_id, + total_area, + current_pa, + fpf, + target, + prop, + target2, + targetocc, + sepnum, + created_by, + metadata, + specification_id) + select id, + feature_class_id, + scenario_id, + total_area, + current_pa, + fpf, + target, + prop, + target2, + targetocc, + sepnum, + created_by, + metadata, + specification_id + from scenario_features_preparation sfp + where sfp.specification_id = $1 + `, + [command.specificationId], + ); + await transactionalEntityManager.delete(ScenarioFeaturesPreparation, { + specificationId: command.specificationId, + }); + }); + + this.eventBus.publish( + new SpecificationProcessingFinished( + command.scenarioId, + command.specificationId, + ), + ); + } +} diff --git a/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.saga.ts b/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.saga.ts new file mode 100644 index 0000000000..f2a174f13d --- /dev/null +++ b/api/apps/api/src/modules/scenarios-features/move-data-from-preparation.saga.ts @@ -0,0 +1,23 @@ +import { ICommand, ofType, Saga } from '@nestjs/cqrs'; +import { Injectable } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { SpecificationActivated } from '@marxan-api/modules/scenario-specification'; +import { MoveDataFromPreparationCommand } from './move-data-from-preparation.command'; + +@Injectable() +export class MoveDataFromPreparationSaga { + @Saga() + specificationActivated = (events$: Observable): Observable => { + return events$.pipe( + ofType(SpecificationActivated), + map( + (event) => + new MoveDataFromPreparationCommand( + event.scenarioId, + event.specificationId.value, + ), + ), + ); + }; +} diff --git a/api/apps/api/src/modules/scenarios-features/scenario-features.module.ts b/api/apps/api/src/modules/scenarios-features/scenario-features.module.ts index 6e5d5ba78e..b00ba5c67b 100644 --- a/api/apps/api/src/modules/scenarios-features/scenario-features.module.ts +++ b/api/apps/api/src/modules/scenarios-features/scenario-features.module.ts @@ -14,6 +14,8 @@ import { CreateFeaturesSaga } from './create-features.saga'; import { CreateFeaturesHandler } from './create-features.handler'; import { CopyDataProvider, CopyOperation, CopyQuery } from './copy'; import { SplitDataProvider, SplitOperation, SplitQuery } from './split'; +import { MoveDataFromPreparationSaga } from './move-data-from-preparation.saga'; +import { MoveDataFromPreparationHandler } from './move-data-from-preparation.handler'; @Module({ imports: [ @@ -36,6 +38,8 @@ import { SplitDataProvider, SplitOperation, SplitQuery } from './split'; SplitQuery, SplitDataProvider, SplitOperation, + MoveDataFromPreparationSaga, + MoveDataFromPreparationHandler, ], exports: [ScenarioFeaturesService], }) diff --git a/api/apps/api/src/modules/scenarios-features/split/split-operation.service.ts b/api/apps/api/src/modules/scenarios-features/split/split-operation.service.ts index 0de07e0aa6..b91b6d74c9 100644 --- a/api/apps/api/src/modules/scenarios-features/split/split-operation.service.ts +++ b/api/apps/api/src/modules/scenarios-features/split/split-operation.service.ts @@ -18,7 +18,11 @@ export class SplitOperation { private readonly events: ApiEventsService, ) {} - async split(data: { scenarioId: string; input: FeatureConfigSplit }) { + async split(data: { + scenarioId: string; + specificationId: string; + input: FeatureConfigSplit; + }) { await this.events.create({ topic: data.scenarioId, kind: API_EVENT_KINDS.scenario__geofeatureSplit__submitted__v1__alpha1, @@ -36,6 +40,7 @@ export class SplitOperation { const { parameters, query } = this.splitQuery.prepareQuery( data.input, data.scenarioId, + data.specificationId, planningAreaLocation, protectedAreaFilterByIds, project, diff --git a/api/apps/api/src/modules/scenarios-features/split/split-query.service.ts b/api/apps/api/src/modules/scenarios-features/split/split-query.service.ts index 475e4e860b..7f1ceb38d4 100644 --- a/api/apps/api/src/modules/scenarios-features/split/split-query.service.ts +++ b/api/apps/api/src/modules/scenarios-features/split/split-query.service.ts @@ -8,6 +8,7 @@ export class SplitQuery { prepareQuery( input: FeatureConfigSplit, scenarioId: string, + specificationId: string, planningAreaLocation: { id: string; tableName: string } | undefined, protectedAreaFilterByIds: string[], project: Pick, @@ -19,6 +20,7 @@ export class SplitQuery { ), splitByProperty: `$${parameters.push(input.splitByProperty)}`, scenarioId: `$${parameters.push(scenarioId)}`, + specificationId: `$${parameters.push(specificationId)}`, planningAreaId: isDefined(planningAreaLocation) ? `$${parameters.push(planningAreaLocation.id)}` : `NULL`, @@ -53,13 +55,14 @@ export class SplitQuery { const hasSubSetFilter = (input.selectSubSets ?? []).length > 0; const query = ` - insert into scenario_features_data as sfd (feature_class_id, - scenario_id, - fpf, - target, - prop, - total_area, - current_pa) + insert into scenario_features_preparation as sfp (feature_class_id, + scenario_id, + specification_id, + fpf, + target, + prop, + total_area, + current_pa) WITH split as ( WITH subsets as ( select value as sub_value, target, fpf, prop @@ -86,6 +89,7 @@ export class SplitQuery { ) select fd.id, ${fields.scenarioId}, + ${fields.specificationId}, split.fpf, split.target, split.prop, @@ -103,7 +107,7 @@ export class SplitQuery { ${fields.bbox[3]}, 4326 ), fd.the_geom) - returning sfd.id as id; + returning sfp.id as id; `; return { parameters, query }; } diff --git a/api/apps/geoprocessing/src/migrations/geoprocessing/1629831194337-AddSpecificationIdToScenarioFeaturesData.ts b/api/apps/geoprocessing/src/migrations/geoprocessing/1629831194337-AddSpecificationIdToScenarioFeaturesData.ts new file mode 100644 index 0000000000..e6124eb53f --- /dev/null +++ b/api/apps/geoprocessing/src/migrations/geoprocessing/1629831194337-AddSpecificationIdToScenarioFeaturesData.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSpecificationIdToScenarioFeaturesData1629831194337 + implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE scenario_features_data ADD COLUMN specification_id uuid`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE scenario_features_data DROP COLUMN specification_id; + `); + } +} diff --git a/api/apps/geoprocessing/src/migrations/geoprocessing/1629896532969-AddPerparationTableForScenarioFeaturesData.ts b/api/apps/geoprocessing/src/migrations/geoprocessing/1629896532969-AddPerparationTableForScenarioFeaturesData.ts new file mode 100644 index 0000000000..8e7088bcdf --- /dev/null +++ b/api/apps/geoprocessing/src/migrations/geoprocessing/1629896532969-AddPerparationTableForScenarioFeaturesData.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPerparationTableForScenarioFeaturesData1629896532969 + implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table scenario_features_preparation (like scenario_features_data including all); + alter table scenario_features_preparation drop column feature_id; + drop index scenario_features_preparation_feature_class_id_idx, + scenario_features_preparation_scenario_id_idx; + create index scenario_features_preparation__specification_id__idx on scenario_features_preparation(specification_id); + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table scenario_features_preparation; + `); + } +} diff --git a/api/libs/features/src/index.ts b/api/libs/features/src/index.ts index 442487d6eb..e2cd7161a7 100644 --- a/api/libs/features/src/index.ts +++ b/api/libs/features/src/index.ts @@ -1,2 +1,3 @@ export { ScenarioFeaturesData } from './scenario-features-data.geo.entity'; +export { ScenarioFeaturesPreparation } from './scenario-features-preparation.geo.entity'; export { FeatureTag } from './domain/feature-tag'; diff --git a/api/libs/features/src/scenario-features-data.geo.entity.ts b/api/libs/features/src/scenario-features-data.geo.entity.ts index a95488a129..df483e9e63 100644 --- a/api/libs/features/src/scenario-features-data.geo.entity.ts +++ b/api/libs/features/src/scenario-features-data.geo.entity.ts @@ -1,136 +1,12 @@ -import { FeatureTag } from '@marxan/features/domain'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity } from 'typeorm'; +import { ScenarioFeaturesPreparation } from './scenario-features-preparation.geo.entity'; @Entity(`scenario_features_data`) -export class ScenarioFeaturesData { - @PrimaryGeneratedColumn('uuid') - id!: string; - +export class ScenarioFeaturesData extends ScenarioFeaturesPreparation { @ApiProperty() @Column({ name: `feature_id`, }) featureId!: number; - - @Column({ name: 'feature_class_id' }) - featuresDataId!: string; - - @Column({ name: 'scenario_id' }) - scenarioId!: string; - - @Column({ name: 'total_area' }) - /** - * total area of the feature in the study area - */ - totalArea!: number; - - @Column({ name: 'current_pa' }) - /** - * total area of the feature in the study area - */ - currentArea!: number; - - @ApiProperty({ - description: `Feature Penalty Factor for this feature run.`, - }) - @Column() - /** - * penalty factor - */ - fpf?: number; - - @ApiProperty({ - description: `Total area space, expressed in m^2`, - }) - @Column() - /** - * target to be met for protection - */ - target?: number; - - @ApiProperty({ - description: - 'Protection target for this feature, as proportion of the conservation feature to be included in the reserve system.', - }) - @Column({ - name: `prop`, - type: `float8`, - }) - prop?: number; - - @Column({ - name: `sepnum`, - type: `float8`, - }) - sepNum?: number; - - @Column({ - name: `targetocc`, - type: `float8`, - }) - targetocc?: number; - - @Column() - /** - * not used yet - * in marxan realm you can set a secondary target for a minimum clump size for the representation of conservation features in the reserve - */ - target2?: number; - - @Column({ - name: 'metadata', - type: 'jsonb', - }) - metadata?: Record<'sepdistance', number | string>; - - /** - * no FK - */ - // @OneToOne(() => RemoteFeaturesData, (featureData) => featureData.id) - // @JoinColumn() - // featureData!: RemoteFeaturesData; - - // what we map - - @ApiProperty({ - description: `0-100 (%) value of target protection coverage of all available species.`, - }) - coverageTarget!: number; - - @ApiProperty({ - description: `Equivalent of \`target\` percentage in covered area, expressed in m^2`, - }) - coverageTargetArea!: number; - - @ApiProperty({ - description: `0-100 (%) value of how many species % is protected currently.`, - }) - met!: number; - - @ApiProperty({ - description: `Equivalent of \`met\` percentage in covered area, expressed in m^2`, - }) - metArea!: number; - - @ApiProperty({ - description: `Shorthand value if current \`met\` is good enough compared to \`target\`.`, - }) - onTarget!: boolean; - - // what we expose with Extend - @ApiProperty({ - enum: FeatureTag, - }) - tag!: FeatureTag; - - @ApiPropertyOptional({ - description: `Name of the feature, for example \`Lion in Deserts\`.`, - }) - name?: string | null; - - @ApiPropertyOptional({ - description: `Description of the feature.`, - }) - description?: string | null; } diff --git a/api/libs/features/src/scenario-features-preparation.geo.entity.ts b/api/libs/features/src/scenario-features-preparation.geo.entity.ts new file mode 100644 index 0000000000..01de2cd648 --- /dev/null +++ b/api/libs/features/src/scenario-features-preparation.geo.entity.ts @@ -0,0 +1,133 @@ +import { FeatureTag } from '@marxan/features/domain'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity(`scenario_features_preparation`) +export class ScenarioFeaturesPreparation { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @Column({ name: 'feature_class_id' }) + featuresDataId!: string; + + @Column({ name: 'scenario_id' }) + scenarioId!: string; + + @Column({ name: 'specification_id', type: 'uuid' }) + specificationId!: string; + + @Column({ name: 'total_area' }) + /** + * total area of the feature in the study area + */ + totalArea!: number; + + @Column({ name: 'current_pa' }) + /** + * total area of the feature in the study area + */ + currentArea!: number; + + @ApiProperty({ + description: `Feature Penalty Factor for this feature run.`, + }) + @Column() + /** + * penalty factor + */ + fpf?: number; + + @ApiProperty({ + description: `Total area space, expressed in m^2`, + }) + @Column() + /** + * target to be met for protection + */ + target?: number; + + @ApiProperty({ + description: + 'Protection target for this feature, as proportion of the conservation feature to be included in the reserve system.', + }) + @Column({ + name: `prop`, + type: `float8`, + }) + prop?: number; + + @Column({ + name: `sepnum`, + type: `float8`, + }) + sepNum?: number; + + @Column({ + name: `targetocc`, + type: `float8`, + }) + targetocc?: number; + + @Column() + /** + * not used yet + * in marxan realm you can set a secondary target for a minimum clump size for the representation of conservation features in the reserve + */ + target2?: number; + + @Column({ + name: 'metadata', + type: 'jsonb', + }) + metadata?: Record<'sepdistance', number | string>; + + /** + * no FK + */ + // @OneToOne(() => RemoteFeaturesData, (featureData) => featureData.id) + // @JoinColumn() + // featureData!: RemoteFeaturesData; + + // what we map + + @ApiProperty({ + description: `0-100 (%) value of target protection coverage of all available species.`, + }) + coverageTarget!: number; + + @ApiProperty({ + description: `Equivalent of \`target\` percentage in covered area, expressed in m^2`, + }) + coverageTargetArea!: number; + + @ApiProperty({ + description: `0-100 (%) value of how many species % is protected currently.`, + }) + met!: number; + + @ApiProperty({ + description: `Equivalent of \`met\` percentage in covered area, expressed in m^2`, + }) + metArea!: number; + + @ApiProperty({ + description: `Shorthand value if current \`met\` is good enough compared to \`target\`.`, + }) + onTarget!: boolean; + + // what we expose with Extend + @ApiProperty({ + enum: FeatureTag, + }) + tag!: FeatureTag; + + @ApiPropertyOptional({ + description: `Name of the feature, for example \`Lion in Deserts\`.`, + }) + name?: string | null; + + @ApiPropertyOptional({ + description: `Description of the feature.`, + }) + description?: string | null; +}