# 车辆数据中台一期数据模型

## 1. 数据职责原则

| 存储 | 负责 | 不负责 |
| --- | --- | --- |
| Redis DB 50 | 最新状态、在线 TTL、字段级当前值、热查询缓存 | 历史事实、告警事件、规则、导出任务 |
| TDengine `lingniu_vehicle_ts` | 高频 RAW 证据、位置/稳定时序历史 | 车辆档案、权限、工作流状态 |
| MySQL `lingniu_vehicle_data` | 车辆主数据、身份映射、接入配置、当前业务投影、统计、规则、事件、通知、导出任务 | 每帧完整历史和无限增长的遥测明细 |

统一业务主键为 VIN。车牌、JT808 手机号、设备号、平台标识只是可变标识，通过映射解析到 VIN；无法解析 VIN 的数据仍留 RAW/接入排障，不进入正式车辆统计。

## 2. 当前 TDengine 模型

### 2.1 `raw_frames` stable

列：`ts, frame_id, event_id, message_id, event_time, received_at, raw_size_bytes, raw_hex, raw_text, parsed_json, parse_status, parse_error, source_endpoint`。

Tags：`protocol, vehicle_key, vin, phone, device_id`。

用途：证明收到什么、何时收到、解析成什么。`parsed_json` 物理列保存 Gateway 已一次扁平化的 `parsed_fields`，是动态协议字段的历史证据。新字段默认先进入这里，不为每个厂家字段立即新增物理列。

### 2.2 `raw_frame_payload_chunks` stable

列：事件/帧、接收时间、payload 类型、分片序号/总数、分片文本；Tags 与 RAW 身份类似。

用途：保存超过主表列长度的 raw/parsed payload，不参与常规业务查询。

### 2.3 `vehicle_locations` stable

列：`ts, event_id, received_at, longitude, latitude, altitude_m, speed_kmh, direction_deg, alarm_flag, status_flag, total_mileage_km`。

Tags：`protocol, vin`。

用途：VIN 非空车辆的高频位置、速度、方向、告警/状态位、总里程历史。当前平台 DTO 尚未完整透出方向、告警和状态位，也没有 SOC 列；轨迹一期需先补 DTO/查询，SOC 可从 RAW 指标取值或按验证后的需求物化。

数据库当前配置 `KEEP 7300 DURATION 10 BUFFER 256`。保留期和磁盘容量应纳入运维监控，不在业务页面查询时临时改变。

## 3. 当前 Redis 模型

| Key | 值/用途 | 约束 |
| --- | --- | --- |
| `vehicle:latest:{vin}` | 跨协议最新核心字段 | 仅 VIN 非空；轻量快照 |
| `vehicle:latest:{vin}:{protocol}` | 单协议最新核心字段 | 可过期/重建 |
| `vehicle:realtime-raw:{protocol}:{vin}` | 单协议最新完整 parsed 状态 | Redis 中完整协议字段唯一副本 |
| `vehicle:rt-kv:{protocol}:{vin}:values` | 扁平字段当前值 | 与类型/时间/meta 配套 |
| `...:types` | 字段值类型 | 支持动态展示和规则类型校验的实时侧依据 |
| `...:times` | 每字段最新归一事件时间 | 防止乱序旧帧覆盖 |
| `...:meta` | 最新事件/接收时间、映射版本 | 用于新鲜度和追踪 |
| `vehicle:online:{protocol}:{vin}` | TTL 在线状态 | 在线事实的快速判断，不应永久保存 |
| `vehicle:online-state:{protocol}:{vin}` | 可分页的 Hash 副本 | 用于在线列表 |
| `vehicle:protocols:{vin}` | 最近出现协议集合 | 车辆来源发现 |
| `vehicle:last_seen` | 最近活跃车辆 ZSET | 分页/排序候选 |

生产只允许 `nats-fast-writer` 写 Redis 当前态；Kafka 慢链路的 realtime writer 不应覆盖快路径。在线产品状态应由“最新时间 + 配置阈值”计算并返回阈值/依据，不能只信一个固定 Boolean。

## 4. 当前 MySQL 模型

### 4.1 身份与档案

