Explorar el Código

学案生成优化——教材出题

yemeishu hace 10 horas
padre
commit
6afc497f8a

+ 4 - 4
app/Http/Controllers/Api/IntelligentExamController.php

@@ -182,9 +182,9 @@ class IntelligentExamController extends Controller
                     'student_id' => $data['student_id'],
                     'grade' => $data['grade'] ?? null,
                     'total_questions' => $data['total_questions'],
-                    // 【修复】同时支持 kp_codes 和 kp_code_list 两种参数名
-                    'kp_codes' => $data['kp_codes'],
-                    'kp_code_list' => $data['kp_code_list'] ?? $data['kp_codes'] ?? [],
+                    // 【修复】教材组卷时不使用用户传入的kp_codes,只使用章节关联的知识点
+                    'kp_codes' => $assembleType == 3 ? null : ($data['kp_codes'] ?? null),
+                    'kp_code_list' => $assembleType == 3 ? null : ($data['kp_code_list'] ?? $data['kp_codes'] ?? []),
                     'skills' => $data['skills'] ?? [],
                     'question_type_ratio' => $questionTypeRatio,
                     'difficulty_category' => $difficultyCategory, // 传递难度分类(数字)
@@ -193,7 +193,7 @@ class IntelligentExamController extends Controller
                     'paper_ids' => $paperIds, // 错题本类型专用参数
                     'textbook_id' => $data['textbook_id'] ?? null, // 摸底和智能组卷专用
                     'chapter_id_list' => $data['chapter_id_list'] ?? null, // 教材组卷专用
-                    'kp_code_list' => $data['kp_code_list'] ?? null, // 知识点组卷专用
+                    'kp_code_list' => $assembleType == 3 ? null : ($data['kp_code_list'] ?? null), // 知识点组卷专用
                     'practice_options' => $data['practice_options'] ?? null, // 传递专项练习选项
                     'mistake_options' => $data['mistake_options'] ?? null, // 传递错题选项
                 ];

+ 157 - 4
app/Services/ExamTypeStrategy.php

@@ -961,6 +961,32 @@ class ExamTypeStrategy
             }
         }
 
+        // 【新增】如果用户指定了章节,解析章节节点类型并获取所有section/subsection节点
+        if (!empty($chapterIdList)) {
+            Log::info('ExamTypeStrategy: 用户指定了章节,开始解析节点类型', [
+                'input_chapter_ids' => $chapterIdList
+            ]);
+
+            // 解析用户传入的chapter_id_list,获取所有section/subsection节点
+            $resolvedSectionIds = $this->resolveSectionNodesFromChapters($chapterIdList);
+
+            if (!empty($resolvedSectionIds)) {
+                // 使用解析后的section/subsection节点ID替换原有的chapterIdList
+                $originalChapterCount = count($chapterIdList);
+                $chapterIdList = $resolvedSectionIds;
+
+                Log::info('ExamTypeStrategy: 章节节点解析完成,使用解析后的节点', [
+                    'original_count' => $originalChapterCount,
+                    'resolved_count' => count($chapterIdList),
+                    'resolved_ids' => $chapterIdList
+                ]);
+            } else {
+                Log::warning('ExamTypeStrategy: 章节节点解析失败,使用原始节点列表', [
+                    'chapter_id_list' => $chapterIdList
+                ]);
+            }
+        }
+
         if (empty($chapterIdList)) {
             Log::warning('ExamTypeStrategy: 教材组卷需要 chapter_id_list 参数或有效的textbook_id');
             return $this->buildGeneralParams($params);
@@ -972,7 +998,7 @@ class ExamTypeStrategy
             'total_questions' => $totalQuestions
         ]);
 
-        // 【优化】第一步:根据章节ID查询知识点关联,并统计每个章节的知识点数量
+        // 【修复】第一步:根据章节ID查询知识点关联,并统计每个章节的知识点数量
         $kpCodes = $this->getKnowledgePointsFromChapters($chapterIdList);
 
         // 【新增】获取每个章节对应的知识点数量统计
@@ -983,16 +1009,17 @@ class ExamTypeStrategy
             'total_chapters' => count($chapterIdList)
         ]);
 
