Просмотр исходного кода

学情分析修复和增加教材系列状态

yemeishu 5 часов назад
Родитель
Сommit
8efaae19a5
2 измененных файлов с 162 добавлено и 27 удалено
  1. 111 8
      app/Services/ExamAnswerAnalysisService.php
  2. 51 19
      app/Services/MasteryCalculator.php

+ 111 - 8
app/Services/ExamAnswerAnalysisService.php

@@ -157,6 +157,7 @@ class ExamAnswerAnalysisService
     private function getQuestionKnowledgeMappings(array $questions): array
     private function getQuestionKnowledgeMappings(array $questions): array
     {
     {
         $mappings = [];
         $mappings = [];
+        $failedQuestions = [];
 
 
         // 直接从题目数据中提取知识点信息(不再调用外部服务)
         // 直接从题目数据中提取知识点信息(不再调用外部服务)
         foreach ($questions as $question) {
         foreach ($questions as $question) {
@@ -172,19 +173,34 @@ class ExamAnswerAnalysisService
                 ?? $question['kp_code']
                 ?? $question['kp_code']
                 ?? null;
                 ?? null;
 
 
-            if (!empty($kpCode)) {
+            // 如果请求中没有知识点信息,从数据库自动获取
+            if (empty($kpCode)) {
+                $dbKpInfo = $this->getQuestionKnowledgePointFromDb($questionId);
+                if ($dbKpInfo) {
+                    $kpMapping[] = [
+                        'kp_id' => $dbKpInfo['kp_id'],
+                        'kp_name' => $dbKpInfo['kp_name'],
+                        'weight' => 1.0
+                    ];
+                    Log::debug('从数据库获取题目知识点', [
+                        'question_id' => $questionId,
+                        'kp_id' => $dbKpInfo['kp_id']
+                    ]);
+                }
+            } else {
                 $kpMapping[] = [
                 $kpMapping[] = [
                     'kp_id' => $kpCode,
                     'kp_id' => $kpCode,
                     'kp_name' => $question['kp_name'] ?? $kpCode,
                     'kp_name' => $question['kp_name'] ?? $kpCode,
                     'weight' => 1.0
                     'weight' => 1.0
                 ];
                 ];
-            } else {
-                // 【修复】不允许使用默认知识点,必须明确指定
-                Log::warning('ExamAnswerAnalysisService: 题目缺少知识点信息', [
-                    'question_id' => $questionId,
-                    'question' => $question
+            }
+
+            // 如果仍然没有知识点信息,跳过该题目
+            if (empty($kpMapping)) {
+                $failedQuestions[] = $questionId;
+                Log::warning('跳过无法获取知识点的题目', [
+                    'question_id' => $questionId
                 ]);
                 ]);
-                // 不创建默认映射,让后续处理明确报错
                 continue;
                 continue;
             }
             }
 
 
@@ -194,14 +210,98 @@ class ExamAnswerAnalysisService
             ];
             ];
         }
         }
 
 
+        if (!empty($failedQuestions)) {
+            Log::warning('部分题目无法获取知识点信息', [
+                'failed_questions' => $failedQuestions,
+                'total_questions' => count($questions),
+                'mapped_questions' => count($mappings)
+            ]);
+        }
+
         Log::info('题目知识点映射构建完成', [
         Log::info('题目知识点映射构建完成', [
             'total_questions' => count($questions),
             'total_questions' => count($questions),
             'mapped_questions' => count($mappings),
             'mapped_questions' => count($mappings),
+            'failed_questions' => count($failedQuestions)
         ]);
         ]);
 
 
         return $mappings;
         return $mappings;
     }
     }
 
 
