浏览代码

按知识点出题

yemeishu 2 天之前
父节点
当前提交
da61bc1cb5

+ 30 - 1
app/Http/Controllers/Api/IntelligentExamController.php

@@ -80,7 +80,7 @@ class IntelligentExamController extends Controller
             'mistake_question_ids.*' => 'string',
             'callback_url' => 'nullable|url',  // 异步完成后推送通知的URL
             // 新增:组卷类型
-            'exam_type' => 'nullable|string|in:general,diagnostic,practice,mistake,textbook,knowledge',
+            'exam_type' => 'nullable|string|in:general,diagnostic,practice,mistake,textbook,knowledge,knowledge_points',
             // 新增:专项练习选项
             'practice_options' => 'nullable|array',
             'practice_options.weakness_threshold' => 'nullable|numeric|min:0|max:1',
@@ -94,6 +94,11 @@ class IntelligentExamController extends Controller
             'mistake_options.intensity' => 'nullable|string|in:low,medium,high',
             'mistake_options.include_new_questions' => 'nullable|boolean',
             'mistake_options.focus_weaknesses' => 'nullable|boolean',
+            // 新增:按知识点组卷选项
+            'knowledge_points_options' => 'nullable|array',
+            'knowledge_points_options.weakness_threshold' => 'nullable|numeric|min:0|max:1',
+            'knowledge_points_options.intensity' => 'nullable|string|in:low,medium,high',
+            'knowledge_points_options.focus_weaknesses' => 'nullable|boolean',
         ]);
 
         if ($validator->fails()) {
@@ -520,6 +525,30 @@ class IntelligentExamController extends Controller
             ];
         }
 
+        // 新增:处理按知识点组卷选项
+        if (isset($payload['knowledge_points_options'])) {
+            if (is_string($payload['knowledge_points_options'])) {
+                $decoded = json_decode($payload['knowledge_points_options'], true);
+                $payload['knowledge_points_options'] = is_array($decoded) ? $decoded : [];
+            } elseif (!is_array($payload['knowledge_points_options'])) {
+                $payload['knowledge_points_options'] = [];
+            }
+
+            // 设置默认值
+            $payload['knowledge_points_options'] = array_merge([
+                'weakness_threshold' => 0.7,
+                'intensity' => 'medium',
+                'focus_weaknesses' => true,
+            ], $payload['knowledge_points_options']);
+        } else {
+            // 如果没有提供 knowledge_points_options,创建默认值
+            $payload['knowledge_points_options'] = [
+                'weakness_threshold' => 0.7,
+                'intensity' => 'medium',
+                'focus_weaknesses' => true,
+            ];
+        }
+
         return $payload;
     }
 

+ 127 - 0
app/Services/ExamTypeStrategy.php

@@ -32,6 +32,7 @@ class ExamTypeStrategy
             'mistake' => $this->buildMistakeParams($baseParams),
             'textbook' => $this->buildTextbookParams($baseParams),
             'knowledge' => $this->buildKnowledgeParams($baseParams),
+            'knowledge_points' => $this->buildKnowledgePointsParams($baseParams),
             default => $this->buildGeneralParams($baseParams)
         };
     }
@@ -332,6 +333,132 @@ class ExamTypeStrategy
         return $enhanced;
     }
 
+    /**
+     * 按知识点组卷:根据指定知识点数组智能选题
+     * 优先级策略:
+     * 1. 直接关联知识点题目(来自输入数组)
+     * 2. 相同知识点其他题目
+     * 3. 子知识点题目(下探1层)
+     * 4. 薄弱点题目比例调整
+     * 5. 子知识点题目(下探2层)
+     */
+    private function buildKnowledgePointsParams(array $params): array
+    {
+        Log::info('ExamTypeStrategy: 构建按知识点组卷参数', $params);
+
+        $studentId = $params['student_id'] ?? null;
+        $totalQuestions = $params['total_questions'] ?? 20;
+        $knowledgePointsOptions = $params['knowledge_points_options'] ?? [];
+        $weaknessThreshold = $knowledgePointsOptions['weakness_threshold'] ?? 0.7;
+        $focusWeaknesses = $knowledgePointsOptions['focus_weaknesses'] ?? true;
+        $intensity = $knowledgePointsOptions['intensity'] ?? 'medium';
+
+        // 获取用户指定的知识点数组
+        $targetKnowledgePoints = $params['kp_codes'] ?? [];
+        if (empty($targetKnowledgePoints)) {
+            Log::warning('ExamTypeStrategy: 未指定知识点数组,使用默认策略');
+            return $this->buildGeneralParams($params);
+        }
+
+        Log::info('ExamTypeStrategy: 目标知识点数组', [
+            'target_knowledge_points' => $targetKnowledgePoints,
+            'count' => count($targetKnowledgePoints)
+        ]);
+
+        // 根据强度调整难度配比
+        $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)
+            ]);
+        }
+
+        // 检查目标知识点中哪些是薄弱点
+        $weaknessKpCodes = array_column($weaknessFilter, 'kp_code');
+        $targetWeaknessKps = array_intersect($targetKnowledgePoints, $weaknessKpCodes);
+
+        Log::info('ExamTypeStrategy: 目标知识点中的薄弱点', [
+            'target_weakness_kps' => $targetWeaknessKps,
+            'weakness_count' => count($targetWeaknessKps)
+        ]);
+
+        // 使用 QuestionExpansionService 按知识点优先级扩展题目
+        // 修改 expandQuestions 支持直接传入知识点数组
+        $questionStrategy = $this->questionExpansionService->expandQuestionsByKnowledgePoints(
+            $params,
+            $studentId,
+            $targetKnowledgePoints,
+            $weaknessFilter,
+            $totalQuestions
+        );
+
+        // 获取扩展统计
+        $expansionStats = $this->questionExpansionService->getExpansionStats($questionStrategy);
+
+        $enhanced = array_merge($params, [
+            'difficulty_ratio' => $difficultyRatio,
+            'kp_codes' => $targetKnowledgePoints, // 确保使用目标知识点
+            'mistake_question_ids' => $questionStrategy['mistake_question_ids'] ?? [],
+            // 优先级知识点:目标知识点 + 薄弱点
+            'priority_knowledge_points' => array_merge(
+                array_values($targetKnowledgePoints),
+                array_column($weaknessFilter, 'kp_code')
+            ),
+            'question_type_ratio' => [
+                '选择题' => 35,
+                '填空题' => 30,
+                '解答题' => 35,
+            ],
+            'paper_name' => $params['paper_name'] ?? ('知识点组卷_' . now()->format('Ymd_His')),
+            // 标记这是按知识点组卷,用于后续处理
+            'is_knowledge_points_exam' => true,
+            'weakness_filter' => $weaknessFilter,
+            // 目标知识点中的薄弱点(用于调整题目数量)
+            'target_weakness_kps' => array_values($targetWeaknessKps),
+            // 题目扩展统计
+            'question_expansion_stats' => $expansionStats
+        ]);
+
+        Log::info('ExamTypeStrategy: 按知识点组卷参数构建完成', [
+            'intensity' => $intensity,
+            'target_knowledge_points_count' => count($targetKnowledgePoints),
+            'target_weakness_kps_count' => count($targetWeaknessKps),
+            '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;
+    }
+
     /**
      * 为摸底测试扩展知识点(确保覆盖全面)
      */

+ 195 - 0
app/Services/QuestionExpansionService.php

@@ -308,4 +308,199 @@ class QuestionExpansionService
 
         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));
+    }
 }