yemeishu пре 3 часа
родитељ
комит
c344e533cb

+ 2 - 0
app/Http/Controllers/Api/IntelligentExamController.php

@@ -173,7 +173,9 @@ class IntelligentExamController extends Controller
                     'student_id' => $data['student_id'],
                     'student_id' => $data['student_id'],
                     'grade' => $data['grade'] ?? null,
                     'grade' => $data['grade'] ?? null,
                     'total_questions' => $data['total_questions'],
                     'total_questions' => $data['total_questions'],
+                    // 【修复】同时支持 kp_codes 和 kp_code_list 两种参数名
                     'kp_codes' => $data['kp_codes'],
                     'kp_codes' => $data['kp_codes'],
+                    'kp_code_list' => $data['kp_code_list'] ?? $data['kp_codes'] ?? [],
                     'skills' => $data['skills'] ?? [],
                     'skills' => $data['skills'] ?? [],
                     'question_type_ratio' => $questionTypeRatio,
                     'question_type_ratio' => $questionTypeRatio,
                     'difficulty_category' => $difficultyCategory, // 传递难度分类(数字)
                     'difficulty_category' => $difficultyCategory, // 传递难度分类(数字)

+ 93 - 22
app/Services/ExamTypeStrategy.php

@@ -129,15 +129,15 @@ class ExamTypeStrategy
                 '中等' => $distribution['medium']['percentage'],
                 '中等' => $distribution['medium']['percentage'],
                 '拔高' => $distribution['high']['percentage'],
                 '拔高' => $distribution['high']['percentage'],
             ],
             ],
-            // 新增:启用难度分布选题标志
+            // 启用难度分布选题标志
             'enable_difficulty_distribution' => true,
             'enable_difficulty_distribution' => true,
+            // 【重要】保持原始的 difficulty_category,不修改
             'difficulty_category' => $difficultyCategory,
             'difficulty_category' => $difficultyCategory,
         ]);
         ]);
 
 
         Log::info('ExamTypeStrategy: 难度分布应用完成', [
         Log::info('ExamTypeStrategy: 难度分布应用完成', [
             'category' => $difficultyCategory,
             'category' => $difficultyCategory,
-            'distribution' => $distribution,
-            'ranges' => $difficultyDistributionConfig['ranges']
+            'distribution' => $distribution
         ]);
         ]);
 
 
         return $enhanced;
         return $enhanced;
@@ -887,7 +887,7 @@ class ExamTypeStrategy
             'kp_codes' => $kpCodeList,
             'kp_codes' => $kpCodeList,
             'exclude_question_ids' => $answeredQuestionIds,
             'exclude_question_ids' => $answeredQuestionIds,
             'paper_name' => $params['paper_name'] ?? ('知识点组卷_' . now()->format('Ymd_His')),
             'paper_name' => $params['paper_name'] ?? ('知识点组卷_' . now()->format('Ymd_His')),
-            // 知识点组卷:注重题型平衡
+            // 知识点组卷:注重题型平衡(恢复原有配比)
             'question_type_ratio' => [
             'question_type_ratio' => [
                 '选择题' => 35,
                 '选择题' => 35,
                 '填空题' => 30,
                 '填空题' => 30,
@@ -914,6 +914,7 @@ class ExamTypeStrategy
     /**
     /**
      * 教材组卷 (assembleType=3)
      * 教材组卷 (assembleType=3)
      * 根据 chapter_id_list 查询课本章节,获取知识点,然后组卷
      * 根据 chapter_id_list 查询课本章节,获取知识点,然后组卷
+     * 优化:按textbook_catalog_node_id筛选题目,添加章节知识点数量统计
      */
      */
     private function buildTextbookAssembleParams(array $params): array
     private function buildTextbookAssembleParams(array $params): array
     {
     {
@@ -934,21 +935,30 @@ class ExamTypeStrategy
             'total_questions' => $totalQuestions
             'total_questions' => $totalQuestions
         ]);
         ]);
 
 
-        // 第一步:根据章节ID查询知识点关联
+        // 【优化】第一步:根据章节ID查询知识点关联,并统计每个章节的知识点数量
         $kpCodes = $this->getKnowledgePointsFromChapters($chapterIdList);
         $kpCodes = $this->getKnowledgePointsFromChapters($chapterIdList);
 
 
+        // 【新增】获取每个章节对应的知识点数量统计
+        $chapterKnowledgePointStats = $this->getChapterKnowledgePointStats($chapterIdList);
+
+        Log::info('ExamTypeStrategy: 获取章节知识点统计', [
+            'chapter_stats' => $chapterKnowledgePointStats,
+            'total_chapters' => count($chapterIdList)
+        ]);
+
+        // 【重要】即使没有找到知识点关联,也保留textbook_catalog_node_ids参数用于题目筛选
         if (empty($kpCodes)) {
         if (empty($kpCodes)) {
-            Log::warning('ExamTypeStrategy: 未找到章节知识点关联', [
-                'chapter_id_list' => $chapterIdList
+            Log::warning('ExamTypeStrategy: 未找到章节知识点关联,但保留章节筛选参数', [
+                'chapter_id_list' => $chapterIdList,
+                'note' => '将在LearningAnalyticsService中按textbook_catalog_nodes_id字段筛选题目'
+            ]);
+        } else {
+            Log::info('ExamTypeStrategy: 获取章节知识点', [
+                'kp_count' => count($kpCodes),
+                'kp_codes' => array_slice($kpCodes, 0, 5)
             ]);
             ]);
-            return $this->buildGeneralParams($params);
         }
         }
 
 
-        Log::info('ExamTypeStrategy: 获取章节知识点', [
-            'kp_count' => count($kpCodes),
-            'kp_codes' => array_slice($kpCodes, 0, 5)
-        ]);
-
         // 第二步:如果有学生ID,获取已做过的题目ID列表(用于排除)
         // 第二步:如果有学生ID,获取已做过的题目ID列表(用于排除)
         $answeredQuestionIds = [];
         $answeredQuestionIds = [];
         if ($studentId) {
         if ($studentId) {
@@ -959,10 +969,15 @@ class ExamTypeStrategy
             ]);
             ]);
         }
         }
 
 
