Metadata-Version: 2.4
Name: vehicle-info-manager
Version: 0.0.8
Summary: Self-validating vehicle identity, PB/F00 paths, and Mdrive4 JSON-only vehicle_cfg validator.
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: minieye-calibration-git>=0.0.2
Requires-Dist: pb-calibration>=0.1.8
Requires-Dist: tomli>=2.0; python_version < "3.11"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"

# vehicle-info-manager

Version: 0.0.8

`vehicle-info parse-consistency evaluate` 对新流程优先读取 `data_source.json.topic_channel_inspection_artifact`，校验 SOC 内相对路径和 SHA-256 后读取 `topic_channel_inspection.json`。历史 `record_topic_inspection` / `mcap_channel_inspection` 仍作为回退；sidecar 完整性问题只打印 advisory warning/error，命令继续保持 stdout-only 和 exit 0。

Self-validating vehicle information manager for Minieye calibration workflows. It keeps `vehicle_model_id`, `vehicle_id`, PB/JSON identity, source metadata, paths, files, and validation state in one result shape. Version 0.0.8 moves Git clone/fetch/show/ls-tree/rev-parse/provenance operations behind `minieye-calibration-git>=0.0.2`, while keeping timestamped `00_temp` run/status isolation and existing CLI result fields.

## Install

```bash
pip install vehicle-info-manager
```

## Quick Start

### 1. 从本地 PB 解析车辆身份

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_pb_path("vehicle_config.pb.txt")
print(manager.info_status.state)
print(manager.pb_vehicle_model_id)
print(manager.f00_paths)
```

`from_pb_path(...)` 会读取本地 PB 中的 `vehicle_info.model` 和 `vehicle_info.id`，组合得到 `pb_vehicle_model_id = "{model}_{id}"`。如果传入 `expected_vehicle_model_id`，会同时检查期望车型与 PB 身份是否一致。

```python
manager = VehicleInfoManager.from_pb_path(
    "vehicle_config.pb.txt",
    expected_vehicle_model_id="DAS_HW1_XZA000005",
)
```

PB 身份缺失、为空、文件不可读或路径推导身份冲突都会进入统一 `state="error"`。CLI `inspect-identity --source-type pb` 输出统一结构：`state/ok/identity/paths/source_meta/files/issues`。

### 2. 从本地 Mdrive4 JSON 解析车辆身份

```python
from vehicle_info_manager import parse_json_vehicle_identity

result = parse_json_vehicle_identity("vehicle_cfg.json", expected_vehicle_id="XZT500021")
print(result.state)
print(result.identity.vehicle_id)
```

JSON 只读取顶层 `vehicle_id`，不读取也不兼容 `model`、`id`、`vehicle_model_id` 或 `vehicle_info` 等其它身份字段。`vehicle_id` 缺失、为空、JSON 非法或文件不可读都会进入 `state="error"`。

### 3. 从 `vehicle_model_id` 生成 F00 路径

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_vehicle_model_id("DAS_HW1_XZA000005")
print(manager.vehicle_id)
print(manager.repo_vehicle_model)
print(manager.f00_paths["l1_record_data_dir"])
```

`from_vehicle_model_id(...)` 会从 `vehicle_model_id` 推导 `vehicle_id`、`repo_vehicle_model`，并生成 F00 L1-L5 和临时工作目录路径。它只做本地推导和自检，不访问 Git、不下载 PB。

### 4. 显式补全 PB 中的车辆身份

当调用方已经明确 `vehicle_model_id`，且 PB 的 `vehicle_info.model` 或 `vehicle_info.id` 缺失/为空时，可以显式生成一个补全后的 PB：

```python
from vehicle_info_manager import complete_pb_vehicle_identity

result = complete_pb_vehicle_identity(
    pb_path="vehicle_config.pb.txt",
    vehicle_model_id="DAS_HW1_XZA000005",
    output_pb_path="vehicle_config.completed.pb.txt",
)
print(result["updated_fields"])
```

`vehicle_model_id` 按最后一个 `_` 拆分：`DAS_HW1_XZA000005` 会得到 `model="DAS_HW1"`、`id="XZA000005"`。补写策略只追加缺失/空字段；已有非空且一致时保持不变，已有非空且不一致时抛错并且不写输出。内部固定通过本地 `pb_calibration>=0.1.8` 执行 `parse -> update_vehicle_info -> build`，不得手写 PB 文本替换。

