初始提交:ai-review 项目当前版本(含赛道一/二提交规范修订与时间节点文档)
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
# AI 人才育成评审系统 · 系统设计书
|
||||
|
||||
> 版本:v2.0
|
||||
> 更新日期:2026-07-27
|
||||
> 适用系统:AuraK 评审系统
|
||||
|
||||
---
|
||||
|
||||
## 1. 系统概要
|
||||
|
||||
### 1.1 系统定位
|
||||
|
||||
AuraK是一个AI人才评测系统,采用LangGraph状态机实现评估流程。本文档描述评审系统的完整功能,涵盖技术大赛评审和AI人才育成L2/L3评审。
|
||||
|
||||
### 1.2 核心术语
|
||||
|
||||
| 术语 | 说明 |
|
||||
|:-----|:------|
|
||||
| 赛道 | 评审类型分类。现有:赛道一(Agent开发实战)、赛道二(IDE+范式创新)、人才测评(AI人才育成) |
|
||||
| 共通维度 | 所有题目共享的评审维度(满分100分),评价受验者的基本AI应用能力 |
|
||||
| 追加维度 | 特定题目独有的L3评审维度,评价高水平的工程化能力 |
|
||||
| L2合格 | 共通维度得分率 ≥ 60% |
|
||||
| L3合格 | 总分(共通+追加)得分率 ≥ 80% |
|
||||
| 成果物 | 参赛者需要提交的文件或材料,评审前通过checklist确认 |
|
||||
|
||||
### 1.3 系统架构
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ 前端 │────▶│ 后端API │────▶│ SQLite │
|
||||
│ (Vite) │ │ (Express) │ │ 数据库 │
|
||||
│ :14001 │◀────│ :3002 │◀────│ │
|
||||
└──────────┘ └────┬─────┘ └──────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ DeepSeek API │
|
||||
│ (AI评审引擎) │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能一:项目管理
|
||||
|
||||
### 2.1 赛道管理
|
||||
|
||||
管理员创建项目时可选择赛道:
|
||||
|
||||
| 赛道 | track值 | 总分 | 说明 |
|
||||
|:-----|:--------|:----:|:-----|
|
||||
| 赛道一:Agent开发实战 | `赛道一` | 150 | 分新規/修正子类型 |
|
||||
| 赛道二:IDE+范式创新 | `赛道二` | 100 | — |
|
||||
| 人才测评(AI人才育成) | `人才测评` | 100~150 | 6题,L2/L3判定 |
|
||||
|
||||
### 2.2 标准自动创建
|
||||
|
||||
项目创建时,系统根据track自动创建对应的评审标准:
|
||||
|
||||
- 从 `server/config/standards/` 读取模板文件
|
||||
- 自动在项目中创建标准记录
|
||||
- 赛道一/二/人才测评各有对应的模板
|
||||
- 创建后管理员可修改或删除
|
||||
|
||||
### 2.3 标准自定义上传
|
||||
|
||||
- 支持上传自定义Markdown格式标准文件
|
||||
- 上传时需指定:
|
||||
- 标准名称
|
||||
- 分类标签(用于匹配赛道或题目)
|
||||
- 总分上限(默认150分,可修改)
|
||||
- 标准内容(`## 维度名(XX分)` 格式)
|
||||
- 支持修改和删除标准
|
||||
|
||||
### 2.4 标准模板内容
|
||||
|
||||
系统内置3个标准模板:
|
||||
|
||||
| 文件 | 总分 | 维度数 | 说明 |
|
||||
|:-----|:----:|:------:|:-----|
|
||||
| `技术大赛-赛道一.md` | 150 | 12 | 场景价值、架构、Agent核心等 |
|
||||
| `技术大赛-赛道二.md` | 100 | 8 | 开发范式设计、IDE集成、提效等 |
|
||||
| `AI人才育成L2.md` | 100~150 | 共通6+追加 | 按题目过滤维度 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能二:条目(参赛作品)管理
|
||||
|
||||
### 3.1 条目创建
|
||||
|
||||
| 字段 | 说明 | 必填 |
|
||||
|:-----|:-----|:----:|
|
||||
| 标题 | 条目标题 | ✅ |
|
||||
| 仓库URL | Git仓库地址 | ✅ |
|
||||
| 参赛者 | 参赛者名称 | |
|
||||
| 分支 | Git分支(默认main) | |
|
||||
| 子类型 | 赛道一专用:新規/修正 | |
|
||||
| 题目选择 | 人才测评专用:Q1~Q6 | 人才测评必填 |
|
||||
|
||||
### 3.2 批量导入
|
||||
|
||||
支持CSV格式批量创建条目:
|
||||
|
||||
```
|
||||
title,repo_url,participant
|
||||
张三月结,https://gitea/zhang-01,张三
|
||||
```
|
||||
|
||||
- CSV header定义列名
|
||||
- 赛道信息从项目继承,CSV中无需指定
|
||||
|
||||
### 3.3 条目编辑
|
||||
|
||||
- 仅 `pending` 状态的条目可编辑
|
||||
- 可修改标题、仓库URL、参赛者、子类型、题目等
|
||||
|
||||
### 3.4 条目删除
|
||||
|
||||
- 仅 `pending` 状态可删除
|
||||
- 删除同时清理克隆目录
|
||||
|
||||
### 3.5 筛选与分页
|
||||
|
||||
- 状态筛选(全部/待评审/排队中/克隆中/分析中/已完成等)
|
||||
- 标题搜索
|
||||
- 题目筛选(人才测评专用:Q1~Q6)
|
||||
- 分页(每页50条)
|
||||
- 总计支持250+条目
|
||||
|
||||
---
|
||||
|
||||
## 4. 功能三:成果物确认
|
||||
|
||||
### 4.1 成果物清单
|
||||
|
||||
所有赛道共通的默认成果物清单:
|
||||
|
||||
| 成果物 | 必须 |
|
||||
|:-------|:----:|
|
||||
| 源代码 | ✅ |
|
||||
| README | ✅ |
|
||||
| 设计文档 | ✅ |
|
||||
| 测试用例与测试结果 | ✅ |
|
||||
| AGENTS.md | ✅ |
|
||||
| 样本数据 | ✅ |
|
||||
| 演示录屏 | 可选 |
|
||||
|
||||
### 4.2 项目级别一览画面
|
||||
|
||||
- 以表格形式展示所有条目与成果物的对应关系
|
||||
- 行:条目(参赛者、标题)
|
||||
- 列:各成果物(复选框)
|
||||
- 红底高亮:必须成果物未提交
|
||||
- 绿底:已提交
|
||||
|
||||
### 4.3 统计
|
||||
|
||||
- 顶部显示各成果物的提交率(已提交/总数)
|
||||
- 整体提交率(必须成果物的提交比例)
|
||||
|
||||
### 4.4 操作
|
||||
|
||||
- 初始化一覧:为所有条目设置默认成果物清单
|
||||
- 复选框点击即保存(自动调用API)
|
||||
- 下载CSV:导出为CSV格式
|
||||
|
||||
---
|
||||
|
||||
## 5. 功能四:评审流程
|
||||
|
||||
### 5.1 评审状态机
|
||||
|
||||
```
|
||||
pending → queued → cloning → analyzing → review_done →(人工修正)→ admin_reviewed
|
||||
├ → clone_fail (克隆失败) ─┐
|
||||
├ → failed (评审异常) ──────┼→ retry → pending
|
||||
└ → analysis_fail (兼容值) ─┘
|
||||
(queued/cloning/analyzing 可 cancel → 终态 cancelled)
|
||||
```
|
||||
|
||||
- 最大并发:3
|
||||
- 超过上限的排入队列
|
||||
- 注:`clone_fail` 由克隆写入;评审流程任何未知异常统一置 `failed`;`analysis_fail` 为 retry 兼容保留值,实际无代码写入
|
||||
|
||||
### 5.2 L2/L3评审流程
|
||||
|
||||
```
|
||||
受理验者提交代码
|
||||
│
|
||||
├─ 人才测评 → 根据 question_id 确定追加维度
|
||||
│ Q1(★★):无追加
|
||||
│ Q2/Q4/Q5/Q6(★★★):追加30分
|
||||
│ Q3(★★★★):追加50分
|
||||
│
|
||||
└─ 赛道一/二 → 普通评审(全部维度)
|
||||
|
||||
AI一次评审该题的维度(共通 + 该题对应的追加维度)
|
||||
│
|
||||
├─ 共通维度得分 ≥ 60 → L2合格
|
||||
│ │
|
||||
│ └─ ★★★/★★★★ → 计算总分
|
||||
│ 共通得分 + 追加得分 / 100 + 追加满分 ≥ 80% → L3合格
|
||||
│ 否则 → 仅L2合格
|
||||
│
|
||||
└─ 共通维度得分 < 60 → 不合格
|
||||
```
|
||||
|
||||
### 5.3 评审维度过滤
|
||||
|
||||
人才测评赛道,根据 `question_id` 过滤追加维度:
|
||||
|
||||
- 共通维度:全部评审
|
||||
- 追加维度:仅评审该题对应的追加维度
|
||||
- 维度通过 `[Qn]` 前缀标记分组
|
||||
|
||||
### 5.4 评审结果展示
|
||||
|
||||
- L2共通评分区域(6维度)
|
||||
- L3追加评分区域(如有)
|
||||
- 最终认定等级(🏆 L3合格 / ✅ L2合格 / ❌ 不合格)
|
||||
- 支持评审者手动修正分数和评语
|
||||
|
||||
---
|
||||
|
||||
## 6. 功能五:评审标准管理
|
||||
|
||||
### 6.1 标准文件格式
|
||||
|
||||
Markdown格式,使用 `## 维度名(XX分)` 定义维度:
|
||||
|
||||
```markdown
|
||||
## 功能完整性(40分)
|
||||
检查以下5项:
|
||||
|
||||
1. 核心功能实现(12分)
|
||||
- 题目要求的主要功能是否全部实现
|
||||
```
|
||||
|
||||
人才测评的追加维度使用 `[Qn]` 前缀:
|
||||
|
||||
```markdown
|
||||
## [Q2] LLM生成问卷(15分)
|
||||
## [Q3] LLM检索回答(11分)
|
||||
```
|
||||
|
||||
### 6.2 标准列表展示
|
||||
|
||||
- 标准名称和分类标签
|
||||
- 总分上限
|
||||
- 展开/收起维度详情(评分要素完整显示)
|
||||
|
||||
### 6.3 维度解析
|
||||
|
||||
系统解析标准文件时自动提取:
|
||||
|
||||
```typescript
|
||||
interface Dimension {
|
||||
name: string; // 维度名(不含[Qn]前缀)
|
||||
maxScore: number; // 满分
|
||||
content: string; // 评分要素详细内容
|
||||
group?: string; // 'common'(共通) | 'Q2' | 'Q3' | ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 功能六:后台配置
|
||||
|
||||
### 7.1 标准上限配置
|
||||
|
||||
```env
|
||||
STANDARD_MAX_SCORE=150
|
||||
```
|
||||
|
||||
- 上传标准时校验总分不超过该值
|
||||
- 默认150分,通过环境变量修改
|
||||
- 修改后需重启服务
|
||||
|
||||
### 7.2 标准模板文件位置
|
||||
|
||||
```
|
||||
server/config/standards/
|
||||
├── 技术大赛-赛道一.md
|
||||
├── 技术大赛-赛道二.md
|
||||
└── AI人才育成L2.md
|
||||
```
|
||||
|
||||
- 服务启动时读取
|
||||
- 创建项目时自动应用
|
||||
- 修改后需重启服务
|
||||
|
||||
---
|
||||
|
||||
## 8. 前后端交互API
|
||||
|
||||
### 8.1 项目管理
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|:-----|:-----|
|
||||
| GET | `/api/projects` | 项目列表(含统计) |
|
||||
| POST | `/api/projects` | 创建项目 |
|
||||
| PUT | `/api/projects/:id` | 修改项目 |
|
||||
| DELETE | `/api/projects/:id` | 删除项目(force可选) |
|
||||
|
||||
### 8.2 条目管理
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|:-----|:-----|
|
||||
| GET | `/api/projects/:pid/entries` | 条目列表(支持limit/offset/status/tag/search/question_id) |
|
||||
| POST | `/api/projects/:pid/entries` | 创建条目 |
|
||||
| PUT | `/api/projects/:pid/entries/:eid` | 编辑条目 |
|
||||
| DELETE | `/api/projects/:pid/entries/:eid` | 删除条目 |
|
||||
| POST | `/api/projects/:pid/entries/batch` | 批量导入 |
|
||||
|
||||
### 8.3 成果物管理
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|:-----|:-----|
|
||||
| PUT | `/api/projects/:pid/entries/:eid/deliverables` | 保存单个条目成果物 |
|
||||
| PUT | `/api/projects/:pid/entries/deliverables/init` | 批量初始化成果物 |
|
||||
| GET | `/api/projects/:pid/entries/deliverables/summary` | 成果物统计 |
|
||||
| GET | `/api/projects/:pid/entries/deliverables/export` | 导出成果物CSV |
|
||||
|
||||
### 8.4 评审
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|:-----|:-----|
|
||||
| POST | `/api/projects/:pid/entries/:eid/start` | 启动评审 |
|
||||
| POST | `/api/projects/:pid/entries/:eid/cancel` | 取消评审 |
|
||||
| POST | `/api/projects/:pid/entries/:eid/retry` | 重试评审 |
|
||||
| POST | `/api/projects/:pid/entries/batch-start` | 批量启动 |
|
||||
| PUT | `/api/projects/:pid/entries/:eid/report` | 修正评审结果 |
|
||||
| GET | `/api/projects/:pid/entries/:eid/report/export` | 导出条目PDF |
|
||||
| GET | `/api/projects/:pid/summary` | 项目汇总排名 |
|
||||
| GET | `/api/projects/:pid/summary/export` | 导出汇总PDF |
|
||||
|
||||
### 8.5 标准管理
|
||||
|
||||
| Method | Path | 说明 |
|
||||
|--------|:-----|:-----|
|
||||
| GET | `/api/projects/:pid/standards` | 标准列表 |
|
||||
| POST | `/api/projects/:pid/standards` | 上传标准 |
|
||||
| PUT | `/api/projects/:pid/standards/:sid` | 修改标准 |
|
||||
| DELETE | `/api/projects/:pid/standards/:sid` | 删除标准 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 画面功能一览
|
||||
|
||||
| 画面 | 功能 |
|
||||
|:-----|:------|
|
||||
| 登录画面 | 密码登录 |
|
||||
| 侧边栏 | 项目列表、新建项目(含赛道选择)、退出 |
|
||||
| 项目详情 | 项目信息、重命名、删除、统计 |
|
||||
| 标准标签 | 标准列表、展开维度详情、上传/删除标准 |
|
||||
| 条目标签 | 条目表格、筛选/搜索/分页、添加/编辑/删除、批量导入、启动评审 |
|
||||
| 成果物标签 | 成果物一览表格、初始化、勾选确认、统计、CSV下载 |
|
||||
| 汇总标签 | 赛道分组排名、L2合格判定 |
|
||||
| 条目详情(弹出面板) | 成果物确认、雷达图、L2/L3维度评分、评语编辑、修正保存 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 数据模型
|
||||
|
||||
### 10.1 projects 表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:-----|:-----|:-----|
|
||||
| id | TEXT | 主键 |
|
||||
| name | TEXT | 项目名称 |
|
||||
| track | TEXT | 赛道(赛道一/赛道二/人才测评) |
|
||||
| description | TEXT | 说明 |
|
||||
| deadline | TEXT | 截止日期 |
|
||||
| late_penalty | INTEGER | 迟交扣分(默认5) |
|
||||
|
||||
### 10.2 standards 表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:-----|:-----|:-----|
|
||||
| id | TEXT | 主键 |
|
||||
| project_id | TEXT | 关联项目 |
|
||||
| name | TEXT | 标准名称 |
|
||||
| category_tag | TEXT | 分类标签 |
|
||||
| content | TEXT | Markdown标准内容 |
|
||||
| max_score | INTEGER | 总分上限 |
|
||||
|
||||
### 10.3 entries 表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|:-----|:-----|:-----|
|
||||
| id | TEXT | 主键 |
|
||||
| project_id | TEXT | 关联项目 |
|
||||
| standard_id | TEXT | 关联标准(评审依据) |
|
||||
| title | TEXT | 条目标题 |
|
||||
| repo_url | TEXT | Git仓库URL |
|
||||
| category_tag | TEXT | 继承自项目的track |
|
||||
| participant | TEXT | 参赛者 |
|
||||
| sub_type | TEXT | 赛道一专用:新規/修正 |
|
||||
| question_id | TEXT | 人才测评专用:Q1~Q6 |
|
||||
| pass_line | INTEGER | 及格线(默认60) |
|
||||
| attempt | INTEGER | 提交次数(默认1) |
|
||||
| max_score_cap | INTEGER | 多次提交分数上限(默认100) |
|
||||
| status | TEXT | 状态(pending/queued/.../review_done) |
|
||||
| progress_log | TEXT | 进度日志JSON数组 |
|
||||
| ai_report | TEXT | AI评审报告JSON |
|
||||
| standard_snapshot | TEXT | 评审时标准快照 |
|
||||
| branch / base_branch | TEXT | 克隆分支 / 对比分支 |
|
||||
| service_url | TEXT | 参赛者服务地址(tryBrowse用) |
|
||||
| late_days | INTEGER | 迟交天数 |
|
||||
| deliverables | TEXT | JSON成果物数组 |
|
||||
| final_level | TEXT | 最终认定(L2/L3/不合格) |
|
||||
| raw_score | REAL | 原始分 |
|
||||
| final_score | REAL | 最终分(含扣分) |
|
||||
|
||||
---
|
||||
|
||||
## 11. 配置参数
|
||||
|
||||
```env
|
||||
# 服务端口
|
||||
PORT=3002
|
||||
|
||||
# 认证密码(自动生成)
|
||||
AUTH_PASSWORD=620f4c96
|
||||
|
||||
# 认证密钥(自动生成)
|
||||
AUTH_SECRET=...
|
||||
|
||||
# DeepSeek API
|
||||
DEEPSEEK_API_KEY=sk-...
|
||||
DEEPSEEK_TIMEOUT=120000
|
||||
|
||||
# 标准总分上限
|
||||
STANDARD_MAX_SCORE=150
|
||||
```
|
||||
@@ -0,0 +1,674 @@
|
||||
# AI 人才育成评审系统 · API 设计书
|
||||
|
||||
版本:v1.0(2026-08-04)
|
||||
依据源码:`server/src/`(Express 5 + TypeScript + better-sqlite3)
|
||||
|
||||
---
|
||||
|
||||
## 1. 总览
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| Base URL | `http://localhost:3002` |
|
||||
| 端口配置 | `server/.env` 中 `PORT`(默认 3002) |
|
||||
| 数据格式 | 请求/响应均为 JSON(`Content-Type: application/json`),异常分支除外 |
|
||||
| 认证方式 | `Authorization: Bearer <JWT>`(登录后 24h 有效) |
|
||||
| 编码 | 统一 UTF-8 |
|
||||
|
||||
### 1.1 鉴权规则
|
||||
|
||||
- 除 `POST /api/auth/login`、`GET /api/health` 外,所有 `/api/*` 路由(**含 `GET /api/backup`**)都需携带 Bearer Token。原因:`authMiddleware` 挂载在 `/api` 前缀且先于 `/api/backup` 路由注册(`index.ts` 中第 29 行先于第 59 行),backup 请求会经过该中间件。
|
||||
- Token 由 `POST /api/auth/login` 签发,JWT payload 为 `{ role: 'admin' }`,`expiresIn: '24h'`。
|
||||
- 未携带 → `401 {"error":"未登录"}`;Token 过期/伪造 → `401 {"error":"登录已过期"}`。
|
||||
- 登录失败按 **IP** 计数,连续 5 次失败后该 IP 锁定 60 秒 → `429 {"error":"登录尝试过多,请60秒后重试"}`。
|
||||
|
||||
### 1.2 通用错误结构
|
||||
|
||||
```json
|
||||
{ "error": "错误描述" }
|
||||
```
|
||||
|
||||
| HTTP 状态码 | 含义 | 常见场景 |
|
||||
|---|---|---|
|
||||
| 200 | 成功 | 常规返回 |
|
||||
| 400 | 参数/格式错误 | 必填项缺失、URL 非法、标准格式异常、维度总分超上限 |
|
||||
| 401 | 未登录 / 密码错误 / Token 过期 | 见 1.1 |
|
||||
| 404 | 资源不存在 | 项目/标准/条目不存在 |
|
||||
| 409 | 状态冲突 | 仅允许编辑 pending、启动非 pending 条目等 |
|
||||
| 429 | 登录限流 | 见 1.1 |
|
||||
| 500 | 服务器内部错误 | 未捕获异常、PDF 生成失败 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 路由清单
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| POST | `/api/auth/login` | 登录,签发 Token |
|
||||
| GET | `/api/health` | 健康检查(免鉴权) |
|
||||
| GET | `/api/backup` | 立即触发一次 DB 备份(需鉴权) |
|
||||
| GET | `/api/projects` | 项目列表(含统计) |
|
||||
| POST | `/api/projects` | 创建项目(赛道自动建默认标准) |
|
||||
| GET | `/api/projects/:id` | 项目详情(含统计+标准列表) |
|
||||
| PUT | `/api/projects/:id` | 修改项目 |
|
||||
| DELETE | `/api/projects/:id` | 删除项目(默认需全部条目完成) |
|
||||
| GET | `/api/projects/:id/summary` | 汇总排名数据 |
|
||||
| GET | `/api/projects/:id/summary/export` | 导出汇总排名 PDF |
|
||||
| GET | `/api/projects/:projectId/standards` | 标准列表(含解析后的维度) |
|
||||
| POST | `/api/projects/:projectId/standards` | 上传标准 |
|
||||
| GET | `/api/projects/:projectId/standards/:standardId` | 标准详情 |
|
||||
| PUT | `/api/projects/:projectId/standards/:standardId` | 更新标准 |
|
||||
| DELETE | `/api/projects/:projectId/standards/:standardId` | 删除标准(被引用时 409) |
|
||||
| GET | `/api/projects/:projectId/entries` | 条目列表(分页/筛选) |
|
||||
| POST | `/api/projects/:projectId/entries` | 创建条目 |
|
||||
| POST | `/api/projects/:projectId/entries/batch` | 批量导入条目 |
|
||||
| GET | `/api/projects/:projectId/entries/:entryId` | 条目详情(含维度+修订历史+快照) |
|
||||
| PUT | `/api/projects/:projectId/entries/:entryId` | 编辑条目(仅 pending) |
|
||||
| DELETE | `/api/projects/:projectId/entries/:entryId` | 删除条目(仅 pending) |
|
||||
| POST | `/api/projects/:projectId/entries/:entryId/start` | 启动评审 |
|
||||
| POST | `/api/projects/:projectId/entries/:entryId/cancel` | 取消评审 |
|
||||
| POST | `/api/projects/:projectId/entries/:entryId/retry` | 失败重试 |
|
||||
| POST | `/api/projects/:projectId/entries/batch-start` | 批量启动评审 |
|
||||
| PUT | `/api/projects/:projectId/entries/:entryId/deliverables` | 提交交付物清单 |
|
||||
| PUT | `/api/projects/:projectId/entries/deliverables/init` | 初始化默认交付物 |
|
||||
| GET | `/api/projects/:projectId/entries/deliverables/summary` | 交付物汇总 |
|
||||
| GET | `/api/projects/:projectId/entries/deliverables/export` | 导出交付物 CSV |
|
||||
| PUT | `/api/projects/:projectId/entries/:entryId/report` | 人工修正评审报告 |
|
||||
| GET | `/api/projects/:projectId/entries/:entryId/report/export` | 导出单条目 PDF 报告 |
|
||||
| PUT | `/api/projects/:projectId/entries/:entryId/force-review` | 【测试专用】强制置为已评审 |
|
||||
| GET | `/api/config/gitea-token/status` | 查询 Gitea Token 是否已配置 |
|
||||
| PUT | `/api/config/gitea-token` | 更新 Gitea Token |
|
||||
|
||||
> 路由顺序说明:`deliverables/init`、`deliverables/summary`、`deliverables/export`、`batch-start` 等静态路径与 `/:entryId` 的注册顺序**不冲突**——它们是多段路径(如 `deliverables/summary`),而 `/:entryId` 只匹配单段,Express 不会把多段静态路径当作 entryId。若未来新增**单段**静态路径(如 `/count`),必须注册在 `/:entryId` 之前。
|
||||
|
||||
---
|
||||
|
||||
## 3. 认证与系统接口
|
||||
|
||||
### 3.1 POST `/api/auth/login`
|
||||
|
||||
登录,返回 JWT。密码比对 `config.authPassword`(来自 `AUTH_PASSWORD`,未设置时自动生成并写入 `.env`)。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{ "password": "620f4c96" }
|
||||
```
|
||||
|
||||
响应 200:
|
||||
```json
|
||||
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
|
||||
```
|
||||
|
||||
错误:
|
||||
- 401 `{"error":"密码错误"}`
|
||||
- 429 `{"error":"登录尝试过多,请60秒后重试"}`(同 IP 连续 5 次失败)
|
||||
|
||||
### 3.2 GET `/api/health`
|
||||
|
||||
健康检查(免鉴权)。
|
||||
```json
|
||||
{ "status": "ok" }
|
||||
```
|
||||
|
||||
### 3.3 GET `/api/backup`
|
||||
|
||||
立即触发 SQLite 在线备份,写入 `server/data/backups/ai-review-<时间戳>.db`。**需要鉴权**(`authMiddleware` 先于该路由注册)。
|
||||
```json
|
||||
{ "success": true }
|
||||
```
|
||||
|
||||
### 3.4 配置接口(`/api/config`)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/api/config/gitea-token/status` | `{"configured": true/false}`,判断 `GITEA_TOKEN` 是否已配置 |
|
||||
| PUT | `/api/config/gitea-token` | 请求体 `{"token":"xxx"}`,写入 `.env` 的 `GITEA_TOKEN`,成功 `{"success":true}`;缺参 → 400 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 项目接口(`/api/projects`)
|
||||
|
||||
### 4.1 GET `/api/projects`
|
||||
|
||||
项目列表(按创建时间倒序),每项附带条目统计。
|
||||
|
||||
响应 200:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "2026技术大赛",
|
||||
"description": "",
|
||||
"deadline": "2026-12-31",
|
||||
"late_penalty": 5,
|
||||
"created_at": "2026-08-01 10:00:00",
|
||||
"track": "赛道一",
|
||||
"total": 12,
|
||||
"reviewed": 8,
|
||||
"active": 2
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
字段说明:
|
||||
- `total`:条目总数
|
||||
- `reviewed`:`review_done` 或 `admin_reviewed` 数量
|
||||
- `active`:`queued/cloning/analyzing` 数量
|
||||
|
||||
### 4.2 POST `/api/projects`
|
||||
|
||||
创建项目。合法赛道为 `赛道一` / `赛道二` / `人才测评`,传非法或空值时 `track` 存空串。赛道存在时自动从 `server/config/standards/` 载入对应模板标准(模板总分 ≤ `STANDARD_MAX_SCORE` 才写入)。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"name": "2026技术大赛",
|
||||
"description": "首届AI应用开发大赛",
|
||||
"deadline": "2026-12-31",
|
||||
"track": "赛道一"
|
||||
}
|
||||
```
|
||||
|
||||
响应 200:项目对象(同 GET 单条,无 stats/standards 字段)。
|
||||
|
||||
错误:
|
||||
- 400 `{"error":"项目名称为必填项"}`
|
||||
|
||||
> 模板映射:`赛道一 → config/standards/技术大赛-赛道一.md`、`赛道二 → 技术大赛-赛道二.md`、`人才测评 → AI人才育成L2.md`。
|
||||
|
||||
### 4.3 GET `/api/projects/:id`
|
||||
|
||||
项目详情,含条目统计与标准摘要。
|
||||
|
||||
响应 200:
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "2026技术大赛",
|
||||
"track": "赛道一",
|
||||
"total": 12,
|
||||
"reviewed": 8,
|
||||
"pending": 2,
|
||||
"active": 2,
|
||||
"failed": 0,
|
||||
"standards": [
|
||||
{ "id": "uuid", "name": "技术大赛·赛道一标准", "category_tag": "赛道一" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
- `pending`:状态为 `pending` 的数量
|
||||
- `failed`:状态以 `_fail` 结尾或等于 `failed` 的数量
|
||||
|
||||
错误:404 `{"error":"项目不存在"}`
|
||||
|
||||
### 4.4 PUT `/api/projects/:id`
|
||||
|
||||
修改项目。缺省字段保持原值。`track` 缺省保持原值;显式传非法值会存空串。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"name": "2026技术大赛(更新)",
|
||||
"description": "更新描述",
|
||||
"deadline": "2027-01-01",
|
||||
"track": "赛道一"
|
||||
}
|
||||
```
|
||||
|
||||
响应 200:更新后的项目对象。
|
||||
|
||||
### 4.5 DELETE `/api/projects/:id`
|
||||
|
||||
删除项目。默认仅当项目内所有条目处于 `review_done/admin_reviewed/failed` 时允许;否则 409。传 `?force=true` 可强制删除。删除前清理所有条目克隆目录,DB 由外键 `ON DELETE CASCADE` 级联删除标准、条目及历史。
|
||||
|
||||
请求:`DELETE /api/projects/{id}?force=true`
|
||||
|
||||
响应 200:`{"success":true}`
|
||||
|
||||
错误:
|
||||
- 409 `{"error":"项目中有 N 个条目未完成"}`
|
||||
- 404 `{"error":"项目不存在"}`
|
||||
|
||||
### 4.6 GET `/api/projects/:id/summary`
|
||||
|
||||
汇总排名数据(供前端排名表 + PDF 使用)。
|
||||
|
||||
响应 200:
|
||||
```json
|
||||
{
|
||||
"project": { "id": "uuid", "name": "2026技术大赛", "track": "赛道一" },
|
||||
"totalEntries": 12,
|
||||
"categories": [
|
||||
{
|
||||
"category": "题目 Q1",
|
||||
"entries": [
|
||||
{
|
||||
"title": "智能客服",
|
||||
"score": 88,
|
||||
"pass_line": 60,
|
||||
"passed": true,
|
||||
"final_level": "L2",
|
||||
"participant": "张三",
|
||||
"rank": 1
|
||||
}
|
||||
],
|
||||
"passed": true
|
||||
}
|
||||
],
|
||||
"participants": [
|
||||
{ "participant": "张三", "entries": [ { "title": "智能客服", "score": 88 } ], "passed": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
分组规则:
|
||||
- `categories` 按分组键:人才测评用 `question_id`(如 `"Q1"`,PDF/前端展示时加「题目」前缀),其余用 `category_tag`(空则 `未分类`)。
|
||||
- `participants` 按参赛者聚合,`passed` = 其所有条目得分均 ≥ 及格线。
|
||||
- 仅统计 `review_done`/`admin_reviewed` 条目,按 `final_score DESC` 排序;`rank` 为组内序号(从 1 起)。
|
||||
|
||||
### 4.7 GET `/api/projects/:id/summary/export`
|
||||
|
||||
导出汇总排名 PDF(puppeteer + 本机 Edge/Chrome 渲染 A4)。
|
||||
|
||||
响应:`Content-Disposition: attachment`,文件名为 `<项目名>_汇总排名.pdf`(非法文件名字符替换为 `_`)。
|
||||
|
||||
错误:
|
||||
- 404 `{"error":"项目不存在"}`
|
||||
- 500 `{"error":"PDF生成失败: ..."}`(如未找到 Chrome/Edge)
|
||||
|
||||
---
|
||||
|
||||
## 5. 标准接口(`/api/projects/:projectId/standards`)
|
||||
|
||||
### 5.1 标准格式约定
|
||||
|
||||
标准内容为 Markdown,维度标题固定格式 `## 维度名(XX分)`(支持全角/半角括号、`分`或`%`):
|
||||
|
||||
```markdown
|
||||
## 场景价值与合理性(15分)
|
||||
场景是否真实、有业务价值...
|
||||
## 开发范式与架构设计(25分)
|
||||
...
|
||||
```
|
||||
|
||||
支持可选增强语法:
|
||||
- 组标记:`## [Q1] 题目一(30分)` → 解析为 `group: "Q1"`,用于人才测评多题共用一份标准。
|
||||
- 文件关键词:维度正文首行 `文件关键词:xxx` → 解析为 `fileKeywords`,正文中该行会被剔除。
|
||||
|
||||
### 5.2 标准维度对象(Dimension)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "场景价值与合理性",
|
||||
"maxScore": 15,
|
||||
"content": "场景是否真实...",
|
||||
"fileKeywords": "设计文档",
|
||||
"group": "common",
|
||||
"order": 1
|
||||
}
|
||||
```
|
||||
|
||||
- `group`:默认 `common`;带 `[Qx]` 前缀时为对应组名。
|
||||
- `order`:按解析顺序从 1 递增。
|
||||
|
||||
### 5.3 GET `/api/projects/:projectId/standards`
|
||||
|
||||
标准列表(`created_at DESC`),每项含解析后的 `dimensions` 数组。
|
||||
|
||||
### 5.4 POST `/api/projects/:projectId/standards`
|
||||
|
||||
上传标准。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"name": "技术大赛·赛道一标准",
|
||||
"content": "## 场景价值与合理性(15分)\n...",
|
||||
"category_tag": "赛道一",
|
||||
"max_score": 150
|
||||
}
|
||||
```
|
||||
|
||||
校验规则(依序):
|
||||
1. `name` 非空 → 400 `标准名称为必填项`
|
||||
2. `content` 非空 → 400 `标准内容为必填项`
|
||||
3. 内容含 `## 维度名(XX分)` 标题 → 否则 400 `标准格式异常:缺少 "## 维度名(XX分)" 格式`
|
||||
4. 解析出至少 1 个维度 → 否则 400 `未能解析出任何评审维度`
|
||||
5. 维度总分 ≤ 上限(显式 `max_score` 或 `STANDARD_MAX_SCORE` 默认 150)→ 否则 400 `各维度总分超过上限(150分),当前合计 X分`
|
||||
|
||||
> 总分算法见《03-后台设计书》§3.3 `computeEffectiveTotal`:共通维度 + 单题组最大附加分。
|
||||
|
||||
响应 200:标准对象 + `dimensions`。
|
||||
|
||||
### 5.5 GET `/api/projects/:projectId/standards/:standardId`
|
||||
|
||||
标准详情(含 `dimensions`)。404 `评审标准不存在`。
|
||||
|
||||
### 5.6 PUT `/api/projects/:projectId/standards/:standardId`
|
||||
|
||||
更新标准。缺省字段保持原值。仅当 `content` 传入时重新校验格式与总分上限;`max_score` 传入且 >0 时使用,否则沿用原值。
|
||||
|
||||
响应 200:更新后标准对象 + `dimensions`。
|
||||
|
||||
错误:
|
||||
- 404 `{"error":"评审标准不存在"}`
|
||||
- 400 `{"error":"标准格式异常"}` / `{"error":"各维度总分超过上限..."}`
|
||||
|
||||
### 5.7 DELETE `/api/projects/:projectId/standards/:standardId`
|
||||
|
||||
删除标准。若已被任何条目引用(`entries.standard_id` 匹配),返回 409。
|
||||
|
||||
响应 200:`{"success":true}`
|
||||
|
||||
错误:
|
||||
- 409 `{"error":"该标准已被 N 个条目引用,无法删除"}`
|
||||
- 404 `{"error":"评审标准不存在"}`
|
||||
|
||||
---
|
||||
|
||||
## 6. 条目接口(`/api/projects/:projectId/entries`)
|
||||
|
||||
### 6.1 条目字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | string | UUID |
|
||||
| project_id / standard_id | string | 外键 |
|
||||
| title / repo_url | string | 必填,标题 / Git 仓库地址 |
|
||||
| category_tag | string | 继承项目 track |
|
||||
| participant | string | 参赛者 |
|
||||
| sub_type | string | 赛道一的子类型(新规/修正),仅赛道一生效 |
|
||||
| question_id | string | 人才测评题目 ID,仅人才测评生效且必填 |
|
||||
| pass_line | int | 及格线(由标准解析计算,见《03-后台设计书》§7.2) |
|
||||
| attempt | int | 提交次数 |
|
||||
| max_score_cap | int | 多次提交分数上限(默认 100) |
|
||||
| status | string | 状态机,见 §6.9 |
|
||||
| progress_log | text(JSON) | 进度日志数组 |
|
||||
| ai_report | text(JSON) | AI 评审报告 |
|
||||
| standard_snapshot | text | 评审时标准快照(JSON 维度 或 原始 MD) |
|
||||
| branch / base_branch | string | 克隆分支 / 对比分支 |
|
||||
| service_url | string | 参赛者服务地址(tryBrowse 用) |
|
||||
| late_days | int | 迟交天数 |
|
||||
| raw_score / final_score | REAL | 原始分 / 最终分(扣迟交) |
|
||||
| deliverables | text(JSON) | 交付物清单 |
|
||||
| final_level | string | 人才测评认定等级(L2/L3) |
|
||||
| created_at / updated_at | string | 时间戳 |
|
||||
|
||||
### 6.2 GET `/api/projects/:projectId/entries`
|
||||
|
||||
分页 + 筛选的条目列表。
|
||||
|
||||
Query 参数:
|
||||
- `offset`(默认 `0`)、`limit`(默认 `50`):分页
|
||||
- `status`:按状态精确过滤
|
||||
- `tag`:按 `category_tag` 过滤
|
||||
- `search`:标题模糊匹配(`LIKE '%xx%'`)
|
||||
- `question_id`:按题目过滤
|
||||
|
||||
响应 200:
|
||||
```json
|
||||
{
|
||||
"items": [ { "id": "uuid", "title": "智能客服", "status": "review_done", ... } ],
|
||||
"total": 12,
|
||||
"offset": 0,
|
||||
"limit": 50
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 POST `/api/projects/:projectId/entries`
|
||||
|
||||
创建条目(不自动启动评审)。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"title": "智能客服",
|
||||
"repo_url": "https://gitea.example.com/team1/chatbot.git",
|
||||
"participant": "张三",
|
||||
"branch": "main",
|
||||
"service_url": "https://demo.example.com",
|
||||
"base_branch": "main",
|
||||
"sub_type": "新规",
|
||||
"question_id": "Q1"
|
||||
}
|
||||
```
|
||||
|
||||
后端处理逻辑:
|
||||
1. `title` / `repo_url` 非空校验 → 400。
|
||||
2. `service_url` 若提供,走 SSRF 校验(见 §6.10)→ 400。
|
||||
3. `track` 继承项目;`sub_type` 仅赛道一生效;`category_tag = track`。
|
||||
4. 人才测评必须带 `question_id` → 否则 400 `人才测评条目必须选择题目`。
|
||||
5. `resolveStandard` 按 子类型(赛道一) → track → 空 tag 兜底 匹配标准;无标准 → 400 `未找到匹配的评审标准,请先上传标准`。
|
||||
6. 解析标准维度、计算及格线,`standard_snapshot` 存入维度 JSON。
|
||||
7. 插入。`repo_url` 与项目组合唯一,重复 → 500(SQLite UNIQUE 约束)。
|
||||
|
||||
响应 200:条目对象(不含 dimensions/revisions/snapshots)。
|
||||
|
||||
### 6.4 POST `/api/projects/:projectId/entries/batch`
|
||||
|
||||
批量导入条目。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"entries": [
|
||||
{ "title": "智能客服", "repo_url": "...", "participant": "张三" },
|
||||
{ "title": "推荐系统", "repo_url": "...", "participant": "李四" }
|
||||
]
|
||||
}
|
||||
```
|
||||
(也接受直接传数组 `[ ... ]`)
|
||||
|
||||
响应 200:
|
||||
```json
|
||||
{
|
||||
"imported": 2,
|
||||
"errors": [ { "row": 1, "reason": "仓库 xxx 已存在" } ],
|
||||
"items": [ { "id": "uuid", "title": "智能客服" } ]
|
||||
}
|
||||
```
|
||||
|
||||
单行失败不中断整体:`row` 为数组下标(从 0),常见原因:标题为空、仓库地址为空、服务地址无效、人才测评缺题目、未找到标准、`UNIQUE constraint` 仓库重复。
|
||||
|
||||
### 6.5 GET `/api/projects/:projectId/entries/:entryId`
|
||||
|
||||
条目详情,附带 `dimensions`(解析 `standard_snapshot`)、`revisions`(`revision_history` 按时间倒序)、`snapshots`(`review_snapshots` 按 attempt 升序)。
|
||||
|
||||
404 `{"error":"条目不存在"}`。
|
||||
|
||||
### 6.6 PUT `/api/projects/:projectId/entries/:entryId`
|
||||
|
||||
编辑条目。**仅 `status = pending` 可编辑**,否则 409。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{
|
||||
"title": "智能客服v2",
|
||||
"repo_url": "https://gitea.example.com/team1/chatbot.git",
|
||||
"participant": "张三",
|
||||
"branch": "dev",
|
||||
"service_url": "https://demo.example.com",
|
||||
"base_branch": "main",
|
||||
"sub_type": "新规",
|
||||
"question_id": "Q1"
|
||||
}
|
||||
```
|
||||
|
||||
逻辑:
|
||||
- `service_url` 非空时校验(§6.10)。
|
||||
- `sub_type` 仅赛道一生效,其余保留原值;`question_id` 仅人才测评生效。
|
||||
- 其余字段缺省保持原值。
|
||||
- 不更新 `pass_line` / `standard_snapshot` / `standard_id`(改标准需删建)。
|
||||
|
||||
错误:404 条目不存在;409 `{"error":"只能编辑待评审的条目"}`;400 服务地址无效。
|
||||
|
||||
### 6.7 DELETE `/api/projects/:projectId/entries/:entryId`
|
||||
|
||||
删除条目。**仅 `pending` 可删除**。删除前清理该条目的克隆目录。
|
||||
|
||||
错误:404;409 `{"error":"只能删除待评审的条目"}`。
|
||||
|
||||
### 6.8 POST `/api/projects/:projectId/entries/:entryId/start`
|
||||
|
||||
启动评审。仅 `pending` 可启动。调用 `startReview(entryId)`,按并发数决定立即执行或排队(见《03-后台设计书》§4.1)。
|
||||
|
||||
响应 200:`{"success":true}`
|
||||
|
||||
错误:
|
||||
- 404 条目不存在
|
||||
- 409 `{"error":"当前状态(pending之外的)不允许启动"}`
|
||||
|
||||
### 6.9 条目状态机
|
||||
|
||||
| 状态 | 含义 | 可转移动作 |
|
||||
|---|---|---|
|
||||
| `pending` | 待评审(可编辑/删除) | start → queued |
|
||||
| `queued` | 排队中 | cancel → cancelled |
|
||||
| `cloning` | 克隆中 | cancel → cancelled;失败 → clone_fail |
|
||||
| `analyzing` | AI 评审中 | cancel → cancelled;评审异常 → failed |
|
||||
| `review_done` | 评审完成(待人工复核) | report 修正 → admin_reviewed |
|
||||
| `admin_reviewed` | 人工复核完成(最终) | report 修正(force 也可) |
|
||||
| `clone_fail` / `analysis_fail` / `failed` | 失败 | retry → pending |
|
||||
| `cancelled` | 已取消(终态) | — |
|
||||
|
||||
补充说明:
|
||||
- 取消仅允许 `queued/cloning/analyzing`,成功后状态为 `cancelled`(终态)。
|
||||
- 重试仅允许 `clone_fail/analysis_fail/failed`,重置为 `pending` 并清空 `ai_report` 后重新排队。
|
||||
- 克隆失败 → `clone_fail`(cloneRepo 内写入);评审流程中任何未捕获异常 → `failed`(review.service 的 runReview catch 兜底)。**`analysis_fail` 目前没有任何代码会写入**,仅被 retry 接口兼容性保留。
|
||||
- 服务器启动时(`index.ts`)自动把 `queued/cloning/analyzing` 重置为 `pending`(崩溃恢复)。
|
||||
|
||||
### 6.10 服务地址 SSRF 校验规则(`validateServiceUrl`)
|
||||
|
||||
- 空串合法(可不填)。
|
||||
- 协议仅允许 `http:/https:`。
|
||||
- 禁止主机:`localhost`、`127.0.0.1`、`0.0.0.0`、`::1`。
|
||||
- IPv4 私网/保留段禁止:`10.x`、`172.16-31.x`、`192.168.x`、`169.254.x`、`0.x`、`100.64-127.x`。
|
||||
- 其余(公网域名/公网 IP)合法。
|
||||
- 校验失败 → 400 `{"error":"服务地址无效: <原因>"}`。
|
||||
|
||||
> 注意:域名形式的私网地址(如 `http://my-nas.local`、`http://router.internal`)不受拦截,仅拦截字面 IPv4。SSRF 校验只在本项目条目的创建/编辑/批量导入接口生效;评审引擎 `tryBrowse` 直接使用 `entry.service_url` 发起访问,不再重复校验。
|
||||
|
||||
### 6.11 POST `/api/projects/:projectId/entries/batch-start`
|
||||
|
||||
批量启动评审。请求体 `{"entryIds": ["id1","id2"]}`;非数组 → 400。逐条校验存在性与 `pending` 状态,失败的记录到 `errors`,成功的加入 `started`。
|
||||
|
||||
响应 200:
|
||||
```json
|
||||
{ "started": 2, "errors": [ { "id": "id3", "reason": "状态(review_done)不允许启动" } ] }
|
||||
```
|
||||
|
||||
### 6.12 交付物接口
|
||||
|
||||
交付物为条目上的一组清单项,每项 `{ name, required, submitted }`。
|
||||
|
||||
**PUT `/api/projects/:projectId/entries/:entryId/deliverables`**
|
||||
请求:`{ "deliverables": [ { "name": "源代码", "required": true, "submitted": true }, ... ] }`
|
||||
非数组 → 400。成功 `{"success":true}`。
|
||||
|
||||
**PUT `/api/projects/:projectId/entries/deliverables/init`**
|
||||
为项目内所有未初始化(`deliverables` 为空)的条目写入默认清单:
|
||||
源代码/README/设计文档/测试用例与测试结果/AGENTS.md/样本数据(必交)+ 演示录屏(选交)。
|
||||
响应 `{"initialized": N}`。
|
||||
|
||||
**GET `/api/projects/:projectId/entries/deliverables/summary`**
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"rows": [ { "title": "智能客服", "participant": "张三", "status": "review_done", "源代码": "✓", "README": "×", ... } ],
|
||||
"summary": [ { "name": "源代码", "required": true, "submitted": 8, "total": 12 } ],
|
||||
"totalRequired": 72,
|
||||
"totalSubmitted": 60,
|
||||
"rate": 83
|
||||
}
|
||||
```
|
||||
`rate` = 必交项提交率四舍五入百分比;`summary` 按必交优先排序。
|
||||
|
||||
**GET `/api/projects/:projectId/entries/deliverables/export`**
|
||||
导出 CSV(UTF-8 BOM)。列动态按全部条目出现的交付物名合并,内容 `✓`/`×`。
|
||||
```
|
||||
Content-Type: text/csv; charset=utf-8
|
||||
Content-Disposition: attachment; filename="deliverables.csv"
|
||||
```
|
||||
|
||||
### 6.13 PUT `/api/projects/:projectId/entries/:entryId/report`
|
||||
|
||||
人工修正评审报告。**仅 `review_done` / `admin_reviewed` 可修正**;传 `?force=true` 可跳过状态检查(用于测试)。
|
||||
|
||||
请求:
|
||||
```json
|
||||
{ "dimensions": [ { "name": "场景价值与合理性", "score": 14, "comment": "场景真实", "suggestion": "..." } ] }
|
||||
```
|
||||
|
||||
逻辑:
|
||||
1. 以现有 `ai_report.dimensions` 为基准,按 `name` 匹配传入维度,未匹配的保持原分。
|
||||
2. 修正分 clamp 到 `[0, maxScore]` 且 `Math.round`。
|
||||
3. 记录 `revision_history`(`scores` = 新维度,`comments` = 旧维度 JSON 字符串)。
|
||||
4. 重算 `totalScore / maxTotal / pct`,并**重新计算迟交扣分**:
|
||||
- `late_days > 0` 时:`penalty = late_days > 7 ? totalScore : min(totalScore, late_days × project.late_penalty)`(`late_penalty` 默认 5)。
|
||||
- `finalScore = max(0, totalScore - penalty)`。
|
||||
5. 状态置为 `admin_reviewed`。
|
||||
|
||||
响应 200:更新后的条目对象。
|
||||
|
||||
错误:
|
||||
- 404 条目不存在
|
||||
- 409 `{"error":"当前状态不允许修正"}`(未传 force 且状态不对)
|
||||
- 400 `{"error":"请提供修正后的维度数组"}`
|
||||
|
||||
### 6.14 GET `/api/projects/:projectId/entries/:entryId/report/export`
|
||||
|
||||
导出单条目 PDF 评审报告(含雷达图 + 维度表)。无 `ai_report` → 409 `{"error":"条目尚未完成评审"}`。
|
||||
|
||||
响应:`Content-Disposition: attachment`,文件名 `<项目名>_<条目名>_评审报告.pdf`。
|
||||
|
||||
### 6.15 PUT `/api/projects/:projectId/entries/:entryId/force-review`(测试专用)
|
||||
|
||||
仅当环境变量 `ADMIN_TEST_TOKEN=true` 时启用;未启用时返回 404。请求 `{ "dimensions": [...] }`,按维度求和直接置为 `admin_reviewed`(`raw_score = final_score = totalScore`)。用于 E2E 测试,**生产环境不应开启**。
|
||||
|
||||
---
|
||||
|
||||
## 7. 请求/响应示例汇总(curl)
|
||||
|
||||
```bash
|
||||
# 登录
|
||||
curl -X POST http://localhost:3002/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"password":"620f4c96"}'
|
||||
|
||||
# 创建项目(需带 token)
|
||||
curl -X POST http://localhost:3002/api/projects \
|
||||
-H "Authorization: Bearer <TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"测试大赛","track":"赛道一"}'
|
||||
|
||||
# 上传标准
|
||||
curl -X POST http://localhost:3002/api/projects/<PID>/standards \
|
||||
-H "Authorization: Bearer <TOKEN>" -H "Content-Type: application/json" \
|
||||
-d '{"name":"标准A","content":"## 场景价值(15分)\n...","max_score":150}'
|
||||
|
||||
# 创建条目
|
||||
curl -X POST http://localhost:3002/api/projects/<PID>/entries \
|
||||
-H "Authorization: Bearer <TOKEN>" -H "Content-Type: application/json" \
|
||||
-d '{"title":"智能客服","repo_url":"https://github.com/x/y.git","participant":"张三"}'
|
||||
|
||||
# 启动评审
|
||||
curl -X POST http://localhost:3002/api/projects/<PID>/entries/<EID>/start \
|
||||
-H "Authorization: Bearer <TOKEN>"
|
||||
|
||||
# 查询条目
|
||||
curl "http://localhost:3002/api/projects/<PID>/entries/<EID>" \
|
||||
-H "Authorization: Bearer <TOKEN>"
|
||||
|
||||
# 汇总
|
||||
curl http://localhost:3002/api/projects/<PID>/summary -H "Authorization: Bearer <TOKEN>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 变更记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|---|---|---|
|
||||
| v1.0 | 2026-08-04 | 依据 `server/src` 全量源码整理,覆盖鉴权/项目/标准/条目/交付物/配置接口 |
|
||||
@@ -0,0 +1,399 @@
|
||||
# AI 人才育成评审系统 · 后台功能设计书
|
||||
|
||||
版本:v1.0(2026-08-04)
|
||||
依据源码:`server/src/`(Express 5 + TypeScript + better-sqlite3 + puppeteer-core)
|
||||
|
||||
---
|
||||
|
||||
## 1. 系统定位
|
||||
|
||||
本后台是评审系统的**编排与计算内核**:负责项目/标准/条目/交付物的持久化,克隆参赛者仓库,执行构建/启动/浏览器三重动态验证,调用 DeepSeek 分维度 AI 评审,再经校准与硬规则引擎产出最终评分,并生成条目与汇总 PDF 报告。
|
||||
|
||||
与《01-系统设计书.md》的关系:本文件聚焦**后台服务端实现设计**,前端交互见《04-前端设计书.md》,HTTP 契约见《02-API设计书.md》。
|
||||
|
||||
### 1.1 技术栈
|
||||
|
||||
| 层 | 技术 | 说明 |
|
||||
|---|---|---|
|
||||
| 运行时 | Node.js + Express 5 | REST API 服务 |
|
||||
| 语言 | TypeScript 5 | 编译到 `server/dist/`,`node dist/index.js` 启动 |
|
||||
| 存储 | better-sqlite3 | 同步 SQLite,WAL 模式,外键 ON |
|
||||
| 鉴权 | jsonwebtoken | JWT,24h 有效期 |
|
||||
| Git | simple-git | 克隆仓库、base_branch diff、commit 时间 |
|
||||
| 浏览器 | puppeteer-core | 复用本机 Chrome/Edge(不自带 Chromium) |
|
||||
| AI | DeepSeek API(`deepseek-v4-flash`) | 概览/子维度/校准三类调用 |
|
||||
| 安全 | helmet + cors + express.json(10mb) | 基础头、跨域白名单、请求体限制 |
|
||||
|
||||
### 1.2 进程与启动
|
||||
|
||||
- 启动入口 `server/src/index.ts`,端口来自 `config.port`(默认 3002)。
|
||||
- 启动时自动执行两个初始化动作:
|
||||
1. **卡死状态恢复**:把 `queued/cloning/analyzing` 状态的条目重置为 `pending`(追加日志「服务重启,已自动重置状态」)。
|
||||
2. **每日备份**:若当日未备份则在 `data/backups/ai-review-<日期>.db` 建一份快照(`db.backup`)。
|
||||
- 顶层捕获 `uncaughtException` / `unhandledRejection` 并打日志(避免进程崩溃)。
|
||||
- 测试可用 `SKIP_LISTEN=1` 抑制监听(供 vitest/supertest 导入 app)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 配置与凭证(config.ts)
|
||||
|
||||
### 2.1 配置来源
|
||||
|
||||
`.env` 文件(`server/.env`),用 `dotenv` 加载;缺少权限时自动生成并回写。
|
||||
|
||||
| 键 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `PORT` | `3002` | HTTP 端口 |
|
||||
| `AUTH_PASSWORD` | 自动生成随机 8 位 hex | 登录密码;首次启动生成并写入 `.env` |
|
||||
| `AUTH_SECRET` | 自动生成 32 字节 hex | JWT 签名密钥;缺失时生成并写入 |
|
||||
| `DEEPSEEK_API_KEY` | 空 | DeepSeek 调用凭证;为空则 AI 评审全部返回 null |
|
||||
| `DEEPSEEK_TIMEOUT` | `120000` | 单次 AI 请求超时毫秒 |
|
||||
| `GITEA_TOKEN` / `GITEA_USERNAME` | 空 | 用于带认证克隆 https 仓库 |
|
||||
| `STANDARD_MAX_SCORE` | `150` | 标准维度总分上限(默认兜底值) |
|
||||
|
||||
### 2.2 配置写入机制(writeEnvVar / updateEnvVar)
|
||||
|
||||
- 已存在则正则替换该键,否则追加一行,并同步 `process.env`。
|
||||
- `routes/config.ts` 的 `PUT /api/config/gitea-token` 借此持久化 Gitea Token。
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据模型(db.ts)
|
||||
|
||||
SQLite 表,`foreign_keys = ON`,`journal_mode = WAL`。外键删除级联:删项目 → 标准/条目 → 修订历史/快照全部清空。
|
||||
|
||||
### 3.1 projects
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | TEXT PK | UUID |
|
||||
| name | TEXT NOT NULL | 项目名 |
|
||||
| description | TEXT | 描述 |
|
||||
| deadline | TEXT | 截止时间(迟交判定依据) |
|
||||
| late_penalty | INTEGER 默认5 | 每日迟交扣分 |
|
||||
| track | TEXT 默认'' | `赛道一`/`赛道二`/`人才测评` |
|
||||
| created_at | TEXT | datetime('now') |
|
||||
|
||||
### 3.2 standards
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | TEXT PK | UUID |
|
||||
| project_id | TEXT FK | 所属项目 |
|
||||
| name | TEXT NOT NULL | 标准名 |
|
||||
| category_tag | TEXT 默认'' | 用于匹配:赛道名 / 赛道一子类型(新规/修正)|
|
||||
| content | TEXT NOT NULL | 标准 Markdown 原文 |
|
||||
| max_score | INTEGER 默认150 | 总分上限 |
|
||||
| created_at / updated_at | TEXT | 时间 |
|
||||
|
||||
### 3.3 entries
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| id | TEXT PK | UUID |
|
||||
| project_id / standard_id | TEXT FK | 归属 |
|
||||
| title / repo_url | TEXT NOT NULL | 标题、仓库;UNIQUE(project_id, repo_url) |
|
||||
| category_tag | TEXT | 继承项目 track |
|
||||
| participant | TEXT | 参赛者 |
|
||||
| difficulty | TEXT | **历史遗留列,已弃用,勿引用** |
|
||||
| sub_type / question_id | TEXT | 赛道一子类型 / 人才测评题目 |
|
||||
| pass_line | INTEGER 默认60 | 及格线(创建时算好写入) |
|
||||
| attempt / max_score_cap | INTEGER | 提交次数 / 多次提交分数上限 |
|
||||
| status | TEXT 默认pending | 状态机(见 §6) |
|
||||
| progress_log | TEXT('[]') | 进度步骤 JSON 数组 |
|
||||
| ai_report | TEXT | 最终 AI 报告 JSON |
|
||||
| standard_snapshot | TEXT | 评审时的标准快照 |
|
||||
| branch / base_branch | TEXT | 克隆分支 / 对比分支 |
|
||||
| service_url | TEXT 默认'' | 参赛者服务地址(tryBrowse 用) |
|
||||
| late_days | INTEGER 默认0 | 迟交天数 |
|
||||
| raw_score / final_score | REAL | 原始分 / 最终分 |
|
||||
| final_level | TEXT | 人才测评认定 L2/L3/不合格 |
|
||||
| deliverables | TEXT('[]') | 交付物清单 |
|
||||
| created_at / updated_at | TEXT | 时间 |
|
||||
|
||||
索引:`project_id`、`status`、`participant`。
|
||||
|
||||
### 3.4 历史表
|
||||
|
||||
- **revision_history**:条目人工修正记录。`scores` 存**新**维度 JSON 字符串,`comments` 存**旧**维度 JSON 字符串;按 `created_at DESC` 读取。
|
||||
- **review_snapshots**:每次自动评审快照。含 `attempt`、`ai_report`、`standard_snapshot`;按 `attempt ASC` 读取,用于多次提交历史回看。
|
||||
|
||||
### 3.5 Migrations(幂等)
|
||||
|
||||
对旧库用 `ALTER TABLE ... ADD COLUMN` + try/catch 逐个追加缺失列:
|
||||
`branch` → `service_url` → `base_branch` → `question_id` → `final_level` → `standards.max_score` → `entries.deliverables` → `projects.track` → `entries.sub_type`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 评审引擎(review.service.ts)
|
||||
|
||||
### 4.1 并发与队列
|
||||
|
||||
```
|
||||
MAX_CONCURRENT = REVIEW_CONSTANTS.MAX_CONCURRENT // = 3
|
||||
activeCount(内存)+ 队列(entryId 数组)+ DB 状态双控
|
||||
```
|
||||
|
||||
- `startReview(entryId)`:仅 `pending` 可进入;统计 DB 中 `queued/cloning/analyzing` 数量,≥ MAX_CONCURRENT 则置 `queued` 排队,否则立即执行。
|
||||
- 执行结束 `activeCount--` 后调 `processQueue()` 从队列取下一个。
|
||||
- 崩溃恢复由 `index.ts` 启动逻辑兜底。
|
||||
|
||||
### 4.2 评审流程(executeReview)
|
||||
|
||||
```
|
||||
cloneRepo → discoverFiles → countCodeStats → tryBuild
|
||||
→ [service_url 提供时直接 tryBrowse,否则 build成功→tryStart→tryBrowse]
|
||||
→ 概览(轻量 context, 1次AI)
|
||||
→ 11子Agent分维度并行(并发3, 每次重试1次)
|
||||
→ AI校准(1次AI, delta调整)
|
||||
→ 硬规则扣顶(确定性)
|
||||
→ L2/L3 拆分(人才测评)
|
||||
→ 迟交扣分 → 分数封顶 → finalScore
|
||||
→ 写 ai_report + review_snapshots + 清理克隆目录
|
||||
```
|
||||
|
||||
#### 4.2.1 克隆(cloneRepo)
|
||||
|
||||
- `--depth 1`,可选 `--branch`。
|
||||
- **本地路径克隆**:`file://`、`盘符:\`、`\` 开头的视为本地仓库,仅允许复制到 `CLONE_DIR` 或自身目录内的路径(防 SSRF),要求源存在。
|
||||
- **远程克隆**:`https://` 且配置了 `giteaToken` 时注入用户名+token 到 URL;否则匿名克隆。失败时错误信息中会脱敏 URL 中的密码/凭据(`https://***@`)。
|
||||
- 失败 → 状态 `clone_fail` 并记录日志。
|
||||
|
||||
#### 4.2.2 构建测试(tryBuild)
|
||||
|
||||
- 递归扫描构建文件,`buildRootMap` 记录每个构建文件的最浅层级目录(支持 monorepo 多包)。
|
||||
- 对 `BUILD_SYSTEMS` 中匹配的系统(package.json / pom.xml / build.gradle / makefile / cargo.toml / go.mod / pyproject.toml):
|
||||
1. `check`:探测工具可用性(如 `npm --version`、`mvn --version`),不可用则跳过该系统。
|
||||
2. `install`:**安装步骤被独立执行,且不视为构建成功依据**(canBuild 会排除 `npm install`/`pip install` 类命令)。Python 项目的 install 为 `pip install -e .`,build 为 `python -m build --wheel --no-isolation`。
|
||||
3. `build`:`npm run build` / `mvn compile -q` / `gradle build -x test` / `make` / `cargo build` / `go build ./...` / `python -m build`。
|
||||
4. `test`:仅在 build 成功后才执行(pytest/jest 等)。
|
||||
- `canBuild` 判定:存在**非 install/依赖解析类**的步骤成功(排除 dependency:resolve / dependencies -q / npm install / pip install)。
|
||||
- 每一步产出 `{ command, status, output(≤1000字符), durationMs }`,写入 summary 供 AI 参考。
|
||||
- 各种构建步骤统一 120s 超时,`runBuildStepAsync` 用 exec 异步 + timeout kill。
|
||||
|
||||
#### 4.2.3 启动测试(tryStart)
|
||||
|
||||
- 读根 `package.json` 的 `scripts.start/dev/serve`;否则查 `docker-compose` / `Dockerfile`。
|
||||
- 用 `cmd /c`(Win)或 `sh -c` 启动,捕获 stdout/stderr;30s 超时 kill。
|
||||
- 在 `COMMON_PORTS`(约 16 个常见端口)轮询探测直到命中响应(最长 28s)。
|
||||
- 返回启动结果与日志(≤1000 字符)。
|
||||
- 若参赛者**已提供 `service_url`,则跳过 tryStart,直接用该地址 tryBrowse**。
|
||||
|
||||
#### 4.2.4 浏览器测试(tryBrowse)
|
||||
|
||||
- **Web 项目**:用 `puppeteer-core` 启动本机 Chrome/Edge(自动探测路径),`domcontentloaded` + 1s 等待,收集 JS 错误、网络错误、整页截图。找不到浏览器则跳过并注明。
|
||||
- **CLI 项目**:按构建系统探测运行时版本(`node --version`/`go version`/`cargo --version`/`java -version`)。
|
||||
- 结果 `{ tested, pageLoaded, jsErrors, networkErrors, screenshot?, summary }` 注入项目上下文。
|
||||
|
||||
#### 4.2.5 代码统计(countCodeStats)
|
||||
|
||||
- 统计 `fileCount/totalLines/languageStats/effectiveLines/blankLines/commentLines/duplicateRatio/dirDepth.avg/tinyFiles`。
|
||||
- 空行、注释行按 `isCommentLine` 行级判定。
|
||||
- `duplicateRatio`:读文件 → trim → 去空行拼成 normalized 字符串 → MD5 哈希计数;`hash` 计数 >1 时按 `approxLines×(count-1)` 估算重复行。
|
||||
- 产出「代码健康度」三段提示(有效代码率 / 重复代码占比 / 目录结构+小文件)注入 AI 上下文。
|
||||
|
||||
#### 4.2.6 文件发现(discoverFiles)
|
||||
|
||||
- 遍历跳过 `node_modules`/`.git`,隐藏目录忽略。
|
||||
- 依 `FILE_PRIORITY_RULES`(README、docs、AGENTS/CLAUDE、design/arch、test/report、构建配置、lint 配置、rule 文件)按序命中并**提升优先级**;其余按 `CODE_FILE_EXTENSIONS` 收容,最多 60 个。
|
||||
- 单文件内容按大小分档截断(>1MB→200KB,>100KB→100KB)。
|
||||
- `base_branch` 提供时做一次 `git diff`(上限 50000 字符),用于赛道二「与基线对比」评审新 AI 生成代码。
|
||||
|
||||
### 4.3 三阶段 AI 评审
|
||||
|
||||
#### Phase 1 概览(1 次调用)
|
||||
|
||||
- 用**轻量 context**(标题/仓库/服务地址/代码统计/构建结果/代码健康,`MAX_OVERVIEW_CHARS=30000` 截断的文件块)。
|
||||
- 要求输出 `{"overview":"..."}`(≤200 字)。解析失败降级为「标题+N文件+N行代码+可构建」拼接。
|
||||
|
||||
#### Phase 2 分维度子 Agent(并发 3)
|
||||
|
||||
- 对 `standardDims` 逐个调 `runSubAgent`:
|
||||
- **文件过滤**:先按维度 `fileKeywords`(正则 OR 匹配文件路径);否则按 `matchDimKey` 命中 `DIM_FILE_FILTERS`(各维度只给相关文件,如「演示与文档」只给 .md/docs、「代码规范」只给源码)。
|
||||
- **截断**:普通维度 15000 字,构建相关维度 40000 字。
|
||||
- **注入**:`projectContext` 含项目信息/代码统计/构建/启动/浏览器结果;构建维度额外附构建步骤详情 + 启动 + 浏览器验证上下文;目录含 `base_branch` 的 diff。
|
||||
- **评分指南**:内置 `dimGuidelines` 覆盖常见维度名(场景价值/架构设计/工具使用/实现完整/开发范式/Agent核心/规模/代码规范/演示与文档/AI使用日志/效果与数据/安全/赛道二维度)。未命中指南时用标准 `content` 作指南。赛道一变体名(如「开发范式与架构设计」)依赖关键词包含匹配。
|
||||
- **严格规则**:强调「无→0 分」的否决项;评语禁止贴代码(≤200 字)。
|
||||
- 失败重试 1 次;仍失败得 0 分并注明。
|
||||
- 返回后 clamp 到 `[0, maxScore]` 并 `Math.round`。
|
||||
|
||||
#### Phase 3a 校准(1 次调用)
|
||||
|
||||
- 将子 Agent 分数清单交给校准 Agent,检测维度间矛盾、分数标准差(偏离均值 >2σ 视为异常维度)。
|
||||
- 输出 `{"adjustments":[{"name","delta","reason"}],"explanation"}`,对匹配维度做 delta 增减(clamp 到 `[0,maxScore]`,`Math.round`)。
|
||||
- 无矛盾则不调整;调用失败给「校准完成」。
|
||||
|
||||
#### Phase 3b 硬规则(确定性扣顶,校准后执行)
|
||||
|
||||
| 触发条件 | 命中维度 | 扣顶 |
|
||||
|---|---|---|
|
||||
| 构建失败(`!canBuild` 且无 service_url) | 构建相关维度(实现完整度/稳定性等) | ≤ floor(maxScore×0.33),即 4/12 |
|
||||
| 构建失败 | 「效果与数据」 | ≤ floor(maxScore×0.3),即 3/10 |
|
||||
| pytest 环节失败 | 「效果与数据」或构建维度 | ≤ floor(maxScore×0.5),即 5/10 |
|
||||
| 重复代码 >50% | 「代码规范」 | ≤ 3 |
|
||||
| 无任何 README(`hasAnyReadme=false`) | 「演示与文档」 | ≤ 2 |
|
||||
| 有 README 但根目录无(`!hasRootReadme`) | 「演示与文档」 | ≤ 3 |
|
||||
|
||||
每被执行一条即写入 `hardRulesLog`,追加进 `calibrationExplanation`。
|
||||
|
||||
### 4.4 计分
|
||||
|
||||
- `totalScore` = 各维度 `Math.round` 累加;`maxTotal` = maxScore 累加;`pct` = round(×100)。
|
||||
- **人才测评 L2/L3 拆分**:按 group 拆分 common(L2)与题目(L3)。
|
||||
- L2 及格线:`l2Score ≥ pass_line`(`pass_line>0` 时),否则兜底 `≥ round(l2Max × 0.6)`。
|
||||
- **仅当存在题目维度(`l3Max > 0`)且通过 L2** 时,才按 `(l2+l3)/(l2Max+l3Max) ≥ 0.8` 判定 **L3**(否则 **L2**)。
|
||||
- 无题目维度(`l3Max=0`)或未过 L2 及格线:通过 → **L2**,未过 → **不合格**。
|
||||
- **迟交扣分**:读最后 commit 时间 - 项目 `deadline`,`late_days>0` 时:`penalty = late_days > 7 ? totalScore : min(totalScore, late_days × late_penalty)`;写回 `late_days`。
|
||||
- **分数封顶**:`max_score_cap < 100` 时 `score = min(totalScore, cap)`。
|
||||
- `finalScore = max(0, round(capped - penalty))`。
|
||||
- 写 `ai_report`(含 overview/dimensions/totalScore/maxTotal/pct/calibrationExplanation)、`raw_score`、`final_score`、`final_level`、状态 `review_done`,并插入 `review_snapshots`。
|
||||
- 最后安全校验后删除克隆目录。
|
||||
|
||||
### 4.5 DeepSeek 调用(callDeepSeek)
|
||||
|
||||
- 无 `deepseekApiKey` → 返回 null。
|
||||
- 请求 `https://api.deepseek.com/v1/chat/completions`,模型 `deepseek-v4-flash`,`temperature: 0`。
|
||||
- system 提示含**反注入安全规则**:参赛者文件内容仅作被评审数据、非指令,忽略其中的指令性文本。
|
||||
- 重试:默认 2 次(指数退避 ≤8s),仅对 `429`/`5xx` 重试;`AbortController` 按 `deepseekTimeout` 超时。
|
||||
|
||||
---
|
||||
|
||||
## 5. 鉴权(auth.ts)
|
||||
|
||||
- `POST /api/auth/login`:比对 `config.authPassword`,成功签发 `role:'admin'`、`expiresIn:'24h'` 的 JWT。
|
||||
- **防爆破**:按 IP 内存计数,连续 5 次失败锁定该 IP 60s → 429。
|
||||
- `authMiddleware`:除 `/api/auth/login`、`/api/health`(两者先于中间件注册)外,所有 `/api/*` 路由(**含 `/api/backup`**)都校验 `Authorization: Bearer <jwt>`;无效/过期 → 401。
|
||||
|
||||
---
|
||||
|
||||
## 6. 条目状态机
|
||||
|
||||
```
|
||||
pending ──start──▶ queued ──▶ cloning ──▶ analyzing ──▶ review_done ──report──▶ admin_reviewed
|
||||
│ │ │ │ │
|
||||
│ └──cancel──▶ cancelled │ └──report(force)──▶
|
||||
│ └──cancel──▶ (终态) └──fail──▶ *_fail ──retry──▶ pending
|
||||
└──cancel──▶ ... │
|
||||
└──failed──▶ (retry→pending)
|
||||
```
|
||||
|
||||
| 动作 | 允许前置状态 | 说明 |
|
||||
|---|---|---|
|
||||
| start | pending | 立即跑或排队 |
|
||||
| cancel | queued/cloning/analyzing | 置 cancelled(终态)|
|
||||
| retry | clone_fail/analysis_fail/failed | 重置 pending 并清空 ai_report 后重排 |
|
||||
|
||||
> 注:`clone_fail` 由 cloneRepo 写入;评审流程任何未知异常由 runReview 的 catch 兜底为 `failed`(review.service.ts:47)。**`analysis_fail` 目前无代码写入**,仅作为 retry 兼容值保留。
|
||||
| 编辑/删除 | 仅 pending | 其余 409 |
|
||||
| report 修正 | review_done/admin_reviewed(force 可跳过)| 写 revision_history |
|
||||
|
||||
---
|
||||
|
||||
## 7. 标准与计分工具(standards.ts + standard-utils.ts)
|
||||
|
||||
### 7.1 parseDimensions
|
||||
|
||||
正则解析 `## 维度名(XX分)` / `## 维度名(XX%)`,支持:
|
||||
- 序号前缀剥离(`1. ` 等)。
|
||||
- 组标记 `[Qx] 名称` → `group`(人才测评多题共用一份标准)。
|
||||
- 维度正文首行 `文件关键词:xxx` → `fileKeywords`(从正文剔除)。
|
||||
- 空内容兼容。
|
||||
|
||||
### 7.2 总分计算(standard-utils)
|
||||
|
||||
- `computeCommonTotal`:`group==='common'` 维度 maxScore 之和。
|
||||
- `computeMaxBonus`:各非 common 组内 maxScore 之和,取**最大值**(单题组)。
|
||||
- `computeEffectiveTotal` = common 和 + 每组最大附加分(↑总上限校验依据)。
|
||||
- `computePassLine`:人才测评 = `round(commonTotal × 0.6)`;其余 = `round(effectiveTotal × 0.6)`。
|
||||
- `matchDimKey`:先精确匹配,否则按最长包含关键词匹配(用于维度 json → 指南/过滤器映射)。
|
||||
|
||||
### 7.3 标准匹配(resolveStandard)
|
||||
|
||||
创建条目的标准匹配顺序:
|
||||
1. 赛道一且带 `sub_type` → `category_tag = sub_type`。
|
||||
2. 有 track → `category_tag = track`。
|
||||
3. 兜底 → `category_tag` 为空/空 NULL 的默认标准。
|
||||
|
||||
### 7.4 内置模板
|
||||
|
||||
项目按赛道创建时自动从 `server/config/standards/` 载入模板标准(`技术大赛-赛道一.md`、`技术大赛-赛道二.md`、`AI人才育成L2.md`),仅当解析后有效总分 ≤ `STANDARD_MAX_SCORE` 才写入。
|
||||
|
||||
---
|
||||
|
||||
## 8. 交付物子系统(entries 内)
|
||||
|
||||
- 交付物为条目上的 `deliverables` JSON 数组 `[{name, required, submitted}]`。
|
||||
- 默认清单:源代码/README/设计文档/测试用例与测试结果/AGENTS.md/样本数据(必交)+ 演示录屏(选交)。
|
||||
- 提供初始化(`deliverables/init`)、汇总(`deliverables/summary`,含必交提交率)、CSV 导出(`deliverables/export`,UTF-8 BOM)。
|
||||
|
||||
---
|
||||
|
||||
## 9. PDF 报告(pdf.service.ts)
|
||||
|
||||
- 基于 puppeteer-core + 本机 Edge/Chrome,`headless`,渲染 A4,`printBackground`。
|
||||
- 输出目录 `server/data/reports/`。
|
||||
- **条目报告** `generateEntryPdf`:SVG 雷达图(`buildRadarSvg`,按维度得分比例)+ 维度得分表 + 总览/校准修正/迟交/多次提交信息。
|
||||
- **汇总报告** `generateSummaryPdf`:按分组(题目/赛道)排名表 + 参赛者 L2/L3 合格判定表(`buildSummaryHtml` 依赖 summaries/categories/participants)。
|
||||
- 找不到浏览器时抛错 → 调用的导出接口返回 500。
|
||||
|
||||
---
|
||||
|
||||
## 10. 路由挂载与中间件(index.ts)
|
||||
|
||||
```
|
||||
app.use(helmet())
|
||||
app.use(cors(origin:[localhost:14001,127.0.0.1:14001], credentials))
|
||||
app.use(express.json(limit:10mb))
|
||||
|
||||
/api/auth → authRouter (login 免鉴权)
|
||||
/api/health → {status:'ok'} (免鉴权;先于 authMiddleware 注册)
|
||||
/api/backup → 手动触发备份 (需鉴权;注册于 authMiddleware 之后)
|
||||
(以下全部 /api 先过 authMiddleware)
|
||||
/api/projects → projectsRouter
|
||||
/api/projects/:projectId/standards → standardsRouter (mergeParams)
|
||||
/api/projects/:projectId/entries → entriesRouter (mergeParams)
|
||||
/api/config → configRouter
|
||||
```
|
||||
|
||||
- 全局错误中间件兜底 → 500 `{"error":"服务器内部错误"}`。
|
||||
- CORS 仅放行前端本地源(14001)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 测试
|
||||
|
||||
- 单元/集成测试在 `server/src/__tests__/`(vitest + supertest 风格),可通过 `cd server && npm test` 运行;`SKIP_LISTEN=1` 使 `app` 可被测试进程 import。
|
||||
- `entries` 提供**测试专用端点** `PUT /:entryId/force-review`(需 `ADMIN_TEST_TOKEN=true`,默认关闭返回 404),供 E2E 快速把条目置为 `admin_reviewed`。
|
||||
- `review.service.ts` 导出向后兼容的纯函数(`buildPrompt` / `parseResult` / `averageDimensions` / `tiebreakDimensions` / `isCommentLine` / `countCodeStats` / `DIM_FILE_FILTERS` / `filterFilesForDim`)供单测直接使用。
|
||||
|
||||
---
|
||||
|
||||
## 12. 关键常量(review-constants.ts)
|
||||
|
||||
```ts
|
||||
MAX_CONCURRENT: 3 // 并行评审数
|
||||
MAX_OVERVIEW_CHARS: 30000
|
||||
MAX_FILE_CHARS_NORMAL: 15000
|
||||
MAX_FILE_CHARS_BUILD: 40000
|
||||
DUP_CAP_SCORE: 3 // 重复>50% → 代码规范≤3
|
||||
NO_README_CAP: 2 // 无 README → 演示与文档≤2
|
||||
NO_ROOT_README_CAP: 3 // 有但非根 README → ≤3(maxScore 为5时)
|
||||
BUILD_FAIL_CAP_RATIO: 0.33 // 构建失败 → 构建维度 ×0.33
|
||||
TEST_FAIL_CAP_RATIO: 0.5
|
||||
EFFECT_DATA_BUILD_FAIL_RATIO: 0.3
|
||||
DUP_RATIO_CAP_TRIGGER: 0.5
|
||||
L2_PASS_RATIO: 0.6
|
||||
L3_RATIO: 0.8
|
||||
DEFAULT_LATE_PENALTY: 5
|
||||
MAX_LATE_DAYS: 7
|
||||
```
|
||||
|
||||
> 注:`MAX_CONCURRENT=3` 为 review-constants 实际值(AGENTS.md 中记录的 1 已过时,文档以本源码为准)。
|
||||
|
||||
---
|
||||
|
||||
## 13. 变更记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|---|---|---|
|
||||
| v1.0 | 2026-08-04 | 依据 `server/src` 全量源码整理(进程/配置/数据模型/评审引擎/状态机/PDF/鉴权/测试) |
|
||||
@@ -0,0 +1,537 @@
|
||||
# AI人才育成评审系统 — 前端画面功能与测试用例设计文档
|
||||
|
||||
> 版本:v1.1
|
||||
> 更新日期:2026-08-04
|
||||
> 适用范围:`web/` 前端(React 19 + Vite + react-router-dom 7)
|
||||
> 配套文档:`docs/design/01-系统设计书.md`(后端系统级设计)
|
||||
|
||||
---
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
本文档从前端**画面**粒度出发,先说明系统的**整体功能**,再逐一说明每个画面的功能组成、测试点与预想结果。
|
||||
|
||||
本文档回答三类问题:
|
||||
1. **系统是干什么的**(整体功能与价值)
|
||||
2. **每个画面提供什么功能**(功能组成、状态、交互)
|
||||
3. **每个功能如何验证**(测试点 + 预想结果,配合后端的已确认缺陷清单)
|
||||
|
||||
本文档是 `web/src/**` 各组件当前实现(2026-08-04 快照)的行为契约,也是前端测试用例的编写依据。
|
||||
|
||||
---
|
||||
|
||||
## 2. 整体功能说明(Overview)
|
||||
|
||||
### 2.1 系统定位
|
||||
|
||||
**AI人才育成评审系统**是一个给「评委(管理员)」使用的 Web 应用。它解决的核心问题:**参赛者提交一个 Git 仓库,系统自动拉取代码、执行 AI 评审、按既有赛道标准打分,并出具可视化汇总**,从而把「人工逐份阅读代码 + 打分」这一耗时流程自动化。
|
||||
|
||||
系统不面向参赛者开放注册(采用管理密码登录),参赛者仅通过仓库 URL 参与,不登录系统。
|
||||
|
||||
### 2.2 核心业务价值
|
||||
|
||||
| 价值 | 说明 |
|
||||
|:---|:---|
|
||||
| 自动化评审 | 拉取仓库 → 构建 → 浏览 → AI 多维评审 → 校准 → 出分 |
|
||||
| 标准化评分 | 按赛道预设标准(维度+满分)评卷,避免人工主观偏差 |
|
||||
| 可视化 | 雷达图、柱状图、汇总排名、按赛道分组统计 |
|
||||
| 可追溯 | 每次评审快照、人工修正历史全记录 |
|
||||
| 成果物跟踪 | 确认参赛者是否提交必须成果物(源码/README/测试等) |
|
||||
|
||||
### 2.3 系统角色
|
||||
|
||||
只有一种角色:**管理员(评委)**,通过 `server/.env` 中的 `AUTH_PASSWORD` 登录。
|
||||
|
||||
### 2.4 核心领域概念
|
||||
|
||||
| 概念 | 说明 | 前端对应画面 |
|
||||
|:---|:---|:---|
|
||||
| 赛道(Track) | 评审类型:赛道一(Agent开发实战)、赛道二(IDE+范式创新)、人才测评 | 新建项目下拉 |
|
||||
| 项目(Project) | 一场赛事的容器,含一组标准与条目 | 侧边栏 / 项目页 / 仪表盘 |
|
||||
| 标准(Standard) | 评审维度定义(`## 维度名(XX分)`),按赛道自动预置或人工上传 | Tab: 标准 |
|
||||
| 条目(Entry) | 单个参赛作品(标题 + 仓库 URL + 参赛者) | Tab: 条目 |
|
||||
| 成果物(Deliverable) | 参赛者须提交的材料清单(源码/README/测试等) | Tab: 成果物 + 详情面板 |
|
||||
| 评审(Review) | 对条目的自动评审流程,产出各维度得分 | 详情面板 |
|
||||
| 认定(Level) | 人才测评的最终等级(L2 合格 / L3 优秀 / 不合格) | 详情面板 / 汇总 |
|
||||
| 汇总(Summary) | 按类别分组排名 +(人才测评)参赛者合格判定 | Tab: 汇总 |
|
||||
|
||||
### 2.5 端到端业务流程
|
||||
|
||||
```
|
||||
① 管理员登录(/login,密码)
|
||||
│
|
||||
② 创建项目并选赛道(侧边栏「+ 新建项目」)
|
||||
│ → 系统按赛道自动生成内置评审标准
|
||||
▼
|
||||
③ 在项目页「标准」Tab 确认/上传/删除评审标准
|
||||
│
|
||||
④ 在「条目」Tab 添加条目(标题 + Git 仓库 URL)
|
||||
│ 或 CSV 批量导入,支持题目(人才测评)/子类型(赛道一)
|
||||
▼
|
||||
⑤ (可选)在「成果物」Tab 初始化成果物清单、勾选提交状态、下载 CSV
|
||||
│
|
||||
⑥ 在「条目」Tab 点击「启动」/「启动选中」触发评审
|
||||
│ → 后端拉取仓库 → 构建 → 浏览 → AI 评审 → 校准 → 出分
|
||||
│ → 状态流转:pending→queued→cloning→analyzing→review_done/fail
|
||||
▼
|
||||
⑦ 在「条目」页点进详情面板:查看评分、雷达图、成果物,人工修正分数/评语
|
||||
│
|
||||
⑧ 在「汇总」Tab 查看分类排名、参赛者合格判定,下载汇总 PDF
|
||||
▼
|
||||
⑨ 在「仪表盘」查看所有项目/赛道概览;侧边栏可删除项目
|
||||
```
|
||||
|
||||
### 2.6 评审管线(后端,供理解各画面状态)
|
||||
|
||||
评审由后端 `review.service.ts` 驱动,前端「条目」Tab 实时反映其状态:
|
||||
|
||||
```
|
||||
cloneRepo → discoverFiles → countCodeStats → tryBuild(7种构建系统)
|
||||
→ tryStart/tryBrowse(puppeteer) → 概览(Phase1) → 11子Agent并肩(Phase2)
|
||||
→ AI校准(Phase3a) → 硬规则(Phase3b) → 总分 → 迟交扣分 → finalScore
|
||||
```
|
||||
|
||||
- 最大并发 `MAX_CONCURRENT=3`(`review-constants.ts`,代码经 `REVIEW_CONSTANTS.MAX_CONCURRENT` 引用),超过上限的条目置 `queued` 排队。
|
||||
- 状态机(前端「条目」Tab 据此渲染按钮):`pending → queued → cloning → analyzing → review_done`,失败态 `clone_fail/analysis_fail/failed`,人工修正后 `admin_reviewed`。
|
||||
- 人才测评按题目(Q1-Q6)过滤追加维度,判定 L2(共通得分率≥60%)/ L3(总分率≥80%)。
|
||||
|
||||
### 2.7 画面与整体功能的关系(地图)
|
||||
|
||||
| 画面 | 在整体流程中的角色 |
|
||||
|:---|:---|
|
||||
| 登录页 | 准入控制(流程 ①) |
|
||||
| 侧边栏 | 全局导航 + 项目/赛道管理(②⑨) |
|
||||
| 仪表盘 | 跨项目/赛道概览统计(⑨,只读) |
|
||||
| 项目页外壳 | 项目级入口、重命名/删除、Tab 导航(②起) |
|
||||
| Tab: 标准 | 评审依据管理(③) |
|
||||
| Tab: 条目 | 参赛作品录入 + 评审触发 + 结果查看(④⑥⑦) |
|
||||
| Tab: 成果物 | 成果物提交跟踪(⑤) |
|
||||
| Tab: 汇总 | 排名与合格判定出展/导出(⑧) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 系统整体架构
|
||||
|
||||
### 3.1 前端技术栈
|
||||
|
||||
| 项 | 值 |
|
||||
|:---|:---|
|
||||
| 框架 | React 19.2.7 |
|
||||
| 路由 | react-router-dom 7.18.1(BrowserRouter) |
|
||||
| 构建 | Vite 8.1.1(端口 14001,`/api` 代理到 `http://localhost:3002`) |
|
||||
| 类型 | TypeScript ~6.0.2 |
|
||||
| 测试(计划) | 单测 vitest + @testing-library/react;E2E @playwright/test 1.61.1 |
|
||||
| 状态持久化 | `localStorage.token`(JWT,24h) |
|
||||
|
||||
### 3.2 路由结构(App.tsx)
|
||||
|
||||
```
|
||||
/login → LoginPage(公开)
|
||||
/ → ProtectedRoute(无 token 重定向 /login)
|
||||
├── index → Layout(Sidebar) + Dashboard
|
||||
└── project/:id → Layout(Sidebar) + ProjectView
|
||||
```
|
||||
|
||||
- `ProtectedRoute`:仅检查 `localStorage.token` 是否存在,**不验证 token 有效性**(有效性问题由后端 401 兜底)。
|
||||
|
||||
### 3.3 组件树
|
||||
|
||||
```
|
||||
App
|
||||
└── Layout
|
||||
├── Sidebar (项目列表 / 新建 / 删除 / 退出)
|
||||
└── <Outlet>
|
||||
├── Dashboard
|
||||
└── ProjectView
|
||||
├── 项目头部(重命名 / 删除 / meta / Tabs)
|
||||
├── StandardsManager (Tab: 标准)
|
||||
├── EntryManager (Tab: 条目)
|
||||
├── DeliverablesView (Tab: 成果物)
|
||||
└── SummaryView (Tab: 汇总)
|
||||
```
|
||||
|
||||
### 3.4 API 层约定(services/api.ts)
|
||||
|
||||
- `BASE = '/api'`,所有请求经 `request<T>()` 封装。
|
||||
- 请求头:始终带 `Content-Type: application/json`;有 token 时带 `Authorization: Bearer <token>`。
|
||||
- **401 处理**:非 `/auth/login` 路径返回 401 时 → 清除 token → `window.location.href = '/login'` → 抛「未登录」。
|
||||
- 错误处理:非 2xx → `throw new Error(data.error || '请求失败')`。
|
||||
- 部分组件直接使用原生 `fetch`(成果物 init/toggle、PDF 下载),绕过此封装。
|
||||
|
||||
---
|
||||
|
||||
## 4. 画面 1:登录页(LoginPage)
|
||||
|
||||
### 4.1 功能说明
|
||||
|
||||
- 单密码认证(`POST /api/auth/login`,后端另有 5 次失败锁定)。
|
||||
- 成功 → 写 `localStorage.token` → `navigate('/')`。
|
||||
- 失败 → 显示后端返回的错误信息。
|
||||
- 提交中 → 按钮禁用并显示「登录中...」。
|
||||
|
||||
### 4.2 测试点与预想结果
|
||||
|
||||
| # | 测试点 | 预想结果 |
|
||||
|:--|:-------|:---------|
|
||||
| L1-1 | 初始状态 | 密码输入框为空,登录按钮禁用 |
|
||||
| L1-2 | 输入任意字符 | 按钮启用 |
|
||||
| L1-3 | 输入空格 | 按钮启用(代码只查 truthy) |
|
||||
| L1-4 | 密码正确提交 | 写 `localStorage.token`,`navigate('/')` |
|
||||
| L1-5 | 密码错误提交 | 显示错误信息,按钮恢复可用,不写 token |
|
||||
| L1-6 | 提交中 | 按钮文字「登录中...」且禁用,无法二次提交 |
|
||||
| L1-7 | 已有 token 时登录失败 | 旧 token **不被清除**(handleSubmit 不删 token) |
|
||||
| L1-8 | 失败后重新输入并提交 | 旧错误信息先被清除再展示新状态 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 画面 2:整体布局 + 侧边栏(Layout + Sidebar)
|
||||
|
||||
### 5.1 功能说明
|
||||
|
||||
- Layout 负责骨架:左侧 Sidebar + 右侧 `<Outlet>` 内容区。
|
||||
- Sidebar 功能:项目列表、激活高亮、新建项目(名称 + 赛道下拉)、删除项目、退出登录、Logo 回首页。
|
||||
- 项目列表数据源:`GET /api/projects`(返回含 `total/reviewed/active` 统计)。
|
||||
- 新建项目:`POST /api/projects`,成功 → 收起表单 → 刷新列表 → `navigate('/project/:id')`。
|
||||
- 删除项目:`DELETE /api/projects/:id?force=true`,删除当前激活项目时跳转 `/`。
|
||||
|
||||
### 5.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| S1-1 | 骨架 | 已登录访问 `/` | 左侧 Sidebar + 右侧内容区(Layout 渲染 Outlet) |
|
||||
| S2-1 | Logo | 点击「AI-Review」标题 | 跳转 `/` |
|
||||
| S3-1 | 退出 | 点击「退出」 | 清除 token,跳转 `/login` |
|
||||
| S4-1 | 列表 | 加载成功 | 显示项目(名称 + 赛道 tag + `reviewed/total ✓`) |
|
||||
| S4-2 | 列表 | 空列表 | 列表区为空,仍显示「+ 新建项目」按钮 |
|
||||
| S4-3 | 列表 | 无项目刷新依赖 | 仅 mount 时加载一次(切页不自动刷新) |
|
||||
| S5-1 | 高亮 | 路由 `/project/:id` | 对应 `project-item` 含 `active` class |
|
||||
| S5-2 | 高亮 | 在 Dashboard(无 :id) | 无 active 项 |
|
||||
| S6-1 | 新建 | 点「+ 新建项目」 | 展开表单(名称输入 + 赛道下拉 + 创建/取消) |
|
||||
| S6-2 | 新建 | 名称为空点创建 | 不请求(`!name.trim()` return),表单保持 |
|
||||
| S6-3 | 新建 | 填名称不选赛道 | 创建成功(track=''),跳转新项目页 |
|
||||
| S6-4 | 新建 | 选赛道一/二/人才测评 | 创建成功带 track,跳转新项目页 |
|
||||
| S6-5 | 新建 | 按 Enter | 触发创建(onKeyDown Enter) |
|
||||
| S6-6 | 新建 | 创建成功 | 表单收起、列表刷新、跳转 |
|
||||
| S6-7 | 新建 | 后端失败 | 显示错误信息(表单内红字) |
|
||||
| S6-8 | 新建 | 点「取消」 | 表单收起 |
|
||||
| S6-9 | 新建 | Enter + 点创建连按 | 可能双请求(onKeyDown 与 onClick 都调 create)→ 见缺陷 D5 |
|
||||
| S7-1 | 删除 | hover 项目行 | 出现「×」按钮 |
|
||||
| S7-2 | 删除 | 点「×」 | 弹 confirm 提示 |
|
||||
| S7-3 | 删除 | confirm 取消 | 不删除 |
|
||||
| S7-4 | 删除 | confirm 确认且为当前项目 | `DELETE ?force=true`,刷新,跳 `/` |
|
||||
| S7-5 | 删除 | confirm 确认且非当前项目 | 刷新列表,**不跳转** |
|
||||
| S7-6 | 删除 | 删除失败 | `alert(err.message)` |
|
||||
| S7-7 | 删除 | confirm 文案 | 含「所有关联标准和条目将被删除」 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 画面 3:仪表盘(Dashboard)
|
||||
|
||||
### 6.1 功能说明
|
||||
|
||||
- 数据源:`GET /api/projects`(**注意:列表接口不返回 `failed` 字段**,见缺陷 D1)。
|
||||
- 统计卡:项目总数、评审条目(Σ total)、已完成评审(Σ reviewed)。
|
||||
- 按赛道分组卡片:每个 track 一张卡,显示项目数/条目数/已完成数 + 项目行(最多 4 条)。
|
||||
- 项目行:显示 `reviewed/total ✓`,点击跳转项目页。
|
||||
- 快捷操作:「新建项目」(跨组件点击 Sidebar 按钮)、「查看项目」(跳第一个项目)。
|
||||
- track 为空的项目归入「未分类」。
|
||||
|
||||
### 6.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| D1-1 | 加载态 | 请求未返回 | 显示「加载中...」 |
|
||||
| D2-1 | 统计卡 | 项目总数 | = 项目数组长度 |
|
||||
| D2-2 | 统计卡 | 评审条目 | = Σ `total` |
|
||||
| D2-3 | 统计卡 | 已完成评审 | = Σ `reviewed` |
|
||||
| D3-1 | 空数据 | 无项目 | 三卡显示 0,无赛道卡片,快捷操作区仍在 |
|
||||
| D3-2 | 空数据 | API 请求失败 | `.catch` 兜底,显示 0 数据不崩溃 |
|
||||
| D4-1 | 赛道分组 | 多赛道 | 每个 track 一张卡,标题 `N个项目 / M个条目 / K个已完成` |
|
||||
| D4-2 | 赛道分组 | track 为空 | 归入「未分类」卡片 |
|
||||
| D4-3 | 赛道分组 | 同赛道多项目 | 各赛道统计互不污染 |
|
||||
| D5-1 | 项目行 | 每赛道 | 最多显示 4 条(slice 0,4) |
|
||||
| D5-2 | 项目行 | 进度显示 | `reviewed/total ✓` |
|
||||
| D5-3 | 项目行 | 条目为 0 的赛道 | 显示「暂无项目」而非列表 |
|
||||
| D5-4 | 项目行 | 点击行 | 跳转 `/project/:id` |
|
||||
| D6-1 | 快捷操作 | 「查看项目」有数据 | 跳转第一个项目 |
|
||||
| D6-2 | 快捷操作 | 「查看项目」无数据 | 不跳转(projects[0] 为 undefined,代码安全) |
|
||||
| D6-3 | 快捷操作 | 「新建项目」 | 触发侧边栏 `.btn-new-project` 点击(跨组件 DOM hack) |
|
||||
|
||||
---
|
||||
|
||||
## 7. 画面 4:项目页外壳(ProjectView)
|
||||
|
||||
### 7.1 功能说明
|
||||
|
||||
- 数据源:`GET /api/projects/:id`(返回含 `total/reviewed/pending/active/failed` + `standards`)。
|
||||
- 404 处理:项目不存在 → 显示错误信息。
|
||||
- 头部:项目名(点击重命名)、赛道 badge、meta(总计/✓/▶/✕)、删除项目按钮。
|
||||
- 重命名:点击标题 → 输入框;Enter 保存 / Escape 放弃 / 失焦自动保存。
|
||||
- 删除项目:confirm → `DELETE ?force=true` → `window.location.href = '/'`(整页跳转)。
|
||||
- Tabs:标准 / 条目 / 成果物 / 汇总,**默认激活「条目」**。
|
||||
- `downloadPdf` 帮助函数:解析 `Content-Disposition` 文件名下载。
|
||||
|
||||
### 7.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| P1-1 | 加载态 | 请求未返回 | 「加载中...」 |
|
||||
| P1-2 | 404 | 项目不存在 | 显示错误信息「项目不存在」 |
|
||||
| P2-1 | 头部 | 显示 | 项目名 + 赛道 badge |
|
||||
| P2-2 | 头部 | meta 数值 | `总计 N / ✓ M / ▶ A / ✕ F` 精确显示 |
|
||||
| P3-1 | 重命名 | 点击项目名 | 变为输入框(值=原项目名) |
|
||||
| P3-2 | 重命名 | Enter | PUT 保存,标题更新,退出编辑 |
|
||||
| P3-3 | 重命名 | Escape | 放弃修改,恢复原名 |
|
||||
| P3-4 | 重命名 | 改名后失焦 | 名字变化 → PUT 保存并退出编辑 |
|
||||
| P3-5 | 重命名 | 清空名字 Enter | 不保存(`!newName.trim()` return) |
|
||||
| P3-6 | 重命名 | Enter 后触发 onBlur | 可能双 PUT(竞态)→ 见缺陷 D5 |
|
||||
| P4-1 | 删除 | 点「删除项目」 | confirm 提示 |
|
||||
| P4-2 | 删除 | 确认 | `DELETE ?force=true`,跳转 `/` |
|
||||
| P4-3 | 删除 | 文案 | 与侧边栏文案不一致(「所有关联数据将丢失」)→ 见缺陷 D6 |
|
||||
| P5-1 | Tabs | 默认 | 激活「条目」 |
|
||||
| P5-2 | Tabs | 点击各 Tab | 对应面板渲染,active class 切换 |
|
||||
| P6-1 | downloadPdf | 响应头含 filename | 按 `Content-Disposition` 下载 |
|
||||
| P6-2 | downloadPdf | 无 filename | 默认「报告.pdf」/「汇总报告.pdf」 |
|
||||
| P6-3 | downloadPdf | 非 200 | `alert` 错误信息 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 画面 4A:标准管理(StandardsManager)
|
||||
|
||||
### 8.1 功能说明
|
||||
|
||||
- 数据源:`GET /api/projects/:pid/standards`。
|
||||
- 列表:标准名称、分类标签、上限分数、维度详情(`<details>` 展开)。
|
||||
- 上传标准:名称 / 分类标签 / 总分上限(默认 150)/ content(Markdown)。
|
||||
- 删除标准:confirm → DELETE。
|
||||
- **保存/删除失败均为静默 catch(无用户提示)** → 见缺陷 D4。
|
||||
|
||||
### 8.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| ST1-1 | 列表 | 加载成功 | 显示「评审标准 (N)」,每卡含名称/分类tag/上限 |
|
||||
| ST1-2 | 列表 | 空 | 「暂无评审标准」 |
|
||||
| ST2-1 | 维度详情 | 点 summary | 展开维度列表(名称+分数+group tag+内容) |
|
||||
| ST2-2 | 维度详情 | 无 dimensions | 显示「0个维度」 |
|
||||
| ST3-1 | 上传 | 点「+ 上传标准」 | 显示表单(4 个输入项) |
|
||||
| ST3-2 | 上传 | 名称或内容为空保存 | 不请求(直接 return) |
|
||||
| ST3-3 | 上传 | 保存成功 | 表单收起、列表刷新、新标准出现 |
|
||||
| ST3-4 | 上传 | max_score 非数字 | 落库 150(`parseInt || 150`) |
|
||||
| ST3-5 | 上传 | max_score=0 | 落库 150(0 为 falsy) |
|
||||
| ST3-6 | 上传 | max_score=负数 | 前端可传负数,后端 `parseInt > 0` 校验兜底为 150(standards.ts:83) |
|
||||
| ST3-7 | 上传 | 保存失败 | **无提示**(静默)→ 见缺陷 D4 |
|
||||
| ST4-1 | 删除 | 点「删除」 | confirm → DELETE → 刷新 |
|
||||
| ST4-2 | 删除 | confirm 取消 | 不删除 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 画面 4B:条目管理(EntryManager)
|
||||
|
||||
### 9.1 功能说明
|
||||
|
||||
- 数据源:`GET /api/projects/:pid/entries`,`limit=50`,支持 `status/search/question_id/offset`。
|
||||
- 筛选:状态下拉、题目下拉(仅人才测评)、标题搜索(**即时搜索**,每击键触发)。
|
||||
- 分页:`total > 50` 时显示上一页/下一页 + 页码。
|
||||
- 表格列:选择、标题、参赛者、赛道(含 sub_type/question_id/final_level badge)、状态、分数、操作。
|
||||
- 操作按钮按状态:pending→启动/编辑/删除;queued/cloning/analyzing→取消;失败类→重试;完成类→详情。
|
||||
- 多选 + 批量启动;全选 checkbox。
|
||||
- 添加条目:标题/仓库URL/分支/参赛者 + 赛道一子类型下拉 + 人才测评题目下拉(Q1-Q6)。
|
||||
- 编辑条目(仅 pending):表单显示标题/URL/分支/参赛者/子类型/题目,**但保存时仅提交 title/repo_url/participant**(分支/子类型/题目修改实际不生效)→ 见缺陷 D10。
|
||||
- 批量导入:CSV 解析(首行 header)→ POST batch。
|
||||
- 分数颜色:`≥60` 绿 / `≥40` 黄 / `<40` 红 / null「-」灰。
|
||||
- **所有操作失败均为静默 catch(无提示)** → 见缺陷 D4。
|
||||
|
||||
### 9.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| E1-1 | 列表 | 进入 Tab | 请求 listEntries,显示表格 + 计数 |
|
||||
| E1-2 | 列表 | 空 | 「暂无条目」(colSpan 7) |
|
||||
| E2-1 | 状态筛选 | 选状态下拉 | 重新请求带 `status`,offset 归 0 |
|
||||
| E3-1 | 题目筛选 | 人才测评项目 | 显示 Q1-Q6 下拉 |
|
||||
| E3-2 | 题目筛选 | 其他赛道 | 不显示 |
|
||||
| E3-3 | 题目筛选 | 选 Q | 请求带 `question_id`,offset 归 0 |
|
||||
| E4-1 | 搜索 | 输入关键词 | **即时过滤**(onChange 触发请求)→ 见缺陷 D7 |
|
||||
| E4-2 | 搜索 | 按 Enter | 再次触发 doSearch |
|
||||
| E4-3 | 搜索 | 清空 | 重新加载全部 |
|
||||
| E5-1 | 分页 | total > 50 | 显示 `x / y 页(共 N 条)` |
|
||||
| E5-2 | 分页 | 首页 | 「上一页」禁用 |
|
||||
| E5-3 | 分页 | 末页 | 「下一页」禁用 |
|
||||
| E6-1 | 多选 | 全选 | 选中所有当前页条目 |
|
||||
| E6-2 | 多选 | 单选 | selected 集合增删 |
|
||||
| E6-3 | 多选 | 取消全选 | 清空 |
|
||||
| E7-1 | 批量启动 | 选中 ≥1 | 「启动选中 (N)」可点 → POST batch-start |
|
||||
| E7-2 | 批量启动 | 无选中 | 按钮禁用 |
|
||||
| E8-1 | 添加 | 点「+ 添加条目」 | 显示表单 |
|
||||
| E8-2 | 添加 | 缺标题或 URL | 「添加」按钮禁用 |
|
||||
| E8-3 | 添加 | 赛道一 | 显示子类型下拉(新規/修正) |
|
||||
| E8-4 | 添加 | 人才测评 | 显示题目下拉 + 说明文字(Q1 仅 L2 / 其他 L2+L3) |
|
||||
| E8-5 | 添加 | 无赛道/其他 | 显示「未设置赛道」灰标 |
|
||||
| E8-6 | 添加 | 人才测评不选题目 | 按钮仍可点(前端无必填校验),提交后后端 400 拒绝(entries.ts:120)但被前端静默吞掉,无任何提示 → 见缺陷 D2 |
|
||||
| E8-7 | 添加 | 成功 | 表单收起、列表刷新 |
|
||||
| E9-1 | 批量导入 | 点「批量导入」 | 显示 textarea |
|
||||
| E9-2 | 批量导入 | <2 行 | 不请求 |
|
||||
| E9-3 | 批量导入 | 解析 | 首行 header,后续行按 header 组装 |
|
||||
| E9-4 | 批量导入 | 含逗号字段 | `split(',')` 不处理引号包裹 → 解析错乱 → 见缺陷 D8 |
|
||||
| E9-5 | 批量导入 | 成功 | 显示「成功 N 条」,失败列错误行(最多 5 条) |
|
||||
| E10-1 | 行操作 | pending | 显示「启动/编辑/删除」 |
|
||||
| E10-2 | 行操作 | queued/cloning/analyzing | 显示「取消」 |
|
||||
| E10-3 | 行操作 | clone_fail/analysis_fail/failed | 显示「重试」 |
|
||||
| E10-4 | 行操作 | review_done/admin_reviewed | 显示「详情」 |
|
||||
| E11-1 | 状态 badge | 各状态 | 映射对应颜色 class |
|
||||
| E11-2 | 分数颜色 | ≥60 / ≥40 / <40 / null | 绿 / 黄 / 红 / 灰「-」 |
|
||||
| E12-1 | 编辑 | 点「编辑」 | 弹出编辑框(含赛道/题目下拉) |
|
||||
| E12-2 | 编辑 | 保存 | PUT 更新 → 关闭 → 刷新;**分支/子类型/题目修改实际不提交** → 见缺陷 D10 |
|
||||
| E12-3 | 编辑 | 字段 | 保存仅提交 title/repo_url/participant(另两个 undefined 被丢弃),**分支/子类型/题目不生效** → 见缺陷 D10 |
|
||||
| E13-1 | 删除 | 点「删除」 | confirm → DELETE → 刷新 |
|
||||
| E14-1 | 详情 | 点标题/详情 | 打开 DetailPanel |
|
||||
|
||||
---
|
||||
|
||||
## 10. 画面 4C:条目详情面板(DetailPanel)
|
||||
|
||||
### 10.1 功能说明
|
||||
|
||||
- 打开方式:点击条目行标题或「详情」按钮。
|
||||
- 关闭方式:右上角「✕」按钮,或点击遮罩(面板外区域)。
|
||||
- 元信息:标题、仓库、参赛者、及格线、选题、认定 badge(🏆L3/✅L2/❌)、迟交天数、提交次数。
|
||||
- 分数行:`L2: x/x | L3: x/x | 总分: x/x(pct%)`(无 L3 时简化)。
|
||||
- 项目总览:解析 `ai_report.overview`;解析失败回退 `entry.dimensions`。
|
||||
- 成果物清单:默认 7 项(源代码/README/设计文档/测试用例与测试结果/AGENTS.md/样本数据 必须 + 演示录屏 可选),勾选即 PUT 保存。
|
||||
- 雷达图(RadarChart):有 dims 时渲染 SVG。
|
||||
- L2/L3 评分表:按维度 group 拆分(common / 非 common 各一张表),得分可编辑、评语点击变 textarea、建议只读。
|
||||
- 评审快照 / 修正历史:`<details>` 展开。
|
||||
- 保存修正:PUT report → onSave 回调(刷新列表 + 重开详情)。
|
||||
- 下载报告:调 PDF 导出接口。
|
||||
|
||||
### 10.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| DT1-1 | 关闭 | 点「✕」 | 关闭面板 |
|
||||
| DT1-2 | 关闭 | 点遮罩(面板外) | 关闭面板 |
|
||||
| DT2-1 | 元信息 | 有 final_level | 显示 🏆/✅/❌ badge |
|
||||
| DT2-2 | 元信息 | late_days > 0 | 显示「迟交 N 天」 |
|
||||
| DT2-3 | 元信息 | attempt > 1 | 显示「第 N 次提交」 |
|
||||
| DT3-1 | 分数行 | 有 L3 维度 | `L2: x/x | L3: x/x | 总分: x/x(pct%)` |
|
||||
| DT3-2 | 分数行 | 无 L3 | `得分:x/x(pct%)` |
|
||||
| DT4-1 | 总览 | ai_report 解析成功 | 显示 overview |
|
||||
| DT4-2 | 总览 | 解析失败 | 回退 entry.dimensions |
|
||||
| DT5-1 | 成果物 | 无 deliverables | 显示 7 项默认(前 6 必须) |
|
||||
| DT5-2 | 成果物 | 勾选某项 | PUT 保存,划线 + 绿色 |
|
||||
| DT5-3 | 成果物 | 必选项未交 | 「缺少 N 项必须成果物」红字 |
|
||||
| DT5-4 | 成果物 | 计数 | 「已提交 x/y」 |
|
||||
| DT6-1 | 雷达图 | 有 dims | 渲染 SVG |
|
||||
| DT6-2 | 雷达图 | 无 dims | 不渲染 |
|
||||
| DT7-1 | L2/L3 表 | dims 含 group | L2(common) 与 L3(非common) 分两张表 |
|
||||
| DT7-2 | L2/L3 表 | 得分 input | 数字可改(无超限强制校验) |
|
||||
| DT7-3 | L2/L3 表 | 点评语 | 变 textarea,失焦退出编辑 |
|
||||
| DT7-4 | L2/L3 表 | 建议 | 只读显示 |
|
||||
| DT8-1 | 快照 | 有 snapshots | details 显示次数与时间 |
|
||||
| DT9-1 | 修正历史 | 有 revisions | 显示修正前后分数 |
|
||||
| DT10-1 | 保存修正 | 点按钮 | PUT report → onSave,按钮「保存中...」禁用 |
|
||||
| DT10-2 | 保存修正 | 失败 | `alert` 错误 |
|
||||
| DT11-1 | 下载报告 | 点按钮 | 调 PDF 导出 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 画面 4D:成果物确认(DeliverablesView)
|
||||
|
||||
### 11.1 功能说明
|
||||
|
||||
- 数据源:`GET /api/projects/:pid/entries?limit=250`。
|
||||
- 无条目 → 📦 空态引导。
|
||||
- 有条目但未初始化 → 提示 + 「初始化一覧」按钮。
|
||||
- 头部:条目数 + 提交率 `N个条目 · 提交率 P%(x/y)`。
|
||||
- 汇总卡:每个成果物 `submitted/total`,全交绿 / 否则红 + 必须/可选标注。
|
||||
- 表格:行=条目(参赛者+标题),列=7 个成果物 checkbox;必须项未勾红底、勾选绿底;无 deliverables 行 opacity 0.5。
|
||||
- 操作:初始化一覧(原生 fetch PUT)、下载 CSV、勾选即保存(原生 fetch)。
|
||||
- 提交率:`round(必须已交 / 必须总数 × 100)`。
|
||||
|
||||
### 11.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| DV1-1 | 加载 | 请求中且无数据 | 「加载中...」 |
|
||||
| DV2-1 | 无条目 | 空 | 📦「暂无条目」引导文案 |
|
||||
| DV3-1 | 未初始化 | 有条目无 deliverables | 「条目已存在,但尚未初始化成果物清单」+ 初始化按钮 |
|
||||
| DV4-1 | 头部 | 计数与提交率 | `N个条目 · 提交率 P%(x/y)` |
|
||||
| DV5-1 | 初始化一覧 | 点按钮 | PUT `/entries/deliverables/init`,刷新 |
|
||||
| DV5-2 | 初始化一覧 | 失败 | **静默**(原生 fetch catch 空)→ 见缺陷 D4 |
|
||||
| DV6-1 | 汇总卡 | 每个成果物 | 显示 `submitted/total`,全交绿/否则红 + 必须/可选 |
|
||||
| DV7-1 | 表格 | 勾选 | PUT 保存,汇总卡与提交率即时更新 |
|
||||
| DV7-2 | 表格 | 必须项未勾 | 单元格红底;勾选绿底 |
|
||||
| DV7-3 | 表格 | 行无 deliverables | opacity 0.5 |
|
||||
| DV8-1 | 下载 CSV | 点按钮 | 调导出接口(downloadPdf) |
|
||||
|
||||
---
|
||||
|
||||
## 12. 画面 4E:汇总(SummaryView)
|
||||
|
||||
### 12.1 功能说明
|
||||
|
||||
- 数据源:`GET /api/projects/:pid/summary`。
|
||||
- 标题:分类组含 Q1-Q6 → 「汇总排名(人才测评)」。
|
||||
- 分类组:每组一个表格(排名/标题/参赛者/得分/及格线/认定或结果),组内 >1 条目渲染柱状图。
|
||||
- 认定显示:有 final_level 显示 badge;否则 ✅通过 / ❌未达线。
|
||||
- 参赛者合格判定表:参赛者 / 题目 / 得分 / 及格线 / 总评。
|
||||
- 下载汇总 PDF。
|
||||
- **请求失败时永不 set state → 永久「加载中...」** → 见缺陷 D4。
|
||||
|
||||
### 12.2 测试点与预想结果
|
||||
|
||||
| # | 功能 | 测试点 | 预想结果 |
|
||||
|:--|:-----|:-------|:---------|
|
||||
| SV1-1 | 加载 | 请求未返回 | 「加载中...」 |
|
||||
| SV1-2 | 加载 | 请求失败 | **永久「加载中...」** → 见缺陷 D4 |
|
||||
| SV2-1 | 标题 | categories 含 Q\d | 「汇总排名(人才测评)」 |
|
||||
| SV3-1 | 分类组 | 每组 | 标题含 Q badge / 类别名 + `N个条目` |
|
||||
| SV3-2 | 分类组 | 排名 | `#rank` |
|
||||
| SV3-3 | 分类组 | 结果 | 有 final_level → badge;否则 ✅通过/❌未达线 |
|
||||
| SV3-4 | 分类组 | 及格线 | 显示 |
|
||||
| SV4-1 | 柱状图 | 组内 >1 条目 | 渲染 BarChart(过线绿/未过红) |
|
||||
| SV5-1 | 参赛者判定 | 有 participants | 表格:参赛者/题目/得分/及格线/总评 |
|
||||
| SV6-1 | 下载 | 点按钮 | 调汇总 PDF 导出 |
|
||||
|
||||
---
|
||||
|
||||
## 13. 已确认缺陷清单
|
||||
|
||||
> 来源:对 `web/src/**` 与 `server/src/**` 代码复核(2026-08-04)。严重度:高=影响数据正确性/核心流程;中=功能缺失或误导;低=体验/一致性。
|
||||
|
||||
| # | 严重度 | 位置 | 缺陷描述 | 建议 |
|
||||
|:--|:------:|:-----|:---------|:-----|
|
||||
| D1 | 中 | Dashboard.tsx:27 + server projects.ts:60-62 | 列表接口 `GET /projects` 不返回 `failed`,Dashboard 的 `p.failed || 0` 恒为 0(死代码)。注意 `GET /projects/:id` 是返回 failed 的(projects.ts:148),仅列表端受影响 | 移除前端死代码,或列表接口补 failed 字段 |
|
||||
| D2 | 低 | EntryManager doAdd(ProjectView.tsx:249) | 人才测评添加条目时前端无 `question_id` 必填校验(按钮不禁用),提交后后端 400 拒绝(entries.ts:120-122)但被前端 `catch {}` 静默吞掉,无任何提示 | 表单加必填校验 + 提示 |
|
||||
| D3 | 低 | StandardsManager(ProjectView.tsx:117) | 前端 `max_score` 无 clamp(可输入负数),但后端 `parseInt > 0` 校验会兜底为 150(standards.ts:83),无实际数据风险 | 前端 clamp ≥1(体验优化) |
|
||||
| D4 | 中 | 多处 `catch {}` / 原生 fetch 静默 | 标准保存/删除、条目所有操作、成果物 init/toggle、汇总加载均**静默失败无提示**;汇总失败永久「加载中...」 | 统一错误反馈;汇总加错误态 |
|
||||
| D5 | 低 | Sidebar create + ProjectView 重命名 | Enter 与 onClick/onBlur 可能**双触发**(create 双请求;重命名 Enter 后 onBlur 再 PUT) | 提交锁/防抖 |
|
||||
| D6 | 低 | Sidebar.tsx:58 vs ProjectView.tsx:78 | 两个删除 confirm 文案不一致 | 统一文案 |
|
||||
| D7 | 低 | EntryManager 搜索 | 即时搜索每击键一次请求(onChange 触发),性能隐患 | 防抖 |
|
||||
| D8 | 低 | EntryManager CSV 解析 | `split(',')` 不支持引号包裹含逗号字段 | 用 CSV 解析库 |
|
||||
| D10 | 中 | EntryManager doEditSave(ProjectView.tsx:234-247) | 编辑表单显示分支/子类型/题目字段,但保存仅提交 title/repo_url/participant(另两个字段为 undefined 被丢弃),**分支/子类型/题目修改不生效**且无提示 | doEditSave 补齐提交字段 |
|
||||
|
||||
---
|
||||
|
||||
## 14. 附:前端组件 → 文件索引
|
||||
|
||||
| 组件 | 文件 |
|
||||
|:-----|:-----|
|
||||
| App / ProtectedRoute | `web/src/App.tsx` |
|
||||
| 登录页 | `web/src/components/LoginPage.tsx` |
|
||||
| 布局 / 侧边栏 | `web/src/components/Layout.tsx`、`Sidebar.tsx` |
|
||||
| 仪表盘 | `web/src/components/Dashboard.tsx` |
|
||||
| 项目页(含 4 个 Tab 全部子面板) | `web/src/components/ProjectView.tsx`(1097 行) |
|
||||
| API 封装 | `web/src/services/api.ts` |
|
||||
| 入口 | `web/src/main.tsx` |
|
||||
|
||||
---
|
||||
|
||||
## 15. 变更记录
|
||||
|
||||
| 日期 | 版本 | 说明 |
|
||||
|:-----|:-----|:-----|
|
||||
| 2026-08-04 | v1.0 | 初版:按画面梳理功能与测试点,附已确认缺陷清单 D1-D9 |
|
||||
| 2026-08-04 | v1.1 | 新增「整体功能说明」章节(§2):系统定位、业务价值、角色、领域概念、端到端流程、评审管线、画面地图 |
|
||||
| 2026-08-04 | v1.2 | 准确性修正:删除误报 D9(difficulty 不发送);D2 改为「后端 400 被静默」(高→低);D3 改为「后端兜底 150」(中→低);新增 D10(编辑分支/子类型/题目不生效);同步更新 E8-6/E12-2/E12-3/ST3-6 预想结果与 §9.1 描述 |
|
||||
@@ -0,0 +1,336 @@
|
||||
# 05-评审流程修正方案(多次评审 + 标准拆两阶段 + 按配置拉取 + 人工构建确认 + 真实性考核)
|
||||
|
||||
> 状态:已确认(2026-08-16,2026-08-16 补人工构建确认 / 真实性考核,2026-08-19 补整体评价合成)
|
||||
> 关联需求:多次评审、评审标准拆两部分(拉取即评 A / 构建后评 B)、按配置文件拉取项目、参考 AuraSpace wiki(方案②:AI 解读代码生成项目理解文档)、自动化测试、真实性考核(声称 vs 实测交叉验证)、整体评价合成(方案A)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
现状评审一次后即 `review_done`,无路径再触发;tryBuild 失败会硬扣「实现完整度」等维度分数。用户需要:
|
||||
|
||||
1. **支持多次评审**——同一条目可重复触发评审,不能一次就结束。
|
||||
2. **评审标准拆两部分**:
|
||||
- **A 部分**:拉取代码后**即可启动**的评审维度(静态分析类)
|
||||
- **B 部分**:拉取之后、**系统构建成功后**才能完成的维度(构建可运行 / 服务可启动 / 测试确认)
|
||||
- 总分 = A 部分得分 + B 部分得分
|
||||
3. **按配置文件拉取项目**——repo_url 以 `config/teams.json` 为准。
|
||||
4. **对项目的理解参考 AuraSpace wiki(方案②,2026-08-16 确认)**——不是只读 README 组树,而是 AI 主动解读参赛代码,生成结构化「项目理解文档」作评审上下文(AuraSpace Docs Hub 的"文件索引 + AI 写作"思路)。
|
||||
5. **自动化测试参考 AuraSpace(2026-08-16 确认)**——两层:①跑参赛者自带测试(已有 tryTest)+ ②B 阶段黑盒冒烟(参考 api_integration.rs 的用户旅程)。
|
||||
|
||||
## 2. 方案(已确认)
|
||||
|
||||
### 2.1 评审标准拆分(核心)
|
||||
|
||||
**按维度整体划分,后端内置映射(标准 MD 不改):**
|
||||
|
||||
- **A 部分(拉取即评,110分)**:场景价值与技术合理性(10) / 开发范式应用(5) / 架构设计(10) / 工具使用与Skill集成深度(5) / Agent核心能力(25) / 规模与功能点(20) / 代码规范性(10) / 演示与文档(10) / AI使用日志(5) / 安全性(10)
|
||||
- **B 部分(构建后评,40分)**:实现完整度与稳定性(20) / 效果与数据(20)
|
||||
|
||||
后端在 `review-constants.ts` 新增 `B_STAGE_DIMENSION_KEYWORDS`(按维度名关键词匹配)。**当前只按赛道一实现**(赛道二/人才测评暂不考虑,维度全部归 A,后续需要再扩展):
|
||||
|
||||
```ts
|
||||
export const B_STAGE_DIMENSION_KEYWORDS = ['实现完整度', '效果与数据'] as const;
|
||||
export function isBStageDim(name: string): boolean {
|
||||
return B_STAGE_DIMENSION_KEYWORDS.some(k => name.includes(k));
|
||||
}
|
||||
```
|
||||
|
||||
说明:赛道二实测维度(开发范式设计清晰度/IDE集成深度/提效设计合理性/提效幅度/稳定性与易用性/规模与功能点与技术难度/演示与文档/AI使用日志)当前全部归 A——即使「稳定性与易用性」「提效幅度」本质依赖运行验证,也**暂不拆分**,待后续赛道二需求明确再扩展名单。
|
||||
|
||||
`parseDimensions` 解析出的每个维度增加 `stage: 'A' | 'B'` 字段(默认 A,命中 B 名单为 B)。
|
||||
|
||||
### 2.2 评审两阶段
|
||||
|
||||
```
|
||||
阶段 A(拉取即评,自动触发):
|
||||
clone → 文件发现 → 代码统计 → Agent门槛检测(GATES) → 项目理解文档(buildProjectUnderstanding)
|
||||
→ 概览(A上下文) → A部分维度子Agent → 校准(A维度) → 硬规则(A相关) → scoreA → 状态 a_done(保留 clone 目录)
|
||||
|
||||
阶段 B(构建后评,手动触发 /verify):
|
||||
校验/复用 clone 目录 → 人工构建确认(评委:构建完成/构建失败)→ tryTest
|
||||
→ tryBrowse(hasWeb 时)→ trySmoke(hasWeb 时)→ B部分维度子Agent
|
||||
→ 校准(B维度) → 硬规则(B相关) → scoreB → 合并 A+B → 迟交扣分 → finalScore → review_done(删 clone 目录)
|
||||
```
|
||||
|
||||
- **A 自动跑**:评审启动即执行 A 阶段,产出 `scoreA`。
|
||||
- **B 手动触发**:A 完成后条目处于 `a_done`,评委确认构建结果、填好 `service_url` 后点「启动系统验证」→ 执行 B 阶段。
|
||||
- **总分 = scoreA + scoreB**,按各自 maxScore 归一,相加后按赛道总分上限封顶。
|
||||
- **人工构建确认(2026-08-16 确认,2026-08-18 扩展至单阶段)**:系统不再自动 tryBuild(参赛者仓库构建环境千差万别,自动构建失败 ≠ 项目差)。评委基于 clone 目录人工确认构建是否成功,结果以 `build_status: 'done' | 'failed'` 传入 /verify。
|
||||
- **赛道一(B 阶段)**:`/verify` 请求体携带 build_status。
|
||||
- **赛道二/人才测评(单阶段)**:条目 `build_status` 字段(创建/编辑时设置,default ''),评审时读取——`done`/`failed` 跳过自动 tryBuild 并注入人工确认证据;`''` 仍自动构建(兼容旧行为)。避免 npm install 等重依赖安装超时被误判"构建失败"。
|
||||
- **构建失败**:注入构建失败证据(如实呈现给 AI)+ B 部分维度经硬规则封顶(可能 0 分),A 部分得分与评审报告保留。
|
||||
- **构建完成且判定有 Web(hasWeb)**:必须填 `service_url`,B 阶段执行 tryBrowse + trySmoke。
|
||||
- **构建完成且无 Web(CLI 形态)**:B 阶段执行 CLI 启动检查(tryStart)+ tryTest,不冒烟。
|
||||
|
||||
### 2.3 状态机(新增 a_done / verifying)
|
||||
|
||||
| 状态 | 含义 | 可操作 |
|
||||
|---|---|---|
|
||||
| `pending` | 待评审 | start(A 阶段) |
|
||||
| `queued`/`cloning`/`analyzing` | A 阶段执行中 | cancel |
|
||||
| `a_done` | **A 完成,等待系统验证** | `/verify`(B 阶段)|
|
||||
| `verifying` | B 阶段执行中 | cancel |
|
||||
| `review_done`/`admin_reviewed` | 完成 | start(重新评审)|
|
||||
| `clone_fail`/`analysis_fail`/`failed` | 失败 | retry |
|
||||
|
||||
**崩溃恢复(index.ts:40 改善)**:`queued/cloning/analyzing` → 重置 `pending`;新增 `verifying` → 重置 `a_done`(保留 A 结果,不丢分)。`a_done` 是稳定等待态,**不参与**卡死重置。
|
||||
|
||||
### 2.4 多次评审
|
||||
|
||||
- **start 放开**:`pending` / `review_done` / `admin_reviewed` 均可触发。
|
||||
- **重评时重新锁定最新标准**(问题2改善):重评启动时重新 `resolveStandard` + `parseDimensions` 更新 `standard_snapshot` 与 `pass_line`——每次评审都锁当时最新标准,历史版本留在 `review_snapshots`。
|
||||
- **重评时实时按配置解析 repo_url**(问题3改善):启动时重新 `resolveRepoUrlFromConfig` 覆盖 `entry.repo_url`(配置优先)。
|
||||
- 每次评审写 `review_snapshots`,`attempt+1`,排名取最新 `final_score`。
|
||||
|
||||
### 2.5 阶段 B 触发端点(问题5改善)
|
||||
|
||||
新增 `POST /:entryId/verify`,请求体 `{ build_status: 'done' | 'failed' }`:
|
||||
- 校验 `status === 'a_done'`,否则 409
|
||||
- **校验 `build_status` 合法**(done/failed),否则 400 `构建结果必须为 done 或 failed`
|
||||
- **hasWeb 判定**(§2.7.1 三态)且 `build_status='done'` 且 hasWeb → **校验 `service_url` 非空**,否则 400 `请先填写服务地址后再启动系统验证`;构建失败或无 Web(CLI 形态)→ 不强制 service_url
|
||||
- 校验通过 → `startReviewB(entryId, buildStatus)` 执行 B 阶段
|
||||
- **`startReviewB` 复用 queue 机制,受 MAX_CONCURRENT 并发限制**(避免多个 a_done 同时 verify 打爆 DeepSeek)
|
||||
|
||||
**本机服务地址放行(2026-08-16)**:`service_url` 默认拒绝本机/内网地址(SSRF 防护)。本地评测需验证本地起服务的参赛作品时,以 `ALLOW_LOCAL_SERVICE_URL=1` 启动(配合 `SSRF_DNS_CHECK=off`)放行 127.0.0.1/localhost/内网地址——**仅测试模式,生产不得开启**。
|
||||
|
||||
前端:`a_done` 状态显示「待系统验证」徽标 + 操作区「确认构建完成」「构建失败」两个按钮(无第三按钮);点「构建完成」且 hasWeb 时校验 service_url 必填。
|
||||
|
||||
### 2.5.1 服务地址可编辑(审核补充:a_done 可填 service_url)
|
||||
|
||||
**放开 `PUT /:entryId` 的状态限制**:允许 `pending` / `a_done` 编辑(其中 service_url 是核心用途)。`review_done`/`admin_reviewed` 仍不可编辑(重评走 start 重置 pending 再编辑)。
|
||||
- 这样「B 前置条件」成立:评委在 a_done 时填入 service_url,然后触发 verify。
|
||||
|
||||
### 2.5.2 B 阶段校准注入 A 维度分(审核补充:修复跨阶段矛盾检测丢失)
|
||||
|
||||
分阶段评审后,`computeCalibration` 的 L1 跨维度语义矛盾检测在 B 阶段只见 B 维度,发现不了"A 维度低分 vs B 维度高分"的矛盾(如规模 2/20 但效果与数据 20/20)。
|
||||
|
||||
**改善**:B 阶段校准 prompt 中注入 A 阶段各维度得分作为参考(A 部分维度列表 + score),供 L1 矛盾判定。A 阶段校准保持现状(只见 A 维度)。
|
||||
|
||||
### 2.5.3 B 阶段 clone 目录校验(审核补充:目录过期)
|
||||
|
||||
A 阶段保留 clone 目录;B 阶段开始时**校验目录存在且含文件**:
|
||||
- 目录完整 → 直接复用(A/B 评同一份代码)
|
||||
- 目录缺失/残缺 → 返回明确错误 **`评审上下文已过期,请重新评审`**(不自动重建),避免 A 评旧代码、B 评新代码相加导致分不对应。评委需重新 start(完整重评)。
|
||||
|
||||
### 2.5.4 重解析 repo_url 撞 UNIQUE 约束(审核补充)
|
||||
|
||||
重评/评审启动时 `resolveRepoUrlFromConfig` 覆盖 repo_url,若与同项目其他条目冲突会抛 `UNIQUE(project_id, repo_url)`。
|
||||
|
||||
**改善**:start 端点对 UPDATE 包 try/catch,冲突时返回 409 `仓库地址与已有条目冲突`,不崩溃。
|
||||
|
||||
### 2.5.5 A 阶段写部分 ai_report(审核补充:a_done 可看半程报告)
|
||||
|
||||
A 阶段完成时写入**部分 ai_report**(含 A 维度 + scoreA),`a_done` 状态可导出半程 PDF 报告;B 完成后再覆盖完整 ai_report(A+B 全维度)。
|
||||
|
||||
### 2.6 按配置拉取
|
||||
|
||||
- `teams-config.ts` 已实现(`resolveRepoUrlFromConfig`),验证 + 单测。
|
||||
- 评审启动时也调用它实时解析 repo_url(覆盖 DB 旧值)。
|
||||
|
||||
### 2.7 项目理解文档(方案②,AI 解读代码生成文档)
|
||||
|
||||
> 2026-08-16 用户确认:参考 AuraSpace Docs Hub 的"文件索引 + AI 写作",**不是只读 README 组树**。
|
||||
> 已查证 AuraSpace 代码:`upload_document` 上传文件存 `[FILE]:` 标记 → `embedding_worker` 按扩展名提取文本 → `DocsEditor` AI Side Panel 生成/改写文档。核心是**对项目文件做 AI 加工**。
|
||||
|
||||
**新增 `buildProjectUnderstanding(dir)` 步骤**(A 阶段,代码统计之后、概览之前,1 次 AI 调用):
|
||||
|
||||
```
|
||||
1. 输入:已发现的 .md 文档 + 源码文件清单 + 代码统计(已有 discoverFiles/countCodeStats)
|
||||
2. 提取代表性内容作 context:README、目录结构、入口文件、构建配置(受 token 限制截断)
|
||||
3. AI 调用 1 次,产出结构化「项目理解文档」(JSON):
|
||||
- project 定位与用途
|
||||
- 技术栈
|
||||
- 架构 / 模块划分
|
||||
- 核心功能点(列表,供黑盒冒烟推断路径)
|
||||
- 数据流 / 运行方式
|
||||
- **运行形态(`"web" | "cli"`,供 B 阶段判定是否必须填 service_url 与是否冒烟)**
|
||||
4. 结果落库 entries.project_understanding(latest),供:
|
||||
- A 阶段概览 prompt 注入(替代原知识树)
|
||||
- B 阶段子 Agent 上下文
|
||||
- B 阶段黑盒冒烟(§2.8)的功能路径依据
|
||||
- hasWeb 判定(§2.7.1)
|
||||
```
|
||||
|
||||
**废弃原 `buildKnowledgeTree`**(只组 README 树,不解读代码,与用户意图不符,删除不保留)。
|
||||
|
||||
### 2.7.1 运行形态与 hasWeb 判定(审核补充,2026-08-16 增强为三态)
|
||||
|
||||
- `buildProjectUnderstanding` 产出 `运行形态: "web" | "cli"`。
|
||||
- **三态确定性探测(detectWebMode)**,命中任一 Web 信号 → `web`;命中 CLI 入口信号 → `cli`;两者皆无 → `ambiguous`:
|
||||
- **Web 信号**:index.html、前端构建(vite/webpack/parcel/next/react/vue)、**Python Web 框架(FastAPI/Flask/Django/Streamlit/Dash)**、`templates/`+`static/` 目录、Go Web(net/http/gin/echo)、requirements/pyproject 含 Web 框架依赖
|
||||
- **CLI 信号**:package.json `bin`、Python `__main__`/argparse(且无 Web 框架)、Go `func main` 无 http
|
||||
- **源码扫描有界**:扩展名过滤 + 深度/文件数/大小上限,跳过 node_modules/.venv/dist/build 等
|
||||
- **三态判定(resolveWebMode)**:
|
||||
- `web`(确定性)→ 直接判 web,`confidence=high`
|
||||
- `cli`(确定性)→ 直接判 cli,`confidence=high`
|
||||
- `ambiguous`(无信号)→ **采信 AI 理解文档的「运行形态」**,`confidence=low`;AI 也没有 → 默认 cli,`confidence=low, source=default`
|
||||
- 结果含 `confidence/source/crossMismatch`,注入 B 阶段 projectContext 供评审参考(审计可追溯)
|
||||
- `hasWeb=true`(含低置信度 AI 判定)时:B 阶段执行 tryBrowse + trySmoke(此时若 service_url 为空,/verify 会 400 拦截)。
|
||||
|
||||
### 2.8 自动化测试(参考 AuraSpace)
|
||||
|
||||
> 2026-08-16 用户确认。AuraSpace 自动化测试 = CI 分层(backend/frontend 各自 check/test/build,`.woodpecker.yml`)+ **黑盒冒烟用户旅程**(`api_integration.rs`:health→login→CRUD,逐断言 HTTP 状态)。
|
||||
> 评审场景不能照抄手写路径(面对陌生项目),移植为两层:
|
||||
|
||||
**① 跑参赛者自带测试(已有 tryTest,B 阶段执行)**
|
||||
- 参考 AuraSpace `backend-test`(跑项目自己的测试):clone 目录真跑 pytest/jest/go test 等,解析通过率/覆盖率。
|
||||
- 软证据:环境失败/工具缺失中性不扣分;确定性测试证据强绑定(AI 评分必须据此)。
|
||||
|
||||
**② B 阶段黑盒冒烟(新增 `trySmoke`,参考 `api_integration.rs` 用户旅程)**
|
||||
- AuraSpace 冒烟是手写路径(针对已知系统);评审面对陌生项目 → **路径由 AI 全程引导点击**(2026-08-16 用户选择):
|
||||
```
|
||||
1. 冒烟计划:从 entries.project_understanding(§2.7)的「核心功能点」取 2-3 条路径目标(声称清单 = 核心功能点,逐条对齐,见 §2.9)
|
||||
2. 引导循环:打开 service_url → 采集「DOM 文本 + 可交互元素清单」(DeepSeek 文本模型,非截图)
|
||||
→ AI 输出动作 JSON(click selector / type field / navigate url / done)→ puppeteer 执行
|
||||
→ 采集新快照 → 循环,直到目标完成或达到上限
|
||||
3. 硬上限:15 步 / 120s(SMOKE_MAX_STEPS / SMOKE_WATCHDOG_MS,防 AI 原地打转)
|
||||
4. 安全约束:AI 引导只做导航/查看类动作;表单填充一律使用假数据(防误删/误提交参赛者数据)
|
||||
5. 收尾:逐条目标标注 ✅可达 / ❌不可达(含页面可达但路径不可达 = 项目证据,扣分依据)
|
||||
→ 产出冒烟证据(对照表 + coreReachabilityRatio = 可达目标数/总目标数)
|
||||
```
|
||||
- 与 tryTest 一致:**软证据**——浏览器不可用/页面加载失败属环境问题 → 中性不扣分(不证明项目差)。
|
||||
- 复用 tryBrowse 基建(puppeteer-core、自动检测 Chrome/Edge、看门狗、SSRF 二次校验 revalidateHost)。
|
||||
|
||||
**构建失败分支(2026-08-16 确认)**:评委确认构建失败 → 跳过 tryBrowse/trySmoke,注入构建失败证据 + **照跑 tryTest**(tryTest 与构建解耦,构建失败≠测试不可跑,cobol-java 实证)。
|
||||
|
||||
### 2.9 真实性考核(声称 vs 实测对照,2026-08-16 确认)
|
||||
|
||||
> 用户要求:评审不能只信文档声称,要交叉验证「参赛者声称的能力 vs 系统实测结果」。**不新增硬评分要素**,靠确定性证据注入。
|
||||
|
||||
**机制(四步落地)**:
|
||||
1. **声称清单**:`buildProjectUnderstanding` 产出「核心功能点[3-8个]」落库(即项目文档声称的功能)。
|
||||
2. **实测路径**:trySmoke 冒烟目标 = 核心功能点逐条派生(§2.8),声称与实测天然对齐。
|
||||
3. **逐条对照**:冒烟产出对照表(声称功能 → ✅实测可达 / ❌实测不可达 / ⚪环境不可用)。
|
||||
4. **确定性注入**:`coreReachabilityRatio = 可达目标数 / 总目标数`,按 tryTest 同一措辞注入「实现完整度与稳定性」「效果与数据」prompt:
|
||||
> 「确定性证据,评分必须据此,不得忽略或低估」
|
||||
另注入提示:理解文档声称功能与冒烟实测不符 → 需甄别文档/日志真实性(如声称实现了 X 但页面无此功能 → 视为虚假声称扣分)。
|
||||
|
||||
**边界(软证据语义)**:
|
||||
- 页面可达但路径不可达(AI 明确标记 unreached)→ **项目证据**,AI 可据此扣分(证明声称功能缺失)
|
||||
- 浏览器不可用 / 服务地址打不开(环境问题)→ **中性**,不扣分
|
||||
- 冒烟中断 / AI 决策失败 / 步数预算耗尽 → **中性**(未验证目标一律 `skipped`,不视为项目证据,不得默认扣分)
|
||||
- 构建失败 → 不冒烟,注入构建失败证据
|
||||
|
||||
**层次对应**:tryTest=CI backend-test(跑项目自己的测试);trySmoke=黑盒冒烟(用户旅程验证系统能跑通)。
|
||||
|
||||
## 2.10 整体评价合成(方案A,2026-08-19)
|
||||
|
||||
**动机**:用户希望评审结果有一段"有判断力的整体评价"(定位/亮点/不足/分数解读),而非散装的 overview + 各维度 comment。
|
||||
|
||||
**机制**:校准 + 硬规则之后(分数已定),用 1 次 LLM 调用 `synthesizeOverall` 合成点评式整体评价,写入 `ai_report.overall`:
|
||||
|
||||
```json
|
||||
{ "highlights": [{"point": "亮点内容", "review": "点评:价值/强度/局限"}],
|
||||
"weaknesses": [{"point": "不足内容", "review": "点评:影响/严重程度"}],
|
||||
"verdict": "总评1-2句(解读得分与真实水平)" }
|
||||
```
|
||||
|
||||
- **项目总览负责中立描述,overall 只做点评**(不重复定位)
|
||||
- **输入全是真实证据**:overview + 各维度(分/评语) + 确定性证据(构建/测试/IDE贡献点/演示视频/冒烟)+ 校准说明;prompt 显式"只依据输入,禁止编造"
|
||||
- 兼容旧格式(highlights/weaknesses 为字符串数组 → point,review 空)
|
||||
- **失败非致命**:LLM 失败/超时 → overall=null,不影响评分
|
||||
- **落点**:赛道一 B 阶段尾(executeReviewB)与赛道二单阶段尾(executeReview)各合成一次;A 阶段(a_done)不合成(分数未定)
|
||||
- **展示**:前端条目详情"整体评价"段(亮点点评绿/不足点评红/总评)+ PDF 报告"整体评价"块
|
||||
- **成本**:约 +15-30s/次评审
|
||||
|
||||
**边界说明**:整体评价是 AI 对真实证据的**叙事性解读**,可靠性同各维度分(LLM 主观),但其引用的事实(证据/得分)均为真实数据,幻觉面小。
|
||||
|
||||
## 2.11 评审可信度改进(2026-08-19,多次聚合 + 可验证能力三档 + 确定性校准)
|
||||
|
||||
### 2.11.1 多次评审聚合排名
|
||||
- 单次评审总分仍存 `entries.final_score`(语义不变),但**排名/汇总改用最近 N 次(默认 3)`review_snapshots.score` 的中位数**(N=2 平均、N=1 单次)
|
||||
- 聚合前提:各快照 `standard_snapshot` 一致,否则退化为单次分(分数不可比)
|
||||
- **评审次数 <3 的 entry 标记"初评(未达聚合样本)"**,排名区分正式分/初评分,避免"少评=占便宜"
|
||||
- 详情/PDF 维度表用跨快照**维度级聚合**(`averageDimensions` 两两合并 reduce),展示每维"历次分差"
|
||||
- 历史快照 backfill:从 ai_report.totalScore best-effort 回填(旧数据缺则留 NULL)
|
||||
|
||||
### 2.11.2 可验证能力三档(效果/提效类维度)
|
||||
效果/提效类维度(名含 效果/数据/提效/效率/量化)在**校准之前**判档:
|
||||
- **A 档**:有基准证据(`entries.benchmark_json` status=done)→ 不封顶,客观数字
|
||||
- **B 档**:有效果证据(测试通过 `testsPassed>0` 或覆盖率非 null)→ 不封顶
|
||||
- **C 档**:数据缺位 → 封顶 `maxScore*0.3`,note="数据缺位(未证明),非无效",前端/PDF 渲染说明
|
||||
- **构建成功 ≠ 效果可验证**,仅构建不豁免 C 档
|
||||
- 基准证据**按 entry 落库**(非 env 变量,防 MAX_CONCURRENT=3 并发串数据)
|
||||
|
||||
### 2.11.3 校准确定性化
|
||||
- L1 新增 `detectStructuralContradictions`(standard-utils.ts):仅**证据性矛盾**触发——有测试/基准证据但效果维度≈0 → under;效果高分而实现全低 → over
|
||||
- **禁止**用"实现高分+效果无数据"当 under(缺数据归 C 档,L1 不得据此上抬效果维度)
|
||||
- `computeCalibration` 硬规则:**效果维度的 under 一律丢弃**(效果维度只降不升,诚实由三档封顶负责)
|
||||
- LLM contradictions 降级为补充,不单独承担 L1
|
||||
|
||||
### 2.11.4 overall 中性边界
|
||||
- 测试"0/0"按 `test-runner` summary 区分:含"中性"(环境失败/工具不可用/执行异常)→ 标 `[中性证据]`;不含(如"未检测到测试框架配置"=真缺测试)→ 真实弱点
|
||||
- synthesizeOverall prompt 约束:`[中性证据]` 内容不得列为不足或负面评价
|
||||
|
||||
### 2.11.5 演示视频 URL 检测
|
||||
- `detectDemoVideo` 除文件扫描外,扫**根目录 README 及根级 docs/*.md** 中的 bilibili/youtube/douyin 视频链接
|
||||
- 弱证据降权:source='url' 时评语注明"外部链接,未核验内容";本地文件(source='file')为强证据优先
|
||||
|
||||
### 2.11.6 决赛圈基准(加分项)
|
||||
- 框架 `server/src/services/benchmark.ts`(design-only):`runBenchmark(dir, seeds, detect)`,检出率与基线**分开呈现**,不做差值
|
||||
- seed 库赛前临时生成、不公开(Goodhart 已知上限),语言覆盖 JS/CSS/Java/SQL
|
||||
- 结果写 `entries.benchmark_json`,评审判档读它
|
||||
|
||||
### 2.11.7 人机标定(可信度天花板)
|
||||
- 多次聚合=统计稳健、三档=语义诚实、中性/矛盾=内部一致;**绝对准确**只有人工专家对照才能回答
|
||||
- 流程见 `docs/design/06-人机标定方案.md`(Task 10):样本→专家独立打分→偏差基线→是否修正系数
|
||||
|
||||
## 3. 数据模型改动
|
||||
|
||||
| 位置 | 改动 |
|
||||
|---|---|
|
||||
| `entries` 表 | migration 新增 `score_a REAL DEFAULT 0`、`score_b REAL DEFAULT 0`、`stage_b_status TEXT DEFAULT ''`(''=未执行 / done / failed / skipped)、`project_understanding TEXT DEFAULT ''`(§2.7 项目理解文档 JSON,A 每次重评覆盖) |
|
||||
| `review_snapshots` | migration 新增 `score REAL`(含迟交扣分的 final_score,多次评审聚合用,2026-08-19) |
|
||||
| `entries` 表 | migration 新增 `benchmark_json TEXT DEFAULT ''`(决赛圈基准证据按 entry 落库,2026-08-19) |
|
||||
| `standards.max_score` | **不改**(仅上传校验上限,非实际满分,不影响出分) |
|
||||
|
||||
## 4. 影响面
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `server/src/services/review-constants.ts` | 新增 `B_STAGE_DIMENSION_KEYWORDS` + `isBStageDim` |
|
||||
| `server/src/routes/standards.ts` | `parseDimensions` 解析 stage |
|
||||
| `server/src/routes/entries.ts` | start 放开 + 重评重锁标准/重解析 repo_url + `/verify` 接收 build_status + hasWeb 校验 |
|
||||
| `server/src/services/review.service.ts` | `executeReview` 拆 `executeReviewA`/`executeReviewB` + `buildProjectUnderstanding`(含运行形态)+ hasWeb 确定性探测 + 人工构建确认 + scoreA/scoreB 落库 + `startReviewB` + 冒烟证据注入 |
|
||||
| `server/src/index.ts` | 崩溃恢复增加 `verifying → a_done` |
|
||||
| `server/src/db.ts` | migration 加 score_a/score_b/stage_b_status/project_understanding |
|
||||
| `server/src/services/smoke.ts`(新) | `trySmoke`:AI 从理解文档推断冒烟计划 + puppeteer 执行 + 对照表/coreReachabilityRatio |
|
||||
| `server/src/__tests__/*` | stage 解析 / start 放开 / verify build_status / hasWeb / 理解文档运行形态 / 冒烟计划 / teams-config 单测 |
|
||||
| `web/src/components/ProjectView.tsx` | 重新评审按钮 + 确认构建完成/构建失败按钮 + A/B 分展示 + a_done/verifying 徽标 |
|
||||
|
||||
## 5. 用户故事补充(问题审查驱动)
|
||||
|
||||
| 用户故事 | 验收 |
|
||||
|---|---|
|
||||
| US-11 迭代重评 | 已 review_done 可重新 start,attempt+1,快照存档,排名取最新 |
|
||||
| US-12 A/B 拆分 | A 自动跑出 scoreA 停 a_done;verify 触发 B;tryBuild 失败 B 封顶 0 分,A 分保留 |
|
||||
| US-13 状态恢复 | verifying 崩溃 → 重置 a_done 保留 A 结果;a_done 不参与卡死重置 |
|
||||
| US-14 重评标准 | 重评锁当时最新标准快照,历史留快照表 |
|
||||
| US-15 B 前置条件 | 构建完成且判定有 Web 未填 service_url 触发 verify → 400 明确提示;构建失败或无 Web 可跳过 service_url |
|
||||
| US-16 配置拉取 | 评审启动按配置实时解析 repo_url |
|
||||
| US-17 项目理解文档 | A 阶段 AI 解读代码生成结构化理解文档落库(含运行形态),注入概览与 B 阶段上下文 |
|
||||
| US-18 黑盒冒烟 | B 阶段基于理解文档冒烟 2-3 条核心功能路径,证据注入实现完整度/效果与数据;环境失败中性不扣分 |
|
||||
| US-19 人工构建确认 | a_done 提供「确认构建完成/构建失败」两按钮,结果注入 B 阶段证据;构建失败照跑 tryTest 不冒烟 |
|
||||
| US-20 真实性考核 | 声称核心功能点逐条冒烟对照,coreReachabilityRatio 确定性注入效果维度;环境失败中性不扣分 |
|
||||
|
||||
## 5.1 多轮审核结论(2026-08-16,已纳入方案)
|
||||
|
||||
| # | 问题 | 处置 |
|
||||
|---|---|---|
|
||||
| 1 | a_done 无法编辑 service_url(PUT 仅限 pending) | §2.5.1 放开 PUT 允许 pending/a_done |
|
||||
| 2 | A/B 分阶段校准丢失跨阶段语义矛盾(L1) | §2.5.2 B 阶段校准注入 A 维度分 |
|
||||
| 3 | verifying 崩溃后 clone 目录残缺 | §2.5.3 B 阶段校验目录并重建 |
|
||||
| 4 | 重解析 repo_url 撞 UNIQUE 约束 | §2.5.4 start 端点 try/catch 返回 409 |
|
||||
| 5 | a_done 时能否看半程报告 | §2.5.5 A 阶段写部分 ai_report |
|
||||
| 6 | verify 需受并发限制 | §2.5 startReviewB 复用 queue |
|
||||
| 7 | 自动 tryBuild 误伤(环境差异≠项目差) | §2.2 人工构建确认:/verify 传 build_status,done/failed 两分支 |
|
||||
| 8 | 冒烟对陌生项目路径不可知 | §2.8 路径由 AI 引导 + 15步/120s 硬上限 + 假数据约束 |
|
||||
| 9 | 只信文档声称无法验证真实性 | §2.9 声称清单 vs 冒烟实测对照 + coreReachabilityRatio 确定性注入 |
|
||||
|
||||
## 6. 验证方式
|
||||
|
||||
- `cd server && npx tsc` 编译通过
|
||||
- `cd server && npm test` 全绿(现有 271 + 新增:stage 解析 / start 放开 / verify build_status / hasWeb / 项目理解文档运行形态 / 冒烟计划 / teams-config)
|
||||
- `cd web && npm run build` 通过
|
||||
- 手工冒烟:已 review_done 条目重新 start → A 完成停 a_done(含理解文档+部分报告)→ 确认构建结果(done/failed)→ 填 service_url(hasWeb 时)触发 verify → B 完成(tryTest + tryBrowse + 黑盒冒烟 + B 维度 + 真实性对照)→ 总分=A+B → 快照追加 → 排名取最新
|
||||
@@ -0,0 +1,72 @@
|
||||
# 06-人机标定方案(AI 分数 vs 人工专家偏差基线)
|
||||
|
||||
> 状态:待执行(2026-08-19 定义)
|
||||
> 关联:多次评审聚合解决**统计稳健**、可验证能力三档解决**语义诚实**、确定性校准解决**内部一致**;
|
||||
> 本方案回答"AI 给的分到底准不准"——**绝对准确**,只能靠人工专家对照。
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么需要人机标定
|
||||
|
||||
系统的所有机制保证的是**相对可信**:
|
||||
- 多次聚合 → 分数可复现(73 不是骰子的一次结果)
|
||||
- 三档封顶 → 效果维度语义诚实("数据缺位"不等于"无效")
|
||||
- 确定性校准 → 内部自洽(效果维度只降不升)
|
||||
|
||||
但没有任何机制能证明 **"78 分" 对作品真实质量来说是偏高还是偏低**。只有拿 AI 分数与人工专家打分对照,得到**偏差基线**,才能回答:
|
||||
- AI 总分系统性偏高 +5?→ 排名可能被虚高分数带偏
|
||||
- 效果类维度系统性偏高 +8?→ 该维度 AI 有系统性乐观偏差
|
||||
|
||||
## 2. 流程(五步)
|
||||
|
||||
### Step 1: 选样本
|
||||
- 从已评审作品选 **2-3 个代表**:高分 / 中分 / 低分各一(如净码特攻 73、逸飞冲天 63、任选一个低分作)
|
||||
- 样本必须覆盖不同赛道(至少一个赛道一、一个赛道二),因为标准不同
|
||||
|
||||
### Step 2: 构造证据包(脱敏)
|
||||
- 每个样本生成只含**证据**的材料:overview + 各维度评语 + 确定性证据(构建/测试/贡献点/视频/冒烟)+ 代码统计
|
||||
- **不含 AI 分数**——防止锚定效应
|
||||
|
||||
### Step 3: 专家独立打分
|
||||
- 1-2 位专家按**同一份标准 MD** 独立打分(不看对方、不看 AI 分)
|
||||
- 记录每个维度的分 + 总分
|
||||
|
||||
### Step 4: 偏差分析
|
||||
对每个样本、每个维度、总分计算:
|
||||
|
||||
```
|
||||
偏差 = AI分 - 人工分
|
||||
```
|
||||
|
||||
汇总表:
|
||||
|
||||
| 作品 | AI 总分 | 人工总分 | 总分偏差 | 效果类维度偏差 | 实现类维度偏差 |
|
||||
|---|---|---|---|---|---|
|
||||
| 净码特攻 | 73 | ? | ? | ? | ? |
|
||||
| 逸飞冲天 | 63 | ? | ? | ? | ? |
|
||||
| 低分样本 | ? | ? | ? | ? | ? |
|
||||
|
||||
### Step 5: 决策
|
||||
- **偏差 < 阈值(如 ±5 分)且方向不一致** → 维持现状,记录结论:"AI 分在排名意义(相对序)上可信"
|
||||
- **系统性偏差**(同方向、同维度)→ 二选一:
|
||||
- 加**修正系数**(如效果类维度 ×0.85),但**不自动改分**(避免双重修正,先人工复核修正后分数是否合理)
|
||||
- 或声明"分数需 +X 解读",保持原分但文档说明
|
||||
- 输出:`docs/design/06-人机标定结果.md`
|
||||
|
||||
## 3. 关键约束
|
||||
|
||||
- **独立打分**:专家只看到证据包,不看 AI 分,否则锚定会污染基线
|
||||
- **标准一致**:专家与 AI 用同一份标准 MD 快照
|
||||
- **样本要够**:2-3 个是起点,偏差方向若摇摆不定,加样本到 5 个
|
||||
- **不自动改分**:标定只产出"解读基线",不擅自改历史分数(历史分是快照,改了就破坏可审计性)
|
||||
|
||||
## 4. 何时做
|
||||
|
||||
- **赛前**(正式数据收集开始前)做一轮 → 若有系统性偏差,调整评分规则后再开赛
|
||||
- **赛初**(前 3-5 个作品评完后)复验一轮 → 确认规则稳定
|
||||
- 之后每届开赛前重复(AI 模型/标准变了,基线会漂)
|
||||
|
||||
## 5. 验收
|
||||
|
||||
- 交付 `06-人机标定结果.md`:含样本、证据包摘要、双打分、偏差表、决策
|
||||
- 若加修正系数:必须附"修正后分数 vs 人工分"对比,证明修正有效
|
||||
Reference in New Issue
Block a user