routes: объём маршрута (volumeMb) — расчёт, хранение в БД и отображение

Объём режима = скорость × длительность, Мбайт: съёмка 120 Мбайт/с (12 ГБ / 100 с), сброс — канал 1200 Мбит/с ÷ 8. Единый источник — ModeDataRates в pcp-types-lib. Хранится колонкой volume_mb (Flyway V10 mission-planing, V9 complex-mission, бэкфилл из duration), заполняется на всех путях построения через @PrePersist/@PreUpdate. DTO отдают вычисляемое read-only поле (та же формула). Фронт pcp-tgu-ops-ui показывает объём в редакторе (инспектор, построенный маршрут) и в сводке комплексного плана.
This commit is contained in:
Дмитрий Соловьев
2026-06-06 18:34:29 +03:00
parent 316e0ee552
commit 2499aebf2d
20 changed files with 190 additions and 13 deletions
@@ -0,0 +1,31 @@
package space.nstart.pcp.pcp_types_lib.dto.satellite.mission
/**
* Скорости накопления/сброса данных для оценки объёма режима.
*
* Объём (`volumeMb`) считается единообразно во всех сервисах в **мегабайтах (Мбайт)** как
* `скорость × длительность`, поэтому константы живут в общей библиотеке типов — один источник
* правды для текущего плана (pcp-ui-service), редактора и комплексного плана (pcp-tgu-ops-ui).
*
* Внимание на единицы: съёмка задана в Мбайт/с, а скорость канала сброса — в Мбит/с,
* поэтому при расчёте объёма сброса скорость приводится к Мбайт делением на 8.
*/
object ModeDataRates {
/**
* Накопление при съёмке — единое для всех КА, **Мбайт/с**.
* Из расчёта 100 с маршрута = 12 ГБ (1 ГБ = 1000 Мбайт): 12000 Мбайт / 100 с = 120 Мбайт/с.
*/
const val SURVEY_MBYTE_PER_SEC: Double = 120.0
/** Сброс — постоянная скорость канала, **Мбит/с**. */
const val DROP_MBIT_PER_SEC: Double = 1200.0
/** Бит в байте — приведение скорости канала (Мбит/с) к объёму памяти (Мбайт). */
private const val BITS_PER_BYTE: Double = 8.0
/** Объём маршрута съёмки, Мбайт, по его длительности (секунды). */
fun surveyVolumeMb(durationSeconds: Double): Double = durationSeconds * SURVEY_MBYTE_PER_SEC
/** Объём сброса, Мбайт, по длительности сеанса (секунды): скорость канала Мбит/с → Мбайт. */
fun dropVolumeMb(durationSeconds: Double): Double = durationSeconds * DROP_MBIT_PER_SEC / BITS_PER_BYTE
}
@@ -1,5 +1,6 @@
package space.nstart.pcp.pcp_types_lib.dto.satellite.mission
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import io.swagger.v3.oas.annotations.media.Schema
@@ -35,6 +36,10 @@ sealed interface ModeResponseDTO {
@get:Schema(description = "Тип режима", allowableValues = ["SURVEY", "DROP"])
val type: String
@get:Schema(description = "Объём режима, Мбайт (накопление съёмки 120 Мбайт/с, сброс — канал 1200 Мбит/с)")
@get:JsonProperty(access = JsonProperty.Access.READ_ONLY)
val volumeMb: Double
}
@Schema(description = "Режим съемки")
@@ -54,7 +59,10 @@ data class SurveyModeResponseDTO(
val roll: Double = 0.0,
@get:Schema(description = "Идентификаторы забронированных слотов")
val bookedSlotIds: List<Long> = emptyList()
) : ModeResponseDTO
) : ModeResponseDTO {
@get:JsonProperty(access = JsonProperty.Access.READ_ONLY)
override val volumeMb: Double get() = ModeDataRates.surveyVolumeMb(duration)
}
@Schema(description = "Режим сброса")
data class DropModeResponseDTO(
@@ -66,4 +74,7 @@ data class DropModeResponseDTO(
val station: String? = "",
val duration: Double = 0.0,
val surveys: List<Long> = emptyList()
) : ModeResponseDTO
) : ModeResponseDTO {
@get:JsonProperty(access = JsonProperty.Access.READ_ONLY)
override val volumeMb: Double get() = ModeDataRates.dropVolumeMb(duration)
}
@@ -1,5 +1,6 @@
package space.nstart.pcp.pcp_types_lib.dto.satellite.mission
import com.fasterxml.jackson.annotation.JsonProperty
import java.time.LocalDateTime
data class SatelliteModeResponseDTO(
@@ -19,4 +20,9 @@ data class SatelliteModeResponseDTO(
val cellNums: List<Long> = emptyList(),
val bookedSlotIds: List<Long> = emptyList(),
val snapshotId: Long? = null
)
) {
/** Объём режима, Мбайт: сброс — канал 1200 Мбит/с (÷8), иначе — накопление съёмки 120 Мбайт/с. */
@get:JsonProperty(access = JsonProperty.Access.READ_ONLY)
val volumeMb: Double
get() = if (type == "DROP") ModeDataRates.dropVolumeMb(duration) else ModeDataRates.surveyVolumeMb(duration)
}
@@ -1,5 +1,6 @@
package space.nstart.pcp.pcp_types_lib.dto.satellite.mission
import com.fasterxml.jackson.annotation.JsonProperty
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.DecimalMin
import java.time.LocalDateTime
@@ -74,4 +75,8 @@ data class SurveyRouteBuildResponseDTO(
val contourWkt: String,
@field:Schema(description = "Идентификатор сохранённого режима съёмки в БД (null — маршрут не сохранялся)")
val surveyModeId: Long? = null
)
) {
@get:Schema(description = "Объём маршрута, Мбайт (накопление съёмки 120 Мбайт/с × длительность)")
@get:JsonProperty(access = JsonProperty.Access.READ_ONLY)
val volumeMb: Double get() = ModeDataRates.surveyVolumeMb(duration)
}
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Test
import kotlin.test.assertTrue
class ModeResponseDTOJacksonTest {
@@ -50,4 +51,26 @@ class ModeResponseDTOJacksonTest {
assertEquals("SURVEY", result[0].type)
assertEquals("DROP", result[1].type)
}
@Test
fun `serializes computed volume and tolerates it on read-back`() {
// Сервис-источник теперь сериализует volumeMb; потребитель (pcp-ui-service)
// десериализует тот же JSON обратно — read-only поле не должно ломать чтение.
val modes: List<ModeResponseDTO> = listOf(
SurveyModeResponseDTO(id = 1, planId = 10, duration = 100.0),
DropModeResponseDTO(id = 2, planId = 10, duration = 5.0)
)
val json = mapper.writeValueAsString(modes)
val tree = mapper.readTree(json)
// Съёмка: 100 с × 120 Мбайт/с = 12000 Мбайт. Сброс: 5 с × 1200 Мбит/с ÷ 8 = 750 Мбайт.
assertEquals(12000.0, tree[0]["volumeMb"].asDouble())
assertEquals(750.0, tree[1]["volumeMb"].asDouble())
val roundTrip: List<ModeResponseDTO> = mapper.readValue(json)
assertEquals(12000.0, roundTrip[0].volumeMb)
assertEquals(750.0, roundTrip[1].volumeMb)
assertTrue(roundTrip[0] is SurveyModeResponseDTO)
}
}