questionExpansionService = $questionExpansionService; } /** * 根据组卷类型构建参数 */ public function buildParams(array $baseParams, string $examType): array { Log::info('ExamTypeStrategy: 构建组卷参数', [ 'exam_type' => $examType, 'base_params_keys' => array_keys($baseParams) ]); return match($examType) { 'diagnostic' => $this->buildDiagnosticParams($baseParams), 'practice' => $this->buildPracticeParams($baseParams), 'mistake' => $this->buildMistakeParams($baseParams), 'textbook' => $this->buildTextbookParams($baseParams), 'knowledge' => $this->buildKnowledgeParams($baseParams), default => $this->buildGeneralParams($baseParams) }; } /** * 通用智能出卷(原有行为) */ private function buildGeneralParams(array $params): array { Log::info('ExamTypeStrategy: 通用智能出卷参数', $params); // 返回原始参数,不做特殊处理 return $params; } /** * 摸底测试:评估当前水平 */ private function buildDiagnosticParams(array $params): array { Log::info('ExamTypeStrategy: 构建摸底测试参数', $params); // 摸底测试:平衡所有难度,覆盖多个知识点 $enhanced = array_merge($params, [ // 难度配比:相对平衡,基础题稍多 'difficulty_ratio' => [ '基础' => 40, '中等' => 40, '拔高' => 20, ], // 题型配比:选择题多一些,便于快速评估 'question_type_ratio' => [ '选择题' => 50, '填空题' => 25, '解答题' => 25, ], // 确保覆盖多个知识点 'kp_codes' => $this->expandKpCodesForDiagnostic($params['kp_codes'] ?? []), // 摸底测试名称 'paper_name' => $params['paper_name'] ?? ('摸底测试_' . now()->format('Ymd_His')), ]); Log::info('ExamTypeStrategy: 摸底测试参数构建完成', [ 'difficulty_ratio' => $enhanced['difficulty_ratio'], 'question_type_ratio' => $enhanced['question_type_ratio'], 'kp_codes_count' => count($enhanced['kp_codes']) ]); return $enhanced; } /** * 错题:针对薄弱点强化 * 使用 QuestionExpansionService 按优先级扩展题目 */ private function buildMistakeParams(array $params): array { Log::info('ExamTypeStrategy: 构建错题参数', $params); $studentId = $params['student_id'] ?? null; $totalQuestions = $params['total_questions'] ?? 20; $mistakeOptions = $params['mistake_options'] ?? []; $weaknessThreshold = $mistakeOptions['weakness_threshold'] ?? 0.7; $intensity = $mistakeOptions['intensity'] ?? 'medium'; $focusWeaknesses = $mistakeOptions['focus_weaknesses'] ?? true; // 根据强度调整难度配比 $difficultyRatio = match($intensity) { 'low' => [ '基础' => 60, '中等' => 35, '拔高' => 5, ], 'medium' => [ '基础' => 45, '中等' => 40, '拔高' => 15, ], 'high' => [ '基础' => 30, '中等' => 45, '拔高' => 25, ], default => [ '基础' => 45, '中等' => 40, '拔高' => 15, ] }; // 获取学生薄弱点 $weaknessFilter = []; if ($studentId && $focusWeaknesses) { $weaknessFilter = $this->getStudentWeaknesses($studentId, $weaknessThreshold); Log::info('ExamTypeStrategy: 获取到学生薄弱点', [ 'student_id' => $studentId, 'weakness_threshold' => $weaknessThreshold, 'weakness_count' => count($weaknessFilter) ]); } // 使用 QuestionExpansionService 按优先级扩展题目 $questionStrategy = $this->questionExpansionService->expandQuestions( $params, $studentId, $weaknessFilter, $totalQuestions ); // 获取错题知识点 $mistakeKnowledgePoints = []; if ($studentId && !empty($questionStrategy['mistake_question_ids'])) { $mistakeRecords = MistakeRecord::forStudent($studentId) ->whereIn('question_id', $questionStrategy['mistake_question_ids']) ->select(['knowledge_point']) ->get(); $mistakeKnowledgePoints = array_unique(array_filter($mistakeRecords->pluck('knowledge_point')->toArray())); Log::info('ExamTypeStrategy: 获取错题知识点', [ 'knowledge_points' => $mistakeKnowledgePoints ]); } // 获取扩展统计 $expansionStats = $this->questionExpansionService->getExpansionStats($questionStrategy); $enhanced = array_merge($params, [ 'difficulty_ratio' => $difficultyRatio, 'mistake_ids' => $questionStrategy['mistake_ids'], 'mistake_question_ids' => $questionStrategy['mistake_question_ids'], // 错题回顾的知识点优先级 'priority_knowledge_points' => array_merge( array_values($mistakeKnowledgePoints), // 错题知识点优先 array_column($weaknessFilter, 'kp_code') // 然后是薄弱点 ), // 错题回顾更注重针对性 'question_type_ratio' => [ '选择题' => 35, '填空题' => 30, '解答题' => 35, ], 'paper_name' => $params['paper_name'] ?? ('错题_' . now()->format('Ymd_His')), // 标记这是错题,用于后续处理 'is_mistake_exam' => true, 'weakness_filter' => $weaknessFilter, // 题目扩展统计 'question_expansion_stats' => $expansionStats ]); Log::info('ExamTypeStrategy: 错题参数构建完成', [ 'intensity' => $intensity, 'total_questions_needed' => $totalQuestions, 'mistake_question_ids_count' => count($enhanced['mistake_question_ids']), 'priority_knowledge_points_count' => count($enhanced['priority_knowledge_points']), 'question_expansion_stats' => $enhanced['question_expansion_stats'], 'weakness_count' => count($weaknessFilter) ]); return $enhanced; } /** * 专项练习:针对特定技能或知识点练习 */ private function buildPracticeParams(array $params): array { Log::info('ExamTypeStrategy: 构建专项练习参数', $params); $studentId = $params['student_id'] ?? null; $practiceOptions = $params['practice_options'] ?? []; $weaknessThreshold = $practiceOptions['weakness_threshold'] ?? 0.7; $intensity = $practiceOptions['intensity'] ?? 'medium'; $focusWeaknesses = $practiceOptions['focus_weaknesses'] ?? true; // 根据强度调整难度配比 $difficultyRatio = match($intensity) { 'low' => [ '基础' => 60, '中等' => 35, '拔高' => 5, ], 'medium' => [ '基础' => 45, '中等' => 40, '拔高' => 15, ], 'high' => [ '基础' => 30, '中等' => 45, '拔高' => 25, ], default => [ '基础' => 45, '中等' => 40, '拔高' => 15, ] }; // 获取学生薄弱点 $weaknessFilter = []; if ($studentId && $focusWeaknesses) { $weaknessFilter = $this->getStudentWeaknesses($studentId, $weaknessThreshold); Log::info('ExamTypeStrategy: 获取到学生薄弱点', [ 'student_id' => $studentId, 'weakness_threshold' => $weaknessThreshold, 'weakness_count' => count($weaknessFilter) ]); } // 优先使用薄弱点知识点,如果没有则使用用户选择的知识点 $kpCodes = $params['kp_codes'] ?? []; if ($studentId && empty($kpCodes) && !empty($weaknessFilter)) { $kpCodes = array_column($weaknessFilter, 'kp_code'); Log::info('ExamTypeStrategy: 使用薄弱点作为知识点', [ 'kp_codes' => $kpCodes ]); } $enhanced = array_merge($params, [ 'difficulty_ratio' => $difficultyRatio, 'kp_codes' => $kpCodes, // 专项练习更注重题型覆盖 'question_type_ratio' => [ '选择题' => 40, '填空题' => 30, '解答题' => 30, ], 'paper_name' => $params['paper_name'] ?? ('专项练习_' . now()->format('Ymd_His')), // 标记这是专项练习,用于后续处理 'is_practice_exam' => true, 'weakness_filter' => $weaknessFilter, ]); Log::info('ExamTypeStrategy: 专项练习参数构建完成', [ 'intensity' => $intensity, 'difficulty_ratio' => $enhanced['difficulty_ratio'], 'kp_codes_count' => count($enhanced['kp_codes']), 'weakness_count' => count($weaknessFilter) ]); return $enhanced; } /** * 教材同步:按教材章节出题 */ private function buildTextbookParams(array $params): array { Log::info('ExamTypeStrategy: 构建教材同步参数', $params); // 教材同步:按章节顺序,难度递增 $textbookOptions = $params['textbook_options'] ?? []; $enhanced = array_merge($params, [ // 教材同步:基础和中等题为主 'difficulty_ratio' => [ '基础' => 50, '中等' => 40, '拔高' => 10, ], 'question_type_ratio' => [ '选择题' => 40, '填空题' => 30, '解答题' => 30, ], 'paper_name' => $params['paper_name'] ?? ('教材同步_' . now()->format('Ymd_His')), 'textbook_options' => $textbookOptions, ]); return $enhanced; } /** * 知识点专练:单个或少量知识点深练 */ private function buildKnowledgeParams(array $params): array { Log::info('ExamTypeStrategy: 构建知识点专练参数', $params); // 知识点专练:深度挖掘,多角度考查 $knowledgeOptions = $params['knowledge_options'] ?? []; $enhanced = array_merge($params, [ // 知识点专练:难度分布更均匀 'difficulty_ratio' => [ '基础' => 35, '中等' => 45, '拔高' => 20, ], 'question_type_ratio' => [ '选择题' => 30, '填空题' => 35, '解答题' => 35, ], 'paper_name' => $params['paper_name'] ?? ('知识点专练_' . now()->format('Ymd_His')), 'knowledge_options' => $knowledgeOptions, ]); return $enhanced; } /** * 为摸底测试扩展知识点(确保覆盖全面) */ private function expandKpCodesForDiagnostic(array $kpCodes): array { if (!empty($kpCodes)) { return $kpCodes; } // 如果没有指定知识点,返回一些通用的数学知识点 return [ '一元二次方程', '二次函数', '旋转', '圆', '概率初步', ]; } /** * 获取学生薄弱点 */ private function getStudentWeaknesses(string $studentId, float $threshold): array { try { // 使用 StudentKnowledgeMastery 模型获取掌握度低于阈值的知识点 $weaknessRecords = StudentKnowledgeMastery::forStudent($studentId) ->weaknesses($threshold) ->orderByMastery('asc') ->limit(20) ->with('knowledgePoint') // 预加载知识点信息 ->get(); // 转换为统一格式 return $weaknessRecords->map(function ($record) { return [ 'kp_code' => $record->kp_code, 'kp_name' => $record->knowledgePoint->name ?? $record->kp_code, 'mastery' => (float) ($record->mastery_level ?? 0), 'attempts' => (int) ($record->total_attempts ?? 0), 'correct' => (int) ($record->correct_attempts ?? 0), 'incorrect' => (int) ($record->incorrect_attempts ?? 0), 'confidence' => (float) ($record->confidence_level ?? 0), 'trend' => $record->mastery_trend ?? 'stable', ]; })->toArray(); } catch (\Exception $e) { Log::error('ExamTypeStrategy: 获取学生薄弱点失败', [ 'student_id' => $studentId, 'threshold' => $threshold, 'error' => $e->getMessage() ]); return []; } } }