| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- <?php
- namespace App\Services;
- use Illuminate\Support\Facades\Log;
- use App\Models\StudentKnowledgeMastery;
- use App\Models\MistakeRecord;
- use App\Models\Question;
- use App\Models\KnowledgePoint;
- class QuestionExpansionService
- {
- /**
- * 按优先级扩展题目数量
- * 优先级策略:
- * 1. 优先使用错题记录的题目
- * 2. 错题相同的知识点的其他题目
- * 3. 相同知识点的子知识点的题目
- * 4. 学生薄弱知识点相关题目
- * 5. 薄弱知识点的子知识点的题目
- */
- public function expandQuestions(array $baseParams, ?string $studentId, array $weaknessFilter, int $totalQuestions): array
- {
- Log::info('QuestionExpansionService: 开始题目扩展', [
- 'student_id' => $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));
- }
- }
|