feat(slots-service): параметр all у request-cover — возврат занятых бронью (BUSY) слотов

«Показать все слоты» должно показывать полный набор. При all=true reqCover
запрашивает статусы [AVAILABLE, BOOKED, BUSY], а selectCoverageSlots собирает
BUSY отдельно и добавляет в ответ только в ветке без покрытия — в solver
(«Подобрать покрытие») занятые слоты по-прежнему не попадают.
This commit is contained in:
Дмитрий Соловьев
2026-06-15 16:45:14 +03:00
parent bffb8e47b7
commit 052ed5f04b
2 changed files with 31 additions and 10 deletions
@@ -103,8 +103,10 @@ class SlotController {
@RequestParam("sun") sun : Double?, @RequestParam("sun") sun : Double?,
@Parameter(description = "Вариант расчета покрытия") @Parameter(description = "Вариант расчета покрытия")
@RequestParam("coverageStrategy", required = false) coverageStrategy: SlotCoverageStrategy?, @RequestParam("coverageStrategy", required = false) coverageStrategy: SlotCoverageStrategy?,
@Parameter(description = "Вернуть и занятые бронью слоты (BUSY) — для обзора; по умолчанию только доступные/забронированные")
@RequestParam("all", required = false) all: Boolean?,
) : List<SlotDTO> ) : List<SlotDTO>
= slotService.reqCover(requestId, timeStart, timeStop, sign, cov, satellites, sun, coverageStrategy).toList() = slotService.reqCover(requestId, timeStart, timeStop, sign, cov, satellites, sun, coverageStrategy, all == true).toList()
@GetMapping("interval") @GetMapping("interval")
@Operation(summary = "Получение всех слотов на заданном интервале времени") @Operation(summary = "Получение всех слотов на заданном интервале времени")
@@ -1216,7 +1216,8 @@ class SlotService {
states: List<SlotStatus>? = null, states: List<SlotStatus>? = null,
sats : List<Long>? = null, sats : List<Long>? = null,
sun : Double? = null, sun : Double? = null,
coverageStrategy: SlotCoverageStrategy? = SlotCoverageStrategy.GREEDY coverageStrategy: SlotCoverageStrategy? = SlotCoverageStrategy.GREEDY,
includeBusy: Boolean = false
): Iterable<SlotDTO> { ): Iterable<SlotDTO> {
val target = buildCoverageTargets( val target = buildCoverageTargets(
items = listOf(SlotCoverageTargetDTO(targetId = 1, wkt = wkt)) items = listOf(SlotCoverageTargetDTO(targetId = 1, wkt = wkt))
@@ -1235,7 +1236,8 @@ class SlotService {
batchContext = batchContext, batchContext = batchContext,
cov = cov, cov = cov,
sun = sun, sun = sun,
coverageStrategy = coverageStrategy coverageStrategy = coverageStrategy,
includeBusy = includeBusy
) )
logger.info( logger.info(
"polySlots finish: targetId={}, candidateSlots={}, sunFilteredSlots={}, returnedSlots={}, sun={}", "polySlots finish: targetId={}, candidateSlots={}, sunFilteredSlots={}, returnedSlots={}, sun={}",
@@ -1668,10 +1670,14 @@ class SlotService {
batchContext: CoverageBatchContext, batchContext: CoverageBatchContext,
cov: Boolean?, cov: Boolean?,
sun: Double?, sun: Double?,
coverageStrategy: SlotCoverageStrategy? coverageStrategy: SlotCoverageStrategy?,
// true — добавить в результат и занятые бронью слоты (BUSY) для обзора. В расчёт покрытия
// (solver) они не попадают: selectedSlots остаётся только из AVAILABLE/BOOKED.
includeBusy: Boolean = false
): CoverageSelectionResult { ): CoverageSelectionResult {
val totalStartedAtMs = System.currentTimeMillis() val totalStartedAtMs = System.currentTimeMillis()
val selectedSlots = mutableListOf<SlotDTO>() val selectedSlots = mutableListOf<SlotDTO>()
val busySlots = mutableListOf<SlotDTO>()
val targetSlotIdsBySatelliteId = batchContext.rawSlotIdsByTargetIdAndSatelliteId[target.targetId].orEmpty() val targetSlotIdsBySatelliteId = batchContext.rawSlotIdsByTargetIdAndSatelliteId[target.targetId].orEmpty()
val sourceSlotIds = targetSlotIdsBySatelliteId.values.sumOf { it.size } val sourceSlotIds = targetSlotIdsBySatelliteId.values.sumOf { it.size }
var preparedSlotsBeforeOccupiedFilter = 0 var preparedSlotsBeforeOccupiedFilter = 0
@@ -1710,6 +1716,12 @@ class SlotService {
val sunFilterResult = filterBySunAngle(filteredSlots, sun) val sunFilterResult = filterBySunAngle(filteredSlots, sun)
sunFilteredSlots += sunFilterResult.filteredCount sunFilteredSlots += sunFilterResult.filteredCount
selectedSlots.addAll(sunFilterResult.slots) selectedSlots.addAll(sunFilterResult.slots)
// «Показать все слоты»: добавляем и занятые бронью (BUSY) для обзора — тот же
// sun-фильтр, чтобы не показывать заведомо непригодные по Солнцу.
if (includeBusy) {
val busyPrepared = preparedSlotsIncludingBlocked.filter { it.state == SlotStatus.BUSY }
busySlots.addAll(filterBySunAngle(busyPrepared, sun).slots)
}
} }
} }
logger.debug( logger.debug(
@@ -1815,15 +1827,17 @@ class SlotService {
candidateSelectionMs, candidateSelectionMs,
totalMs totalMs
) )
// Без покрытия («Показать все слоты»): при includeBusy возвращаем и занятые бронью слоты.
val resultSlots = if (includeBusy) selectedSlots + busySlots else selectedSlots
return CoverageSelectionResult( return CoverageSelectionResult(
slots = selectedSlots, slots = resultSlots,
sourceSlotIds = sourceSlotIds, sourceSlotIds = sourceSlotIds,
preparedSlotsBeforeOccupiedFilter = preparedSlotsBeforeOccupiedFilter, preparedSlotsBeforeOccupiedFilter = preparedSlotsBeforeOccupiedFilter,
occupiedFilteredSlots = occupiedFilteredSlots, occupiedFilteredSlots = occupiedFilteredSlots,
candidateSlotsBeforeSunFilter = candidateSlotsBeforeSunFilter, candidateSlotsBeforeSunFilter = candidateSlotsBeforeSunFilter,
sunFilteredSlots = sunFilteredSlots, sunFilteredSlots = sunFilteredSlots,
solverInputSlots = selectedSlots.size, solverInputSlots = selectedSlots.size,
solverOutputSlots = selectedSlots.size, solverOutputSlots = resultSlots.size,
candidateSelectionDurationMs = candidateSelectionMs, candidateSelectionDurationMs = candidateSelectionMs,
solverDurationMs = 0L, solverDurationMs = 0L,
totalDurationMs = totalMs totalDurationMs = totalMs
@@ -1870,9 +1884,12 @@ class SlotService {
cov: Boolean?, cov: Boolean?,
sats : List<Long>? = null, sats : List<Long>? = null,
sun : Double? = null, sun : Double? = null,
coverageStrategy: SlotCoverageStrategy? = SlotCoverageStrategy.GREEDY coverageStrategy: SlotCoverageStrategy? = SlotCoverageStrategy.GREEDY,
// true — вернуть и занятые бронью слоты (BUSY) для обзора («Показать все слоты»).
// Бронировать их нельзя, в расчёт покрытия они не идут.
includeBusy: Boolean = false
): Iterable<SlotDTO> { ): Iterable<SlotDTO> {
logger.info("Запрос покрытия заявки $id") logger.info("Запрос покрытия заявки $id (includeBusy=$includeBusy)")
val req = earthService.reqcells(id)?.request?.contour ?: throw CustomErrorException("Нет такой заявки!!!!") val req = earthService.reqcells(id)?.request?.contour ?: throw CustomErrorException("Нет такой заявки!!!!")
logger.info("Получена заявка : $req") logger.info("Получена заявка : $req")
return polySlots( return polySlots(
@@ -1881,10 +1898,12 @@ class SlotService {
timeStop, timeStop,
sign, sign,
cov, cov,
listOf(SlotStatus.AVAILABLE, SlotStatus.BOOKED), if (includeBusy) listOf(SlotStatus.AVAILABLE, SlotStatus.BOOKED, SlotStatus.BUSY)
else listOf(SlotStatus.AVAILABLE, SlotStatus.BOOKED),
sats, sats,
sun, sun,
coverageStrategy coverageStrategy,
includeBusy
) )
} }