-        // 【重要】即使没有找到知识点关联,也保留textbook_catalog_node_ids参数用于题目筛选
+        // 【重要】教材组卷严格限制知识点数量,不进行任何扩展
         if (empty($kpCodes)) {
             Log::warning('ExamTypeStrategy: 未找到章节知识点关联,但保留章节筛选参数', [
                 'chapter_id_list' => $chapterIdList,
                 'note' => '将在LearningAnalyticsService中按textbook_catalog_nodes_id字段筛选题目'
             ]);
         } else {
-            Log::info('ExamTypeStrategy: 获取章节知识点', [
+            Log::info('ExamTypeStrategy: 获取章节知识点(教材组卷严格限制)', [
                 'kp_count' => count($kpCodes),
-                'kp_codes' => array_slice($kpCodes, 0, 5)
+                'kp_codes' => $kpCodes,
+                'note' => '教材组卷只返回章节关联的知识点,不进行扩展'
             ]);
         }
 
@@ -1395,4 +1422,130 @@ class ExamTypeStrategy
             return [];
         }
     }
+
+    /**
+     * 【新增】递归获取section节点ID列表
+     * 根据用户传入的chapter_id_list,自动识别节点类型:
+     * - 如果是chapter节点,查找其下的所有section子节点(不包含subsection)
+     * - 如果是section节点,直接使用
+     *
+     * @param array $chapterIdList 用户传入的章节ID列表
+     * @return array 最终用于查询知识点的section节点ID列表
+     */
+    private function resolveSectionNodesFromChapters(array $chapterIdList): array
+    {
+        try {
+            if (empty($chapterIdList)) {
+                return [];
+            }
+
+            Log::info('ExamTypeStrategy: 开始解析章节节点', [
+                'input_chapter_ids' => $chapterIdList
+            ]);
+
+            $finalSectionIds = [];
+
+            foreach ($chapterIdList as $nodeId) {
+                // 查询节点信息
+                $node = DB::table('textbook_catalog_nodes')
+                    ->where('id', $nodeId)
+                    ->first();
+
+                if (!$node) {
+                    Log::warning('ExamTypeStrategy: 未找到节点', ['node_id' => $nodeId]);
+                    continue;
+                }
+
+                Log::debug('ExamTypeStrategy: 处理节点', [
+                    'node_id' => $nodeId,
+                    'node_type' => $node->node_type,
+                    'title' => $node->title
+                ]);
+
+                // 根据节点类型处理
+                switch ($node->node_type) {
+                    case 'chapter':
+                        // 如果是chapter节点,查找其下的所有section子节点
+                        $childNodes = DB::table('textbook_catalog_nodes')
+                            ->where('parent_id', $nodeId)
+                            ->where('node_type', 'section')
+                            ->orderBy('sort_order')
+                            ->get();
+
+                        Log::info('ExamTypeStrategy: 解析chapter节点', [
+                            'chapter_id' => $nodeId,
+                            'chapter_title' => $node->title,
+                            'section_count' => count($childNodes)
+                        ]);
+
+                        foreach ($childNodes as $child) {
+                            if (!in_array($child->id, $finalSectionIds)) {
+                                $finalSectionIds[] = $child->id;
+                                Log::debug('ExamTypeStrategy: 添加section子节点', [
+                                    'parent_chapter' => $nodeId,
+                                    'child_node_id' => $child->id,
+                                    'child_title' => $child->title,
+                                    'child_type' => $child->node_type
+                                ]);
+                            }
+                        }
+                        break;
+
+                    case 'section':
+                        // 如果是section节点,直接使用
+                        if (!in_array($nodeId, $finalSectionIds)) {
+                            $finalSectionIds[] = $nodeId;
+                            Log::debug('ExamTypeStrategy: 直接使用section节点', [
+                                'node_id' => $nodeId,
+                                'title' => $node->title,
+                                'node_type' => $node->node_type
+                            ]);
+                        }
+                        break;
+
+                    case 'subsection':
+                        // 如果是subsection节点,需要找到其父级section节点
+                        $parentSection = DB::table('textbook_catalog_nodes')
+                            ->where('id', $node->parent_id)
+                            ->where('node_type', 'section')
+                            ->first();
+
+                        if ($parentSection && !in_array($parentSection->id, $finalSectionIds)) {
+                            $finalSectionIds[] = $parentSection->id;
+                            Log::debug('ExamTypeStrategy: 通过subsection找到父级section', [
+                                'subsection_id' => $nodeId,
+                                'subsection_title' => $node->title,
+                                'parent_section_id' => $parentSection->id,
+                                'parent_section_title' => $parentSection->title
+                            ]);
+                        }
+                        break;
+
+                    default:
+                        Log::warning('ExamTypeStrategy: 未知节点类型', [
+                            'node_id' => $nodeId,
+                            'node_type' => $node->node_type
+                        ]);
+                        break;
+                }
+            }
+
+            Log::info('ExamTypeStrategy: 章节节点解析完成', [
+                'input_count' => count($chapterIdList),
+                'output_section_count' => count($finalSectionIds),
+                'section_ids' => $finalSectionIds
+            ]);
+
+            return $finalSectionIds;
+
+        } catch (\Exception $e) {
+            Log::error('ExamTypeStrategy: 解析章节节点失败', [
+                'chapter_id_list' => $chapterIdList,
+                'error' => $e->getMessage()
+            ]);
+
+            // 出错时返回空数组
+            return [];
+        }
+    }
 }