PB 中的 `vehicle_info.model` 和 `vehicle_info.id` 只是来源字段；对外结果使用 `vehicle_model_id`、`vehicle_id` 和 `repo_vehicle_model`。`complete_pb_vehicle_identity(...)` 的返回 JSON 也使用 `vehicle_id`，不会输出旧入口字段。

### 5. 从本地 PB 推导 F00 static_data 输入

```python
from vehicle_info_manager import resolve_static_data_input

result = resolve_static_data_input("vehicle_config.pb.txt")
print(result.vehicle_model_id)
print(result.static_data_dir)
print(result.ok)
```

`resolve_static_data_input(...)` 只解析本地 PB 的 `vehicle_info.model/id`，再按 F00 路径配置推导 `03_single_frame/{vehicle_model_id}/static_data`。它可以校验显式 `expected_vehicle_model_id` 或 `explicit_static_data_dir` 是否与 PB 一致，但不会调用 `fetch_pb`、`fetch_pb_by_vehicle_id` 或访问 Git。

### 6. 已知 `vehicle_id` 时只记录 Git 查询条件

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_vehicle_id("XZA000005", branch="master")
print(manager.vehicle_id)
print(manager.branch)
print(manager.info_status.state)
```

`from_vehicle_id(...)` 只记录 `vehicle_id`、`branch`、`commit` 等条件并触发一致性检查；它不会自动查找或下载 `mdrive_conf` 中的 PB。

### 7. Mdrive4 JSON-only `vehicle_cfg` 验证

先显式同步 `vehicle_cfg`，再做本地校验：

```python
from vehicle_info_manager import resolve_mdrive4_vehicle_cfg_input, sync_mdrive4_vehicle_cfg

sync_info = sync_mdrive4_vehicle_cfg(
    branch="feat_ecar",
    repo_path="vehicle/ECAR_HW4/XZT500021/calib",
)
result = resolve_mdrive4_vehicle_cfg_input(
    "ECAR_HW4_XZT500021",
    repo_dir=sync_info["vehicle_cfg_repo_dir"],
    repo_path=sync_info["vehicle_cfg_repo_path"],
)
print(result.state)
print(result.identity.vehicle_id)
print(result.source_meta.commit)
```

`sync_mdrive4_vehicle_cfg(...)` 不传 `repo_dir` 时，每次都会 clone 到 `{mdrive4_data_dir}/00_temp/vehicle_info_manager/vehicle_cfg/{timestamp}/repo`，并返回 `timestamp`、`vehicle_cfg_git_work_dir` 和 `vehicle_cfg_repo_dir`。同一个显式 `timestamp` 的 run 目录已存在时会直接报错，避免多个流程写同一 checkout。

`resolve_mdrive4_vehicle_cfg_input(...)` 默认只检查本地 `repo_dir` 或按当前输入推导的本次 checkout，绝不自动 clone/fetch。若本地 repo 不存在，返回 `vehicle_cfg_repo_missing` error；缺少 `branch` 或 `repo_path` 且配置中也没有默认值时，返回 `vehicle_cfg_config_missing` error。需要网络同步时必须显式调用 `sync_mdrive4_vehicle_cfg(...)` 或 CLI `mdrive4-sync-vehicle-cfg`。

```python
from vehicle_info_manager import resolve_mdrive4_vehicle_cfg_input

result = resolve_mdrive4_vehicle_cfg_input(
    "ECAR_HW4_XZT500021",
    repo_dir="/tmp/vehicle_cfg_checkout",
    repo_path="vehicle/ECAR_HW4/XZT500021/calib",
)
print(result.ok)
print(result.mdrive4_paths)
print(result.vehicle_cfg_git_commit)
print(result.calib_dir)
print(result.json_files)
```

Mdrive4 流程只校验目录和元数据：

- 校验 `vehicle_model_id` 与 `repo_vehicle_model` 是否能生成 Mdrive4 L1-L5 路径。
- 读取本地 `vehicle_cfg` Git 仓库，输出 branch、commit、repo_dir、repo_path、tree_url、calib_dir。

### 8. Topic/channel 与解析产物一致性评估（只打印）

PB/JSON 前链完成 L1 topic/channel inspection 和 L2 标准化后，可用 D10 做统一评估：

```bash
vehicle-info parse-consistency evaluate \
  --l1-data-source-json "<L1/data_source.json>" \
  --l2-soc-dir "<02_parsed_frames/.../{soc_name}>"
