Przeglądaj źródła

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

yemeishu 11 godzin temu
rodzic
commit
4c8c834f29

+ 11 - 1
app/Http/Controllers/Api/ExamAnswerAnalysisController.php

@@ -39,7 +39,7 @@ class ExamAnswerAnalysisController extends Controller
                 'paper_id' => 'required|string|max:255',
                 'student_id' => 'required|string|max:255',
                 'questions' => 'required|array|min:1',
-                'questions.*.question_id' => 'required|string|max:255',
+                'questions.*.question_bank_id' => 'required|string|max:255',
                 'questions.*.student_answer' => 'sometimes|string',
                 'questions.*.is_correct' => 'sometimes|array',
                 'questions.*.teacher_comment' => 'sometimes|string|nullable',
@@ -62,6 +62,16 @@ class ExamAnswerAnalysisController extends Controller
 
             $examData = $request->only(['paper_id', 'student_id', 'questions']);
 
+            // 【修改】将 question_bank_id 转换为 question_id 供内部使用
+            if (isset($examData['questions'])) {
+                foreach ($examData['questions'] as &$question) {
+                    if (isset($question['question_bank_id'])) {
+                        $question['question_id'] = $question['question_bank_id'];
+                        unset($question['question_bank_id']);
+                    }
+                }
+            }
+
             // 调用分析服务
             $result = $this->analysisService->analyzeExamAnswers($examData);
 

+ 125 - 12
app/Services/ExamAnswerAnalysisService.php

@@ -517,13 +517,104 @@ class ExamAnswerAnalysisService
                 'previous_mastery' => $historyMasteryLevel,
                 'confidence' => $newConfidence,
                 'change' => $newMastery - $historyMasteryLevel,
-                'weight' => $currentWeight
+                'weight' => $currentWeight,
+                'is_parent' => false
             ];
         }
 
+        // 【修复】计算并更新父节点掌握度,同时添加到返回数组中
+        $parentMasteryData = $this->updateParentMasteryLevels($studentId, array_keys($knowledgeMasteryVector));
+
+        // 合并父节点数据到返回数组
+        $updatedMastery = array_merge($updatedMastery, $parentMasteryData);
+
         return $updatedMastery;
     }
 
+    /**
+     * 计算并更新父节点掌握度
+     */
+    private function updateParentMasteryLevels(string $studentId, array $childKpCodes): array
+    {
+        $parentMasteryData = [];
+
+        try {
+            // 获取所有子节点的父节点
+            $parentKpCodes = DB::connection('mysql')
+                ->table('knowledge_points')
+                ->whereIn('kp_code', $childKpCodes)
+                ->whereNotNull('parent_kp_code')
+                ->distinct()
+                ->pluck('parent_kp_code')
+                ->toArray();
+
+            foreach ($parentKpCodes as $parentKpCode) {
+                // 使用MasteryCalculator计算父节点掌握度
+                $parentMastery = $this->masteryCalculator->calculateParentMastery($studentId, $parentKpCode);
+
+                // 获取父节点历史数据
+                $historyParentMastery = DB::connection('mysql')
+                    ->table('student_knowledge_mastery')
+                    ->where('student_id', $studentId)
+                    ->where('kp_code', $parentKpCode)
+                    ->first();
+
+                $previousMastery = $historyParentMastery->mastery_level ?? 0.5;
+
+                // 保存父节点掌握度到数据库
+                DB::connection('mysql')
+                    ->table('student_knowledge_mastery')
+                    ->updateOrInsert(
+                        ['student_id' => $studentId, 'kp_code' => $parentKpCode],
+                        [
+                            'mastery_level' => $parentMastery,
+                            'confidence_level' => 0.8, // 父节点置信度默认为0.8
+                            'total_attempts' => 1, // 父节点不记录答题次数
+                            'correct_attempts' => 0, // 父节点不记录正确次数
+                            'mastery_trend' => 'calculated', // 标记为计算得出
+                            'last_mastery_update' => now(),
+                            'updated_at' => now(),
+                        ]
+                    );
+
+                // 添加到返回数组中
+                $parentMasteryData[$parentKpCode] = [
+                    'kp_id' => $parentKpCode,
+                    'current_mastery' => $parentMastery,
+                    'previous_mastery' => $previousMastery,
+                    'confidence' => 0.8,
+                    'change' => $parentMastery - $previousMastery,
+                    'weight' => 1,
+                    'is_parent' => true
+                ];
+
+                Log::debug('父节点掌握度已更新', [
+                    'student_id' => $studentId,
+                    'parent_kp_code' => $parentKpCode,
+                    'parent_mastery' => $parentMastery
+                ]);
+            }
+
+            if (!empty($parentKpCodes)) {
+                Log::info('父节点掌握度计算完成', [
+                    'student_id' => $studentId,
+                    'child_count' => count($childKpCodes),
+                    'parent_count' => count($parentKpCodes),
+                    'parent_kp_codes' => $parentKpCodes
+                ]);
+            }
+
+        } catch (\Exception $e) {
+            Log::warning('计算父节点掌握度失败', [
+                'student_id' => $studentId,
+                'child_kp_codes' => $childKpCodes,
+                'error' => $e->getMessage()
+            ]);
+        }
+
+        return $parentMasteryData;
+    }
+
     /**
      * 生成题目维度分析(包含AI分析和解题思路)
      */
