Bläddra i källkod

智能出卷(教材组卷)

yemeishu 2 dagar sedan
förälder
incheckning
53ed01e1b5

+ 38 - 21
app/Http/Controllers/Api/IntelligentExamController.php

@@ -72,7 +72,7 @@ class IntelligentExamController extends Controller
             'skills' => 'nullable|array',
             'skills.*' => 'string',
             'question_type_ratio' => 'nullable|array',
-            'difficulty_ratio' => 'nullable|array',
+            // 'difficulty_ratio' 参数已废弃,使用 difficulty_category 控制难度分布
             'total_score' => 'nullable|numeric|min:1|max:1000',
             'mistake_ids' => 'nullable|array',
             'mistake_ids.*' => 'string',
@@ -85,6 +85,12 @@ class IntelligentExamController extends Controller
             // 错题本类型专用参数
             'paper_ids' => 'nullable|array',
             'paper_ids.*' => 'string',
+            // 新增:各组卷类型的专用参数
+            'textbook_id' => 'nullable|integer|min:1',           // 摸底和智能组卷专用
+            'chapter_id_list' => 'nullable|array',                // 教材组卷专用
+            'chapter_id_list.*' => 'integer|min:1',
+            'kp_code_list' => 'nullable|array',                   // 知识点组卷专用
+            'kp_code_list.*' => 'string',
             // 新增:专项练习选项
             'practice_options' => 'nullable|array',
             'practice_options.weakness_threshold' => 'nullable|numeric|min:0|max:1',
@@ -124,9 +130,9 @@ class IntelligentExamController extends Controller
         }
 
         $questionTypeRatio = $this->normalizeQuestionTypeRatio($data['question_type_ratio'] ?? []);
-        $difficultyRatio = $this->normalizeDifficultyRatio($data['difficulty_ratio'] ?? []);
+        // 注意: difficulty_ratio 参数已废弃,使用 difficulty_category 控制难度分布
         $paperName = $data['paper_name'] ?? ('智能试卷_' . now()->format('Ymd_His'));
-        $difficultyCategory = $this->normalizeDifficultyCategory($data['difficulty_category'] ?? null);
+        $difficultyCategory = $data['difficulty_category'] ?? 1; // 直接使用数字,不转换
         $mistakeIds = $data['mistake_ids'] ?? [];
         $mistakeQuestionIds = $data['mistake_question_ids'] ?? [];
         $paperIds = $data['paper_ids'] ?? [];
@@ -170,7 +176,7 @@ class IntelligentExamController extends Controller
                     'kp_codes' => $data['kp_codes'],
                     'skills' => $data['skills'] ?? [],
                     'question_type_ratio' => $questionTypeRatio,
-                    'difficulty_ratio' => $difficultyRatio,
+                    'difficulty_category' => $difficultyCategory, // 传递难度分类(数字)
                     'assemble_type' => $assembleType, // 新版组卷类型
                     'exam_type' => $data['exam_type'] ?? 'general', // 兼容旧版参数
                     'paper_ids' => $paperIds, // 错题本类型专用参数
@@ -489,6 +495,34 @@ class IntelligentExamController extends Controller
             }
         }
 
+        // 新增:处理组卷专用参数
+        foreach (['chapter_id_list', 'kp_code_list'] as $key) {
+            if (isset($payload[$key])) {
+                if (is_string($payload[$key])) {
+                    $raw = trim($payload[$key]);
+                    $payload[$key] = $raw === ''
+                        ? []
+                        : array_values(array_filter(array_map('trim', explode(',', $raw))));
+                } elseif (!is_array($payload[$key])) {
+                    $payload[$key] = [];
+                }
+            } else {
+                $payload[$key] = [];
+            }
+        }
+
+        // 处理 textbook_id:字符串转换为整数
+        if (isset($payload['textbook_id'])) {
+            if (is_string($payload['textbook_id'])) {
+                $payload['textbook_id'] = (int) trim($payload['textbook_id']);
+                if ($payload['textbook_id'] <= 0) {
+                    unset($payload['textbook_id']);
+                }
+            } elseif (!is_int($payload['textbook_id']) || $payload['textbook_id'] <= 0) {
+                unset($payload['textbook_id']);
+            }
+        }
+
         // 新增:处理组卷类型,默认值为 general
         if (!isset($payload['exam_type'])) {
             $payload['exam_type'] = 'general';
@@ -692,23 +726,6 @@ class IntelligentExamController extends Controller
         return array_merge($defaults, $normalized);
     }
 
