TLA v1.0.3 实施任务清单(逐文件·逐函数)
| 项 | 内容 |
|---|---|
| 版本 | v1.0 |
| 日期 | 2026-06-01 |
| 对应方案 | TLA_IMPROVEMENT_PLAN_V2.md |
| 执行方式 | 按 Phase 顺序,每个 Task 独立可验证,完成后打 ✅ |
总览
Phase 0 (P0 · 数据正确性) ≈ 1-2 天 5 个 Task
Phase 1 (P1 · 分级报表) ≈ 3-5 天 7 个 Task
Phase 2 (P2 · 可维护性) ≈ 按需 3 个 Task
验证 (Verify) 贯穿全程 4 个 SQL 验收查询
Phase 0 — 数据正确性(立即)
目标:板体失控三条全部落地、0x400000 中性命名、版本 bump 触发重算
Task 0-A esc_faults.py — 码表命名修正 + P0 最小扩充
文件 trajectory-log-analyzer/tla/esc_faults.py
改动清单(当前 → 目标):
| 当前 | 修改为 | 原因 |
|---|---|---|
"ESC_FAULT_OVERVOLTAGE_PROTECTION": 0x400000 |
"ESC_EVENT_0x400000": 0x400000 |
语义未确认,中性命名 |
"ECU_FAULT_UNKNOWN_0x40000000": 0x40000000 |
"FAULT_ESC_COMMUNICATION": 0x40000000 |
与协议常量名对齐 |
"ECU_FAULT_UNKNOWN_0x80000000": 0x80000000 |
"FAULT_BATTERY_COMMUNICATION": 0x80000000 |
同上 |
"ECU_FAULT_UNKNOWN_0x8000000": 0x8000000 |
"FAULT_FLASH_COMMUNICATION": 0x8000000 |
同上 |
"BMS_FAULT_0x20": 0x20 |
"BMS_LOW_SOC_WARNING_20": 0x20 |
使用协议名 |
"BMS_FAULT_0x10": 0x10 |
"BMS_LOW_SOC_WARNING_10": 0x10 |
同上 |
"BMS_FAULT_0x1000": 0x1000 |
"BMS_TOTAL_VOLTAGE_LOW": 0x1000 |
同上 |
"BMS_FAULT_0x400": 0x400 |
"BMS_CELL_VOLTAGE_LOW": 0x400 |
同上 |
新增(P0 必须):
# PRIMARY_ESC_FAULTS 更新(两处 board_out_of_control)
PRIMARY_ESC_FAULTS = (
"ESC_FAULT_STALL_PROTECTION",
"ESC_EVENT_0x400000", # 原 ESC_FAULT_OVERVOLTAGE_PROTECTION
)
新增 severity 字典(P0 最小集,供 session_analyzer 读取):
# 每条码的分级(fault / warning / notice)
ESC_SEVERITY: dict[str, str] = {
"ESC_FAULT_STALL_PROTECTION": "warning",
"ESC_EVENT_0x400000": "warning", # TBD
"ESC_FAULT_UNKNOWN_0x800000": "warning",
"ESC_FAULT_UNKNOWN_0x1000000": "warning",
"ESC_FAULT_UNKNOWN_0x2000000": "warning",
"ESC_FAULT_UNKNOWN_0x4": "fault",
}
ECU_SEVERITY: dict[str, str] = {
"FAULT_ESC_COMMUNICATION": "fault",
"FAULT_BATTERY_COMMUNICATION": "fault",
"FAULT_FLASH_COMMUNICATION": "fault",
}
BMS_SEVERITY: dict[str, str] = {
"BMS_LOW_SOC_WARNING_20": "notice",
"BMS_LOW_SOC_WARNING_10": "warning",
"BMS_TOTAL_VOLTAGE_LOW": "notice",
"BMS_CELL_VOLTAGE_LOW": "notice",
}
# 参与 board_out_of_control 合成的 ESC 码
ESC_BOARD_OUT_OF_CONTROL: frozenset[str] = frozenset({
"ESC_FAULT_STALL_PROTECTION",
"ESC_EVENT_0x400000",
})
验证:
python -c "from tla.esc_faults import ESC_FAULTS, ESC_BOARD_OUT_OF_CONTROL; print(ESC_FAULTS); print(ESC_BOARD_OUT_OF_CONTROL)"
Task 0-B session_analyzer.py — 新增三个字段 + 修正 has_fault
文件 trajectory-log-analyzer/tla/session_analyzer.py
第 1 步: 改版本号(第 17 行)
# 改前
ANALYZER_VERSION = "1.0.2"
# 改后
ANALYZER_VERSION = "1.0.3"
第 2 步: analyze_trajectory_row() 中新增两个辅助函数(文件顶部)
def is_invalid_motor_speed(motor_speed) -> bool:
"""|motorSpeed| >= 20000 (0.1 RPM单位,固件阈值),排除 None。"""
if motor_speed is None:
return False
v = int(motor_speed)
return v <= -20000 or v >= 20000
第 3 步: analyze_trajectory_row() 函数体内,在 corrupt_cnt 计算之后新增:
# ── P0 新增:invalid_speed_points ─────────────────────────────────────
invalid_speed_cnt = sum(
1 for p in tl
if not is_corrupt_motor_speed(p.get("motorSpeed")) # 排除脏包,避免重叠计数
and is_invalid_motor_speed(p.get("motorSpeed"))
)
# ── P0 新增:board_out_of_control ─────────────────────────────────────
stall_cnt = rep.esc_fault_points.get("ESC_FAULT_STALL_PROTECTION", 0)
esc_0x400000_cnt = rep.esc_fault_points.get("ESC_EVENT_0x400000", 0)
has_board_out_of_control = bool(
stall_cnt > 0
or esc_0x400000_cnt > 0
or invalid_speed_cnt > 0
)
第 4 步: has_fault 重定义(原第 66-71 行位置),改为:
# v1.0.3: has_fault 收口 = 协议故障 OR 板体失控
# 旧: severity in (high,critical) OR 任意码命中 → 过宽(BMS 低电触发)
has_fault = (
rep.severity in ("high", "critical") # 保留评分路径(stall主导会话)
or bool(rep.esc_fault_points) # ESC 任意码(含 warning 级堵转)
or bool(rep.ecu_fault_points) # ECU 通信故障
or has_board_out_of_control # 新增:失控合成
)
# 注意: BMS 单独触发不再构成 has_fault(只计入 has_warning, 见 P1)
第 5 步: return dict 中新增三个字段(追加到现有字段之后):
# P0 新增字段
"invalid_speed_points": invalid_speed_cnt,
"has_board_out_of_control": 1 if has_board_out_of_control else 0,
# 向后兼容:overvoltage_points 保留同值双写
"esc_event_0x400000_points": esc_0x400000_cnt,
"overvoltage_points": esc_0x400000_cnt, # 与原字段同名,值相同
注意: overvoltage_ratio 也需同步更新(原来引用 ESC_FAULT_OVERVOLTAGE_PROTECTION,现在改为 ESC_EVENT_0x400000):
# 改前
"overvoltage_ratio": rep.esc_fault_ratio.get("ESC_FAULT_OVERVOLTAGE_PROTECTION", 0),
# 改后
"overvoltage_ratio": rep.esc_fault_ratio.get("ESC_EVENT_0x400000", 0),
第 6 步: _count_stall_thr_mismatch() 不需要改(仍用 0x20000 bitmask,与名字无关)。
验证:
cd trajectory-log-analyzer
python -c "
from tla.session_analyzer import ANALYZER_VERSION, is_invalid_motor_speed
print('version:', ANALYZER_VERSION)
assert is_invalid_motor_speed(20000) == True
assert is_invalid_motor_speed(-20001) == True
assert is_invalid_motor_speed(19999) == False
assert is_invalid_motor_speed(None) == False
print('is_invalid_motor_speed: OK')
"
Task 0-C fault_engine.py — 字段名同步
文件 trajectory-log-analyzer/tla/fault_engine.py
fault_engine.py 内部通过 rep.esc_fault_points.get("ESC_FAULT_OVERVOLTAGE_PROTECTION", 0) 取值(第 171 行),需同步改名:
# 改前(第 171 行)
ov_cnt = rep.esc_fault_points.get("ESC_FAULT_OVERVOLTAGE_PROTECTION", 0)
# 改后
ov_cnt = rep.esc_fault_points.get("ESC_EVENT_0x400000", 0)
同步更新 symptoms 输出(第 174 行):
# 改前
rep.symptoms.append(f"overvoltage:{ov_cnt}/{n}")
# 改后(保持可读)
rep.symptoms.append(f"esc_0x400000:{ov_cnt}/{n}")
第 176 行 symptom 名同步:
# 改前
rep.symptoms.append(f"overvoltage_under_load:{ov_under_load}")
# 改后
rep.symptoms.append(f"esc_0x400000_under_load:{ov_under_load}")
验证:
python -c "
from tla.esc_faults import ESC_FAULTS
assert 'ESC_EVENT_0x400000' in ESC_FAULTS
assert 'ESC_FAULT_OVERVOLTAGE_PROTECTION' not in ESC_FAULTS
print('fault_engine name sync: OK')
"
Task 0-D 迁移 SQL — P0 新增列
新建文件 trajectory-log-analyzer/sql/migrations/20260601_tla_v1_0_3_p0.sql
-- TLA v1.0.3 P0 迁移:新增板体失控与中性命名字段
-- 执行前请备份:mysqldump surf-dev surf_motion_trajectory_analysis > backup_before_1_0_3.sql
-- 执行方式:mysql -h HOST -u USER -p surf-dev < sql/migrations/20260601_tla_v1_0_3_p0.sql
SET NAMES utf8mb4;
-- 1. 主表新增 4 列(DEFAULT 安全,不阻塞读)
ALTER TABLE surf_motion_trajectory_analysis
ADD COLUMN `invalid_speed_points` INT NOT NULL DEFAULT 0
COMMENT '|motorSpeed|>=20000 的轨迹点数(固件异常转速阈值,0.1RPM单位)'
AFTER `stall_thr_motor_mismatch`,
ADD COLUMN `has_board_out_of_control` TINYINT(1) NOT NULL DEFAULT 0
COMMENT '板体失控:堵转(0x20000) OR ESC_0x400000 OR 超范围转速'
AFTER `invalid_speed_points`,
ADD COLUMN `esc_event_0x400000_points` INT NOT NULL DEFAULT 0
COMMENT 'escEventCode & 0x400000 点数(原 overvoltage_points 同义中性名)'
AFTER `overvoltage_ratio`;
-- 2. 新增索引(可选,视查询频率决定)
ALTER TABLE surf_motion_trajectory_analysis
ADD KEY `idx_board_out_of_control` (`has_board_out_of_control`);
-- 3. 验证列已加入
SELECT COLUMN_NAME, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_NAME = 'surf_motion_trajectory_analysis'
AND COLUMN_NAME IN ('invalid_speed_points','has_board_out_of_control','esc_event_0x400000_points')
ORDER BY ORDINAL_POSITION;
执行顺序: 1. 先执行 SQL(新列 DEFAULT 安全,线上无需停服) 2. 再部署新代码(此时新字段才有非零值写入)
Task 0-E 本地端到端验证
用本地测试数据跑一遍,确认新字段输出正确:
cd trajectory-log-analyzer
# 用 .test-data 已有的缓存文件验证
python -c "
import json, pathlib
from tla.session_analyzer import analyze_trajectory_row
# 加载一个本地缓存文件
cache = pathlib.Path('.test-data/cache')
for f in cache.glob('*.txt'):
raw = f.read_bytes()
data = json.loads(raw)
row = {'id': 99999, 'object_key': f.name, 'run_data': '2026-01-01', 'run_time': None}
rec = analyze_trajectory_row(row, data, raw_for_hash=raw)
print(f'file={f.name[:40]}')
print(f' invalid_speed_points={rec[\"invalid_speed_points\"]}')
print(f' has_board_out_of_control={rec[\"has_board_out_of_control\"]}')
print(f' esc_event_0x400000_points={rec[\"esc_event_0x400000_points\"]}')
print(f' overvoltage_points={rec[\"overvoltage_points\"]}')
print(f' has_fault={rec[\"has_fault\"]}')
print(f' analyzer_version={rec[\"analyzer_version\"]}')
break
"
期望:
- analyzer_version = "1.0.3"
- esc_event_0x400000_points = overvoltage_points(值完全相同)
- 有堵转/超范围转速的会话 has_board_out_of_control = 1
- 仅 BMS 低电的会话 has_board_out_of_control = 0
Phase 1 — 三级分级报表(1-2 周后)
目标:有完整 ESC/ECU/BMS 三级分级;has_protocol_fault / has_warning / fault_severity_display 落地;BMS 细分;日聚合更新
Task 1-A esc_faults.py — 全量码表扩充
文件 trajectory-log-analyzer/tla/esc_faults.py
将 ESC_FAULTS 从 6 个码扩至 24 个(完整清单见 TLA_IMPROVEMENT_PLAN_V2.md §6.1),
同时完善 ECU_FAULTS(6 码全覆盖)和 BMS_FAULTS(22 码全覆盖)。
验收标准:
from tla.esc_faults import ESC_FAULTS, ECU_FAULTS, BMS_FAULTS
assert len(ESC_FAULTS) >= 24, f"ESC 码数不足: {len(ESC_FAULTS)}"
assert len(ECU_FAULTS) == 6, f"ECU 码数不足: {len(ECU_FAULTS)}"
assert len(BMS_FAULTS) >= 22, f"BMS 码数不足: {len(BMS_FAULTS)}"
# 所有码有 severity 映射
from tla.esc_faults import ESC_SEVERITY, ECU_SEVERITY, BMS_SEVERITY
assert set(ESC_FAULTS) == set(ESC_SEVERITY)
assert set(ECU_FAULTS) == set(ECU_SEVERITY)
assert set(BMS_FAULTS) == set(BMS_SEVERITY)
print("码表完整性: OK")
Task 1-B session_analyzer.py — 三级分级字段
文件 trajectory-log-analyzer/tla/session_analyzer.py
在 analyze_trajectory_row() 中(invalid_speed_cnt 计算之后)新增分级统计循环:
from tla.esc_faults import ESC_SEVERITY, ECU_SEVERITY, BMS_SEVERITY
# ── P1: 分级统计 ───────────────────────────────────────────────────────
hit_esc_severity: set[str] = set() # 本会话命中的 ESC severity 集合
hit_ecu_severity: set[str] = set()
bms_notice_cnt = bms_warning_cnt = bms_fault_cnt = 0
for p in tl:
for name in match_esc_faults(int(p.get("escEventCode") or 0)):
hit_esc_severity.add(ESC_SEVERITY.get(name, "unknown"))
for name in match_ecu_faults(int(p.get("ecuEventCode") or 0)):
hit_ecu_severity.add(ECU_SEVERITY.get(name, "unknown"))
for name in match_bms_faults(int(p.get("bmsEventCode") or 0)):
sev = BMS_SEVERITY.get(name, "unknown")
if sev == "notice": bms_notice_cnt += 1
elif sev == "warning": bms_warning_cnt += 1
elif sev == "fault": bms_fault_cnt += 1
has_protocol_fault = (
"fault" in hit_esc_severity
or "fault" in hit_ecu_severity
or bms_fault_cnt > 0
)
has_warning = (
not has_protocol_fault
and (
bool(hit_esc_severity - {"fault"})
or bool(hit_ecu_severity - {"fault"})
or bms_warning_cnt > 0
or bms_notice_cnt > 0
)
)
# 展示用最高级
if has_protocol_fault or has_board_out_of_control:
fault_severity_display = "fault"
elif has_warning or has_board_out_of_control:
fault_severity_display = "warning"
elif bms_notice_cnt > 0:
fault_severity_display = "notice"
else:
fault_severity_display = "none"
# has_fault 最终定义(覆盖 P0 的临时版本)
has_fault = has_protocol_fault or has_board_out_of_control
return dict 追加:
"has_protocol_fault": 1 if has_protocol_fault else 0,
"has_warning": 1 if has_warning else 0,
"fault_severity_display": fault_severity_display,
"bms_notice_points": bms_notice_cnt,
"bms_warning_points": bms_warning_cnt,
"bms_critical_points": bms_fault_cnt,
"bms_fault_points": bms_fault_cnt, # v1.0.3 重定义:仅故障级
Task 1-C _build_fault_details() — 明细表新增分级列
文件 trajectory-log-analyzer/tla/session_analyzer.py 中的 _build_fault_details()
导入 severity 字典,构建 severity_map 和 boc_codes:
from tla.esc_faults import (
ESC_SEVERITY, ECU_SEVERITY, BMS_SEVERITY, ESC_BOARD_OUT_OF_CONTROL
)
_SEVERITY_MAP = {**ESC_SEVERITY, **ECU_SEVERITY, **BMS_SEVERITY}
_BOC_CODES = ESC_BOARD_OUT_OF_CONTROL # 参与失控合成的码
def _build_fault_details(trajectory_id: int, tl: list[dict]) -> list[dict]:
...
rows.append({
"trajectory_id": trajectory_id,
"fault_domain": domain,
"fault_code": name,
"severity_level": _SEVERITY_MAP.get(name, "unknown"), # 新增
"board_out_of_control_related": 1 if name in _BOC_CODES else 0, # 新增
"fault_mask": mask,
"point_count": cnt,
"point_ratio": round(cnt / n, 4),
"first_seen_ms": first_ms,
})
Task 1-D 迁移 SQL — P1 新增列
新建文件 trajectory-log-analyzer/sql/migrations/20260601_tla_v1_0_3_p1.sql
-- TLA v1.0.3 P1 迁移:三级分级字段 + BMS 细分 + 故障明细扩列
ALTER TABLE surf_motion_trajectory_analysis
ADD COLUMN `has_protocol_fault` TINYINT(1) NOT NULL DEFAULT 0
COMMENT '命中协议级故障码(对应 App 红色弹窗 backIdx=2)'
AFTER `has_board_out_of_control`,
ADD COLUMN `has_warning` TINYINT(1) NOT NULL DEFAULT 0
COMMENT '命中告警/提醒级(App 黄/蓝弹窗)且无协议故障'
AFTER `has_protocol_fault`,
ADD COLUMN `fault_severity_display` VARCHAR(16) NOT NULL DEFAULT 'none'
COMMENT '展示用最高级别: fault|warning|notice|none'
AFTER `has_warning`,
ADD COLUMN `bms_notice_points` INT NOT NULL DEFAULT 0
COMMENT 'BMS 提醒级事件点数(低电20%/单体偏低等)'
AFTER `bms_fault_points`,
ADD COLUMN `bms_warning_points` INT NOT NULL DEFAULT 0
COMMENT 'BMS 告警级事件点数(低电10%/高温限速等)'
AFTER `bms_notice_points`,
ADD COLUMN `bms_critical_points` INT NOT NULL DEFAULT 0
COMMENT 'BMS 故障级事件点数(压差过大/单体失效/过流等)'
AFTER `bms_warning_points`;
-- 索引
ALTER TABLE surf_motion_trajectory_analysis
ADD KEY `idx_protocol_fault` (`has_protocol_fault`),
ADD KEY `idx_fault_severity` (`fault_severity_display`);
-- 故障明细表新增分级列
ALTER TABLE surf_motion_trajectory_fault_detail
ADD COLUMN `severity_level` VARCHAR(8) NOT NULL DEFAULT 'unknown'
COMMENT 'fault|warning|notice|unknown'
AFTER `fault_code`,
ADD COLUMN `board_out_of_control_related` TINYINT(1) NOT NULL DEFAULT 0
COMMENT '是否参与板体失控合成'
AFTER `severity_level`;
-- 日聚合表新增失控/分级统计列
ALTER TABLE surf_log_version_daily_stat
ADD COLUMN `session_board_out_of_control` INT NOT NULL DEFAULT 0
COMMENT '板体失控会话数' AFTER `session_overvoltage`,
ADD COLUMN `session_warning` INT NOT NULL DEFAULT 0
COMMENT '纯告警(非协议故障)会话数' AFTER `session_board_out_of_control`,
ADD COLUMN `session_protocol_fault` INT NOT NULL DEFAULT 0
COMMENT '协议故障会话数' AFTER `session_warning`,
ADD COLUMN `invalid_speed_points_sum` BIGINT NOT NULL DEFAULT 0
COMMENT '超范围转速点数合计' AFTER `session_protocol_fault`;
Task 1-E repository.py — refresh_daily_stats 更新
文件 trajectory-log-analyzer/tla/db/repository.py
在 sql_version 中补充新聚合列(INSERT INTO ... SELECT ... 和 ON DUPLICATE KEY UPDATE 两处都要加):
sql_version = f"""
INSERT INTO surf_log_version_daily_stat (
stat_date, esc_version, ecu_version, bms_version, rc_version,
session_total, session_fault, session_critical, session_stall,
session_overvoltage, session_ecu_comm, session_corrupt_speed,
session_board_out_of_control, -- 新增
session_warning, -- 新增
session_protocol_fault, -- 新增
invalid_speed_points_sum, -- 新增
stall_points_sum
)
SELECT
session_date,
IFNULL(esc_version,''), IFNULL(ecu_version,''), IFNULL(bms_version,''), IFNULL(rc_version,''),
COUNT(*),
SUM(has_fault),
SUM(severity = 'critical'),
SUM(stall_points > 0),
SUM(overvoltage_points > 0),
SUM(ecu_comm_points > 0),
SUM(corrupt_speed_points > 0),
SUM(has_board_out_of_control), -- 新增
SUM(has_warning), -- 新增
SUM(has_protocol_fault), -- 新增
SUM(invalid_speed_points), -- 新增
SUM(stall_points)
FROM surf_motion_trajectory_analysis
WHERE session_date IS NOT NULL {date_filter}
GROUP BY 1,2,3,4,5
ON DUPLICATE KEY UPDATE
session_total=VALUES(session_total),
session_fault=VALUES(session_fault),
session_critical=VALUES(session_critical),
session_stall=VALUES(session_stall),
session_overvoltage=VALUES(session_overvoltage),
session_ecu_comm=VALUES(session_ecu_comm),
session_corrupt_speed=VALUES(session_corrupt_speed),
session_board_out_of_control=VALUES(session_board_out_of_control), -- 新增
session_warning=VALUES(session_warning), -- 新增
session_protocol_fault=VALUES(session_protocol_fault), -- 新增
invalid_speed_points_sum=VALUES(invalid_speed_points_sum), -- 新增
stall_points_sum=VALUES(stall_points_sum)
"""
Task 1-F analysis/session_analyzer.py — 与 tla 版本对齐
文件 analysis/session_analyzer.py
当前版本为 1.0.0,需将 P0/P1 的新字段同步过来。
最简单的方式:将 analysis/session_analyzer.py 改为直接导入 tla 版本:
# analysis/session_analyzer.py 最简同步方案
# 将文件内容替换为:
from tla.session_analyzer import * # noqa: F401,F403
from tla.session_analyzer import ANALYZER_VERSION, analyze_trajectory_row
或者保持独立文件但人工同步关键函数(若 analysis 目录不在 Python 路径上,则手动复制)。
Task 1-G 全量重算部署
# 1. 确保 P0 SQL 已执行
mysql -h HOST -u USER -p surf-dev < sql/migrations/20260601_tla_v1_0_3_p0.sql
# 2. 确保 P1 SQL 已执行
mysql -h HOST -u USER -p surf-dev < sql/migrations/20260601_tla_v1_0_3_p1.sql
# 3. 构建并部署新镜像
docker compose build
docker compose up -d # 或 docker compose run --rm analyzer(单次跑)
# 4. 全量重算(bms_fault_points 重定义是 breaking change,所有行都要重算)
FORCE_REANALYZE=1 BATCH_SIZE=200 docker compose run --rm analyzer
Phase 2 — 可维护性(按需)
Task 2-A fault_catalog.yaml 外置码表
新建 trajectory-log-analyzer/tla/fault_catalog.yaml,内容见 TLA_IMPROVEMENT_PLAN_V2.md §5。
Task 2-B esc_faults.py 改为从 YAML 加载
import yaml, pathlib
def _load_catalog() -> dict:
p = pathlib.Path(__file__).parent / "fault_catalog.yaml"
return yaml.safe_load(p.read_text(encoding="utf-8"))
_CATALOG = _load_catalog()
def _build_dicts(domain: str):
faults, severity, boc = {}, {}, set()
for entry in _CATALOG.get(domain, []):
code = entry["code"]
faults[code] = entry["mask"]
severity[code] = entry.get("severity", "unknown")
if entry.get("board_out_of_control"):
boc.add(code)
return faults, severity, frozenset(boc)
ESC_FAULTS, ESC_SEVERITY, ESC_BOARD_OUT_OF_CONTROL = _build_dicts("esc")
ECU_FAULTS, ECU_SEVERITY, _ = _build_dicts("ecu")
BMS_FAULTS, BMS_SEVERITY, _ = _build_dicts("bms")
Task 2-C 单元测试补全
新建 trajectory-log-analyzer/tests/test_fault_grading.py:
"""验证每条协议码都有正确的分级映射。"""
from tla.esc_faults import ESC_FAULTS, ESC_SEVERITY, ECU_FAULTS, ECU_SEVERITY, BMS_FAULTS, BMS_SEVERITY
def test_all_esc_codes_have_severity():
for code in ESC_FAULTS:
assert code in ESC_SEVERITY, f"ESC 码 {code} 缺少 severity"
def test_all_ecu_codes_have_severity():
for code in ECU_FAULTS:
assert code in ECU_SEVERITY, f"ECU 码 {code} 缺少 severity"
def test_all_bms_codes_have_severity():
for code in BMS_FAULTS:
assert code in BMS_SEVERITY, f"BMS 码 {code} 缺少 severity"
def test_stall_is_board_out_of_control():
from tla.esc_faults import ESC_BOARD_OUT_OF_CONTROL
assert "ESC_FAULT_STALL_PROTECTION" in ESC_BOARD_OUT_OF_CONTROL
def test_0x400000_is_board_out_of_control():
from tla.esc_faults import ESC_BOARD_OUT_OF_CONTROL
assert "ESC_EVENT_0x400000" in ESC_BOARD_OUT_OF_CONTROL
def test_invalid_speed():
from tla.session_analyzer import is_invalid_motor_speed
assert is_invalid_motor_speed(20000) is True
assert is_invalid_motor_speed(-20000) is True
assert is_invalid_motor_speed(19999) is False
assert is_invalid_motor_speed(None) is False
验收 SQL(每阶段部署后执行)
V-1 P0 验收:新字段存在且有非零值
-- 确认新列存在
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_NAME = 'surf_motion_trajectory_analysis'
AND COLUMN_NAME IN ('invalid_speed_points','has_board_out_of_control','esc_event_0x400000_points');
-- 期望 = 3
-- 确认重算后有失控会话
SELECT COUNT(*) AS boc_sessions
FROM surf_motion_trajectory_analysis
WHERE has_board_out_of_control = 1 AND analyzer_version = '1.0.3';
-- 转速超范围会话数
SELECT COUNT(*) AS invalid_spd_sessions
FROM surf_motion_trajectory_analysis
WHERE invalid_speed_points > 0 AND analyzer_version = '1.0.3';
V-2 P0 验收:原 overvoltage_points 与新字段一致
-- 必须为 0(双写完全一致)
SELECT COUNT(*) AS mismatch
FROM surf_motion_trajectory_analysis
WHERE overvoltage_points != esc_event_0x400000_points
AND analyzer_version = '1.0.3';
V-3 P1 验收:BMS 低电不再单独触发 has_fault
-- 这类会话 v1.0.3 下 has_fault 应 = 0,has_warning = 1
SELECT
has_fault,
has_warning,
has_protocol_fault,
has_board_out_of_control,
fault_severity_display,
bms_warning_points,
bms_fault_points
FROM surf_motion_trajectory_analysis
WHERE stall_points = 0
AND esc_event_0x400000_points = 0
AND invalid_speed_points = 0
AND ecu_comm_points = 0
AND ecu_other_points = 0
AND bms_fault_points = 0 -- 无 BMS 故障级
AND bms_warning_points > 0 -- 只有 BMS 告警
AND analyzer_version = '1.0.3'
LIMIT 10;
-- 期望:has_fault=0, has_warning=1, fault_severity_display='warning'
V-4 P1 验收:故障分级分布合理
-- 看分布,确认 notice/warning/fault 均有数据且合理
SELECT
fault_severity_display,
COUNT(*) AS cnt,
ROUND(COUNT(*)*100.0 / SUM(COUNT(*)) OVER(), 1) AS pct
FROM surf_motion_trajectory_analysis
WHERE analyzer_version = '1.0.3'
GROUP BY fault_severity_display
ORDER BY FIELD(fault_severity_display,'fault','warning','notice','none');
文件改动汇总
| 文件 | Phase | 改动类型 | 预计行数 |
|---|---|---|---|
tla/esc_faults.py |
P0+P1 | 改名 + 扩充 + 新增 severity 字典 | +100 行 |
tla/fault_engine.py |
P0 | 字段名同步 3 处 | ~5 行 |
tla/session_analyzer.py |
P0+P1 | 新函数 + 新字段 + has_fault 重定义 | +60 行 |
tla/db/repository.py |
P1 | refresh_daily_stats SQL 扩列 |
+15 行 |
sql/migrations/20260601_tla_v1_0_3_p0.sql |
P0 | 新建 | 30 行 |
sql/migrations/20260601_tla_v1_0_3_p1.sql |
P1 | 新建 | 50 行 |
analysis/session_analyzer.py |
P1 | 同步或导入 | <10 行 |
tla/fault_catalog.yaml |
P2 | 新建 | ~250 行 |
tests/test_fault_grading.py |
P2 | 新建 | ~40 行 |
变更记录
| 版本 | 日期 | 说明 |
|---|---|---|
| v1.0 | 2026-06-01 | 首版:逐文件逐函数 Checklist,对应 TLA_IMPROVEMENT_PLAN_V2.md |