feat: implement QuestionBank CRUD with pagination and template query

- 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
This commit is contained in:
Developer
2026-04-23 17:19:11 +08:00
commit 0a9588abb7
492 changed files with 112453 additions and 0 deletions
@@ -0,0 +1,490 @@
# 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测试通过