-    private function normalizeDifficultyCategory(?string $category): string
-    {
-        if (!$category) {
-            return '基础';
-        }
-
-        $category = trim($category);
-        if (in_array($category, ['基础', '进阶', '中等', 'easy'])) {
-            return $category === 'easy' ? '基础' : $category;
-        }
-        if (in_array($category, ['拔高', '困难', 'hard', '竞赛'])) {
-            return '拔高';
-        }
-
-        return '基础';
-    }
-
     private function hydrateQuestions(array $questions, array $kpCodes): array
     {
         $normalized = [];

+ 5 - 2
app/Services/ExamTypeStrategy.php

@@ -1054,10 +1054,13 @@ class ExamTypeStrategy
                 ->values()
                 ->toArray();
 
-            Log::debug('ExamTypeStrategy: 查询卷子错题完成', [
+            Log::info('ExamTypeStrategy: 查询卷子错题完成', [
                 'paper_count' => count($paperIds),
+                'paper_ids_input' => $paperIds,
                 'mistake_record_count' => $mistakeRecords->count(),
-                'unique_question_count' => count($questionIds)
+                'unique_question_count' => count($questionIds),
+                'question_ids' => array_slice($questionIds, 0, 20), // 最多记录20个ID
+                'per_paper_stats' => $mistakeRecords->groupBy('paper_id')->map->count()->toArray()
             ]);
 
             return array_filter($questionIds);

+ 43 - 96
app/Services/LearningAnalyticsService.php

@@ -1277,11 +1277,7 @@ class LearningAnalyticsService
                 '填空题' => 30,
                 '解答题' => 30,
             ];
-            $difficultyRatio = $params['difficulty_ratio'] ?? [
-                '基础' => 50,
-                '中等' => 35,
-                '拔高' => 15,
-            ];
+            // 注意: difficulty_ratio 参数已废弃,使用 difficulty_category 控制难度分布
             $difficultyLevels = $params['difficulty_levels'] ?? [];
             // 如果用户没有选择任何难度,difficultyLevels 为空数组,表示随机难度
 
@@ -1331,7 +1327,7 @@ class LearningAnalyticsService
                 ]);
 
                 // 获取学生错题的详细信息
-                $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $difficultyRatio, 200, $mistakeQuestionIds);
+                $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, 200, $mistakeQuestionIds);
 
                 Log::info('LearningAnalyticsService: 错题获取完成', [
                     'priority_questions_count' => count($priorityQuestions),
@@ -1362,7 +1358,7 @@ class LearningAnalyticsService
                         'assemble_type' => $assembleType
                     ]);
 
-                    $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, $difficultyRatio, 200);
+                    $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, 200);
                     $allQuestions = array_merge($priorityQuestions, $additionalQuestions);
 
                     Log::info('getQuestionsFromBank 调用完成', [
@@ -1419,18 +1415,23 @@ class LearningAnalyticsService
             }
 
             // 3. 根据掌握度对题目进行筛选和排序
+            // 错题本类型:使用所有错题,不限制数量
+            $targetQuestionCount = $isMistakeBook ? count($allQuestions) : $totalQuestions;
+
             Log::info('开始调用 selectQuestionsByMastery', [
                 'input_count' => count($allQuestions),
-                'target_count' => $totalQuestions
+                'target_count' => $targetQuestionCount,
+                'is_mistake_book' => $isMistakeBook,
+                'assemble_type' => $assembleType,
+                'total_questions_param' => $totalQuestions
             ]);
 
             $startTime = microtime(true);
             $selectedQuestions = $this->selectQuestionsByMastery(
                 $allQuestions,
                 $studentId,
-                $totalQuestions,
+                $targetQuestionCount,
                 $questionTypeRatio,
-                $difficultyRatio,
                 $difficultyLevels,
                 $weaknessFilter
             );
@@ -1439,7 +1440,8 @@ class LearningAnalyticsService
             Log::info('题目筛选结果', [
                 'input_count' => count($allQuestions),
                 'selected_count' => count($selectedQuestions),
-                'target_count' => $totalQuestions,
+                'target_count' => $targetQuestionCount,
+                'is_mistake_book' => $isMistakeBook,
                 'select_time_ms' => round($selectTime, 2)
             ]);
 
@@ -1517,7 +1519,7 @@ class LearningAnalyticsService
      * 从本地题库获取题目(错题回顾优先)
      * 支持优先获取指定题目ID的题目
      */
-    private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], array $difficultyRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = []): array
+    private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = []): array
     {
         $startTime = microtime(true);
 
@@ -1548,7 +1550,7 @@ class LearningAnalyticsService
                 'skills' => $skills,
                 'total_needed' => $totalNeeded,
                 'question_type_ratio' => $questionTypeRatio,
-                'difficulty_ratio' => $difficultyRatio
+                'note' => '难度筛选由 QuestionLocalService 处理'
             ]);
 
             $query = \App\Models\Question::query();