+ 17 - 15
app/Services/LearningAnalyticsService.php

@@ -1311,21 +1311,22 @@ class LearningAnalyticsService
 
                 $weaknessFilter = array_column($weaknesses, 'kp_code');
 
-                // 如果用户没有指定知识点,使用学生的薄弱点
-                if (empty($kpCodes)) {
-                    $kpCodes = $weaknessFilter;
-                    Log::info("用户未选择知识点,使用薄弱点作为kp_codes", [
-                        '最终kp_codes' => $kpCodes,
-                    ]);
-                }
-
-                // 【修复】教材出卷(assemble_type=3)不使用薄弱点,只按章节筛选
+                // 【修复】教材出卷(assemble_type=3)不使用薄弱点,严格按章节获取知识点
                 if ($assembleType == 3) {
-                    $kpCodes = [];
-                    Log::info("LearningAnalyticsService: 教材出卷不使用薄弱点,清空知识点", [
+                    Log::info("LearningAnalyticsService: 教材出卷不使用薄弱点", [
                         'assemble_type' => $assembleType,
-                        'chapter_ids' => $params['chapter_id_list'] ?? [],
+                        'weakness_count' => count($weaknessFilter),
+                        'action' => '将使用ExamTypeStrategy从章节关联获取的知识点'
                     ]);
+                    // 教材组卷不使用薄弱点
+                } else {
+                    // 如果用户没有指定知识点,使用学生的薄弱点(非教材组卷)
+                    if (empty($kpCodes)) {
+                        $kpCodes = $weaknessFilter;
+                        Log::info("用户未选择知识点,使用薄弱点作为kp_codes", [
+                            '最终kp_codes' => $kpCodes,
+                        ]);
+                    }
                 }
             }
 
@@ -1447,12 +1448,13 @@ class LearningAnalyticsService
 
             if (!$isMistakeBook && count($priorityQuestions) < $totalQuestions) {
                 try {
-                    // 【修复】教材出卷(assemble_type=3)只按章节筛选,不按知识点筛选
+                    // 【优化】教材出卷(assemble_type=3)保留知识点筛选,但额外使用章节筛选
                     if ($assembleType == 3) {
-                        $kpCodes = [];
-                        Log::info('LearningAnalyticsService: 教材出卷模式,清空知识点筛选', [
+                        Log::info('LearningAnalyticsService: 教材出卷模式,保留知识点筛选', [
                             'assemble_type' => $assembleType,
+                            'kp_codes_count' => count($kpCodes),
                             'chapter_ids' => $params['chapter_id_list'] ?? [],
+                            'textbook_catalog_node_ids' => $params['textbook_catalog_node_ids'] ?? [],
                         ]);
                     }