```

该命令默认只打印人类可读摘要，供 agent 在最终回复中说明风险；不会写文件，也不会阻断 workflow。需要机器可读输出时使用 `--json`，JSON 仍只输出到 stdout。

Python API：

```python
from vehicle_info_manager import evaluate_parse_consistency

result = evaluate_parse_consistency("data_source.json", "02_parsed_frames/.../soc1")
print(result.state)
print(result.warnings)
```
- 校验 `calib_dir` 是否存在，并枚举 `*.json` 文件。
- 校验 `calib_dir` 下所有 `*.json` 顶层 `vehicle_id` 都存在、非空且一致。
- 不解析 JSON 外参内容，不做 JSON schema 校验，不接入 PB。

Mdrive4 字段不得解释为 PB Git 来源字段。`mdrive4_vehicle_cfg_*` 只服务 JSON-only `vehicle_cfg`；禁止把它用于 PB 下载、PB 校验、PB tftree 或旧 `record/soc1/soc2` 语义。

### 8. 已知 `vehicle_id` 时统一查询 `model`

```python
from vehicle_info_manager import id_get_model

result = id_get_model(
    source_type="mdrive4-json",
    vehicle_id="XZT500021",
    input_path="/path/to/vehicle/ECAR_HW4/XZT500021/calib",
    git_repo="/path/to/vehicle_cfg",
)
print(result.to_dict())
```

CLI：

```bash
vehicle-info id-get-model --source-type mdrive4-json --vehicle-id XZT500021 --input-path /path/to/calib --git-repo /path/to/vehicle_cfg --pretty
vehicle-info id-get-model --source-type mdrive-pb --vehicle-id XZA000005 --git-repo /path/to/mdrive_conf --pretty
```

`id_get_model(...)` 支持 `mdrive-pb` 与 `mdrive4-json`。它优先从 `input_path` 解析 `model/id`；如果路径只能得到 `id` 或无法得到完整结果，再扫描本地 Git checkout。默认不 clone 或 fetch，匹配必须唯一，否则返回 `find_matched=False` 和 `issues`。

### 9. D10-only `vehicle_context.json`

`vehicle_context.json` 是跨 workflow/agent 的车辆身份唯一事实源，只能由 D10 写入；其它程序只能读取、校验和上报 drift。跨 agent 流转时不要传递 `VehicleInfoManager` 可变对象，应该生成只读 `vehicle_context.json`：

```python
from vehicle_info_manager import (
    VehicleInfoManager,
    build_vehicle_context,
    load_vehicle_context,
    verify_vehicle_context_integrity,
    write_vehicle_context,
)

