From 29465e21317d6d7a657e92c857ce276cbd867eb4 Mon Sep 17 00:00:00 2001 From: windyboy Date: Mon, 21 Sep 2026 11:44:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(codec):=20FLOP=20=E5=AD=90=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=99=BD=E5=90=8D=E5=8D=95=EF=BC=8C=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E5=A4=96=20STYP=20=E8=90=BD=20Unsupported=EF=BC=88ACM?= =?UTF-8?q?2-84=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - kindOf FLOP 分支仅放行现行七类(US-05 AC1),FDEL 独立分派不受影响 - 白名单外子类型(ACTT/CNCL 等 Q3 闭合目标)解码为 Unsupported,经 ACM2-83 路径记 SKIPPED、不改航班 - 补 codec 白名单回归与处理层「跳过且不触碰航班」用例 --- .../omms/msgexchange/codec/JacksonXmlCodec.kt | 11 ++++++++- .../msgexchange/codec/JacksonXmlCodecTest.kt | 21 +++++++++++++++++ .../processing/IgnoreBranchTest.kt | 23 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodec.kt b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodec.kt index d6af86b..bcfb631 100644 --- a/src/main/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodec.kt +++ b/src/main/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodec.kt @@ -88,6 +88,8 @@ class JacksonXmlCodec : XmlCodec { * 子类型分派必须是**白名单**:未知的 SCHD 子类型不能静默当成全量日计划(DNLD)—— * 那会让一条本该"暂不支持、等人工处置"的报文走最重的整包合并写入路径。 * 空 STYP 按 legacy 约定视为 DNLD(该约定待与 SIS 逐类对拍确认,见 Q3)。 + * FLOP 同样按白名单:仅现行七类(`implementation.md`「动态运行事件」`US-05` AC1) + * 走合并路径,其余 STYP 是 `Q3` 闭合目标,落 `Unsupported`(`FDEL` 单独成类,不参与白名单)。 * 静态参考数据类别码(`implementation.md`「静态参考数据」`SIS:3.1`~`3.14`)单列成 * [MsgKind.RefData]:它们必须走参考数据处理(US-13),不得混入"不支持"跳过。 */ @@ -98,11 +100,18 @@ class JacksonXmlCodec : XmlCodec { "DNLD", "" -> MsgKind.Schd(MsgKind.SchdSubtype.DNLD) else -> MsgKind.Unsupported("SCHD-$styp") } - "FLOP" -> if (styp == "FDEL") MsgKind.Fdel else MsgKind.Flop(styp) + "FLOP" -> when { + styp == "FDEL" -> MsgKind.Fdel + styp in FLOP_SUBTYPES -> MsgKind.Flop(styp) + else -> MsgKind.Unsupported("FLOP-$styp") + } in REF_DATA_TYPES -> MsgKind.RefData(type) else -> MsgKind.Unsupported("$type-$styp") } + /** 现行 FLOP 白名单 7 类(`US-05` AC1);表内其余行是 `Q3` 闭合目标,不是现行白名单。 */ + private val FLOP_SUBTYPES = setOf("ABTM", "DELY", "PSDT", "CKDT", "CLDT", "CHDT", "GTDT") + private val REF_DATA_TYPES = setOf( "COUL", "ARPT", "AIRL", "AIRC", "REGN", "ORGN", "FLTL", "TLST", "GLST", "SLST", "CLST", "BLST", "CHLT", "RSTA", diff --git a/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt b/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt index 8f71ec3..6cc77f3 100644 --- a/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt +++ b/src/test/kotlin/com/gzzn/omms/msgexchange/codec/JacksonXmlCodecTest.kt @@ -250,4 +250,25 @@ class JacksonXmlCodecTest { val unknown = (codec.decode(raw("XYZZ", "TEST")) as DecodeResult.Ok).message assertEquals(MsgKind.Unsupported("XYZZ-TEST"), unknown.kind) } + + @Test + fun `FLOP subtype whitelist sends only the seven current types to FlopProcessor`() { + fun raw(styp: String) = """ + AODB920021010090311 + FLOP$styp + """.trimIndent() + + // 现行白名单 7 类(US-05 AC1) + for (styp in listOf("ABTM", "DELY", "PSDT", "CKDT", "CLDT", "CHDT", "GTDT")) { + assertEquals(MsgKind.Flop(styp), (codec.decode(raw(styp)) as DecodeResult.Ok).message.kind) + } + + // FDEL 独立分派,不参与白名单判定 + assertEquals(MsgKind.Fdel, (codec.decode(raw("FDEL")) as DecodeResult.Ok).message.kind) + + // 白名单外子类型(ACTT/CNCL 是 Q3 闭合目标)与空 STYP 都落 Unsupported,不走合并路径 + assertEquals(MsgKind.Unsupported("FLOP-ACTT"), (codec.decode(raw("ACTT")) as DecodeResult.Ok).message.kind) + assertEquals(MsgKind.Unsupported("FLOP-CNCL"), (codec.decode(raw("CNCL")) as DecodeResult.Ok).message.kind) + assertEquals(MsgKind.Unsupported("FLOP-"), (codec.decode(raw("")) as DecodeResult.Ok).message.kind) + } } diff --git a/src/test/kotlin/com/gzzn/omms/msgexchange/processing/IgnoreBranchTest.kt b/src/test/kotlin/com/gzzn/omms/msgexchange/processing/IgnoreBranchTest.kt index d356de6..2f5e285 100644 --- a/src/test/kotlin/com/gzzn/omms/msgexchange/processing/IgnoreBranchTest.kt +++ b/src/test/kotlin/com/gzzn/omms/msgexchange/processing/IgnoreBranchTest.kt @@ -24,6 +24,7 @@ import com.gzzn.omms.msgexchange.infra.stub.StubSnapshotLog import com.fasterxml.jackson.databind.ObjectMapper import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.time.Clock import java.time.Instant @@ -212,6 +213,28 @@ class IgnoreBranchTest { assertEquals("refdata-pending:ARPT", row.lastError) } + @Test + fun `flop subtype outside whitelist is skipped without touching flights`() { + val proc = StubProcState() + proc.insertIfAbsent(msgId, null) + val inbox = StubInbox() + inbox.raws[msgId] = "" + val msg = DecodedMessage( + meta = MetaFields("AODB", "FLOP", "ACTT", 500L, 1L), + kind = MsgKind.Unsupported("FLOP-ACTT"), + rawXml = "", + ) + val flights = StubFlightState() + val p = processor(proc = proc, inbox = inbox, codec = codecReturning(msg)) + + p.processOne(head()) + + val row = proc.find(msgId)!! + assertEquals(ProcStatus.SKIPPED, row.state) + assertEquals("unsupported:FLOP-ACTT", row.lastError) + assertTrue(flights.snapshots.isEmpty()) // 白名单外子类型不改航班(US-05 AC1) + } + @Test fun `ignore increments counter`() { val proc = StubProcState()