14 KiB
14 KiB
CNC 多检测结果归档与报告取数说明
1. 目标
为 CNC 执行结果提供一套适合报告模块直接取数的归档结构。
设计目标:
- 以“一次工件检测实例”作为主归档单位
- 同时保留到“检测节点级别”的明细
- 支持保存:
- CNC 程序名
- 工件号 / 序列号
- 检测节点信息
- 节点使用的 Pipeline / 配方快照
- 原图、节点输入图、节点最终结果图
- 节点输出的多个数值结果
- 节点判定和整次实例判定
- 方便后续报告模块直接读取,不依赖运行时最新配方
当前实现采用:
- SQLite 保存结构化索引数据
- 文件系统保存图片资产和
manifest.json
2. 总体设计
一次检测实例会生成:
- 一组数据库记录
- 一组文件目录和图像文件
- 一份
manifest.json快照文件
归档核心对象:
InspectionRunRecord- 一次完整检测实例
InspectionNodeResult- 一个 CNC 检测节点的结果
InspectionMetricResult- 节点输出的数值结果
InspectionAssetRecord- 图像或附件索引
PipelineExecutionSnapshot- 节点执行时使用的 Pipeline 快照
默认图片保留策略:
- 整次实例原图
- 每个节点的输入图
- 每个节点的最终结果图
不默认保存每个算法步骤的中间图。
3. 文件存储结构
3.1 根目录
默认根目录:
%AppData%\XplorePlane\InspectionResults
每次检测实例按日期和 RunId 分层:
Results/{yyyy}/{MM}/{dd}/{RunId}/
3.2 示例目录
假设:
ProgramName = NewCncProgramWorkpieceId = QFN_1SerialNumber = SN-001RunId = 7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a- 检测节点共 2 个
则文件结构大致为:
%AppData%\XplorePlane\InspectionResults\
└─ Results\
└─ 2026\
└─ 04\
└─ 21\
└─ 7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a\
├─ manifest.json
├─ run\
│ └─ source.bmp
└─ nodes\
├─ 001_检测节点1\
│ ├─ input.bmp
│ └─ result_overlay.bmp
└─ 002_检测节点2\
└─ result_overlay.bmp
3.3 文件说明
run/source.bmp- 本次工件检测实例的原始输入图
nodes/001_检测节点1/input.bmp- 节点 1 输入图
nodes/001_检测节点1/result_overlay.bmp- 节点 1 最终结果图
nodes/002_检测节点2/result_overlay.bmp- 节点 2 最终结果图
manifest.json- 本次检测完整快照,便于离线查看、调试和导出
4. 数据库表设计
当前实现包含 5 张主表。
4.1 inspection_runs
用途:保存一次完整检测实例的主记录。
| 字段 | 类型 | 说明 |
|---|---|---|
run_id |
TEXT |
主键,检测实例 ID,GUID |
program_name |
TEXT |
CNC 程序名 |
workpiece_id |
TEXT |
工件号 |
serial_number |
TEXT |
序列号 |
started_at |
TEXT |
开始时间,ISO 8601 |
completed_at |
TEXT |
结束时间,ISO 8601,可空 |
overall_pass |
INTEGER |
整体判定,0/1 |
source_image_path |
TEXT |
原图相对路径 |
result_root_path |
TEXT |
本次结果包根目录相对路径 |
node_count |
INTEGER |
节点数量 |
样例数据:
| run_id | program_name | workpiece_id | serial_number | started_at | completed_at | overall_pass | source_image_path | result_root_path | node_count |
|---|---|---|---|---|---|---|---|---|---|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
NewCncProgram |
QFN_1 |
SN-001 |
2026-04-21T10:00:00.0000000Z |
2026-04-21T10:00:03.2000000Z |
0 |
Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/run/source.bmp |
Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
2 |
4.2 inspection_node_results
用途:保存一次检测实例中的节点级结果。
| 字段 | 类型 | 说明 |
|---|---|---|
run_id |
TEXT |
所属检测实例 ID |
node_id |
TEXT |
节点 ID,GUID |
node_index |
INTEGER |
节点序号 |
node_name |
TEXT |
节点名称 |
pipeline_id |
TEXT |
Pipeline ID,GUID |
pipeline_name |
TEXT |
Pipeline 名称 |
pipeline_version_hash |
TEXT |
Pipeline 快照 hash |
node_pass |
INTEGER |
节点判定,0/1 |
source_image_path |
TEXT |
节点输入图相对路径 |
result_image_path |
TEXT |
节点结果图相对路径 |
status |
TEXT |
节点状态:Succeeded / Failed / PartialSuccess / AssetMissing |
duration_ms |
INTEGER |
节点耗时 |
样例数据:
| run_id | node_id | node_index | node_name | pipeline_name | pipeline_version_hash | node_pass | source_image_path | result_image_path | status | duration_ms |
|---|---|---|---|---|---|---|---|---|---|---|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
11111111-1111-1111-1111-111111111111 |
1 |
检测节点1 |
Recipe-A |
A1B2C3... |
1 |
Results/2026/04/21/.../nodes/001_检测节点1/input.bmp |
Results/2026/04/21/.../nodes/001_检测节点1/result_overlay.bmp |
Succeeded |
135 |
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
22222222-2222-2222-2222-222222222222 |
2 |
检测节点2 |
Recipe-B |
D4E5F6... |
0 |
`` | Results/2026/04/21/.../nodes/002_检测节点2/result_overlay.bmp |
Failed |
240 |
4.3 inspection_metric_results
用途:保存节点输出的结构化数值结果。
| 字段 | 类型 | 说明 |
|---|---|---|
run_id |
TEXT |
所属检测实例 ID |
node_id |
TEXT |
所属节点 ID |
metric_key |
TEXT |
指标 key |
metric_name |
TEXT |
指标名称 |
metric_value |
REAL |
指标值 |
unit |
TEXT |
单位 |
lower_limit |
REAL |
下限,可空 |
upper_limit |
REAL |
上限,可空 |
is_pass |
INTEGER |
单指标判定,0/1 |
display_order |
INTEGER |
展示顺序 |
样例数据:
| run_id | node_id | metric_key | metric_name | metric_value | unit | lower_limit | upper_limit | is_pass | display_order |
|---|---|---|---|---|---|---|---|---|---|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
11111111-1111-1111-1111-111111111111 |
bridge.rate |
Bridge Rate |
0.12 |
% |
0.2 |
1 |
1 |
|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
11111111-1111-1111-1111-111111111111 |
void.area |
Void Area |
5.6 |
px |
8 |
1 |
2 |
|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
22222222-2222-2222-2222-222222222222 |
solder.height |
Solder Height |
1.7 |
mm |
1.8 |
0 |
1 |
4.4 inspection_assets
用途:保存文件资产索引。
| 字段 | 类型 | 说明 |
|---|---|---|
run_id |
TEXT |
所属检测实例 ID |
node_id |
TEXT |
所属节点 ID,可空 |
asset_type |
TEXT |
资产类型 |
relative_path |
TEXT |
相对路径 |
file_format |
TEXT |
文件格式 |
width |
INTEGER |
宽度 |
height |
INTEGER |
高度 |
约定的 asset_type:
RunSourceImageNodeInputImageNodeResultImage
样例数据:
| run_id | node_id | asset_type | relative_path | file_format | width | height |
|---|---|---|---|---|---|---|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
RunSourceImage |
Results/2026/04/21/.../run/source.bmp |
bmp |
0 |
0 |
|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
11111111-1111-1111-1111-111111111111 |
NodeInputImage |
Results/2026/04/21/.../nodes/001_检测节点1/input.bmp |
bmp |
0 |
0 |
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
11111111-1111-1111-1111-111111111111 |
NodeResultImage |
Results/2026/04/21/.../nodes/001_检测节点1/result_overlay.bmp |
bmp |
0 |
0 |
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
22222222-2222-2222-2222-222222222222 |
NodeResultImage |
Results/2026/04/21/.../nodes/002_检测节点2/result_overlay.bmp |
bmp |
0 |
0 |
4.5 pipeline_execution_snapshots
用途:保存节点执行时的 Pipeline 快照,避免后续配方修改影响历史报告。
| 字段 | 类型 | 说明 |
|---|---|---|
run_id |
TEXT |
所属检测实例 ID |
node_id |
TEXT |
所属节点 ID |
pipeline_name |
TEXT |
Pipeline 名称 |
pipeline_definition_json |
TEXT |
Pipeline 序列化 JSON |
pipeline_hash |
TEXT |
Pipeline JSON 的 SHA-256 |
样例数据:
| run_id | node_id | pipeline_name | pipeline_hash |
|---|---|---|---|
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
11111111-1111-1111-1111-111111111111 |
Recipe-A |
A1B2C3... |
7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a |
22222222-2222-2222-2222-222222222222 |
Recipe-B |
D4E5F6... |
5. manifest.json 示例
每次 CompleteRunAsync 后,会在结果包目录下生成 manifest.json。
示例:
{
"Run": {
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"ProgramName": "NewCncProgram",
"WorkpieceId": "QFN_1",
"SerialNumber": "SN-001",
"StartedAt": "2026-04-21T10:00:00.0000000Z",
"CompletedAt": "2026-04-21T10:00:03.2000000Z",
"OverallPass": false,
"SourceImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/run/source.bmp",
"ResultRootPath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeCount": 2
},
"Nodes": [
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "11111111-1111-1111-1111-111111111111",
"NodeIndex": 1,
"NodeName": "检测节点1",
"PipelineName": "Recipe-A",
"PipelineVersionHash": "A1B2C3...",
"NodePass": true,
"SourceImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/001_检测节点1/input.bmp",
"ResultImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/001_检测节点1/result_overlay.bmp",
"Status": 0,
"DurationMs": 135
},
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "22222222-2222-2222-2222-222222222222",
"NodeIndex": 2,
"NodeName": "检测节点2",
"PipelineName": "Recipe-B",
"PipelineVersionHash": "D4E5F6...",
"NodePass": false,
"SourceImagePath": "",
"ResultImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/002_检测节点2/result_overlay.bmp",
"Status": 1,
"DurationMs": 240
}
],
"Metrics": [
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "11111111-1111-1111-1111-111111111111",
"MetricKey": "bridge.rate",
"MetricName": "Bridge Rate",
"MetricValue": 0.12,
"Unit": "%",
"LowerLimit": null,
"UpperLimit": 0.2,
"IsPass": true,
"DisplayOrder": 1
},
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "22222222-2222-2222-2222-222222222222",
"MetricKey": "solder.height",
"MetricName": "Solder Height",
"MetricValue": 1.7,
"Unit": "mm",
"LowerLimit": 1.8,
"UpperLimit": null,
"IsPass": false,
"DisplayOrder": 1
}
]
}
说明:
manifest.json是文件侧的完整快照- SQLite 是主索引
- 报告模块可以优先查库,再按需读取文件
6. 报告模块如何取数
6.1 首页列表 / 历史查询
建议通过:
inspection_runs- 必要时联查
inspection_node_results
可支持筛选条件:
- 时间范围
- 程序名
- 工件号
- 序列号
- Pipeline 名称
6.2 单份报告生成
建议按 RunId 调用:
GetRunDetailAsync(runId)
得到:
RunNodesMetricsAssetsPipelineSnapshots
即可直接组装报告:
- 报告首页
- 工件号、序列号、程序名、开始/结束时间、整体判定
- 节点章节
- 节点名称
- 配方名
- 结果图
- 关键指标值
- 节点判定
- 追溯信息
- Pipeline 快照 hash
- 原图路径
- 结果图路径
6.3 离线导出
若后续需要将单个检测实例导出给第三方或留档,可直接复制:
- 整个
Results/{yyyy}/{MM}/{dd}/{RunId}目录
这样会同时带走:
manifest.json- 原图
- 节点图
7. 当前实现接口
当前服务接口:
BeginRunAsync(...)- 创建实例记录和结果目录
AppendNodeResultAsync(...)- 写入节点结果、指标、图片索引、Pipeline 快照
CompleteRunAsync(...)- 回填结束时间、整体判定,并写出
manifest.json
- 回填结束时间、整体判定,并写出
QueryRunsAsync(...)- 查询检测实例列表
GetRunDetailAsync(...)- 查询单个实例的完整报告数据
当前 DI 注册:
IInspectionResultStore -> InspectionResultStore
8. 设计约束与说明
8.1 为什么不直接扩展 MeasurementDataService
因为原有 MeasurementRecord 只适合:
- 单值统计
- 简单的 pass/fail 汇总
它不适合承载:
- 多节点
- 多图像
- 多指标
- Pipeline 快照
- 报告导出
所以当前设计中:
MeasurementDataService继续保留给旧统计用途InspectionResultStore作为报告归档主通道
8.2 为什么图片不直接进 SQLite
因为图片数据量大,直接入库会带来:
- 数据库膨胀
- 查询性能下降
- 迁移和备份成本变高
因此使用:
- SQLite 存索引
- 文件系统存图片
这是更适合报告场景的折中方案。
8.3 为什么要保存 Pipeline 快照
因为报告要可追溯。
如果只保存 PipelineName,后续配方被修改后,历史报告就无法复原当时的真实算法链。
因此需要保存:
pipeline_definition_jsonpipeline_hash
9. 后续可扩展方向
后续可继续扩展:
-
增加
manifest.json中的设备上下文- 运动位置
- 射线源状态
- 探测器状态
-
增加中间图可选保留策略
- 默认关闭
- 调试模式开启
-
增加结果导出包
- ZIP 打包
- 单份报告 PDF
-
增加报告模板字段映射
- 将
MetricKey映射到报告模板占位符
- 将
-
增加数据清理策略
- 保留天数
- 自动清理旧图片
- 保留数据库索引或同时删除
10. 结论
当前这套归档设计的核心特点是:
- 以“检测实例”为主组织数据
- 以“检测节点”为明细展开
- 以“结构化指标 + 图片文件 + Pipeline 快照”支撑报告
- 通过 SQLite 和文件系统混合存储兼顾查询效率和图片落盘
对于后续报告模块,这套结构已经可以直接支持:
- 历史列表查询
- 单次检测报告生成
- 结果图展示
- 节点级指标展示
- 历史结果可追溯