release: bump version to 0.66.0
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

This commit is contained in:
rayd1o
2026-05-26 03:41:47 +08:00
parent e65267fe21
commit 5bf5c73ca0
173 changed files with 8669 additions and 13210 deletions

View File

@@ -0,0 +1,127 @@
# 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 收敛解决