manager = VehicleInfoManager.from_pb_path(
    "vehicle_config.pb.txt",
    expected_vehicle_model_id="DAS_HW1_XZA000005",
)
manager.set_git_info(
    branch="master",
    commit="abc123",
    file_path="vehicle_name/XZA000005/vehicle_config.pb.txt",
)
context = build_vehicle_context(
    manager,
    source_type="mdrive-pb",
    source_mode="git_branch",
    calib_source_path="vehicle_config.pb.txt",
    static_data_dir="/data/03_single_frame/DAS_HW1_XZA000005/static_data",
    trace_root_dir="/data/04.calibration_results/DAS_HW1_XZA000005/static_calibration/20260611",
    result_root_dir="/data/04.calibration_results",
)
write_vehicle_context(context, "goal/vehicle_context.json")
loaded = load_vehicle_context("goal/vehicle_context.json")
print(loaded.version.context_id)
print(verify_vehicle_context_integrity("goal/vehicle_context.json").ok)
```

公开 JSON 契约固定为：

```json
{
  "schema_version": 1,
  "state": "ok",
  "identity": {
    "vehicle_model_id": "DAS_HW1_XZA000005",
    "vehicle_id": "XZA000005",
    "repo_vehicle_model": "DAS_HW1",
    "source_type": "mdrive-pb"
  },
  "source_meta": {
    "source_mode": "git_branch",
    "branch": "master",
    "commit": "abc123",
    "file_path": "vehicle_name/XZA000005/vehicle_config.pb.txt",
    "repo_dir": null,
    "tree_url": null
  },
  "paths": {
    "calib_source_path": "/abs/vehicle_config.pb.txt",
    "static_data_dir": "/data/03_single_frame/DAS_HW1_XZA000005/static_data",
    "dynamic_data_root_dir": null,
    "trace_root_dir": "/data/04.calibration_results/DAS_HW1_XZA000005/static_calibration/20260611",
    "result_root_dir": "/data/04.calibration_results"
  },
  "issues": [],
  "version": {
    "context_timestamp": "20260611-151851-341979",
    "context_hash": "sha256:...",
    "context_id": "vc_20260611-151851-341979_0123456789abcdef",
    "writer": "D10.vehicle_info_manager",
    "write_policy": "d10-only"
  }
}
```

`source_type` 只使用 `mdrive-pb` 或 `mdrive4-json`。`source_mode="git_branch"` 时必须同时具备 `branch`、`commit`、`file_path`，缺任一字段会得到 `state="error"` 和 `git_source_meta_missing`。D10 context 的冻结身份字段只允许出现在 `identity.vehicle_model_id`、`identity.vehicle_id`、`identity.repo_vehicle_model` 和 `identity.source_type`；旧字段 `car_model_id`、`car_id`、`repo_car_model` 只作为兼容输入 alias，不会出现在 `VehicleContext.to_dict()` 输出中。

每次 D10 写入会生成顶层 `version` 区块：`context_timestamp` 使用 `YYYYMMDD-HHMMSS-ffffff`；`context_hash` 基于 canonical JSON 计算，输入包含 `schema_version/context_timestamp/identity/source_meta/paths/state/issues`；`context_id = vc_{context_timestamp}_{hash16}`。修改已有文件必须传 `expected_context_id`，且写入前会重新校验已有文件 hash；外部手动改 JSON、非 D10 writer、旧车辆字段都会使 `load/verify` 失败。

禁止把 `car_model_id`、`vehicle_model_id`、`repo_vehicle_model`、`car_model_list` 或类似旧身份字段作为顶层、`source_meta`、`paths`、`workflow` 中的持久事实源写入 `vehicle_context.json`。跨 workflow 的 `workflow_confirmed_inputs.json` 只应保存 `vehicle_context_path` 和 `context_id`；其它模块若需要 `vehicle_model_id` 或 `repo_vehicle_model`，必须从 D10 context 派生为展示、报告或底层 CLI 兼容参数，不能重新持久化为事实源。

Worker/Test/Validator agent 只能读取 `vehicle_context_path`：

```python
from vehicle_info_manager import detect_vehicle_context_drift, load_vehicle_context

context = load_vehicle_context("goal/vehicle_context.json")
drift = detect_vehicle_context_drift(context, static_data_dir="/runtime/static_data")
if drift.state == "error":
    print(drift.to_dict())  # 上报 drift signal，不自行覆盖 context
