pcp-mission-planing: Ф4 — валидация-заглушка POST /missions/{id}/validate (контракт конфликтов, пустой diff)
This commit is contained in:
@@ -262,8 +262,13 @@ P2 = DRAFT (base_plan_id=P0, окно=W)
|
|||||||
- **Приёмка:** правка/удаление затрагивают только `DRAFT`; живая миссия неизменна; контур пересчитан корректно; каскады при `DELETE` (связь `drop_mode ↔ survey`) целы.
|
- **Приёмка:** правка/удаление затрагивают только `DRAFT`; живая миссия неизменна; контур пересчитан корректно; каскады при `DELETE` (связь `drop_mode ↔ survey`) целы.
|
||||||
- **Тесты:** PATCH меняет контур; DELETE с каскадами.
|
- **Тесты:** PATCH меняет контур; DELETE с каскадами.
|
||||||
|
|
||||||
### Фаза 4 — Валидация-заглушка
|
### Фаза 4 — Валидация-заглушка — ✅ СДЕЛАНО
|
||||||
**Модель: Sonnet.**
|
**Модель: Sonnet.**
|
||||||
|
|
||||||
|
> **Реализовано:**
|
||||||
|
> - `POST /api/missions/{id}/validate` в [MissionController](services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/controller/MissionController.kt) → [validateMission](services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/service/MissionPlaningService.kt): проверяет существование миссии (404, если нет) и возвращает корректный контракт с **пустым** списком конфликтов. Реальные правила — TODO раздела 6.
|
||||||
|
> - Контракт `MissionValidationResponse { missionId, conflicts: [ModeConflict] }`, где `ModeConflict { modeId, rule, severity, message, relatedModeId? }` ([MissionValidationDTOs.kt](services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/controller/MissionValidationDTOs.kt)). Коды правил (`ConflictRule`: TIME_OVERLAP/TARGET_VISIBILITY/COMMS_SESSION/DROP_ORDER/ONBOARD_LIMIT) и уровни (`ConflictSeverity`: error/warning) заведены сразу как стабильная схема — появление реальных правил не сломает фронт. Дифф по режимам (а не весь план).
|
||||||
|
> - Тесты: 2 кейса в `MissionEditModesTest` (валидация существующей миссии → пустой список; неизвестной → 404). Весь модуль зелёный.
|
||||||
- `pcp-mission-planing-service`: `POST /api/missions/{id}/validate` → корректный контракт, пустой/тривиальный список конфликтов. Реальные правила — TODO раздела 6.
|
- `pcp-mission-planing-service`: `POST /api/missions/{id}/validate` → корректный контракт, пустой/тривиальный список конфликтов. Реальные правила — TODO раздела 6.
|
||||||
- **Приёмка:** `200` с пустым diff; контракт стабилен для фронта.
|
- **Приёмка:** `200` с пустым diff; контракт стабилен для фронта.
|
||||||
|
|
||||||
|
|||||||
+19
@@ -252,6 +252,25 @@ class MissionController(
|
|||||||
return ResponseEntity.noContent().build()
|
return ResponseEntity.noContent().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Проверить миссию-черновик на конфликты",
|
||||||
|
description = "Считает конфликты/наложения включений плана-черновика и возвращает дифф по " +
|
||||||
|
"режимам. MVP: валидатор-заглушка — контракт корректен, список конфликтов пуст; " +
|
||||||
|
"реальные правила (пересечения по времени, видимость цели, ЗРВ, порядок сброса, борт) — " +
|
||||||
|
"отдельной задачей."
|
||||||
|
)
|
||||||
|
@ApiResponses(
|
||||||
|
value = [
|
||||||
|
ApiResponse(responseCode = "200", description = "Валидация выполнена, конфликты в теле ответа"),
|
||||||
|
ApiResponse(responseCode = "404", description = "Миссия не найдена")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
@PostMapping("/{mission_id}/validate")
|
||||||
|
fun validateMission(
|
||||||
|
@Parameter(description = "Идентификатор миссии-черновика")
|
||||||
|
@PathVariable("mission_id") id: UUID
|
||||||
|
): MissionValidationResponse = missionPlaningService.validateMission(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Operation(summary = "Создать миссию")
|
@Operation(summary = "Создать миссию")
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package space.nstart.pcp.pcp_mission_planing_service.controller
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Коды правил валидации. MVP: валидатор-заглушка их **не вычисляет** — список существует как
|
||||||
|
* стабильный контракт для фронта, чтобы появление реальных правил (раздел 6 дизайн-дока) не ломало схему.
|
||||||
|
*/
|
||||||
|
object ConflictRule {
|
||||||
|
/** Пересечение по времени между включениями (съёмка↔съёмка, съёмка↔сброс, сброс↔сброс). */
|
||||||
|
const val TIME_OVERLAP = "TIME_OVERLAP"
|
||||||
|
/** Окно съёмки вне окна видимости цели (баллистика). */
|
||||||
|
const val TARGET_VISIBILITY = "TARGET_VISIBILITY"
|
||||||
|
/** Окно сброса вне зоны радиовидимости станции. */
|
||||||
|
const val COMMS_SESSION = "COMMS_SESSION"
|
||||||
|
/** Сбрасываемая съёмка не завершается строго до начала зоны сброса. */
|
||||||
|
const val DROP_ORDER = "DROP_ORDER"
|
||||||
|
/** Бортовые ограничения: длительность/скважность, энергетика, память. */
|
||||||
|
const val ONBOARD_LIMIT = "ONBOARD_LIMIT"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Уровень конфликта: [ERROR] блокирует «Применить», [WARNING] — информативный. */
|
||||||
|
object ConflictSeverity {
|
||||||
|
const val ERROR = "error"
|
||||||
|
const val WARNING = "warning"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Один конфликт на затронутом включении. Возвращается диффом по режимам (а не как весь план),
|
||||||
|
* чтобы фронт мог точечно подсвечивать проблемные включения.
|
||||||
|
*
|
||||||
|
* @param modeId id режима (survey или drop), к которому относится конфликт.
|
||||||
|
* @param rule код правила, см. [ConflictRule].
|
||||||
|
* @param severity уровень, см. [ConflictSeverity].
|
||||||
|
* @param message человекочитаемое описание для оператора.
|
||||||
|
* @param relatedModeId id второго участника парного правила (например, пересечения по времени), если есть.
|
||||||
|
*/
|
||||||
|
data class ModeConflict(
|
||||||
|
val modeId: Long,
|
||||||
|
val rule: String,
|
||||||
|
val severity: String,
|
||||||
|
val message: String,
|
||||||
|
val relatedModeId: Long? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Результат валидации миссии-черновика.
|
||||||
|
*
|
||||||
|
* MVP: [conflicts] всегда пуст — реальные правила ещё не считаются (TODO раздела 6 дизайн-дока).
|
||||||
|
* Контракт стабилен: фронт уже завязывается на него и не сломается, когда правила появятся.
|
||||||
|
*/
|
||||||
|
data class MissionValidationResponse(
|
||||||
|
val missionId: UUID,
|
||||||
|
val conflicts: List<ModeConflict>
|
||||||
|
)
|
||||||
+16
@@ -32,6 +32,7 @@ import space.nstart.pcp.pcp_types_lib.dto.satellite.mission.SurveyRouteBuildResp
|
|||||||
import space.nstart.pcp.pcp_mission_planing_service.controller.CloneMode
|
import space.nstart.pcp.pcp_mission_planing_service.controller.CloneMode
|
||||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneRequest
|
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneRequest
|
||||||
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneResponse
|
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionCloneResponse
|
||||||
|
import space.nstart.pcp.pcp_mission_planing_service.controller.MissionValidationResponse
|
||||||
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeEntity
|
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeEntity
|
||||||
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeSurveyEntity
|
import space.nstart.pcp.pcp_mission_planing_service.entity.DropModeSurveyEntity
|
||||||
import space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity
|
import space.nstart.pcp.pcp_mission_planing_service.entity.MissionEntity
|
||||||
@@ -910,6 +911,21 @@ class MissionPlaningService(
|
|||||||
logger.info("Удалён сброс (drop_mode id={}), снято связей со съёмками={}", dropId, links.size)
|
logger.info("Удалён сброс (drop_mode id={}), снято связей со съёмками={}", dropId, links.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Валидация миссии-черновика на конфликты/наложения включений.
|
||||||
|
*
|
||||||
|
* MVP-заглушка: проверяет существование миссии (404, если нет) и возвращает корректный контракт
|
||||||
|
* с **пустым** списком конфликтов. Реальные правила (пересечения по времени, видимость цели, ЗРВ
|
||||||
|
* станции, порядок сброса, бортовые ограничения) — TODO раздела 6 дизайн-дока. Контракт
|
||||||
|
* ([MissionValidationResponse]/[ModeConflict]) стабилен, чтобы фронт не ломался при их появлении.
|
||||||
|
*/
|
||||||
|
fun validateMission(missionId: UUID): MissionValidationResponse {
|
||||||
|
if (!missionRepository.existsById(missionId)) {
|
||||||
|
throw notFound(missionId)
|
||||||
|
}
|
||||||
|
return MissionValidationResponse(missionId = missionId, conflicts = emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Строит контур полосы съёмки по параметрам видимости точки.
|
* Строит контур полосы съёмки по параметрам видимости точки.
|
||||||
* Чистый расчёт без персистенции; переиспользуется [buildSurveyRoute] и [saveSurveyRoute].
|
* Чистый расчёт без персистенции; переиспользуется [buildSurveyRoute] и [saveSurveyRoute].
|
||||||
|
|||||||
+19
@@ -199,6 +199,25 @@ class MissionEditModesTest {
|
|||||||
assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
|
assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- validate (заглушка) ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `validate of an existing mission returns an empty conflict list`() {
|
||||||
|
val missionId = UUID.randomUUID()
|
||||||
|
`when`(missionRepository.existsById(missionId)).thenReturn(true)
|
||||||
|
|
||||||
|
val result = service.validateMission(missionId)
|
||||||
|
|
||||||
|
assertEquals(missionId, result.missionId)
|
||||||
|
assertTrue(result.conflicts.isEmpty(), "MVP-заглушка не считает конфликтов")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `validate of a missing mission yields 404`() {
|
||||||
|
val ex = assertThrows<ResponseStatusException> { service.validateMission(UUID.randomUUID()) }
|
||||||
|
assertEquals(HttpStatus.NOT_FOUND, ex.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
// --- helpers ---
|
// --- helpers ---
|
||||||
|
|
||||||
private fun survey(): SurveyModeEntity {
|
private fun survey(): SurveyModeEntity {
|
||||||
|
|||||||
Reference in New Issue
Block a user