+        // 【优化】第三步:按textbook_catalog_node_id筛选题目,添加筛选条件
+        // 在LearningAnalyticsService中会使用这些参数进行题目筛选
+        $textbookCatalogNodeIds = $chapterIdList; // 直接使用章节ID作为textbook_catalog_node_id筛选条件
+
         // 组装增强参数
         // 组装增强参数
         $enhanced = array_merge($params, [
         $enhanced = array_merge($params, [
-            'kp_codes' => $kpCodes,
+            'kp_codes' => $kpCodes, // 可能为空,但仍保留
             'chapter_id_list' => $chapterIdList,
             'chapter_id_list' => $chapterIdList,
+            'textbook_catalog_node_ids' => $textbookCatalogNodeIds, // 【重要】即使kpCodes为空也设置此参数
             'exclude_question_ids' => $answeredQuestionIds,
             'exclude_question_ids' => $answeredQuestionIds,
             'paper_name' => $params['paper_name'] ?? ('教材组卷_' . now()->format('Ymd_His')),
             'paper_name' => $params['paper_name'] ?? ('教材组卷_' . now()->format('Ymd_His')),
             // 教材组卷:按教材特点分配题型
             // 教材组卷:按教材特点分配题型
@@ -978,13 +993,16 @@ class ExamTypeStrategy
             ],
             ],
             'question_category' => 0, // question_category=0 代表普通题目
             'question_category' => 0, // question_category=0 代表普通题目
             'is_textbook_assemble' => true,
             'is_textbook_assemble' => true,
+            // 【新增】章节知识点数量统计
+            'chapter_knowledge_point_stats' => $chapterKnowledgePointStats,
         ]);
         ]);
 
 
         Log::info('ExamTypeStrategy: 教材组卷参数构建完成', [
         Log::info('ExamTypeStrategy: 教材组卷参数构建完成', [
             'chapter_count' => count($chapterIdList),
             'chapter_count' => count($chapterIdList),
             'kp_count' => count($kpCodes),
             'kp_count' => count($kpCodes),
             'exclude_count' => count($answeredQuestionIds),
             'exclude_count' => count($answeredQuestionIds),
-            'total_questions' => $totalQuestions
+            'total_questions' => $totalQuestions,
+            'chapter_stats' => $chapterKnowledgePointStats
         ]);
         ]);
 
 
         return $enhanced;
         return $enhanced;
@@ -1186,18 +1204,19 @@ class ExamTypeStrategy
     private function getStudentAnsweredQuestionIds(string $studentId, array $kpCodes): array
     private function getStudentAnsweredQuestionIds(string $studentId, array $kpCodes): array
     {
     {
         try {
         try {
-            // 查询 student_answer_questions 表
-            $questionIds = DB::table('student_answer_questions')
+            // 【修复】查询 student_answer_questions 表,不使用 kp_code 过滤(该字段可能不存在)
+            $query = DB::table('student_answer_questions')
                 ->where('student_id', $studentId)
                 ->where('student_id', $studentId)
-                ->whereIn('kp_code', $kpCodes)
-                ->distinct()
-                ->pluck('question_id')
-                ->toArray();
+                ->distinct();
+
+            // 如果有 question_id 字段,直接查询
+            $questionIds = $query->pluck('question_id')->toArray();
 
 
             Log::debug('ExamTypeStrategy: 查询学生已答题目', [
             Log::debug('ExamTypeStrategy: 查询学生已答题目', [
                 'student_id' => $studentId,
                 'student_id' => $studentId,
                 'kp_count' => count($kpCodes),
                 'kp_count' => count($kpCodes),
-                'answered_count' => count($questionIds)
+                'answered_count' => count($questionIds),
+                'note' => '只按学生ID过滤,不按kp_code过滤'
             ]);
             ]);
 
 
             return array_filter($questionIds); // 移除空值
             return array_filter($questionIds); // 移除空值
@@ -1287,4 +1306,56 @@ class ExamTypeStrategy
             return [];
             return [];
         }
         }
     }
     }
+
+    /**
+     * 【新增】获取每个章节对应的知识点数量统计
+     * 通过textbook_chapter_knowledge_relation表关联查询
+     *
+     * @param array $chapterIdList 章节ID列表
+     * @return array 章节知识点数量统计
+     */
+    private function getChapterKnowledgePointStats(array $chapterIdList): array
+    {
+        try {
+            // 查询每个章节对应的知识点数量
+            $stats = DB::table('textbook_chapter_knowledge_relation as tckr')
+                ->join('textbook_catalog_nodes as tcn', 'tckr.catalog_chapter_id', '=', 'tcn.id')
+                ->whereIn('tckr.catalog_chapter_id', $chapterIdList)
+                ->select(
+                    'tckr.catalog_chapter_id as chapter_id',
+                    'tcn.title as chapter_title',
+                    DB::raw('COUNT(DISTINCT tckr.kp_code) as knowledge_point_count')
+                )
+                ->groupBy('tckr.catalog_chapter_id', 'tcn.title')
+                ->orderBy('tckr.catalog_chapter_id')
+                ->get();
+
+            // 转换为数组格式
+            $result = [];
+            foreach ($stats as $stat) {
+                $result[] = [
+                    'chapter_id' => $stat->chapter_id,
+                    'chapter_title' => $stat->chapter_title,
+                    'knowledge_point_count' => (int) $stat->knowledge_point_count
+                ];
+            }
+
+            Log::debug('ExamTypeStrategy: 获取章节知识点统计', [
+                'chapter_count' => count($chapterIdList),
+                'stats_count' => count($result),
+                'stats' => $result
+            ]);
+
+            return $result;
+
+        } catch (\Exception $e) {
+            Log::error('ExamTypeStrategy: 获取章节知识点统计失败', [
+                'chapter_id_list' => $chapterIdList,
+                'error' => $e->getMessage()
+            ]);
+
+            // 返回空数组,不影响组卷流程
+            return [];
+        }
+    }
 }
 }

+ 236 - 96
app/Services/LearningAnalyticsService.php

@@ -1270,7 +1270,13 @@ class LearningAnalyticsService
             $studentId = $params['student_id'] ?? null;
             $studentId = $params['student_id'] ?? null;
             $grade = $params['grade'] ?? null; // 用户选择的年级
             $grade = $params['grade'] ?? null; // 用户选择的年级
             $totalQuestions = $params['total_questions'] ?? 20;
             $totalQuestions = $params['total_questions'] ?? 20;
-            $kpCodes = $params['kp_codes'] ?? [];
+
+            // 【修复】参数映射:支持 kp_codes 和 kp_code_list 两种参数名
+            $kpCodes = $params['kp_codes'] ?? $params['kp_code_list'] ?? [];
+            if (!is_array($kpCodes)) {
+                $kpCodes = [];
+            }
+
             $skills = $params['skills'] ?? [];
             $skills = $params['skills'] ?? [];
             $questionTypeRatio = $params['question_type_ratio'] ?? [
             $questionTypeRatio = $params['question_type_ratio'] ?? [
                 '选择题' => 40,
                 '选择题' => 40,
@@ -1343,7 +1349,7 @@ class LearningAnalyticsService
                 }
                 }
 
 
                 // 获取学生错题的详细信息
                 // 获取学生错题的详细信息