| 表 | 主键/关键字段 | 用途 |
| --- | --- | --- |
| `vehicle` | `vin`；`plate, oem, enabled, updated_at` | 轻量车辆主实体；当前字段不足一期完整档案 |
| `vehicle_identifier` | `(protocol, source_code, identifier_type, identifier_value)`；VIN/车牌/OEM | 多协议、多来源、多标识解析到 VIN |
| `vehicle_identity_binding` | VIN；车牌、phone、OEM | 外部维护的兼容映射事实，运行期只读 |
| `jt808_registration` | `phone`；device、plate、VIN、厂家、鉴权、来源、首次/最近注册鉴权/出现时间 | JT808 注册鉴权与绑定证据 |

### 4.2 当前态与来源

| 表 | 主键/关键字段 | 用途 |
| --- | --- | --- |
| `vehicle_realtime_snapshot` | `(protocol, vin)`；plate、platform、peer、扁平 `parsed_json`、event/received time | 每协议每 VIN 最新合并遥测快照 |
| `vehicle_realtime_location` | `(protocol, vin)`；经纬度、速度、总里程、SOC、海拔、方向、alarm/status、event/received time | 每协议每 VIN 最新位置业务缓存 |
| `vehicle_data_source` | 自增 ID，唯一 `(protocol, source_ip)`；source code/kind、platform、priority、enabled、first/latest seen | 数据来源配置和可信源选择 |

### 4.3 日里程

| 表 | 主键/关键字段 | 用途 |
| --- | --- | --- |
| `vehicle_daily_mileage_source` | `(vin, stat_date, protocol, source_key)`；首末里程、样本、质量、是否选中 | 多来源日里程事实和质量解释 |
| `vehicle_daily_mileage` | `(vin, stat_date, protocol)`；source ID、日里程、最新总里程 | 对外查询结果投影 |

当前明确不存在/不应恢复的旧模型包括泛化 `vehicle_daily_metric`、TDengine `vehicle_mileage_points` 和 `raw_frames.fields_json` 重复列。

## 5. 一期统一领域模型

### 5.1 车辆标识

```text
VehicleId          = VIN（内部规范化大写、去空格）
VehicleIdentifier  = protocol + sourceCode + type + value -> VIN
Plate              = 可变展示标识，不作为跨表唯一主键
Source             = protocol + sourceCode/sourceId + endpoint
```

所有 API 都以 VIN 作为详情路径主键；搜索接口可接受车牌、VIN、车辆编号/手机号并返回解析结果。多协议数据保留 `protocol` 和 `sourceId` 做归因，不能在合并时丢失来源。

### 5.2 时间模型

- `eventTime`：车端/协议事件时间，是轨迹、曲线和告警判断的主要时间。
- `receivedAt`：平台接收时间，用于延迟、迟到数据和链路健康。
- `storedAt/updatedAt`：投影或业务记录更新时间，不冒充车辆上报时间。
- `dataDelaySec = receivedAt - eventTime`；`silenceSec = now - max(receivedAt/eventTime 的已确认口径)`。

### 5.3 在线状态

```text
never_reported: 无任何 latestReceivedAt
online:         silenceSec <= thresholdSec
offline:        silenceSec > thresholdSec
unknown:        时间非法、未来时间过大或来源状态无法判断
```

行驶/静止是独立 motion 状态，建议以速度阈值 + 最近更新时间判断；告警也是独立状态。不要把 `online/running/alarm` 压成一个互斥枚举，UI 可基于多维状态决定主色和徽标。

### 5.4 指标值

```text
MetricValue {
  vehicleId, metricKey, value, valueType, unit,
  eventTime, receivedAt, protocol, sourceId,
  quality, mappingVersion
}
```

`metricKey` 是协议无关的规范键；协议原字段通过映射表关联。厂家扩展字段允许命名空间，但必须有显示名、类型、分类和单位后才可进入图表/告警。

## 6. 建议新增 MySQL 模型

### 6.1 档案与组织

建议优先用关联表而非不断扩宽 `vehicle`：

- `vehicle_profile(vin PK, vehicle_no, model_id, vehicle_type, company_id, operation_status, access_vendor_id, first_access_at, ...)`
- `vehicle_model(id, oem_id, code, name, vehicle_type, ...)`
- `organization(id, parent_id, type, code, name, enabled, ...)`
- `access_vendor(id, code, name, enabled, ...)`

外部主数据通过受控同步写入 `source_system/source_version/synced_at`。默认策略保护 `manual` 和其他外部来源；只有管理员显式选择 takeover 才能改变所有权。同一来源版本相同载荷幂等，不同载荷冲突，避免上游版本不可变性被破坏；每次创建/更新都进入 `vehicle_profile_audit`。

