# NavSea 可追溯映射体系设计 v1

版本：v1 Draft  
用途：定义 NavSea “旧版原始数据 -> 新版语义字段 / 渲染字段”的可追溯映射体系，确保后续可以稳定定位分类错误、渲染错误和规则变更影响。

## 1 目标

这套设计解决的不是“如何分类”本身，而是“分类和渲染映射的过程如何可追溯”。

必须满足：

- 能知道某个 feature 命中了哪条 taxonomy 规则
- 能知道某个 feature 命中了哪条 render 规则
- 能知道规则来自哪个版本
- 能知道规则判断时参考了哪些原始字段
- 能从最终 `pbf` 或 `fid` 回查到完整映射过程

一句话概括：

> 最终结果不是终点，规则命中过程也必须被记录成正式资产。

## 2 为什么必须做可追溯

如果只保留结果字段，例如：

- `canonical_family`
- `canonical_object_type`
- `detection_key`
- `chart_render_type`

那后续虽然能看到“结果是什么”，但看不到：

- 为什么会分成这个结果
- 是哪条规则起了作用
- 如果结果错了，到底该改 taxonomy 还是改 render mapping
- 某次规则调整影响了哪些 feature

而 NavSea 后续一定会反复遇到这些问题：

- 某个对象分错类
- 某类对象在新样式里显示错误
- 某次规则升级导致历史区域表现变化
- 检索命中和渲染表现不一致

这时如果没有 trace，就只能人工猜。

## 3 设计原则

### 3.1 文件是规则源头

规则的 source of truth 必须是版本可控文件，而不是代码里的隐式判断，也不是数据库里唯一的一份规则。

建议：

- taxonomy 规则放 YAML/CSV 文件
- render mapping 规则放 YAML/CSV 文件
- 文档文件负责解释，不作为程序执行源

### 3.2 SQL 是运行时载体

数据库负责：

- 导入规则
- 物化计算结果
- 记录 feature 命中过程
- 支撑查询和审计

但数据库不是唯一规则源头。

### 3.3 结果和过程都要存

至少要同时存两类数据：

- 规则定义
- feature 命中结果

只存最终结果表不够。

### 3.4 规则必须有稳定 ID

每条规则必须有稳定 `rule_id`，不能只靠“第几条规则”或描述文本识别。

### 3.5 规则必须有版本

每次可影响结果的变更都要有版本号或 bundle 版本。

建议使用：

- `bundle_version`
- `rule_revision`

## 4 总体架构

建议分成 4 层。

### 4.1 规则源文件层

建议目录：

- `tasks/pbf/mappings/navsea_taxonomy_rules_v1.yaml`
- `tasks/pbf/mappings/navsea_render_rules_v1.yaml`
- `tasks/pbf/mappings/navsea_rule_bundle_v1.yaml`

职责：

- 人可读
- 可进入 git
- 可 code review
- 可回滚

### 4.2 规则表层

建议导入 MySQL 生成：

- `navsea_rule_bundle`
- `navsea_taxonomy_rules`
- `navsea_render_rules`

职责：

- 运行时查询
- 规则生效控制
- 按版本执行构建

### 4.3 结果表层

建议生成：

- `navsea_feature_taxonomy_result`
- `navsea_feature_render_result`
- `navsea_feature_rule_trace`
- `navsea_mapping_run`

职责：

- 记录某次构建的命中结果
- 记录 feature 与 rule 的关系
- 记录本次运行使用的规则版本

### 4.4 交付层

交付层包括：

- delivery pbf
- engineering pbf

其中：

- delivery pbf 只保留必要结果字段
- engineering pbf 或内部查询表保留 trace 回查能力

## 5 推荐文件结构

建议新增目录：

```text
tasks/pbf/mappings/
  navsea_rule_bundle_v1.yaml
  navsea_taxonomy_rules_v1.yaml
  navsea_render_rules_v1.yaml
```

建议新增导入脚本：

```text
scripts/pbf/
  import_navsea_rules.py
  run_navsea_mapping.py
  export_navsea_trace_report.py
```

## 6 规则文件设计

## 6.1 bundle 文件

bundle 文件用于声明：

- 当前规则集版本
- taxonomy 规则文件版本
- render 规则文件版本
- 适用范围
- 是否为当前默认版本

示例字段：

- `bundle_id`
- `bundle_version`
- `status`
- `effective_date`
- `taxonomy_ruleset`
- `render_ruleset`
- `notes`

## 6.2 taxonomy 规则文件

每条 taxonomy 规则至少包含：

- `rule_id`
- `rule_revision`
- `priority`
- `enabled`
- `match_scope`
- `match_expr`
- `output.canonical_family`
- `output.canonical_object_type`
- `output.detection_key_template`
- `reason`