@@ -1574,22 +1576,8 @@ class LearningAnalyticsService
                   ->where('solution', '!=', '')
                   ->where('solution', '!=', '[]');
 
-            // 按难度范围筛选
-            if (!empty($difficultyRatio)) {
-                $difficultyRanges = $this->buildDifficultyRanges($difficultyRatio);
-                $query->where(function ($q) use ($difficultyRanges) {
-                    $first = true;
-                    foreach ($difficultyRanges as $range) {
-                        if ($first) {
-                            $q->whereBetween('difficulty', [$range['min'], $range['max']]);
-                            $first = false;
-                        } else {
-                            $q->orWhereBetween('difficulty', [$range['min'], $range['max']]);
-                        }
-                    }
-                });
-                Log::info('应用难度筛选', ['difficulty_ranges' => $difficultyRanges]);
-            }
+            // 注意: 难度筛选由 QuestionLocalService 的难度分布系统处理
+            // 不在这里进行难度筛选,让 QuestionLocalService 做精确的难度分布
 
             // 限制数量并随机排序
             $query->limit($totalNeeded * 2) // 多取一些用于后续筛选
@@ -1623,13 +1611,9 @@ class LearningAnalyticsService
                 ];
             })->toArray();
 
-            // 按题型和难度配比筛选
-            $selectedQuestions = $this->selectQuestionsByRatio(
-                $formattedQuestions,
-                $totalNeeded,
-                $questionTypeRatio,
-                $difficultyRatio
-            );
+            // 注意: 题型和难度配比调整由 QuestionLocalService 处理
+            // 这里只做初步筛选,让 QuestionLocalService 做精确的配比调整
+            $selectedQuestions = array_slice($formattedQuestions, 0, $totalNeeded);
 
             Log::info('getQuestionsFromBank 完成', [
                 'selected_count' => count($selectedQuestions),
@@ -1679,8 +1663,11 @@ class LearningAnalyticsService
             })->toArray();
 
             Log::info('getLocalQuestionsByIds 获取成功', [
-                'count' => count($result),
-                'question_ids' => $questionIds
+                'requested_count' => count($questionIds),
+                'found_count' => count($result),
+                'missing_ids' => array_diff($questionIds, array_column($result, 'id')),
+                'question_ids' => array_slice($questionIds, 0, 20),
+                'found_question_ids' => array_slice(array_column($result, 'id'), 0, 20)
             ]);
 
             return $result;