-                $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $truncatedMistakeIds, [], null);
+                $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $truncatedMistakeIds, [], null, null);
 
 
                 Log::info('LearningAnalyticsService: 错题获取完成', [
                 Log::info('LearningAnalyticsService: 错题获取完成', [
                     'priority_questions_count' => count($priorityQuestions),
                     'priority_questions_count' => count($priorityQuestions),
@@ -1381,7 +1387,7 @@ class LearningAnalyticsService
                     // 使用原卷子题目补充到目标数量
                     // 使用原卷子题目补充到目标数量
                     $additionalNeeded = max(5, count($priorityQuestions)) - count($priorityQuestions);
                     $additionalNeeded = max(5, count($priorityQuestions)) - count($priorityQuestions);
                     $paperQuestionIds = array_diff($paperQuestionIds, array_column($priorityQuestions, 'id'));
                     $paperQuestionIds = array_diff($paperQuestionIds, array_column($priorityQuestions, 'id'));
-                    $additionalPaperQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $additionalNeeded, $paperQuestionIds, [], null);
+                    $additionalPaperQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $additionalNeeded, $paperQuestionIds, [], null, null);
 
 
                     $allQuestions = array_merge($priorityQuestions, $additionalPaperQuestions);
                     $allQuestions = array_merge($priorityQuestions, $additionalPaperQuestions);
                     Log::info('LearningAnalyticsService: 错题本题目补充完成', [
                     Log::info('LearningAnalyticsService: 错题本题目补充完成', [
@@ -1399,7 +1405,7 @@ class LearningAnalyticsService
                 // 获取原卷子的所有题目
                 // 获取原卷子的所有题目
                 $paperQuestionIds = $this->getPaperAllQuestions($params['paper_ids']);
                 $paperQuestionIds = $this->getPaperAllQuestions($params['paper_ids']);
                 if (!empty($paperQuestionIds)) {
                 if (!empty($paperQuestionIds)) {
-                    $allQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $paperQuestionIds, [], null);
+                    $allQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $paperQuestionIds, [], null, null);
 
 
                     // 检查是否获取到题目
                     // 检查是否获取到题目
                     if (empty($allQuestions)) {
                     if (empty($allQuestions)) {
@@ -1440,7 +1446,10 @@ class LearningAnalyticsService
                         'assemble_type' => $assembleType
                         'assemble_type' => $assembleType
                     ]);
                     ]);
 
 
-                    $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, $maxQuestions, [], [], $questionCategory);
+                    // 【优化】获取textbook_catalog_node_ids参数(教材组卷时使用)
+                    $textbookCatalogNodeIds = $params['textbook_catalog_node_ids'] ?? null;
+
+                    $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, $maxQuestions, [], [], $questionCategory, $textbookCatalogNodeIds);
                     $allQuestions = array_merge($priorityQuestions, $additionalQuestions);
                     $allQuestions = array_merge($priorityQuestions, $additionalQuestions);
 
 
                     Log::info('getQuestionsFromBank 调用完成', [
                     Log::info('getQuestionsFromBank 调用完成', [
@@ -1515,7 +1524,8 @@ class LearningAnalyticsService
                 $targetQuestionCount,
                 $targetQuestionCount,
                 $questionTypeRatio,
                 $questionTypeRatio,
                 $difficultyLevels,
                 $difficultyLevels,
-                $weaknessFilter
+                $weaknessFilter,
+                $assembleType  // 新增assembleType参数
             );
             );
             $selectTime = (microtime(true) - $startTime) * 1000;
             $selectTime = (microtime(true) - $startTime) * 1000;
 
 
@@ -1535,14 +1545,24 @@ class LearningAnalyticsService
                 ];
                 ];
             }
             }
 
 
-            // 如果启用了难度分布且不是排除类型,则应用难度分布
+            // 【恢复】简化难度分布检查
             $difficultyCategory = $params['difficulty_category'] ?? 1;
             $difficultyCategory = $params['difficulty_category'] ?? 1;
             $enableDistribution = $params['enable_difficulty_distribution'] ?? false;
             $enableDistribution = $params['enable_difficulty_distribution'] ?? false;
             $isExcludedType = ($assembleType === 5); // 只有错题本类型(assembleType=5)不应用难度分布
             $isExcludedType = ($assembleType === 5); // 只有错题本类型(assembleType=5)不应用难度分布
 
 
+            // 【重要】添加详细的difficulty_category追踪日志
+            Log::info('LearningAnalyticsService: 难度分布检查开始', [
+                'input_difficulty_category' => $difficultyCategory,
+                'assemble_type' => $assembleType,
+                'enable_distribution' => $enableDistribution,
+                'is_excluded_type' => $isExcludedType,
+                'selected_questions_before' => count($selectedQuestions),
+                'params_keys' => array_keys($params)
+            ]);
+
             if ($enableDistribution && !$isExcludedType) {
             if ($enableDistribution && !$isExcludedType) {
                 Log::info('LearningAnalyticsService: 应用难度系数分布', [
                 Log::info('LearningAnalyticsService: 应用难度系数分布', [
-                    'difficulty_category' => $difficultyCategory,
+                    'difficulty_category_before' => $difficultyCategory,
                     'assemble_type' => $assembleType,
                     'assemble_type' => $assembleType,
                     'before_count' => count($selectedQuestions)
                     'before_count' => count($selectedQuestions)
                 ]);
                 ]);
@@ -1560,11 +1580,13 @@ class LearningAnalyticsService
                     );
                     );
 
 
                     Log::info('LearningAnalyticsService: 难度分布应用完成', [
                     Log::info('LearningAnalyticsService: 难度分布应用完成', [
+                        'difficulty_category_after' => $difficultyCategory,
                         'after_count' => count($selectedQuestions)
                         'after_count' => count($selectedQuestions)
                     ]);
                     ]);
                 } catch (\Exception $e) {
                 } catch (\Exception $e) {
                     Log::warning('LearningAnalyticsService: 难度分布应用失败,继续使用原结果', [
                     Log::warning('LearningAnalyticsService: 难度分布应用失败,继续使用原结果', [
-                        'error' => $e->getMessage()
+                        'error' => $e->getMessage(),
+                        'difficulty_category_when_error' => $difficultyCategory
                     ]);
                     ]);
                 }
                 }
             }
             }
