Sfoglia il codice sorgente

fix: 题目超纲问题

已修复超纲问题,修改了以下文件:

  app/Services/LearningAnalyticsService.php

  1. 修改 getGradeKnowledgePoints 方法 (第 2995-3041 行)

  - 新增 $textbookId 参数,用于限制知识点查询范围
  - 如果传入 textbook_id,只返回该教材关联的知识点

  private function getGradeKnowledgePoints(int $grade, ?int $textbookId = null): array
  {
      // 如果有 textbook_id,严格限制在该教材范围内
      if ($textbookId) {
          $query->where('t.id', $textbookId);
      }
  }

  2. 修改 getSupplementaryQuestionsForGrade 方法 (第 2878-3007 行)

  - 新增 $textbookId 参数
  - 调用 getGradeKnowledgePoints 时传入 textbookId
  - 如果找不到该教材的知识点,直接返回空数组(避免从其他教材取题)

  private function getSupplementaryQuestionsForGrade(
      int $grade,
      array $existingKpCodes,
      int $needCount,
      int $difficultyCategory,
      ?int $textbookId = null  // 新增参数
  ): array

  3. 修改 getQuestionsFromBank 方法 (第 1601-1797 行)

  - 新增 $grade、$textbookId、$difficultyCategory 参数
  - 调用 getSupplementaryQuestionsForGrade 时传入这些参数

  4. 修改调用处 (第 1415-1447 行)

  - 在 generateIntelligentExam 方法中获取 textbook_id 并传入 getQuestionsFromBank

  ---
  修复原理

  之前的问题:
  七年级上册学生 → 题目不足时补充 → getGradeKnowledgePoints(7)
  → 获取七年级所有知识点(包括下册)→ 超纲!

  修复后:
  七年级上册学生 → 题目不足时补充 → getGradeKnowledgePoints(7, textbook_id=上册)
  → 只获取七年级上册的知识点 → 不超纲 ✓

  ---
  注意事项

  确保 API 调用时传入正确的参数:
  - series_id: 教材系列ID
  - semester_code: 上下册 (1=上册, 2=下册)
  - grade: 年级

  系统会自动解析 textbook_id 并在智能补充时使用它来限制范围。
过卫栋 1 mese fa
parent
commit
efeb51e6e5
1 ha cambiato i file con 114 aggiunte e 30 eliminazioni
  1. 114 30
      app/Services/LearningAnalyticsService.php

+ 114 - 30
app/Services/LearningAnalyticsService.php

@@ -1358,8 +1358,8 @@ class LearningAnalyticsService
                     $truncatedMistakeIds = array_slice($mistakeQuestionIds, 0, $maxQuestions);
                 }
 
-                // 获取学生错题的详细信息
-                $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $truncatedMistakeIds, [], null, null);
+                // 获取学生错题的详细信息(错题获取不需要智能补充,不传入 grade/textbook_id)
+                $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, $maxQuestions, $truncatedMistakeIds, [], null, null, null, null, 1);
 
                 Log::info('LearningAnalyticsService: 错题获取完成', [
                     'priority_questions_count' => count($priorityQuestions),
@@ -1412,18 +1412,39 @@ class LearningAnalyticsService
                         ]);
                     }
 
+                    // 【优化】获取textbook_catalog_node_ids参数(教材组卷时使用)
+                    $textbookCatalogNodeIds = $params['textbook_catalog_node_ids'] ?? null;
+                    // 【修复超纲问题】获取 textbook_id 和 difficulty_category,用于智能补充时限制范围
+                    $textbookId = isset($params['textbook_id']) ? (int) $params['textbook_id'] : null;
+                    $difficultyCategory = (int) ($params['difficulty_category'] ?? 1);
+
                     Log::info('开始调用 getQuestionsFromBank 补充题目', [
                         'kp_codes_count' => count($kpCodes),
                         'skills_count' => count($skills),
                         'has_mistake_priority' => !empty($mistakeQuestionIds),
                         'need_more' => $totalQuestions - count($priorityQuestions),
-                        'assemble_type' => $assembleType
+                        'assemble_type' => $assembleType,
+                        'grade' => $grade,
+                        'textbook_id' => $textbookId,
+                        'difficulty_category' => $difficultyCategory,
+                        'note' => $textbookId ? '将使用 textbook_id 限制补充范围,避免超纲' : '未指定教材,使用整个年级范围'
                     ]);
 