+    /**
+     * 从数据库获取题目的知识点信息
+     */
+    private function getQuestionKnowledgePointFromDb(string $questionId): ?array
+    {
+        try {
+            // 优先从 MySQL 的 questions 表获取
+            $question = DB::connection('mysql')
+                ->table('questions')
+                ->where('id', $questionId)
+                ->orWhere('question_code', $questionId)
+                ->first();
+
+            if ($question) {
+                // 尝试从 kp_code 或 knowledge_point 字段获取
+                $kpCode = $question->kp_code
+                    ?? $question->knowledge_point
+                    ?? $question->knowledge_points
+                    ?? null;
+
+                if ($kpCode) {
+                    return [
+                        'kp_id' => $kpCode,
+                        'kp_name' => $kpCode
+                    ];
+                }
+            }
+
+            // 备选:从 QuestionBank API 获取知识点
+            $questionBankInfo = $this->getQuestionFromQuestionBank($questionId);
+            if ($questionBankInfo && !empty($questionBankInfo['knowledge_points'])) {
+                $kpCode = $questionBankInfo['knowledge_points'][0]['code'] ?? null;
+                if ($kpCode) {
+                    return [
+                        'kp_id' => $kpCode,
+                        'kp_name' => $questionBankInfo['knowledge_points'][0]['name'] ?? $kpCode
+                    ];
+                }
+            }
+
+            return null;
+
+        } catch (\Exception $e) {
+            Log::warning('从数据库获取题目知识点失败', [
+                'question_id' => $questionId,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
+    /**
+     * 从 QuestionBank API 获取题目信息
+     */
+    private function getQuestionFromQuestionBank(string $questionId): ?array
+    {
+        try {
+            $baseUrl = config('services.question_bank_api.base_url', 'http://question-bank-api:5015');
+            $response = Http::timeout(10)->get($baseUrl . '/questions/' . $questionId);
+
+            if ($response->successful()) {
+                $data = $response->json();
+                return $data['data'] ?? null;
+            }
+
+            return null;
+        } catch (\Exception $e) {
+            Log::warning('从QuestionBank获取题目信息失败', [
+                'question_id' => $questionId,
+                'error' => $e->getMessage()
+            ]);
+            return null;
+        }
+    }
+
     /**
     /**
      * 计算知识点掌握度向量
      * 计算知识点掌握度向量
      * 【修复】集成MasteryCalculator的BKT模型进行精确计算
      * 【修复】集成MasteryCalculator的BKT模型进行精确计算
@@ -299,13 +399,16 @@ class ExamAnswerAnalysisService
         foreach ($knowledgeAttempts as $kpId => $data) {
         foreach ($knowledgeAttempts as $kpId => $data) {
             $attempts = $data['attempts'];
             $attempts = $data['attempts'];
 
 
+            // 如果没有学案基准难度,使用默认值2(提分)
+            $baseDifficulty = $examBaseDifficulty ?? 2;
+
             // 调用MasteryCalculator的核心算法(传入学案基准难度)
             // 调用MasteryCalculator的核心算法(传入学案基准难度)
             // 该算法包含:正确率、难度加权、时间效率、技能熟练度、遗忘曲线衰减
             // 该算法包含:正确率、难度加权、时间效率、技能熟练度、遗忘曲线衰减
             $masteryResult = $this->masteryCalculator->calculateMasteryLevel(
             $masteryResult = $this->masteryCalculator->calculateMasteryLevel(
                 $studentId ?? '', // 传递学生ID,用于保存掌握度到数据库
                 $studentId ?? '', // 传递学生ID,用于保存掌握度到数据库
                 $kpId,
                 $kpId,
                 $attempts,
                 $attempts,
-                $examBaseDifficulty
+                $baseDifficulty
             );
             );
 
 
             $masteryVector[$kpId] = [
             $masteryVector[$kpId] = [

+ 51 - 19
app/Services/MasteryCalculator.php

@@ -41,12 +41,17 @@ class MasteryCalculator
      */
      */
     public function calculateMasteryLevel(string $studentId, string $kpCode, ?array $attempts = null, ?int $examBaseDifficulty = null): array
     public function calculateMasteryLevel(string $studentId, string $kpCode, ?array $attempts = null, ?int $examBaseDifficulty = null): array
     {
     {
-        // 如果没有提供答题记录,从数据库查询
-        if ($attempts === null) {
+        // 优先使用传递的答题记录,如果没有则从数据库查询
+        if ($attempts === null || empty($attempts)) {
             $attempts = $this->getStudentAttempts($studentId, $kpCode);
             $attempts = $this->getStudentAttempts($studentId, $kpCode);
         }
         }
 
 
         if (empty($attempts)) {
         if (empty($attempts)) {
+            Log::warning('没有答题记录,无法计算掌握度', [
+                'student_id' => $studentId,
+                'kp_code' => $kpCode,
+                'attempts_provided' => $attempts !== null,
+            ]);
             return [
             return [
                 'mastery' => 0.0,
                 'mastery' => 0.0,
                 'confidence' => 0.0,
                 'confidence' => 0.0,
@@ -57,22 +62,14 @@ class MasteryCalculator
             ];
             ];
         }
         }
 
 
-        // 【新算法】使用学案基准难度的动态加减逻辑
+        // 如果没有学案基准难度,使用默认值2(提分)
         if ($examBaseDifficulty === null) {
         if ($examBaseDifficulty === null) {
-            Log::warning('缺少学案基准难度,无法计算掌握度', [
+            Log::warning('缺少学案基准难度,使用默认值2(提分)', [
                 'student_id' => $studentId,
                 'student_id' => $studentId,
                 'kp_code' => $kpCode,
                 'kp_code' => $kpCode,
-                'exam_base_difficulty' => $examBaseDifficulty,
+                'attempts_count' => count($attempts),
             ]);
             ]);
-            return [
-                'mastery' => 0.0,
-                'confidence' => 0.0,
-                'trend' => 'insufficient',
-                'total_attempts' => count($attempts),
-                'correct_attempts' => 0,
-                'accuracy_rate' => 0.0,
-                'error' => '缺少学案基准难度参数',
-            ];
+            $examBaseDifficulty = 2; // 默认提分难度
         }
         }
 
 
         $masteryData = $this->calculateMasteryWithExamDifficulty($studentId, $kpCode, $attempts, $examBaseDifficulty);
         $masteryData = $this->calculateMasteryWithExamDifficulty($studentId, $kpCode, $attempts, $examBaseDifficulty);
@@ -316,15 +313,50 @@ class MasteryCalculator
      */
      */
     private function getStudentAttempts(string $studentId, string $kpCode): array
     private function getStudentAttempts(string $studentId, string $kpCode): array
     {
     {
-        $attempts = DB::table('student_attempts')
+        $attempts = [];
+
+        // 优先从 student_answer_steps 表获取(步骤级记录)
+        $stepAttempts = DB::table('student_answer_steps')
             ->where('student_id', $studentId)
             ->where('student_id', $studentId)
-            ->where('kp_code', $kpCode)
+            ->where('kp_id', $kpCode)
             ->orderBy('created_at', 'asc')
             ->orderBy('created_at', 'asc')
             ->get();
             ->get();
 
 
-        return $attempts->map(function ($item) {
-            return (array) $item;
-        })->toArray();
+        foreach ($stepAttempts as $step) {
+            $attempts[] = [
+                'student_id' => $step->student_id,
+                'paper_id' => $step->exam_id,
+                'question_id' => $step->question_id,
+                'kp_code' => $step->kp_id,
+                'is_correct' => (bool) $step->is_correct,
+                'score_obtained' => $step->step_score,
+                'max_score' => $step->step_score, // 步骤分数本身就是满分
+                'created_at' => $step->created_at,
+            ];
+        }
+
+        // 如果没有步骤级记录,从 student_answer_questions 表获取(题目级记录)
+        if (empty($attempts)) {
+            $questionAttempts = DB::table('student_answer_questions')
+                ->where('student_id', $studentId)
+                ->orderBy('created_at', 'asc')
+                ->get();
+
+            foreach ($questionAttempts as $question) {
+                $attempts[] = [
+                    'student_id' => $question->student_id,
+                    'paper_id' => $question->exam_id,
+                    'question_id' => $question->question_id,
+                    'kp_code' => $kpCode, // 使用传入的kpCode
+                    'is_correct' => ($question->score_obtained ?? 0) > 0,
+                    'score_obtained' => $question->score_obtained ?? 0,
+                    'max_score' => $question->max_score ?? 0,
+                    'created_at' => $question->created_at,
+                ];
+            }
+        }
+
+        return $attempts;
     }
     }
 
 
     /**
     /**