Explorar o código

摸底智能出题,尽可能覆盖到完整知识点

yemeishu hai 2 horas
pai
achega
319de5c17d
Modificáronse 1 ficheiros con 138 adicións e 6 borrados
  1. 138 6
      app/Services/ExamTypeStrategy.php

+ 138 - 6
app/Services/ExamTypeStrategy.php

@@ -1004,20 +1004,152 @@ class ExamTypeStrategy
     }
 
     /**
-     * 根据章节ID列表获取知识点代码列表
+     * 根据章节ID列表获取知识点代码列表(均等抽样版本)
+     * 使用"知识点均等抽样"算法,确保覆盖全书章节
+     *
+     * @param array $chapterIds 章节ID列表
+     * @param int $limit 目标题目数量(默认25)
+     * @return array 抽样后的知识点代码列表
      */
     private function getKnowledgePointsFromChapters(array $chapterIds, int $limit = 25): array
     {
         try {
-            // 查询 textbook_chapter_knowledge_relation 表
-            $kpCodes = DB::table('textbook_chapter_knowledge_relation')
-                ->whereIn('catalog_chapter_id', $chapterIds)
+            // 如果章节数较少,使用原有逻辑
+            if (count($chapterIds) <= 5) {
+                Log::info('ExamTypeStrategy: 章节数较少,使用原有逻辑', [
+                    'chapter_count' => count($chapterIds)
+                ]);
+                return $this->getKnowledgePointsFromChaptersLegacy($chapterIds, $limit);
+            }
+
+            // ========== 步骤1: 构建全量有序池 ==========
+            // 查询所有知识点,按章节顺序排列,去重后得到有序数组
+            $allKpData = 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', $chapterIds)
+                ->select('tckr.kp_code', 'tcn.sort_order', 'tcn.id as chapter_id', 'tcn.title as chapter_title')
+                ->orderBy('tcn.sort_order', 'asc')
+                ->orderBy('tckr.kp_code', 'asc')
+                ->get();
+
+            // 去重并构建有序数组
+            $allKpCodes = [];
+            $chapterInfo = [];
+            foreach ($allKpData as $row) {
+                if (!in_array($row->kp_code, $allKpCodes)) {
+                    $allKpCodes[] = $row->kp_code;
+                    $chapterInfo[$row->kp_code] = [
+                        'chapter_id' => $row->chapter_id,
+                        'chapter_title' => $row->chapter_title,
+                        'sort_order' => $row->sort_order
+                    ];
+                }
+            }
+
+            $totalKps = count($allKpCodes);
+
+            Log::info('ExamTypeStrategy: 构建全量有序池完成', [
+                'chapter_count' => count($chapterIds),
+                'total_knowledge_points' => $totalKps,
+                'target_questions' => $limit
+            ]);
+
+            // ========== 步骤2: 计算抽样步长 ==========
+            $step = $totalKps > 0 ? $totalKps / $limit : 1;
+            Log::info('ExamTypeStrategy: 计算抽样步长', [
+                'step' => $step
+            ]);
+
+            // ========== 步骤3: 等距取样 ==========
+            $selectedKps = [];
+            $selectedCount = 0;
+            $currentIndex = 0;
+
+            while ($selectedCount < $limit && $selectedCount < $totalKps) {
+                $index = (int) floor($currentIndex);
+                if ($index < $totalKps && isset($allKpCodes[$index])) {
+                    $kpCode = $allKpCodes[$index];
+                    if (!in_array($kpCode, $selectedKps)) {
+                        $selectedKps[] = $kpCode;
+                        $selectedCount++;
+                    }
+                }
+                $currentIndex += $step;
+            }
+
+            // 如果数量不足,循环取样
+            if (count($selectedKps) < $limit && $totalKps > 0) {
+                Log::info('ExamTypeStrategy: 第一次抽样不足,进行循环取样', [
+                    'selected_count' => count($selectedKps),
+                    'target_count' => $limit
+                ]);
+
+                $needed = $limit - count($selectedKps);
+                $startIndex = 0;
+
+                for ($i = 0; $i < $needed && count($selectedKps) < $limit; $i++) {
+                    $index = ($startIndex + $i) % $totalKps;
+                    $kpCode = $allKpCodes[$index];
+                    if (!in_array($kpCode, $selectedKps)) {
+                        $selectedKps[] = $kpCode;
+                    }
+                }
+            }
+
+            // ========== 步骤4: 记录抽样统计 ==========
+            $chapterDistribution = [];
+            foreach ($selectedKps as $kpCode) {
+                if (isset($chapterInfo[$kpCode])) {
+                    $chapterId = $chapterInfo[$kpCode]['chapter_id'];
+                    if (!isset($chapterDistribution[$chapterId])) {
+                        $chapterDistribution[$chapterId] = [
+                            'count' => 0,
+                            'title' => $chapterInfo[$kpCode]['chapter_title']
+                        ];
+                    }
+                    $chapterDistribution[$chapterId]['count']++;
+                }
+            }
+
+            Log::info('ExamTypeStrategy: 等距抽样完成', [
+                'total_kps' => $totalKps,
+                'selected_kps' => count($selectedKps),
+                'step' => $step,
+                'chapter_distribution' => $chapterDistribution,
+                'sample_kp_codes' => array_slice($selectedKps, 0, 10)
+            ]);
+
+            return array_filter($selectedKps);
+
+        } catch (\Exception $e) {
+            Log::error('ExamTypeStrategy: 均等抽样获取章节知识点失败,使用原有逻辑', [
+                'chapter_ids' => $chapterIds,
+                'error' => $e->getMessage()
+            ]);
+
+            // 失败时回退到原有逻辑
+            return $this->getKnowledgePointsFromChaptersLegacy($chapterIds, $limit);
+        }
+    }
+
+    /**
+     * 原有逻辑(保留作为回退方案)
+     */
+    private function getKnowledgePointsFromChaptersLegacy(array $chapterIds, int $limit = 25): array
+    {
+        try {
+            // 查询 textbook_chapter_knowledge_relation 表(修复:添加排序避免数据偏向)
+            $kpCodes = DB::table('textbook_chapter_knowledge_relation as tckr')
+                ->leftJoin('textbook_catalog_nodes as tcn', 'tckr.catalog_chapter_id', '=', 'tcn.id')
+                ->whereIn('tckr.catalog_chapter_id', $chapterIds)
+                ->orderBy('tcn.sort_order', 'asc')
+                ->orderBy('tckr.kp_code', 'asc')
                 ->limit($limit)
                 ->distinct()
-                ->pluck('kp_code')
+                ->pluck('tckr.kp_code')
                 ->toArray();
 
-            Log::debug('ExamTypeStrategy: 查询章节知识点', [
+            Log::debug('ExamTypeStrategy: 查询章节知识点(原有逻辑)', [
                 'chapter_count' => count($chapterIds),
                 'found_kp_count' => count($kpCodes)
             ]);