# InSAR 配对系统重构方案 更新日期:2026-04-13 状态:待实施 适用范围:`Z:\Code\Insar_management_system_v2` 当前代码基线 ## 1. 文档目标 这份文档不是抽象算法说明,而是基于当前系统现状给出的可落地重构方案,目标是: - 重构 D-InSAR 配对内核,解决当前正确性、稳定性和可维护性问题 - 尽量复用现有数据库自动维护、root/cursor 自维护、manifest inventory、自检面板 - 明确开发机条件下的迁移策略 - 为后续实现提供分阶段执行顺序、表设计、接口边界和验收口径 ## 2. 当前系统基础与约束 ### 2.1 当前可直接复用的系统能力 当前系统已经具备以下基础设施,重构应直接接入,而不是另起一套平行机制: - 数据库启动自维护 - `backend/app/db_maintenance.py` - `ensure_database_ready()` 会在启动时创建表、补缺列、应用 SQL migration,并可在开发机上按环境变量执行整库 schema reset - root 与 cursor 自维护 - `backend/app/services/root_registry_service.py` - 启动时会根据 `.env` 自动同步 `managed_roots` 与 `scan_cursors` - manifest inventory 自维护 - `backend/app/services/manifest_inventory_service.py` - 会维护 `path_inventory`、`scan_cursors.last_seen_*`、fingerprint 与 root 缺失状态 - 结果目录自维护 - `backend/app/services/dinsar_scan_service.py` - D-InSAR 统一扫描链路已经是“发布 -> manifest inventory -> catalog rebuild -> compat sync” - 健康检查和一致性检查 - `backend/app/services/health_service.py` - `frontend/src/HealthCheckPanel.jsx` - 当前自检已包含:数据库 schema、worker、catalog、source roots、D-InSAR bridge、一致性统计、系统维护入口 ### 2.2 当前配对系统的核心问题 现有配对系统的主要问题不是功能不够多,而是核心语义不稳定: - SQL 路径和 Python fallback 路径不是同一算法 - 同日影像会出现反向重复配对 - `start_date` 在 SQL 主路径上已失效,但接口仍保留 - `star` 策略没有固定参考像角色 - `sequential` 对同日多景不稳定 - `all` 策略排序与前端描述不一致 - `aoi_overlap_threshold=0` 的语义前后端不一致 - 当前任务命名与配对唯一性混在一起,业务主键不稳定 ### 2.3 当前系统约束 - 当前扫描模式是 manual-only,调度器默认关闭 - `backend/app/scheduler.py` - `radar_data` 是当前源影像事实表,配对系统必须以它为唯一源 - `dinsar_task_batches` / `dinsar_task_items` 已经是生产任务落地表 - `result_products` / `dinsar_product_profiles` 已经承担结果资产追踪 - 健康检查和统计接口已上线,新增配对系统状态应并入现有面板 ## 3. 重构总目标 本轮重构的目标不是继续修补 `find_dinsar_pairs_v2`,而是将配对系统改造成四层结构: 1. 影像事实层 2. 配对指标缓存层 3. 网络选择层 4. 任务物化层 核心原则: - 候选对生成与网络选择分层 - 所有策略共享同一候选池 - 所有配对方向统一规范化 - 所有结果可追溯到策略版本、参数快照和候选边来源 - 不再保留“算法不等价的 fallback” ## 4. 目标架构 ### 4.1 Layer A:影像事实层 沿用现有 `radar_data` 表作为唯一源影像事实表。 重构要求: - `radar_data.id` 继续作为数据库主键 - 引入明确的场景业务键 `scene_uid` - 优先复用 `radar_data.unique_id` - 若历史数据存在 `unique_id` 缺失或不稳定,启动时执行一次回填 - 回填规则:`hash(normalized file_path)`,并写入新列或标准化字段 - 所有配对相关对象都基于 `scene_uid` 或 `radar_data.id` 建立引用 建议: - 本轮实现阶段直接在 `radar_data` 上新增: - `scene_uid` - `scene_signature` - `pairing_eligible` - `pairing_updated_at` - 如果希望减少变更面,也可以只新增 `scene_uid` 与 `pairing_updated_at` ### 4.2 Layer B:配对指标缓存层 这是本次重构的核心。 目标: - 将“任意两个影像是否可配、其基础指标是什么”从请求时临时计算,改为可维护的持久化缓存 - 缓存只保存与 AOI 无关的基础指标 - AOI 相关指标在请求时动态叠加计算 建议新增表: #### `pairing_metric_cache` 一行代表一个规范化后的影像对。 建议字段: - `id` - `master_scene_ref_id` - `slave_scene_ref_id` - `master_scene_uid` - `slave_scene_uid` - `pair_uid` - `metric_version` - `orientation_rule_version` - `time_baseline_days` - `spatial_baseline_meters` - `scene_overlap_ratio` - `orbit_direction` - `same_satellite` - `same_imaging_mode` - `same_polarization` - `master_imaging_date` - `slave_imaging_date` - `master_satellite` - `slave_satellite` - `master_imaging_mode` - `slave_imaging_mode` - `master_polarization` - `slave_polarization` - `master_file_path` - `slave_file_path` - `status` - `computed_at` 唯一约束: - `unique(master_scene_ref_id, slave_scene_ref_id, metric_version)` 关键约定: - `master/slave` 不是请求期可变概念,而是规范化方向 - 规范化规则: - 早时间 = master - 同时间时,较小 `scene_uid` 或较小 `id` = master ### 4.3 Layer C:缓存状态与脏队列 为了接入现有“自动维护 + 健康检查”体系,建议新增两张状态表。 #### `pairing_cache_state` 建议设计为单例表或按 `cache_scope='global'` 管理。 字段建议: - `id` - `cache_scope` - `metric_version` - `status` - `scene_count` - `pair_count` - `dirty_scene_count` - `last_full_rebuild_at` - `last_incremental_reconcile_at` - `last_error` - `updated_at` 状态建议: - `READY` - `DIRTY` - `REBUILDING` - `DEGRADED` - `FAILED` #### `pairing_dirty_scenes` 用于增量维护。 字段建议: - `id` - `scene_ref_id` - `scene_uid` - `reason` - `marked_at` - `resolved_at` - `status` 唯一约束建议: - `unique(scene_ref_id, status='PENDING')` 或代码层去重 ### 4.4 Layer D:网络选择层 网络选择层不直接查 `radar_data`,而是查 `pairing_metric_cache`。 建议新增: #### `pairing_network_runs` 代表一次配对请求或一次已保存的网络。 字段建议: - `id` - `network_run_id` - `strategy` - `policy_version` - `request_hash` - `request_params_json` - `aoi_source` - `aoi_hash` - `aoi_summary_json` - `candidate_count` - `selected_edge_count` - `warning_count` - `status` - `fallback_used` - `created_by` - `created_at` #### `pairing_network_edges` 代表某个网络 run 选中的边。 字段建议: - `id` - `network_run_ref_id` - `metric_cache_ref_id` - `edge_rank` - `selection_reason` - `selection_score` - `selection_meta_json` - `is_reference_edge` - `created_at` 唯一约束: - `unique(network_run_ref_id, metric_cache_ref_id)` ### 4.5 Layer E:任务物化层 当前 `dinsar_task_batches` / `dinsar_task_items` 可继续保留。 但建议增加可追溯字段: 在 `dinsar_task_items` 上新增: - `network_run_id` - `network_edge_id` - `policy_version` - `selection_strategy` - `scene_pair_uid` 在结果侧建议同步补充: - `result_products.pair_key` 继续保留 - 在 `dinsar_product_profiles` 或 `params_json` 中补充: - `network_run_id` - `network_edge_id` - `policy_version` - `master_scene_uid` - `slave_scene_uid` 这样最终可以把: - 配对网络 - 任务项 - 生产结果 串成一条可追踪链路。 ## 5. 配对算法设计 ### 5.1 候选对生成 候选对生成只负责回答: - 这两个场景在基础事实上是否可配 - 基础指标是什么 不负责回答: - 用什么策略选 - 最后是否进入任务 基础硬约束: - `master != slave` - 方向规范化后只保留一个方向 - 同轨向 - 日期格式合法 - 几何相交 基础指标: - `time_baseline_days` - `spatial_baseline_meters` - `scene_overlap_ratio` 注意: - `same_satellite` - `same_imaging_mode` - `same_polarization` 只作为缓存事实,不在缓存层提前删边,以便不同请求复用同一缓存。 ### 5.2 请求期过滤 请求期过滤发生在 `pairing_metric_cache` 之上: - `time_baseline_min/max` - `spatial_baseline_max_meters` - `overlap_threshold` - `allowed_satellites` - `cross_satellite_pairing` - `require_same_imaging_mode` - `require_same_polarization` - `master_date_from/to` - `slave_date_from/to` - `aoi_overlap_threshold` AOI 处理原则: - AOI 不进入持久化 pair cache 主键 - AOI 相关 overlap 在请求时动态计算 - `0` 不再表示数值阈值,而统一转成 `null` - 前后端统一语义: - `null` = 不启用 AOI 覆盖阈值 - `0 < x <= 1` = 启用阈值 ### 5.3 四种策略的正式定义 #### `all` - 返回全部合法候选边 - 排序统一为稳定顺序: - `master_imaging_date` - `slave_imaging_date` - `selection_score desc` - `pair_uid` 不再使用“数据库返回顺序”作为结果语义。 #### `sequential` - 基于稳定时间序列排序构图 - 排序键: - `acquisition_time_utc` - 无则 `imaging_date` - 再无则 `scene_uid` - 每景连接后续 `N` 景 - 同日多景必须稳定,不允许受候选池原始顺序影响 #### `star` - 参考像必须固定角色 - 本系统建议统一采用: - 参考像始终为 `master` - 若物理上参考像日期晚于目标像,则在选择层进行拒绝或显式反转并记录相位方向规则 本轮建议: - 为降低复杂度,先采用“参考像必须是 master”的严格规则 - 如果用户选择的参考像晚于部分场景,则这些边不入网,并给出 warning #### `sbas` 本轮不采用“简单按 overlap 贪心去重”的旧实现。 建议目标: - 图连通 - 每景至少达到最小连接数 - 时间覆盖尽量连续 - 每景度数受控,避免过密 - AOI 覆盖尽量多样 可落地实现: 1. 从合法候选边中先构造基础时间邻接骨架 2. 保证每个场景至少连接前后最近邻 3. 再按综合评分补边 4. 每补一条边,对已覆盖区域施加惩罚 5. 达到目标连通性和最大边数后停止 综合评分建议: `selection_score = w_time * time_score + w_spatial * spatial_score + w_overlap * overlap_score + w_aoi * aoi_gain - w_redundancy * redundancy_penalty` ### 5.4 删除旧 fallback 的原则 本轮不再保留“Python 语义独立 fallback”。 替代方案: - 候选边缓存是主路径 - 如果缓存不存在或脏数据过多: - 返回明确的 `DEGRADED` / `DIRTY` 状态 - 允许管理员触发重建 - 或在安全阈值内自动执行增量重建 也就是说: - 可以有“降级状态” - 不能再有“悄悄换算法” ## 6. 与现有扫描和自动维护机制的集成 ### 6.1 源影像扫描后的集成点 当前源影像扫描入口: - `backend/app/services/data_service.py::scan_radar_data` - `backend/app/scheduler.py::scan_data_job` 建议改造: 在 `scan_radar_data()` 中,对新增、更新、删除的 `radar_data` 记录执行: 1. 标记 `pairing_dirty_scenes` 2. 更新 `pairing_cache_state.status = DIRTY` 3. 根据变更规模决定是否立即增量重建 建议阈值: - 变更场景数 `<= SMALL_RECONCILE_LIMIT` - 扫描结束后直接执行增量 reconcile - 变更场景数 `> SMALL_RECONCILE_LIMIT` - 仅标脏,不在扫描链路内同步重建 - 由管理员在自检面板手动触发 这与当前 manual-only 模式兼容,不依赖后台常驻调度器。 ### 6.2 启动期自维护 当前启动期会执行: - `ensure_database_ready()` - `root_registry_service.sync_from_settings()` - `manifest_inventory_service.sync_manifest_roots()` - catalog bootstrap - health check 建议新增启动动作: - `pairing_service.bootstrap_pairing_cache_state()` 只做轻量检查: - 新表是否存在 - 是否有缓存状态行 - `metric_version` 是否匹配当前版本 - dirty scene 是否堆积 不建议启动即全量重建,避免启动时间失控。 ### 6.3 数据库自动维护策略 当前数据库自维护具备两个模式: - 常规模式:建表、补列、应用 migration - 开发机强制 reset 模式:整库 schema reset 由于本轮是配对内核重构,且当前是开发机,推荐采用: #### 推荐落地策略 第一轮实施时直接执行一次开发机 schema reset。 原因: - 旧配对 SQL 函数与新缓存设计语义完全不同 - 旧任务表中的配对数据没有保留价值 - 旧结果可以通过 manifest inventory 和结果 catalog 重建 - 源影像可以重新扫描 具体方式: - 合并新 ORM 与 migration 后 - 启动前临时设置: - `DB_SCHEMA_RESET_ON_MISMATCH=true` - `DB_SCHEMA_RESET_CONFIRM=true` - 启动一次后让 `ensure_database_ready()` 重建 schema - 启动完成后恢复这两个环境变量为 `false` ### 6.4 migration 文件策略 当前 `db_maintenance.py` 中 migration 顺序为: - `001_st_intersection_agg.sql` - `002_spatial_functions.sql` - `003_pairing_enhancement.sql` 建议新增: - `004_pairing_refactor.sql` 该 migration 负责: - 创建新 pairing 表 - 创建必要索引 - 创建候选边计算函数或 SQL helper view - 如有需要,保留旧函数为 deprecated wrapper 建议: - 不要在 `004` 里继续强化 `find_dinsar_pairs_v2` - 将旧函数标记 deprecated,仅作为过渡壳 - 新逻辑直接走新 service + 新表 ## 7. 自检与一致性检测升级 ### 7.1 Health 接口扩展 当前健康检查聚合点: - `backend/app/services/health_service.py::get_health_status` 建议新增检查项: #### `pairing_system` 字段建议: - `ok` - `status` - `metric_version` - `scene_count` - `pair_count` - `dirty_scene_count` - `network_run_count_last_7d` - `last_full_rebuild_at` - `last_incremental_reconcile_at` - `last_error` - `duplicate_reverse_pair_count` - `orphan_metric_scene_ref_count` - `task_without_network_ref_count` - `result_without_pair_trace_count` 判定规则建议: - `READY` 且无关键异常 -> `ok=true` - `DIRTY` 但 dirty 数量较少 -> `ok=false`,级别 `warn` - 存在重复反向边、孤儿引用、版本漂移 -> `ok=false`,级别 `error` ### 7.2 Health 面板扩展 当前 `HealthCheckPanel.jsx` 已能展示结构化卡片。 建议新增卡片: - `配对系统` 展示内容: - 缓存状态 - 指标版本 - 场景数 / 缓存对数 - dirty scene 数 - 最近全量 / 增量时间 - 关键错误数 操作按钮建议: - `增量修复配对缓存` - `全量重建配对缓存` - `清理旧网络记录` ### 7.3 Statistics 接口扩展 当前 `/statistics` 已承担一致性统计角色。 建议追加: - `pairing_consistency` 字段建议: - `metric_cache_count` - `duplicate_reverse_pair_count` - `invalid_orientation_count` - `network_edge_orphan_count` - `task_orphan_count` - `result_trace_missing_count` ## 8. API 重构计划 ### 8.1 后端接口 当前接口: - `POST /api/find-pairs` 建议分两阶段改造。 #### Phase 1:保持前端可用 保留: - `POST /api/find-pairs` 但内部改为: - 从 `pairing_metric_cache` 查询 - 选择网络 - 返回 `network_run_id + pairs` #### Phase 2:正式 API 新增: - `POST /api/pairing/preview` - `POST /api/pairing/networks/{network_run_id}/materialize-batch` - `POST /api/pairing/reconcile-dirty` - `POST /api/pairing/rebuild-cache` - `GET /api/pairing/health` - `GET /api/pairing/networks/{network_run_id}` ### 8.2 请求模型 `PairingRequest` 应清理为正式语义: - 删除 `start_date` - `aoi_overlap_threshold` 前端空值统一传 `null` - `strategy` 继续保留: - `all` - `sequential` - `star` - `sbas` - `reference_image_id` 仅在 `star` 时有效 ### 8.3 返回模型 建议新增返回字段: - `network_run_id` - `policy_version` - `candidate_count` - `selected_edge_count` - `degraded` - `warnings` ## 9. 前端改造计划 涉及文件: - `frontend/src/components/PairingModal.jsx` - `frontend/src/hooks/usePairingLogic.js` - `frontend/src/store/pairingStore.js` 改造原则: - 前端不再承载配对语义补丁 - 所有“0 表示不限”“默认排序说明”等语义统一交回后端协议 - 结果列表显示: - `network_run_id` - `strategy` - `policy_version` - warning 界面调整建议: - `all` 策略文案改为“全部合法候选边,按稳定顺序展示” - `star` 明确说明“参考像固定为主像” - `sequential` 明确说明“同日多景按稳定键排序” - AOI 覆盖阈值为空时传 `null` ## 10. 旧代码处理策略 ### 10.1 可以删除的内容 在新缓存路径稳定后,可以删除: - `spatial_service.find_dinsar_pairs()` 中当前 Python fallback - 旧 `_apply_star_strategy()` 非固定角色实现 - 旧 `start_date` 兼容逻辑 ### 10.2 需要保留过渡一段时间的内容 - 旧前端 `find-pairs` 调用入口 - 旧 `task_alias / pair_key` 命名辅助 但注意: - 命名只能作为展示与兼容字段 - 不能继续作为算法主键 ## 11. 实施阶段划分 ### Phase 0:开发机切换准备 - 确认当前开发机允许 DB reset - 记录 `.env` 中路径配置 - 保留现有 `managed_roots` / `path_inventory` / manifest publish 目录,不做文件层清理 - 准备新 migration 与 ORM 验收: - 可以安全启动一次 schema reset ### Phase 1:数据模型落地 - 新增 pairing 相关 ORM - 新增 `004_pairing_refactor.sql` - 接入 `ensure_database_ready()` - 建立 `pairing_cache_state` 初始化逻辑 验收: - 后端启动后新表自动创建 - 健康检查不报 schema 错 ### Phase 2:候选缓存引擎 - 实现 `pairing_metric_cache` 全量构建 - 实现 `pairing_dirty_scenes` - 在 `scan_radar_data()` 中接入 dirty 标记 验收: - 能从 `radar_data` 构建稳定 pair cache - 同日反向重复为 0 ### Phase 3:网络选择器 - 重写 `all / sequential / star / sbas` - 删除算法不等价 fallback - 为 `find-pairs` 接口切换到新内核 验收: - 同一输入重复请求结果稳定 - `star` 固定参考像角色 - `sequential` 同日多景稳定 ### Phase 4:任务物化与结果追踪 - `dinsar_task_items` 增加 network trace 字段 - 结果 manifest / product profile 补充配对追踪字段 验收: - 任一结果可追溯到 network edge ### Phase 5:自维护与自检 - 健康检查新增 `pairing_system` - Health 面板新增配对卡片与修复按钮 - Statistics 新增 pairing consistency 验收: - 能显示 dirty scene 数、缓存状态、重复边异常 - 能从 UI 触发增量修复与全量重建 ### Phase 6:清理旧逻辑 - 删除旧 fallback - 删除 `start_date` - 下线旧语义说明 验收: - 配对链路只剩单一语义实现 ## 12. 测试矩阵 本轮至少补以下自动化测试: ### 12.1 候选缓存层 - 同日双景只生成单方向边 - 同日多景生成顺序稳定 - 不同轨向不生成边 - 时间基线越界不入缓存 ### 12.2 请求过滤层 - `allowed_satellites` 生效 - `cross_satellite_pairing=false` 生效 - `require_same_imaging_mode/polarization` 生效 - `aoi_overlap_threshold=null` 与 `0.2` 行为不同 ### 12.3 策略层 - `all` 顺序稳定 - `sequential` 连接数正确 - `star` 参考像固定为 master - `sbas` 至少满足基础连通性 ### 12.4 集成层 - 扫描新增源影像后 dirty scene 正确增加 - 执行增量 reconcile 后 dirty scene 清零 - `find-pairs` 返回 `network_run_id` - `materialize-batch` 后任务项带 network trace ### 12.5 自检层 - 重复边异常能被 health 检出 - 孤儿网络边能被 health 检出 - 任务无 network trace 能被 statistics 检出 ## 13. 风险与取舍 ### 13.1 本轮明确接受的取舍 - 开发机允许整库 reset - 不迁移旧配对记录 - 不保留旧 fallback ### 13.2 需要重点控制的风险 - 首轮全量构建 pair cache 可能较慢 - `radar_data.unique_id` 可能不稳定,需要一次性规范 - 旧结果如果缺少 trace 字段,需要通过 manifest/profile 尽可能补齐 - 若前端仍传 `0`,后端必须做强制归一化,不能再依赖 UI 自觉 ## 14. 推荐实施顺序 建议严格按下面顺序推进: 1. 先落表和缓存状态,不先改前端 2. 再实现候选缓存和增量脏标记 3. 再重写四种策略 4. 再接任务物化追踪 5. 最后接健康检查与自修复入口 不要反过来做: - 不要先改 UI 文案 - 不要先修补 `find_dinsar_pairs_v2` - 不要保留旧 fallback 到最后 ## 15. 本轮结论 在当前代码基线上,最合理的重构路线不是继续维护“实时 SQL + Python fallback”模式,而是: - 用 `radar_data` 作为唯一事实层 - 用 `pairing_metric_cache` 承载稳定候选对 - 用 `pairing_network_runs / edges` 承载策略结果 - 用 `pairing_cache_state / dirty_scenes` 承接自维护 - 用现有 `HealthCheckPanel` 和 `/statistics` 承接自检 这样做的直接收益是: - 正确性可锁定 - 结果可追溯 - 与现有 DB 自维护、扫描链路、自检体系自然对接 - 后续再接 SBAS、跨卫星和多引擎生产时不会继续堆积配对债务