Files
massive-cloud/docs/algorithm-integration-spec.md

335 lines
13 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.
# 算法集成规范(Route B · 通用接口接入)
> **文档分工说明(2026-08-13)**:本文继续负责插件运行、Manifest、输入、进度、
> 产物和平台接入方式。算法 `result.json` 中的正式测量项、结构化任务号、检测对象、
> 多位置结果以及中间数据隔离,已由
> [`alg/00_算法交付与输出规范/README.md`](../alg/00_算法交付与输出规范/README.md)
> 统一规定。若本文第 4 节旧示例与该规范冲突,**以算法交付与输出规范为准**。
本文是宏量点云处理平台对**多类算法批量接入**的对外规范,面向算法团队 +
验收方。一句话总结:
> 算法侧只交付 **`一个 CLI` + `一份 result.json`**,平台用统一插件机制
> 接入、并发执行、按 manifest 渲染表单、按 result.json 渲染结果。
满足工信部验收"算法在软件中集成运行"且 20 分钟内出结果的要求。
---
## 1. 总体架构
```
前端 (React)
└── AlgorithmRunPanel ← 由 manifest 自动生成插件列表 + 多选并发
└── GenericResultView ← 按 result.json 统一渲染(误差/测量/图片/3D 叠加)
└── PointCloudViewer ← pluginOverlay prop 接收插件生成的热力 / PLY
后端 (Node + tRPC)
└── algorithm.list ← 列出已注册插件
└── algorithm.run ← 单插件执行(入并发池)
└── algorithm.runBatch ← 多插件并发执行(入并发池)
└── /api/files/read/analysis/:taskId/:resultId/:filename ← 通用产物路由
算法层(按算法各自交付)
└── manifest/<id>.json ← 元数据 + UI 表单 + runtime
└── adapters/<id>_run.py ← 平台薄适配层
└── fixtures/<id>.json ← 未就绪时的 mock 预设
```
并发池:默认 4 路(环境变量 `ALGORITHM_JOB_CONCURRENCY` 可覆盖),
单算法超时默认 20 分钟(manifest 内 `runtime.timeoutMs` 可覆盖)。
---
## 2. Manifest JSON 协议
文件位置:`server/plugins/manifests/<plugin-id>.json`。完整示例见
`coupler-panel.json` 等。
```jsonc
{
"id": "coupler-panel", // 全局唯一 kebab-case ID
"name": "车钩面板检测", // UI 显示中文名
"description": "...",
"version": "1.0.0", // 算法版本
"schemaVersion": 1, // result.json schema 版本
"category": "几何测量", // UI 分组(可选)
"requiresPointCloud": true, // 默认 true
"requiresCad": false, // 默认 false;true 时需车型下有 CAD
"inputs": [
{
"name": "profile",
"label": "调参档位",
"type": "select", // number | integer | string | boolean | select
"default": "auto",
"options": [
{ "value": "auto", "label": "自动" },
{ "value": "high_density", "label": "高密度" }
],
"description": "辅助说明,渲染在字段下方"
}
],
"runtime": {
"kind": "process", // 真实算法
"cmd": ["{pythonBin}", "{adapterDir}/coupler_panel_run.py", "{inputJson}"],
"envExtra": {
"PYTHONPATH": "{algRoot}/挠度-偏转角/delivery/coupler_panel_release"
},
"timeoutMs": 1200000 // 默认 20 分钟
}
}
```
或 mock 模式(用于占位未就绪算法):
```jsonc
"runtime": {
"kind": "mock",
"fixture": "flatness-suite.json", // 相对 fixtures/ 的文件名
"simulateMs": 1500 // 模拟耗时,UI 演示进度条
}
```
### 2.1 cmd 占位符
| 占位符 | 含义 |
|--------|------|
| `{pythonBin}` | 容器内 Python 解释器(`$PYTHON3_PATH` 或 `python3`) |
| `{inputJson}` | runner 写到 workDir 的 `input.json` 绝对路径 |
| `{workDir}` | 任务工作目录(也是 outDir) |
| `{adapterDir}` | `server/plugins/adapters/` 绝对路径 |
| `{algRoot}` | 算法源码根目录(`$ALG_ROOT` 或 `/home/lipan/massive-cloud/alg`) |
---
## 3. 输入契约 — `input.json`
runner 在 workDir 写出 `input.json` 后调用算法 CLI,固定路径:
`/data/tasks/<taskId>/analysis/<resultId>/input.json`。
```jsonc
{
"schemaVersion": 1,
"pluginId": "coupler-panel",
"pluginVersion": "1.0.0",
"resultId": 123,
"taskId": 88,
"workDir": "/data/tasks/88/analysis/123",
"outDir": "/data/tasks/88/analysis/123", // 与 workDir 相同
"inputs": {
"pointCloudPaths": ["/data/tasks/88/pointcloud/scan.ply"],
"cadPath": null, // 或 "/data/models/55/cad/panel.stl"
"config": { // 来自 UI 表单
"profile": "auto",
"rightAxisSign": "1"
}
}
}
```
约束:
- 算法**不读全局环境**,所有产物落 `outDir`;
- 不修改 algorithm package 自身目录;
- CLI 必须**非交互**:禁用 GUI / 弹窗。
---
## 4. 输出契约 — `result.json`
以下示例仅说明平台可接收的基础结构。新交付算法必须同时符合
[`alg/00_算法交付与输出规范/README.md`](../alg/00_算法交付与输出规范/README.md),
并为每个正式测量项提供 `taskNo`、`metricCode` 等结构化字段;算法中间量不得放入
`measurements`。
算法在 `outDir/result.json` 写最终结果:
```jsonc
{
"schemaVersion": 1,
"status": "completed", // 或 "error"
"message": "可选简短说明",
"measurements": [
{ "name": "右偏转角", "value": 0.83, "unit": "°", "tolerance": 1.0, "status": "pass" },
{ "name": "最大 X 偏差", "value": 0.42, "unit": "mm", "tolerance": 1.0, "status": "pass" },
{ "name": "拟合内点率", "value": 0.92, "status": "info" }
],
"errorStats": { // 可选;用于 GenericResultView 误差卡片
"min": -1.83, "max": 1.28, "mean": 0.04, "stdDev": 0.32,
"totalPoints": 1500000,
"withinTolerance": 1450000, "outOfTolerance": 50000
},
"overlay3d": { // 可选;前端通过 PointCloudViewer.pluginOverlay 消费
"kind": "vertexColors", // 或 "ply" / "none"
"file": "heatmap.bin", // 相对 outDir
"vertexCount": 123456,
"dispMin": -0.5, "dispMax": 0.5
},
"images": ["plot.png", "images/diagnostic.png"], // 相对 outDir
"extra": { "...": "算法专属字段,前端折叠展示" }
}
```
`measurements[*].status` ∈ {`pass` | `warning` | `fail` | `info`},前端按 badge
颜色渲染。
---
## 5. 进度上报协议
子进程通过 **stdout 单行 JSON** 上报进度,runner 解析后写入数据库:
```text
{"type":"progress","percent":0.12,"message":"去噪"}
{"type":"progress","percent":0.45,"message":"平面拟合"}
{"type":"progress","percent":0.80,"message":"轮廓度计算"}
```
字段说明:
- `percent`:允许 0~1 或 0~100,runner 自动归一化为 0~1;
- `message`:当前阶段名,会写入 `resultData.stage`。
stderr 仅作日志,runner 会原样转发到平台日志。
---
## 6. 错误约定
| 情况 | 算法行为 | 平台行为 |
|------|----------|----------|
| 数据不合规 / 计算失败 | 写 `result.json` 且 `status="error"`,`message` 中文+英文摘要 | DB.status="error",前端红字提示 |
| 进程异常退出(无 result.json) | exit code 非 0 | runner 写一条带 `exitCode` 的 error 记录 |
| 超时 | runner 发 SIGKILL | DB.status="error",错误信息为「超时 N ms」 |
---
## 7. tRPC 接口
```ts
// 列出所有插件
trpc.algorithm.list.useQuery()
// → { plugins: PluginSummary[], scheduler: { running, queued, limit } }
// 单个算法
trpc.algorithm.run.useMutation()
// .mutate({ taskId, pluginId, params })
// → { id, pluginId, schemaVersion }
// 并发批量
trpc.algorithm.runBatch.useMutation()
// .mutate({ taskId, jobs: [{ pluginId, params }] })
// → { jobs: [{ id, pluginId }], scheduler }
```
每次 run 都会在 `task_analysis_results` 表插入一条 `type='plugin'` 的记录。
前端通过原有 `trpc.analysisResult.list` 轮询,按 `pluginId` 区分。
---
## 8. 数据库变更
迁移文件:`drizzle/0007_task_analysis_plugin_columns.sql`。
```sql
ALTER TABLE task_analysis_results
MODIFY COLUMN type ENUM(..., 'plugin') NOT NULL;
ALTER TABLE task_analysis_results
ADD COLUMN pluginId VARCHAR(64) NULL AFTER type,
ADD COLUMN schemaVersion INT NULL AFTER pluginId;
CREATE INDEX idx_task_analysis_plugin_id ON task_analysis_results (pluginId);
```
升级流程:
1. 应用 SQL 迁移(`pnpm db:push` 或手动执行)。
2. 重启 Node 服务。
3. 新增算法只需新增 manifest + adapter,无需再变更数据库。
---
## 9. 文件路由(产物)
```
GET /api/files/read/analysis/:taskId/:resultId/:filename
```
返回 outDir 下任意相对路径的文件(含 `images/foo.png` 等)。前端通过
`result.json.images` / `overlay3d.file` 直接拼接 URL。
> 与现存 `/read/analysis/heatmap/...`、`/read/analysis/assembly-vertex/...`
> 共存;具体路由优先匹配,最后由通用路由兜底。
---
## 10. 已接入的算法
页面右侧算法步骤与插件、平台适配器、算法源码包的对应关系如下。全部 5
个步骤都是真实 `process` 运行时,没有任何 mock。
| 页面步骤 | 插件 ID | 版本 | manifest | 平台适配器 | 算法源码包 / 工程 | 算法入口 | 算法负责人 |
|----------|---------|------|----------|------------|-------------------|----------|------------|
| 整车配准 | `vehicle-registration` | `1.3.0` | `server/plugins/manifests/vehicle-registration.json` | `server/plugins/adapters/vehicle_registration_run.py` | `alg/整车配准v1.3` | `translate_align_pointcloud_to_cad.translate_align_by_front_tail` | 待算法侧补充 |
| 车钩面板检测 | `coupler-panel` | `1.2.1` | `server/plugins/manifests/coupler-panel.json` | `server/plugins/adapters/coupler_panel_run.py` | `alg/delivery_v1.2.1/coupler_panel_release` | `coupler_panel.pipeline.run_coupler_measurement_pipeline` | 待算法侧补充 |
| 平整度 / 平面度 / 轮廓测量 | `flatness-suite` | `1.5.0` | `server/plugins/manifests/flatness-suite.json` | `server/plugins/adapters/flatness_suite_run.py` | `alg/平整度、平面度、轮廓测量v1.5` | `pipeline.MeasurementPipeline`(`contour` / `plane` / `curvature`) | 待算法侧补充 |
| 列车点云测距 | `train-measurement` | `3.2.0` | `server/plugins/manifests/train-measurement.json` | `server/plugins/adapters/train_measurement_run.py` | `alg/train_measurement_system_v3.2_0629` | `task_manager.TaskManager` | 待算法侧补充 |
| 构架挠度测量 | `underframe-deflection` | `1.2.1` | `server/plugins/manifests/underframe-deflection.json` | `server/plugins/adapters/underframe_deflection_run.py` | `alg/delivery_v1.2.1/underframe_deflection` | `underframe_deflection.run_single_beam_pipeline` | 待算法侧补充 |
说明:
- 负责人列用于区分各算法工程师归属;当前源码和 manifest 未记录个人姓名,需算法侧补充。
- `train-measurement` 即用户常说的 `train_measurement_system`,当前接入版本为
`train_measurement_system_v3.2_0629`。
- 车钩面板检测和构架挠度测量同属 `delivery_v1.2.1` 交付包,但分别是两个独立算法工程目录。
- flatness-suite 的 contour 子模块依赖较重(~2GB torch)。adapter 在 torch
缺失时会自动跳过 contour,**plane / curvature 仍能正常出结果**。
### 10.1 容器内依赖安装
镜像构建阶段:
```bash
# 通用部分 — 已经在 Dockerfile 里自动执行
python3 -m pip install --break-system-packages \
--default-timeout=300 --retries 10 \
-i https://pypi.tuna.tsinghua.edu.cn/simple \
-r requirements-geometry.txt
# 可选:启用 flatness-suite 的 contour 子模块(CPU 版,~2GB)
python3 -m pip install --break-system-packages \
-r requirements-flatness-contour.txt
```
### 10.2 算法源码挂载
`docker-compose.yml` 已配置:
```yaml
app:
environment:
- ALG_ROOT=/alg
volumes:
- ${LOCAL_ALG_DIR:-./alg}:/alg:ro # 只读挂载,安全
```
宿主机上的 `alg/` 目录(与本仓库 `alg/` 结构一致)只读挂载到容器内 `/alg`。
manifest 里所有 `{algRoot}` 占位符在运行时都解析为该路径。算法源码通过卷挂载
而非打入镜像,避免 13.5GB 的构建上下文。
---
## 11. 验收检查清单
- [x] 系统中存在「算法运行(多选并发)」面板,能列出 5 类算法
- [x] 多选执行后所有算法**并行**调度(默认 4 路并发池)
- [x] 单次运行有进度条,超时时间按各算法 manifest 配置
- [x] 结果统一渲染:误差统计 / 测量值表 / 图片 / 3D 叠加按钮
- [x] 数据库记录算法 ID + schemaVersion,可回放、可追溯
- [x] 算法层与 UI 层完全解耦:新增算法不改前端 / 不改 tRPC / 不改数据库
---
## 12. 后续接入新算法的步骤
1. 把算法封装成一个 Python 入口:`python -m my_algo.run <input.json>`,
保证:读 `inputs.pointCloudPaths[0]` → 计算 → 写 `outDir/result.json`。
2. 复制一份 manifest(如 `coupler-panel.json`)改 `id`、`cmd`、`envExtra`、`inputs`。
3. (可选)写一个 adapter `server/plugins/adapters/my_algo_run.py` 做参数翻译,
或者算法 CLI 直接吃 input.json。
4. 重启服务,前端自动刷出新条目,**不需要任何前端 / SQL 改动**。