feat(all): 源精度命名体系、工作流可观测台、节点停用管理与白名单归档
核心变更:
1. GridAxisValue 源精度命名
- 新增 GridAxisValue 类型,携带 f64 数值 + YAML 源书写文本(Deref 透明兼容算术)
- config.rs 绕过 serde_yaml 归一化,逐 token 捕获轴值原文(logg: 5.0 → g5.0)
- runner/executor/scheduler 全链路改用 DB TEXT 列权威 point_name,
修复 REAL 列回读丢精度导致的 model_name 错配
2. 工作流执行可观测台
- 新增 stats/progress/points 三组 API(进度时间序列、经验速率 ETA、
停滞预警、逐点明细分页、收敛性热力图数据)
- 新增 workflow_progress_snapshots 表 + tasks/grid_points 耗时列
- runner 携带 last_iter/worst_depth/n_depths 进 conv.json
- 前端新增 hash 路由、工作流详情页(概览/网格点/收敛分析三 Tab)、YAML 编辑器
3. 节点停用/启用管理
- 新增 disabled 状态 + disable/enable API;停用节点保持心跳但停止分发,
worker 空闲待命而非退出;移除 revoke API,token 失效统一走重发覆盖;
移除 host_name 字段
4. 白名单结果归档
- 新增 result_filter 模块,只归档有语义产物,丢弃 Tlusty 中间单元(~2MB/模型)
- executor 原子写入归档 + 200 点 LRU 上限
5. 历史数据导入
- sync_seeds 重写为 import_results:经 /admin/import_seed 标记 converged +
按新版命名迁移产物树
6. 部署与目录重规划
- data/results→seeds、data/archive→result + migrate_data_dirs.sh
- deploy.sh 增强(SSH 复用、Profile、远程 env);Dockerfile 瘦身
7. 文档同步更新 api/database/architecture/deployment
This commit is contained in:
+310
-32
@@ -29,7 +29,7 @@
|
||||
|
||||
服务端基于角色访问控制 (RBAC) 划分三种请求鉴权级别:
|
||||
|
||||
1. **Admin 角色**:具有系统管理权限(工作流 CRUD/起停、节点凭据审批/吊销/重发、系统恢复)。在 Request Header 中需携带:
|
||||
1. **Admin 角色**:具有系统管理权限(工作流 CRUD/起停、节点凭据审批/重发/停用/启用、系统恢复)。在 Request Header 中需携带:
|
||||
```http
|
||||
Authorization: Bearer <DCTS_ADMIN_TOKEN>
|
||||
# 或
|
||||
@@ -63,19 +63,29 @@
|
||||
### 2.1 6维网格点参数 (`GridPointParams`)
|
||||
定义恒星大气模型的 6 维大气参数:温度 $T_{\text{eff}}$、重力加速度 $\log g$、以及元素丰度 $\log(N_{\text{He}}/N_{\text{H}})$, $\log(N_{\text{C}}/N_{\text{H}})$, $\log(N_{\text{N}}/N_{\text{H}})$, $\log(N_{\text{O}}/N_{\text{H}})$。
|
||||
|
||||
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L6-L14)):
|
||||
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs)):
|
||||
```rust
|
||||
// 每个轴值携带数值与 YAML 源书写文本,使命名严格忠于源精度。
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct GridAxisValue { /* 内部:value: f64 + text: 源文本 */ }
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct GridPointParams {
|
||||
pub teff: f64, // 有效温度 (K), e.g. 35000.0
|
||||
pub logg: f64, // 表面重力加速度对数 (cgs), e.g. 5.5
|
||||
pub loghe: f64, // 氦丰度对数, e.g. -1.0
|
||||
pub logc: f64, // 碳丰度对数, e.g. -2.0
|
||||
pub logn: f64, // 氮丰度对数, e.g. -2.0
|
||||
pub logo: f64, // 氧丰度对数, e.g. -2.0
|
||||
pub teff: GridAxisValue, // 有效温度 (K), e.g. 35000.0
|
||||
pub logg: GridAxisValue, // 表面重力加速度对数 (cgs), e.g. 5.5
|
||||
pub loghe: GridAxisValue, // 氦丰度对数, e.g. -1.0
|
||||
pub logc: GridAxisValue, // 碳丰度对数, e.g. -2.0
|
||||
pub logn: GridAxisValue, // 氮丰度对数, e.g. -2.0
|
||||
pub logo: GridAxisValue, // 氧丰度对数, e.g. -2.0
|
||||
}
|
||||
```
|
||||
|
||||
> **serde 行为**:`GridAxisValue` 序列化为纯数值(`f64`),下游 JSON 消费者无感;
|
||||
> 反序列化时优先捕获 YAML/JSON 标量原文。**命名精度**:`model_name()` 直接拼接各轴
|
||||
> 源书写文本——配置里多少位小数就多少位(`logg: 5.0` → `g5.0`,`teff: 20000` → `t20000`),
|
||||
> 与旧版 Python `gen_input5.model_name` 逐字符一致,保证历史数据可迁移。详见
|
||||
> [`GridConfig::from_yaml_str`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/config.rs)(YAML 源文本捕获路径)。
|
||||
|
||||
- **TypeScript 类型声明**:
|
||||
```typescript
|
||||
export interface GridPointParams {
|
||||
@@ -188,7 +198,6 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
```rust
|
||||
pub struct NodeRegisterRequest {
|
||||
pub node_id: String,
|
||||
pub host_name: String,
|
||||
pub max_slots: i32,
|
||||
}
|
||||
|
||||
@@ -201,7 +210,6 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
|
||||
pub struct NodeInfo {
|
||||
pub node_id: String,
|
||||
pub host_name: String,
|
||||
pub max_slots: i32,
|
||||
pub active_slots: i32,
|
||||
pub status: String,
|
||||
@@ -215,7 +223,6 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
```typescript
|
||||
export interface NodeRegisterRequest {
|
||||
node_id: string;
|
||||
host_name: string;
|
||||
max_slots: number;
|
||||
}
|
||||
|
||||
@@ -228,7 +235,6 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
|
||||
export interface NodeInfo {
|
||||
node_id: string;
|
||||
host_name: string;
|
||||
max_slots: number;
|
||||
active_slots: number;
|
||||
status: 'online' | 'offline';
|
||||
@@ -237,6 +243,7 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
last_heartbeat: string;
|
||||
}
|
||||
```
|
||||
> **节点 `status` 取值**: `'online'`(在线分发中)、`'offline'`(心跳超时离线)、`'pending_approval'`(待管理员审批)、`'disabled'`(管理员手动停用——在线但不分发任务,详见 [`/disable`](#6-停用节点-post-apiadminnodesnode_iddisable))。
|
||||
|
||||
---
|
||||
|
||||
@@ -302,42 +309,42 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
```rust
|
||||
pub async fn register_node(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<NodeRegisterRequest>,
|
||||
auth_node: Option<Extension<AuthenticatedNode>>,
|
||||
Json(req): Json<NodeRegisterRequest>
|
||||
) -> impl IntoResponse
|
||||
```
|
||||
- **鉴权**: 是 (若配置 Token)
|
||||
- **鉴权**: 否(免凭据申请,提交后进入 `pending_approval` 待管理员审批)
|
||||
- **请求 Header**: `Content-Type: application/json`
|
||||
- **请求 Body**:
|
||||
```json
|
||||
{
|
||||
"node_id": "node-worker-01",
|
||||
"host_name": "hpc-node-01.local",
|
||||
"max_slots": 8
|
||||
}
|
||||
```
|
||||
- **响应 Schema**:
|
||||
- `200 OK` (成功):
|
||||
- `200 OK` (新节点申请提交成功,待审批):
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"message": "节点注册成功"
|
||||
"status": "pending_approval",
|
||||
"message": "节点注册申请已成功提交!请在管理 Dashboard 控制台上点击【同意接入】授权该节点",
|
||||
"node_token": null
|
||||
}
|
||||
```
|
||||
- `200 OK` (数据库异常):
|
||||
- `200 OK` (已授权节点带专属 token 刷新配置):
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "数据库错误详情"
|
||||
"status": "approved",
|
||||
"message": "节点配置更新成功",
|
||||
"node_token": null
|
||||
}
|
||||
```
|
||||
- **curl 示例**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8090/api/node/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer secret_token" \
|
||||
-d '{
|
||||
"node_id": "node-worker-01",
|
||||
"host_name": "hpc-node-01.local",
|
||||
"max_slots": 8
|
||||
}'
|
||||
```
|
||||
@@ -406,13 +413,14 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
"data": [
|
||||
{
|
||||
"node_id": "node-worker-01",
|
||||
"has_token": true,
|
||||
"is_revoked": false,
|
||||
"issued_at": "2026-07-28T12:00:00Z"
|
||||
"status": "online",
|
||||
"token_status": "active",
|
||||
"token_issued_at": "2026-07-28T12:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
> `token_status` 取值:`active`(有效)/ `none`(无凭据记录)。token 失效靠重发覆盖 hash 实现,不存在「已吊销」中间态。
|
||||
|
||||
#### 2. 同意节点接入申请 (`POST /api/admin/nodes/:node_id/approve`)
|
||||
- **权限**: Admin 角色
|
||||
@@ -422,13 +430,34 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
- **权限**: Admin 角色
|
||||
- **说明**: 拒绝处于待审批状态的 Node 接入。
|
||||
|
||||
#### 4. 吊销节点专属 Token (`POST /api/admin/nodes/:node_id/revoke`)
|
||||
#### 4. 重新颁发节点专属 Token (`POST /api/admin/nodes/:node_id/reissue`)
|
||||
- **权限**: Admin 角色
|
||||
- **说明**: 立即吊销节点专属 Token,被吊销的 Token 无法再通过 Node 鉴权,需重新申请。
|
||||
- **说明**: 作废旧 Token(hash 被覆盖,立即失效)并重新生成新 Token 文本返回。新明文同时暂存到服务端,节点可通过 `/node/check_status` 自动拉取(限 1 天内有效),或由管理员手动同步到节点本地 `.node_token`。
|
||||
|
||||
#### 5. 重新颁发节点专属 Token (`POST /api/admin/nodes/:node_id/reissue`)
|
||||
#### 5. 停用节点 (`POST /api/admin/nodes/:node_id/disable`)
|
||||
- **权限**: Admin 角色
|
||||
- **说明**: 作废旧 Token 并重新生成新 Token 文本返回。
|
||||
- **说明**: 手动停用一个处于 `online`/`offline` 状态的节点。停用后节点**保持在线心跳**(Dashboard 仍可见其存活),但 `claim` 不再向其分发任务,Worker 收到 `{"status":"disabled"}` 后会拉长轮询、空闲待命,可随时调用 `enable` 恢复。
|
||||
- **状态语义**: 节点 `status` 切为 `disabled`。与「重发 Token」(凭据失效、Worker 强制退出重注册)不同——停用仅是运维意图,不退出 Worker 进程、不动凭据。
|
||||
- **响应**:
|
||||
- `200 OK`: `{"success": true, "message": "节点 '...' 已停用,不再分发任务"}`
|
||||
- `409 Conflict`: 节点当前状态不支持停用(仅 `online`/`offline` 可停用,`pending_approval`/`disabled` 返回此码)。
|
||||
- **curl 示例**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/disable \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
```
|
||||
|
||||
#### 6. 重新启用节点 (`POST /api/admin/nodes/:node_id/enable`)
|
||||
- **权限**: Admin 角色
|
||||
- **说明**: 重新启用被手动停用(`disabled`)的节点。状态切为 `offline`,靠节点下一次心跳自然翻为 `online` 后即恢复分发任务——既能自愈,又不会对真实离线的节点虚报在线。
|
||||
- **响应**:
|
||||
- `200 OK`: `{"success": true, "message": "节点 '...' 已重新启用,将在下一次心跳后恢复分发任务"}`
|
||||
- `409 Conflict`: 节点当前状态不支持启用(仅 `disabled` 可启用)。
|
||||
- **curl 示例**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/enable \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -474,6 +503,14 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
"task": null
|
||||
}
|
||||
```
|
||||
- `200 OK` (节点被管理员手动停用,不再分发任务):
|
||||
```json
|
||||
{
|
||||
"status": "disabled",
|
||||
"task": null
|
||||
}
|
||||
```
|
||||
> Worker 收到此响应后保持存活、拉长轮询(每 60s)空闲待命,**不会**退出进程(区别于 401/403 的 Token 失效语义)。管理员调用 `/enable` 后下一次轮询即恢复领用。
|
||||
- `500 Internal Server Error`:
|
||||
```json
|
||||
{
|
||||
@@ -525,7 +562,7 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
"message": "无法解析 params 或 summary_json"
|
||||
}
|
||||
```
|
||||
- **说明**: 当 `converged == true` 且 `atmosphere_has_nan == false` 且包含 `seed_file` 时,服务端会将种子保存至 `results_dir/<point_name>/<point_name>.7` 并记入 `seeds` 表。若冷启动任务失败,服务端会自动唤醒 `GridScheduler` 触发针对该网格点的步进回退算法 (Seed-step Fallback)。
|
||||
- **说明**: 当 `converged == true` 且 `atmosphere_has_nan == false` 且包含 `seed_file` 时,服务端会将种子保存至 `seeds_dir/<point_name>/<point_name>.7` 并记入 `seeds` 表。若冷启动任务失败,服务端会自动唤醒 `GridScheduler` 触发针对该网格点的步进回退算法 (Seed-step Fallback)。
|
||||
- **curl 示例**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8090/api/task/report \
|
||||
@@ -545,6 +582,44 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
-F 'seed_file=@/path/to/t35000_g5.5_he-1_c-2_n-2_o-2.7'
|
||||
```
|
||||
|
||||
### 4.3 历史种子批量导入 (`POST /api/admin/import_seed`)
|
||||
|
||||
把旧版单机 `run_grid.py` 产物(`conv.json` + `.7` 大气文件)批量回灌进 DCTS。与 `/task/report` 的关键区别:**跳过任务归属校验**(历史数据无领用语义),直接幂等落库。供 [`tools/import_results`](file:///home/fmq/program/tlusty/tl208-s54/dcts/tools/import_results/src/main.rs) 调用。
|
||||
|
||||
- **处理函数**: [`import_seed`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/task.rs)
|
||||
- **鉴权**: **Admin 角色**(admin token 或登录 session)
|
||||
- **请求格式**: `multipart/form-data`
|
||||
- Part `report`: 旧版 `conv.json` 的**原文 JSON**(映射为 `ModelSummary`,服务端解析出 `name` / `params` / `converged` / `final_max_relc`)
|
||||
- Part `seed_file`: 二进制数据(`.7` 大气种子文件;收敛点必传)
|
||||
- **Query 参数**: `workflow`(可选,默认 `imported`):目标工作流名,种子导入到该工作流的 `grid_points`。
|
||||
- **命名保真**: `point_name` 取旧 `conv.json` 的 `name` 字段(源精度真名,如 `t20000_g5.0_...`),**逐字符**落库(磁盘目录、`grid_points.name`、`seeds.point_name`),与旧版 Python `gen_input5.model_name` 完全一致。
|
||||
- **幂等**: `ON CONFLICT DO NOTHING` upsert `grid_points`、`conv.json` 与 `.7` 原子覆盖写,可重复运行。
|
||||
- **响应 Schema**:
|
||||
- `200 OK`:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"point_name": "t20000_g5.0_he-2_c-4_n-4_o-4",
|
||||
"converged": true,
|
||||
"max_relc": 0.000321
|
||||
}
|
||||
```
|
||||
- `400 Bad Request`: 缺少 `report` / `conv.json` 解析失败 / 非法网格点名称
|
||||
- `401 Unauthorized`: 缺少或无效 admin 凭据
|
||||
- **curl 示例**:
|
||||
```bash
|
||||
curl -X POST "http://localhost:8090/api/admin/import_seed?workflow=sdB_cno" \
|
||||
-H "Authorization: Bearer <admin_token>" \
|
||||
-F 'report=@/path/to/old_results/t20000_g5.0_he-2_c-4_n-4_o-4/conv.json;type=application/json' \
|
||||
-F 'seed_file=@/path/to/old_results/t20000_g5.0_he-2_c-4_n-4_o-4/t20000_g5.0_he-2_c-4_n-4_o-4.7'
|
||||
```
|
||||
- **批量导入工具**: [`import_results`](file:///home/fmq/program/tlusty/tl208-s54/dcts/tools/import_results/src/main.rs) 自动扫描结果目录、校验无 NaN 且收敛、逐点调用本端点(同时把完整产物树迁移到 `data/result/`):
|
||||
```bash
|
||||
cargo run -p import_results -- --dir <旧 results 根目录> \
|
||||
--config workflows/sdB_cno.yaml \
|
||||
--server http://127.0.0.1:8090 --workflow sdB_cno --token <admin_token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 种子文件管理 API (Seed Management)
|
||||
@@ -656,7 +731,6 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
"nodes": [
|
||||
{
|
||||
"node_id": "node-worker-01",
|
||||
"host_name": "hpc-node-01.local",
|
||||
"max_slots": 8,
|
||||
"active_slots": 2,
|
||||
"status": "online",
|
||||
@@ -930,6 +1004,210 @@ Worker 节点向服务端上报的任务计算结果。
|
||||
|
||||
---
|
||||
|
||||
### 8.7 工作流执行统计 (`GET /api/workflows/:name/stats`)
|
||||
|
||||
- **处理函数**: `get_workflow_stats`(`crates/server/src/api/workflow.rs`)
|
||||
- **权限**: Admin(`/workflows/*` 前缀统一映射)
|
||||
- **用途**: 详情页"任务控制条 + 执行概览"数据源。在 `get_grid_summary_stats(Some(wf))`
|
||||
之上追加难度波次分布与近似 ETA。**`pending` 与 `queued` 分开计数**(与 `/api/status`
|
||||
的全局口径不同,后者由前端合并展示)。
|
||||
- **路径参数**: `:name` — 工作流名(白名单 `[A-Za-z0-9._-]{1,64}`);未知工作流 → `404`。
|
||||
- **响应 Schema (`200 OK`)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功获取工作流统计",
|
||||
"data": {
|
||||
"name": "sdB_cno",
|
||||
"status": "running",
|
||||
"total": 432,
|
||||
"pending": 120,
|
||||
"queued": 40,
|
||||
"running": 8,
|
||||
"converged": 261,
|
||||
"failed": 3,
|
||||
"cold_run_converged": 220,
|
||||
"seed_step_converged": 41,
|
||||
"imported_converged": 0,
|
||||
"waves": [
|
||||
{ "wave": 0, "total": 108, "converged": 108, "failed": 0 }
|
||||
],
|
||||
"avg_point_sec": 740.5,
|
||||
"eta_sec": 9620.0
|
||||
}
|
||||
}
|
||||
```
|
||||
> `avg_point_sec` = `AVG(COALESCE(tasks.elapsed_sec, created_at→completed_at 时间戳差))`——
|
||||
> 优先用 Worker 回报的精确墙钟(不含排队等待),历史无 `elapsed_sec` 的行回退时间戳差近似;
|
||||
> `eta_sec` = `avg_point_sec × (total - converged - failed) ÷ 在线节点总槽位`(并发感知;
|
||||
> 无在线节点按串行兜底);无历史数据时二者为 `null`。
|
||||
|
||||
---
|
||||
|
||||
### 8.8 工作流网格点列表 (`GET /api/workflows/:name/points`)
|
||||
|
||||
- **处理函数**: `get_workflow_points`(`crates/server/src/api/workflow.rs`)
|
||||
- **权限**: Admin
|
||||
- **用途**: 详情页"网格点明细"表与"收敛性分析"热力图、概览"最近动态"流的数据源。
|
||||
每行附带**最近一次尝试**信息(`tasks` 关联子查询取最新行,从未派发过的点 `last_*` 为 null)。
|
||||
- **查询参数**(全部可选,枚举值白名单校验,非法 → `400`):
|
||||
|
||||
| 参数 | 取值 | 默认 |
|
||||
| :--- | :--- | :--- |
|
||||
| `status` | `pending`/`queued`/`running`/`converged`/`failed` | 不过滤 |
|
||||
| `method` | `cold_run`/`seed_step`/`imported` | 不过滤 |
|
||||
| `wave` | 整数波次 | 不过滤 |
|
||||
| `q` | 点名子串(LIKE 通配符已转义) | 不过滤 |
|
||||
| `sort` | `wave`/`teff`/`max_relc`/`attempts`/`last_completed_at` | `wave` |
|
||||
| `order` | `asc`/`desc` | `asc` |
|
||||
| `limit` | 1–500(超出钳位) | 100 |
|
||||
| `offset` | ≥0 | 0 |
|
||||
|
||||
- **响应 Schema (`200 OK`)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功获取工作流网格点列表",
|
||||
"data": {
|
||||
"total": 432,
|
||||
"points": [
|
||||
{
|
||||
"name": "t60000_g5.0_he-2_c-4_n-4_o-4",
|
||||
"teff": 60000.0, "logg": 5.0, "loghe": -2.0,
|
||||
"logc": -4.0, "logn": -4.0, "logo": -4.0,
|
||||
"cno_sum": -12.0, "wave": 0,
|
||||
"status": "converged",
|
||||
"success_method": "seed_step",
|
||||
"attempt_count": 2,
|
||||
"last_max_relc": 0.00043,
|
||||
"last_task_type": "seed_step",
|
||||
"seed_point_name": "t60000_g5.0_he2_c-4_n-4_o-4",
|
||||
"node_id": "node-a1b2",
|
||||
"last_completed_at": "2026-07-30 11:12:00",
|
||||
"last_error": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.9 网格点详情 (`GET /api/workflows/:name/points/:point`)
|
||||
|
||||
- **处理函数**: `get_workflow_point_detail`(`crates/server/src/api/workflow.rs`)
|
||||
- **权限**: Admin
|
||||
- **用途**: 点详情滑入面板——尝试历史(还原"冷启动失败 → 种子步进救回"剧情)
|
||||
+ `conv.json` 逐阶段诊断。
|
||||
- **路径参数**: `:point` — 点名白名单 `[A-Za-z0-9._-+@]`(非空、拒前导 `.`、≤128,
|
||||
即路径穿越前置闸门);conv.json 读盘前再经 canonicalize 归属校验。非法点名 → `400`;
|
||||
点不存在 → `404`。
|
||||
- **响应 Schema (`200 OK`)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功获取网格点详情",
|
||||
"data": {
|
||||
"point": { "...": "同 8.8 单行" },
|
||||
"attempts": [
|
||||
{
|
||||
"task_id": "uuid",
|
||||
"task_type": "cold_run",
|
||||
"seed_point_name": null,
|
||||
"status": "failed",
|
||||
"max_relc": 954000.0,
|
||||
"atmosphere_has_nan": false,
|
||||
"node_id": "node-a1b2",
|
||||
"error_message": "nl stage diverged",
|
||||
"created_at": "2026-07-30 09:00:00",
|
||||
"completed_at": "2026-07-30 09:30:00"
|
||||
},
|
||||
{ "task_type": "seed_step", "status": "completed", "...": "第二次尝试(救回)" }
|
||||
],
|
||||
"conv": {
|
||||
"converged": true,
|
||||
"final_max_relc": 0.00043,
|
||||
"final_chmax": 0.001,
|
||||
"seed": "data/seeds/<seed_name>/<seed_name>.7",
|
||||
"atmosphere_has_nan": false,
|
||||
"synspec_rc": 0,
|
||||
"synspec_sec": 3.1,
|
||||
"elapsed_sec": 126.4,
|
||||
"stages": [
|
||||
{ "label": "seed_nc", "chmax": 0.001, "lte": false, "converged": false, "best_max_relc": 0.02, "elapsed_sec": 62.4, "note": null },
|
||||
{ "label": "nl", "chmax": 0.001, "lte": false, "converged": true, "best_max_relc": 0.00043, "elapsed_sec": 64.0, "note": null }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
> `conv` 来自 `seeds_dir/<point>/conv.json`(`ModelSummary` 结构,见
|
||||
> `crates/common/src/models.rs`)。文件缺失/解析失败时为 `null`(仍返回 200,
|
||||
> 前端降级显示"诊断文件不可用")。
|
||||
|
||||
---
|
||||
|
||||
### 8.10 工作流进度时间序列 (`GET /api/workflows/:name/progress`)
|
||||
|
||||
- **处理函数**: `get_workflow_progress`(`crates/server/src/api/workflow.rs`)
|
||||
- **权限**: Admin
|
||||
- **用途**: 详情页概览的进度曲线(sparkline)、经验速率 ETA 与停滞预警数据源。
|
||||
快照由服务端后台循环(~30s)对每个运行中工作流写入 `workflow_progress_snapshots`
|
||||
表,**计数无变化不落库**(去重防膨胀),保留期 7 天自动清理。
|
||||
- **查询参数**: `hours`(时间窗口,默认 24,钳位 1–168)。
|
||||
- **响应 Schema (`200 OK`)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功获取进度时间序列",
|
||||
"data": {
|
||||
"hours": 24,
|
||||
"series": [
|
||||
{ "ts": "2026-07-31 08:00:00", "total": 432, "pending": 120, "queued": 40,
|
||||
"running": 8, "converged": 261, "failed": 3 }
|
||||
],
|
||||
"rate_per_hour": 12.5,
|
||||
"stalled_minutes": 0.0
|
||||
}
|
||||
}
|
||||
```
|
||||
> `series` 超 300 条自动降采样(首末点保留);`rate_per_hour` = 窗口首末
|
||||
> converged 增量 ÷ 时长(快照不足 2 条为 null);`stalled_minutes` = 终态数
|
||||
> (converged+failed)最后一次增长至窗口末端的分钟数(前端 >10 分钟触发停滞预警)。
|
||||
|
||||
---
|
||||
|
||||
### 8.11 工作流列表内联统计(`GET /api/workflows` 响应增强)
|
||||
|
||||
`WorkflowSummary` 新增 `stats` 字段(单条 `GROUP BY` 聚合回填,无 N+1):
|
||||
工作流尚无网格点(未启动)时为 `null`,前端据此不渲染卡片进度条。
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "成功获取工作流列表",
|
||||
"data": [
|
||||
{
|
||||
"name": "sdB_cno",
|
||||
"description": "sdB 6维恒星大气模型计算网格",
|
||||
"status": "running",
|
||||
"created_at": "2026-07-30 10:00:00",
|
||||
"updated_at": "2026-07-30 11:00:00",
|
||||
"stats": {
|
||||
"total": 432,
|
||||
"converged": 261,
|
||||
"failed": 3,
|
||||
"running": 8,
|
||||
"cold_run_converged": 220,
|
||||
"seed_step_converged": 41
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 错误处理与状态码汇总
|
||||
|
||||
| HTTP 状态码 | 触发场景说明 | 响应格式 | 核心原因与解决建议 |
|
||||
|
||||
Reference in New Issue
Block a user