4. 基于日志的列车运行模式转换序列重建

任务定位:从事件流到模式序列

车载ATP日志中,模式转换事件通常以离散记录形式散落在不同时间点,单条记录只说明“某时刻进入了某模式”,并不能直接回答“本次运行经历了怎样的模式链、每次转换是否合法”。本案例的任务是把这些离散事件按时间排序、去重、补全上下文,重建一条完整的运行模式转换序列,再逐条对照CTCS等级规范核查转换合法性。

需要区分两个概念:模式(Mode)描述ATP当前的工作状态(如待机、完全监控、部分监控、目视行车、调车、隔离等),CTCS等级(Level)描述地面—车载信息传输体制(CTCS-0/1/2/3/4)。模式转换与等级切换是两类事件,本案例聚焦模式转换序列,等级信息作为上下文一并保留,用于判断某次模式转换在当时的等级下是否被允许。

日志字段与提取口径

假设从例3已得到结构化事件表,本案例直接消费该表。为保持自洽,下面给出本案例所需的最小字段定义与一段可运行的提取、排序、重建代码。示例日志为教学构造数据,不代表任何真实线路记录。

字段含义示例值
ts_ms事件时间戳(毫秒,已按例2完成时钟对齐)1723456800120
src日志来源设备ATP / DMI / BTM
evt事件类型MODE_ENTER / LEVEL_SET
mode进入的模式(MODE_ENTER时有效)FS / OS / PS / SH / CO / SB / IS
level当前CTCS等级0 / 1 / 2 / 3
speed当时速度(km/h)0 / 45 / 120
pos_m当时里程位置(米)128400
seq同毫秒内原始行序号,用于稳定排序3

提取口径有三条硬约束:其一,只保留 evt == "MODE_ENTER" 的记录作为序列节点,LEVEL_SET 作为上下文挂接;其二,排序键为 (ts_ms, seq),避免同一毫秒内多条事件顺序颠倒;其三,连续重复进入同一模式视为冗余记录,只保留首条,但需在重建结果中标记 dup_removed 计数,便于回溯日志质量。

重建实现

import json
from dataclasses import dataclass, field

# 教学构造数据:已按例2完成时钟对齐的结构化事件
RAW = [
    {"ts_ms": 1723456800000, "src": "ATP", "evt": "LEVEL_SET",  "level": 2, "speed": 0,   "pos_m": 128000, "seq": 1},
    {"ts_ms": 1723456800120, "src": "ATP", "evt": "MODE_ENTER", "mode": "SB", "level": 2, "speed": 0,   "pos_m": 128000, "seq": 2},
    {"ts_ms": 1723456815000, "src": "ATP", "evt": "MODE_ENTER", "mode": "CO", "level": 2, "speed": 0,   "pos_m": 128050, "seq": 1},
    {"ts_ms": 1723456830000, "src": "ATP", "evt": "MODE_ENTER", "mode": "FS", "level": 2, "speed": 12,  "pos_m": 128400, "seq": 1},
    {"ts_ms": 1723456900000, "src": "ATP", "evt": "MODE_ENTER", "mode": "FS", "level": 2, "speed": 80,  "pos_m": 131200, "seq": 1},
    {"ts_ms": 1723457010000, "src": "ATP", "evt": "MODE_ENTER", "mode": "OS", "level": 2, "speed": 45,  "pos_m": 136800, "seq": 1},
    {"ts_ms": 1723457050000, "src": "ATP", "evt": "MODE_ENTER", "mode": "FS", "level": 2, "speed": 60,  "pos_m": 138100, "seq": 1},
    {"ts_ms": 1723457200000, "src": "ATP", "evt": "MODE_ENTER", "mode": "SH", "level": 2, "speed": 0,   "pos_m": 141000, "seq": 1},
    {"ts_ms": 1723457260000, "src": "ATP", "evt": "MODE_ENTER", "mode": "CO", "level": 2, "speed": 0,   "pos_m": 141050, "seq": 1},
]

# 规范允许的模式转换(简化教学子集,按CTCS-2常用逻辑整理)
ALLOWED = {
    ("SB", "CO"), ("CO", "FS"), ("FS", "OS"), ("OS", "FS"),
    ("FS", "SH"), ("SH", "CO"), ("CO", "SB"), ("FS", "SB"),
    ("OS", "SH"), ("SH", "FS"),
}