@@ -1580,7 +1602,10 @@ class LearningAnalyticsService
                         return in_array($q['kp_code'] ?? '', $weaknessFilter);
                         return in_array($q['kp_code'] ?? '', $weaknessFilter);
                     })) : 0,
                     })) : 0,
                     'difficulty_distribution_applied' => $enableDistribution && !$isExcludedType,
                     'difficulty_distribution_applied' => $enableDistribution && !$isExcludedType,
-                    'difficulty_category' => $difficultyCategory
+                    'difficulty_category' => $difficultyCategory,
+                    // 【新增】章节知识点数量统计(教材组卷时)
+                    'chapter_knowledge_point_stats' => $params['chapter_knowledge_point_stats'] ?? null,
+                    'textbook_catalog_node_ids' => $params['textbook_catalog_node_ids'] ?? null
                 ]
                 ]
             ];
             ];
         } catch (\Exception $e) {
         } catch (\Exception $e) {
@@ -1600,8 +1625,9 @@ class LearningAnalyticsService
     /**
     /**
      * 从本地题库获取题目(错题回顾优先)
      * 从本地题库获取题目(错题回顾优先)
      * 支持优先获取指定题目ID的题目
      * 支持优先获取指定题目ID的题目
+     * 【优化】新增textbookCatalogNodeIds参数,支持按textbook_catalog_node_id筛选题目
      */
      */
-    private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = [], array $excludeQuestionIds = [], ?int $questionCategory = null): array
+    private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = [], array $excludeQuestionIds = [], ?int $questionCategory = null, ?array $textbookCatalogNodeIds = null): array
     {
     {
         $startTime = microtime(true);
         $startTime = microtime(true);
 
 
@@ -1667,6 +1693,12 @@ class LearningAnalyticsService
                 Log::info('应用题目分类筛选', ['question_category' => $questionCategory]);
                 Log::info('应用题目分类筛选', ['question_category' => $questionCategory]);
             }
             }
 
 
+            // 【优化】按教材章节节点筛选(textbook_catalog_nodes_id)
+            if (!empty($textbookCatalogNodeIds)) {
+                $query->whereIn('textbook_catalog_nodes_id', $textbookCatalogNodeIds);
+                Log::info('应用教材章节节点筛选', ['textbook_catalog_nodes_ids' => $textbookCatalogNodeIds]);
+            }
+
             // 筛选有解题思路的题目
             // 筛选有解题思路的题目
             $query->whereNotNull('solution')
             $query->whereNotNull('solution')
                   ->where('solution', '!=', '')
                   ->where('solution', '!=', '')
@@ -1675,17 +1707,30 @@ class LearningAnalyticsService
             // 注意: 难度筛选由 QuestionLocalService 的难度分布系统处理
             // 注意: 难度筛选由 QuestionLocalService 的难度分布系统处理
             // 不在这里进行难度筛选,让 QuestionLocalService 做精确的难度分布
             // 不在这里进行难度筛选,让 QuestionLocalService 做精确的难度分布
 
 
-            // 限制数量并随机排序
-            $query->limit($totalNeeded * 2) // 多取一些用于后续筛选
-                  ->inRandomOrder();
+            // 【重要】移除数量限制,获取所有符合条件的题目
+            // 不使用limit()限制查询结果,让后续处理逻辑决定最终数量
+            $query->inRandomOrder();
 
 
             $questions = $query->get();
             $questions = $query->get();
 
 
             Log::info('getQuestionsFromBank: 查询完成', [
             Log::info('getQuestionsFromBank: 查询完成', [
                 'raw_count' => $questions->count(),
                 'raw_count' => $questions->count(),
+                'total_needed' => $totalNeeded,
+                'note' => '移除limit限制,获取所有符合条件的题目',
                 'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
                 'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
             ]);
             ]);
 
 
+            // 【重要】添加更详细的日志来追踪题目筛选过程
+            Log::info('getQuestionsFromBank: 题目筛选过程详情', [
+                'database_query_count' => $questions->count(),
+                'kp_codes_filter' => $kpCodes,
+                'skills_filter' => $skills,
+                'exclude_count' => count($excludeQuestionIds),
+                'question_category' => $questionCategory,
+                'textbook_catalog_node_ids_filter' => $textbookCatalogNodeIds,
+                'note' => '将返回所有符合条件的题目,不限制数量'
+            ]);
+
             // 转换为标准格式
             // 转换为标准格式
             $formattedQuestions = $questions->map(function ($q) {
             $formattedQuestions = $questions->map(function ($q) {
                 return [
                 return [
@@ -1707,12 +1752,13 @@ class LearningAnalyticsService
                 ];
                 ];
             })->toArray();
             })->toArray();
 
 
-            // 注意: 题型和难度配比调整由 QuestionLocalService 处理
-            // 这里只做初步筛选,让 QuestionLocalService 做精确的配比调整
-            $selectedQuestions = array_slice($formattedQuestions, 0, $totalNeeded);
+            // 【重要】返回所有符合条件的题目,不限制数量
+            // 让上层调用者根据需要选择题目数量
+            $selectedQuestions = $formattedQuestions;
 
 
             Log::info('getQuestionsFromBank 完成', [
             Log::info('getQuestionsFromBank 完成', [
-                'selected_count' => count($selectedQuestions),
+                'final_count' => count($selectedQuestions),
+                'raw_database_count' => $questions->count(),
                 'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
                 'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
             ]);
             ]);
 
 
@@ -1922,7 +1968,8 @@ class LearningAnalyticsService
         int $totalQuestions,
         int $totalQuestions,
         array $questionTypeRatio,
         array $questionTypeRatio,
         array $difficultyLevels,
         array $difficultyLevels,
