diff --git a/src/main/kotlin/com/terraformation/backend/tracking/db/PlantingSiteStore.kt b/src/main/kotlin/com/terraformation/backend/tracking/db/PlantingSiteStore.kt index 421a5c849c2c..c93283f67a23 100644 --- a/src/main/kotlin/com/terraformation/backend/tracking/db/PlantingSiteStore.kt +++ b/src/main/kotlin/com/terraformation/backend/tracking/db/PlantingSiteStore.kt @@ -76,7 +76,6 @@ import com.terraformation.backend.tracking.model.PlantingZoneModel import com.terraformation.backend.tracking.model.ReplacementResult import com.terraformation.backend.tracking.model.UpdatedPlantingSeasonModel import com.terraformation.backend.util.calculateAreaHectares -import com.terraformation.backend.util.createRectangle import com.terraformation.backend.util.equalsOrBothNull import com.terraformation.backend.util.toInstant import jakarta.inject.Named @@ -951,7 +950,6 @@ class PlantingSiteStore( private fun setMonitoringPlotCluster( monitoringPlotId: MonitoringPlotId, permanentCluster: Int, - permanentClusterSubplot: Int ) { with(MONITORING_PLOTS) { dslContext @@ -959,7 +957,7 @@ class PlantingSiteStore( .set(MODIFIED_BY, currentUser().userId) .set(MODIFIED_TIME, clock.instant()) .set(PERMANENT_CLUSTER, permanentCluster) - .set(PERMANENT_CLUSTER_SUBPLOT, permanentClusterSubplot) + .set(PERMANENT_CLUSTER_SUBPLOT, 1) .where(ID.eq(monitoringPlotId)) .execute() } @@ -1374,8 +1372,6 @@ class PlantingSiteStore( throw IllegalStateException("Planting site ${plantingSite.id} has no grid origin") } - val geometryFactory = plantingSite.gridOrigin.factory - // List of [boundary, cluster number] val clusterBoundaries: List> = plantingZone @@ -1385,65 +1381,46 @@ class PlantingSiteStore( exclusion = plantingSite.exclusion, gridOrigin = plantingSite.gridOrigin, searchBoundary = searchBoundary, - sizeMeters = MONITORING_PLOT_SIZE * 2, + sizeMeters = MONITORING_PLOT_SIZE, ) .zip(clusterNumbers) - return clusterBoundaries.flatMap { (clusterBoundary, clusterNumber) -> - val westX = clusterBoundary.coordinates[0].x - val eastX = clusterBoundary.coordinates[2].x - val southY = clusterBoundary.coordinates[0].y - val northY = clusterBoundary.coordinates[2].y - val middleX = clusterBoundary.centroid.x - val middleY = clusterBoundary.centroid.y - val clusterPlots = - listOf( - // The order is important here: southwest, southeast, northeast, northwest - // (the position in this list turns into the cluster subplot number). - geometryFactory.createRectangle(westX, southY, middleX, middleY), - geometryFactory.createRectangle(middleX, southY, eastX, middleY), - geometryFactory.createRectangle(middleX, middleY, eastX, northY), - geometryFactory.createRectangle(westX, middleY, middleX, northY), - ) + return clusterBoundaries.map { (plotBoundary, clusterNumber) -> + val existingPlot = plantingZone.findMonitoringPlot(plotBoundary) - clusterPlots.mapIndexed { plotIndex, plotBoundary -> - val existingPlot = plantingZone.findMonitoringPlot(plotBoundary.centroid) + if (existingPlot != null) { + if (existingPlot.permanentCluster != null) { + throw IllegalStateException("Cannot place new permanent cluster over existing one") + } - if (existingPlot != null) { - if (existingPlot.permanentCluster != null) { - throw IllegalStateException("Cannot place new permanent cluster over existing one") - } + setMonitoringPlotCluster(existingPlot.id, clusterNumber) - setMonitoringPlotCluster(existingPlot.id, clusterNumber, plotIndex + 1) + existingPlot.id + } else { + val subzone = + plantingZone.findPlantingSubzone(plotBoundary) + ?: throw IllegalStateException( + "Planting zone ${plantingZone.id} not fully covered by subzones") + val plotNumber = nextPlotNumber++ - existingPlot.id - } else { - val subzone = - plantingZone.findPlantingSubzone(plotBoundary) - ?: throw IllegalStateException( - "Planting zone ${plantingZone.id} not fully covered by subzones", - ) - val plotNumber = nextPlotNumber++ - - val monitoringPlotsRow = - MonitoringPlotsRow( - boundary = plotBoundary, - createdBy = userId, - createdTime = now, - fullName = "${subzone.fullName}-$plotNumber", - modifiedBy = userId, - modifiedTime = now, - name = "$plotNumber", - permanentCluster = clusterNumber, - permanentClusterSubplot = plotIndex + 1, - plantingSubzoneId = subzone.id, - sizeMeters = MONITORING_PLOT_SIZE_INT, - ) + val monitoringPlotsRow = + MonitoringPlotsRow( + boundary = plotBoundary, + createdBy = userId, + createdTime = now, + fullName = "${subzone.fullName}-$plotNumber", + modifiedBy = userId, + modifiedTime = now, + name = "$plotNumber", + permanentCluster = clusterNumber, + permanentClusterSubplot = 1, + plantingSubzoneId = subzone.id, + sizeMeters = MONITORING_PLOT_SIZE_INT, + ) - monitoringPlotsDao.insert(monitoringPlotsRow) + monitoringPlotsDao.insert(monitoringPlotsRow) - monitoringPlotsRow.id!! - } + monitoringPlotsRow.id!! } } } diff --git a/src/main/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModel.kt b/src/main/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModel.kt index d3a9a7a677bb..d1dda40ec819 100644 --- a/src/main/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModel.kt +++ b/src/main/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModel.kt @@ -113,7 +113,10 @@ data class PlantingZoneModel exclusion: MultiPolygon? = null, requestedSubzoneIds: Set = emptySet(), ): Collection { - val permanentPlotIds = choosePermanentPlots(plantedSubzoneIds, requestedSubzoneIds) + if (plantingSubzones.isEmpty()) { + throw IllegalArgumentException("No subzones found for planting zone $id (wrong fetch depth?)") + } + val eligibleSubzoneIds = getEligibleSubzoneIds(plantedSubzoneIds, requestedSubzoneIds) // We will assign as many plots as possible evenly across all subzones, eligible or not. @@ -129,7 +132,9 @@ data class PlantingZoneModel return plantingSubzones .sortedWith( compareBy { subzone: PlantingSubzoneModel -> - subzone.monitoringPlots.count { it.id in permanentPlotIds } + subzone.monitoringPlots.count { plot -> + plot.permanentCluster != null && plot.permanentCluster <= numPermanentClusters + } } .thenBy { if (it.id != null && it.id in eligibleSubzoneIds) 0 else 1 } .thenBy { it.id?.value ?: 0L }) diff --git a/src/test/kotlin/com/terraformation/backend/tracking/ObservationServiceTest.kt b/src/test/kotlin/com/terraformation/backend/tracking/ObservationServiceTest.kt index 18fe59bd3eab..8ae9a959035b 100644 --- a/src/test/kotlin/com/terraformation/backend/tracking/ObservationServiceTest.kt +++ b/src/test/kotlin/com/terraformation/backend/tracking/ObservationServiceTest.kt @@ -182,32 +182,24 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { // Zone 1 (2 permanent, 3 temporary) // Subzone 1 (has plants) // Plot A - permanent cluster 1 - // Plot B - permanent cluster 1 - // Plot C - permanent cluster 1 - // Plot D - permanent cluster 1 - // Plot E - permanent cluster 3 - // Plot F - permanent cluster 3 - // Plot G - permanent cluster 3 - // Plot H - permanent cluster 3 - // Plot I - // Plot J + // Plot B - permanent cluster 3 + // Plot C + // Plot D // Subzone 2 (no plants) - // Plot K - permanent cluster 2 - // Plot L - permanent cluster 2 - // Plot M - permanent cluster 2 - // Plot N - permanent cluster 2 - // Plot O - // Plot P + // Plot E - permanent cluster 2 + // Plot F + // Plot G // Zone 2 (2 permanent, 2 temporary) // Subzone 3 (no plants) - // Plot Q - permanent 1 + // Plot H - permanent 1 // // We should get: - // - One permanent cluster with four plots that all lie in subzone 1. - // - One temporary plot in subzone 1. The zone is configured for 3 temporary plots. 2 of them + // - One permanent plot in subzone 1. + // - Two temporary plots in subzone 1. The zone is configured for 3 temporary plots. 2 of them // are spread evenly across the 2 subzones, and the remaining one is placed in the subzone - // with the fewest permanent plots, which is subzone 2, but subzone 2's plots are excluded - // because it has no plants. + // with the fewest permanent plots that could potentially be included in the observation. + // Since permanent clusters 1 and 2 are spread evenly across subzones, preference is given + // to planted subzones, meaning subzone 1 is picked. // - Nothing from zone 2 because it has no plants. insertFacility(type = FacilityType.Nursery) @@ -216,25 +208,25 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { insertDelivery() insertPlantingZone( - x = 0, width = 8, height = 2, numPermanentClusters = 2, numTemporaryPlots = 3) + x = 0, width = 4, height = 1, numPermanentClusters = 2, numTemporaryPlots = 3) val subzone1Boundary = - rectangle(width = 5 * MONITORING_PLOT_SIZE, height = 2 * MONITORING_PLOT_SIZE) + rectangle(width = 4 * MONITORING_PLOT_SIZE, height = MONITORING_PLOT_SIZE) insertPlantingSubzone(boundary = subzone1Boundary) insertPlanting() insertCluster(1) - insertCluster(3, x = 2, y = 0) - insertMonitoringPlot(x = 4, y = 0) - insertMonitoringPlot(x = 4, y = 1) + insertCluster(3, x = 1, y = 0) + insertMonitoringPlot(x = 2, y = 0) + insertMonitoringPlot(x = 3, y = 0) - insertPlantingSubzone(x = 5, width = 3, height = 2) - insertCluster(2, x = 5, y = 0) - insertMonitoringPlot(x = 7, y = 0) - insertMonitoringPlot(x = 7, y = 1) + insertPlantingSubzone(x = 4, width = 3, height = 1) + insertCluster(2, x = 4, y = 0) + insertMonitoringPlot(x = 5, y = 0) + insertMonitoringPlot(x = 6, y = 0) insertPlantingZone( - x = 8, width = 3, height = 2, numPermanentClusters = 2, numTemporaryPlots = 2) - insertPlantingSubzone(x = 8, width = 3, height = 2) - insertCluster(1, x = 8, y = 0) + x = 7, width = 3, height = 1, numPermanentClusters = 2, numTemporaryPlots = 2) + insertPlantingSubzone(x = 7, width = 3, height = 1) + insertCluster(1, x = 7, y = 0) val observationId = insertObservation(state = ObservationState.Upcoming) @@ -243,8 +235,8 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { val observationPlots = observationPlotsDao.findAll() val monitoringPlots = monitoringPlotsDao.findAll().associateBy { it.id } - assertEquals(4, observationPlots.count { it.isPermanent!! }, "Number of permanent plots") - assertEquals(1, observationPlots.count { !it.isPermanent!! }, "Number of temporary plots") + assertEquals(1, observationPlots.count { it.isPermanent!! }, "Number of permanent plots") + assertEquals(2, observationPlots.count { !it.isPermanent!! }, "Number of temporary plots") observationPlots.forEach { observationPlot -> val plotBoundary = monitoringPlots[observationPlot.monitoringPlotId]!!.boundary!! @@ -275,40 +267,41 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { // | | | | // +-------------------------|-------------+ // - // Zone 1 (1 permanent, 3 temporary) + // Zone 1 (2 permanent, 3 temporary) // Subzone 1 (has plants) // Subzone 2 (no plants) // Zone 2 (2 permanent, 2 temporary) // Subzone 3 (no plants) // - // In zone 1, the service should create one permanent cluster with four plots. The plots might - // lie completely in subzone 1, completely in subzone 2, or half in each subzone, depending on - // the random number generator, and the cluster may or may not be included in the observation. + // In zone 1, the service should create two permanent clusters of one plot each. The plots + // might both end up in subzone 1 or subzone 2, or there might be one plot in each subzone, + // depending on the random number generator. // - // The placement of the cluster will also determine how many temporary plots are created and - // included in the observation. + // The placement of the permanent plots will also determine how many temporary plots are + // created and included in the observation. // - // If the cluster is all in subzone 1: - // - It should be included in the observation. + // If both permanent plots are in subzone 1: + // - They should both be included in the observation. // - We should get one temporary plot in subzone 1. The zone is configured for 3 temporary // plots. 2 of them are spread evenly across the 2 subzones, and the remaining one is placed // in the subzone with the fewest permanent plots, which is subzone 2, but subzone 2's plots // are excluded because it has no plants. - // If the cluster is partially in subzone 1 and partially in subzone 2: - // - It should not be included in the observation because we only include permanent clusters - // whose plots all lie in planted subzones. + // If one permanent plot is in subzone 1 and the other in subzone 2: + // - The plot in subzone 1 should be included in the observation, but not the one in subzone + // 2, because we only include permanent clusters whose plots all lie in planted subzones. // - We should get two temporary plots in subzone 1. Two of zone 1's temporary plots are // spread evenly across the two subzones, and the remaining plot is placed in the subzone // with the fewest permanent plots, but in this case the subzones have the same number. // As a tiebreaker, planted subzones are preferred over unplanted ones, which means we // should choose subzone 1. - // If the cluster is all in subzone 2: + // If both permanent plots are in subzone 2: + // - Neither permanent plot should be included in the observation. // - We should get two temporary plots in subzone 1. Two of zone 1's temporary plots are // spread evenly across the two subzones, and the remaining plot is placed in the subzone // with the fewest temporary plots, which is subzone 1 in this case. // - // In zone 2, the service should create two permanent clusters, but neither of them should - // be included in the observation since they all lie in an unplanted subzone. + // In zone 2, the service should create two permanent plots, but neither of them should be + // included in the observation since they all lie in an unplanted subzone. plantingSiteId = insertPlantingSite(x = 0, width = 14, gridOrigin = point(1)) insertFacility(type = FacilityType.Nursery) @@ -317,7 +310,7 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { insertDelivery() insertPlantingZone( - x = 0, width = 6, height = 2, numPermanentClusters = 1, numTemporaryPlots = 3) + x = 0, width = 6, height = 2, numPermanentClusters = 2, numTemporaryPlots = 3) val subzone1Boundary = rectangle(width = 3 * MONITORING_PLOT_SIZE, height = 2 * MONITORING_PLOT_SIZE) val subzone1Id = insertPlantingSubzone(boundary = subzone1Boundary) @@ -333,15 +326,15 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { // Make sure we actually get all the possible plot configurations. var got0PermanentPlotsInSubzone1 = false + var got1PermanentPlotInSubzone1 = false var got2PermanentPlotsInSubzone1 = false - var got4PermanentPlotsInSubzone1 = false val maxTestRuns = 100 var testRuns = 0 while (testRuns++ < maxTestRuns && !(got0PermanentPlotsInSubzone1 && - got2PermanentPlotsInSubzone1 && - got4PermanentPlotsInSubzone1)) { + got1PermanentPlotInSubzone1 && + got2PermanentPlotsInSubzone1)) { dslContext.savepoint("start").execute() service.startObservation(observationId) @@ -374,22 +367,22 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { val numPermanentPlotsInSubzone2 = numPermanentPlotsBySubzone[subzone2Id] ?: 0 assertEquals( - 12, + 4, monitoringPlots.count { it.permanentCluster != null }, "Total number of permanent plots created") assertEquals( - 4, + 2, numPermanentPlotsInSubzone1 + numPermanentPlotsInSubzone2, "Number of permanent plots created in zone 1") val expectedTemporaryPlots = when (numPermanentPlotsInSubzone1) { - 4 -> 1 - 2 -> 2 + 2 -> 1 + 1 -> 2 0 -> 2 else -> fail( - "Expected 0, 2, or 4 permanent plots in subzone $subzone1Id, but " + + "Expected 0, 1, or 2 permanent plots in subzone $subzone1Id, but " + "got $numPermanentPlotsInSubzone1") } @@ -408,8 +401,8 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { when (numPermanentPlotsInSubzone1) { 0 -> got0PermanentPlotsInSubzone1 = true + 1 -> got1PermanentPlotInSubzone1 = true 2 -> got2PermanentPlotsInSubzone1 = true - 4 -> got4PermanentPlotsInSubzone1 = true } eventPublisher.clear() @@ -1434,7 +1427,7 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { val plots = monitoringPlotsDao.findAll().associateBy { it.id!! } assertEquals( - listOf(null, null, null, null), + listOf(null), cluster1.map { plots[it]!!.permanentCluster }, "Permanent cluster numbers of cluster whose plot was replaced") @@ -1538,7 +1531,7 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { result) assertEquals( - listOf(1, 1, 1, 1), + listOf(1), monitoringPlotsDao.fetchById(*cluster2.toTypedArray()).map { it.permanentCluster }, "Should have moved second permanent cluster to first place") monitoringPlotsDao.fetchById(*cluster1.toTypedArray()).forEach { plot -> @@ -1548,14 +1541,14 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { @Test fun `removes permanent cluster if replacement cluster is in an unplanted subzone`() { - insertPlantingZone(numPermanentClusters = 1, width = 2, height = 4) - insertPlantingSubzone(width = 2, height = 2) + insertPlantingZone(numPermanentClusters = 1, width = 1, height = 2) + insertPlantingSubzone(width = 1, height = 1) insertWithdrawal() insertDelivery() insertPlanting() val cluster1 = insertCluster(1, isPermanent = true) - insertPlantingSubzone(y = 2, width = 2, height = 2) + insertPlantingSubzone(y = 1, width = 1, height = 1) val cluster2 = insertCluster(2) val result = @@ -1568,7 +1561,7 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { result) assertEquals( - listOf(1, 1, 1, 1), + listOf(1), monitoringPlotsDao.fetchById(*cluster2.toTypedArray()).map { it.permanentCluster }, "Should have moved second permanent cluster to first place") monitoringPlotsDao.fetchById(*cluster1.toTypedArray()).forEach { plot -> @@ -1598,17 +1591,17 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { observationId, cluster1[0], "why not", ReplacementDuration.LongTerm) assertEquals(cluster1.toSet(), result.removedMonitoringPlotIds, "Removed plot IDs") - assertEquals(4, result.addedMonitoringPlotIds.size, "Number of plot IDs added") + assertEquals(1, result.addedMonitoringPlotIds.size, "Number of plot IDs added") val plots = monitoringPlotsDao.findAll().associateBy { it.id!! } assertEquals( - listOf(null, null, null, null), + listOf(null), cluster1.map { plots[it]!!.permanentCluster }, "Permanent cluster numbers of cluster whose plot was replaced") assertEquals( - 4, + 1, plots.values.count { it.permanentCluster == 1 }, "Number of plots with permanent cluster number 1") @@ -1622,11 +1615,10 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { @Test fun `removes permanent plot but keeps it available if this is the first observation and there are already completed plots`() { val cluster1 = insertCluster(1) + val cluster2 = insertCluster(2) insertObservationPlot(monitoringPlotId = cluster1[0], isPermanent = true) - insertObservationPlot(monitoringPlotId = cluster1[1], isPermanent = true) - insertObservationPlot(monitoringPlotId = cluster1[2], isPermanent = true) insertObservationPlot( - monitoringPlotId = cluster1[3], + monitoringPlotId = cluster2[0], isPermanent = true, claimedBy = user.userId, claimedTime = Instant.EPOCH, @@ -1646,13 +1638,6 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { true, monitoringPlotsDao.fetchOneById(cluster1[0])!!.isAvailable, "Monitoring plot should remain available") - assertEquals( - setOf(cluster1[1], cluster1[2], cluster1[3]), - observationPlotsDao - .fetchByObservationId(observationId) - .map { it.monitoringPlotId } - .toSet(), - "Other plots in cluster should remain in observation") } @Test @@ -1679,13 +1664,6 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { true, monitoringPlotsDao.fetchOneById(cluster1[0])!!.isAvailable, "Monitoring plot should remain available") - assertEquals( - setOf(cluster1[1], cluster1[2], cluster1[3]), - observationPlotsDao - .fetchByObservationId(newObservationId) - .map { it.monitoringPlotId } - .toSet(), - "Other plots in cluster should remain in observation") } @Test @@ -1905,7 +1883,7 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { val observationPlots = observationPlotsDao.fetchByObservationId(inserted.observationId) assertEquals( - 4, + 1, observationPlots.filter { it.isPermanent!! }.size, "Number of permanent plots in observation") assertEquals( @@ -1922,9 +1900,6 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { val newPermanentPlotIds = setOf( insertMonitoringPlot(x = 0, y = 8, permanentCluster = 1, permanentClusterSubplot = 1), - insertMonitoringPlot(x = 1, y = 8, permanentCluster = 1, permanentClusterSubplot = 2), - insertMonitoringPlot(x = 0, y = 9, permanentCluster = 1, permanentClusterSubplot = 3), - insertMonitoringPlot(x = 1, y = 9, permanentCluster = 1, permanentClusterSubplot = 4), ) val event = @@ -2036,11 +2011,10 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { } /** - * Inserts a cluster of four plots arranged in the correct counterclockwise positions. By default, - * the clusters are stacked northward, that is, cluster 1 is at y=0, cluster 2 is at y=2, and - * cluster 3 is at y=4. + * Inserts a permanent monitoring plot with a given cluster number. By default, the clusters are + * stacked northward, that is, cluster 1 is at y=0, cluster 2 is at y=1, and cluster 3 is at y=2. * - * Optionally also includes the cluster's plots in the most-recently-inserted observation. + * Optionally also includes the cluster's plot in the most-recently-inserted observation. */ private fun insertCluster( permanentCluster: Int, @@ -2049,38 +2023,18 @@ class ObservationServiceTest : DatabaseTest(), RunsAsUser { isPermanent: Boolean = false, insertObservationPlots: Boolean = isPermanent, ): List { - val plotIds = - listOf( - insertMonitoringPlot( - permanentCluster = permanentCluster, - permanentClusterSubplot = 1, - x = x, - y = y, - ), - insertMonitoringPlot( - permanentCluster = permanentCluster, - permanentClusterSubplot = 2, - x = x + 1, - y = y, - ), - insertMonitoringPlot( - permanentCluster = permanentCluster, - permanentClusterSubplot = 3, - x = x + 1, - y = y + 1, - ), - insertMonitoringPlot( - permanentCluster = permanentCluster, - permanentClusterSubplot = 4, - x = x, - y = y + 1, - ), + val plotId = + insertMonitoringPlot( + permanentCluster = permanentCluster, + permanentClusterSubplot = 1, + x = x, + y = y, ) if (insertObservationPlots) { - plotIds.forEach { insertObservationPlot(monitoringPlotId = it, isPermanent = isPermanent) } + insertObservationPlot(isPermanent = isPermanent) } - return plotIds + return listOf(plotId) } } diff --git a/src/test/kotlin/com/terraformation/backend/tracking/PlotAssignmentTest.kt b/src/test/kotlin/com/terraformation/backend/tracking/PlotAssignmentTest.kt index d2c1236f9b84..0ae9ecb16af4 100644 --- a/src/test/kotlin/com/terraformation/backend/tracking/PlotAssignmentTest.kt +++ b/src/test/kotlin/com/terraformation/backend/tracking/PlotAssignmentTest.kt @@ -146,7 +146,7 @@ class PlotAssignmentTest : DatabaseTest(), RunsAsUser { // go in either one of them depending on where the permanent plots ended up. Any temporary // plots that ended up in subzone 2 will be discarded because subzone 2 has no plants. - val numPermanentClusters = observationPlots.count { it.model.isPermanent } / 4 + val numPermanentClusters = observationPlots.count { it.model.isPermanent } val numTemporaryPlots = observationPlots.count { !it.model.isPermanent } when (numPermanentClusters) { diff --git a/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreApplyEditTest.kt b/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreApplyEditTest.kt index dc507a48a375..015657e03e43 100644 --- a/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreApplyEditTest.kt +++ b/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreApplyEditTest.kt @@ -158,8 +158,8 @@ internal class PlantingSiteStoreApplyEditTest : PlantingSiteStoreTest() { }, expectedPlotCounts = listOf( - rectangle(x = 0, width = 500, height = 500) to 7 * 4, - rectangle(x = 500, width = 250, height = 500) to 3 * 4, + rectangle(x = 0, width = 500, height = 500) to 7, + rectangle(x = 500, width = 250, height = 500) to 3, )) } @@ -230,8 +230,8 @@ internal class PlantingSiteStoreApplyEditTest : PlantingSiteStoreTest() { }, expectedPlotCounts = listOf( - initialArea to 8, - addedArea to 8, + initialArea to 2, + addedArea to 2, )) val monitoringPlots = edited.plantingZones[0].plantingSubzones[0].monitoringPlots @@ -250,7 +250,7 @@ internal class PlantingSiteStoreApplyEditTest : PlantingSiteStoreTest() { monitoringPlots.groupBy { it.permanentCluster }.mapValues { (_, plots) -> plots.size } assertEquals( - mapOf(1 to 4, 2 to 4, 3 to 4, 4 to 4), + mapOf(1 to 1, 2 to 1, 3 to 1, 4 to 1), plotCountsByClusterNumber, "Number of plots with each cluster number after edit") } @@ -291,7 +291,7 @@ internal class PlantingSiteStoreApplyEditTest : PlantingSiteStoreTest() { } @Test - fun `destroys permanent cluster if one plot is no longer in plantable area`() { + fun `destroys permanent plot if no longer in plantable area`() { val (edited) = runScenario( newSite { @@ -308,29 +308,13 @@ internal class PlantingSiteStoreApplyEditTest : PlantingSiteStoreTest() { }) assertEquals( - mapOf( - "1" to null, - "2" to null, - "3" to null, - "4" to null, - "5" to 2, - "6" to 2, - "7" to 2, - "8" to 2), + mapOf("1" to null, "2" to 2), edited.plantingZones[0].plantingSubzones[0].monitoringPlots.associate { it.name to it.permanentCluster }, "Cluster numbers of monitoring plots") assertEquals( - mapOf( - "1" to false, - "2" to true, - "3" to true, - "4" to true, - "5" to true, - "6" to true, - "7" to true, - "8" to true), + mapOf("1" to false, "2" to true), edited.plantingZones[0].plantingSubzones[0].monitoringPlots.associate { it.name to it.isAvailable }, diff --git a/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreEnsurePermanentTest.kt b/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreEnsurePermanentTest.kt index ddbf558a59da..6c088156a616 100644 --- a/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreEnsurePermanentTest.kt +++ b/src/test/kotlin/com/terraformation/backend/tracking/db/plantingSiteStore/PlantingSiteStoreEnsurePermanentTest.kt @@ -1,6 +1,7 @@ package com.terraformation.backend.tracking.db.plantingSiteStore import com.terraformation.backend.point +import com.terraformation.backend.tracking.model.MONITORING_PLOT_SIZE import com.terraformation.backend.util.Turtle import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Nested @@ -21,16 +22,19 @@ internal class PlantingSiteStoreEnsurePermanentTest : PlantingSiteStoreTest() { val plots = monitoringPlotsDao.findAll() - assertEquals(16, plots.size, "Number of monitoring plots created") + assertEquals(4, plots.size, "Number of monitoring plots created") assertEquals( setOf(1, 2, 3, 4), plots.map { it.permanentCluster }.toSet(), "Permanent cluster numbers") assertEquals(1, plots.minOf { it.name!!.toInt() }, "Smallest plot number") - assertEquals(16, plots.maxOf { it.name!!.toInt() }, "Largest plot number") + assertEquals(4, plots.maxOf { it.name!!.toInt() }, "Largest plot number") } @Test fun `creates as many clusters as there is room for`() { - val siteBoundary = Turtle(point(0)).makeMultiPolygon { rectangle(101, 51) } + val siteBoundary = + Turtle(point(0)).makeMultiPolygon { + rectangle(MONITORING_PLOT_SIZE * 2 + 1, MONITORING_PLOT_SIZE + 1) + } val plantingSiteId = insertPlantingSite(boundary = siteBoundary, gridOrigin = point(0)) insertPlantingZone(boundary = siteBoundary, numPermanentClusters = 4) @@ -41,11 +45,11 @@ internal class PlantingSiteStoreEnsurePermanentTest : PlantingSiteStoreTest() { val plots = monitoringPlotsDao.findAll() - assertEquals(8, plots.size, "Number of monitoring plots created") + assertEquals(2, plots.size, "Number of monitoring plots created") assertEquals( setOf(1, 2), plots.map { it.permanentCluster }.toSet(), "Permanent cluster numbers") assertEquals(1, plots.minOf { it.name!!.toInt() }, "Smallest plot number") - assertEquals(8, plots.maxOf { it.name!!.toInt() }, "Largest plot number") + assertEquals(2, plots.maxOf { it.name!!.toInt() }, "Largest plot number") } @Test @@ -63,19 +67,18 @@ internal class PlantingSiteStoreEnsurePermanentTest : PlantingSiteStoreTest() { store.ensurePermanentClustersExist(plantingSiteId) val plots = monitoringPlotsDao.findAll() - val plotsPerCluster = plots.groupBy { it.permanentCluster }.mapValues { it.value.size } - assertEquals(9, plots.size, "Number of monitoring plots including existing one") + assertEquals(3, plots.size, "Number of monitoring plots including existing one") assertEquals( - mapOf(1 to 4, 2 to 1, 3 to 4), plotsPerCluster, "Number of plots in each cluster") + setOf(1, 2, 3), plots.map { it.permanentCluster }.toSet(), "Permanent cluster numbers") } @Test - fun `can use temporary plot from previous observation as part of cluster`() { + fun `can use temporary plot from previous observation as permanent plot`() { val gridOrigin = point(0) - val siteBoundary = Turtle(gridOrigin).makeMultiPolygon { square(51) } + val siteBoundary = Turtle(gridOrigin).makeMultiPolygon { rectangle(51, 26) } - // Temporary plot is in the southeast corner of the cluster. + // Temporary plot is in the east half of the site. val existingPlotBoundary = Turtle(gridOrigin).makePolygon { east(25) @@ -83,7 +86,7 @@ internal class PlantingSiteStoreEnsurePermanentTest : PlantingSiteStoreTest() { } val plantingSiteId = insertPlantingSite(boundary = siteBoundary, gridOrigin = gridOrigin) - insertPlantingZone(boundary = siteBoundary, numPermanentClusters = 1) + insertPlantingZone(boundary = siteBoundary, numPermanentClusters = 2) insertPlantingSubzone(boundary = siteBoundary) val existingPlotId = insertMonitoringPlot(boundary = existingPlotBoundary) @@ -92,13 +95,8 @@ internal class PlantingSiteStoreEnsurePermanentTest : PlantingSiteStoreTest() { val plots = monitoringPlotsDao.findAll() val existingPlot = plots.first { it.id == existingPlotId } - // Subplots are numbered counterclockwise starting from the southwest. - val southeastSubplot = 2 - - assertEquals(4, plots.size, "Number of monitoring plots including existing one") - assertEquals(1, existingPlot.permanentCluster, "Permanent cluster of existing plot") - assertEquals( - southeastSubplot, existingPlot.permanentClusterSubplot, "Subplot number of existing plot") + assertEquals(2, plots.size, "Number of monitoring plots including existing one") + assertNotNull(existingPlot.permanentCluster, "Cluster number of existing plot") } @Test @@ -139,8 +137,8 @@ internal class PlantingSiteStoreEnsurePermanentTest : PlantingSiteStoreTest() { val plots = monitoringPlotsDao.findAll() assertEquals( - listOf(123, 124, 125, 126, 127), - plots.map { it.name!!.toInt() }.sorted(), + setOf("123", "124"), + plots.map { it.name }.toSet(), "Plot numbers including existing plot") } } diff --git a/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingSiteBuilder.kt b/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingSiteBuilder.kt index 89947950861e..c22ccccbfbf2 100644 --- a/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingSiteBuilder.kt +++ b/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingSiteBuilder.kt @@ -245,7 +245,7 @@ private constructor( size: Int = MONITORING_PLOT_SIZE_INT, ): MonitoringPlotModel { lastCluster = cluster - nextMonitoringPlotX = x + size.toInt() + nextMonitoringPlotX = x + size if (subplot != null) { nextSubplot = subplot + 1 @@ -272,19 +272,8 @@ private constructor( y: Int = this.y, cluster: Int = nextPermanentCluster++, isAvailable: Boolean = true, - ): List { - val plotSize = MONITORING_PLOT_SIZE_INT - val plots = - listOf( - plot(x, y, cluster, isAvailable = isAvailable), - plot(x + plotSize, y, cluster, isAvailable = isAvailable), - plot(x + plotSize, y + plotSize, cluster, isAvailable = isAvailable), - plot(x, y + plotSize, cluster, isAvailable = isAvailable), - ) - - nextMonitoringPlotX = x + plotSize * 2 - - return plots + ): MonitoringPlotModel { + return plot(x, y, cluster, isAvailable = isAvailable) } } } diff --git a/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModelTest.kt b/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModelTest.kt index 574e6af53e8d..b5324092688c 100644 --- a/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModelTest.kt +++ b/src/test/kotlin/com/terraformation/backend/tracking/model/PlantingZoneModelTest.kt @@ -466,7 +466,7 @@ class PlantingZoneModelTest { @Nested inner class FindUnusedSquare { @RepeatedTest(20) - fun `can place permanent cluster in minimal-size planting zone`() { + fun `can find square in non-rectangular planting zone`() { // Boundary shape: // // +-------+