Files
msgexchange-v2/src/main/kotlin/com/gzzn/omms/msgexchange/domain/OperationDay.kt
T
windyboy 99a0f5f738 refactor(flight-state): SCHD 日计划收敛为缺失保留合并语义并对齐现行设计文档
按现行 docs/flight-state.md(111 行版,§1-§7)全面对齐 domain 包及其消费方,
删除整套指向已退役长版文档(§5.x-§10)的引用与死代码:

- 语义:FlightStateEngine.snapshotState 由"完整快照整体替换"改为 §3.1 合并语义
  ——出现 Set/Replace、缺失保留、标量空串显式清空;DELETED 不被日计划恢复(§3.3)。
- 校验:validateMessage 移除从未接线的报文覆盖范围(scope)参数,只保留 §4 步骤 2
  的声明数量/航班标识/运营日推导校验;ScheduleBody 删除 scopeStart/scopeEnd。
- 删除死代码:domain/Decision.kt、FlightModel 的 SnapshotPatch/UpsertOutcome、
  ProcState.isTerminal/archivable、SnapshotFlag.SEQN_REGRESSION、
  ScheduleRecord.seqn(及 wire FlightRecordXml.SEQN);codec 移除未消费 FFID。
- 删除未接线且引用已退役列(fday/last_message_id)的 SqlDialect 方言脚手架,
  oracle11g README 改为按 V1 现列重建的口径。
- 注释/测试:domain、processing、infra 仓储与 jobs/delivery、配置类及对应测试的
  KDoc 章节引用全部对齐现行 flight-state.md/design.md;FlightStateEngineTest
  重写为合并语义(62/62 通过)。

V1__flight_state_baseline.sql 保留原样(内容注释仍带旧章节号,改动会破坏
已应用迁移的 Flyway checksum,待重建基线或 V2 净迁移时收敛)。
2026-09-09 22:17:04 +08:00

51 lines
2.2 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.gzzn.omms.msgexchange.domain
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
/**
* 运营日计算(docs/flight-state.md §2.1):由计划运行时间字段 SODTddMMMyyHHmm
* 与机场时区按切日边界推导;OPERATION_DAY 不是消息接收日或落库日,一经确定不可变。
* 切日边界由 msgx.operation-day.cutoff-hour 配置(默认 0 = 自然日零点,业务口径待确认)。
*/
class OperationDayCalculator(
zone: ZoneId,
cutoffHour: Int,
) {
private val zone: ZoneId = zone
/** 切日边界:SODT 本地时刻早于该小时的归属前一运营日(0–23,越界收敛到边界值)。 */
private val cutoffHour: Int = cutoffHour.coerceIn(0, 23)
/** SODT → 运营日;输入 null/空/非法返回 null(不抛异常,调用方按"运营日不可计算"处理)。 */
fun compute(sodt: String?): LocalDate? {
if (sodt.isNullOrBlank()) return null
val local = parseSodt(sodt.trim()) ?: return null
val day = local.atZone(zone).toLocalDate()
return if (local.hour < cutoffHour) day.minusDays(1) else day
}
companion object {
/**
* SIS SODT 线格式:ddMMMyyHHmm(如 15DEC031723),月份英文三字母、大小写不敏感。
* 两位年显式按 2000 基准展开(java.time 的 yy reduced-value 解析跨实现不一致,
* 显式展开保证 AODB 侧年份窗口唯一口径;基准年待真实报文验收确认)。
*/
private val SODT_REGEX = Regex("(\\d{1,2})([A-Za-z]{3})(\\d{2})(\\d{2})(\\d{2})")
private val MONTHS = mapOf(
"JAN" to 1, "FEB" to 2, "MAR" to 3, "APR" to 4, "MAY" to 5, "JUN" to 6,
"JUL" to 7, "AUG" to 8, "SEP" to 9, "OCT" to 10, "NOV" to 11, "DEC" to 12,
)
internal fun parseSodt(raw: String): LocalDateTime? {
val m = SODT_REGEX.matchEntire(raw.uppercase()) ?: return null
val (d, mon, yy, hh, mm) = m.destructured
val month = MONTHS[mon] ?: return null
return runCatching {
LocalDateTime.of(2000 + yy.toInt(), month, d.toInt(), hh.toInt(), mm.toInt())
}.getOrNull()
}
}
}