-        array $weaknessFilter
+        array $weaknessFilter,
+        int $assembleType  // 新增assembleType参数
     ): array {
     ): array {
         Log::info('selectQuestionsByMastery 开始', [
         Log::info('selectQuestionsByMastery 开始', [
             'question_count' => count($questions),
             'question_count' => count($questions),
@@ -1930,32 +1977,30 @@ class LearningAnalyticsService
             'total_questions' => $totalQuestions
             'total_questions' => $totalQuestions
         ]);
         ]);
 
 
-        // 错题本类型:使用所有题目,不进行权重分配和筛选
-        if ($totalQuestions >= count($questions)) {
-            Log::info('错题本类型:使用所有题目,跳过权重分配', [
+        // 【修复】题目数量处理逻辑:无论题目数量多少,都要进行权重分配和筛选
+        // 如果题目数量超过目标,则截取到目标数量
+        // 如果题目数量不足,则使用所有题目,并记录警告
+        if (count($questions) > $totalQuestions) {
+            Log::info('题目数量超过目标,进行截取', [
                 'question_count' => count($questions),
                 'question_count' => count($questions),
                 'total_questions' => $totalQuestions,
                 'total_questions' => $totalQuestions,
-                'input_question_count' => func_num_args() > 0 ? count($questions) : 'N/A'
+                'note' => '将按权重选择最合适的题目'
             ]);
             ]);
-            return $questions;
-        }
-
-        // 如果未选择难度,则不过滤(随机生成所有难度)
-        if (empty($difficultyLevels)) {
-            Log::info('用户未选择难度,将随机生成所有难度的题目');
-            // 不过滤任何题目,保留所有难度
         } else {
         } else {
-            // 按难度筛掉不在选择范围内的题目
-            $questions = array_values(array_filter($questions, function ($q) use ($difficultyLevels) {
-                $d = $q['difficulty'] ?? null;
-                if ($d === null) return true; // 无难度信息则保留
-                $level = $this->mapDifficultyLevel((float)$d);
-                return in_array($level, $difficultyLevels);
-            }));
+            Log::warning('题目数量不足,将使用所有可用题目', [
+                'available_count' => count($questions),
+                'requested_count' => $totalQuestions,
+                'note' => '可能需要补充题库或放宽筛选条件'
+            ]);
         }
         }
 
 
-        Log::info('难度筛选完成', [
-            'after_filter_count' => count($questions)
+        // 【移除】删除多余的难度筛选逻辑
+        // 题目本身就有难度系数,QuestionLocalService的难度分布系统会处理题目分布
+        // 不需要额外的难度筛选,让题目保持原始的难度分布
+
+        Log::info('跳过多余的难度筛选,使用题目原始难度分布', [
+            'question_count' => count($questions),
+            'note' => '由QuestionLocalService处理难度分布'
         ]);
         ]);
 
 
         // 1. 按知识点分组
         // 1. 按知识点分组
@@ -2085,11 +2130,20 @@ class LearningAnalyticsService
             $questionsByType[$type][] = $q;
             $questionsByType[$type][] = $q;
         }
         }
 
 
-        // ========== 步骤1:按知识点分组,优先选不同知识点 ==========
+        // ========== 步骤1:按题型分配题目 ==========
         $selectedQuestions = [];
         $selectedQuestions = [];
+
+        // 【区分】根据assembleType决定是否使用知识点优先机制
+        $useKnowledgePointPriority = ($assembleType === 0); // 摸底测试需要知识点优先
         $kpSelected = []; // 已选知识点记录
         $kpSelected = []; // 已选知识点记录
 
 
-        // 先确保每种题型至少选1题(来自不同知识点)
+        Log::info('selectQuestionsByMastery: 知识点优先策略', [
+            'assemble_type' => $assembleType,
+            'use_knowledge_point_priority' => $useKnowledgePointPriority,
+            'note' => $useKnowledgePointPriority ? '摸底测试:需要均衡分配知识点' : '知识点组卷:允许同一知识点选多题'
+        ]);
+
+        // 确保每种题型至少选1题
         foreach (['choice', 'fill', 'answer'] as $type) {
         foreach (['choice', 'fill', 'answer'] as $type) {
             if (empty($questionsByType[$type])) {
             if (empty($questionsByType[$type])) {
                 Log::warning('题型分配:题型无题目', ['type' => $type]);
                 Log::warning('题型分配:题型无题目', ['type' => $type]);
@@ -2105,78 +2159,127 @@ class LearningAnalyticsService
                 return $weightB <=> $weightA;
                 return $weightB <=> $weightA;
             });
             });
 
 
-            // 选择第一个未选过知识点的题目
-            foreach ($questionsByType[$type] as $q) {
-                $kpCode = $q['kp_code'] ?? '';
-                if (!isset($kpSelected[$kpCode])) {
-                    $selectedQuestions[] = $q;
-                    $kpSelected[$kpCode] = true;
-                    Log::debug('题型基础分配', ['type' => $type, 'kp' => $kpCode]);
-                    break;
+            // 根据策略选择题目
+            if ($useKnowledgePointPriority) {
+                // 摸底测试:选择第一个未选过知识点的题目
+                foreach ($questionsByType[$type] as $q) {
+                    $kpCode = $q['kp_code'] ?? '';
+                    if (!isset($kpSelected[$kpCode])) {
+                        $selectedQuestions[] = $q;
+                        $kpSelected[$kpCode] = true;
+                        Log::debug('题型基础分配(知识点优先)', ['type' => $type, 'kp' => $kpCode]);
+                        break;
+                    }
                 }
                 }
+            } else {
+                // 【修复】知识点组卷:随机选择该题型的一道题,避免固定选择第一个导致知识点分布不均
+                $randomIndex = array_rand($questionsByType[$type]);
+                $selectedQuestions[] = $questionsByType[$type][$randomIndex];
+                Log::debug('题型基础分配(随机选择)', [
+                    'type' => $type,
+                    'kp' => $questionsByType[$type][$randomIndex]['kp_code'] ?? 'unknown',
+                    'random_index' => $randomIndex,
+                    'total_in_type' => count($questionsByType[$type])
+                ]);
             }
             }
         }
         }
 
 
-        // ========== 步骤2:继续选不同知识点,直到达到目标数量 ==========
+        // ========== 步骤2:继续选题目,直到达到目标数量 ==========
         $allQuestions = array_merge($questionsByType['choice'], $questionsByType['fill'], $questionsByType['answer']);
         $allQuestions = array_merge($questionsByType['choice'], $questionsByType['fill'], $questionsByType['answer']);