```

Goal Agent 是唯一可更新全局 `vehicle_context.json` 的角色；child agent 发现 PB、路径或 Git 元数据冲突时，只提交 drift/issue 证据。

只读接口：

```python
load_vehicle_context(path, require_ok=False)
get_vehicle_context_status(path)
verify_vehicle_context_integrity(path)
detect_vehicle_context_drift(context, dynamic_data_root_dir="/runtime/path")
```

受控写入接口：

```python
create_vehicle_context(...)
write_vehicle_context(context, path, expected_context_id=None, allow_create=True)
update_vehicle_context(path, new_context, expected_context_id="vc_...")
```

## Automatic vs Manual Behavior

这个包默认偏向“本地信息聚合 + 自动自检”，但不会隐式触网或隐式下载 PB。

自动发生的行为：

- `VehicleInfoManager.from_pb_path(...)`、`from_vehicle_model_id(...)`、`from_vehicle_id(...)` 构造对象后会立即调用一致性检查，并更新 `info_status`。
- `manager.update(...)`、`manager.set_pb_path(...)`、`manager.set_git_info(...)` 修改信息后会立即复检，并返回新的 `InfoStatus`。
- 默认会通过 `print("[vehicle-info][WARNING] ...")` 输出 warning/error issue，便于脚本和 agent 直接看到异常线索。
- 配置会自动读取 `MINIEYE_CONFIG_FILE`，若未设置则读取 `~/.minieye_config/config.toml`；两者都不存在时使用内置默认路径。

必须手动显式触发的行为：

- 只有 `fetch_pb(...)`、`fetch_by_vehicle_id(...)`、CLI `fetch-pb`、CLI `fetch-pb-by-vehicle-id` 会通过 `minieye-calibration-git` 访问 Git 或下载 PB。
- 只有 `sync_mdrive4_vehicle_cfg(...)`、CLI `mdrive4-sync-vehicle-cfg` 会通过 `minieye-calibration-git>=0.0.2` clone/fetch Mdrive4 `vehicle_cfg`。
- `validate_consistency(print_warnings=False)` 可用于脚本中静默复检，不向 stdout 打印 warning。
- `infer_git_source(...)` / CLI `infer-git-source` 只在显式调用时才会委托 `minieye-calibration-git` 向上查找 `.git` 并读取 Git 元数据。

推荐规则：如果只是判断本地 PB/JSON、`vehicle_model_id`、路径和显式字段是否一致，使用本地 inspect/validate；如果明确需要从 Git 获取 PB 或 Mdrive4 JSON repo，再调用 `fetch_*` 或 `sync_mdrive4_vehicle_cfg`。

## CLI

```bash
vehicle-info inspect-identity --source-type pb --source-path vehicle_config.pb.txt --pretty
vehicle-info inspect-identity --source-type json --source-path vehicle_cfg.json --expected-vehicle-id XZT500021 --pretty
vehicle-info paths DAS_HW1_XZA000005 --pretty
vehicle-info complete-identity --source-type pb --source-path vehicle_config.pb.txt --vehicle-model-id DAS_HW1_XZA000005 --output-path vehicle_config.completed.pb.txt
vehicle-info complete-identity --source-type json --source-path vehicle_cfg.json --vehicle-id XZT500021 --output-path vehicle_cfg.completed.json
vehicle-info validate --vehicle-id XZA000005 --file-path vehicle_name/XZA000005/vehicle_config.pb.txt
vehicle-info infer-git-source vehicle_config.pb.txt --pretty
vehicle-info mdrive4-paths ECAR_HW4_XZT500021 --repo-vehicle-model ECAR_HW4 --pretty
vehicle-info mdrive4-sync-vehicle-cfg --repo-dir /tmp/vehicle_cfg_checkout --branch feat_ecar --pretty
vehicle-info mdrive4-validate ECAR_HW4_XZT500021 --repo-dir /tmp/vehicle_cfg_checkout --repo-path vehicle/ECAR_HW4/XZT500021/calib --pretty
vehicle-info vehicle-context from-pb --pb-path vehicle_config.pb.txt --expected-vehicle-model-id DAS_HW1_XZA000005 --output-path goal/vehicle_context.json --pretty
vehicle-info vehicle-context show --path goal/vehicle_context.json --pretty
vehicle-info vehicle-context from-mdrive4-json --calib-json-dir /tmp/vehicle/ECAR_HW4/XZT500021/calib --output-path vehicle_context.json --pretty
vehicle-info vehicle-context status --path vehicle_context.json --pretty
vehicle-info vehicle-context verify --path vehicle_context.json --pretty
vehicle-info vehicle-context drift --path vehicle_context.json --static-data-dir /abs/path/static_data --pretty
vehicle-info vehicle-context update --path vehicle_context.json --expected-context-id vc_... --trace-root-dir /abs/path/new_trace --pretty
```

`complete-identity` 必须显式传 `--output-path`，不支持 `--force`，不会默认原地修改，也不会覆盖冲突值。PB 补全需要 `--vehicle-model-id`，JSON 补全只写顶层 `vehicle_id` 且需要 `--vehicle-id`。成功时输出 JSON；冲突时非 0 退出，并在 stderr 中报告具体冲突字段。

CLI 只注册新身份参数：`--vehicle-model-id`、`--vehicle-id`、`--repo-vehicle-model`。旧 `--car-*`、`--car_model` 和 `--car-model` 不提供 alias，也没有 deprecated warning；传入会被 argparse 直接拒绝，help 中不会展示旧参数。

Network or Git fetch is never automatic. PB download only happens through explicit calls:

```bash
vehicle-info fetch-pb --branch master --file-path vehicle_name/XZA000005/vehicle_config.pb.txt
vehicle-info fetch-pb-by-vehicle-id XZA000005 --branch master
```

默认 PB run 根目录为 `{local_disk_dir}/00_temp/vehicle_info_manager/mdrive_conf/{timestamp}`。D10 在该目录下管理 `.lock`、`status.json` 和最终 PB 输出 `{timestamp}/pb/{relative_file}`；实际 Git 读取委托 D17，D17 的 fresh snapshot 位于 `{timestamp}/d17_snapshots`，中间下载位于 `{timestamp}/d17_download`。`timestamp` 默认格式为 `YYYYMMDD-HHMMSS-ffffff`；显式传入且目录已存在时会失败。返回 JSON 保持兼容，仍包含 `timestamp`、`git_work_dir`、`git_repo_dir`。

Mdrive4 `vehicle_cfg` 同步也必须显式执行。推荐 CLI 两步：

```bash
vehicle-info mdrive4-sync-vehicle-cfg --branch feat_ecar --repo-path vehicle/ECAR_HW4/XZT500021/calib --pretty
vehicle-info mdrive4-validate ECAR_HW4_XZT500021 --repo-dir /path/from/sync-output/repo --repo-path vehicle/ECAR_HW4/XZT500021/calib --pretty
```

默认 Mdrive4 Git 工作区为 `{mdrive4_data_dir}/00_temp/vehicle_info_manager/vehicle_cfg/{timestamp}/repo`。返回 JSON 包含 `timestamp` 和 `vehicle_cfg_git_work_dir`；`calib_dir` 指向本次独立 repo 下的 `repo_path`。

## Configuration

The package reads `MINIEYE_CONFIG_FILE` first, then `~/.minieye_config/config.toml`.

Supported keys:

```toml
[paths]
local_disk_dir = "/data"
record_data_dir = "/data/01_record_data"
parsed_frames_dir = "/data/02_parsed_frames"
single_frame_dir = "/data/03_single_frame"
calib_results_dir = "/data/calib_results"
feishu_report_info_dir = "/data/feishu_report_info"
temp_base_dir = "/data/temp"
mdrive4_data_dir = "/data/mdrive4-json"