### 6.2 指标目录

- `metric_definition(metric_key PK, display_name, category, value_type, unit, precision, chartable, alertable, enabled, sort_order, version)`
- `metric_protocol_mapping(metric_key, protocol, source_field, transform, mapping_version, enabled)`

`transform` 不能存任意可执行代码；使用有限转换类型或由版本化 Go 映射实现。目录服务可缓存，变更有版本和审计。

### 6.3 告警域

- `alert_rule(id, name, description, metric_key, operator, threshold_json, severity, duration_sec, recovery_json, repeat_interval_sec, enabled, version, created_by, updated_by, timestamps)`
- `alert_rule_scope(rule_id, scope_type, scope_value)`：vehicle/model/oem/protocol/company；一期可限制组合复杂度。
- `alert_event(id, rule_id, rule_version, vin, protocol, source_id, status, trigger_value_json, threshold_json, first_triggered_at, last_triggered_at, recovered_at, closed_at, location_json, dedupe_key, assignee, timestamps)`
- `alert_event_action(id, event_id, action, from_status, to_status, operator_id, note, created_at)`
- `notification(id, user_id, event_id, title, body, read_at, created_at)`

`alert_event.dedupe_key` 建唯一约束或等价幂等机制；阈值快照和 rule version 必须保存在事件上，避免规则修改后历史无法解释。

当前实现使用带业务前缀的实体表，完整 DDL 位于 `vehicle-data-platform/deploy/migrations/002_alert_center.sql`：

- `vehicle_alert_rule` + `vehicle_alert_rule_audit`：当前规则与每版本不可变快照。
- `vehicle_alert_candidate`：`(rule_id, vin, protocol)` 持续命中计时，避免单帧达到阈值就误开事件。
- `vehicle_alert_stream_checkpoint`：`(consumer_group, topic, partition_id)` 保存数据库权威 `next_offset`、Kafka high watermark、处理/非法/迟到/回放计数、最近事件证据以及最近非法代码/时间。流 worker 先提交该事务，再提交 Kafka group offset；崩溃后重复抓取的 offset 会被数据库 checkpoint 跳过。累计非法数用于审计，健康状态只对五分钟内仍在发生的非法消息告警。
- `vehicle_alert_event`：保存规则名/版本、触发阈值快照、证据时间、位置和乐观锁版本；`fingerprint + active status` 用于评估器幂等检查。
- `vehicle_alert_event_action`：触发、恢复和人工处置的不可变时间线。
- `vehicle_alert_notification`：站内通知真实已读状态；外部通道只有 `reserved`，没有配置供应商时绝不标记 `sent`。
- `vehicle_alert_rule_state`：为 Boolean `changed` 规则保存每个 rule/VIN/protocol 的最近观测值；`003` 同时增加区间上限和 OEM 范围，`004` 为 fingerprint 重复间隔查询增加索引。

一期 evaluator 读取 `vehicle_realtime_location` 的当前快照并用 candidate 表证明持续时长。除 `freshness_sec` 明确按平台墙钟增长外，候选只允许由不同 `source_event_id` 且事件时间不回退的新观测推进；重复快照不能制造持续时长，迟到观测不能回退 candidate 或 Boolean 状态。规则行会在评估事务内加锁，停用规则、清理 candidate/Boolean 状态和写版本审计在同一事务提交，从而避免 evaluator 与管理员停用并发后留下幽灵候选。它仍不是 Kafka event-time 可重放评估器，因此跨当前快照的迟到数据重算、历史窗口规则和消费 offset 仍是阶段 8 的后续能力。

`alert-stream-evaluator` 已以 active 模式消费三类 canonical fields topic。它复用 Kafka consumer group 的分区顺序，校验 `event_kind/field_mapping/protocol/VIN/source_event_id/字段命名空间`，用接收时间识别超过可配置窗口的迟到观测。动态规则锁、当前批次范围内的 candidate/Boolean/活跃事件/重复窗口、事件/动作/通知副作用和 checkpoint 在同一个 MySQL 事务内完成，成功后才 commit Kafka；数据库 ahead 时的重复 offset 由 checkpoint 跳过。超过迟到窗口的观测只允许驱动 `data_delay_sec`，不能开关速度/SOC/告警位事件。快照 evaluator 在 active 模式只负责按墙钟增长的 `freshness_sec`，不再读取动态规则。

