$studentId, 'total_needed' => $totalQuestions, 'weakness_count' => count($weaknessFilter) ]); $strategy = [ 'mistake_ids' => $baseParams['mistake_ids'] ?? [], 'mistake_question_ids' => $baseParams['mistake_question_ids'] ?? [], 'step2_count' => 0, 'step3_count' => 0, 'step4_count' => 0, 'step5_count' => 0, 'expansion_details' => [] ]; $includeMistakes = ($baseParams['mistake_options']['review_mistakes'] ?? true) && $studentId; if (!$includeMistakes) { Log::info('QuestionExpansionService: 未开启错题回顾,跳过扩展'); return $strategy; } // Step 1: 获取学生历史错题(优先级:最高) // 直接使用学生历史错题,学习价值最高 if (empty($strategy['mistake_question_ids'])) { $studentMistakes = $this->getStudentMistakes($studentId); $strategy['mistake_question_ids'] = array_values(array_unique(array_column($studentMistakes, 'question_id'))); $strategy['expansion_details']['step1_mistakes'] = count($strategy['mistake_question_ids']); Log::info('QuestionExpansionService: Step1 - 获取历史错题', [ 'count' => count($strategy['mistake_question_ids']), 'mistake_ids' => $strategy['mistake_question_ids'] ]); } // 如果错题数量已满足需求,直接返回 if (count($strategy['mistake_question_ids']) >= $totalQuestions) { Log::info('QuestionExpansionService: Step1 - 错题数量已满足需求', [ 'total_needed' => $totalQuestions, 'mistake_count' => count($strategy['mistake_question_ids']) ]); return $strategy; } // Step 2: 错题相同的知识点的其他题目(优先级:高) // 强化特定知识点的掌握,题目类型多样化 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); $mistakeKps = $this->getMistakeKnowledgePoints($studentId, $strategy['mistake_question_ids']); if (!empty($mistakeKps) && $remaining > 0) { $step2Questions = $this->getQuestionsByKnowledgePoints($mistakeKps, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step2Questions); $strategy['step2_count'] = count($step2Questions); $strategy['expansion_details']['step2_same_kp'] = [ 'knowledge_points' => $mistakeKps, 'added_count' => count($step2Questions) ]; Log::info('QuestionExpansionService: Step2 - 获取同知识点其他题目', [ 'knowledge_points' => $mistakeKps, 'added_count' => count($step2Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } // 如果数量仍不足,继续扩展 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); if ($remaining <= 0) { return $strategy; } // Step 3: 相同知识点的子知识点的题目(优先级:中高) // 深入知识点的细分类别,实现精准练习 if (!empty($mistakeKps) && $remaining > 0) { $childKps = $this->getChildKnowledgePoints($mistakeKps); if (!empty($childKps)) { $step3Questions = $this->getQuestionsByKnowledgePoints($childKps, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step3Questions); $strategy['step3_count'] = count($step3Questions); $strategy['expansion_details']['step3_child_kp'] = [ 'child_knowledge_points' => $childKps, 'added_count' => count($step3Questions) ]; Log::info('QuestionExpansionService: Step3 - 获取子知识点题目', [ 'child_knowledge_points' => $childKps, 'added_count' => count($step3Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } } // 如果数量仍不足,继续扩展 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); if ($remaining <= 0) { return $strategy; } // Step 4: 学生薄弱知识点相关题目(优先级:中) // 针对性强化薄弱环节,提升整体掌握度 if (!empty($weaknessFilter) && $remaining > 0) { $weaknessKps = array_column($weaknessFilter, 'kp_code'); $step4Questions = $this->getQuestionsByKnowledgePoints($weaknessKps, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step4Questions); $strategy['step4_count'] = count($step4Questions); $strategy['expansion_details']['step4_weakness'] = [ 'weakness_knowledge_points' => $weaknessKps, 'added_count' => count($step4Questions) ]; Log::info('QuestionExpansionService: Step4 - 获取薄弱知识点题目', [ 'weakness_knowledge_points' => $weaknessKps, 'added_count' => count($step4Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } // 如果数量仍不足,继续扩展 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); if ($remaining <= 0) { return $strategy; } // Step 5: 薄弱知识点的子知识点的题目(优先级:低) // 全面覆盖薄弱环节的各个细分领域 if (!empty($weaknessFilter) && $remaining > 0) { $weaknessKps = array_column($weaknessFilter, 'kp_code'); $childWeaknessKps = $this->getChildKnowledgePoints($weaknessKps); if (!empty($childWeaknessKps)) { $step5Questions = $this->getQuestionsByKnowledgePoints($childWeaknessKps, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step5Questions); $strategy['step5_count'] = count($step5Questions); $strategy['expansion_details']['step5_child_weakness'] = [ 'child_weakness_knowledge_points' => $childWeaknessKps, 'added_count' => count($step5Questions) ]; Log::info('QuestionExpansionService: Step5 - 获取薄弱点子知识点题目', [ 'child_weakness_knowledge_points' => $childWeaknessKps, 'added_count' => count($step5Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } } Log::info('QuestionExpansionService: 题目扩展完成', [ 'total_final_count' => count($strategy['mistake_question_ids']), 'expansion_details' => $strategy['expansion_details'] ]); return $strategy; } /** * 获取学生错题(去重,按question_id分组) */ private function getStudentMistakes(string $studentId): array { try { // 使用 MistakeRecord 模型获取学生的错题(不限制数量,获取全部) // 按 question_id 分组去重,保留第一条记录 $mistakeRecords = MistakeRecord::forStudent($studentId) ->where('review_status', '!=', MistakeRecord::REVIEW_STATUS_MASTERED) // 排除已掌握的错题 ->select(['question_id', 'knowledge_point', 'error_type', 'difficulty']) ->get() ->unique('question_id') // 按 question_id 去重 ->values(); // 重新索引 Log::info('QuestionExpansionService: 获取学生错题(去重后)', [ 'student_id' => $studentId, 'total_records' => MistakeRecord::forStudent($studentId) ->where('review_status', '!=', MistakeRecord::REVIEW_STATUS_MASTERED)->count(), 'unique_count' => $mistakeRecords->count() ]); return $mistakeRecords->map(function ($record) { return [ 'question_id' => $record->question_id, 'kp_code' => $record->knowledge_point, 'error_type' => $record->error_type, 'difficulty' => $record->difficulty, ]; })->toArray(); } catch (\Exception $e) { Log::error('QuestionExpansionService: 获取学生错题失败', [ 'student_id' => $studentId, 'error' => $e->getMessage() ]); return []; } } /** * 获取错题对应的知识点 */ private function getMistakeKnowledgePoints(string $studentId, array $questionIds): array { $mistakeRecords = MistakeRecord::forStudent($studentId) ->whereIn('question_id', $questionIds) ->whereNotNull('knowledge_point') ->select(['knowledge_point']) ->get(); return array_values(array_unique(array_filter($mistakeRecords->pluck('knowledge_point')->toArray()))); } /** * 根据知识点获取题目 */ private function getQuestionsByKnowledgePoints(array $kpCodes, array $excludeQuestionIds, int $limit): array { if (empty($kpCodes)) { return []; } try { $questions = Question::whereIn('kp_code', $kpCodes) ->whereNotIn('id', $excludeQuestionIds) ->select(['id']) ->limit($limit) ->get(); return $questions->pluck('id')->toArray(); } catch (\Exception $e) { Log::warning('QuestionExpansionService: 获取题目失败', [ 'kp_codes' => $kpCodes, 'limit' => $limit, 'error' => $e->getMessage() ]); return []; } } /** * 获取子知识点 */ private function getChildKnowledgePoints(array $parentKpCodes): array { try { $childKps = KnowledgePoint::whereIn('parent_kp_code', $parentKpCodes) ->select(['kp_code']) ->get(); return $childKps->pluck('kp_code')->toArray(); } catch (\Exception $e) { Log::warning('QuestionExpansionService: 获取子知识点失败', [ 'parent_kp_codes' => $parentKpCodes, 'error' => $e->getMessage() ]); return []; } } /** * 获取扩展统计信息 */ public function getExpansionStats(array $strategy): array { return [ 'total_questions' => count($strategy['mistake_question_ids'] ?? []), 'step1_mistakes' => count($strategy['mistake_question_ids'] ?? []), 'step2_same_kp' => $strategy['step2_count'] ?? 0, 'step3_child_kp' => $strategy['step3_count'] ?? 0, 'step4_weakness' => $strategy['step4_count'] ?? 0, 'step5_child_weakness' => $strategy['step5_count'] ?? 0, 'expansion_rate' => $this->calculateExpansionRate($strategy), 'details' => $strategy['expansion_details'] ?? [] ]; } /** * 计算扩展率 */ private function calculateExpansionRate(array $strategy): float { $totalExpanded = ($strategy['step2_count'] ?? 0) + ($strategy['step3_count'] ?? 0) + ($strategy['step4_count'] ?? 0) + ($strategy['step5_count'] ?? 0); $totalQuestions = count($strategy['mistake_question_ids'] ?? []); if ($totalQuestions === 0) { return 0.0; } return round(($totalExpanded / $totalQuestions) * 100, 2); } /** * 按知识点优先级扩展题目数量(按知识点组卷专用) * 优先级策略: * 1. 直接关联知识点题目(来自输入数组) * 2. 相同知识点其他题目 * 3. 子知识点题目(下探1层) * 4. 薄弱知识点题目 * 5. 子知识点题目(下探2层) */ public function expandQuestionsByKnowledgePoints(array $baseParams, ?string $studentId, array $targetKnowledgePoints, array $weaknessFilter, int $totalQuestions): array { Log::info('QuestionExpansionService: 开始按知识点扩展题目', [ 'student_id' => $studentId, 'target_knowledge_points' => $targetKnowledgePoints, 'total_needed' => $totalQuestions, 'weakness_count' => count($weaknessFilter) ]); $strategy = [ 'mistake_ids' => [], 'mistake_question_ids' => [], 'step2_count' => 0, 'step3_count' => 0, 'step4_count' => 0, 'step5_count' => 0, 'expansion_details' => [] ]; // Step 1: 获取直接关联知识点题目(优先级:最高) // 直接使用用户指定的知识点数组中的题目 if (!empty($targetKnowledgePoints)) { $step1Questions = $this->getQuestionsByKnowledgePoints($targetKnowledgePoints, $strategy['mistake_question_ids'], $totalQuestions); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step1Questions); $strategy['expansion_details']['step1_direct_kp'] = [ 'target_knowledge_points' => $targetKnowledgePoints, 'added_count' => count($step1Questions) ]; Log::info('QuestionExpansionService: Step1 - 获取直接关联知识点题目', [ 'target_knowledge_points' => $targetKnowledgePoints, 'added_count' => count($step1Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } // 如果直接关联题目数量已满足需求,直接返回 if (count($strategy['mistake_question_ids']) >= $totalQuestions) { Log::info('QuestionExpansionService: Step1 - 直接关联知识点题目数量已满足需求', [ 'total_needed' => $totalQuestions, 'direct_kp_count' => count($strategy['mistake_question_ids']) ]); return $strategy; } // Step 2: 相同知识点其他题目(优先级:高) // 强化特定知识点的掌握,题目类型多样化 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); if (!empty($targetKnowledgePoints) && $remaining > 0) { $step2Questions = $this->getQuestionsByKnowledgePoints($targetKnowledgePoints, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step2Questions); $strategy['step2_count'] = count($step2Questions); $strategy['expansion_details']['step2_same_kp'] = [ 'knowledge_points' => $targetKnowledgePoints, 'added_count' => count($step2Questions) ]; Log::info('QuestionExpansionService: Step2 - 获取相同知识点其他题目', [ 'knowledge_points' => $targetKnowledgePoints, 'added_count' => count($step2Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } // 如果数量仍不足,继续扩展 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); if ($remaining <= 0) { return $strategy; } // Step 3: 子知识点题目(下探1层)(优先级:中高) // 深入知识点的细分类别,实现精准练习 if (!empty($targetKnowledgePoints) && $remaining > 0) { $childKps = $this->getChildKnowledgePoints($targetKnowledgePoints); if (!empty($childKps)) { $step3Questions = $this->getQuestionsByKnowledgePoints($childKps, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step3Questions); $strategy['step3_count'] = count($step3Questions); $strategy['expansion_details']['step3_child_kp_level1'] = [ 'child_knowledge_points' => $childKps, 'added_count' => count($step3Questions) ]; Log::info('QuestionExpansionService: Step3 - 获取子知识点题目(下探1层)', [ 'child_knowledge_points' => $childKps, 'added_count' => count($step3Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } } // 如果数量仍不足,继续扩展 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); if ($remaining <= 0) { return $strategy; } // Step 4: 薄弱知识点题目(优先级:中) // 针对性强化薄弱环节,提升整体掌握度 if (!empty($weaknessFilter) && $remaining > 0) { $weaknessKps = array_column($weaknessFilter, 'kp_code'); // 排除已使用的目标知识点 $weaknessKps = array_diff($weaknessKps, $targetKnowledgePoints); if (!empty($weaknessKps)) { $step4Questions = $this->getQuestionsByKnowledgePoints($weaknessKps, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step4Questions); $strategy['step4_count'] = count($step4Questions); $strategy['expansion_details']['step4_weakness'] = [ 'weakness_knowledge_points' => $weaknessKps, 'added_count' => count($step4Questions) ]; Log::info('QuestionExpansionService: Step4 - 获取薄弱知识点题目', [ 'weakness_knowledge_points' => $weaknessKps, 'added_count' => count($step4Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } } // 如果数量仍不足,继续扩展 $remaining = $totalQuestions - count($strategy['mistake_question_ids']); if ($remaining <= 0) { return $strategy; } // Step 5: 子知识点题目(下探2层)(优先级:低) // 全面覆盖薄弱环节的各个细分领域 if (!empty($weaknessFilter) && $remaining > 0) { $weaknessKps = array_column($weaknessFilter, 'kp_code'); // 排除已使用的目标知识点 $weaknessKps = array_diff($weaknessKps, $targetKnowledgePoints); if (!empty($weaknessKps)) { $childWeaknessKps = $this->getChildKnowledgePoints($weaknessKps); if (!empty($childWeaknessKps)) { $step5Questions = $this->getQuestionsByKnowledgePoints($childWeaknessKps, $strategy['mistake_question_ids'], $remaining); $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step5Questions); $strategy['step5_count'] = count($step5Questions); $strategy['expansion_details']['step5_child_weakness_level2'] = [ 'child_weakness_knowledge_points' => $childWeaknessKps, 'added_count' => count($step5Questions) ]; Log::info('QuestionExpansionService: Step5 - 获取薄弱点子知识点题目(下探2层)', [ 'child_weakness_knowledge_points' => $childWeaknessKps, 'added_count' => count($step5Questions), 'total_count' => count($strategy['mistake_question_ids']) ]); } } } Log::info('QuestionExpansionService: 按知识点题目扩展完成', [ 'total_final_count' => count($strategy['mistake_question_ids']), 'target_knowledge_points' => $targetKnowledgePoints, 'expansion_details' => $strategy['expansion_details'] ]); return $strategy; } /** * 获取子知识点(下探多层) * 支持追溯下探两层 */ private function getChildKnowledgePointsMultiLevel(array $parentKpCodes, int $maxDepth = 2): array { $allChildKps = []; $currentLevelKps = $parentKpCodes; $depth = 0; while ($depth < $maxDepth && !empty($currentLevelKps)) { Log::info('QuestionExpansionService: 获取子知识点', [ 'depth' => $depth + 1, 'parent_kp_codes' => $currentLevelKps ]); $childKps = $this->getChildKnowledgePoints($currentLevelKps); if (!empty($childKps)) { $allChildKps = array_merge($allChildKps, $childKps); $currentLevelKps = $childKps; // 下一层的父级是这一层的子级 } else { break; // 没有更多子知识点时停止 } $depth++; } // 去重 return array_values(array_unique($allChildKps)); } }