[git]
mdrive_conf_remote_url = "/path/to/mdrive_conf"
mdrive_conf_default_branch = "master"
vehicle_cfg_remote_url = "/path/to/vehicle_cfg"
vehicle_cfg_default_branch = "feat_ecar"
vehicle_cfg_default_repo_path = "vehicle/ECAR_HW4/XZT500021/calib"
vehicle_cfg_tree_url_base = "https://gitlab.example.com/group/vehicle_cfg"
```

`temp_base_dir` 仍用于非 Git 的历史临时路径兼容，不再作为 PB 或 Mdrive4 Git 默认拉取根。PB Git 默认从 `local_disk_dir/00_temp` 派生；Mdrive4 Git 默认从 `mdrive4_data_dir/00_temp` 派生。

`vehicle_cfg_*` 是 Mdrive4 JSON `vehicle_cfg` 的标准配置字段；legacy `mdrive4_vehicle_cfg_*` 仅用于过渡兼容。`vehicle_cfg_default_branch` 和 `vehicle_cfg_default_repo_path` 是可选配置项，上面的 `feat_ecar` 与 `vehicle/ECAR_HW4/XZT500021/calib` 只是示例值。调用 `sync_mdrive4_vehicle_cfg(...)` 时必须通过参数或配置提供 `remote_url`、`branch`、`repo_path`。

## Mdrive4 Path Layout

`build_mdrive4_paths(vehicle_model_id, repo_vehicle_model, config, input_data_type=None, soc_name=None)` 按 Mdrive4 L1-L5 生成路径：

```text
{mdrive4_data_dir}/01_mcap_data/{vehicle_model_id}
{mdrive4_data_dir}/02_parsed_frames/{vehicle_model_id}
{mdrive4_data_dir}/03_single_frame/{vehicle_model_id}
{mdrive4_data_dir}/04.calibration_results/{vehicle_model_id}
{mdrive4_data_dir}/05.feishu_report_info/{vehicle_model_id}
```

如果传入 `input_data_type` 或 `soc_name`，返回值会额外包含对应的 `*_input_data_dir` 和 `*_soc_dir` 下钻路径；基础 L1-L5 路径保持不变。

## Validation

Warnings are printed as:

```text
[vehicle-info][WARNING] code: message field=... expected=... actual=...
```

Conflicts between explicit `vehicle_model_id`/`vehicle_id`, PB-derived `vehicle_info.model/id`, JSON top-level `vehicle_id`, path-derived identity, or Git `vehicle_name/{vehicle_id}` are marked as `error`.

## Common Agent Use Cases

### 检查本地 PB 是否包含完整 `vehicle_info.model/id`

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_pb_path("vehicle_config.pb.txt")
if manager.info_status.state != "ok":
    print(manager.info_status.to_dict())
```