@@ -730,21 +821,37 @@ class ExamAnswerAnalysisService
             ];
         }
 
-        // 计算平均掌握度
-        $averageMastery = array_sum(array_column($knowledgePoints, 'current_mastery')) / count($knowledgePoints);
+        // 计算平均掌握度(只计算子节点,不包括父节点)
+        $childNodes = array_filter($knowledgePoints, fn($kp) => !($kp['is_parent'] ?? false));
+        $totalChildNodes = count($childNodes);
 
-        // 掌握度分布
-        $mastered = array_filter($knowledgePoints, fn($kp) => $kp['current_mastery'] >= 0.85);
-        $good = array_filter($knowledgePoints, fn($kp) => $kp['current_mastery'] >= 0.70 && $kp['current_mastery'] < 0.85);
-        $weak = array_filter($knowledgePoints, fn($kp) => $kp['current_mastery'] < 0.70);
+        if ($totalChildNodes === 0) {
+            // 如果没有子节点,只计算所有节点
+            $averageMastery = array_sum(array_column($knowledgePoints, 'current_mastery')) / count($knowledgePoints);
+        } else {
+            // 计算所有节点的平均掌握度(包括父节点)
+            $allMastery = array_column($knowledgePoints, 'current_mastery');
+            $averageMastery = array_sum($allMastery) / count($allMastery);
+        }
+
+        // 掌握度分布(只统计子节点)
+        $mastered = array_filter($childNodes, fn($kp) => $kp['current_mastery'] >= 0.85);
+        $good = array_filter($childNodes, fn($kp) => $kp['current_mastery'] >= 0.70 && $kp['current_mastery'] < 0.85);
+        $weak = array_filter($childNodes, fn($kp) => $kp['current_mastery'] < 0.70);
+
+        // 排序找出优势和薄弱点(只包括子节点)
+        usort($childNodes, fn($a, $b) => $b['current_mastery'] <=> $a['current_mastery']);
+        $topStrengths = array_slice($childNodes, 0, 3);
+        $topWeaknesses = array_slice(array_reverse($childNodes), 0, 3);
 
