release: bump version to 0.47.0

This commit is contained in:
linkong
2026-04-30 16:56:37 +08:00
parent f22079d33a
commit 421234301a
42 changed files with 2501 additions and 454 deletions

View File

@@ -1,6 +1,6 @@
# AIS 多源采集、冲突记录与聚合接口计划
**状态**:规划中
**状态**v0-v3 已实现v4+ 规划中
**创建日期**2026-04-30
**核心原则**:采集器只写原始观测;去重、合并、冲突解释放在聚合接口中完成
@@ -14,6 +14,8 @@
| 冲突处理 | 先记录冲突事实和当前选择原因,后续再开放用户规则配置 |
| 默认可信度 | 同类 AIS 数据源优先按 `delivery_mode` 评估:`realtime_stream` 优于 `batch_stream`,再优于 `polling``snapshot` |
| 过期保护 | 实时流源断流超过 freshness 窗口后,不能仅凭“实时源”身份压过更新的轮询数据 |
| 源健康状态 | 聚合时必须参考采集器健康状态,不能只看配置中的理论优先级 |
| 媒体富化 | 船只图片等媒体信息不进入 AIS 实时聚合主链路,后续单独做 enrichment |
## 背景
@@ -24,7 +26,7 @@
- WebSocket 或其他实时流通常更接近实时,但也可能断流或批量延迟。
- 如果每个 collector 自己做去重合并,规则会分散、不可审计,也很难让用户后续配置“某个字段信任哪个来源”。
因此 v1 不应让采集器直接覆盖最终船只表。更稳的方式是先保留观测事实,再由聚合接口统一给出当前展示视图。
因此第一阶段不应让采集器直接覆盖最终船只表。更稳的方式是先保留观测事实,再由聚合接口统一给出当前展示视图。
## 目标架构
@@ -53,11 +55,36 @@ flowchart LR
| `transport` | `websocket``sse``http``file` 等 |
| `observed_at` | 上游数据时间,优先使用 AIS 消息时间 |
| `collected_at` | 本系统接收或采集时间 |
| `source_message_id` | 上游消息 ID 或可推导 ID没有则为空 |
| `observation_hash` | 幂等去重指纹,用于防止同一来源重复写入同一条观测 |
| `normalized_payload` | 标准化后的 AIS JSON |
| `raw_payload` | 可选,保存原始或裁剪后的上游记录 |
| `quality_flags` | 观测级质量标记,例如 `stale``position_jump``future_timestamp` |
`delivery_mode``transport` 不应混为一谈。WebSocket 是传输方式streaming 是交付模式。聚合可信度主要看 `delivery_mode``transport` 只作为辅助信息。
原始观测层需要做存储级幂等去重,但这里的去重不是业务合并。推荐使用 `source + entity_key + message_type + observed_at + payload_hash` 或上游稳定消息 ID 作为唯一约束,避免 WebSocket 重连、HTTP 重试或批量回放导致同一事实重复入库。
### 源健康状态
每个采集器应维护独立的健康状态,供聚合服务读取:
| 字段 | 用途 |
|-----|------|
| `source` | 采集器标识 |
| `connection_state` | `connected``reconnecting``disconnected``disabled` 等 |
| `last_seen_at` | 最近收到上游消息或响应的时间 |
| `last_success_at` | 最近成功写入观测的时间 |
| `last_error` | 最近错误摘要 |
| `message_rate` | 最近窗口内的消息速率 |
| `lag_seconds` | 上游观测时间与本系统接收时间的延迟 |
聚合优先级不能只看 `source_priority`。例如 `aisstream_vessels` 默认优先于 `barentswatch_vessels`,但如果它处于 `disconnected``lag_seconds` 超过 freshness 窗口,则动态字段应回退到更新的可用来源。
### 身份键边界
v1 可以继续用 MMSI 作为 `entity_key`,因为它是 AIS 动态消息里最稳定、最容易获得的主键。但文档和模型都要为后续扩展留出口MMSI 可能复用、填错或缺少静态信息,后续身份解析应结合 `mmsi + imo + callsign + name + dimensions` 判断是否需要拆分或合并实体。
### 冲突记录层
聚合服务发现同一个实体、同一个字段存在多个非空不同值时,写入冲突记录。冲突记录不代表错误,只代表“有多个可用候选值”。
@@ -90,7 +117,8 @@ flowchart LR
| 动态位置 | `lat``lon``sog``cog``heading``nav_status` | 优先最新 `observed_at`,同时间再按来源优先级 |
| 静态身份 | `name``callsign``imo``flag` | 非空优先,再按字段策略或来源优先级 |
| 静态规格 | `vessel_type``vessel_type_name``length``width``draught` | 非空优先;冲突时记录候选值 |
| 元信息 | `field_sources``conflict_count``selected_reasons` | 聚合接口生成,便于调试和后续 UI 展示 |
| 轨迹点 | `track_points` | 按时间线合并;同一时间窗口内相近点去重;保留点级 `source` |
| 元信息 | `field_sources``conflict_count``selected_reasons``quality_flags` | 聚合接口生成,便于调试和后续 UI 展示 |
### 默认优先级
@@ -124,6 +152,27 @@ freshness:
如果 `aisstream_vessels` 最近 15 分钟没有该 MMSI 的新观测,而 BarentsWatch 轮询源有更新位置,则位置类字段应采用 BarentsWatch 的更新观测,并记录选择原因 `newest_observation``freshness_fallback`
### 异常位置保护
多源 AIS 接入后,聚合服务必须过滤或降权明显异常的位置观测:
- 经纬度必须在合法范围内。
- `observed_at` 不能明显来自未来。
- 同一 MMSI 短时间内跨越不合理距离时,标记 `position_jump`,默认不直接采用该点。
- 当异常点来自当前优先源时,应记录 `selected_reason = anomaly_rejected`,再回退到其他可用来源。
异常保护不应静默丢弃事实。原始观测仍应保留,聚合结果通过 `quality_flags` 和冲突记录解释为什么没有采用它。
### 轨迹聚合
轨迹接口不能简单拼接所有来源,否则前端会出现折返、抖动和重复点。默认规则:
-`observed_at` 排序,生成统一时间线。
- 同一来源的完全重复点通过 `observation_hash` 去重。
- 多来源在短时间窗口内上报的相近位置视为同一轨迹点,优先选择 freshness 和 source priority 更高的一条。
- 每个轨迹点保留 `source``selected_reason` 和必要的 `quality_flags`
- 对被判定为 `position_jump` 的点,默认不进入展示轨迹,但可通过调试参数查看。
## 聚合接口
现有展示接口应逐步改为消费聚合服务,而不是自己直接拼 `VesselPosition + VesselStatic`
@@ -155,6 +204,7 @@ GeoJSON properties 建议增加:
"lat": "newest_observation",
"vessel_type": "non_empty_priority"
},
"quality_flags": [],
"conflict_count": 2
}
```
@@ -231,15 +281,89 @@ aisstream_vessels:
- ShipStaticData
```
## 实施顺序
默认不建议直接订阅全球范围。AISStream 采集器应支持以下订阅策略:
1. 新增原始观测模型和冲突记录模型
2. 实现 AIS 聚合服务,先从现有 `vessel_position` / `vessel_static` 兼容读取,再逐步切换到原始观测层
3.`/geo/vessels``/vessels/{mmsi}` 改为走聚合服务
4. 改造 BarentsWatch 保存逻辑,让它写入原始观测,同时保留现有表作为兼容缓存
5. 实现 AISStream WebSocket collector
6. 接入系统设置中的聚合策略配置。
7. 做冲突治理 UI。
- 使用配置的固定 `bounding_boxes`
- 后续支持按 Earth 当前视口或关注区域动态调整订阅范围
- 支持限制 `message_types`,避免静态信息、位置报告和扩展消息全量涌入
- 断线后使用指数退避重连,并把连接状态写入源健康状态
- 重连后可能收到重复或回放消息,因此必须依赖原始观测层的幂等去重
### 媒体富化边界
VesselFinder 等服务里的船只图片不属于 AIS 实时数据本身。图片、船籍详情、公司信息等后续应作为独立 enrichment 链路:
- 通过 MMSI、IMO、船名等字段异步查询。
- 使用独立缓存和授权配置。
- 不阻塞 `vessel_ais` 实时观测入库。
- 聚合接口只暴露已经缓存好的媒体引用,不在请求链路中现场抓取。
## 版本拆分
计划按 5 个版本推进:
### v0 — 聚合基础设施(已实现)
目标是不改变前端展示行为,先把数据底座铺好。
1. 新增原始观测模型、冲突记录模型和源健康状态模型。
2. 为现有 BarentsWatch collector 写入原始观测,同时保留现有 `vessel_position` / `vessel_static` 兼容写入。
3. 实现存储级 `observation_hash` 幂等去重。
4. 补基础管理命令或调试接口,用于查看某个 MMSI 的原始观测和冲突候选。
### v1 — 聚合读接口(已实现)
目标是让展示接口开始消费聚合结果,但前端形状保持兼容。
1. 实现 AIS 聚合服务,先兼容读取现有表,再逐步切换到原始观测层。
2.`/geo/vessels``/vessels/{mmsi}` 改为走聚合服务。
3.`/vessels/{mmsi}/track` 改为走轨迹聚合逻辑。
4. 返回 `field_sources``selected_reasons``quality_flags``conflict_count`
5. 加入 freshness fallback 和异常位置保护。
### v2 — AISStream WebSocket collector已实现
目标是接入第二个真实 AIS 来源,并验证多源冲突和回退逻辑。
1. 实现 `aisstream_vessels` collector。
2. 支持 API key、订阅范围、消息类型、重连和限流配置。
3. 将 AISStream 写入原始观测层,不直接 upsert 最终展示表。
4. 接入源健康状态和 message rate 统计。
5. 提供 AISStream API Key 获取教程、设置页入口和连接验证支持。
6. 为重复消息、断流回退、WS 优先级写集成测试。
### v3 — AISStream 可用性与配置体验(已实现)
目标是让 AISStream 从“能采集”变成日常可观察、可调试、可配置的数据源。
1. 设置页展示 AISStream 运行状态:连接状态、最近收到、最近成功、本轮消息数、延迟和最近错误。
2. AISStream 设置页提供常用采集范围 preset并保留自定义 Bounding Boxes JSON。
3. 聚合结果返回 `source_summary`,展示每艘船的来源、观测数量、最新观测时间、传输模式和消息类型。
4. 保留 `field_sources``selected_reasons`,用于解释动态字段来自实时流、静态字段来自可用非空来源。
5. 船名标准化会读取 AISStream `MetaData.ShipName`;船型展示会从 `vessel_type_name` 和 AIS 数字 `vessel_type` 共同归一化,保证 marker 颜色、详情卡、hover 和搜索结果一致。
6. `/geo/vessels` 不再默认限制 5000 艘;不传 `limit` 或传 `limit=0` 表示全量返回,前端默认也不再二次裁剪到 5000。
### v4 — 策略配置
目标是开放系统级配置,但仍以安全默认值兜底。
1. 接入系统设置中的聚合策略配置。
2. 支持 source priority、字段级规则、freshness 窗口和高级保护开关。
3. 保存配置时校验未知字段、非法模式和危险动态字段锁定。
4. 聚合接口返回当前命中的配置版本,方便排查。
### v5 — 船舶资料 enrichment 与冲突治理
目标是把 AIS 实时流里不稳定或低频出现的静态信息,补成可缓存、可审计的船舶资料层,同时把冲突解释变成可操作能力。
1. 做冲突治理 UI。
2. 支持把人工选择沉淀成字段级规则。
3. 支持恢复默认策略。
4. 设计 `vessel_profile_enrichment`,按 `mmsi + imo + name + callsign` 异步补充船名、船型细分、AIS 大类、旗国、尺寸、建造年份、运营方等静态资料。
5. 设计 `vessel_media_enrichment`,异步补充船只图片和外部详情缓存。
6. enrichment 结果必须带 `source``fetched_at``expires_at``confidence` 和原始引用,不覆盖 AIS 原始观测。
7. 聚合接口只读取已缓存 enrichment请求链路不现场抓取第三方页面避免慢请求和授权风险。
8. 前端船只详情面板展示已缓存资料和媒体,并标注字段来源,不阻塞 AIS 实时链路。
## 测试计划
@@ -247,8 +371,12 @@ aisstream_vessels:
- 多来源同一 MMSI 的位置字段优先选择最新观测。
- 实时流和轮询源同时间冲突时,实时流优先。
- 实时流过期后,更新的轮询源可以接管动态字段。
- 实时流源健康状态异常时,动态字段可以回退到更新的可用来源。
- 静态字段不会被空值覆盖。
- 静态字段冲突会写入冲突记录。
- 明显异常位置不会进入默认展示轨迹,并会留下 `quality_flags`
- 同一时间窗口内多来源相近轨迹点只展示一个点。
- AISStream 重连或回放导致的重复消息不会重复进入聚合结果。
- 字段级配置可以覆盖默认来源优先级。
- 聚合接口在没有冲突表时仍可返回兼容 GeoJSON。