### 6.4 导出域

- `export_job(id, user_id, type, format, query_json, status, progress, estimated_rows, exported_rows, file_uri, file_size, checksum, error_code, error_message, started_at, completed_at, expires_at, created_at)`

索引至少覆盖 `(user_id, created_at)`、`(status, created_at)` 和过期清理。`query_json` 保存规范化查询快照，不保存数据库密码或签名下载 URL。

### 6.5 在线阈值与审计

- `online_threshold(scope_type, scope_value, threshold_sec, version, updated_by, updated_at)`，一期建议 scope 仅 global/protocol。
- `audit_log(id, actor, action, resource_type, resource_id, before_json, after_json, trace_id, created_at)`。

当前 V2 接入管理已先落地等价的窄表：

- `vehicle_access_threshold_config(id=1, version, default_threshold_sec, delay_threshold_sec, long_offline_sec, protocol_overrides_json, updated_by, updated_at)`。
- `vehicle_access_threshold_audit(id, version, actor, summary, config_json, changed_at)`。

表仅向前新增，不改变 gateway 的 `vehicle_realtime_snapshot(protocol, vin)` 主键与写入语义。后续统一审计域上线时可迁移到通用 `audit_log`，但必须保留版本历史。

## 7. 查询与物化策略

1. 实时页面优先 Redis，MySQL realtime 作为业务查询/降级投影；返回值统一由 BFF 归一化。
2. 轨迹只查 TDengine `vehicle_locations`，必要的 SOC/告警补充应避免对每个点逐条回查 RAW；先验证是否需要将 SOC 提升为位置列或建立专用指标 stable。
3. 通用历史指标首版可从 `raw_frames.parsed_json` 按白名单提取，但必须限制车辆数、指标数、时间范围并做基准；高频稳定指标按实际查询热度物化为专用时序模型。
4. 档案和字典在 BFF/Redis 做版本化短缓存，避免每次实时请求重复查不变数据。
5. 地图聚合可从 MySQL realtime/Redis 快照构建短周期服务端缓存；不要把地理历史查询压到实时表，也不要把全量点放浏览器聚合。
6. 告警 worker 使用 Kafka 可重放流和 eventTime，MySQL 保存工作流事实；Redis 可保存短期去重/规则缓存但不是唯一事实。

## 8. 数据质量与治理要求

- 数值同时记录单位和精度；未知单位不得参与跨协议比较或规则判断。
- 原始值、规范值和转换版本可追踪；异常值标记不直接篡改 RAW。
- 未来时间、经纬度越界、速度突变、里程回退、重复事件均定义质量码。
- 轨迹过滤返回算法版本、原始/过滤/抽稀数量，可选择查看未过滤证据。
- 协议新增字段先进入扁平 RAW + Redis KV，经目录登记后才进入 UI；形成稳定高频查询后再物化。
- 所有新增表、字段、索引、保留期和清理任务随 migration 与数据字典发布。

## 9. 容量和保留

- 约 1,000 到 10,000 车辆、5～30 秒上报意味着时序写入持续增长；TDengine 查询必须带 VIN/时间，禁止开放无界扫描。
- RAW 保留期、位置保留期、导出文件保留期和告警/审计保留期分别配置，不能共用一个默认值。
- MySQL 告警事件和审计按时间索引并规划归档；导出任务定时过期，文件删除和 DB 状态更新需幂等。
- 单 ECS 上告警和导出 worker 设置独立并发、内存、CPU 和 systemd 限制，避免影响接入写链路。

## 10. 仍需确认的数据问题

1. `vehicle_identity_binding` 的实际 DDL、权威维护方和未来是否继续兼容。
2. 车型、车辆类型、运营公司、车辆编号、运营状态、接入厂家的权威来源与更新频率。
3. 各协议 SOC、方向、启动、告警等级、电机/燃料电池等规范字段映射和单位。
4. “累计运行时长”是已有车端字段、由状态积分计算，还是一期可不提供。
5. 在线时间基准使用 eventTime 还是 receivedAt，以及不同协议的默认阈值。
6. 轨迹停车阈值、漂移判定、迟到点容忍和跨协议主轨迹选择规则。
7. 导出存储与保留期、单用户并发/最大行数。
8. 告警规则适用范围组合、迟到窗口、重复/恢复语义和事件归属人规则。