+
+        // 【重要】添加排序前的知识点分布日志
+        $preSortKpDistribution = [];
+        foreach ($allQuestions as $q) {
+            $kpCode = $q['kp_code'] ?? '';
+            if (!isset($preSortKpDistribution[$kpCode])) {
+                $preSortKpDistribution[$kpCode] = 0;
+            }
+            $preSortKpDistribution[$kpCode]++;
+        }
+
+        Log::info('selectQuestionsByMastery: 排序前知识点分布', [
+            'total_questions' => count($allQuestions),
+            'kp_distribution' => $preSortKpDistribution,
+            'note' => '用于对比排序后的分布变化'
+        ]);
+
+        // 【修复】添加随机因子到排序中,避免因ID排序导致知识点分布不均
         usort($allQuestions, function ($a, $b) use ($kpWeights) {
         usort($allQuestions, function ($a, $b) use ($kpWeights) {
             $kpA = $a['kp_code'] ?? '';
             $kpA = $a['kp_code'] ?? '';
             $kpB = $b['kp_code'] ?? '';
             $kpB = $b['kp_code'] ?? '';
             $weightA = $kpWeights[$kpA] ?? 1.0;
             $weightA = $kpWeights[$kpA] ?? 1.0;
             $weightB = $kpWeights[$kpB] ?? 1.0;
             $weightB = $kpWeights[$kpB] ?? 1.0;
-            if ($weightA == $weightB) {
-                $idA = $a['id'] ?? $a['question_id'] ?? 0;
-                $idB = $b['id'] ?? $b['question_id'] ?? 0;
-                return $idA <=> $idB;
+
+            // 主要按权重排序
+            if ($weightA != $weightB) {
+                return $weightB <=> $weightA;
             }
             }
-            return $weightB <=> $weightA;
-        });
 
 
-        // 选择未选过知识点的题目(优先)
-        foreach ($allQuestions as $q) {
-            if (count($selectedQuestions) >= $totalQuestions) break;
+            // 权重相同时,添加随机排序而不是按ID排序
+            // 使用随机因子确保相同权重的知识点有公平的选中机会
+            return mt_rand(-1, 1);
+        });
 
 
+        // 【重要】添加排序后的知识点分布日志
+        $postSortKpDistribution = [];
+        $postSortFirst50 = []; // 记录前50题的知识点分布
+        foreach ($allQuestions as $idx => $q) {
             $kpCode = $q['kp_code'] ?? '';
             $kpCode = $q['kp_code'] ?? '';
-            if (!isset($kpSelected[$kpCode])) {
-                $selectedQuestions[] = $q;
-                $kpSelected[$kpCode] = true;
+            if (!isset($postSortKpDistribution[$kpCode])) {
+                $postSortKpDistribution[$kpCode] = 0;
             }
             }
-        }
+            $postSortKpDistribution[$kpCode]++;
 
 
-        // ========== 步骤3:如果还有空缺,从已选知识点中补充 ==========
-        if (count($selectedQuestions) < $totalQuestions) {
-            Log::info('知识点分配后题目不足,开始补充', [
-                'selected_count' => count($selectedQuestions),
-                'need_more' => $totalQuestions - count($selectedQuestions),
-                'kp_covered' => count($kpSelected)
-            ]);
-
-            // 统计每个知识点的题目数量
-            $kpCount = [];
-            foreach ($selectedQuestions as $q) {
-                $kpCode = $q['kp_code'] ?? '';
-                $kpCount[$kpCode] = ($kpCount[$kpCode] ?? 0) + 1;
+            // 记录前50题的知识点
+            if ($idx < 50) {
+                $postSortFirst50[] = $kpCode;
             }
             }
+        }
+
+        Log::info('selectQuestionsByMastery: 排序后知识点分布', [
+            'total_questions' => count($allQuestions),
+            'kp_distribution' => $postSortKpDistribution,
+            'first_50_kp_codes' => $postSortFirst50,
+            'note' => '观察排序是否均衡分布A07和A08'
+        ]);
 
 
-            // 优先补充知识点数量少的题目
+        // 根据策略继续选择题目
+        if ($useKnowledgePointPriority) {
+            // 摸底测试:选择未选过知识点的题目(优先)
             foreach ($allQuestions as $q) {
             foreach ($allQuestions as $q) {
                 if (count($selectedQuestions) >= $totalQuestions) break;
                 if (count($selectedQuestions) >= $totalQuestions) break;
 
 
                 $kpCode = $q['kp_code'] ?? '';
                 $kpCode = $q['kp_code'] ?? '';
-                $count = $kpCount[$kpCode] ?? 0;
+                if (!isset($kpSelected[$kpCode])) {
+                    $selectedQuestions[] = $q;
+                    $kpSelected[$kpCode] = true;
+                    Log::debug('继续选择题目(知识点优先)', ['kp' => $kpCode, 'id' => $q['id'] ?? 'unknown']);
+                }
+            }
+        } else {
+            // 知识点组卷:选择未选过的题目(不要求知识点不重复)
+            $selectedIds = array_column($selectedQuestions, 'id');
+            foreach ($allQuestions as $q) {
+                if (count($selectedQuestions) >= $totalQuestions) break;
 
 
-                // 如果该知识点题目数量少于2题,且题目不在已选列表中
-                if ($count < 2 && !in_array($q, $selectedQuestions)) {
+                $qid = $q['id'] ?? null;
+                if ($qid && !in_array($qid, $selectedIds)) {
                     $selectedQuestions[] = $q;
                     $selectedQuestions[] = $q;
-                    $kpCount[$kpCode] = $count + 1;
+                    $selectedIds[] = $qid;
+                    Log::debug('继续选择题目(无知识点限制)', ['kp' => $q['kp_code'] ?? 'unknown', 'id' => $qid]);
                 }
                 }
             }
             }
         }
         }
 
 
-        Log::info('知识点优先选择完成', [
+        // 【移除】删除步骤3的多余逻辑
+        // 前面的逻辑已经能选够题目,不需要额外的补充步骤
+
+        Log::info('selectQuestionsByMastery: 题目选择完成', [
             'total_questions' => $totalQuestions,
             'total_questions' => $totalQuestions,
             'selected_count' => count($selectedQuestions),
             'selected_count' => count($selectedQuestions),
-            'kp_covered' => count($kpSelected),
+            'success' => count($selectedQuestions) === $totalQuestions,
+            'assemble_type' => $assembleType,
+            'strategy' => $useKnowledgePointPriority ? '知识点优先' : '无知识点限制',
             'type_distribution' => array_count_values(array_map(function($q) {
             'type_distribution' => array_count_values(array_map(function($q) {
                 $qid = $q['id'] ?? $q['question_id'] ?? null;
                 $qid = $q['id'] ?? $q['question_id'] ?? null;
                 if ($qid && isset($questionTypeCache[$qid])) {
                 if ($qid && isset($questionTypeCache[$qid])) {
@@ -2184,10 +2287,10 @@ class LearningAnalyticsService
                 }
                 }
                 return $this->determineQuestionType($q);
                 return $this->determineQuestionType($q);
             }, $selectedQuestions)),
             }, $selectedQuestions)),
-            'top_kp_distribution' => array_count_values(array_column($selectedQuestions, 'kp_code'))
+            'kp_distribution' => array_count_values(array_column($selectedQuestions, 'kp_code'))
         ]);
         ]);
 
 