-                    // 【优化】获取textbook_catalog_node_ids参数(教材组卷时使用)
-                    $textbookCatalogNodeIds = $params['textbook_catalog_node_ids'] ?? null;
-
-                    $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, $maxQuestions, [], [], $questionCategory, $textbookCatalogNodeIds);
+                    // 【修复超纲问题】传入 grade 和 textbook_id,用于智能补充时限制范围
+                    $additionalQuestions = $this->getQuestionsFromBank(
+                        $kpCodes,
+                        $skills,
+                        $studentId,
+                        $questionTypeRatio,
+                        $maxQuestions,
+                        [],
+                        [],
+                        $questionCategory,
+                        $textbookCatalogNodeIds,
+                        $grade ? (int) $grade : null,  // 年级
+                        $textbookId,                    // 教材ID(避免超纲)
+                        $difficultyCategory             // 难度类别
+                    );
                     $allQuestions = array_merge($priorityQuestions, $additionalQuestions);
 
                     Log::info('getQuestionsFromBank 调用完成', [
@@ -1599,11 +1620,23 @@ class LearningAnalyticsService
     }
 
     /**
-     * 从本地题库获取题目(错题回顾优先)
-     * 支持优先获取指定题目ID的题目
-     * 【优化】新增textbookCatalogNodeIds参数,支持按textbook_catalog_node_id筛选题目
+     * 从题库获取题目
+     *
+     * @param array $kpCodes 知识点代码列表
+     * @param array $skills 技能标签列表
+     * @param string|null $studentId 学生ID
+     * @param array $questionTypeRatio 题型比例
+     * @param int $totalNeeded 需要的题目数量
+     * @param array $priorityQuestionIds 优先获取的题目ID(错题)
+     * @param array $excludeQuestionIds 排除的题目ID
+     * @param int|null $questionCategory 题目分类
+     * @param array|null $textbookCatalogNodeIds 教材章节节点ID
+     * @param int|null $grade 年级(用于智能补充时限制范围)
+     * @param int|null $textbookId 教材ID(用于智能补充时限制范围,避免超纲)
+     * @param int $difficultyCategory 难度类别(用于智能补充)
+     * @return 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
+    private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = [], array $excludeQuestionIds = [], ?int $questionCategory = null, ?array $textbookCatalogNodeIds = null, ?int $grade = null, ?int $textbookId = null, int $difficultyCategory = 1): array
     {
         $startTime = microtime(true);
 
@@ -1732,31 +1765,40 @@ class LearningAnalyticsService
             // 让上层调用者根据需要选择题目数量
             $selectedQuestions = $formattedQuestions;
 
-            // 【修复】重新启用智能补充功能
-            if (count($selectedQuestions) < $totalNeeded) {
+            // 【修复】重新启用智能补充功能,增加 textbook_id 限制避免超纲
+            if (count($selectedQuestions) < $totalNeeded && $grade !== null) {
                 $deficit = $totalNeeded - count($selectedQuestions);
                 Log::warning('getQuestionsFromBank: 指定知识点题目不足,尝试智能补充', [
                     'deficit' => $deficit,
                     'available_count' => count($selectedQuestions),
-                    'strategy' => '从同年级其他知识点或相邻难度补充'
+                    'grade' => $grade,
+                    'textbook_id' => $textbookId,
+                    'strategy' => $textbookId ? '从指定教材的其他知识点补充(避免超纲)' : '从同年级其他知识点补充'
                 ]);
 
-                // 补充策略:从同年级其他知识点补充
+                // 【修复超纲问题】补充策略:从同年级同教材的其他知识点补充
+                // 传入 textbook_id 参数,避免七年级上册学生拿到下册题目
                 $supplementaryQuestions = $this->getSupplementaryQuestionsForGrade(
-                    $params['grade'] ?? 9,
+                    $grade,
                     array_column($selectedQuestions, 'kp_code'),
                     $deficit,
-                    $params['difficulty_category'] ?? 1
+                    $difficultyCategory,
+                    $textbookId  // 【关键】传入教材ID,限制补充范围
                 );
 
                 if (!empty($supplementaryQuestions)) {
                     $selectedQuestions = array_merge($selectedQuestions, $supplementaryQuestions);
                     Log::info('getQuestionsFromBank: 智能补充完成', [
                         'supplementary_count' => count($supplementaryQuestions),
-                        'total_after_supplement' => count($selectedQuestions)
+                        'total_after_supplement' => count($selectedQuestions),
+                        'textbook_id' => $textbookId
                     ]);
                 } else {
-                    Log::warning('getQuestionsFromBank: 智能补充失败,未找到合适的题目');
+                    Log::warning('getQuestionsFromBank: 智能补充失败,未找到合适的题目', [
+                        'grade' => $grade,
+                        'textbook_id' => $textbookId,
+                        'note' => $textbookId ? '可能是该教材知识点题目不足' : '该年级题目不足'
+                    ]);
                 }
             }
 
@@ -2876,21 +2918,30 @@ class LearningAnalyticsService
     }
 
     /**
-     * 【新增】为指定年级智能补充题目
-     * 当指定知识点题目不足时,从同年级其他知识点补充
+     * 智能补充题目(当指定知识点的题目不足时)
+     *
+     * @param int $grade 年级
+     * @param array $existingKpCodes 已有的知识点代码(用于排除)
+     * @param int $needCount 需要补充的题目数量
+     * @param int $difficultyCategory 难度类别
+     * @param int|null $textbookId 教材ID(用于限制补充范围,避免超纲)
+     * @return array 补充的题目列表
      */
     private function getSupplementaryQuestionsForGrade(
         int $grade,
         array $existingKpCodes,
         int $needCount,
-        int $difficultyCategory
+        int $difficultyCategory,
+        ?int $textbookId = null
     ): array {
         try {
             Log::info('getSupplementaryQuestionsForGrade: 开始智能补充', [
                 'grade' => $grade,
                 'existing_kp_count' => count($existingKpCodes),
                 'need_count' => $needCount,
-                'difficulty_category' => $difficultyCategory
+                'difficulty_category' => $difficultyCategory,
+                'textbook_id' => $textbookId,
+                'note' => $textbookId ? '限制在指定教材范围内,避免超纲' : '使用整个年级范围'
             ]);
 
             // 查询同年级其他知识点的题目
@@ -2901,10 +2952,21 @@ class LearningAnalyticsService
                 $query->whereNotIn('kp_code', $existingKpCodes);
             }
 
-            // 限制同年级(通过教材关联)
-            $gradeKpCodes = $this->getGradeKnowledgePoints($grade);
+            // 【修复超纲问题】限制在指定教材范围内(通过教材关联)
+            // 如果有 textbook_id,只从该教材的知识点中补充,避免七年级上册学生拿到下册题目
+            $gradeKpCodes = $this->getGradeKnowledgePoints($grade, $textbookId);
             if (!empty($gradeKpCodes)) {
                 $query->whereIn('kp_code', $gradeKpCodes);
+                Log::info('getSupplementaryQuestionsForGrade: 应用知识点范围限制', [
+                    'kp_codes_count' => count($gradeKpCodes),
+                    'textbook_id' => $textbookId
+                ]);
+            } else {
+                Log::warning('getSupplementaryQuestionsForGrade: 未找到年级知识点,跳过补充', [
+                    'grade' => $grade,
+                    'textbook_id' => $textbookId
+                ]);
+                return [];
             }
 
             // 筛选有解题思路的题目
@@ -2993,23 +3055,45 @@ class LearningAnalyticsService
     }
 
     /**
-     * 【新增】获取指定年级的所有知识点
+     * 获取指定年级的知识点列表
+     *
+     * @param int $grade 年级
+     * @param int|null $textbookId 教材ID(可选,用于限制在特定教材范围内,避免超纲)
+     * @return array 知识点代码列表
      */
-    private function getGradeKnowledgePoints(int $grade): array
+    private function getGradeKnowledgePoints(int $grade, ?int $textbookId = null): array
     {
         try {
-            $kpCodes = DB::table('textbook_chapter_knowledge_relation as tckr')
+            $query = DB::table('textbook_chapter_knowledge_relation as tckr')
                 ->join('textbook_catalog_nodes as tcn', 'tckr.catalog_chapter_id', '=', 'tcn.id')
                 ->join('textbooks as t', 'tcn.textbook_id', '=', 't.id')
-                ->where('t.grade', $grade)
-                ->distinct()
+                ->where('t.grade', $grade);
+
+            // 【修复超纲问题】如果有 textbook_id,严格限制在该教材范围内
+            // 避免七年级上册学生拿到七年级下册的知识点
+            if ($textbookId) {
+                $query->where('t.id', $textbookId);
+                Log::info('getGradeKnowledgePoints: 限制在指定教材范围内', [
+                    'grade' => $grade,
+                    'textbook_id' => $textbookId
+                ]);
+            }
+
+            $kpCodes = $query->distinct()
                 ->pluck('tckr.kp_code')
                 ->toArray();
 
+            Log::info('getGradeKnowledgePoints: 获取知识点完成', [
+                'grade' => $grade,
+                'textbook_id' => $textbookId,
+                'kp_count' => count($kpCodes)
+            ]);
+
             return array_filter($kpCodes);
         } catch (\Exception $e) {
             Log::error('getGradeKnowledgePoints: 查询失败', [
                 'grade' => $grade,
+                'textbook_id' => $textbookId,
                 'error' => $e->getMessage()
             ]);
             return [];