Browse Source

fix:修复学习进度计算规则,排除初高中干扰

yemeishu 1 tuần trước cách đây
mục cha
commit
382d972e2b
1 tập tin đã thay đổi với 128 bổ sung57 xóa
  1. 128 57
      app/Services/StudentProgressService.php

+ 128 - 57
app/Services/StudentProgressService.php

@@ -5,6 +5,7 @@ namespace App\Services;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Cache;
+use App\Models\Student;
 
 class StudentProgressService
 {
@@ -15,8 +16,12 @@ class StudentProgressService
     {
         Log::info('获取学生学习进度', ['student_id' => $studentId]);
 
+        $studentGrade = Student::query()
+            ->where('student_id', $studentId)
+            ->value('grade');
+
         // 获取知识图谱结构
-        $graphData = $this->getKnowledgeGraphStructure();
+        $graphData = $this->getKnowledgeGraphStructure($studentGrade ? (int) $studentGrade : null);
         $leafKpCodes = $graphData['leafKpCodes'];
         $leafKpCodesSet = $graphData['leafKpCodesSet'];
         $kpCodes = $graphData['kpCodes'];
@@ -24,6 +29,18 @@ class StudentProgressService
 
         // 获取学生掌握度数据
         $mergedData = $this->getStudentMasteryData($studentId);
+        $mergedCountBefore = count($mergedData);
+        // 仅保留叶子节点数据,避免父节点干扰进度
+        $mergedData = array_filter($mergedData, function ($item) use ($leafKpCodesSet) {
+            return isset($leafKpCodesSet[$item['kp_code']]);
+        });
+        if ($mergedCountBefore > 0 && count($mergedData) !== $mergedCountBefore) {
+            Log::info('学习进度过滤父节点', [
+                'student_id' => $studentId,
+                'before' => $mergedCountBefore,
+                'after' => count($mergedData),
+            ]);
+        }
 
         // 如果没有数据,返回空结构(和批量接口行为一致)
         if (empty($mergedData)) {
@@ -44,9 +61,7 @@ class StudentProgressService
         }
 
         // 筛选叶子节点
-        $childMasteryData = collect($mergedData)->filter(function ($item) use ($leafKpCodesSet) {
-            return isset($leafKpCodesSet[$item['kp_code']]);
-        });
+        $childMasteryData = collect($mergedData);
 
         // 如果没有子知识点数据,也返回空结构
         if ($childMasteryData->isEmpty()) {
@@ -79,7 +94,7 @@ class StudentProgressService
             'child_mastery_sum' => round($totalChildMasterySum, 4),
             'max_child_score' => round($maxChildScore, 4),
             'learning_progress_percentage' => round($learningProgress * 100, 2),
-            'data_source' => implode(', ', collect($mergedData)->pluck('source_table')->unique()->toArray()),
+            'data_source' => 'student_knowledge_mastery',
             'child_mastery_max' => round($childMasteryData->max('mastery_level'), 4),
             'child_mastery_min' => round($childMasteryData->min('mastery_level'), 4),
             'child_mastery_avg' => round($childMasteryData->avg('mastery_level'), 4),
@@ -129,11 +144,13 @@ class StudentProgressService
 
         Log::info('批量获取学生学习进度', ['count' => count($studentIds)]);
 
-        // 获取知识图谱结构(所有学生共用)
-        $graphData = $this->getKnowledgeGraphStructure();
-        $leafKpCodes = $graphData['leafKpCodes'];
-        $leafKpCodesSet = $graphData['leafKpCodesSet'];
-        $maxChildScore = count($leafKpCodes) * 1.0;
+        $gradeMap = Student::query()
+            ->whereIn('student_id', $studentIds)
+            ->pluck('grade', 'student_id')
+            ->toArray();
+
+        $stageGraph = [];
+        $stageStats = [];
 
         // 批量获取掌握度数据
         $batchData = $this->getBatchStudentMasteryData($studentIds);
@@ -145,6 +162,19 @@ class StudentProgressService
 
         foreach ($studentIds as $studentId) {
             $studentId = (string) $studentId;
+            $grade = $gradeMap[$studentId] ?? null;
+            $stageKey = $this->getStageKey($grade ? (int) $grade : null);
+
+            if (!isset($stageGraph[$stageKey])) {
+                $stageGraph[$stageKey] = $this->getKnowledgeGraphStructure($grade ? (int) $grade : null);
+                $stageStats[$stageKey] = [
+                    'total_child_knowledge_points' => count($stageGraph[$stageKey]['leafKpCodes']),
+                    'student_count' => 0,
+                ];
+            }
+            $stageStats[$stageKey]['student_count']++;
+            $leafKpCodesSet = $stageGraph[$stageKey]['leafKpCodesSet'];
+            $maxChildScore = count($stageGraph[$stageKey]['leafKpCodes']) * 1.0;
             $mergedData = [];
 
             // 从 student_knowledge_mastery 获取
@@ -183,12 +213,25 @@ class StudentProgressService
                     'learning_progress' => 0,
                     'learning_progress_percentage' => 0,
                     'mastered_child_count' => 0,
-                    'total_child_count' => count($leafKpCodes),
+                    'total_child_count' => count($stageGraph[$stageKey]['leafKpCodes']),
                     'has_data' => false,
                 ];
                 continue;
             }
 
+            $mergedCountBefore = count($mergedData);
+            // 仅保留叶子节点数据,避免父节点干扰进度
+            $mergedData = array_filter($mergedData, function ($item) use ($leafKpCodesSet) {
+                return isset($leafKpCodesSet[$item['kp_code']]);
+            });
+            if ($mergedCountBefore > 0 && count($mergedData) !== $mergedCountBefore) {
+                Log::info('批量学习进度过滤父节点', [
+                    'student_id' => $studentId,
+                    'before' => $mergedCountBefore,
+                    'after' => count($mergedData),
+                ]);
+            }
+
             // 筛选叶子节点并计算
             $childMasterySum = 0;
             $masteredChildCount = 0;
@@ -207,7 +250,7 @@ class StudentProgressService
                 'learning_progress' => round($learningProgress, 6),
                 'learning_progress_percentage' => round($learningProgress * 100, 2),
                 'mastered_child_count' => $masteredChildCount,
-                'total_child_count' => count($leafKpCodes),
+                'total_child_count' => count($stageGraph[$stageKey]['leafKpCodes']),
                 'child_mastery_sum' => round($childMasterySum, 4),
                 'has_data' => true,
             ];
@@ -220,7 +263,7 @@ class StudentProgressService
             'data' => $results,
             'meta' => [
                 'total_students' => count($studentIds),
-                'total_child_knowledge_points' => count($leafKpCodes),
+                'stage_summary' => $stageStats,
                 'calculated_at' => now()->toISOString(),
             ]
         ];