其中：

- `rule_id` 是稳定主键，例如 `TAX-NAV-001`
- `rule_revision` 是规则修订号，例如 `3`
- `priority` 用于冲突时排序
- `match_expr` 描述匹配条件
- `reason` 解释为什么有这条规则

## 6.3 render 规则文件

每条 render 规则至少包含：

- `rule_id`
- `rule_revision`
- `priority`
- `enabled`
- `match_expr`
- `output.chart_render_type`
- `output.chart_symbol_family`
- `output.chart_symbol_code`
- `output.chart_line_style`
- `output.chart_fill_style`
- `output.chart_text_style`
- `output.chart_priority`
- `reason`

render 规则的输入通常包括：

- `canonical_object_type`
- `canonical_family`
- geometry type
- 必要旧字段

## 7 match_expr 建议

不建议把匹配逻辑写成自由文本。

建议使用结构化表达，例如：

```yaml
match_expr:
  all:
    - field: source_layer
      op: in
      value: [p航路標識群, P航行危険障害物]
    - field: class_name
      op: contains_any
      value: [港湾灯台, 灯柱, 灯浮標]
    - field: geom_type
      op: eq
      value: Point
```

支持的运算建议限制在少数几类：

- `eq`
- `neq`
- `in`
- `contains`
- `contains_any`
- `is_null`
- `not_null`
- `regex`

这样规则可审计、可导入、可执行。

## 8 SQL 表设计

## 8.1 `navsea_rule_bundle`

建议字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `bundle_id` | varchar(64) PK | 规则集 ID |
| `bundle_version` | varchar(32) | 规则集版本 |
| `status` | varchar(20) | `draft` / `active` / `retired` |
| `taxonomy_ruleset` | varchar(64) | taxonomy 文件版本 |
| `render_ruleset` | varchar(64) | render 文件版本 |
| `effective_date` | datetime | 生效时间 |
| `created_at` | datetime | 创建时间 |
| `notes` | text | 备注 |

## 8.2 `navsea_taxonomy_rules`

建议字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `rule_id` | varchar(64) PK | 稳定规则 ID |
| `bundle_id` | varchar(64) | 所属 bundle |
| `rule_revision` | int | 修订号 |
| `priority` | int | 优先级，值越小越先匹配 |
| `enabled` | tinyint | 是否启用 |
| `match_scope` | varchar(32) | `feature` / `layer` / `object` |
| `match_expr_json` | json | 结构化匹配表达式 |
| `canonical_family` | varchar(100) | 输出 family |
| `canonical_object_type` | varchar(191) | 输出 object type |
| `detection_key_template` | varchar(191) | 输出 detection 模板 |
| `rule_reason` | text | 规则说明 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |

## 8.3 `navsea_render_rules`

建议字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `rule_id` | varchar(64) PK | 稳定规则 ID |
| `bundle_id` | varchar(64) | 所属 bundle |
| `rule_revision` | int | 修订号 |
| `priority` | int | 优先级 |
| `enabled` | tinyint | 是否启用 |
| `match_expr_json` | json | 匹配表达式 |
| `chart_render_type` | varchar(32) | 输出 |
| `chart_symbol_family` | varchar(64) | 输出 |
| `chart_symbol_code` | varchar(64) | 输出 |
| `chart_line_style` | varchar(64) | 输出 |
| `chart_fill_style` | varchar(64) | 输出 |
| `chart_text_style` | varchar(64) | 输出 |
| `chart_priority` | int | 输出 |
| `chart_visibility_min` | int | 输出 |
| `chart_visibility_max` | int | 输出 |
| `rule_reason` | text | 规则说明 |
| `created_at` | datetime | 创建时间 |
| `updated_at` | datetime | 更新时间 |

## 8.4 `navsea_mapping_run`

记录一次完整构建执行。

建议字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `run_id` | bigint PK | 执行 ID |
| `bundle_id` | varchar(64) | 本次使用的规则集 |
| `run_type` | varchar(32) | `full` / `aoi` / `qa` |
| `tile_scope` | text | 范围说明 |
| `started_at` | datetime | 开始时间 |
| `finished_at` | datetime | 结束时间 |
| `status` | varchar(20) | `running` / `success` / `failed` |
| `source_snapshot` | varchar(128) | 源数据快照标识 |
| `notes` | text | 备注 |

## 8.5 `navsea_feature_rule_trace`

这是最关键的 trace 表。

