|
|
@@ -1358,8 +1358,8 @@ class LearningAnalyticsService
|
|
|
$truncatedMistakeIds = array_slice($mistakeQuestionIds, 0, $maxQuestions);
|
|
|
}
|
|
|
|
|
|
- // 获取学生错题的详细信息
|
|
|
- $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $truncatedMistakeIds, [], null, null);
|
|
|
+ // 获取学生错题的详细信息(错题获取不需要智能补充,不传入 grade/textbook_id)
|
|
|
+ $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $truncatedMistakeIds, [], null, null, null, null, 1);
|
|
|
|
|
|
Log::info('LearningAnalyticsService: 错题获取完成', [
|
|
|
'priority_questions_count' => count($priorityQuestions),
|
|
|
@@ -1412,18 +1412,39 @@ class LearningAnalyticsService
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
+ // 【优化】获取textbook_catalog_node_ids参数(教材组卷时使用)
|
|
|
+ $textbookCatalogNodeIds = $params['textbook_catalog_node_ids'] ?? null;
|
|
|
+ // 【修复超纲问题】获取 textbook_id 和 difficulty_category,用于智能补充时限制范围
|
|
|
+ $textbookId = isset($params['textbook_id']) ? (int) $params['textbook_id'] : null;
|
|
|
+ $difficultyCategory = (int) ($params['difficulty_category'] ?? 1);
|
|
|
+
|
|
|
Log::info('开始调用 getQuestionsFromBank 补充题目', [
|
|
|
'kp_codes_count' => count($kpCodes),
|
|
|
'skills_count' => count($skills),
|
|
|
'has_mistake_priority' => !empty($mistakeQuestionIds),
|
|
|
'need_more' => $totalQuestions - count($priorityQuestions),
|
|
|
- 'assemble_type' => $assembleType
|
|
|
+ 'assemble_type' => $assembleType,
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
+ 'difficulty_category' => $difficultyCategory,
|
|
|
+ 'note' => $textbookId ? '将使用 textbook_id 限制补充范围,避免超纲' : '未指定教材,使用整个年级范围'
|
|
|
]);
|
|
|
|
|
|
- // 【优化】获取textbook_catalog_node_ids参数(教材组卷时使用)
|
|
|
- $textbookCatalogNodeIds = $params['textbook_catalog_node_ids'] ?? null;
|
|
|
-
|
|
|
- $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, $maxQuestions, [], [], $questionCategory, $textbookCatalogNodeIds);
|
|
|
+ // 【修复超纲问题】传入 grade 和 textbook_id,用于智能补充时限制范围
|
|
|
+ $additionalQuestions = $this->getQuestionsFromBank(
|
|
|
+ $kpCodes,
|
|
|
+ $skills,
|
|
|
+ $studentId,
|
|
|
+ $questionTypeRatio,
|
|
|
+ $maxQuestions,
|
|
|
+ [],
|
|
|
+ [],
|
|
|
+ $questionCategory,
|
|
|
+ $textbookCatalogNodeIds,
|
|
|
+ $grade ? (int) $grade : null, // 年级
|
|
|
+ $textbookId, // 教材ID(避免超纲)
|
|
|
+ $difficultyCategory // 难度类别
|
|
|
+ );
|
|
|
$allQuestions = array_merge($priorityQuestions, $additionalQuestions);
|
|
|
|
|
|
Log::info('getQuestionsFromBank 调用完成', [
|
|
|
@@ -1599,11 +1620,23 @@ class LearningAnalyticsService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 从本地题库获取题目(错题回顾优先)
|
|
|
- * 支持优先获取指定题目ID的题目
|
|
|
- * 【优化】新增textbookCatalogNodeIds参数,支持按textbook_catalog_node_id筛选题目
|
|
|
+ * 从题库获取题目
|
|
|
+ *
|
|
|
+ * @param array $kpCodes 知识点代码列表
|
|
|
+ * @param array $skills 技能标签列表
|
|
|
+ * @param string|null $studentId 学生ID
|
|
|
+ * @param array $questionTypeRatio 题型比例
|
|
|
+ * @param int $totalNeeded 需要的题目数量
|
|
|
+ * @param array $priorityQuestionIds 优先获取的题目ID(错题)
|
|
|
+ * @param array $excludeQuestionIds 排除的题目ID
|
|
|
+ * @param int|null $questionCategory 题目分类
|
|
|
+ * @param array|null $textbookCatalogNodeIds 教材章节节点ID
|
|
|
+ * @param int|null $grade 年级(用于智能补充时限制范围)
|
|
|
+ * @param int|null $textbookId 教材ID(用于智能补充时限制范围,避免超纲)
|
|
|
+ * @param int $difficultyCategory 难度类别(用于智能补充)
|
|
|
+ * @return array 题目列表
|
|
|
*/
|
|
|
- private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = [], array $excludeQuestionIds = [], ?int $questionCategory = null, ?array $textbookCatalogNodeIds = null): array
|
|
|
+ private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = [], array $excludeQuestionIds = [], ?int $questionCategory = null, ?array $textbookCatalogNodeIds = null, ?int $grade = null, ?int $textbookId = null, int $difficultyCategory = 1): array
|
|
|
{
|
|
|
$startTime = microtime(true);
|
|
|
|
|
|
@@ -1732,31 +1765,40 @@ class LearningAnalyticsService
|
|
|
// 让上层调用者根据需要选择题目数量
|
|
|
$selectedQuestions = $formattedQuestions;
|
|
|
|
|
|
- // 【修复】重新启用智能补充功能
|
|
|
- if (count($selectedQuestions) < $totalNeeded) {
|
|
|
+ // 【修复】重新启用智能补充功能,增加 textbook_id 限制避免超纲
|
|
|
+ if (count($selectedQuestions) < $totalNeeded && $grade !== null) {
|
|
|
$deficit = $totalNeeded - count($selectedQuestions);
|
|
|
Log::warning('getQuestionsFromBank: 指定知识点题目不足,尝试智能补充', [
|
|
|
'deficit' => $deficit,
|
|
|
'available_count' => count($selectedQuestions),
|
|
|
- 'strategy' => '从同年级其他知识点或相邻难度补充'
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
+ 'strategy' => $textbookId ? '从指定教材的其他知识点补充(避免超纲)' : '从同年级其他知识点补充'
|
|
|
]);
|
|
|
|
|
|
- // 补充策略:从同年级其他知识点补充
|
|
|
+ // 【修复超纲问题】补充策略:从同年级同教材的其他知识点补充
|
|
|
+ // 传入 textbook_id 参数,避免七年级上册学生拿到下册题目
|
|
|
$supplementaryQuestions = $this->getSupplementaryQuestionsForGrade(
|
|
|
- $params['grade'] ?? 9,
|
|
|
+ $grade,
|
|
|
array_column($selectedQuestions, 'kp_code'),
|
|
|
$deficit,
|
|
|
- $params['difficulty_category'] ?? 1
|
|
|
+ $difficultyCategory,
|
|
|
+ $textbookId // 【关键】传入教材ID,限制补充范围
|
|
|
);
|
|
|
|
|
|
if (!empty($supplementaryQuestions)) {
|
|
|
$selectedQuestions = array_merge($selectedQuestions, $supplementaryQuestions);
|
|
|
Log::info('getQuestionsFromBank: 智能补充完成', [
|
|
|
'supplementary_count' => count($supplementaryQuestions),
|
|
|
- 'total_after_supplement' => count($selectedQuestions)
|
|
|
+ 'total_after_supplement' => count($selectedQuestions),
|
|
|
+ 'textbook_id' => $textbookId
|
|
|
]);
|
|
|
} else {
|
|
|
- Log::warning('getQuestionsFromBank: 智能补充失败,未找到合适的题目');
|
|
|
+ Log::warning('getQuestionsFromBank: 智能补充失败,未找到合适的题目', [
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
+ 'note' => $textbookId ? '可能是该教材知识点题目不足' : '该年级题目不足'
|
|
|
+ ]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -2876,21 +2918,30 @@ class LearningAnalyticsService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 【新增】为指定年级智能补充题目
|
|
|
- * 当指定知识点题目不足时,从同年级其他知识点补充
|
|
|
+ * 智能补充题目(当指定知识点的题目不足时)
|
|
|
+ *
|
|
|
+ * @param int $grade 年级
|
|
|
+ * @param array $existingKpCodes 已有的知识点代码(用于排除)
|
|
|
+ * @param int $needCount 需要补充的题目数量
|
|
|
+ * @param int $difficultyCategory 难度类别
|
|
|
+ * @param int|null $textbookId 教材ID(用于限制补充范围,避免超纲)
|
|
|
+ * @return array 补充的题目列表
|
|
|
*/
|
|
|
private function getSupplementaryQuestionsForGrade(
|
|
|
int $grade,
|
|
|
array $existingKpCodes,
|
|
|
int $needCount,
|
|
|
- int $difficultyCategory
|
|
|
+ int $difficultyCategory,
|
|
|
+ ?int $textbookId = null
|
|
|
): array {
|
|
|
try {
|
|
|
Log::info('getSupplementaryQuestionsForGrade: 开始智能补充', [
|
|
|
'grade' => $grade,
|
|
|
'existing_kp_count' => count($existingKpCodes),
|
|
|
'need_count' => $needCount,
|
|
|
- 'difficulty_category' => $difficultyCategory
|
|
|
+ 'difficulty_category' => $difficultyCategory,
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
+ 'note' => $textbookId ? '限制在指定教材范围内,避免超纲' : '使用整个年级范围'
|
|
|
]);
|
|
|
|
|
|
// 查询同年级其他知识点的题目
|
|
|
@@ -2901,10 +2952,21 @@ class LearningAnalyticsService
|
|
|
$query->whereNotIn('kp_code', $existingKpCodes);
|
|
|
}
|
|
|
|
|
|
- // 限制同年级(通过教材关联)
|
|
|
- $gradeKpCodes = $this->getGradeKnowledgePoints($grade);
|
|
|
+ // 【修复超纲问题】限制在指定教材范围内(通过教材关联)
|
|
|
+ // 如果有 textbook_id,只从该教材的知识点中补充,避免七年级上册学生拿到下册题目
|
|
|
+ $gradeKpCodes = $this->getGradeKnowledgePoints($grade, $textbookId);
|
|
|
if (!empty($gradeKpCodes)) {
|
|
|
$query->whereIn('kp_code', $gradeKpCodes);
|
|
|
+ Log::info('getSupplementaryQuestionsForGrade: 应用知识点范围限制', [
|
|
|
+ 'kp_codes_count' => count($gradeKpCodes),
|
|
|
+ 'textbook_id' => $textbookId
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ Log::warning('getSupplementaryQuestionsForGrade: 未找到年级知识点,跳过补充', [
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'textbook_id' => $textbookId
|
|
|
+ ]);
|
|
|
+ return [];
|
|
|
}
|
|
|
|
|
|
// 筛选有解题思路的题目
|
|
|
@@ -2993,23 +3055,45 @@ class LearningAnalyticsService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 【新增】获取指定年级的所有知识点
|
|
|
+ * 获取指定年级的知识点列表
|
|
|
+ *
|
|
|
+ * @param int $grade 年级
|
|
|
+ * @param int|null $textbookId 教材ID(可选,用于限制在特定教材范围内,避免超纲)
|
|
|
+ * @return array 知识点代码列表
|
|
|
*/
|
|
|
- private function getGradeKnowledgePoints(int $grade): array
|
|
|
+ private function getGradeKnowledgePoints(int $grade, ?int $textbookId = null): array
|
|
|
{
|
|
|
try {
|
|
|
- $kpCodes = DB::table('textbook_chapter_knowledge_relation as tckr')
|
|
|
+ $query = DB::table('textbook_chapter_knowledge_relation as tckr')
|
|
|
->join('textbook_catalog_nodes as tcn', 'tckr.catalog_chapter_id', '=', 'tcn.id')
|
|
|
->join('textbooks as t', 'tcn.textbook_id', '=', 't.id')
|
|
|
- ->where('t.grade', $grade)
|
|
|
- ->distinct()
|
|
|
+ ->where('t.grade', $grade);
|
|
|
+
|
|
|
+ // 【修复超纲问题】如果有 textbook_id,严格限制在该教材范围内
|
|
|
+ // 避免七年级上册学生拿到七年级下册的知识点
|
|
|
+ if ($textbookId) {
|
|
|
+ $query->where('t.id', $textbookId);
|
|
|
+ Log::info('getGradeKnowledgePoints: 限制在指定教材范围内', [
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'textbook_id' => $textbookId
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ $kpCodes = $query->distinct()
|
|
|
->pluck('tckr.kp_code')
|
|
|
->toArray();
|
|
|
|
|
|
+ Log::info('getGradeKnowledgePoints: 获取知识点完成', [
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
+ 'kp_count' => count($kpCodes)
|
|
|
+ ]);
|
|
|
+
|
|
|
return array_filter($kpCodes);
|
|
|
} catch (\Exception $e) {
|
|
|
Log::error('getGradeKnowledgePoints: 查询失败', [
|
|
|
'grade' => $grade,
|
|
|
+ 'textbook_id' => $textbookId,
|
|
|
'error' => $e->getMessage()
|
|
|
]);
|
|
|
return [];
|