@@ -229,26 +272,92 @@ class StudentProgressService
     /**
      * 获取知识图谱结构(缓存 5 分钟)
      */
-    private function getKnowledgeGraphStructure(): array
+    private function getKnowledgeGraphStructure(?int $grade = null): array
     {
-        return Cache::remember('knowledge_graph_structure', 300, function () {
-            $allKps = DB::connection('remote_mysql')
+        $stageKey = $this->getStageKey($grade);
+        $cacheKey = 'knowledge_graph_structure_' . $stageKey;
+
+        return Cache::remember($cacheKey, 300, function () use ($grade) {
+            $query = DB::connection('remote_mysql')
                 ->table('knowledge_points')
-                ->select(['kp_code', 'parent_kp_code'])
-                ->get();
+                ->select(['kp_code', 'parent_kp_code', 'grade']);
+
+            $stageLabel = $this->getStageLabel($grade);
+            if ($stageLabel) {
+                $query->where('grade', $stageLabel);
+            }
+
+            $allKps = $query->get();
 
             $kpCodes = $allKps->pluck('kp_code')->toArray();
             $parentCodes = $allKps->whereNotNull('parent_kp_code')->pluck('parent_kp_code')->unique()->toArray();
             $leafKpCodes = array_values(array_diff($kpCodes, $parentCodes));
+            $maxDepth = $this->calculateMaxDepth($allKps);
 
             return [
                 'kpCodes' => $kpCodes,
                 'leafKpCodes' => $leafKpCodes,
                 'leafKpCodesSet' => array_flip($leafKpCodes),
+                'maxDepth' => $maxDepth,
             ];
         });
     }
 
+    private function getStageLabel(?int $grade): ?string
+    {
+        if ($grade === null || $grade <= 0) {
+            return null;
+        }
+
+        if ($grade <= 6) {
+            return '小学';
+        }
+
+        if ($grade <= 9) {
+            return '初中';
+        }
+
+        return '高中';
+    }
+
+    private function getStageKey(?int $grade): string
+    {
+        $label = $this->getStageLabel($grade);
+        return $label ?: 'all';
+    }
+
+    private function calculateMaxDepth($knowledgePoints): int
+    {
+        $children = [];
+        foreach ($knowledgePoints as $kp) {
+            if (!empty($kp->parent_kp_code)) {
+                $children[$kp->parent_kp_code][] = $kp->kp_code;
+            }
+        }
+
+        $depthCache = [];
+        $maxDepth = 1;
+        $visit = function ($kpCode) use (&$visit, &$children, &$depthCache, &$maxDepth): int {
+            if (isset($depthCache[$kpCode])) {
+                return $depthCache[$kpCode];
+            }
+            if (empty($children[$kpCode])) {
+                $depthCache[$kpCode] = 1;
+                return 1;
+            }
+            $childDepths = array_map(fn ($child) => $visit($child), $children[$kpCode]);
+            $depthCache[$kpCode] = 1 + max($childDepths);
+            $maxDepth = max($maxDepth, $depthCache[$kpCode]);
+            return $depthCache[$kpCode];
+        };
+
+        foreach ($knowledgePoints as $kp) {
+            $visit($kp->kp_code);
+        }
+
+        return $maxDepth;
+    }
+
     /**
      * 获取单个学生的掌握度数据
      */
@@ -277,37 +386,6 @@ class StudentProgressService
             Log::warning('从 student_knowledge_mastery 获取数据失败', ['error' => $e->getMessage()]);
         }
 
-        try {
-            $simpleData = DB::connection('remote_mysql')
-                ->table('student_mastery')
-                ->where('student_id', $studentId)
-                ->select(['kp as kp_code', 'mastery', 'attempts as total_attempts', 'correct as correct_attempts', 'updated_at'])
-                ->get();
-
-            foreach ($simpleData as $item) {
-                $kpCode = $item->kp_code;
-                $masteryLevel = (float) $item->mastery;
-
-                if (isset($mergedData[$kpCode])) {
-                    if ($masteryLevel > $mergedData[$kpCode]['mastery_level']) {
-                        $mergedData[$kpCode]['mastery_level'] = $masteryLevel;
-                        $mergedData[$kpCode]['source_table'] = 'student_mastery (updated)';
-                    }
-                } else {
-                    $mergedData[$kpCode] = [
-                        'kp_code' => $kpCode,
-                        'mastery_level' => $masteryLevel,
-                        'total_attempts' => $item->total_attempts ?? 0,
-                        'correct_attempts' => $item->correct_attempts ?? 0,
-                        'source_table' => 'student_mastery',
-                        'updated_at' => $item->updated_at ?? null
-                    ];
-                }
-            }
-        } catch (\Exception $e) {
-            Log::warning('从 student_mastery 获取数据失败', ['error' => $e->getMessage()]);
-        }
-
         return $mergedData;
     }
 
@@ -323,16 +401,9 @@ class StudentProgressService
             ->get()
             ->groupBy('student_id');
 
-        $simpleData = DB::connection('remote_mysql')
-            ->table('student_mastery')
-            ->whereIn('student_id', $studentIds)
-            ->select(['student_id', 'kp as kp_code', 'mastery'])
-            ->get()
-            ->groupBy('student_id');
-
         return [
             'detailed' => $detailedData->toArray(),
-            'simple' => $simpleData->toArray(),
+            'simple' => [],
         ];
     }
 }