-        // 排序找出优势和薄弱点
-        usort($knowledgePoints, fn($a, $b) => $b['current_mastery'] <=> $a['current_mastery']);
-        $topStrengths = array_slice($knowledgePoints, 0, 3);
-        $topWeaknesses = array_slice(array_reverse($knowledgePoints), 0, 3);
+        // 统计父子节点数量
+        $childCount = count(array_filter($knowledgePoints, fn($kp) => !($kp['is_parent'] ?? false)));
+        $parentCount = count(array_filter($knowledgePoints, fn($kp) => $kp['is_parent'] ?? false));
 
         return [
-            'total_knowledge_points' => count($knowledgePoints),
+            'total_knowledge_points' => $totalChildNodes, // 只统计子节点数量
+            'child_nodes_count' => $childCount,
+            'parent_nodes_count' => $parentCount,
             'average_mastery' => round($averageMastery, 4),
             'mastery_distribution' => [
                 'mastered' => count($mastered),
@@ -765,7 +872,13 @@ class ExamAnswerAnalysisService
     {
         $recommendations = [];
 
+        // 只对子节点生成推荐,忽略父节点
         foreach ($updatedMastery as $kpId => $data) {
+            // 跳过父节点
+            if ($data['is_parent'] ?? false) {
+                continue;
+            }
+
             $mastery = $data['current_mastery'];
             $confidence = $data['confidence'];
             $weight = $data['weight'];

+ 53 - 26
app/Services/ExamPdfExportService.php

@@ -307,11 +307,19 @@ class ExamPdfExportService
         }])->find($paperId);
 
         if (!$paper) {
-            Log::error('ExamPdfExportService: 未找到试卷', [
+            Log::warning('ExamPdfExportService: 未找到试卷,将尝试仅基于分析数据生成PDF', [
                 'paper_id' => $paperId,
                 'student_id' => $studentId,
             ]);
-            return null;
+
+            // 【修复】即使试卷不存在,也尝试基于分析数据生成PDF
+            $paper = new \stdClass();
+            $paper->paper_id = $paperId;
+            $paper->paper_name = "学情分析报告_{$studentId}_{$paperId}";
+            $paper->question_count = 0;
+            $paper->total_score = 0;
+            $paper->created_at = now();
+            $paper->questions = collect();
         }
 
         $student = Student::find($studentId);
@@ -449,38 +457,50 @@ class ExamPdfExportService
 
                 // 计算与本次考试相关的父节点掌握度(基于所有兄弟节点)
                 $parentMasteryLevels = [];
+
+                // 【修复】使用数据库查询正确匹配父子关系,而不是字符串前缀
                 foreach ($allParentMasteryLevels as $parentKpCode => $parentMastery) {
-                    // 检查这个父节点是否有子节点在本次考试中出现
-                    $hasRelevantChild = false;
-                    foreach ($examKpCodes as $childKpCode) {
-                        if (str_starts_with($childKpCode, $parentKpCode)) {
-                            $hasRelevantChild = true;
-                            break;
-                        }
-                    }
-                    if ($hasRelevantChild) {
+                    // 查询这个父节点的所有子节点
+                    $childNodes = DB::connection('mysql')
+                        ->table('knowledge_points')
+                        ->where('parent_kp_code', $parentKpCode)
+                        ->pluck('kp_code')
+                        ->toArray();
+
+                    // 检查是否有子节点在本次考试中出现
+                    $relevantChildren = array_intersect($examKpCodes, $childNodes);
+
+                    if (!empty($relevantChildren)) {
                         // 【修复】计算父节点变化:基于所有子节点的平均变化
                         $childChanges = [];
-                        foreach ($examKpCodes as $childKpCode) {
-                            if (str_starts_with($childKpCode, $parentKpCode)) {
-                                $previousChild = $previousMasteryData[$childKpCode]['previous_mastery'] ?? null;
-                                $currentChild = null;
-                                foreach ($masteryData as $item) {
-                                    if ($item['kp_code'] === $childKpCode) {
-                                        $currentChild = $item['mastery_level'];
-                                        break;
-                                    }
-                                }
-                                if ($previousChild !== null && $currentChild !== null) {
-                                    $childChanges[] = floatval($currentChild) - floatval($previousChild);
+                        foreach ($relevantChildren as $childKpCode) {
+                            $previousChild = $previousMasteryData[$childKpCode]['previous_mastery'] ?? null;
+                            $currentChild = null;
+                            foreach ($masteryData as $item) {
+                                if ($item['kp_code'] === $childKpCode) {
+                                    $currentChild = $item['mastery_level'];
+                                    break;
                                 }
                             }
+                            if ($previousChild !== null && $currentChild !== null) {
+                                $childChanges[] = floatval($currentChild) - floatval($previousChild);
+                            }
                         }
                         $avgChange = !empty($childChanges) ? array_sum($childChanges) / count($childChanges) : null;
 
+                        // 获取父节点中文名称
+                        $parentKpInfo = DB::connection('mysql')
+                            ->table('knowledge_points')
+                            ->where('kp_code', $parentKpCode)
+                            ->first();
+
                         $parentMasteryLevels[$parentKpCode] = [
+                            'kp_code' => $parentKpCode,
+                            'kp_name' => $parentKpInfo->name ?? $parentKpCode,
                             'mastery_level' => $parentMastery,
+                            'mastery_percentage' => round($parentMastery * 100, 1),
                             'mastery_change' => $avgChange,
+                            'children' => $relevantChildren,
                         ];
                     }
                 }
@@ -671,7 +691,7 @@ class ExamPdfExportService
     /**
      * 处理题目数据(用于报告)
      */
-    private function processQuestionsForReport(Paper $paper, array $questionDetails, array $kpNameMap): array
+    private function processQuestionsForReport($paper, array $questionDetails, array $kpNameMap): array
     {
         $grouped = [
             'choice' => [],
@@ -679,8 +699,15 @@ class ExamPdfExportService
             'answer' => [],
         ];
 
-        $sortedQuestions = $paper->questions
-            ->sortBy(function (PaperQuestion $q, int $idx) {
+        // 【修复】处理空的试卷(questions可能不存在)
+        $questions = $paper->questions ?? collect();
+        if ($questions->isEmpty()) {
+            Log::info('ExamPdfExportService: 试卷没有题目,返回空数组');
+            return $grouped;
+        }
+
+        $sortedQuestions = $questions
+            ->sortBy(function ($q, int $idx) {
                 $number = $q->question_number ?? $idx + 1;
                 return is_numeric($number) ? (float) $number : ($q->id ?? $idx);
             });