| 文件 | 修改类型 | 说明 |
|---|---|---|
app/Services/ExamTypeStrategy.php |
组卷去重、参数传递 | 排除已做题、grade 推断 |
app/Services/LearningAnalyticsService.php |
智能补充、已学知识点 | 补充范围、totalNeeded、新方法 |
getStudentAnsweredQuestionIds原逻辑:从 student_answer_questions 表按 student_id 查 question_id
新逻辑:从 paper_questions 表,按该学生名下试卷的 question_bank_id 去重
// 唯一来源:paper_questions → question_bank_id(对应 questions.id)
// 确保新卷子不出现学生做过的重复题目
PaperQuestion::query()
->whereHas('paper', fn ($q) => $q->where('student_id', $studentId))
->whereNotNull('question_bank_id')
->where('question_bank_id', '>', 0)
->distinct()
->pluck('question_bank_id')
原因:与组卷实际数据源(paper_questions)保持一致,去重更准确。
buildTextbookAssembleParams新增:当传入 textbook_id 但 grade 为空时,从 textbooks 表按 textbook_id 查询 grade
if ($textbookId && $grade === null) {
$grade = DB::table('textbooks')->where('id', $textbookId)->value('grade');
}
保证:params 中同时传递 textbook_id 和 grade,供智能补充使用。
buildIntelligentAssembleParams新增:同上,从教材推断 grade,保证智能补充可触发。
原逻辑:传入 $poolLimit(固定为 0),totalNeeded 恒为 0,智能补充条件 totalNeeded > 0 && count < totalNeeded 恒不成立
新逻辑:传入 $needCount = $totalQuestions - count($priorityQuestions),题目不足时可正确触发智能补充
getSupplementaryQuestionsForGrade补充范围策略:
| 场景 | 补充来源 | 说明 |
|---|---|---|
有 textbookId |
getGradeKnowledgePoints(grade, textbookId) |
同教材知识点 |
有 textbookId + textbookCatalogNodeIds |
上述 + getEarlierChapterNodeIds |
仅同教材前章节 |
无 textbookId + 有 studentId |
getStudentLearnedKpCodes(studentId) |
仅学生已学知识点 |
无 textbookId + 无 studentId |
不补充 | 无法确定学过的内容 |
getStudentLearnedKpCodes(?string $studentId)
从 paper_questions + Paper + Question 获取该学生做过的题目的 kp_code,作为「已学知识点」。
getEarlierChapterNodeIds(int $textbookId, array $chapterNodeIds)
按 sort_order <= max(选中章节) 筛选同教材节点,用于「前章节补充」。
getSupplementaryQuestionsForGrade 新增参数:
excludeQuestionIds:排除学生已做题目,避免重复textbookCatalogNodeIds:教材组卷时限制为前章节studentId:无教材时获取已学知识点| 风险点 | 分析 | 结论 |
|---|---|---|
| 新学生无做题记录 | getStudentLearnedKpCodes 返回 [],gradeKpCodes 为空,不补充 |
✅ 正确:宁可少题,不补未学内容 |
| 无教材且无 studentId | 如教师预览卷子,不补充 | ✅ 正确:无法确定学过的内容时不补充 |
| textbooks 表无 grade | value('grade') 返回 null,grade 为 null,智能补充不触发 |
⚠️ 依赖数据:需保证教材配置完整 |
| paper_questions.question_bank_id 为空 | getStudentAnsweredQuestionIds 已过滤 whereNotNull 且 > 0 |
✅ 已处理 |
| poolLimit 改为 needCount | 原 poolLimit=0 导致智能补充从不触发;现传入实际需要数量 |
✅ 修复正确 |
| getStudentAnsweredQuestionIds 的 $kpCodes 参数 | 仍保留参数,内部已不再使用 | ✅ 兼容调用方,无影响 |
组卷请求
→ ExamTypeStrategy::buildParams
→ getStudentAnsweredQuestionIds(studentId) → exclude_question_ids
→ 教材组卷:grade 推断、textbook_id、textbook_catalog_node_ids
→ 智能组卷:grade 推断
→ LearningAnalyticsService::generateIntelligentExam
→ getQuestionsFromBank(kpCodes, ..., excludeQuestionIds, textbookCatalogNodeIds, grade, textbookId)
→ 主查询:whereNotIn('id', excludeQuestionIds)
→ 不足时:getSupplementaryQuestionsForGrade(grade, existingKps, deficit, ..., textbookId, excludeQuestionIds, textbookCatalogNodeIds, studentId)
→ 有教材:getGradeKnowledgePoints + getEarlierChapterNodeIds(若有章节)
→ 无教材:getStudentLearnedKpCodes(studentId)
→ whereNotIn('id', excludeQuestionIds) 再次排除
textbook_id 和 textbookCatalogNodeIds 传递正确,前章节补充逻辑生效。textbooks.grade 有值,避免 grade 推断失败。