|
|
@@ -0,0 +1,311 @@
|
|
|
+<?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);
|
|
|
+ }
|
|
|
+}
|