@@ -1721,40 +1708,20 @@ class LearningAnalyticsService
     }
 
     /**
-     * 构建难度范围(根据配比)
-     */
-    private function buildDifficultyRanges(array $difficultyRatio): array
-    {
-        $ranges = [];
-
-        if (isset($difficultyRatio['基础'])) {
-            $ranges[] = ['min' => 0.0, 'max' => 0.4, 'label' => '基础'];
-        }
-        if (isset($difficultyRatio['中等'])) {
-            $ranges[] = ['min' => 0.4, 'max' => 0.7, 'label' => '中等'];
-        }
-        if (isset($difficultyRatio['拔高'])) {
-            $ranges[] = ['min' => 0.7, 'max' => 1.0, 'label' => '拔高'];
-        }
-
-        return $ranges;
-    }
-
-    /**
-     * 根据题型和难度配比选择题目
+     * 根据题型配比选择题目
+     * 注意: 难度配比调整由 QuestionLocalService 处理
      */
     private function selectQuestionsByRatio(
         array $questions,
         int $totalNeeded,
-        array $questionTypeRatio = [],
-        array $difficultyRatio = []
+        array $questionTypeRatio = []
     ): array {
         if (empty($questions)) {
             return [];
         }
 
         // 如果没有配比要求,直接返回
-        if (empty($questionTypeRatio) && empty($difficultyRatio)) {
+        if (empty($questionTypeRatio)) {
             return array_slice($questions, 0, $totalNeeded);
         }
 
@@ -1790,37 +1757,8 @@ class LearningAnalyticsService
             }
         }
 
-        // 如果还有空缺,按难度配比补充
-        $remaining = $totalNeeded - count($selected);
-        if ($remaining > 0 && !empty($difficultyRatio)) {
-            $difficultyRanges = $this->buildDifficultyRanges($difficultyRatio);
-
-            foreach ($difficultyRanges as $range) {
-                if ($remaining <= 0) break;
-
-                $diffQuestions = [];
-                foreach ($questions as $idx => $q) {
-                    if (in_array($idx, $usedIndices)) continue;
-
-                    $difficulty = (float) ($q['difficulty'] ?? 0);
-                    if ($difficulty >= $range['min'] && $difficulty < $range['max']) {
-                        $diffQuestions[] = ['idx' => $idx, 'question' => $q];
-                    }
-                }
-
-                // 随机选择
-                shuffle($diffQuestions);
-                $count = min($remaining, count($diffQuestions));
-
-                for ($i = 0; $i < $count; $i++) {
-                    $selected[] = $diffQuestions[$i]['question'];
-                    $usedIndices[] = $diffQuestions[$i]['idx'];
-                    $remaining--;
-                }
-            }
-        }
-
         // 如果还有空缺,补充剩余题目
+        // 注意: 难度配比调整由 QuestionLocalService 处理
         if (count($selected) < $totalNeeded) {
             foreach ($questions as $idx => $q) {
                 if (count($selected) >= $totalNeeded) break;
@@ -1842,7 +1780,6 @@ class LearningAnalyticsService
         ?string $studentId,
         int $totalQuestions,
         array $questionTypeRatio,
-        array $difficultyRatio,
         array $difficultyLevels,
         array $weaknessFilter
     ): array {
@@ -1852,6 +1789,16 @@ class LearningAnalyticsService
             'total_questions' => $totalQuestions
         ]);
 
+        // 错题本类型:使用所有题目,不进行权重分配和筛选
+        if ($totalQuestions >= count($questions)) {
+            Log::info('错题本类型:使用所有题目,跳过权重分配', [
+                'question_count' => count($questions),
+                'total_questions' => $totalQuestions,
+                'input_question_count' => func_num_args() > 0 ? count($questions) : 'N/A'
+            ]);
+            return $questions;
+        }
+
         // 如果未选择难度,则不过滤(随机生成所有难度)
         if (empty($difficultyLevels)) {
             Log::info('用户未选择难度,将随机生成所有难度的题目');
@@ -2001,8 +1948,8 @@ class LearningAnalyticsService
             'target_count' => $totalQuestions
         ]);
 
-        // 5. 按题型和难度进行微调
-        return $this->adjustQuestionsByRatio($selectedQuestions, $questionTypeRatio, $difficultyRatio, $totalQuestions);
+        // 5. 按题型进行微调(难度分布由 QuestionLocalService 处理)
+        return $this->adjustQuestionsByRatio($selectedQuestions, $questionTypeRatio, $totalQuestions);
     }
 
     /**
@@ -2030,7 +1977,7 @@ class LearningAnalyticsService
     /**
      * 根据题型和难度配比调整题目
      */
-    private function adjustQuestionsByRatio(array $questions, array $typeRatio, array $difficultyRatio, int $targetCount): array
+    private function adjustQuestionsByRatio(array $questions, array $typeRatio, int $targetCount): array
     {
         Log::info('开始题型配比调整', [
             'input_questions' => count($questions),