forked from hangshuo652/aurak
0a9588abb7
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
490 lines
11 KiB
Markdown
490 lines
11 KiB
Markdown
# L1人才育成评估系统实施计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 实现L1人才育成评估系统,支持五门课程考核、多知识组关联、加权计分、证书生成
|
||
|
||
**Architecture:** 基于现有AuraK评估系统(LangGraph状态机)扩展,新增模板多知识组支持、维度计分、证书功能
|
||
|
||
**Tech Stack:** NestJS + TypeORM + LangGraph + React
|
||
|
||
---
|
||
|
||
## Phase 1: 模板扩展(P0)
|
||
|
||
### Task 1.1: 扩展 AssessmentTemplate 实体
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/entities/assessment-template.entity.ts:1-80`
|
||
|
||
**Step 1: Add new columns**
|
||
|
||
```typescript
|
||
// Add after existing columns (line ~79)
|
||
@Column({ type: 'simple-json', name: 'linked_group_ids', nullable: true })
|
||
linkedGroupIds: string[];
|
||
|
||
@Column({ type: 'simple-json', name: 'weight_config', nullable: true })
|
||
weightConfig: {
|
||
prompt: number;
|
||
other: number;
|
||
};
|
||
|
||
@Column({ type: 'simple-json', name: 'difficulty_config', nullable: true })
|
||
difficultyConfig: {
|
||
standard: number;
|
||
advanced: number;
|
||
specialist: number;
|
||
};
|
||
|
||
@Column({ type: 'int', name: 'question_count_min', default: 8 })
|
||
questionCountMin: number;
|
||
|
||
@Column({ type: 'int', name: 'question_count_max', default: 10 })
|
||
questionCountMax: number;
|
||
|
||
@Column({ type: 'int', name: 'passing_score', default: 90 })
|
||
passingScore: number;
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/entities/assessment-template.entity.ts
|
||
git commit -m "feat: extend AssessmentTemplate with multi-group and weight config"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.2: 创建数据库迁移
|
||
|
||
**Files:**
|
||
- Create: `server/src/migrations/XXXXXX-add-template-extensions.sql`
|
||
|
||
**Step 1: Write migration**
|
||
|
||
```sql
|
||
ALTER TABLE assessment_templates
|
||
ADD COLUMN linked_group_ids TEXT,
|
||
ADD COLUMN weight_config TEXT,
|
||
ADD COLUMN difficulty_config TEXT,
|
||
ADD COLUMN question_count_min INTEGER DEFAULT 8,
|
||
ADD COLUMN question_count_max INTEGER DEFAULT 10,
|
||
ADD COLUMN passing_score INTEGER DEFAULT 90;
|
||
```
|
||
|
||
**Step 2: Test migration**
|
||
|
||
```bash
|
||
cd server && npx typeorm query "SELECT linked_group_ids, weight_config FROM assessment_templates LIMIT 1"
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add server/src/migrations/
|
||
git commit -m "db: add template extension columns"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.3: 更新 TemplateService CRUD
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/services/template.service.ts`
|
||
|
||
**Step 1: Add linkedGroupIds handling**
|
||
|
||
```typescript
|
||
// In create() method, add:
|
||
if (createTemplateDto.linkedGroupIds) {
|
||
template.linkedGroupIds = createTemplateDto.linkedGroupIds;
|
||
}
|
||
if (createTemplateDto.weightConfig) {
|
||
template.weightConfig = createTemplateDto.weightConfig;
|
||
}
|
||
// ... other fields
|
||
```
|
||
|
||
**Step 2: Verify compilation**
|
||
|
||
```bash
|
||
cd server && npx tsc --noEmit
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/services/template.service.ts
|
||
git commit -m "feat: support linkedGroupIds in TemplateService"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 2: 评估流程适配(P0)
|
||
|
||
### Task 2.1: 修改 QuestionGenerator 支持多知识组
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/assessment.service.ts:266-368` (startSession method)
|
||
|
||
**Step 1: Support multi-group content retrieval**
|
||
|
||
```typescript
|
||
// In startSession(), replace single content retrieval with multi-group:
|
||
private async getMultiGroupContent(
|
||
groupIds: string[],
|
||
userId: string,
|
||
tenantId: string,
|
||
): Promise<string> {
|
||
const contents: string[] = [];
|
||
for (const groupId of groupIds) {
|
||
const files = await this.groupService.getGroupFiles(groupId, userId, tenantId);
|
||
const content = files.map(f => f.content).join('\n\n---\n\n');
|
||
contents.push(content);
|
||
}
|
||
return contents.join('\n\n');
|
||
}
|
||
```
|
||
|
||
**Step 2: Pass content with dimension tag**
|
||
|
||
```typescript
|
||
// In getSessionContent, add dimension tag:
|
||
// === Document: [工作能力] ===
|
||
// ...content...
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/assessment.service.ts
|
||
git commit -m "feat: support multi-group content retrieval"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.2: Generator 输出维度信息
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/graph/nodes/generator.node.ts:176-182`
|
||
|
||
**Step 1: Add dimension field**
|
||
|
||
```typescript
|
||
const mappedNewQuestions = newQuestions.map((q: any) => ({
|
||
id: (existingQuestions.length + 1).toString(),
|
||
questionText: q.question_text,
|
||
keyPoints: q.key_points,
|
||
difficulty: q.difficulty,
|
||
basis: q.basis,
|
||
dimension: q.dimension || this.inferDimension(knowledgeBaseContent), // NEW FIELD
|
||
}));
|
||
|
||
private inferDimension(content: string): string {
|
||
// Detect dimension from content tags
|
||
if (content.includes('[提示词]')) return 'prompt';
|
||
if (content.includes('[LLM]')) return 'llm';
|
||
// ... etc
|
||
}
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/graph/nodes/generator.node.ts
|
||
git commit -m "feat: generator outputs dimension field"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 3: 评分逻辑(P0)
|
||
|
||
### Task 3.1: DimensionScores 计算
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/assessment.service.ts:600-630` (score calculation)
|
||
|
||
**Step 1: Calculate dimension scores**
|
||
|
||
```typescript
|
||
// Replace simple weighted score with dimension-aware calculation:
|
||
const calculateDimensionScores = (questions: any[], scores: Record<string, number>) => {
|
||
const dimensionScores: Record<string, number> = {
|
||
prompt: 0, llm: 0, ide: 0, devPattern: 0, workCapability: 0
|
||
};
|
||
const dimensionCounts: Record<string, number> = { prompt: 0, llm: 0, ide: 0, devPattern: 0, workCapability: 0 };
|
||
|
||
questions.forEach((q, idx) => {
|
||
const dim = q.dimension || 'workCapability';
|
||
dimensionScores[dim] += scores[q.id || idx.toString()] || 0;
|
||
dimensionCounts[dim]++;
|
||
});
|
||
|
||
// Average per dimension
|
||
Object.keys(dimensionScores).forEach(d => {
|
||
dimensionScores[d] = dimensionCounts[d] > 0
|
||
? dimensionScores[d] / dimensionCounts[d]
|
||
: 0;
|
||
});
|
||
|
||
return dimensionScores;
|
||
};
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/assessment.service.ts
|
||
git commit -m "feat: calculate dimension scores"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.2: 追问策略调整
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/graph/nodes/grader.node.ts:207`
|
||
|
||
**Step 1: Increase follow-up limit**
|
||
|
||
```typescript
|
||
// Current: currentFollowUpCount >= 1
|
||
// Change to:
|
||
if (currentFollowUpCount >= 2 || result.score >= 8 || saysIDontKnow) {
|
||
shouldFollowUp = false;
|
||
}
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/graph/nodes/grader.node.ts
|
||
git commit -m "feat: increase follow-up limit to 2"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 4: 报告生成(P1)
|
||
|
||
### Task 4.1: 扩展 Session 存储 dimension 数据
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/entities/assessment-session.entity.ts`
|
||
|
||
**Step 1: Add dimension fields**
|
||
|
||
```typescript
|
||
@Column({ type: 'simple-json', name: 'dimension_scores', nullable: true })
|
||
dimensionScores: Record<string, number>;
|
||
|
||
@Column({ type: 'simple-json', name: 'radar_data', nullable: true })
|
||
radarData: Record<string, number>;
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/entities/assessment-session.entity.ts
|
||
git commit -m "feat: add dimension scores to session entity"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4.2: Analyzer 生成结构化报告
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/graph/nodes/analyzer.node.ts`
|
||
|
||
**Step 1: Generate dimension-aware report**
|
||
|
||
```typescript
|
||
// Modify system prompt to include:
|
||
const dimensionSummary = Object.entries(dimensionScores)
|
||
.map(([dim, score]) => `${dim}: ${score}/10`)
|
||
.join('\n');
|
||
|
||
const reportPrompt = `...
|
||
维度得分:
|
||
${dimensionSummary}
|
||
|
||
请在报告中包含:
|
||
1. 各维度得分分析
|
||
2. 薄弱环节识别
|
||
3. 针对性改进建议
|
||
`;
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/graph/nodes/analyzer.node.ts
|
||
git commit -m "feat: generate structured report with dimension analysis"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 5: 证书功能(P1)
|
||
|
||
### Task 5.1: 创建证书表
|
||
|
||
**Files:**
|
||
- Create: `server/src/assessment/entities/assessment-certificate.entity.ts`
|
||
|
||
**Step 1: Define entity**
|
||
|
||
```typescript
|
||
@Entity('assessment_certificates')
|
||
export class AssessmentCertificate {
|
||
@PrimaryGeneratedColumn('uuid')
|
||
id: string;
|
||
|
||
@Column({ name: 'user_id' })
|
||
userId: string;
|
||
|
||
@Column({ name: 'session_id' })
|
||
sessionId: string;
|
||
|
||
@Column({ name: 'template_id' })
|
||
templateId: string;
|
||
|
||
@Column()
|
||
level: string;
|
||
|
||
@Column({ type: 'float', name: 'total_score' })
|
||
totalScore: number;
|
||
|
||
@Column({ name: 'qr_code', nullable: true })
|
||
qrCode: string;
|
||
|
||
@CreateDateColumn({ name: 'issued_at' })
|
||
issuedAt: Date;
|
||
}
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/entities/assessment-certificate.entity.ts
|
||
git commit -m "feat: create certificate entity"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5.2: 证书生成API
|
||
|
||
**Files:**
|
||
- Modify: `server/src/assessment/assessment.controller.ts`
|
||
|
||
**Step 1: Add certificate endpoints**
|
||
|
||
```typescript
|
||
@Get('certificate/:sessionId')
|
||
async getCertificate(
|
||
@Param('sessionId') sessionId: string,
|
||
@Req() req,
|
||
): Promise<AssessmentCertificate> {
|
||
// Generate or return existing certificate
|
||
}
|
||
|
||
@Get('certificate/:sessionId/download')
|
||
async downloadCertificate(
|
||
@Param('sessionId') sessionId: string,
|
||
): Promise<StreamableFile> {
|
||
// Generate PDF with jsPDF
|
||
}
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add server/src/assessment/assessment.controller.ts
|
||
git commit -m "feat: add certificate endpoints"
|
||
```
|
||
|
||
---
|
||
|
||
## Phase 6: 前端集成(P2)
|
||
|
||
### Task 6.1: 模板选择页面
|
||
|
||
**Files:**
|
||
- Modify: `web/components/views/AssessmentView.tsx`
|
||
|
||
**Step 1: Add template selection**
|
||
|
||
```typescript
|
||
// Add state:
|
||
const [templates, setTemplates] = useState<AssessmentTemplate[]>([]);
|
||
|
||
// Load templates on mount:
|
||
useEffect(() => {
|
||
const loadTemplates = async () => {
|
||
const res = await assessmentService.getTemplates();
|
||
setTemplates(res.data);
|
||
};
|
||
loadTemplates();
|
||
}, []);
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add web/components/views/AssessmentView.tsx
|
||
git commit -m "feat: add template selection to frontend"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6.2: 报告页面的雷达图
|
||
|
||
**Files:**
|
||
- Create: `web/components/RadarChart.tsx`
|
||
|
||
**Step 1: Create component**
|
||
|
||
```typescript
|
||
export const RadarChart = ({ data }) => {
|
||
// Use recharts or echarts for radar
|
||
// Data: { prompt: 9.6, llm: 9.0, ide: 7.0, devPattern: 5.0, workCapability: 5.0 }
|
||
};
|
||
```
|
||
|
||
**Step 2: Commit**
|
||
|
||
```bash
|
||
git add web/components/RadarChart.tsx
|
||
git commit -m "feat: add radar chart component"
|
||
```
|
||
|
||
---
|
||
|
||
## 实施顺序
|
||
|
||
1. Task 1.1 → 1.3 (模板扩展)
|
||
2. Task 2.1 → 2.2 (评估流程)
|
||
3. Task 3.1 → 3.2 (评分逻辑)
|
||
4. Task 4.1 → 4.2 (报告)
|
||
5. Task 5.1 → 5.2 (证书)
|
||
6. Task 6.1 → 6.2 (前端)
|
||
|
||
**每个Task约需1-2小时**
|
||
|
||
---
|
||
|
||
## 依赖说明
|
||
|
||
- Phase 1 (模板) → Phase 2 → Phase 3 → Phase 4 → Phase 5
|
||
- 前端(Phase 6) 可在后端基本完成后并行开发
|
||
- 测试贯穿每个Task
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
- [ ] 模板支持多知识组关联
|
||
- [ ] 题目带dimension字段
|
||
- [ ] 报告含dimensionScores和radarData
|
||
- [ ] 可生成��书PDF
|
||
- [ ] 前端显示雷达图
|
||
- [ ] E2E测试通过 |