View File

@@ -124,7 +124,7 @@ CREATE UNIQUE INDEX ON vessel_latest(mmsi);
GET /api/v1/visualization/geo/vessels
?bbox=lon_min,lat_min,lon_max,lat_max # 视口裁剪
?type=cargo,tanker,passenger # 船型过滤
?limit=5000
?limit=0 # 可选;不传或 0 表示不裁剪数量
→ GeoJSON FeatureCollectionPoint
GET /api/v1/visualization/vessels/{mmsi} # 单船详情
@@ -163,6 +163,8 @@ GeoJSON Feature 格式:
- 后端 BarentsWatch collector 继续以 HTTP polling 方式采集
- AISStream 等实时源以独立 WebSocket collector 写入原始观测层
- 展示接口从聚合服务读取当前船只视图,而不是由单个 collector 决定最终展示值
- 前端默认不再给 `/geo/vessels``limit=5000``VESSEL_CONFIG.maxRenderedMarkers = 0` 表示不做前端数量裁剪;后续如性能不足再引入显式 LOD 上限
- marker 颜色、详情卡、hover 和搜索结果必须共享 `vessel_type_display` 船型归一化结果,避免 AIS 数字类型码已驱动颜色但卡片仍显示 `Other`
- 前端是否升级为 WebSocket delta push 是独立优化,不影响后端采集器可以使用 WebSocket 接上游实时源
---
@@ -196,8 +198,8 @@ GeoJSON Feature 格式:
| 相机距离 | 渲染策略 |
|---------|---------|
| > 400 | 仅渲染 top 1000 艘(按数据新鲜度 + 船型优先级) |
| 200400 | 渲染 top 5000 艘 |
| > 400 | 默认渲染当前接口返回的全部船只;如性能不足,再引入可配置 LOD 上限 |
| 200400 | 默认渲染当前接口返回的全部船只;如性能不足,再引入可配置 LOD 上限 |
| < 200 | 渲染当前视口 bbox 内全部船只 |
前端根据相机位置动态计算 bbox附加到 API 请求中。