Files
planet/docs/deprecated/location-resolver-shared-pipeline-plan.md
rayd1o 5bf5c73ca0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.66.0
2026-05-26 03:41:47 +08:00

128 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Location Resolver Shared Pipeline Plan
**状态**:已实现,当前用户流程见 [Planet 使用手册](/home/ray/dev/linkong/planet/docs/technical/zh/manual.md) 的 Earth 位置候选采集章节,开发接口见 [通用位置估算管线开发说明](/home/ray/dev/linkong/planet/docs/technical/zh/location-pipeline-development.md)。
## Goal
把"给定一条记录,决定它的 lat/lon"这件事抽象成一条统一的可插拔管线让算力中心、BGP 观测站、BGP 事件——以及未来任何需要位置估算的实体——共用同一套接口。新算法peeringdb 设施查询、IXP 表、用户认领的精确点位等)通过实现一个 Resolver 类即可挂入,不需要改任何上层调用方。
## Background
### 实施前现状
- **算力中心** (`backend/app/services/compute_center_locations.py`) 早期曾使用源坐标 → 本地 JSON 注册表 → 城市兜底 → Nominatim 在线地理编码。后续为避免硬编码位置污染事实链路,算力中心本地注册表已移除;主地图只使用源坐标,手动候选采集使用 ROR 和 Nominatim。
- **BGP 观测站** (`collectors/bgp_common.py:RIPE_RIS_COLLECTOR_COORDS`) 是一张写死的字典26 个 RIPE RIS collector 的城市级坐标。新增 collector / 升级到设施级精度都得改 Python。
- **BGP 事件**继承所属 collector 的城市级坐标(`BGPObservation.collector_geo`)。
- 用户原本以为 BGP 观测站位置是通过 iptoasn 推断的——其实 iptoasn 只用于前缀级国家归属(`bgp_enrichment.py`),不影响 marker 坐标。
### 痛点
1. 算力中心那条 4 层链路写死在算力中心模块里BGP 想用得复制一遍。
2. 三类实体各走各的坐标策略,缺统一抽象。
3. 未来要插更精的算法peeringdb / IXP / 用户认领),现在没有挂入点。
## Design
### 接口契约
`backend/app/services/location/`
- `models.py` —— `LocationQuery`(输入)、`LocationCandidate`(候选)、`ResolverOutput`(单 resolver 输出)、`ResolutionResult`/`ResolutionDiagnostic`(管线最终结果)
- `pipeline.py` —— `LocationResolver` Protocol、`LocationPipeline` 编排器
- `resolvers/source_coordinates.py` —— 记录自带 lat/lon 时直通
- `resolvers/registry.py` —— 本地 JSON 注册表locations + city_fallbacks按别名得分
- `resolvers/nominatim.py` —— 通用 Nominatim 客户端rate-limited + LRU 缓存)+ 可注入 query plan
- `resolvers/inherit.py` —— 从外部回调取候选(事件继承 collector 用)
- `text.py` —— 文本规范化共享工具
核心 Protocol
```python
class LocationResolver(Protocol):
name: str
def resolve(self, query: LocationQuery) -> ResolverOutput: ...
```
`LocationPipeline.collect_candidates()` 跑全部 resolver聚合所有候选`(source_rank, precision_rank, -confidence)` 排序去重;`resolve_best()` 选 top 候选。
### 各领域管线
```python
# compute_center_locations.py重构后公共 API 不变)
COMPUTE_CENTER_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
])
COMPUTE_CENTER_COLLECTION_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
ROROrganizationResolver(),
NominatimResolver(query_plan_builder=_compute_center_query_plan,
geocoder=lambda q: _geocode_online(q)),
])
# bgp_collector_locations.py
BGP_COLLECTOR_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
StoredCollectorLocationResolver(),
])
BGP_COLLECTOR_COLLECTION_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
NominatimResolver(query_plan_builder=_bgp_collector_query_plan,
geocoder=lambda q: _geocode_online(q)),
])
# bgp_event_locations.py
BGP_EVENT_PIPELINE = LocationPipeline([
SourceCoordinatesResolver(),
InheritFromAnotherEntityResolver(source_lookup=_inherit_from_owning_collector),
# 占位:将来插 ASNFacilityResolver / PrefixGeoResolver
])
```
### 关键设计决策
1. **算力中心公共 API 完全不变**`resolve_compute_center_location()``collect_location_candidates()``ComputeCenterLocation` dataclass、`_geocode_online` 模块级符号都保留,前端 / 上层调用方零改动;现有 19 个回归测试全绿。
2. **`_geocode_online` 用 lambda 晚绑定**`NominatimResolver(geocoder=lambda q: _geocode_online(q))` 能让测试 `monkeypatch.setattr(module, "_geocode_online", fake)` 继续生效。
3. **`RIPE_RIS_COLLECTOR_COORDS` 自动从 DB-backed cache 重建**:启动时 seed/refresh `bgp_collector_locations` 维表,再原地刷新旧 `{rrcXX → {city, country, lat, lon}}` 字典。下游消费者(`bgp_collectors.py`、序列化、detector不动即可获得新元数据。
4. **修复隐藏 bug**BGP collector 不再通过 registry/operator 模糊匹配晋升候选,避免 `operator="RIPE NCC"` 让每个事件都落到 `rrc00`
5. **事件继承走严格名字查询**:事件继承不跑 collector 的完整 pipeline改成直接查 DB-backed cache。"改进位置"用户触发流程只跑源坐标和在线地理编码候选。
## Files
### 新增
- `backend/app/services/location/__init__.py`
- `backend/app/services/location/models.py`
- `backend/app/services/location/pipeline.py`
- `backend/app/services/location/text.py`
- `backend/app/services/location/resolvers/__init__.py`
- `backend/app/services/location/resolvers/source_coordinates.py`
- `backend/app/services/location/resolvers/registry.py`
- `backend/app/services/location/resolvers/nominatim.py`
- `backend/app/services/location/resolvers/inherit.py`
- `backend/app/services/bgp_collector_locations.py`
- `backend/app/services/bgp_event_locations.py`
- `backend/app/models/bgp_collector_location.py`
- `backend/tests/test_location_pipeline.py`16 用例)
- `backend/tests/test_bgp_collector_locations.py`11 用例)
### 修改
- `backend/app/services/compute_center_locations.py` —— 改为薄包装
- `backend/app/services/collectors/bgp_common.py` —— 删除写死字典,改调 `resolve_bgp_event_geo_dict()`
- `backend/app/api/v1/bgp.py` —— 新增 `POST /api/v1/bgp/collectors/{collector_id}/collect-location`
- `frontend/public/earth/js/info-card.js` —— `renderComputeCenterCollectSection``renderLocationCollectSection`BGP collector 走通用化路径
- `frontend/public/earth/js/compute-centers.js` —— 新增通用 `collectLocationCandidates(endpoint, payload)`
- `frontend/public/earth/js/main.js` —— `previewComputeCenterCandidate``previewLocationCandidate`,事件名改为 `earth:preview-location-candidate`
## Verification
- `uv run pytest backend/tests/test_visualization_compute_centers.py` —— 19 个用例全绿(公共 API 未改)
- `uv run pytest backend/tests/test_location_pipeline.py backend/tests/test_bgp_collector_locations.py` —— 16 + 11 用例全绿
- 抽象可插拔性测试:`test_pluggability_custom_resolver_works_without_changing_pipeline` —— 临时实现 `_PeeringDBStubResolver` 直接接入 `LocationPipeline`,验证管线不需要改一行就能识别新 source
## Out of scope
- 持久化用户认领的精确坐标(写回 JSON 注册表)—— `suggested_registry_entry` 字段已就绪,工作流单独立项
- 真正实现 `ASNFacilityResolver` / `PrefixGeoResolver` —— 接口已留好具体算法peeringdb / IXP 表 / iptoasn 升级)单独立项
- 算力中心 / 观测站 marker 合并避让 —— 上一轮已用 `SURFACE_AVOIDANCE_PROFILES.city` + halo 收敛解决