版本:v1.1
状态:Accepted
适用范围:本仓库全部源代码、测试、配置、脚本、迁移和文档
相关文档:PRD、System Design、Agent Development Guide、ADR
本文是 TrafficVerse 的代码规范和文件目录规范。所有开发者与 Agent 在修改仓库前必须阅读并遵守。
关键字含义:
- MUST / 必须:不可违反;违反时不能合并。
- SHOULD / 应该:默认遵守;偏离时需在变更说明中写明理由。
- MAY / 可以:按任务需要选择。
文档发生冲突时,不允许自行选择方便的版本:
- 产品范围以
docs/PRD.md为准; - 已接受架构决策以
docs/ADR.md为准; - 模块、协议和运行时设计以
docs/SYSTEM_DESIGN.md为准; - 代码和目录写法以本文为准;
- 任务边界和验收标准以
docs/AGENT_DEVELOPMENT_GUIDE.md为准。
若上层文档之间存在实质冲突,停止扩散实现,先更新对应设计或 ADR。不得用局部代码绕过文档冲突。
任何代码变更都必须保持:
- SUMO/TraCI 是车辆、路线、车道和信号灯的全局真值源;SUMO GUI 不属于产品 UI。
- CARLA 是 ROI 内视觉镜像,不独立决定全局运动学状态。
- 只有
SimulationManager可以调用 TraCIsimulationStep和 CARLAworld.tick()。 - Core Run 使用 50 ms 固定步长、Town04 同源资产和严格信号灯映射。
- ROI 使用核心区+Buffer 滞回,不得改回单阈值同步。
- 跨模块只使用公共领域模型、事件和 Port,不共享第三方 SDK 对象或全局可变状态。
- 配置来自类型化 YAML,不硬编码领域和部署参数。
- 实时状态与异步结果使用版本化 WebSocket envelope;资源 CRUD 使用 REST。
- 高频轨迹使用 Parquet,关系元数据使用 PostgreSQL。
- Core Run Gate 优先于 Product Gate 和性能优化。
修改上述约束必须先新增或替代 ADR。
TrafficSIM/
├── AGENTS.md
├── README.md
├── pyproject.toml
├── uv.lock # 项目唯一 lockfile
├── .env.example
├── configs/
│ ├── runtime-baseline.yaml
│ ├── defaults.yaml
│ ├── scenarios/
│ │ └── core-run-town04.yaml
│ └── maps/
│ └── town04/
│ ├── manifest.yaml
│ ├── Town04.xodr
│ ├── network.json
│ ├── routes.yaml
│ ├── registration.yaml
│ ├── signals.yaml
│ └── network.geojson
├── contracts/
│ ├── scenario.schema.json
│ ├── openapi.yaml
│ └── websocket/
│ └── *.schema.json
├── src/
│ └── trafficverse/
│ ├── __init__.py
│ ├── cli.py
│ ├── bootstrap.py
│ ├── config/
│ ├── domain/
│ │ ├── models/
│ │ ├── enums.py
│ │ ├── errors.py
│ │ └── events.py
│ ├── ports/
│ │ ├── simulation.py
│ │ ├── persistence.py
│ │ └── messaging.py
│ ├── application/
│ │ ├── scenario_service.py
│ │ ├── simulation_manager.py
│ │ ├── metrics_engine.py
│ │ └── replay_service.py
│ ├── maps/
│ │ ├── opendrive_parser.py
│ │ ├── compiler.py
│ │ ├── geojson.py
│ │ └── validator.py
│ ├── adapters/
│ │ ├── carla/
│ │ ├── persistence/
│ │ └── messaging/
│ ├── roi/
│ │ ├── geometry.py
│ │ ├── synchronizer.py
│ │ ├── signal_synchronizer.py
│ │ └── coordinate_transformer.py
│ ├── controllers/
│ ├── api/
│ │ ├── app.py
│ │ ├── dependencies.py
│ │ ├── rest/
│ │ └── websocket/
│ └── logging/
├── ui/
│ ├── app/
│ ├── api_client/
│ ├── models/
│ ├── views/
│ ├── viewmodels/
│ ├── widgets/
│ ├── web/
│ │ ├── map/
│ │ └── dashboard/
│ └── assets/
├── migrations/
├── tests/
│ ├── unit/
│ ├── contract/
│ ├── integration/
│ ├── e2e/
│ ├── performance/
│ └── fixtures/
├── scripts/
│ ├── maps/
│ ├── dev/
│ └── ci/
├── artifacts/ # 运行产物;不提交
└── docs/
| 内容 | 必须放置位置 | 禁止放置位置 |
|---|---|---|
| 领域模型、枚举、领域错误 | src/trafficverse/domain/ |
api/、adapters/、ui/ |
| 第三方系统抽象接口 | src/trafficverse/ports/ |
具体 adapter 文件中临时定义 |
| 用例编排、生命周期 | src/trafficverse/application/ |
FastAPI handler、UI callback |
| 依赖注入和具体 adapter 装配 | src/trafficverse/bootstrap.py |
domain/application 内部 |
| CARLA/SQLAlchemy/WebSocket 实现 | 对应 adapters/ |
domain/、controllers/ |
| OpenDRIVE 导入和原生地图编译 | src/trafficverse/maps/ |
CARLA adapter、UI |
| SUMO/TraCI adapter | src/trafficverse/adapters/sumo/ |
API handler、UI callback |
| ROI、配准、信号灯纯逻辑 | src/trafficverse/roi/ |
CARLA adapter 私有函数 |
| 车辆控制策略 | src/trafficverse/controllers/ |
TraCI connection、Simulation Manager |
| HTTP/WS 接入层 | src/trafficverse/api/ |
application/domain |
| PySide6、MapLibre、deck.gl、Plotly | ui/ |
后端 domain/application |
| 公共机器契约 | contracts/ |
手工复制在多个模块 |
| 用户可调配置 | configs/ |
Python 常量、UI 默认值 |
| 数据库 schema 变更 | migrations/ |
应用启动时动态建表 |
| 开发/构建脚本 | scripts/<purpose>/ |
仓库根目录散落脚本 |
| 运行日志、Parquet、截图、视频 | artifacts/ |
src/、tests/fixtures/ |
目录必须在有实际文件时创建,不建立空目录树。仓库根目录不得新增临时 Python、SQL、JSON 或 shell 文件。根目录 main.py 不承载产品逻辑,T01 建立 CLI 后应删除示例入口。
允许的依赖方向:
ui → REST/WebSocket contracts
api → application → ports → domain
adapters → ports + domain
maps → domain
roi → domain + ports
controllers → domain + ports
bootstrap/cli → application + concrete adapters
必须遵守:
domain只允许导入 Python 标准库和 Pydantic;不得导入 FastAPI、CARLA、SQLAlchemy、PySide6。ports只导入标准库、typing 和 domain。application可以导入 domain/ports,不导入具体 adapter SDK。- adapter 之间不得直接导入;协作由 application 层通过 Port 编排。
- 只有
bootstrap.py/CLI composition root 可以同时导入 application 和具体 adapters,用于构造依赖;其中不得包含业务规则。 api不直接导入 TraCI、CARLA 或 SQLAlchemy model。ui不导入src/trafficverse;只能使用 REST/WebSocket 和生成/本地镜像的协议模型。controllers不持有 TraCI connection、不调用 CARLA,不访问数据库或 WebSocket。__init__.py保持轻量,不启动线程、不连接外部系统、不读取文件、不执行注册副作用。- 禁止循环导入。若出现循环,优先修正职责边界,不使用函数内 import 掩盖问题。
项目固定 Python 3.10。T01 必须在 pyproject.toml 配置:
- Ruff:格式化、lint、import sorting;
- mypy:静态类型检查;
- pytest:测试;
- coverage.py/pytest-cov:覆盖率报告。
项目统一使用 uv 管理 Python 环境和 lockfile。安装与检查命令:
uv sync --frozen
uv run ruff format --check .
uv run ruff check .
uv run mypy src tests
uv run pytest自动修复只针对当前任务涉及文件;不得借格式化工具重写无关目录。Ruff 行宽固定为 100。
| 对象 | 规则 | 示例 |
|---|---|---|
| package、module、函数、变量 | snake_case |
simulation_manager.py、run_tick |
| 类、枚举、Pydantic model | PascalCase |
VehicleState、ExperimentStatus |
| 常量 | UPPER_SNAKE_CASE |
DEFAULT_STEP_MS |
| Protocol/外部端口 | 能力名 + Port |
TrafficEnginePort、EventPublisherPort |
| 具体实现/adapter | 技术名 + 能力名 | SumoTrafficEngineAdapter、CarlaAdapter |
| application service | 用例名 + Service 或 Manager |
ScenarioService、SimulationManager |
| 异常 | 语义名 + Error |
VersionMismatchError |
| 测试 | test_<behavior>_<expected> |
test_vehicle_exits_buffer_destroys_actor |
缩写在类名中按普通单词处理:RoiSynchronizer、ApiClient,不要写成 ROISynchronizer。协议字段沿用已冻结形式,例如 experiment_id、simulation_time_ms。
- 所有公共函数、方法和属性必须有完整类型标注。
- 禁止无约束
dict[str, Any]作为公共输入输出。 - Pydantic model 用于配置、API、WebSocket、持久化边界和公共领域契约。
dataclass(frozen=True, slots=True)可用于模块内部不可变值对象和纯计算结果。- 第三方 SDK 对象必须在 adapter 内转换为 TrafficVerse model。
Any仅允许在不可避免的第三方原始边界,必须局部收窄并注释原因。- 不使用可变默认参数;集合默认值使用
default_factory。 - 对外返回集合优先使用
Sequence、Mapping;内部确需修改时再使用list、dict。 - 空值必须有明确语义;不要用空字符串、
0或空字典代替None。 - 枚举值显式稳定,不依赖
auto()生成对外协议值。
字段名必须携带单位:
- 时间:
*_ms、*_s;权威仿真时间为整数simulation_time_ms。 - 距离:
*_m;速度:*_mps;加速度:*_mps2;角度:*_rad。 - 字节:
*_bytes;频率:*_hz。
禁止使用无单位的 timeout、speed、duration 作为跨模块字段。UI 显示 km/h 时,只在 view/viewmodel 转换,不修改领域值。
资源 ID 使用 UUID,车辆主 ID 使用场景指定或引擎生成的稳定字符串 ID。CARLA Actor ID 只在当前 world 生命周期内使用,禁止作为持久化主键;信号灯持久化使用原生 signal ID 和 OpenDRIVE signal ID binding。
- 一个函数只承担一个可描述的动作;优先早返回,避免超过三层嵌套。
- 四个以上含义相近的参数应使用类型化 command/config 对象;避免布尔参数控制多种行为。
- 副作用与纯计算分离。例如 ROI
reconcile只生成 plan,adapter 负责应用。 - 一个类只有一个主要变化原因。不要创建
Utils、Helpers、Common等无领域含义的聚合类或文件。 - 模块通常应保持在约 400 行以内;超过时按职责拆分,不为满足行数机械拆文件。
- 函数通常应保持在约 50 行以内;复杂状态机和协议解析可例外,但必须有测试和清晰分段。
- 公共 API 只暴露调用方需要的最小表面;下划线前缀表示模块私有实现。
- 不为了未来可能需求提前创建抽象。第二个真实实现出现或 Port 边界已由架构明确时再抽象。
async只用于实际异步 I/O、队列或生命周期编排,不把纯计算包装成 async。- 禁止在 event loop 中执行阻塞 CARLA/文件调用或长时间 CPU 密集地图编译;使用专用执行边界并保持调用顺序。
- 所有队列必须有明确容量、溢出策略和监控指标;禁止无界队列。
- 只有 Simulation Manager 推进仿真;任何 callback、worker、UI、logger 不得调用 step/tick。
- 外部资源使用显式
start/connect/close生命周期;关闭必须幂等。 - 使用
try/finally或 async context manager 保证清理。 - 任务取消不得吞掉
CancelledError;清理完成后继续传播取消。 - 不使用真实
sleep协调测试;使用 fake clock、event 或受控队列。
- 领域和应用层抛出
domain/errors.py中的稳定错误类型和错误码。 - adapter 捕获 SDK 异常并转换,禁止将 TraCI/CARLA/SQLAlchemy 原始异常泄漏到 API/UI。
- 不写裸
except:;捕获Exception时必须在边界记录上下文并重新抛出或转换。 - 不静默忽略失败。允许降级时必须发布领域事件并更新组件健康。
- 错误信息不得包含凭证、完整环境变量、用户隐私或任意本机路径。
- 状态变化和资源清理必须保持幂等;重复命令返回稳定结果或明确冲突。
- 使用结构化日志,不使用
print()记录运行状态。 - 每条运行日志至少包含:
component、event、level;有上下文时增加trace_id、experiment_id、simulation_time_ms、vehicle_id。 - 日志消息使用稳定事件名,例如
roi.actor_spawn_failed,说明文字放字段中。 - 高频每车每 tick 日志默认关闭;使用计数器、采样或 debug level。
- 不在日志中写 base64 相机数据、完整轨迹帧或大型配置。
- 异常使用
logger.exception保留 stack trace;预期业务拒绝不记录为系统异常。
- 标识符、公共 docstring、错误码和协议字段使用英文。
- 面向用户的 UI 文案和项目说明可使用中文。
- 注释解释“为什么”和约束来源,不重复代码表面行为。
- 公共 Port、领域模型中的非显然字段、复杂算法和兼容 workaround 必须有 docstring。
- TODO 格式:
TODO(owner-or-task): reason and removal condition;禁止无上下文 TODO。 - 不保留大段注释掉的代码;删除后依赖版本控制恢复。
- 所有用户可调和部署参数进入类型化配置 model。
- YAML 使用两个空格缩进、UTF-8、显式单位字段和稳定
schema_version。 - 未知字段默认拒绝,避免拼写错误静默生效。
- 配置加载顺序固定:代码安全默认值 → YAML → 允许的环境变量覆盖 → 类型/交叉/环境校验 → 不可变 resolved snapshot。
- 环境变量只覆盖部署字段,例如 host、port、数据库连接;不得覆盖 seed、路线和自动驾驶比例而不写入 resolved snapshot。
- secret 只来自环境或 secret store,不进入 YAML、日志、测试 fixture 或 Git。
configs/defaults.yaml只放跨场景默认值;地图专属参数放对应 map 目录。
- REST 基础路径固定
/api/v1;handler 只做校验、鉴权预留、调用 application service 和序列化。 - request/response 必须使用命名 Pydantic model,不返回临时字典。
- 错误使用统一
error.code/message/details/trace_id结构。 - 写接口按设计实现幂等和乐观锁;不得在 HTTP 请求内等待长时间仿真完成。
- WebSocket 消息必须使用统一 envelope,并携带
schema_version、type、experiment_id、simulation_time_ms和sequence。 - 命令回复必须设置
correlation_id。 - 新增消息类型必须同时更新 model、JSON Schema、契约测试和文档。
- Core Run 不定义或发布
camera.frame;CARLA 画面由 Qt 直接托管本机原生窗口。
- 新增可选字段可以保持当前主版本;删除、改名、类型改变或语义改变必须提升主版本。
contracts/中的 OpenAPI/JSON Schema 由代码生成并由 snapshot test 锁定。- 生成文件头部必须注明生成来源和命令;禁止手工修改生成结果。
- 公共契约变更必须列出生产者、消费者、兼容性和迁移顺序。
- TraCI SDK 只放在
adapters/sumo/,通过TrafficEnginePort向 application 暴露能力。 - SUMO 网络必须由同一 Town04 OpenDRIVE 生成;运行时只加载已校验的
.sumocfg资产。 - 每次 step 恰好调用一次
simulationStep,并返回同一 SUMO 时间的不可变TrafficSnapshot。 - SUMO 连接丢失、时间回退或 step 失败必须转换为稳定领域错误并使当前实验失败。
- 控制命令在 step 前批量应用;单车非法命令不得阻断其他合法命令。
network.json/GeoJSON 只用于展示和查找,不参与车辆推进,不形成第二真值。
- CARLA SDK 只出现在
adapters/carla/。 - client/server 版本必须一致;不匹配时拒绝 READY。
- Simulation Manager 是唯一 tick 发起者。
- 车辆和信号灯使用 batch API;部分失败返回逐项结果。
- 镜像车辆禁用 autopilot,不让 CARLA 物理反写 SUMO。
- CARLA world reload 后必须重新解析 Actor 和 OpenDRIVE signal binding。
- 产品不创建 UI 专用 RGB sensor;CARLA 原生窗口由 Qt foreign-window 容器托管。
- close 时恢复原始 world settings 并销毁本系统创建的全部 Actor/sensor。
- SQLAlchemy model 留在 persistence adapter,不进入 domain。
- repository 方法实现 Port,不将 session 暴露给 application。
- 一个用例需要原子性时,由明确 Unit of Work/transaction 边界控制。
- 查询必须分页;禁止无条件加载整张事件或轨迹表。
- 高频轨迹不得逐车逐 tick 写 PostgreSQL。
- 所有 schema 修改通过 Alembic migration;文件名使用
<revision>_<short_description>.py。 - migration 必须包含可执行 upgrade;可逆变更提供 downgrade,不可逆时在文件和变更说明中明确。
- 生产启动不得调用
create_all()自动建表。 - 表名、列名、索引名使用
snake_case。 - 主键、外键、唯一约束和状态枚举在数据库层同时约束,不能只依赖应用校验。
- 时间审计字段使用 UTC timezone-aware timestamp;仿真时间使用 bigint 毫秒。
- JSONB 只保存结构确实可变的数据;核心可查询字段使用显式列。
- migration 测试至少验证空库 upgrade 到 head,以及 downgrade/upgrade 循环(可逆时)。
- UI 是 API 客户端,不得导入后端 domain/application/adapters。
views/只负责布局和事件转发;状态与交互逻辑放viewmodels/。api_client/统一管理 REST、WebSocket、重连、sequence gap 和错误转换。- 网络、JPEG 解码和大数据转换不得阻塞 Qt UI thread。
- Qt signal/slot 名称表达领域事件,例如
experiment_state_changed。 - Widget 不自行计算权威指标,不保存第二份业务状态。
- MapLibre/deck.gl 只消费路网和 SUMO 派生的标准车辆/信号消息;不得嵌入 SUMO GUI。
- JavaScript 代码放
ui/web/,不以内联字符串散落在 Python view 中。 - 用户可见错误必须给出可执行恢复建议,不只显示 stack trace。
- 控件启用状态由实验状态机驱动,不在多个页面重复判断。
测试目录按行为层级组织,并尽量镜像源码路径:
src/trafficverse/roi/synchronizer.py
tests/unit/roi/test_synchronizer.py
src/trafficverse/adapters/carla/client.py
tests/unit/adapters/carla/test_client.py
tests/integration/carla/test_carla_adapter.py
- 测试文件:
test_<subject>.py。 - 测试函数:
test_<condition>_<expected_behavior>。 - 一个测试验证一个主要行为;Arrange/Act/Assert 清晰分离。
- 参数化用于相同规则的输入矩阵,不把不同业务行为塞入一个巨型测试。
- Unit:无网络、无真实时钟、无真实数据库、无 SUMO/CARLA/GUI;adapter 使用 Fake runtime。
- Contract:验证 Port fake、JSON Schema、OpenAPI、错误结构和版本兼容。
- Integration:验证单个真实 adapter;通过 marker 显式选择。
- E2E:验证 Core Run/Product Gate 的完整用户路径。
- Performance:独立运行,不混入默认快速测试。
推荐 marker:integration、traffic、carla、postgres、e2e、performance。
- 使用固定 seed、fake clock、稳定 vehicle ID 和受控 route。
- 不依赖测试执行顺序,不共享可变 fixture。
conftest.py只放当前目录树通用 fixture;局部 fixture 放测试文件。- 优先使用真实 model 和手写 Fake Port,少用深层 SDK mock 链。
- golden/snapshot 文件必须小、可审阅并说明生成方式。
- 测试不得访问公网。
- 临时文件使用 pytest
tmp_path,测试完成后不在仓库留下 artifact。
每个缺陷修复必须包含能在修复前失败的回归测试。每个新行为至少覆盖:
- 正常路径;
- 边界/空输入;
- 外部失败和清理;
- 非法状态或非法配置;
- 与时间、顺序、幂等相关的性质(适用时)。
Core Run 的真实验收不能用 Fake 代替,也不能只提供截图。
- 文本文件使用 UTF-8、LF、文件末尾换行。
- Python/YAML/JSON/Markdown 不含无意义尾随空格。
- JSON 机器文件使用稳定键顺序生成;YAML 保持人类可读。
- Town04 派生资产必须来自同一 CARLA 发行版,记录 checksum 和生成命令。
manifest.yaml是地图资产权威清单;修改任何被追踪文件后必须更新并重新验证。- 大于 10 MB 的新增文件、二进制资产或录制结果不得直接提交;确需版本化时先决定 Git LFS/外部存储并记录 ADR。
artifacts/、日志、Parquet、截图、视频、数据库文件、cache 和 IDE 文件必须 gitignore。- 测试 fixture 不复制完整 Town04 大文件;集成测试通过 manifest 引用受控资产。
- 生成的 OpenAPI、JSON Schema 和 GeoJSON 必须可由脚本重复生成。
- 新增依赖前先确认标准库或现有依赖不能合理完成任务。
- 运行依赖、开发依赖和可选集成依赖分组声明。
- 依赖版本写入
pyproject.toml并通过 uv 更新uv.lock;禁止只修改本地环境。 - 不使用未固定 Git branch、nightly 或来源不明 wheel 作为 Core Run 依赖。
- CARLA Python API 必须与 server package 同源同版。
- 不提交 token、密码、私钥、真实数据库 URI 或包含凭证的日志。
.env.example只放无敏感示例值。- 文件路径输入必须限制在允许根目录,API 不接受任意 shell 命令。
每个 Agent 必须按以下顺序工作:
- 阅读 PRD、ADR、System Design、本文、Agent Guide 和自己的任务章节。
- 检查当前文件和已有改动,保留用户或其他任务的工作。
- 确认任务允许修改的路径、依赖状态和 Core/Product Gate。
- 先定义或确认接口、数据模型和失败语义。
- 编写或更新能证明行为的测试。
- 实现满足当前 Gate 的最小代码,不提前实现延期优化。
- 运行与风险相称的 format、lint、typecheck、unit、contract、integration/E2E。
- 更新受影响文档、schema、配置示例和生成物。
- 按 Agent Guide 格式提交完成报告,逐项给出验收证据。
禁止:
- 未经要求重写无关模块;
- 为通过测试删除断言、跳过关键测试或吞掉异常;
- 在缺少真实依赖时宣称集成/E2E 已通过;
- 静默修改公共协议或 ADR;
- 用全局变量、monkey patch 或模块导入副作用绕过依赖设计;
- 提交仅能在个人绝对路径运行的实现;
- 为未来优化延迟 Core Run 主链。
一个代码任务只有同时满足以下条件才算完成:
- 代码位于正确目录,依赖方向合法;
- 公共接口有类型标注,配置和协议使用命名 model;
- 成功、边界、失败和清理路径有测试;
- Ruff format/check、mypy 和相关 pytest 通过;
- 外部资源无泄漏,队列有界,状态变化幂等;
- 新配置有 schema、默认值、校验和示例;
- 公共契约和生成文件同步更新;
- 相关文档和 ADR 未过期;
- 未引入凭证、运行 artifact、大型未管理文件或本机路径;
- 完成报告明确区分已运行、未运行和受环境阻塞的验证。
任何“功能看起来能跑”但不满足上述条件的变更都不是完成状态。
确需偏离本文时,变更说明必须包含:
- 具体规则;
- 无法遵守的技术原因;
- 影响范围和风险;
- 替代保护措施;
- 恢复到规范的条件或后续任务。
涉及架构、真值权属、时间同步、地图资产、信号灯、协议兼容或存储分工的例外,必须通过 ADR,不接受仅在代码评审说明中豁免。