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”的调车衔接,这正是仅看单条日志无法直接看出的运行轮廓。
判读要点:序列中每次转换都落在允许集合内,说明本次运行的模式转换逻辑与CTCS-2等级规范一致;若出现 ILLEGAL 标记,则需回到原始日志核查该时刻的等级、速度与应答器信息,判断是真实违规还是提取口径问题(例如等级上下文挂接错误)。此外,dup_removed 大于 0 提示日志存在重复上报,应在数据质量报告中记录,但不影响序列重建结论。
结果校验与常见偏差
- 排序稳定性:同一毫秒内多条事件必须用
seq二次排序,否则可能出现“先 FS 后 CO”被颠倒的假违规。 - 等级上下文:
MODE_ENTER记录若未自带等级字段,需向前查找最近的LEVEL_SET挂接;跨等级边界时尤其要检查挂接是否正确。 - 去重边界:仅合并“连续相同模式”,中间夹有其他模式事件时不得合并,否则会掩盖真实的模式抖动。
- 允许集合版本:
ALLOWED是教学简化子集,工程中应按实际适用的CTCS等级规范维护,并对不同等级使用不同允许集合。
(prev_mode, mode) 是否在允许集合内,并核对每条节点的 level 是否与当时生效等级一致。两项均通过,即可认为序列重建与合法性核查完成。