缺少 `vehicle_info`、`vehicle_info.model` 或 `vehicle_info.id` 时会产生 error，并记录在 `manager.info_status.issues`。

### 验证期望 `vehicle_model_id` 与 PB 是否一致

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_pb_path(
    "vehicle_config.pb.txt",
    expected_vehicle_model_id="DAS_HW1_XZA000005",
)
if manager.info_status.state == "error":
    raise ValueError(manager.info_status.last_check_summary)
```

当显式期望值与 PB 中的 `vehicle_info.model/id` 组合结果不一致时，`info_status.state` 会变成 `error`。

### 只根据 `vehicle_model_id` 生成 F00 L1-L5 路径

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_vehicle_model_id("DAS_HW1_XZA000005")
paths = manager.f00_paths
print(paths["l1_record_data_dir"])
print(paths["l2_parsed_frames_dir"])
print(paths["l3_single_frame_dir"])
print(paths["l4_calib_result_dir"])
print(paths["l5_feishu_report_dir"])
print(paths["temp_work_dir"])
```

这个用法不会检查这些目录是否真实存在，只负责按配置生成标准路径。
`l2_parsed_frames_dir` 是标准 L2 输出键，指向 `02_parsed_frames`；`l2_parsed_data_dir` 作为历史兼容键暂时保留，值与 `l2_parsed_frames_dir` 相同。

### 已知 `vehicle_id` 时记录 Git 条件但不下载

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_vehicle_id("XZA000005", branch="master", commit=None)
manager.set_git_info(file_path="vehicle_name/XZA000005/vehicle_config.pb.txt")
print(manager.to_dict())
```

这个流程只做字段记录和一致性检查；即使提供 `branch`、`commit`、`file_path`，也不会访问 Git。

### 明确需要 PB 时通过 Git 下载

```python
from vehicle_info_manager import VehicleInfoManager, fetch_by_vehicle_id, fetch_pb

pb_info = fetch_pb(
    "master",
    "vehicle_name/XZA000005/vehicle_config.pb.txt",
)
manager = VehicleInfoManager.from_pb_path(pb_info.pb_path)
manager.set_git_info(
    branch=pb_info.branch,
    commit=pb_info.commit,
    file_path=pb_info.file_path,
    gitlab_url=pb_info.gitlab_url,
)
```

```python
pb_info = fetch_by_vehicle_id("XZA000005", branch="master")
manager = VehicleInfoManager.from_pb_path(pb_info.pb_path)
```

`fetch_by_vehicle_id(...)` 要求 Git 中按 `vehicle_id` 找到唯一 PB；找不到或找到多个候选都会抛出 `ValueError`。
`fetch_pb(...)` 和 `fetch_by_vehicle_id(...)` 每次都在 `{local_disk_dir}/00_temp/vehicle_info_manager/mdrive_conf/{timestamp}` 创建独立 run，写入 `.lock` 和 `status.json`；D10 不再自行 clone `mdrive_conf`，而是把 Git 读取委托给 D17，并将 D17 snapshot 固定在本次 run 的 `d17_snapshots` 下。它不会复用或修改 `temp_base_dir/vehicle_info_manager/mdrive_conf_repo` 这类历史共享 checkout。

### 在脚本中根据 `info_status.state` 阻断 `error`

```python
from vehicle_info_manager import VehicleInfoManager

manager = VehicleInfoManager.from_pb_path("vehicle_config.pb.txt")
status = manager.validate_consistency(print_warnings=False)
if status.state == "error":
    raise SystemExit(status.to_dict())
if status.state == "warning":
    print(status.to_dict())
```

建议 agent 将 `error` 作为硬阻断，将 `warning` 作为需要上报但不一定中断的状态。
