# ISCE2 生产可靠性加固设计 2026-04-24 ## 1. 背景 当前 ISCE2 生产链路已经具备: - 统一的 WSL runtime 入口 - 共享 conda 环境 `insar_wsl_v1` - 结果发布到 `D:\production_results` - 统一 catalog / 自检 / 运维面板 但这条链路仍存在一个核心问题: 系统现在更像是“子进程执行器”,还不是“结果可靠性交付器”。 也就是说,当前成功判定主要依赖: - WSL runner 返回码是否为 `0` - 输出目录是否存在 - 自动发布阶段是否“尝试过” 这会导致几类假成功: 1. ISCE2 子进程返回 `0`,但没有形成可发布的主产物,任务仍显示完成。 2. 历史运行只留下目录和 `.dinsar_run.json`,但主产物已损坏或缺失,“只跑未完成” 仍会跳过。 3. 运维自检用的是一套路径规则,真实运行用的是另一套路径规则,面板和实际行为可能不一致。 本设计的目标不是重写 ISCE2 算法链,而是把 ISCE2 生产从“命令执行成功”收紧到“结果可验收、可发布、可重跑、可运维”。 ## 2. 本轮范围 本轮只做可靠性加固,不做算法重写。 ### 纳入本轮 - ISCE2 原生产结果的验收契约 - “只跑未完成” 判定收紧 - 自动发布阶段收紧 - 健康检查与真实运行入口对齐 - 文档、自检、日志字段同步 ### 暂不纳入本轮 - stripmap/SNAPHU/PICKLE 恢复链的算法级改写 - 与 ENVI/IDL 流水线逐步逐项数值对比 - Gamma 生产实现细节 - MintPy / 时序生产链的算法可靠性评估 ## 3. 当前问题拆解 ### 3.1 成功判定过宽 当前 `backend/app/dinsar_engines/isce2_engine.py` 中,单对任务成功主要由 `rc == 0` 决定。 现状后果: - 只要 runner 没报错,就写入 `.dinsar_run.json` - 只要 `output_dir` 被加入 `output_dirs`,后续任务层就进入自动发布 - 自动发布即使 `processed == 0`,任务最终仍可标记为 `COMPLETED` 这不符合“生产完成”的系统定义。 ### 3.2 未完成重跑判定过宽 当前 `_has_completed_task_result()` 只看: - 历史 `run_dir` - `.dinsar_run.json` - `output_dir` 目录存在 它不看: - 主产物是否存在 - 主产物是否可读 - 该结果是否满足发布器识别规则 因此历史半成品会被误判为“已完成”。 ### 3.3 健康检查与真实运行入口不一致 当前存在两套 stripmapApp 路径逻辑: - 健康检查依赖 `ISCE2_STRIPMAP_APP` - 真正运行时在 WSL 内通过 `import isce` 动态定位 这会导致: - 面板报错但实际能跑 - 面板通过但实际解释器/包位置已经漂移 ### 3.4 手工恢复链缺少显式边界 当前 `run_lt1_dinsar_pipeline.py` 通过复制 `PICKLE` 中文件并改写 XML 来衔接: - `filter -> filter_high_band` - `unwrap -> ionosphere` 这条链路短期可用,但它对 ISCE2 内部文件结构有隐含依赖。 本轮不重写它,但必须把它从“隐式成功”改成“显式验收”。 ## 4. 目标状态 ISCE2 生产链路调整后,系统对“完成”的定义变为: 1. WSL runner 执行完成。 2. 原生产物通过本地验收。 3. 验收通过的产物可被统一发布器识别。 4. 自动发布成功写入标准包。 5. 自检面板能看到 runtime、native output、publish package 三层状态一致。 换句话说: - `rc == 0` 只表示“执行成功” - “任务完成” 必须是“执行成功 + 产物验收成功 + 发布链成功” ## 5. 具体改造方案 ### 5.1 新增 ISCE2 原生产物验收层 新增一个独立验收模块,建议位置: - `backend/app/services/isce2_result_validator.py` 职责: - 不参与计算 - 不参与数据库写入 - 只负责判断一个 `output_dir` 是否是“可发布、可交付”的 ISCE2 原生结果 #### 输入 - `output_dir` - `task_alias` - `engine_code` - `profile_code` - `run_key` #### 核心检查项 必选检查: - `output_dir` 目录存在 - 至少存在一个符合发布规则的主位移产物 - 主产物文件大小大于 0 - 主产物能被 GDAL 打开 - 若存在配对的相干产品,则记录为辅助资产 建议主产物识别规则直接复用发布器当前的规则,避免两套规则分叉: - `*_disp.tif` - 可选 `*_coh.tif` #### 输出结构 返回统一 `acceptance_result`: ```json { "accepted": true, "primary_file": "D:/.../Task_xxx_disp.tif", "asset_files": [ "D:/.../Task_xxx_disp.tif", "D:/.../Task_xxx_coh.tif" ], "issues": [], "metrics": { "primary_exists": true, "primary_readable": true, "asset_count": 2 } } ``` #### 运行时行为 `isce2_engine.run()` 中: - `rc != 0` 直接失败 - `rc == 0` 后必须执行验收 - 只有 `accepted == true` 才计入 `pairs_processed` - 只有验收通过的 `output_dir` 才进入 `output_dirs` 这样“runner 成功但没产物”的情况会被收口为失败。 ### 5.2 收紧 `.dinsar_run.json` 语义 当前 `.dinsar_run.json` 只是运行元数据,需要补成“运行 + 验收”的统一侧写。 本轮不改数据库表结构,先改 sidecar 内容。 新增字段建议: ```json { "run_key": "run_xxx", "engine_code": "isce2", "profile_code": "lt1_stripmap", "runtime_id": "isce2_runtime_v1", "output_dir": "D:/.../native", "acceptance": { "accepted": true, "accepted_at": "2026-04-24T15:00:00Z", "primary_file": "D:/.../Task_xxx_disp.tif", "asset_files": [ "D:/.../Task_xxx_disp.tif", "D:/.../Task_xxx_coh.tif" ], "issues": [], "metrics": { "primary_exists": true, "primary_readable": true, "asset_count": 2 } } } ``` 原则: - `.dinsar_run.json` 不再只表示“跑过” - 它要能表达“验收通过 / 未通过” ### 5.3 重写“只跑未完成”判定 将 `_has_completed_task_result()` 调整为 `_has_accepted_task_result()`。 新的跳过条件: 1. 历史 run 的 `.dinsar_run.json` 存在 2. `engine_code / profile_code` 匹配当前请求 3. `acceptance.accepted == true` 4. `acceptance.primary_file` 存在且可读 只有满足以上条件,才视为“已完成”。 #### 不再采用的旧判断 - 仅凭 `output_dir` 存在 - 仅凭 `.dinsar_run.json` 存在 #### 这样做的效果 - 目录空壳不会再被跳过 - 历史损坏结果会重新进入生产 - “只跑未完成” 的含义变成“只跳过已验收成功的对” ### 5.4 自动发布阶段从“尽力而为”改为“交付闭环” 当前自动发布逻辑即使 `processed == 0` 也只记日志。 本轮改为: - 引擎层只把“已验收通过”的 `output_dir` 交给发布器 - 任务层以“验收通过数量”作为发布期望值 - 发布结果必须满足: - `processed == accepted_output_count` - `failed == 0` 若不满足,则任务整体失败。 #### 原则 - 自动发布不再是可有可无的附属步骤 - 对托管生产来说,发布成功是交付定义的一部分 #### 失败语义 若 native output 已验收通过,但标准包发布失败: - 任务状态标记为 `FAILED` - 保留 native output 目录 - 日志明确写出: - accepted count - processed count - failed count 这样后续可以修发布器或手动重建 catalog,但不会再出现“任务完成却没有结果”的误导状态。 ### 5.5 健康检查与真实运行入口统一 目标:只保留一套 runtime 事实来源。 #### 调整方向 `ISCE2_STRIPMAP_APP` 不再作为运行事实来源,只保留为兼容字段。 健康检查改成: 1. 从 `wsl_runtime_registry` 读取共享 Python 2. 在对应 Python 中执行: ```python import isce from pathlib import Path print(Path(isce.__file__).resolve().parent / "applications" / "stripmapApp.py") ``` 3. 检查该路径是否存在 4. 将解析出的真实路径回显到自检面板 #### 自检项调整 保留: - WSL 可用 - 共享 Python 可执行 - `import isce` 成功 - stripmapApp 动态解析成功 - runner 脚本存在 弱化: - 直接检查 `.env` 中的 `ISCE2_STRIPMAP_APP` #### 自检返回建议新增字段 - `resolved_stripmap_app` - `resolved_isce_package_path` - `python_matches_shared` - `runtime_id` ### 5.6 数据库与自维护策略 本轮尽量不动数据库 schema。 原因: - 当前问题核心在文件系统验收语义,不在表结构 - 用户已经明确要求兼容数据库自维护与自检 - 现阶段没有必要为了可靠性问题引入一轮 schema 变更风险 #### 本轮原则 - 不新增非必要列 - 先把验收信息写入 `.dinsar_run.json` - 数据库继续通过现有 `result_products / result_assets / result_issues` 表表达发布结果 #### 若后续必须加列 只允许: - nullable - 可由 `_add_missing_columns()` 自动补齐 - 不要求清库 但这不属于本轮必须项。 ### 5.7 运维自检扩展 为避免“native 有结果但 publish 不一致”的灰区,本轮建议在健康面板新增两个聚合视角。 #### 1. runtime 视角 - 共享 distro / Python / stripmapApp 动态解析是否一致 #### 2. product 视角 - 已入库产品是否存在 `native_output_dir` - `native_output_dir` 是否仍存在 - `manifest_path` 是否存在 #### 3. 新增建议计数项 - `missing_native_output_count` - `missing_manifest_count` - `missing_runtime_count` 其中前两项已有基础,重点是让 ISCE2 runtime 检查结果和产品检查结果在面板里串起来。 ## 6. 文件落点 ### 核心代码 - `backend/app/dinsar_engines/isce2_engine.py` - `backend/app/services/job_handlers.py` - `backend/app/services/result_catalog_service.py` - `backend/app/services/health_service.py` - `backend/app/services/wsl_service.py` ### 新增模块 - `backend/app/services/isce2_result_validator.py` ### 文档与运维 - `docs/ISCE2_PRODUCTION_RELIABILITY_HARDENING_DESIGN_20260424.md` ## 7. 分阶段实施 ### Phase 1: 成功判定收口 - 新增 `isce2_result_validator` - `rc == 0` 后执行验收 - 未验收通过不写成功结果 - `.dinsar_run.json` 补 `acceptance` ### Phase 2: 重跑语义收口 - `_has_completed_task_result()` 改为验收驱动 - “只跑未完成” 只跳过已验收成功的结果 ### Phase 3: 发布闭环收口 - 自动发布必须与验收通过数量对齐 - `processed == 0` 不再仅告警,改为任务失败 ### Phase 4: 自检对齐 - stripmapApp 改为动态解析检查 - 面板展示 runtime 实际解析结果 ### Phase 5: 后续科学性加固 不在本轮立即落地,但建议保留后续专题: - PIKCLE 恢复链版本敏感性评估 - bbox / geoPosting 的科学性收紧 - 与 ENVI/IDL 基线链的阶段性对比 ## 8. 验收标准 改造完成后,至少满足以下场景: 1. WSL runner 返回 `0`,但没有 `*_disp.tif`。 - 结果:任务失败,不得显示完成。 2. 历史 run 目录仍在,但 `*_disp.tif` 已被删掉。 - 结果:`只跑未完成` 不应跳过,应重新生产。 3. 健康面板显示 stripmapApp 路径。 - 结果:必须来自共享 Python 动态解析,不再依赖旧硬编码。 4. native output 验收通过,但发布器没识别到包。 - 结果:任务失败,日志明确指出 publish mismatch。 5. 自检面板与实际 runtime 使用的 Python / runner / runtime_id 一致。 ## 9. 本轮结论 本轮实现不应该再把 ISCE2 生产理解为“跑完脚本”,而应理解为: “执行成功 -> 原生结果验收通过 -> 标准包发布成功 -> 系统完成交付”。 先把这个闭环收紧,再谈后续算法科学性优化,顺序才是健康的。