From 969aece6ad412beec77777b97ba631bda5c1d956 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Thu, 2 May 2024 11:27:35 +0200 Subject: [PATCH 1/7] Filter by fao area in JDP and return first included fao area and segment --- .../mission_actions/actrep/JDPToFaoAreas.kt | 7 + .../mission/mission_actions/actrep/species.kt | 11 +- .../fao_areas/ComputeVesselFAOAreas.kt | 4 +- .../mission_actions/GetActivityReports.kt | 57 ++++- .../mission_actions/dtos/ActivityReport.kt | 2 + .../api/outputs/ActivityReportDataOutput.kt | 4 + .../cache/CaffeineConfiguration.kt | 5 +- .../repositories/JpaFleetSegmentRepository.kt | 2 + .../ComputeFAOAreasFromCoordinatesUTests.kt | 7 +- .../GetActivityReportsUTests.kt | 207 ++++++++++++++++-- .../api/bff/MissionActionsControllerITests.kt | 2 + .../JpaFleetSegmentRepositoryITests.kt | 4 + 12 files changed, 279 insertions(+), 33 deletions(-) create mode 100644 backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt new file mode 100644 index 0000000000..63d7191921 --- /dev/null +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt @@ -0,0 +1,7 @@ +package fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep + +val JDP_TO_FAO_AREAS = mapOf( + Pair(JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC, MED_FAO_CODES + EASTERN_ATLANTIC_FAO_CODES), + Pair(JointDeploymentPlan.NORTH_SEA, NORTH_SEA_FAO_CODES), + Pair(JointDeploymentPlan.WESTERN_WATERS, WESTERN_WATERS_FAO_CODES), +) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt index 13945bb2d6..963405b1c8 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt @@ -18,6 +18,7 @@ typealias FaoZonesAndSpecy = Pair */ val MED_FAO_CODES = listOf("37.1", "37.2", "37.3") +val EASTERN_ATLANTIC_FAO_CODES = listOf("27.7", "27.8", "27.9", "27.10") val MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES: List = generateSpeciesWithFaoCode( MED_FAO_CODES, listOf( @@ -68,11 +69,11 @@ val MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES: List = generat ), ) + // Eastern Atlantic part - listOf(Pair(listOf("27.7", "27.8", "27.9", "27.10"), "BFT")) + listOf(Pair(EASTERN_ATLANTIC_FAO_CODES, "BFT")) -val NS_01_FAO_CODES = listOf("27.4", "27.3.a") +val NORTH_SEA_FAO_CODES = listOf("27.4", "27.3.a") val NORTH_SEA_SPECIES: List = generateSpeciesWithFaoCode( - NS_01_FAO_CODES, + NORTH_SEA_FAO_CODES, listOf( "HOM", "JAX", @@ -115,12 +116,12 @@ val NORTH_SEA_SPECIES: List = generateSpeciesWithFaoCode( ), ) -val WW_01_FAO_CODES = listOf("27.6", "27.7", "27.8", "27.9", "27.10") +val WESTERN_WATERS_FAO_CODES = listOf("27.6", "27.7", "27.8", "27.9", "27.10") val WESTERN_WATERS_SPECIES: List = listOf( Pair(listOf("27.6", "27.7", "27.8", "27.9"), "PIL"), Pair(listOf("27.6", "27.7", "27.8", "27.9"), "ELE"), ) + generateSpeciesWithFaoCode( - WW_01_FAO_CODES, + WESTERN_WATERS_FAO_CODES, listOf( "ANE", "HOM", diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fao_areas/ComputeVesselFAOAreas.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fao_areas/ComputeVesselFAOAreas.kt index 2a38407586..1cd52e00fd 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fao_areas/ComputeVesselFAOAreas.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/fao_areas/ComputeVesselFAOAreas.kt @@ -13,7 +13,7 @@ import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.removeRedundantFa * return computed fao zones from the given coordinates/port. * * Priority : - * 1. Fetch the fao zones from the `risk_factors` table + * 1. Fetch the fao zones from the `risk_factors` table to have the areas of the entire voyage * 2. Otherwise, * - Fetch the fao zones from the latitude/longitude if given * - Fetch the fao zones from the portLocode if given @@ -34,7 +34,7 @@ class ComputeVesselFAOAreas( return listOf() } - // Fetch the fao zones from the `risk_factors` table + // Fetch the fao zones from the `risk_factors` table to have the areas of the entire voyage if (internalReferenceNumber != null) { // Get faoZones from speciesOnboard in risk factors table (updated by the pipeline) val vesselRiskFactor = riskFactorRepository.findByInternalReferenceNumber(internalReferenceNumber) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt index 5d36ec677b..bbf58dd008 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt @@ -1,14 +1,16 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions import fr.gouv.cnsp.monitorfish.config.UseCase +import fr.gouv.cnsp.monitorfish.domain.entities.fao_area.FAOArea +import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionAction import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionActionType import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.ActivityCode +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.JDP_TO_FAO_AREAS import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.JointDeploymentPlan import fr.gouv.cnsp.monitorfish.domain.exceptions.CodeNotFoundException -import fr.gouv.cnsp.monitorfish.domain.repositories.MissionActionsRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.MissionRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.PortRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.* +import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.hasFaoCodeIncludedIn import fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions.dtos.ActivityReport import fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions.dtos.ActivityReports import org.slf4j.LoggerFactory @@ -19,11 +21,13 @@ class GetActivityReports( private val missionActionsRepository: MissionActionsRepository, private val portRepository: PortRepository, private val vesselRepository: VesselRepository, + private val segmentRepository: FleetSegmentRepository, private val missionRepository: MissionRepository, ) { private val logger = LoggerFactory.getLogger(GetActivityReports::class.java) fun execute(beforeDateTime: ZonedDateTime, afterDateTime: ZonedDateTime, jdp: JointDeploymentPlan): ActivityReports { + val jdpFaoAreas = JDP_TO_FAO_AREAS[jdp] val controls = missionActionsRepository.findControlsInDates(beforeDateTime, afterDateTime) logger.info("Found ${controls.size} controls between dates [$afterDateTime, $beforeDateTime].") @@ -52,14 +56,21 @@ class GetActivityReports( MissionActionType.SEA_CONTROL -> { val controlMission = missions.firstOrNull { mission -> mission.id == control.missionId } + val isUnderJdp = controlMission?.isUnderJdp == true if (controlMission == null) { logger.error( "Mission id '${control.missionId}' linked to SEA control id '${control.id}' could not be found. Is this mission deleted ?", ) } + if (control.faoAreas.isNotEmpty()) { + val foundFaoAreaIncludedInJdp = getFirstFaoAreaIncludedInJdp(jdpFaoAreas, control.faoAreas) + + return@filter isUnderJdp && foundFaoAreaIncludedInJdp != null + } + // The mission must be under JDP - return@filter controlMission?.isUnderJdp == true + return@filter isUnderJdp } else -> throw IllegalArgumentException("Bad control type: ${control.actionType}") @@ -104,11 +115,15 @@ class GetActivityReports( logger.warn(e.message) } } + val faoArea = getFirstFaoAreaIncludedInJdp(jdpFaoAreas, control.faoAreas) + val segment = getFirstFleetSegmentIncludedInFaoArea(control, faoArea) ActivityReport( action = control, activityCode = activityCode, controlUnits = controlMission.controlUnits, + faoArea = faoArea?.faoCode, + segment = segment?.segment, vesselNationalIdentifier = controlledVessel.getNationalIdentifier(), vessel = controlledVessel, ) @@ -119,4 +134,36 @@ class GetActivityReports( jdpSpecies = jdp.getSpeciesCodes(), ) } + + private fun getFirstFleetSegmentIncludedInFaoArea( + control: MissionAction, + controlFaoArea: FAOArea?, + ): FleetSegment? { + val segment = segmentRepository.findAllByYear(control.actionDatetimeUtc.year) + .firstOrNull { yearSegment -> + control.segments.any { controlSegment -> + yearSegment.segment === controlSegment.segment && + yearSegment.faoAreas.any { controlFaoArea?.hasFaoCodeIncludedIn(it) ?: false } + } + } + + return segment + } + + private fun getFirstFaoAreaIncludedInJdp( + jdpFaoAreas: List?, + faoAreas: List, + ): FAOArea? { + val foundFaoArea = jdpFaoAreas?.let { jdpFaoAreasNotNull -> + faoAreas + .map { controlFaoArea -> FAOArea(controlFaoArea) } + .firstOrNull { controlFaoArea -> + jdpFaoAreasNotNull.any { jdpFaoArea -> + controlFaoArea.hasFaoCodeIncludedIn(jdpFaoArea) + } + } + } + + return foundFaoArea + } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt index 42aaf98de1..53df863986 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/dtos/ActivityReport.kt @@ -7,6 +7,8 @@ import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel data class ActivityReport( val action: MissionAction, + val faoArea: String?, + val segment: String?, val activityCode: ActivityCode, // The `districtCode` and `internalReferenceNumber` concatenation val vesselNationalIdentifier: String, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt index 9eeb5c3745..64b4842291 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/outputs/ActivityReportDataOutput.kt @@ -7,6 +7,8 @@ import fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions.dtos.Ac data class ActivityReportDataOutput( val action: MissionActionDataOutput, val activityCode: ActivityCode, + val faoArea: String?, + val segment: String?, val vesselNationalIdentifier: String, val controlUnits: List, val vessel: VesselDataOutput, @@ -15,6 +17,8 @@ data class ActivityReportDataOutput( fun fromActivityReport(activityReport: ActivityReport) = ActivityReportDataOutput( action = MissionActionDataOutput.fromMissionAction(activityReport.action), activityCode = activityReport.activityCode, + faoArea = activityReport.faoArea, + segment = activityReport.segment, vesselNationalIdentifier = activityReport.vesselNationalIdentifier, controlUnits = activityReport.controlUnits, vessel = VesselDataOutput.fromVessel(activityReport.vessel), diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt index 2a52fd44aa..f859921b8f 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt @@ -52,7 +52,8 @@ class CaffeineConfiguration { val riskFactors = "risk_factors" // Segments - val currentSegments = "current_segment" + val currentSegments = "current_segments" + val segmentsByYear = "segments_by_year" // Species val allSpecies = "all_species" @@ -119,6 +120,7 @@ class CaffeineConfiguration { // Segments val currentSegmentsCache = buildMinutesCache(currentSegments, ticker, 1) + val segmentsByYearCache = buildMinutesCache(segmentsByYear, ticker, 2) // Species val allSpeciesCache = buildMinutesCache(allSpecies, ticker, oneWeek) @@ -153,6 +155,7 @@ class CaffeineConfiguration { controlAnteriorityCache, controlUnitsCache, currentSegmentsCache, + segmentsByYearCache, districtCache, faoAreasCache, findBeaconCache, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt index 29b357cc56..300e31cf90 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepository.kt @@ -8,6 +8,7 @@ import fr.gouv.cnsp.monitorfish.domain.use_cases.dtos.CreateOrUpdateFleetSegment import fr.gouv.cnsp.monitorfish.infrastructure.database.entities.FleetSegmentEntity import fr.gouv.cnsp.monitorfish.infrastructure.database.repositories.interfaces.DBFleetSegmentRepository import jakarta.transaction.Transactional +import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Repository @Repository @@ -21,6 +22,7 @@ class JpaFleetSegmentRepository( } } + @Cacheable(value = ["segments_by_year"]) override fun findAllByYear(year: Int): List { return dbFleetSegmentRepository.findAllByYearEquals(year).map { it.toFleetSegment() diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/faoAreas/ComputeFAOAreasFromCoordinatesUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/faoAreas/ComputeFAOAreasFromCoordinatesUTests.kt index ec13ea7ea5..babab880c5 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/faoAreas/ComputeFAOAreasFromCoordinatesUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/faoAreas/ComputeFAOAreasFromCoordinatesUTests.kt @@ -1,18 +1,19 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.faoAreas -import com.nhaarman.mockitokotlin2.* +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.given +import com.nhaarman.mockitokotlin2.verify import fr.gouv.cnsp.monitorfish.domain.entities.fao_area.FAOArea import fr.gouv.cnsp.monitorfish.domain.repositories.FAOAreasRepository import fr.gouv.cnsp.monitorfish.domain.use_cases.fao_areas.ComputeFAOAreasFromCoordinates import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.locationtech.jts.geom.Coordinate import org.locationtech.jts.geom.Point import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.test.context.junit.jupiter.SpringExtension -import java.time.* @ExtendWith(SpringExtension::class) class ComputeFAOAreasFromCoordinatesUTests { diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt index 2f87c09a05..e9b019d0df 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt @@ -8,18 +8,13 @@ import fr.gouv.cnsp.monitorfish.domain.entities.mission.ControlUnit import fr.gouv.cnsp.monitorfish.domain.entities.mission.Mission import fr.gouv.cnsp.monitorfish.domain.entities.mission.MissionSource import fr.gouv.cnsp.monitorfish.domain.entities.mission.MissionType -import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.Completion -import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionAction -import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionActionType -import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.SpeciesControl +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.* import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.ActivityCode import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.JointDeploymentPlan import fr.gouv.cnsp.monitorfish.domain.entities.port.Port import fr.gouv.cnsp.monitorfish.domain.entities.vessel.Vessel -import fr.gouv.cnsp.monitorfish.domain.repositories.MissionActionsRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.MissionRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.PortRepository -import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.* +import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.TestUtils import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -40,12 +35,19 @@ class GetActivityReportsUTests { @MockBean private lateinit var vesselRepository: VesselRepository + @MockBean + private lateinit var fleetSegmentRepository: FleetSegmentRepository + @MockBean private lateinit var missionRepository: MissionRepository @Test fun `execute Should return the activity report of a JDP control`() { // Given + given(fleetSegmentRepository.findAllByYear(any())).willReturn( + TestUtils.getDummyFleetSegments(), + ) + val species = SpeciesControl() species.speciesCode = "HKE" @@ -56,7 +58,11 @@ class GetActivityReportsUTests { missionId = 1, actionDatetimeUtc = ZonedDateTime.now(), portLocode = "AEFAT", - faoAreas = listOf("27.4.b", "27.4.c"), + faoAreas = listOf("27.7.b", "27.4.c"), + segments = listOf( + FleetSegment("NWW01/02", "Trawl"), + FleetSegment("NS01/03", "North sea"), + ), actionType = MissionActionType.LAND_CONTROL, gearOnboard = listOf(), speciesOnboard = listOf(species), @@ -75,6 +81,7 @@ class GetActivityReportsUTests { missionId = 2, actionDatetimeUtc = ZonedDateTime.now(), actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), seizureAndDiversion = false, speciesInfractions = listOf(), isDeleted = false, @@ -91,6 +98,7 @@ class GetActivityReportsUTests { missionId = 3, actionDatetimeUtc = ZonedDateTime.now(), actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), seizureAndDiversion = false, speciesInfractions = listOf(), isDeleted = false, @@ -160,27 +168,186 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, + fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), ZonedDateTime.now().minusDays(1), - JointDeploymentPlan.NORTH_SEA, + JointDeploymentPlan.WESTERN_WATERS, ) // Then - assertThat(activityReports.jdpSpecies).hasSize(38) + assertThat(activityReports.jdpSpecies).hasSize(35) assertThat(activityReports.activityReports).hasSize(2) - val landReport = activityReports.activityReports.first() - assertThat(landReport.activityCode).isEqualTo(ActivityCode.LAN) - assertThat(landReport.action.portName).isEqualTo("Al Jazeera Port") - val seaReport = activityReports.activityReports.last() - assertThat(seaReport.activityCode).isEqualTo(ActivityCode.FIS) - assertThat(landReport.vesselNationalIdentifier).isEqualTo("AYFR00022680") + + activityReports.activityReports.first().let { landReport -> + assertThat(landReport.activityCode).isEqualTo(ActivityCode.LAN) + assertThat(landReport.action.portName).isEqualTo("Al Jazeera Port") + assertThat(landReport.faoArea).isEqualTo("27.7.b") + assertThat(landReport.segment).isEqualTo("NWW01/02") + } + + activityReports.activityReports.last().let { seaReport -> + assertThat(seaReport.activityCode).isEqualTo(ActivityCode.FIS) + assertThat(seaReport.vesselNationalIdentifier).isEqualTo("AYFR00022680") + assertThat(seaReport.faoArea).isEqualTo("27.7.b") + assertThat(seaReport.segment).isNull() + } + } + + @Test + fun `execute Should filter a control done outside the JDP FAO area`() { + // Given + given(fleetSegmentRepository.findAllByYear(any())).willReturn( + TestUtils.getDummyFleetSegments(), + ) + + val species = SpeciesControl() + species.speciesCode = "HKE" + + val controls = listOf( + MissionAction( + id = 2, + vesselId = 1, + missionId = 2, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + // These fao areas are outside WESTERN WATERS + faoAreas = listOf("27.4.c", "27.4.b"), + seizureAndDiversion = false, + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + ), + ) + given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + + val vessels = listOf( + Vessel( + id = 1, + internalReferenceNumber = "FR00022680", + vesselName = "MY AWESOME VESSEL", + flagState = CountryCode.FR, + declaredFishingGears = listOf("Trémails"), + vesselType = "Fishing", + districtCode = "AY", + ), + ) + given(vesselRepository.findVesselsByIds(eq(listOf(1)))).willReturn(vessels) + + val missions = listOf( + Mission( + 2, + missionTypes = listOf(MissionType.SEA), + missionSource = MissionSource.MONITORFISH, + isClosed = false, + isUnderJdp = true, + isGeometryComputedFromControls = false, + startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), + ), + ) + given(missionRepository.findByIds(listOf(2))).willReturn(missions) + + // When + val activityReports = GetActivityReports( + missionActionsRepository, + portRepository, + vesselRepository, + fleetSegmentRepository, + missionRepository, + ).execute( + ZonedDateTime.now(), + ZonedDateTime.now().minusDays(1), + JointDeploymentPlan.WESTERN_WATERS, + ) + + // Then + assertThat(activityReports.activityReports).hasSize(0) + } + + @Test + fun `execute Should include a control done within the JDP FAO area`() { + // Given + given(fleetSegmentRepository.findAllByYear(any())).willReturn( + TestUtils.getDummyFleetSegments(), + ) + + val species = SpeciesControl() + species.speciesCode = "HKE" + + val controls = listOf( + MissionAction( + id = 2, + vesselId = 1, + missionId = 2, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + // The first fao area "27.7.c" is within WESTERN WATERS + faoAreas = listOf("27.7.c", "27.4.b"), + seizureAndDiversion = false, + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + ), + ) + given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + + val vessels = listOf( + Vessel( + id = 1, + internalReferenceNumber = "FR00022680", + vesselName = "MY AWESOME VESSEL", + flagState = CountryCode.FR, + declaredFishingGears = listOf("Trémails"), + vesselType = "Fishing", + districtCode = "AY", + ), + ) + given(vesselRepository.findVesselsByIds(eq(listOf(1)))).willReturn(vessels) + + val missions = listOf( + Mission( + 2, + missionTypes = listOf(MissionType.SEA), + missionSource = MissionSource.MONITORFISH, + isClosed = false, + isUnderJdp = true, + isGeometryComputedFromControls = false, + startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), + ), + ) + given(missionRepository.findByIds(listOf(2))).willReturn(missions) + + // When + val activityReports = GetActivityReports( + missionActionsRepository, + portRepository, + vesselRepository, + fleetSegmentRepository, + missionRepository, + ).execute( + ZonedDateTime.now(), + ZonedDateTime.now().minusDays(1), + JointDeploymentPlan.WESTERN_WATERS, + ) + + // Then + assertThat(activityReports.activityReports).hasSize(1) } @Test fun `execute Should not throw When a SEA mission is not found in the mission repository`() { // Given + given(fleetSegmentRepository.findAllByYear(any())).willReturn( + TestUtils.getDummyFleetSegments(), + ) + val species = SpeciesControl() species.speciesCode = "HKE" @@ -288,6 +455,7 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, + fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), @@ -306,6 +474,10 @@ class GetActivityReportsUTests { @Test fun `execute Should not throw When a LAND mission is not found in the mission repository`() { // Given + given(fleetSegmentRepository.findAllByYear(any())).willReturn( + TestUtils.getDummyFleetSegments(), + ) + val species = SpeciesControl() species.speciesCode = "HKE" @@ -389,6 +561,7 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, + fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt index 5afcfaa35e..cfd0067365 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/api/bff/MissionActionsControllerITests.kt @@ -361,6 +361,8 @@ class MissionActionsControllerITests { activityCode = ActivityCode.FIS, vesselNationalIdentifier = "AYFR000654", controlUnits = listOf(ControlUnit(1234, "DIRM", false, "Cross Etel", listOf())), + faoArea = "27.7.c", + segment = "NS01/03", vessel = Vessel( id = 1, internalReferenceNumber = "FR00022680", diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt index 1e9070ae79..7779e51f40 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaFleetSegmentRepositoryITests.kt @@ -29,6 +29,8 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { @BeforeEach fun setup() { cacheManager.getCache("fleet_segments")?.clear() + cacheManager.getCache("current_segments")?.clear() + cacheManager.getCache("segments_by_year")?.clear() } @Test @@ -187,6 +189,7 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { jpaFleetSegmentRepository.delete(segmentToDelete.segment, currentYear - 1) // Then + cacheManager.getCache("segments_by_year")?.clear() val expectedFleetSegment = jpaFleetSegmentRepository.findAllByYear(currentYear - 1) assertThat(expectedFleetSegment).hasSize(22) assertThat(expectedFleetSegment).doesNotContain(segmentToDelete) @@ -228,6 +231,7 @@ class JpaFleetSegmentRepositoryITests : AbstractDBTests() { jpaFleetSegmentRepository.addYear(currentYear - 1, currentYear + 1) // Then + cacheManager.getCache("segments_by_year")?.clear() assertThat(jpaFleetSegmentRepository.findAllByYear(currentYear - 1)).hasSize(23) val updatedFleetSegments = jpaFleetSegmentRepository.findAllByYear(currentYear + 1).sortedBy { it.segment } assertThat(updatedFleetSegments).hasSize(23) From ff8d03d891790dd49922a2978991f56ccaf985bd Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Thu, 2 May 2024 13:23:19 +0200 Subject: [PATCH 2/7] Fix hour/date, fao areas and segment export --- .../mission_list/export_activity_reports.spec.ts | 2 +- .../components/ExportActivityReportsDialog/csvMap.ts | 10 +++++----- frontend/src/features/ActivityReport/types.ts | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/cypress/e2e/side_window/mission_list/export_activity_reports.spec.ts b/frontend/cypress/e2e/side_window/mission_list/export_activity_reports.spec.ts index d9a868efd3..9d908a341b 100644 --- a/frontend/cypress/e2e/side_window/mission_list/export_activity_reports.spec.ts +++ b/frontend/cypress/e2e/side_window/mission_list/export_activity_reports.spec.ts @@ -32,7 +32,7 @@ context('Side Window > Mission List > Export Activity Reports', () => { ) .should( 'contains', - '"LCross Etel","L","Cross Etel","","INSPECTION","2020018","7:19","7:19","FRA","FRA","","","","Vessel","FRA","AYFAK000999999"' + '"LCross Etel","L","Cross Etel","","INSPECTION","20200118","07:19","07:19","FRA","FRA","","","","Vessel","FRA","AYFAK000999999"' ) .should( 'contains', diff --git a/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts b/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts index ccdbbda3c6..e59e8f3656 100644 --- a/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts +++ b/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts @@ -32,7 +32,7 @@ export const JDP_CSV_MAP_BASE: DownloadAsCsvMap = { transform: activity => { const dateTime = customDayjs(activity.action.actionDatetimeUtc) - return `${dateTime.year()}${dateTime.month()}${dateTime.date()}` + return dateTime.format('YYYYMMDD') } }, eventTime: { @@ -40,7 +40,7 @@ export const JDP_CSV_MAP_BASE: DownloadAsCsvMap = { transform: activity => { const dateTime = customDayjs(activity.action.actionDatetimeUtc) - return `${dateTime.hour()}:${dateTime.minute()}` + return dateTime.format('HH:mm') } }, // See MED JDP Decision 2018/030 (3.6.1.1) @@ -49,7 +49,7 @@ export const JDP_CSV_MAP_BASE: DownloadAsCsvMap = { transform: activity => { const dateTime = customDayjs(activity.action.actionDatetimeUtc) - return `${dateTime.hour()}:${dateTime.minute()}` + return dateTime.format('HH:mm') } }, leadingState: { @@ -89,11 +89,11 @@ export const JDP_CSV_MAP_BASE: DownloadAsCsvMap = { }, faoArea: { label: 'FAO_AREA_CODE', - transform: activity => activity.action.faoAreas[0] ?? '' + transform: activity => activity.faoCode ?? '' }, fleetSegment: { label: 'FLEET_SEGMENT', - transform: activity => activity.action.segments[0]?.segment ?? '' + transform: activity => activity.segment ?? '' }, latitude: { label: 'LA', diff --git a/frontend/src/features/ActivityReport/types.ts b/frontend/src/features/ActivityReport/types.ts index 3412871384..e10763716d 100644 --- a/frontend/src/features/ActivityReport/types.ts +++ b/frontend/src/features/ActivityReport/types.ts @@ -11,6 +11,8 @@ export type ActivityReport = { action: MissionAction.MissionAction activityCode: ActivityCode controlUnits: LegacyControlUnit.LegacyControlUnit[] + faoCode: string | undefined + segment: string | undefined vessel: Vessel.Vessel vesselNationalIdentifier: string } From 49df5e1b03c072609e1f0d2b78848bf7ec672f96 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Thu, 2 May 2024 17:45:46 +0200 Subject: [PATCH 3/7] Fix fao zone compute --- .../mission_actions/actrep/JDPToFaoAreas.kt | 7 -- .../actrep/JointDeploymentPlan.kt | 41 ++++++-- .../mission/mission_actions/actrep/species.kt | 14 +-- .../mission_actions/GetActivityReports.kt | 3 +- .../cache/CaffeineConfiguration.kt | 2 +- .../GetActivityReportsUTests.kt | 96 +++++++++++++++++++ 6 files changed, 135 insertions(+), 28 deletions(-) delete mode 100644 backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt deleted file mode 100644 index 63d7191921..0000000000 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JDPToFaoAreas.kt +++ /dev/null @@ -1,7 +0,0 @@ -package fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep - -val JDP_TO_FAO_AREAS = mapOf( - Pair(JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC, MED_FAO_CODES + EASTERN_ATLANTIC_FAO_CODES), - Pair(JointDeploymentPlan.NORTH_SEA, NORTH_SEA_FAO_CODES), - Pair(JointDeploymentPlan.WESTERN_WATERS, WESTERN_WATERS_FAO_CODES), -) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt index 79545d1407..efd36d1b82 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt @@ -1,21 +1,44 @@ package fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep -import com.neovisionaries.i18n.CountryCode +/** + * JDP MED / EASTERN ATLANTIC operational zones + * + * cf. https://extranet.legipeche.metier.developpement-durable.gouv.fr/fichier/pdf/med_jdp_2024_med_sg_final_fr_cle5197c6.pdf?arg=25289&cle=c065370e6727cc3f839e254fcc19c4b24e36dc9d&file=pdf%2Fmed_jdp_2024_med_sg_final_fr_cle5197c6.pdf + */ +val MEDITERRANEAN_OPERATIONAL_ZONES = listOf("37.1", "37.2", "37.3") +val EASTERN_ATLANTIC_OPERATIONAL_ZONES = listOf("34.1.2", "27.7", "27.8", "27.9", "27.10") -enum class JointDeploymentPlan(private val species: List) { - MEDITERRANEAN_AND_EASTERN_ATLANTIC(MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES), - NORTH_SEA(NORTH_SEA_SPECIES), - WESTERN_WATERS(WESTERN_WATERS_SPECIES), - ; +/** + * JDP NORTH SEA operational zones + * + * cf. https://extranet.legipeche.metier.developpement-durable.gouv.fr/fichier/pdf/ed_decision_2023-24_-_ns_jdp_2024_planning_-_adoption_fr_cle11191a.pdf?arg=25287&cle=a5d3eecb0e2bdd9a229e8b34bf5ae11f96e89118&file=pdf%2Fed_decision_2023-24_-_ns_jdp_2024_planning_-_adoption_fr_cle11191a.pdf + */ +val NORTH_SEA_OPERATIONAL_ZONES = listOf("27.4", "27.3.a") - fun getFaoZonesAndSpeciesCodes(): List { - return this.species - } +/** + * JDP WESTERN WATERS operational zones + * + * cf. https://extranet.legipeche.metier.developpement-durable.gouv.fr/fichier/pdf/ed_decision_2023-25_-_ww_jdp_2024_planning_-_adoption_fr_cle128883.pdf?arg=25288&cle=9a2d7705425e766258f0d648353a05aa04249faf&file=pdf%2Fed_decision_2023-25_-_ww_jdp_2024_planning_-_adoption_fr_cle128883.pdf + */ +val WESTERN_WATERS_OPERATIONAL_ZONES = listOf("27.5", "27.6", "27.7", "27.8", "27.9", "27.10", "34.1.1", "34.1.2", "34.2.0") + +enum class JointDeploymentPlan(private val species: List, private val operationalZones: List) { + MEDITERRANEAN_AND_EASTERN_ATLANTIC( + MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES, + MEDITERRANEAN_OPERATIONAL_ZONES + EASTERN_ATLANTIC_OPERATIONAL_ZONES, + ), + NORTH_SEA(NORTH_SEA_SPECIES, NORTH_SEA_OPERATIONAL_ZONES), + WESTERN_WATERS(WESTERN_WATERS_SPECIES, WESTERN_WATERS_OPERATIONAL_ZONES), + ; fun getSpeciesCodes(): List { return this.species.map { it.second }.distinct() } + fun getOperationalZones(): List { + return this.operationalZones + } + /** * See "DÉCISION D’EXÉCUTION (UE) 2023/2376 DE LA COMMISSION": * https://extranet.legipeche.metier.developpement-durable.gouv.fr/fichier/pdf/oj_l_202302376_fr_txt_cle6b198e.pdf?arg=24774&cle=7d14626b709ff7e8c62586bcd8683e7e9fcaa348&file=pdf%2Foj_l_202302376_fr_txt_cle6b198e.pdf diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt index 963405b1c8..a9b8d0f937 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt @@ -17,10 +17,8 @@ typealias FaoZonesAndSpecy = Pair * * See issue: https://github.com/MTES-MCT/monitorfish/issues/1750 */ -val MED_FAO_CODES = listOf("37.1", "37.2", "37.3") -val EASTERN_ATLANTIC_FAO_CODES = listOf("27.7", "27.8", "27.9", "27.10") val MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES: List = generateSpeciesWithFaoCode( - MED_FAO_CODES, + MEDITERRANEAN_OPERATIONAL_ZONES, listOf( "ANE", "HOM", @@ -68,12 +66,11 @@ val MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES: List = generat "ANN", ), ) + - // Eastern Atlantic part - listOf(Pair(EASTERN_ATLANTIC_FAO_CODES, "BFT")) + // Eastern Atlantic part for BFT + listOf(Pair(listOf("27.7", "27.8", "27.9", "27.10"), "BFT")) -val NORTH_SEA_FAO_CODES = listOf("27.4", "27.3.a") val NORTH_SEA_SPECIES: List = generateSpeciesWithFaoCode( - NORTH_SEA_FAO_CODES, + NORTH_SEA_OPERATIONAL_ZONES, listOf( "HOM", "JAX", @@ -116,12 +113,11 @@ val NORTH_SEA_SPECIES: List = generateSpeciesWithFaoCode( ), ) -val WESTERN_WATERS_FAO_CODES = listOf("27.6", "27.7", "27.8", "27.9", "27.10") val WESTERN_WATERS_SPECIES: List = listOf( Pair(listOf("27.6", "27.7", "27.8", "27.9"), "PIL"), Pair(listOf("27.6", "27.7", "27.8", "27.9"), "ELE"), ) + generateSpeciesWithFaoCode( - WESTERN_WATERS_FAO_CODES, + listOf("27.6", "27.7", "27.8", "27.9", "27.10"), listOf( "ANE", "HOM", diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt index bbf58dd008..06fe8be276 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt @@ -6,7 +6,6 @@ import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionAction import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionActionType import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.ActivityCode -import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.JDP_TO_FAO_AREAS import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.JointDeploymentPlan import fr.gouv.cnsp.monitorfish.domain.exceptions.CodeNotFoundException import fr.gouv.cnsp.monitorfish.domain.repositories.* @@ -27,7 +26,7 @@ class GetActivityReports( private val logger = LoggerFactory.getLogger(GetActivityReports::class.java) fun execute(beforeDateTime: ZonedDateTime, afterDateTime: ZonedDateTime, jdp: JointDeploymentPlan): ActivityReports { - val jdpFaoAreas = JDP_TO_FAO_AREAS[jdp] + val jdpFaoAreas = jdp.getOperationalZones() val controls = missionActionsRepository.findControlsInDates(beforeDateTime, afterDateTime) logger.info("Found ${controls.size} controls between dates [$afterDateTime, $beforeDateTime].") diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt index f859921b8f..dd0546bf9c 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/cache/CaffeineConfiguration.kt @@ -120,7 +120,7 @@ class CaffeineConfiguration { // Segments val currentSegmentsCache = buildMinutesCache(currentSegments, ticker, 1) - val segmentsByYearCache = buildMinutesCache(segmentsByYear, ticker, 2) + val segmentsByYearCache = buildSecondsCache(segmentsByYear, ticker, 10) // Species val allSpeciesCache = buildMinutesCache(allSpecies, ticker, oneWeek) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt index e9b019d0df..ff16b65104 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt @@ -22,6 +22,7 @@ import org.springframework.boot.test.mock.mockito.MockBean import org.springframework.test.context.junit.jupiter.SpringExtension import java.time.ZoneOffset import java.time.ZonedDateTime +import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment as FullFleetSegment @ExtendWith(SpringExtension::class) class GetActivityReportsUTests { @@ -195,6 +196,101 @@ class GetActivityReportsUTests { } } + @Test + fun `execute Should add the fao area for a LAND control`() { + // Given + val species = SpeciesControl() + species.speciesCode = "HKE" + + given(fleetSegmentRepository.findAllByYear(any())).willReturn( + listOf( + FullFleetSegment( + "NS01/03", + "Otter trawls/Seines", + gears = listOf("OTB", "OTT", "TBN", "PTB", "SDN", "SSC", "SPR", "OT", "TBS", "OTM", "PTM", "TMS", "TM", "TX", "TB", "SX", "SV"), + targetSpecies = listOf("COD", "HAD", "WHG", "POK", "SOL", "PLE", "NEP", "HKE"), + faoAreas = listOf("27.2.a", "27.4.a", "27.4.b", "27.4.c"), + year = ZonedDateTime.now().year, + impactRiskFactor = 2.56, + ), + ), + ) + + val controls = listOf( + MissionAction( + id = 1, + vesselId = 1, + missionId = 1, + actionDatetimeUtc = ZonedDateTime.now(), + portLocode = "AEFAT", + faoAreas = listOf("27.4.a"), + segments = listOf( + FleetSegment("NS01/03", "North Sea"), + ), + actionType = MissionActionType.LAND_CONTROL, + gearOnboard = listOf(), + speciesOnboard = listOf(species), + seizureAndDiversion = true, + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + ), + ) + given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + + val vessels = listOf( + Vessel( + id = 1, + internalReferenceNumber = "FR00022680", + vesselName = "MY AWESOME VESSEL", + flagState = CountryCode.FR, + declaredFishingGears = listOf("Trémails"), + vesselType = "Fishing", + districtCode = "AY", + ), + ) + given(vesselRepository.findVesselsByIds(eq(listOf(1)))).willReturn(vessels) + + val missions = listOf( + Mission( + 1, + missionTypes = listOf(MissionType.LAND), + missionSource = MissionSource.MONITORFISH, + isClosed = false, + isUnderJdp = false, + isGeometryComputedFromControls = false, + startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), + ), + ) + given(missionRepository.findByIds(eq(listOf(1)))).willReturn(missions) + given(portRepository.findByLocode(eq("AEFAT"))).willReturn(Port("AEFAT", "Al Jazeera Port")) + + // When + val activityReports = GetActivityReports( + missionActionsRepository, + portRepository, + vesselRepository, + fleetSegmentRepository, + missionRepository, + ).execute( + ZonedDateTime.now(), + ZonedDateTime.now().minusDays(1), + JointDeploymentPlan.NORTH_SEA, + ) + + // Then + assertThat(activityReports.activityReports).hasSize(1) + + activityReports.activityReports.first().let { landReport -> + assertThat(landReport.activityCode).isEqualTo(ActivityCode.LAN) + assertThat(landReport.action.portName).isEqualTo("Al Jazeera Port") + assertThat(landReport.faoArea).isEqualTo("27.4.a") + assertThat(landReport.segment).isEqualTo("NS01/03") + } + } + @Test fun `execute Should filter a control done outside the JDP FAO area`() { // Given From a4a6bc9b55614ef5027e3def9a1eabc59549a754 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Wed, 29 May 2024 11:41:44 +0200 Subject: [PATCH 4/7] Prevent a control to be added in multiple Act-Rep and filter Med JDP --- .../actrep/JointDeploymentPlan.kt | 86 ++++- .../mission/mission_actions/actrep/species.kt | 3 +- .../repositories/MissionActionsRepository.kt | 2 +- .../mission_actions/GetActivityReports.kt | 61 +-- .../JpaMissionActionsRepository.kt | 2 +- .../actrep/JointDeploymentPlanUTests.kt | 351 ++++++++++++++++-- .../GetActivityReportsUTests.kt | 176 +++++++-- .../JpaMissionActionRepositoryITests.kt | 4 +- 8 files changed, 559 insertions(+), 126 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt index efd36d1b82..17dd074827 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt @@ -1,5 +1,10 @@ package fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep +import fr.gouv.cnsp.monitorfish.domain.entities.fao_area.FAOArea +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionAction +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionActionType +import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.hasFaoCodeIncludedIn + /** * JDP MED / EASTERN ATLANTIC operational zones * @@ -35,7 +40,7 @@ enum class JointDeploymentPlan(private val species: List, priv return this.species.map { it.second }.distinct() } - fun getOperationalZones(): List { + private fun getOperationalZones(): List { return this.operationalZones } @@ -43,11 +48,19 @@ enum class JointDeploymentPlan(private val species: List, priv * See "DÉCISION D’EXÉCUTION (UE) 2023/2376 DE LA COMMISSION": * https://extranet.legipeche.metier.developpement-durable.gouv.fr/fichier/pdf/oj_l_202302376_fr_txt_cle6b198e.pdf?arg=24774&cle=7d14626b709ff7e8c62586bcd8683e7e9fcaa348&file=pdf%2Foj_l_202302376_fr_txt_cle6b198e.pdf */ - fun isLandControlApplicable(flagState: CountryCode, speciesOnboardCodes: List, tripFaoCodes: List): Boolean { - val isThirdCountryVessel = EU_THIRD_COUNTRIES.contains(flagState) + fun isLandControlApplicable(control: MissionAction): Boolean { + val speciesOnboardCodes = control.speciesOnboard.mapNotNull { it.speciesCode } + val tripFaoCodes = control.faoAreas + + val isThirdCountryVessel = EU_THIRD_COUNTRIES.contains(control.flagState) + + val isFirstJdpCurrentJdp = isFirstJdpFound(control) + if (!isFirstJdpCurrentJdp) { + return false + } val hasSpeciesInJdp = this.species.any { (jdpFaoZones, jdpSpecy) -> - val isSpecyFoundInJdpSPecies = speciesOnboardCodes.contains(jdpSpecy) + val isSpecyFoundInJdpSpecies = speciesOnboardCodes.contains(jdpSpecy) val isFaoZoneFoundInJdpFaoZones = jdpFaoZones .any { jdpFaoCode -> @@ -55,7 +68,7 @@ enum class JointDeploymentPlan(private val species: List, priv tripFaoCodes.any { tripFaoCode -> tripFaoCode.contains(jdpFaoCode) } } - return@any isSpecyFoundInJdpSPecies && isFaoZoneFoundInJdpFaoZones + return@any isSpecyFoundInJdpSpecies && isFaoZoneFoundInJdpFaoZones } val hasSpeciesInEUQuotas = if (isThirdCountryVessel) { @@ -66,4 +79,67 @@ enum class JointDeploymentPlan(private val species: List, priv return hasSpeciesInJdp || hasSpeciesInEUQuotas } + + fun getFirstFaoAreaIncludedInJdp( + control: MissionAction, + ): FAOArea? { + val jdpFaoAreas = this.getOperationalZones() + + if (control.actionType == MissionActionType.SEA_CONTROL && !isFirstJdpFound(control)) { + return null + } + + val firstFaoAreaIncludedInJdp = control.faoAreas + .map { FAOArea(it) } + .firstOrNull { controlFaoArea -> + jdpFaoAreas.any { controlFaoArea.hasFaoCodeIncludedIn(it) } + } + + return firstFaoAreaIncludedInJdp + } + + /** + * We use an arbitrary method to de-duplicated reporting of controls made in multiple fao areas, + * hence in multiple JDP. + * `JointDeploymentPlan.entries.firstOrNull` is the arbitrary rule to attach a control to only one JDP. + * see: https://github.com/MTES-MCT/monitorfish/issues/3157#issuecomment-2093036583 + */ + private fun isFirstJdpFound( + control: MissionAction, + ) = JointDeploymentPlan.entries + .firstOrNull { jdpEntry -> + /** + * There is an overlap between the `MEDITERRANEAN_AND_EASTERN_ATLANTIC` and the WESTERN_WATERS JDPs. + * We add a filter by species to avoid counting all controls done in + * `EASTERN_ATLANTIC_OPERATIONAL_ZONES without targeted species in catches. + */ + if (control.actionType == MissionActionType.SEA_CONTROL && jdpEntry == MEDITERRANEAN_AND_EASTERN_ATLANTIC) { + return@firstOrNull isMedJdp(control) + } + + return@firstOrNull jdpEntry.getOperationalZones().any { jdpFaoArea -> + control.faoAreas.any { controlFaoArea -> + FAOArea(controlFaoArea).hasFaoCodeIncludedIn(jdpFaoArea) + } + } + } == this + + private fun isMedJdp( + control: MissionAction, + ) = MEDITERRANEAN_AND_EASTERN_ATLANTIC.getOperationalZones().any { jdpFaoArea -> + /** + * Filter by FAO zone AND `EASTERN_ATLANTIC_SPECY` + * if the fao zone is included in the `EASTERN_ATLANTIC_OPERATIONAL_ZONES` + */ + if (EASTERN_ATLANTIC_OPERATIONAL_ZONES.contains(jdpFaoArea)) { + return@any control.faoAreas.any { controlFaoArea -> + FAOArea(controlFaoArea).hasFaoCodeIncludedIn(jdpFaoArea) && + control.speciesOnboard.map { it.speciesCode }.contains(EASTERN_ATLANTIC_SPECY.second) + } + } + + return@any control.faoAreas.any { controlFaoArea -> + FAOArea(controlFaoArea).hasFaoCodeIncludedIn(jdpFaoArea) + } + } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt index a9b8d0f937..73119d4839 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/species.kt @@ -17,6 +17,7 @@ typealias FaoZonesAndSpecy = Pair * * See issue: https://github.com/MTES-MCT/monitorfish/issues/1750 */ +val EASTERN_ATLANTIC_SPECY = Pair(EASTERN_ATLANTIC_OPERATIONAL_ZONES, "BFT") val MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES: List = generateSpeciesWithFaoCode( MEDITERRANEAN_OPERATIONAL_ZONES, listOf( @@ -67,7 +68,7 @@ val MEDITERRANEAN_AND_EASTERN_ATLANTIC_SPECIES: List = generat ), ) + // Eastern Atlantic part for BFT - listOf(Pair(listOf("27.7", "27.8", "27.9", "27.10"), "BFT")) + listOf(EASTERN_ATLANTIC_SPECY) val NORTH_SEA_SPECIES: List = generateSpeciesWithFaoCode( NORTH_SEA_OPERATIONAL_ZONES, diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionActionsRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionActionsRepository.kt index 1d70e3fe5b..ccc4688f7b 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionActionsRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/repositories/MissionActionsRepository.kt @@ -7,7 +7,7 @@ interface MissionActionsRepository { fun findById(id: Int): MissionAction fun findByMissionId(missionId: Int): List fun findVesselMissionActionsAfterDateTime(vesselId: Int, afterDateTime: ZonedDateTime): List - fun findControlsInDates(beforeDateTime: ZonedDateTime, afterDateTime: ZonedDateTime): List + fun findSeaAndLandControlBetweenDates(beforeDateTime: ZonedDateTime, afterDateTime: ZonedDateTime): List fun findMissionActionsIn(missionIds: List): List fun save(missionAction: MissionAction): MissionAction } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt index 06fe8be276..7aa1a09580 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt @@ -1,15 +1,14 @@ package fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions import fr.gouv.cnsp.monitorfish.config.UseCase -import fr.gouv.cnsp.monitorfish.domain.entities.fao_area.FAOArea -import fr.gouv.cnsp.monitorfish.domain.entities.fleet_segment.FleetSegment -import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionAction import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionActionType import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.ActivityCode import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep.JointDeploymentPlan import fr.gouv.cnsp.monitorfish.domain.exceptions.CodeNotFoundException -import fr.gouv.cnsp.monitorfish.domain.repositories.* -import fr.gouv.cnsp.monitorfish.domain.use_cases.fleet_segment.hasFaoCodeIncludedIn +import fr.gouv.cnsp.monitorfish.domain.repositories.MissionActionsRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.MissionRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.PortRepository +import fr.gouv.cnsp.monitorfish.domain.repositories.VesselRepository import fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions.dtos.ActivityReport import fr.gouv.cnsp.monitorfish.domain.use_cases.mission.mission_actions.dtos.ActivityReports import org.slf4j.LoggerFactory @@ -20,14 +19,12 @@ class GetActivityReports( private val missionActionsRepository: MissionActionsRepository, private val portRepository: PortRepository, private val vesselRepository: VesselRepository, - private val segmentRepository: FleetSegmentRepository, private val missionRepository: MissionRepository, ) { private val logger = LoggerFactory.getLogger(GetActivityReports::class.java) fun execute(beforeDateTime: ZonedDateTime, afterDateTime: ZonedDateTime, jdp: JointDeploymentPlan): ActivityReports { - val jdpFaoAreas = jdp.getOperationalZones() - val controls = missionActionsRepository.findControlsInDates(beforeDateTime, afterDateTime) + val controls = missionActionsRepository.findSeaAndLandControlBetweenDates(beforeDateTime, afterDateTime) logger.info("Found ${controls.size} controls between dates [$afterDateTime, $beforeDateTime].") if (controls.isEmpty()) { @@ -47,10 +44,7 @@ class GetActivityReports( val filteredControls = controls.filter { control -> when (control.actionType) { MissionActionType.LAND_CONTROL -> { - val speciesOnboardCodes = control.speciesOnboard.mapNotNull { it.speciesCode } - val tripFaoCodes = control.faoAreas - - return@filter jdp.isLandControlApplicable(control.flagState, speciesOnboardCodes, tripFaoCodes) + return@filter jdp.isLandControlApplicable(control) } MissionActionType.SEA_CONTROL -> { @@ -63,7 +57,7 @@ class GetActivityReports( } if (control.faoAreas.isNotEmpty()) { - val foundFaoAreaIncludedInJdp = getFirstFaoAreaIncludedInJdp(jdpFaoAreas, control.faoAreas) + val foundFaoAreaIncludedInJdp = jdp.getFirstFaoAreaIncludedInJdp(control) return@filter isUnderJdp && foundFaoAreaIncludedInJdp != null } @@ -114,15 +108,18 @@ class GetActivityReports( logger.warn(e.message) } } - val faoArea = getFirstFaoAreaIncludedInJdp(jdpFaoAreas, control.faoAreas) - val segment = getFirstFleetSegmentIncludedInFaoArea(control, faoArea) + val faoArea = jdp.getFirstFaoAreaIncludedInJdp(control) ActivityReport( action = control, activityCode = activityCode, controlUnits = controlMission.controlUnits, faoArea = faoArea?.faoCode, - segment = segment?.segment, + /** + * The fleet segment is set as null, as we need to integrate the EFCA segments referential + * see: https://github.com/MTES-MCT/monitorfish/issues/3157#issuecomment-2093036583 + */ + segment = null, vesselNationalIdentifier = controlledVessel.getNationalIdentifier(), vessel = controlledVessel, ) @@ -133,36 +130,4 @@ class GetActivityReports( jdpSpecies = jdp.getSpeciesCodes(), ) } - - private fun getFirstFleetSegmentIncludedInFaoArea( - control: MissionAction, - controlFaoArea: FAOArea?, - ): FleetSegment? { - val segment = segmentRepository.findAllByYear(control.actionDatetimeUtc.year) - .firstOrNull { yearSegment -> - control.segments.any { controlSegment -> - yearSegment.segment === controlSegment.segment && - yearSegment.faoAreas.any { controlFaoArea?.hasFaoCodeIncludedIn(it) ?: false } - } - } - - return segment - } - - private fun getFirstFaoAreaIncludedInJdp( - jdpFaoAreas: List?, - faoAreas: List, - ): FAOArea? { - val foundFaoArea = jdpFaoAreas?.let { jdpFaoAreasNotNull -> - faoAreas - .map { controlFaoArea -> FAOArea(controlFaoArea) } - .firstOrNull { controlFaoArea -> - jdpFaoAreasNotNull.any { jdpFaoArea -> - controlFaoArea.hasFaoCodeIncludedIn(jdpFaoArea) - } - } - } - - return foundFaoArea - } } diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionsRepository.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionsRepository.kt index 50c6ae9bcc..4dffb405a7 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionsRepository.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionsRepository.kt @@ -50,7 +50,7 @@ class JpaMissionActionsRepository( return dbMissionActionsRepository.findById(id).get().toMissionAction(mapper) } - override fun findControlsInDates(beforeDateTime: ZonedDateTime, afterDateTime: ZonedDateTime): List { + override fun findSeaAndLandControlBetweenDates(beforeDateTime: ZonedDateTime, afterDateTime: ZonedDateTime): List { return dbMissionActionsRepository.findAllByActionDatetimeUtcBeforeAndActionDatetimeUtcAfterAndIsDeletedIsFalseAndActionTypeIn( beforeDateTime.toInstant(), afterDateTime.toInstant(), diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt index 00d9661086..5551bb21f3 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt @@ -1,83 +1,356 @@ package fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.actrep import com.neovisionaries.i18n.CountryCode +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.Completion +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionAction +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.MissionActionType +import fr.gouv.cnsp.monitorfish.domain.entities.mission.mission_actions.SpeciesControl import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource import org.springframework.test.context.junit.jupiter.SpringExtension +import java.time.ZonedDateTime @ExtendWith(SpringExtension::class) class JointDeploymentPlanUTests { - @Test - fun `isLandControlConcerned Should return true When a targeted specy and fao code are contained in the control`() { + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `isLandControlApplicable Should return true When a targeted specy and fao code are contained in the control`( + jdp: JointDeploymentPlan, + ) { // Given - val jdp = JointDeploymentPlan.NORTH_SEA - val faoCodes = listOf("27.4.b", "27.4.c") - val species = listOf("HKE", "ANN", "BOR") + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.4.b", "27.4.c"), + seizureAndDiversion = false, + speciesOnboard = getSpecies(listOf("HKE", "ANN", "BOR")), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH" + ) // When - val isLandControlConcerned = jdp.isLandControlApplicable(CountryCode.FR, species, faoCodes) + val isLandControlApplicable = jdp.isLandControlApplicable(control) // Then - assertThat(isLandControlConcerned).isTrue() + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.NORTH_SEA -> assertThat(isLandControlApplicable).isTrue() + JointDeploymentPlan.WESTERN_WATERS -> assertThat(isLandControlApplicable).isFalse() + } } - @Test - fun `isLandControlConcerned Should return false When a targeted specy is not contained in the control`() { + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `isLandControlApplicable Should return false When a targeted specy is not contained in the control`( + jdp: JointDeploymentPlan, + ) { // Given - val jdp = JointDeploymentPlan.NORTH_SEA - val faoCodes = listOf("27.4.b", "27.4.c") - // The "HKE" specy is missing - val species = listOf("ANN", "BOR") + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.4.b", "27.4.c"), + seizureAndDiversion = false, + // The HKE specy is missing + speciesOnboard = getSpecies(listOf("ANN", "BOR")), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH" + ) // When - val isLandControlConcerned = jdp.isLandControlApplicable(CountryCode.FR, species, faoCodes) + val isLandControlApplicable = jdp.isLandControlApplicable(control) // Then - assertThat(isLandControlConcerned).isFalse() + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.NORTH_SEA -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.WESTERN_WATERS -> assertThat(isLandControlApplicable).isFalse() + } } - @Test - fun `isLandControlConcerned Should return false When a targeted fao code is not contained in the control`() { + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `isLandControlApplicable Should return false When a targeted fao code is not contained in the control`( + jdp: JointDeploymentPlan, + ) { // Given - val jdp = JointDeploymentPlan.NORTH_SEA - // The "27.4" fao code is missing - val faoCodes = listOf("27.5.b", "27.5.c") - val species = listOf("HKE", "ANN", "BOR") + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + // The "27.4" fao code is missing + faoAreas = listOf("27.5.b", "27.5.c"), + seizureAndDiversion = false, + speciesOnboard = getSpecies(listOf("HKE", "ANN", "BOR")), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH" + ) // When - val isLandControlConcerned = jdp.isLandControlApplicable(CountryCode.FR, species, faoCodes) + val isLandControlApplicable = jdp.isLandControlApplicable(control) // Then - assertThat(isLandControlConcerned).isFalse() + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.NORTH_SEA -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.WESTERN_WATERS -> assertThat(isLandControlApplicable).isFalse() + } } - @Test - fun `isLandControlConcerned Should return true When a third country vessel has species in the EU quota list`() { + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `isLandControlApplicable Should return true When a third country vessel has species in the EU quota list`( + jdp: JointDeploymentPlan, + ) { // Given - val jdp = JointDeploymentPlan.NORTH_SEA - val faoCodes = listOf("27.5.b", "27.5.c") - // ALB is contained in the quotas - val species = listOf("HKE", "ANN", "BOR", "ALB") + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.5.b", "27.5.c"), + seizureAndDiversion = false, + // ALB is contained in the quotas + speciesOnboard = getSpecies(listOf("HKE", "ANN", "BOR", "ALB")), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.GB, + userTrigram = "LTH" + ) // When - val isLandControlConcerned = jdp.isLandControlApplicable(CountryCode.GB, species, faoCodes) + val isLandControlApplicable = jdp.isLandControlApplicable(control) // Then - assertThat(isLandControlConcerned).isTrue() + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.NORTH_SEA -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.WESTERN_WATERS -> assertThat(isLandControlApplicable).isTrue() + } } - @Test - fun `isLandControlConcerned Should return false When a third country vessel has no species in the EU quota list`() { + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `isLandControlApplicable Should return false When a third country vessel has no species in the EU quota list`( + jdp: JointDeploymentPlan, + ) { // Given - val jdp = JointDeploymentPlan.NORTH_SEA - val faoCodes = listOf("27.5.b", "27.5.c") - val species = listOf("HKE", "ANN", "BOR") + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.5.b", "27.5.c"), + seizureAndDiversion = false, + speciesOnboard = getSpecies(listOf("HKE", "ANN", "BOR")), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.GB, + userTrigram = "LTH" + ) // When - val isLandControlConcerned = jdp.isLandControlApplicable(CountryCode.GB, species, faoCodes) + val isLandControlApplicable = jdp.isLandControlApplicable(control) // Then - assertThat(isLandControlConcerned).isFalse() + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.NORTH_SEA -> assertThat(isLandControlApplicable).isFalse() + JointDeploymentPlan.WESTERN_WATERS -> assertThat(isLandControlApplicable).isFalse() + } + } + + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `getFirstFaoAreaIncludedInJdp Should return the fao area When the first JDP found is the JDP of the Act-Rep`( + jdp: JointDeploymentPlan, + ) { + // Given + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), + seizureAndDiversion = false, + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH" + ) + + // When + val faoArea = jdp.getFirstFaoAreaIncludedInJdp(control) + + // Then + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(faoArea?.faoCode).isNull() + JointDeploymentPlan.NORTH_SEA -> assertThat(faoArea?.faoCode).isEqualTo("27.4.c") + JointDeploymentPlan.WESTERN_WATERS -> assertThat(faoArea?.faoCode).isNull() + } + } + + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `getFirstFaoAreaIncludedInJdp Should return the fao area for all Act-Rep When the control is done at LAND (because the filtering is done in isLandControlApplicable)`( + jdp: JointDeploymentPlan, + ) { + // Given + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.LAND_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), + seizureAndDiversion = false, + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH" + ) + + // When + val faoArea = jdp.getFirstFaoAreaIncludedInJdp(control) + + // Then + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(faoArea?.faoCode).isEqualTo("27.7.b") + JointDeploymentPlan.NORTH_SEA -> assertThat(faoArea?.faoCode).isEqualTo("27.4.c") + JointDeploymentPlan.WESTERN_WATERS -> assertThat(faoArea?.faoCode).isEqualTo("27.7.b") + } + } + + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `getFirstFaoAreaIncludedInJdp Should return the fao area When the control contains BFT for the MEDITERRANEAN_AND_EASTERN_ATLANTIC JDP`( + jdp: JointDeploymentPlan, + ) { + // Given + val firstSpecy = SpeciesControl() + firstSpecy.speciesCode = "BFT" + val secondSpecy = SpeciesControl() + secondSpecy.speciesCode = "HKE" + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), + seizureAndDiversion = false, + speciesOnboard = listOf(firstSpecy, secondSpecy), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH" + ) + + // When + val faoArea = jdp.getFirstFaoAreaIncludedInJdp(control) + + // Then + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(faoArea?.faoCode).isEqualTo("27.7.b") + JointDeploymentPlan.NORTH_SEA -> assertThat(faoArea?.faoCode).isNull() + JointDeploymentPlan.WESTERN_WATERS -> assertThat(faoArea?.faoCode).isNull() + } + } + + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `getFirstFaoAreaIncludedInJdp Should return the fao area for the NORTH_SEA JDP When the control does not contains BFT for the MEDITERRANEAN_AND_EASTERN_ATLANTIC JDP`( + jdp: JointDeploymentPlan, + ) { + // Given + val specy = SpeciesControl() + specy.speciesCode = "HKE" + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), + seizureAndDiversion = false, + speciesOnboard = listOf(specy), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH" + ) + + // When + val faoArea = jdp.getFirstFaoAreaIncludedInJdp(control) + + // Then + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(faoArea?.faoCode).isNull() + JointDeploymentPlan.NORTH_SEA -> assertThat(faoArea?.faoCode).isEqualTo("27.4.c") + JointDeploymentPlan.WESTERN_WATERS -> assertThat(faoArea?.faoCode).isNull() + } + } + + private fun getSpecies(species: List): List { + return species.map { + val specy = SpeciesControl() + specy.speciesCode = it + + return@map specy + } } } diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt index ff16b65104..bc70a02f6e 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt @@ -43,7 +43,7 @@ class GetActivityReportsUTests { private lateinit var missionRepository: MissionRepository @Test - fun `execute Should return the activity report of a JDP control`() { + fun `execute Should filter controls done in two fao areas When the first JDP found for this control is NORTH_SEA`() { // Given given(fleetSegmentRepository.findAllByYear(any())).willReturn( TestUtils.getDummyFleetSegments(), @@ -111,7 +111,7 @@ class GetActivityReportsUTests { completion = Completion.TO_COMPLETE, ), ) - given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) val vessels = listOf( Vessel( @@ -169,7 +169,6 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, - fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), @@ -179,19 +178,145 @@ class GetActivityReportsUTests { // Then assertThat(activityReports.jdpSpecies).hasSize(35) - assertThat(activityReports.activityReports).hasSize(2) + assertThat(activityReports.activityReports).hasSize(0) + } - activityReports.activityReports.first().let { landReport -> - assertThat(landReport.activityCode).isEqualTo(ActivityCode.LAN) - assertThat(landReport.action.portName).isEqualTo("Al Jazeera Port") - assertThat(landReport.faoArea).isEqualTo("27.7.b") - assertThat(landReport.segment).isEqualTo("NWW01/02") - } + @Test + fun `execute Should include a control done in two fao areas as the first JDP found for this control is NORTH_SEA`() { + // Given + given(fleetSegmentRepository.findAllByYear(any())).willReturn( + TestUtils.getDummyFleetSegments(), + ) + + val species = SpeciesControl() + species.speciesCode = "HKE" - activityReports.activityReports.last().let { seaReport -> + val controls = listOf( + MissionAction( + id = 1, + vesselId = 1, + missionId = 1, + actionDatetimeUtc = ZonedDateTime.now(), + portLocode = "AEFAT", + faoAreas = listOf("27.7.b", "27.4.c"), + segments = listOf( + FleetSegment("NWW01/02", "Trawl"), + FleetSegment("NS01/03", "North sea"), + ), + actionType = MissionActionType.LAND_CONTROL, + gearOnboard = listOf(), + speciesOnboard = listOf(species), + seizureAndDiversion = true, + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + ), + MissionAction( + id = 2, + vesselId = 1, + missionId = 2, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), + seizureAndDiversion = false, + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + ), + MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.SEA_CONTROL, + faoAreas = listOf("27.7.b", "27.4.c"), + seizureAndDiversion = false, + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + ), + ) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) + + val vessels = listOf( + Vessel( + id = 1, + internalReferenceNumber = "FR00022680", + vesselName = "MY AWESOME VESSEL", + flagState = CountryCode.FR, + declaredFishingGears = listOf("Trémails"), + vesselType = "Fishing", + districtCode = "AY", + ), + Vessel( + id = 2, + internalReferenceNumber = "FR00065455", + vesselName = "MY SECOND AWESOME VESSEL", + flagState = CountryCode.FR, + declaredFishingGears = listOf("Trémails"), + vesselType = "Fishing", + districtCode = "LO", + ), + ) + given(vesselRepository.findVesselsByIds(eq(listOf(1, 2)))).willReturn(vessels) + + val missions = listOf( + Mission( + 1, + missionTypes = listOf(MissionType.LAND), + missionSource = MissionSource.MONITORFISH, + isUnderJdp = false, + isGeometryComputedFromControls = false, + startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), + ), + Mission( + 2, + missionTypes = listOf(MissionType.SEA), + missionSource = MissionSource.MONITORFISH, + isUnderJdp = true, + isGeometryComputedFromControls = false, + startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), + ), + Mission( + 2, + missionTypes = listOf(MissionType.SEA), + missionSource = MissionSource.MONITORFISH, + isUnderJdp = false, + isGeometryComputedFromControls = false, + startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), + ), + ) + given(missionRepository.findByIds(listOf(1, 2, 3))).willReturn(missions) + given(portRepository.findByLocode(eq("AEFAT"))).willReturn(Port("AEFAT", "Al Jazeera Port")) + + // When + val activityReports = GetActivityReports( + missionActionsRepository, + portRepository, + vesselRepository, + missionRepository, + ).execute( + ZonedDateTime.now(), + ZonedDateTime.now().minusDays(1), + JointDeploymentPlan.NORTH_SEA, + ) + + // Then + assertThat(activityReports.jdpSpecies).hasSize(38) + assertThat(activityReports.activityReports).hasSize(1) + + activityReports.activityReports.first().let { seaReport -> assertThat(seaReport.activityCode).isEqualTo(ActivityCode.FIS) assertThat(seaReport.vesselNationalIdentifier).isEqualTo("AYFR00022680") - assertThat(seaReport.faoArea).isEqualTo("27.7.b") + assertThat(seaReport.faoArea).isEqualTo("27.4.c") assertThat(seaReport.segment).isNull() } } @@ -238,7 +363,7 @@ class GetActivityReportsUTests { completion = Completion.TO_COMPLETE, ), ) - given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) val vessels = listOf( Vessel( @@ -258,7 +383,6 @@ class GetActivityReportsUTests { 1, missionTypes = listOf(MissionType.LAND), missionSource = MissionSource.MONITORFISH, - isClosed = false, isUnderJdp = false, isGeometryComputedFromControls = false, startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), @@ -272,7 +396,6 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, - fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), @@ -287,7 +410,7 @@ class GetActivityReportsUTests { assertThat(landReport.activityCode).isEqualTo(ActivityCode.LAN) assertThat(landReport.action.portName).isEqualTo("Al Jazeera Port") assertThat(landReport.faoArea).isEqualTo("27.4.a") - assertThat(landReport.segment).isEqualTo("NS01/03") + assertThat(landReport.segment).isNull() } } @@ -319,7 +442,7 @@ class GetActivityReportsUTests { completion = Completion.TO_COMPLETE, ), ) - given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) val vessels = listOf( Vessel( @@ -339,7 +462,6 @@ class GetActivityReportsUTests { 2, missionTypes = listOf(MissionType.SEA), missionSource = MissionSource.MONITORFISH, - isClosed = false, isUnderJdp = true, isGeometryComputedFromControls = false, startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), @@ -352,7 +474,6 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, - fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), @@ -381,7 +502,8 @@ class GetActivityReportsUTests { missionId = 2, actionDatetimeUtc = ZonedDateTime.now(), actionType = MissionActionType.SEA_CONTROL, - // The first fao area "27.7.c" is within WESTERN WATERS + // The first fao area "27.7.c" is within WESTERN_WATERS + // The second fao area "27.4.b" is within NORTH_SEA faoAreas = listOf("27.7.c", "27.4.b"), seizureAndDiversion = false, speciesInfractions = listOf(), @@ -392,7 +514,7 @@ class GetActivityReportsUTests { completion = Completion.TO_COMPLETE, ), ) - given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) val vessels = listOf( Vessel( @@ -412,7 +534,6 @@ class GetActivityReportsUTests { 2, missionTypes = listOf(MissionType.SEA), missionSource = MissionSource.MONITORFISH, - isClosed = false, isUnderJdp = true, isGeometryComputedFromControls = false, startDateTimeUtc = ZonedDateTime.of(2020, 5, 5, 3, 4, 5, 3, ZoneOffset.UTC), @@ -425,12 +546,11 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, - fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), ZonedDateTime.now().minusDays(1), - JointDeploymentPlan.WESTERN_WATERS, + JointDeploymentPlan.NORTH_SEA, ) // Then @@ -500,7 +620,7 @@ class GetActivityReportsUTests { completion = Completion.TO_COMPLETE, ), ) - given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) val vessels = listOf( Vessel( @@ -551,7 +671,6 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, - fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), @@ -614,7 +733,7 @@ class GetActivityReportsUTests { completion = Completion.TO_COMPLETE, ), ) - given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) val vessels = listOf( Vessel( @@ -657,7 +776,6 @@ class GetActivityReportsUTests { missionActionsRepository, portRepository, vesselRepository, - fleetSegmentRepository, missionRepository, ).execute( ZonedDateTime.now(), @@ -694,7 +812,7 @@ class GetActivityReportsUTests { userTrigram = "CPAMOI", ), ) - given(missionActionsRepository.findControlsInDates(any(), any())).willReturn(controls) + given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) val vessels = listOf( Vessel( diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionRepositoryITests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionRepositoryITests.kt index 1c1bf75e74..bb3a32883b 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionRepositoryITests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/infrastructure/database/repositories/JpaMissionActionRepositoryITests.kt @@ -301,7 +301,7 @@ class JpaMissionActionRepositoryITests : AbstractDBTests() { val beforeDateTime = ZonedDateTime.now().plusWeeks(1) // When - val controls = jpaMissionActionsRepository.findControlsInDates(beforeDateTime, afterDateTime) + val controls = jpaMissionActionsRepository.findSeaAndLandControlBetweenDates(beforeDateTime, afterDateTime) // Then assertThat(controls).hasSize(1) @@ -318,7 +318,7 @@ class JpaMissionActionRepositoryITests : AbstractDBTests() { val afterDateTime = ZonedDateTime.parse("2020-01-17T00:00:00.000Z") // When - val controls = jpaMissionActionsRepository.findControlsInDates(beforeDateTime, afterDateTime) + val controls = jpaMissionActionsRepository.findSeaAndLandControlBetweenDates(beforeDateTime, afterDateTime) // Then assertThat(controls).hasSize(1) From b43a230db6b3f21b84b8c155af2f8c690e3679d0 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Wed, 29 May 2024 11:49:48 +0200 Subject: [PATCH 5/7] Add missing flag states in tests --- .../actrep/JointDeploymentPlanUTests.kt | 18 +++++++++--------- .../GetActivityReportsUTests.kt | 12 ++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt index 5551bb21f3..b7acddb3a0 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt @@ -36,7 +36,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.FR, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -73,7 +73,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.FR, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -110,7 +110,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.FR, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -147,7 +147,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.GB, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -183,7 +183,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.GB, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -218,7 +218,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.FR, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -253,7 +253,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.FR, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -293,7 +293,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.FR, - userTrigram = "LTH" + userTrigram = "LTH", ) // When @@ -331,7 +331,7 @@ class JointDeploymentPlanUTests { isFromPoseidon = false, completion = Completion.TO_COMPLETE, flagState = CountryCode.FR, - userTrigram = "LTH" + userTrigram = "LTH", ) // When diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt index bc70a02f6e..b0aeee57a8 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReportsUTests.kt @@ -212,6 +212,8 @@ class GetActivityReportsUTests { hasSomeSpeciesSeized = false, isFromPoseidon = false, completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH", ), MissionAction( id = 2, @@ -227,6 +229,8 @@ class GetActivityReportsUTests { hasSomeSpeciesSeized = false, isFromPoseidon = false, completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH", ), MissionAction( id = 3, @@ -242,6 +246,8 @@ class GetActivityReportsUTests { hasSomeSpeciesSeized = false, isFromPoseidon = false, completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH", ), ) given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) @@ -361,6 +367,8 @@ class GetActivityReportsUTests { hasSomeSpeciesSeized = false, isFromPoseidon = false, completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH", ), ) given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) @@ -440,6 +448,8 @@ class GetActivityReportsUTests { hasSomeSpeciesSeized = false, isFromPoseidon = false, completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH", ), ) given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) @@ -512,6 +522,8 @@ class GetActivityReportsUTests { hasSomeSpeciesSeized = false, isFromPoseidon = false, completion = Completion.TO_COMPLETE, + flagState = CountryCode.FR, + userTrigram = "LTH", ), ) given(missionActionsRepository.findSeaAndLandControlBetweenDates(any(), any())).willReturn(controls) From 58857df295d5d42e9476136ada578c73371269f6 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Wed, 29 May 2024 16:36:43 +0200 Subject: [PATCH 6/7] Fix typo --- .../actrep/JointDeploymentPlanUTests.kt | 36 +++++++++++++++++++ .../ExportActivityReportsDialog/csvMap.ts | 2 +- frontend/src/features/ActivityReport/types.ts | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt index b7acddb3a0..9e77611f44 100644 --- a/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt +++ b/backend/src/test/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlanUTests.kt @@ -345,6 +345,42 @@ class JointDeploymentPlanUTests { } } + @ParameterizedTest + @EnumSource(JointDeploymentPlan::class) + fun `getFirstFaoAreaIncludedInJdp Should return the fao area for a LAND control`( + jdp: JointDeploymentPlan, + ) { + // Given + val control = MissionAction( + id = 3, + vesselId = 2, + missionId = 3, + actionDatetimeUtc = ZonedDateTime.now(), + actionType = MissionActionType.LAND_CONTROL, + faoAreas = listOf("27.4.a"), + seizureAndDiversion = false, + speciesOnboard = getSpecies(listOf("JAX", "CRF")), + speciesInfractions = listOf(), + isDeleted = false, + hasSomeGearsSeized = false, + hasSomeSpeciesSeized = false, + isFromPoseidon = false, + completion = Completion.TO_COMPLETE, + flagState = CountryCode.GB, + userTrigram = "LTH", + ) + + // When + val faoArea = jdp.getFirstFaoAreaIncludedInJdp(control) + + // Then + when (jdp) { + JointDeploymentPlan.MEDITERRANEAN_AND_EASTERN_ATLANTIC -> assertThat(faoArea?.faoCode).isNull() + JointDeploymentPlan.NORTH_SEA -> assertThat(faoArea?.faoCode).isEqualTo("27.4.a") + JointDeploymentPlan.WESTERN_WATERS -> assertThat(faoArea?.faoCode).isNull() + } + } + private fun getSpecies(species: List): List { return species.map { val specy = SpeciesControl() diff --git a/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts b/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts index e59e8f3656..25750dc6db 100644 --- a/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts +++ b/frontend/src/features/ActivityReport/components/ExportActivityReportsDialog/csvMap.ts @@ -89,7 +89,7 @@ export const JDP_CSV_MAP_BASE: DownloadAsCsvMap = { }, faoArea: { label: 'FAO_AREA_CODE', - transform: activity => activity.faoCode ?? '' + transform: activity => activity.faoArea ?? '' }, fleetSegment: { label: 'FLEET_SEGMENT', diff --git a/frontend/src/features/ActivityReport/types.ts b/frontend/src/features/ActivityReport/types.ts index e10763716d..f73e83b942 100644 --- a/frontend/src/features/ActivityReport/types.ts +++ b/frontend/src/features/ActivityReport/types.ts @@ -11,7 +11,7 @@ export type ActivityReport = { action: MissionAction.MissionAction activityCode: ActivityCode controlUnits: LegacyControlUnit.LegacyControlUnit[] - faoCode: string | undefined + faoArea: string | undefined segment: string | undefined vessel: Vessel.Vessel vesselNationalIdentifier: string From 217db88099088c4498901e432cfa5137c4ac36f9 Mon Sep 17 00:00:00 2001 From: Loup Theron Date: Wed, 29 May 2024 17:38:02 +0200 Subject: [PATCH 7/7] Refactor usage of sea control filter function --- .../mission_actions/actrep/JointDeploymentPlan.kt | 10 +++++----- .../mission/mission_actions/GetActivityReports.kt | 4 +--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt index 17dd074827..7fb5e319c4 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/entities/mission/mission_actions/actrep/JointDeploymentPlan.kt @@ -54,7 +54,7 @@ enum class JointDeploymentPlan(private val species: List, priv val isThirdCountryVessel = EU_THIRD_COUNTRIES.contains(control.flagState) - val isFirstJdpCurrentJdp = isFirstJdpFound(control) + val isFirstJdpCurrentJdp = isAttributedJdp(control) if (!isFirstJdpCurrentJdp) { return false } @@ -85,7 +85,7 @@ enum class JointDeploymentPlan(private val species: List, priv ): FAOArea? { val jdpFaoAreas = this.getOperationalZones() - if (control.actionType == MissionActionType.SEA_CONTROL && !isFirstJdpFound(control)) { + if (control.actionType == MissionActionType.SEA_CONTROL && !isAttributedJdp(control)) { return null } @@ -104,7 +104,7 @@ enum class JointDeploymentPlan(private val species: List, priv * `JointDeploymentPlan.entries.firstOrNull` is the arbitrary rule to attach a control to only one JDP. * see: https://github.com/MTES-MCT/monitorfish/issues/3157#issuecomment-2093036583 */ - private fun isFirstJdpFound( + fun isAttributedJdp( control: MissionAction, ) = JointDeploymentPlan.entries .firstOrNull { jdpEntry -> @@ -114,7 +114,7 @@ enum class JointDeploymentPlan(private val species: List, priv * `EASTERN_ATLANTIC_OPERATIONAL_ZONES without targeted species in catches. */ if (control.actionType == MissionActionType.SEA_CONTROL && jdpEntry == MEDITERRANEAN_AND_EASTERN_ATLANTIC) { - return@firstOrNull isMedJdp(control) + return@firstOrNull isMedJdpAttributed(control) } return@firstOrNull jdpEntry.getOperationalZones().any { jdpFaoArea -> @@ -124,7 +124,7 @@ enum class JointDeploymentPlan(private val species: List, priv } } == this - private fun isMedJdp( + private fun isMedJdpAttributed( control: MissionAction, ) = MEDITERRANEAN_AND_EASTERN_ATLANTIC.getOperationalZones().any { jdpFaoArea -> /** diff --git a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt index 7aa1a09580..7ea7a9a435 100644 --- a/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt +++ b/backend/src/main/kotlin/fr/gouv/cnsp/monitorfish/domain/use_cases/mission/mission_actions/GetActivityReports.kt @@ -57,9 +57,7 @@ class GetActivityReports( } if (control.faoAreas.isNotEmpty()) { - val foundFaoAreaIncludedInJdp = jdp.getFirstFaoAreaIncludedInJdp(control) - - return@filter isUnderJdp && foundFaoAreaIncludedInJdp != null + return@filter isUnderJdp && jdp.isAttributedJdp(control) } // The mission must be under JDP