-        // 最终截取到目标数量(如果超过)
+        // 【重要】最终截取到目标数量(如果超过)
         if (count($selectedQuestions) > $totalQuestions) {
         if (count($selectedQuestions) > $totalQuestions) {
             Log::info('题目数量超过目标,进行最终截取', [
             Log::info('题目数量超过目标,进行最终截取', [
                 'before' => count($selectedQuestions),
                 'before' => count($selectedQuestions),
@@ -2196,6 +2299,13 @@ class LearningAnalyticsService
             $selectedQuestions = array_slice($selectedQuestions, 0, $totalQuestions);
             $selectedQuestions = array_slice($selectedQuestions, 0, $totalQuestions);
         }
         }
 
 
+        // 【重要】添加最终数量验证日志
+        Log::info('selectQuestionsByMastery: 最终题目数量验证', [
+            'before_final_check' => count($selectedQuestions),
+            'target_count' => $totalQuestions,
+            'is_array' => is_array($selectedQuestions)
+        ]);
+
         // ========== 最终排查:确保无重复题目且题型分布合理 ==========
         // ========== 最终排查:确保无重复题目且题型分布合理 ==========
         $finalQuestions = [];
         $finalQuestions = [];
         $seenQuestionIds = [];
         $seenQuestionIds = [];
@@ -2231,11 +2341,49 @@ class LearningAnalyticsService
             }
             }
         }
         }
 
 
+        // 【重要】如果去重后数量不足,补充到目标数量
+        if (count($finalQuestions) < $totalQuestions) {
+            Log::warning('selectQuestionsByMastery: 去重后数量不足,尝试补充', [
+                'final_count' => count($finalQuestions),
+                'target_count' => $totalQuestions,
+                'need_more' => $totalQuestions - count($finalQuestions)
+            ]);
+
+            // 从原始题目中选择未选过的题目补充
+            $usedIds = array_column($finalQuestions, 'id');
+            $supplementCount = 0;
+            foreach ($selectedQuestions as $question) {
+                if ($supplementCount >= ($totalQuestions - count($finalQuestions))) break;
+
+                $qid = $question['id'] ?? null;
+                if ($qid && !in_array($qid, $usedIds)) {
+                    $finalQuestions[] = $question;
+                    $usedIds[] = $qid;
+                    $supplementCount++;
+                }
+            }
+
+            Log::info('selectQuestionsByMastery: 补充完成', [
+                'supplement_added' => $supplementCount,
+                'final_count_after_supplement' => count($finalQuestions)
+            ]);
+        }
+
         Log::info('最终排查完成', [
         Log::info('最终排查完成', [
             'original_count' => count($selectedQuestions),
             'original_count' => count($selectedQuestions),
             'final_count' => count($finalQuestions),
             'final_count' => count($finalQuestions),
             'duplicate_removed' => $duplicateCount,
             'duplicate_removed' => $duplicateCount,
-            'final_type_distribution' => $typeDistribution
+            'final_type_distribution' => $typeDistribution,
+            'target_count' => $totalQuestions
+        ]);
+
+        // 【重要】确保返回的题目数量正确
+        $finalQuestions = array_slice($finalQuestions, 0, $totalQuestions);
+
+        Log::info('selectQuestionsByMastery: 返回结果', [
+            'return_count' => count($finalQuestions),
+            'target_count' => $totalQuestions,
+            'success' => count($finalQuestions) === $totalQuestions
         ]);
         ]);
 
 
         // 注意:题型平衡已在上面完成,不需要再调用adjustQuestionsByRatio
         // 注意:题型平衡已在上面完成,不需要再调用adjustQuestionsByRatio
@@ -2497,16 +2645,8 @@ class LearningAnalyticsService
         return 'answer';
         return 'answer';
     }
     }
 
 
