feat(inspection): 新增运行事件流水表和运行状态字段
- inspection_runs 表新增 status 字段(pending/running/completed/stopped/error) - 新增 inspection_run_events 表,记录运行过程事件时间线 - 新增 InspectionRunStatus/InspectionRunEventType 枚举和 InspectionRunEvent 模型 - IInspectionResultStore 接口新增 AppendRunEventAsync/QueryRunEventsAsync - BeginRunAsync/CompleteRunAsync 自动记录运行事件 - 更新 CNC多检测结果归档 文档,同步新表和新字段说明
This commit is contained in:
@@ -45,6 +45,8 @@
|
||||
- 图像或附件索引
|
||||
- `PipelineExecutionSnapshot`
|
||||
- 节点执行时使用的 Pipeline 快照
|
||||
- `InspectionRunEvent`
|
||||
- 运行过程事件记录,用于完整回放执行时间线
|
||||
|
||||
默认图片保留策略:
|
||||
|
||||
@@ -119,7 +121,7 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
|
||||
## 4. 数据库表设计
|
||||
|
||||
当前实现包含 5 张主表。
|
||||
当前实现包含 6 张主表。
|
||||
|
||||
### 4.1 `inspection_runs`
|
||||
|
||||
@@ -134,10 +136,21 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
| `started_at` | `TEXT` | 开始时间,ISO 8601 |
|
||||
| `completed_at` | `TEXT` | 结束时间,ISO 8601,可空 |
|
||||
| `overall_pass` | `INTEGER` | 整体判定,`0/1` |
|
||||
| `status` | `TEXT` | 运行状态:`pending / running / completed / stopped / error` |
|
||||
| `source_image_path` | `TEXT` | 原图相对路径 |
|
||||
| `result_root_path` | `TEXT` | 本次结果包根目录相对路径 |
|
||||
| `node_count` | `INTEGER` | 节点数量 |
|
||||
|
||||
`status` 字段说明:
|
||||
|
||||
| 状态值 | 含义 |
|
||||
|---|---|
|
||||
| `pending` | 等待执行(默认初始值) |
|
||||
| `running` | 正在执行中 |
|
||||
| `completed` | 正常完成 |
|
||||
| `stopped` | 用户手动停止 |
|
||||
| `error` | 异常终止 |
|
||||
|
||||
样例数据:
|
||||
|
||||
| run_id | program_name | workpiece_id | serial_number | started_at | completed_at | overall_pass | source_image_path | result_root_path | node_count |
|
||||
@@ -243,6 +256,42 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
| `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...` |
|
||||
|
||||
### 4.6 `inspection_run_events`
|
||||
|
||||
用途:记录运行过程中的事件流水,用于完整回放一次 CNC 运行的时间线,便于故障排查和性能分析。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | `INTEGER` | 主键,自增 |
|
||||
| `run_id` | `TEXT` | 所属检测实例 ID |
|
||||
| `node_id` | `TEXT` | 关联节点 ID,运行级事件时为空 |
|
||||
| `event_type` | `TEXT` | 事件类型 |
|
||||
| `event_time` | `TEXT` | 事件时间,ISO 8601 |
|
||||
| `payload_json` | `TEXT` | 事件附加数据,JSON 格式,可空 |
|
||||
|
||||
约定的 `event_type`:
|
||||
|
||||
| 事件类型 | 含义 |
|
||||
|---|---|
|
||||
| `RunStarted` | 运行开始 |
|
||||
| `NodeStarted` | 节点开始执行 |
|
||||
| `NodeCompleted` | 节点执行完成 |
|
||||
| `NodeFailed` | 节点执行失败 |
|
||||
| `RunStopped` | 运行被用户停止 |
|
||||
| `RunCompleted` | 运行正常完成 |
|
||||
| `RunError` | 运行异常终止 |
|
||||
|
||||
样例数据:
|
||||
|
||||
| id | run_id | node_id | event_type | event_time | payload_json |
|
||||
|---:|---|---|---|---|---|
|
||||
| `1` | `7d7d8d7d-...` | | `RunStarted` | `2026-04-21T10:00:00.0000000Z` | |
|
||||
| `2` | `7d7d8d7d-...` | `11111111-...` | `NodeStarted` | `2026-04-21T10:00:00.1000000Z` | |
|
||||
| `3` | `7d7d8d7d-...` | `11111111-...` | `NodeCompleted` | `2026-04-21T10:00:00.2350000Z` | |
|
||||
| `4` | `7d7d8d7d-...` | `22222222-...` | `NodeStarted` | `2026-04-21T10:00:00.2400000Z` | |
|
||||
| `5` | `7d7d8d7d-...` | `22222222-...` | `NodeFailed` | `2026-04-21T10:00:00.4800000Z` | `{"error":"Solder height out of range"}` |
|
||||
| `6` | `7d7d8d7d-...` | | `RunCompleted` | `2026-04-21T10:00:03.2000000Z` | |
|
||||
|
||||
---
|
||||
|
||||
## 5. `manifest.json` 示例
|
||||
@@ -261,6 +310,7 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
"StartedAt": "2026-04-21T10:00:00.0000000Z",
|
||||
"CompletedAt": "2026-04-21T10:00:03.2000000Z",
|
||||
"OverallPass": false,
|
||||
"Status": "completed",
|
||||
"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
|
||||
@@ -318,6 +368,40 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
"IsPass": false,
|
||||
"DisplayOrder": 1
|
||||
}
|
||||
],
|
||||
"Events": [
|
||||
{
|
||||
"Id": 1,
|
||||
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
|
||||
"NodeId": null,
|
||||
"EventType": "RunStarted",
|
||||
"EventTime": "2026-04-21T10:00:00.0000000Z",
|
||||
"PayloadJson": ""
|
||||
},
|
||||
{
|
||||
"Id": 2,
|
||||
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
|
||||
"NodeId": "11111111-1111-1111-1111-111111111111",
|
||||
"EventType": "NodeStarted",
|
||||
"EventTime": "2026-04-21T10:00:00.1000000Z",
|
||||
"PayloadJson": ""
|
||||
},
|
||||
{
|
||||
"Id": 3,
|
||||
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
|
||||
"NodeId": "11111111-1111-1111-1111-111111111111",
|
||||
"EventType": "NodeCompleted",
|
||||
"EventTime": "2026-04-21T10:00:00.2350000Z",
|
||||
"PayloadJson": ""
|
||||
},
|
||||
{
|
||||
"Id": 6,
|
||||
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
|
||||
"NodeId": null,
|
||||
"EventType": "RunCompleted",
|
||||
"EventTime": "2026-04-21T10:00:03.2000000Z",
|
||||
"PayloadJson": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -355,11 +439,12 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
|
||||
得到:
|
||||
|
||||
- `Run`
|
||||
- `Run`(含 `Status` 字段)
|
||||
- `Nodes`
|
||||
- `Metrics`
|
||||
- `Assets`
|
||||
- `PipelineSnapshots`
|
||||
- `Events`(运行事件时间线)
|
||||
|
||||
即可直接组装报告:
|
||||
|
||||
@@ -395,15 +480,19 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
当前服务接口:
|
||||
|
||||
- `BeginRunAsync(...)`
|
||||
- 创建实例记录和结果目录
|
||||
- 创建实例记录和结果目录,设置状态为 `running`,记录 `RunStarted` 事件
|
||||
- `AppendNodeResultAsync(...)`
|
||||
- 写入节点结果、指标、图片索引、Pipeline 快照
|
||||
- `CompleteRunAsync(...)`
|
||||
- 回填结束时间、整体判定,并写出 `manifest.json`
|
||||
- 回填结束时间、整体判定,设置状态为 `completed` 或 `stopped`,记录对应事件,并写出 `manifest.json`
|
||||
- `AppendRunEventAsync(...)`
|
||||
- 追加运行事件记录,用于完整回放执行时间线
|
||||
- `QueryRunEventsAsync(...)`
|
||||
- 查询指定运行的所有事件记录
|
||||
- `QueryRunsAsync(...)`
|
||||
- 查询检测实例列表
|
||||
- `GetRunDetailAsync(...)`
|
||||
- 查询单个实例的完整报告数据
|
||||
- 查询单个实例的完整报告数据(含事件列表)
|
||||
|
||||
当前 DI 注册:
|
||||
|
||||
@@ -498,8 +587,9 @@ Results/{yyyy}/{MM}/{dd}/{RunId}/
|
||||
|
||||
对于后续报告模块,这套结构已经可以直接支持:
|
||||
|
||||
- 历史列表查询
|
||||
- 历史列表查询(支持按状态筛选)
|
||||
- 单次检测报告生成
|
||||
- 结果图展示
|
||||
- 节点级指标展示
|
||||
- 历史结果可追溯
|
||||
- 执行过程时间线回放(故障排查、性能分析)
|
||||
|
||||
@@ -10,6 +10,44 @@ namespace XplorePlane.Models
|
||||
NodeResultImage
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测运行状态枚举
|
||||
/// </summary>
|
||||
public enum InspectionRunStatus
|
||||
{
|
||||
/// <summary>等待执行</summary>
|
||||
Pending,
|
||||
/// <summary>正在执行</summary>
|
||||
Running,
|
||||
/// <summary>正常完成</summary>
|
||||
Completed,
|
||||
/// <summary>用户停止</summary>
|
||||
Stopped,
|
||||
/// <summary>异常终止</summary>
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行事件类型枚举
|
||||
/// </summary>
|
||||
public enum InspectionRunEventType
|
||||
{
|
||||
/// <summary>运行开始</summary>
|
||||
RunStarted,
|
||||
/// <summary>节点开始执行</summary>
|
||||
NodeStarted,
|
||||
/// <summary>节点执行完成</summary>
|
||||
NodeCompleted,
|
||||
/// <summary>节点执行失败</summary>
|
||||
NodeFailed,
|
||||
/// <summary>运行被用户停止</summary>
|
||||
RunStopped,
|
||||
/// <summary>运行正常完成</summary>
|
||||
RunCompleted,
|
||||
/// <summary>运行异常终止</summary>
|
||||
RunError
|
||||
}
|
||||
|
||||
public enum InspectionNodeStatus
|
||||
{
|
||||
Succeeded,
|
||||
@@ -27,6 +65,7 @@ namespace XplorePlane.Models
|
||||
public DateTime StartedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public bool OverallPass { get; set; }
|
||||
public InspectionRunStatus Status { get; set; } = InspectionRunStatus.Pending;
|
||||
public string SourceImagePath { get; set; } = string.Empty;
|
||||
public string ResultRootPath { get; set; } = string.Empty;
|
||||
public int NodeCount { get; set; }
|
||||
@@ -112,5 +151,19 @@ namespace XplorePlane.Models
|
||||
public IReadOnlyList<InspectionMetricResult> Metrics { get; set; } = Array.Empty<InspectionMetricResult>();
|
||||
public IReadOnlyList<InspectionAssetRecord> Assets { get; set; } = Array.Empty<InspectionAssetRecord>();
|
||||
public IReadOnlyList<PipelineExecutionSnapshot> PipelineSnapshots { get; set; } = Array.Empty<PipelineExecutionSnapshot>();
|
||||
public IReadOnlyList<InspectionRunEvent> Events { get; set; } = Array.Empty<InspectionRunEvent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行事件记录,用于完整回放一次 CNC 运行的时间线
|
||||
/// </summary>
|
||||
public class InspectionRunEvent
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public Guid RunId { get; set; }
|
||||
public Guid? NodeId { get; set; }
|
||||
public InspectionRunEventType EventType { get; set; }
|
||||
public DateTime EventTime { get; set; } = DateTime.UtcNow;
|
||||
public string PayloadJson { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,16 @@ namespace XplorePlane.Services.InspectionResults
|
||||
|
||||
Task CompleteRunAsync(Guid runId, bool? overallPass = null, DateTime? completedAt = null);
|
||||
|
||||
/// <summary>
|
||||
/// 追加运行事件记录,用于完整回放一次 CNC 运行的时间线。
|
||||
/// </summary>
|
||||
Task AppendRunEventAsync(Guid runId, Guid? nodeId, InspectionRunEventType eventType, string payloadJson = null);
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定运行的所有事件记录。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<InspectionRunEvent>> QueryRunEventsAsync(Guid runId);
|
||||
|
||||
Task<IReadOnlyList<InspectionRunRecord>> QueryRunsAsync(InspectionRunQuery query = null);
|
||||
|
||||
Task<InspectionRunDetail> GetRunDetailAsync(Guid runId);
|
||||
|
||||
@@ -37,6 +37,7 @@ CREATE TABLE IF NOT EXISTS inspection_runs (
|
||||
started_at TEXT NOT NULL,
|
||||
completed_at TEXT NULL,
|
||||
overall_pass INTEGER NOT NULL DEFAULT 0,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
source_image_path TEXT NOT NULL,
|
||||
result_root_path TEXT NOT NULL,
|
||||
node_count INTEGER NOT NULL DEFAULT 0
|
||||
@@ -45,6 +46,7 @@ CREATE INDEX IF NOT EXISTS idx_inspection_runs_started_at ON inspection_runs(sta
|
||||
CREATE INDEX IF NOT EXISTS idx_inspection_runs_program_name ON inspection_runs(program_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_inspection_runs_workpiece_id ON inspection_runs(workpiece_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_inspection_runs_serial_number ON inspection_runs(serial_number);
|
||||
CREATE INDEX IF NOT EXISTS idx_inspection_runs_status ON inspection_runs(status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inspection_node_results (
|
||||
run_id TEXT NOT NULL,
|
||||
@@ -99,15 +101,27 @@ CREATE TABLE IF NOT EXISTS pipeline_execution_snapshots (
|
||||
pipeline_hash TEXT NOT NULL,
|
||||
PRIMARY KEY (run_id, node_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pipeline_snapshots_run_id ON pipeline_execution_snapshots(run_id);";
|
||||
CREATE INDEX IF NOT EXISTS idx_pipeline_snapshots_run_id ON pipeline_execution_snapshots(run_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inspection_run_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
run_id TEXT NOT NULL,
|
||||
node_id TEXT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
event_time TEXT NOT NULL,
|
||||
payload_json TEXT NULL,
|
||||
FOREIGN KEY (run_id) REFERENCES inspection_runs(run_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_run_events_run_id ON inspection_run_events(run_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_run_events_event_type ON inspection_run_events(event_type);";
|
||||
|
||||
private const string InsertRunSql = @"
|
||||
INSERT INTO inspection_runs (
|
||||
run_id, program_name, workpiece_id, serial_number, started_at, completed_at,
|
||||
overall_pass, source_image_path, result_root_path, node_count)
|
||||
overall_pass, status, source_image_path, result_root_path, node_count)
|
||||
VALUES (
|
||||
@run_id, @program_name, @workpiece_id, @serial_number, @started_at, @completed_at,
|
||||
@overall_pass, @source_image_path, @result_root_path, @node_count)";
|
||||
@overall_pass, @status, @source_image_path, @result_root_path, @node_count)";
|
||||
|
||||
private const string InsertNodeSql = @"
|
||||
INSERT OR REPLACE INTO inspection_node_results (
|
||||
@@ -140,10 +154,19 @@ VALUES (
|
||||
private const string DeleteNodeMetricsSql = "DELETE FROM inspection_metric_results WHERE run_id = @run_id AND node_id = @node_id";
|
||||
private const string DeleteNodeAssetsSql = "DELETE FROM inspection_assets WHERE run_id = @run_id AND node_id = @node_id";
|
||||
|
||||
private const string InsertEventSql = @"
|
||||
INSERT INTO inspection_run_events (run_id, node_id, event_type, event_time, payload_json)
|
||||
VALUES (@run_id, @node_id, @event_type, @event_time, @payload_json)";
|
||||
|
||||
private const string QueryEventsSql = @"
|
||||
SELECT id, run_id, node_id, event_type, event_time, payload_json
|
||||
FROM inspection_run_events WHERE run_id = @run_id ORDER BY id ASC";
|
||||
|
||||
private const string UpdateRunSql = @"
|
||||
UPDATE inspection_runs
|
||||
SET completed_at = @completed_at,
|
||||
overall_pass = @overall_pass,
|
||||
status = @status,
|
||||
node_count = @node_count,
|
||||
source_image_path = @source_image_path
|
||||
WHERE run_id = @run_id";
|
||||
@@ -207,6 +230,7 @@ WHERE run_id = @run_id";
|
||||
["started_at"] = runRecord.StartedAt.ToString("o"),
|
||||
["completed_at"] = runRecord.CompletedAt?.ToString("o"),
|
||||
["overall_pass"] = runRecord.OverallPass ? 1 : 0,
|
||||
["status"] = runRecord.Status.ToString().ToLowerInvariant(),
|
||||
["source_image_path"] = runRecord.SourceImagePath,
|
||||
["result_root_path"] = runRecord.ResultRootPath,
|
||||
["node_count"] = runRecord.NodeCount
|
||||
@@ -217,6 +241,9 @@ WHERE run_id = @run_id";
|
||||
_logger.Error(result.Exception!, "创建检测实例失败 | Failed to create inspection run: {Message}", result.Message);
|
||||
throw new InvalidOperationException($"创建检测实例失败: {result.Message}", result.Exception);
|
||||
}
|
||||
|
||||
// 记录运行开始事件
|
||||
await AppendRunEventAsync(runRecord.RunId, null, InspectionRunEventType.RunStarted).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task AppendNodeResultAsync(
|
||||
@@ -392,11 +419,22 @@ WHERE run_id = @run_id";
|
||||
run.NodeCount = detail.Nodes.Count;
|
||||
run.OverallPass = overallPass ?? detail.Nodes.All(node => node.NodePass);
|
||||
|
||||
// 根据 overallPass 参数确定最终状态
|
||||
if (overallPass == null)
|
||||
{
|
||||
run.Status = InspectionRunStatus.Stopped;
|
||||
}
|
||||
else
|
||||
{
|
||||
run.Status = InspectionRunStatus.Completed;
|
||||
}
|
||||
|
||||
var update = await _db.ExecuteNonQueryAsync(UpdateRunSql, new Dictionary<string, object>
|
||||
{
|
||||
["run_id"] = run.RunId.ToString("D"),
|
||||
["completed_at"] = run.CompletedAt?.ToString("o"),
|
||||
["overall_pass"] = run.OverallPass ? 1 : 0,
|
||||
["status"] = run.Status.ToString().ToLowerInvariant(),
|
||||
["node_count"] = run.NodeCount,
|
||||
["source_image_path"] = run.SourceImagePath
|
||||
}).ConfigureAwait(false);
|
||||
@@ -407,6 +445,10 @@ WHERE run_id = @run_id";
|
||||
throw new InvalidOperationException($"完成检测实例失败: {update.Message}", update.Exception);
|
||||
}
|
||||
|
||||
// 记录运行完成/停止事件
|
||||
var eventType = overallPass == null ? InspectionRunEventType.RunStopped : InspectionRunEventType.RunCompleted;
|
||||
await AppendRunEventAsync(runId, null, eventType).ConfigureAwait(false);
|
||||
|
||||
detail.Run = run;
|
||||
await WriteManifestAsync(detail).ConfigureAwait(false);
|
||||
}
|
||||
@@ -540,6 +582,66 @@ WHERE run_id = @run_id";
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追加运行事件记录,用于完整回放一次 CNC 运行的时间线。
|
||||
/// </summary>
|
||||
public async Task AppendRunEventAsync(Guid runId, Guid? nodeId, InspectionRunEventType eventType, string payloadJson = null)
|
||||
{
|
||||
await EnsureInitializedAsync().ConfigureAwait(false);
|
||||
|
||||
var result = await _db.ExecuteNonQueryAsync(InsertEventSql, new Dictionary<string, object>
|
||||
{
|
||||
["run_id"] = runId.ToString("D"),
|
||||
["node_id"] = nodeId?.ToString("D"),
|
||||
["event_type"] = eventType.ToString(),
|
||||
["event_time"] = DateTime.UtcNow.ToString("o"),
|
||||
["payload_json"] = payloadJson
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_logger.Warn("记录运行事件失败 | Failed to append run event: {Message}", result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定运行的所有事件记录。
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<InspectionRunEvent>> QueryRunEventsAsync(Guid runId)
|
||||
{
|
||||
await EnsureInitializedAsync().ConfigureAwait(false);
|
||||
|
||||
var result = await _db.QueryListAsync<InspectionRunEventRow>(QueryEventsSql, new Dictionary<string, object>
|
||||
{
|
||||
["run_id"] = runId.ToString("D")
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (!result.Result.IsSuccess)
|
||||
{
|
||||
return Array.Empty<InspectionRunEvent>();
|
||||
}
|
||||
|
||||
var events = new List<InspectionRunEvent>();
|
||||
foreach (var row in result.Data)
|
||||
{
|
||||
events.Add(MapEvent(row));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private static InspectionRunEvent MapEvent(InspectionRunEventRow row)
|
||||
{
|
||||
return new InspectionRunEvent
|
||||
{
|
||||
Id = row.id,
|
||||
RunId = Guid.Parse(row.run_id),
|
||||
NodeId = string.IsNullOrWhiteSpace(row.node_id) ? null : Guid.Parse(row.node_id),
|
||||
EventType = Enum.TryParse<InspectionRunEventType>(row.event_type, out var et) ? et : InspectionRunEventType.RunStarted,
|
||||
EventTime = DateTime.Parse(row.event_time, null, System.Globalization.DateTimeStyles.RoundtripKind),
|
||||
PayloadJson = row.payload_json ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private async Task EnsureInitializedAsync()
|
||||
{
|
||||
if (_initialized)
|
||||
@@ -813,6 +915,9 @@ WHERE run_id = @run_id";
|
||||
runRecord.StartedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// BeginRun 时状态应为 Running
|
||||
runRecord.Status = InspectionRunStatus.Running;
|
||||
|
||||
runRecord.ResultRootPath = Path.Combine(
|
||||
"Results",
|
||||
runRecord.StartedAt.ToString("yyyy"),
|
||||
@@ -860,12 +965,25 @@ WHERE run_id = @run_id";
|
||||
? null
|
||||
: DateTime.Parse(row.completed_at, null, System.Globalization.DateTimeStyles.RoundtripKind),
|
||||
OverallPass = row.overall_pass != 0,
|
||||
Status = ParseRunStatus(row.status),
|
||||
SourceImagePath = row.source_image_path,
|
||||
ResultRootPath = row.result_root_path,
|
||||
NodeCount = row.node_count
|
||||
};
|
||||
}
|
||||
|
||||
private static InspectionRunStatus ParseRunStatus(string status)
|
||||
{
|
||||
return status?.ToLowerInvariant() switch
|
||||
{
|
||||
"running" => InspectionRunStatus.Running,
|
||||
"completed" => InspectionRunStatus.Completed,
|
||||
"stopped" => InspectionRunStatus.Stopped,
|
||||
"error" => InspectionRunStatus.Error,
|
||||
_ => InspectionRunStatus.Pending
|
||||
};
|
||||
}
|
||||
|
||||
private static InspectionNodeResult MapNode(InspectionNodeRow row)
|
||||
{
|
||||
_ = Enum.TryParse<InspectionNodeStatus>(row.status, out var status);
|
||||
@@ -957,6 +1075,7 @@ WHERE run_id = @run_id";
|
||||
public string started_at { get; set; } = string.Empty;
|
||||
public string completed_at { get; set; } = string.Empty;
|
||||
public int overall_pass { get; set; }
|
||||
public string status { get; set; } = "pending";
|
||||
public string source_image_path { get; set; } = string.Empty;
|
||||
public string result_root_path { get; set; } = string.Empty;
|
||||
public int node_count { get; set; }
|
||||
@@ -1011,5 +1130,15 @@ WHERE run_id = @run_id";
|
||||
public string pipeline_definition_json { get; set; } = string.Empty;
|
||||
public string pipeline_hash { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
internal class InspectionRunEventRow
|
||||
{
|
||||
public long id { get; set; }
|
||||
public string run_id { get; set; } = string.Empty;
|
||||
public string node_id { get; set; } = string.Empty;
|
||||
public string event_type { get; set; } = string.Empty;
|
||||
public string event_time { get; set; } = string.Empty;
|
||||
public string payload_json { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user