@dataclass
class Node:
    idx: int
    ts_ms: int
    mode: str
    level: int
    speed: int
    pos_m: int
    dup_removed: int = 0
    prev_mode: str = field(default="", init=False)
    legal: bool = field(default=True, init=False)

def rebuild(rows):
    # 1) 稳定排序
    rows = sorted(rows, key=lambda r: (r["ts_ms"], r["seq"]))
    # 2) 挂接等级上下文 + 抽取模式事件
    cur_level = None
    events = []
    for r in rows:
        if r["evt"] == "LEVEL_SET":
            cur_level = r["level"]
        elif r["evt"] == "MODE_ENTER":
            events.append({**r, "level": r.get("level", cur_level)})
    # 3) 去重 + 编号
    seq, last = [], None
    for e in events:
        if last and e["mode"] == last.mode:
            last.dup_removed += 1
            continue
        n = Node(len(seq) + 1, e["ts_ms"], e["mode"], e["level"], e["speed"], e["pos_m"])
        if seq:
            n.prev_mode = seq[-1].mode
            n.legal = (n.prev_mode, n.mode) in ALLOWED
        seq.append(n)
        last = n
    return seq

seq = rebuild(RAW)
for n in seq:
    flag = "OK" if n.legal else "ILLEGAL"
    print(f"{n.idx:02d} {n.ts_ms} {n.prev_mode or '-':>2} -> {n.mode:<2} "
          f"L{n.level} v={n.speed:>3} pos={n.pos_m} dup={n.dup_removed} {flag}")

运行后得到 8 个序列节点(原始 9 条模式事件中,第 5 条与第 4 条同为 FS,被判为冗余并计入 dup_removed)。序列为:SB → CO → FS → OS → FS → SH → CO,其中第 4 条 FS 因去重被合并,实际节点链为 SB→CO→FS→OS→FS→SH→CO。逐条比对 ALLOWED,全部命中允许集合,输出均为 OK。

序列结构与合法性判读

下图把重建结果按时间轴展开,节点内标注模式、等级、速度与位置,节点间箭头标注转换是否落在允许集合内。可以看到两次“FS→OS→FS”的短时降级—恢复过程,以及一次“FS→SH→CO”的调车衔接,这正是仅看单条日志无法直接看出的运行轮廓。

t0 t_end 时间(已对齐,单位 ms) SB L2 · 0km/h 128000m CO L2 · 0km/h 128050m FS L2 · 12km/h 128400m OS L2 · 45km/h 136800m FS L2 · 60km/h 138100m SH L2 · 0km/h 141000m CO L2 · 0km/h 141050m 允许 允许 允许 允许 允许 允许 冗余 FS 记录已合并 dup_removed = 1 图例 FS 完全监控 OS 部分监控 CO 调车 SH 待机

判读要点:序列中每次转换都落在允许集合内,说明本次运行的模式转换逻辑与CTCS-2等级规范一致;若出现 ILLEGAL 标记,则需回到原始日志核查该时刻的等级、速度与应答器信息,判断是真实违规还是提取口径问题(例如等级上下文挂接错误)。此外,dup_removed 大于 0 提示日志存在重复上报,应在数据质量报告中记录,但不影响序列重建结论。

结果校验与常见偏差

  • 排序稳定性:同一毫秒内多条事件必须用 seq 二次排序,否则可能出现“先 FS 后 CO”被颠倒的假违规。
  • 等级上下文:MODE_ENTER 记录若未自带等级字段,需向前查找最近的 LEVEL_SET 挂接;跨等级边界时尤其要检查挂接是否正确。
  • 去重边界:仅合并“连续相同模式”,中间夹有其他模式事件时不得合并,否则会掩盖真实的模式抖动。
  • 允许集合版本:ALLOWED 是教学简化子集,工程中应按实际适用的CTCS等级规范维护,并对不同等级使用不同允许集合。
验证方法:对重建序列做一次“反向回放”——按序列顺序逐条检查 (prev_mode, mode) 是否在允许集合内,并核对每条节点的 level 是否与当时生效等级一致。两项均通过,即可认为序列重建与合法性核查完成。