建议字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `run_id` | bigint | 所属执行 |
| `feature_id` | bigint | 内部 feature 主键 |
| `fid` | varchar(191) | 原始对象标识 |
| `z` | int | tile z |
| `x` | int | tile x |
| `y` | int | tile y |
| `source_layer` | varchar(100) | 原始 layer |
| `geom_type` | varchar(32) | 几何类型 |
| `taxonomy_rule_id` | varchar(64) | 命中的 taxonomy 规则 |
| `taxonomy_rule_revision` | int | taxonomy 修订号 |
| `render_rule_id` | varchar(64) | 命中的 render 规则 |
| `render_rule_revision` | int | render 修订号 |
| `bundle_id` | varchar(64) | 规则集版本 |
| `classification_basis` | varchar(64) | 使用的主要依据 |
| `source_fields_used_json` | json | 实际参与判定的字段和值摘要 |
| `canonical_family` | varchar(100) | 产出结果 |
| `canonical_object_type` | varchar(191) | 产出结果 |
| `detection_key` | varchar(191) | 产出结果 |
| `chart_render_type` | varchar(32) | 产出结果 |
| `chart_symbol_family` | varchar(64) | 产出结果 |
| `chart_symbol_code` | varchar(64) | 产出结果 |
| `chart_line_style` | varchar(64) | 产出结果 |
| `chart_fill_style` | varchar(64) | 产出结果 |
| `chart_text_style` | varchar(64) | 产出结果 |
| `trace_status` | varchar(20) | `matched` / `fallback` / `manual_review` |
| `created_at` | datetime | 记录时间 |

说明：

- `source_fields_used_json` 不要求保存整份原始属性
- 但必须记录本次命中实际使用的关键字段和值摘要

例如：

```json
{
  "fid": "123456",
  "分類番号": "403",
  "表示用番号": "31135504",
  "名称": "鷹島灯台",
  "geom_type": "Point"
}
```

## 9 交付与追溯分离策略

为了兼顾交付体积和可追溯性，建议分两条线：

### 9.1 delivery pbf

保留：

- 业务必需字段
- taxonomy 结果字段
- render 结果字段
- 必要旧字段

不强制保留：

- 完整 trace 细节

### 9.2 engineering trace

通过以下方式保留完整追溯能力：

- SQL trace 表
- engineering pbf
- 按 `fid` / `feature_id` 回查脚本

建议 engineering pbf 可额外保留：

- `taxonomy_rule_id`
- `render_rule_id`
- `bundle_id`
- `trace_status`

## 10 builder 接入方式

当前 [navsea_tile_builder.py](/root/weather/navsea_tile_builder.py) 是从 `pbf_relayer_candidates` 读取结果并写入 `pbf`。

建议改造成两段式：

### 阶段 A：规则求值阶段

输入：

- 原始 feature
- 规则表

输出：

- `navsea_feature_taxonomy_result`
- `navsea_feature_render_result`
- `navsea_feature_rule_trace`

### 阶段 B：PBF 输出阶段

输入：

- 已物化结果表

输出：

- delivery pbf
- engineering pbf

这样做的好处是：

- “规则判断”与“PBF 编码”解耦
- 同一份判定结果可重复出图
- QA 和回归检查更容易做

## 11 问题排查路径

未来排查一个对象时，建议统一走这条链路：

1. 先从前端或瓦片里拿到 `fid`
2. 用 `fid` 或 `feature_id` 查 `navsea_feature_rule_trace`
3. 看命中的 `taxonomy_rule_id` 和 `render_rule_id`
4. 回到规则表查看该规则定义
5. 必要时回到规则源文件查看变更历史
6. 确认问题属于：
   - 原始数据异常
   - taxonomy 规则错误
   - render 规则错误
   - 样式解释错误

## 12 变更管理

建议所有规则变更都遵循：

- 改规则文件
- 提交 git
- 生成新 `bundle_version`
- 导入 SQL
- 执行局部或全量重算
- 输出差异报告

不建议：

- 直接手改结果表
- 直接在 SQL 里无版本地改规则
- 直接在代码里插入一条临时 if 判断后不回写规则文件

## 13 最小可落地版本

如果要尽快开始，建议第一阶段先做到：

1. 新增规则源文件目录
2. 给 taxonomy 和 render 规则都定义 `rule_id`
3. 新增 `navsea_rule_bundle`
4. 新增 `navsea_feature_rule_trace`
5. builder 在出 engineering 数据时至少写出：
   - `bundle_id`
   - `taxonomy_rule_id`
   - `render_rule_id`
   - `trace_status`

这样即使规则体系还不完整，也已经具备“能追”的能力。

## 14 结论

NavSea 的“旧版 -> 新版”对应关系，不应只是一套结果字段，也不应只存在于代码里。

它应该被正式建设成一套可追溯资产，包括：

- 可版本化的规则文件
- 可运行的 SQL 规则表
- 可查询的 feature trace 表
- 与 delivery / engineering pbf 配套的回查能力

后续查错、验收、回归、争议复盘，都会依赖这套体系。