-    private function mapDifficultyLevel(float $d): string
-    {
-        if ($d <= 0.4) {
-            return '基础';
-        }
-        if ($d <= 0.7) {
-            return '中等';
-        }
-        return '拔高';
-    }
+    // 【移除】删除未使用的mapDifficultyLevel方法
+    // 难度分布由QuestionLocalService处理,不需要额外的难度映射
 
 
     private function computeTypeTargets(int $targetCount, array $questionTypeRatio): array
     private function computeTypeTargets(int $targetCount, array $questionTypeRatio): array
     {
     {

+ 8 - 1
app/Services/PdfMerger.php

@@ -53,9 +53,16 @@ class PdfMerger
     {
     {
         // 【修复】异步环境中PATH可能不完整,尝试绝对路径
         // 【修复】异步环境中PATH可能不完整,尝试绝对路径
         $fullPaths = [
         $fullPaths = [
+            // 本地开发路径
             "/opt/homebrew/bin/{$command}",
             "/opt/homebrew/bin/{$command}",
-            "/usr/bin/{$command}",
             "/usr/local/bin/{$command}",
             "/usr/local/bin/{$command}",
+            // 系统路径
+            "/usr/bin/{$command}",
+            "/usr/sbin/{$command}",
+            // Docker/Laravel 容器常见路径
+            "/bin/{$command}",
+            "/sbin/{$command}",
+            "/usr/local/sbin/{$command}",
         ];
         ];
 
 
         Log::debug("检查命令是否存在: {$command}", [
         Log::debug("检查命令是否存在: {$command}", [

+ 63 - 20
app/Services/QuestionLocalService.php

@@ -580,66 +580,105 @@ class QuestionLocalService
             return [];
             return [];
         }
         }
 
 
-        // 计算目标分布
+        // 【恢复】简化逻辑,避免复杂处理
         $distribution = $this->calculateDifficultyDistribution($difficultyCategory, $totalQuestions);
         $distribution = $this->calculateDifficultyDistribution($difficultyCategory, $totalQuestions);
 
 
         Log::info('QuestionLocalService: 难度分布计算', [
         Log::info('QuestionLocalService: 难度分布计算', [
-            'distribution' => $distribution
+            'distribution' => $distribution,
+            'target_count' => $totalQuestions
         ]);
         ]);
 
 
         // 按难度范围分桶
         // 按难度范围分桶
         $buckets = $this->groupQuestionsByDifficultyRange($questions, $difficultyCategory);
         $buckets = $this->groupQuestionsByDifficultyRange($questions, $difficultyCategory);
 
 
         Log::info('QuestionLocalService: 题目分桶', [
         Log::info('QuestionLocalService: 题目分桶', [
-            'buckets' => array_map(fn($bucket) => count($bucket), $buckets)
+            'buckets' => array_map(fn($bucket) => count($bucket), $buckets),
+            'total_input' => count($questions)
         ]);
         ]);
 
 
         // 根据分布选择题目
         // 根据分布选择题目
         $selected = [];
         $selected = [];
-        $usedIndices = [];
+        $usedIds = [];
 
 
         foreach ($distribution as $level => $config) {
         foreach ($distribution as $level => $config) {
             $targetCount = $config['count'];
             $targetCount = $config['count'];
             if ($targetCount <= 0) {
             if ($targetCount <= 0) {
+                Log::debug('QuestionLocalService: 跳过难度层级', [
+                    'level' => $level,
+                    'target_count' => $targetCount
+                ]);
                 continue;
                 continue;
             }
             }
 
 
             $rangeKey = $this->mapDifficultyLevelToRangeKey($level, $difficultyCategory);
             $rangeKey = $this->mapDifficultyLevelToRangeKey($level, $difficultyCategory);
             $bucket = $buckets[$rangeKey] ?? [];
             $bucket = $buckets[$rangeKey] ?? [];
 
 
+            Log::debug('QuestionLocalService: 处理难度层级', [
+                'level' => $level,
+                'range_key' => $rangeKey,
+                'target_count' => $targetCount,
+                'bucket_size' => count($bucket)
+            ]);
+
             // 随机打乱
             // 随机打乱
             shuffle($bucket);
             shuffle($bucket);
 
 
             // 选择题目
             // 选择题目
-            $takeCount = min($targetCount, count($bucket));
-            for ($i = 0; $i < $takeCount; $i++) {
-                if (isset($bucket[$i])) {
-                    $selected[] = $bucket[$i];
-                    $usedIndices[] = $bucket[$i]['id'] ?? $i;
+            $taken = 0;
+            foreach ($bucket as $question) {
+                if ($taken >= $targetCount) break;
+
+                $questionId = $question['id'] ?? null;
+                if ($questionId && !in_array($questionId, $usedIds)) {
+                    $selected[] = $question;
+                    $usedIds[] = $questionId;
+                    $taken++;
                 }
                 }
             }
             }
 
 
             Log::debug('QuestionLocalService: 难度层级选择', [
             Log::debug('QuestionLocalService: 难度层级选择', [
                 'level' => $level,
                 'level' => $level,
                 'target' => $targetCount,
                 'target' => $targetCount,
-                'actual' => $takeCount,
-                'bucket_size' => count($bucket)
+                'actual' => $taken,
+                'bucket_size' => count($bucket),
+                'range_key' => $rangeKey
             ]);
             ]);
         }
         }
 
 
+        Log::info('QuestionLocalService: 分布选择后统计', [
+            'selected_count' => count($selected),
+            'target_count' => $totalQuestions,
+            'need_more' => max(0, $totalQuestions - count($selected))
+        ]);
+
         // 如果数量不足,从剩余题目中补充
         // 如果数量不足,从剩余题目中补充
         if (count($selected) < $totalQuestions) {
         if (count($selected) < $totalQuestions) {
+            Log::info('QuestionLocalService: 开始补充题目', [
+                'need_more' => $totalQuestions - count($selected),
+                'selected_count' => count($selected)
+            ]);
+
             $remaining = [];
             $remaining = [];
             foreach ($questions as $q) {
             foreach ($questions as $q) {
                 $id = $q['id'] ?? null;
                 $id = $q['id'] ?? null;
-                if ($id && !in_array($id, $usedIndices)) {
+                if ($id && !in_array($id, $usedIds)) {
                     $remaining[] = $q;
                     $remaining[] = $q;
                 }
                 }
             }
             }
 
 
+            Log::info('QuestionLocalService: 剩余题目统计', [
+                'remaining_count' => count($remaining),
+                'need_more' => $totalQuestions - count($selected)
+            ]);
+
             shuffle($remaining);
             shuffle($remaining);
             $needMore = $totalQuestions - count($selected);
             $needMore = $totalQuestions - count($selected);
             $selected = array_merge($selected, array_slice($remaining, 0, $needMore));
             $selected = array_merge($selected, array_slice($remaining, 0, $needMore));
+
+            Log::info('QuestionLocalService: 补充完成', [
+                'supplement_added' => $needMore,
+                'final_count_before_truncate' => count($selected)
+            ]);
         }
         }
 
 
         // 截断至目标数量
         // 截断至目标数量
@@ -647,7 +686,10 @@ class QuestionLocalService
 
 
         Log::info('QuestionLocalService: 难度分布选择完成', [
         Log::info('QuestionLocalService: 难度分布选择完成', [
             'final_count' => count($selected),
             'final_count' => count($selected),
-            'target_count' => $totalQuestions
+            'target_count' => $totalQuestions,
+            'success' => count($selected) === $totalQuestions,
+            'input_count' => count($questions),
+            'distribution_applied' => true
         ]);
         ]);
 
 
         return $selected;
         return $selected;
@@ -804,14 +846,15 @@ class QuestionLocalService
      */
      */
     private function mapDifficultyLevelToRangeKey(string $level, int $category): string
     private function mapDifficultyLevelToRangeKey(string $level, int $category): string
     {
     {
-        // 根据类别和层级确定范围键
+        // 【修复】难度分布映射逻辑:确保 'other' 桶中的题目能被正确选择
+        // 对于 difficulty_category=1,'other' 桶中的题目应该映射到 'secondary' 桶
         switch ($category) {
         switch ($category) {
             case 1:
             case 1:
                 return match($level) {
                 return match($level) {
-                    'low' => 'other',
+                    'low' => 'secondary',  // 修复:映射到 secondary 桶,而非 other
                     'medium' => 'primary_medium',
                     'medium' => 'primary_medium',
-                    'high' => 'other',
-                    default => 'other'
+                    'high' => 'secondary',  // 修复:映射到 secondary 桶,而非 other
+                    default => 'secondary'
                 };
                 };
 
 
             case 2:
             case 2:
@@ -832,10 +875,10 @@ class QuestionLocalService
 
 
             case 4:
             case 4:
                 return match($level) {
                 return match($level) {
-                    'low' => 'other',
+                    'low' => 'secondary',  // 修复:映射到 secondary 桶,而非 other
                     'medium' => 'primary_medium',
                     'medium' => 'primary_medium',
-                    'high' => 'other',
-                    default => 'other'
+                    'high' => 'secondary',  // 修复:映射到 secondary 桶,而非 other
+                    default => 'secondary'
                 };
                 };
 
 
             default:
             default: