Bladeren bron

学生掌握度计算

yemeishu 21 uur geleden
bovenliggende
commit
67151fc752
1 gewijzigde bestanden met toevoegingen van 207 en 11 verwijderingen
  1. 207 11
      app/Services/KnowledgeMasteryService.php

+ 207 - 11
app/Services/KnowledgeMasteryService.php

@@ -144,6 +144,22 @@ class KnowledgeMasteryService
         return $result;
     }
 
+    /**
+     * 批量获取知识点名称映射(返回关联数组)
+     */
+    private function getKnowledgePointNamesMap(array $kpCodes): array
+    {
+        $result = [];
+
+        foreach ($kpCodes as $kpCode) {
+            $name = $this->getKnowledgePointName($kpCode);
+            // 如果获取不到名称,使用kpCode作为默认值
+            $result[$kpCode] = $name ?: $kpCode;
+        }
+
+        return $result;
+    }
+
     /**
      * 获取单个知识点名称(带缓存)
      */
@@ -153,24 +169,104 @@ class KnowledgeMasteryService
 
         return Cache::remember($cacheKey, 3600, function () use ($kpCode) {
             try {
+                // 首先尝试从知识图谱服务获取
                 $response = Http::timeout(5)
                     ->get($this->knowledgeServiceBase . '/knowledge-points/' . $kpCode);
 
                 if ($response->successful()) {
                     $data = $response->json();
-                    return $data['cn_name'] ?? $data['en_name'] ?? null;
+                    $name = $data['cn_name'] ?? $data['en_name'] ?? null;
+                    if ($name) {
+                        return $name;
+                    }
                 }
             } catch (\Throwable $e) {
-                Log::debug('Failed to get knowledge point name', [
+                Log::debug('Failed to get knowledge point name from service, will use local mapping', [
                     'kp_code' => $kpCode,
                     'error' => $e->getMessage(),
                 ]);
             }
 
-            return null;
+            // 如果知识图谱服务不可用,使用本地映射
+            return $this->getLocalKnowledgePointName($kpCode);
         });
     }
 
+    /**
+     * 本地知识点名称映射(作为备选方案)
+     */
+    private function getLocalKnowledgePointName(string $kpCode): ?string
+    {
+        // 常见的数学知识点中文名称映射
+        $nameMap = [
+            'A09' => '因式分解',
+            'E01' => '一元二次方程',
+            'E03' => '一元二次方程的解法',
+            'E04' => '一元二次方程的应用',
+            'E06' => '判别式',
+            'F03' => '函数的概念',
+            'F05' => '函数的图像',
+            'F06' => '函数的性质',
+            'G02' => '几何图形',
+            'G03' => '三角形',
+            'R06' => '实数',
+            'S01' => '三角函数',
+            'S02' => '正弦函数',
+            'S03' => '余弦函数',
+            'S05' => '正切函数',
+            'E01B' => '因式分解法',
+            'E03B' => '公式法',
+            'E04B' => '配方法',
+            'G02B' => '平行四边形',
+            'G03E' => '等腰三角形',
+            'G03G' => '等边三角形',
+            'M01C' => '有理数',
+            'M04E' => '不等式',
+            'PY02' => '概率初步',
+            'ST04' => '统计与概率',
+            'M04E1' => '一元一次不等式',
+            'PY02D' => '随机事件',
+            'SIM02' => '相似三角形',
+            'ST04D' => '数据的收集',
+            'APP_E4' => '应用题',
+            'SIM02A' => '相似比',
+        ];
+
+        return $nameMap[$kpCode] ?? null;
+    }
+
+    /**
+     * 判断知识点是否为另一个知识点的子知识点
+     */
+    private function isChildOf(string $childCode, string $parentCode): bool
+    {
+        // 常见的父子关系映射
+        $parentChildMap = [
+            'E01' => ['E01B'], // 一元二次方程 -> 因式分解法
+            'E03' => ['E03B'], // 一元二次方程的解法 -> 公式法
+            'E04' => ['E04B'], // 一元二次方程的应用 -> 配方法
+            'G02' => ['G02B'], // 几何图形 -> 平行四边形
+            'G03' => ['G03E', 'G03G'], // 三角形 -> 等腰三角形, 等边三角形
+            'M04E' => ['M04E1'], // 不等式 -> 一元一次不等式
+            'PY02' => ['PY02D'], // 概率初步 -> 随机事件
+            'SIM02' => ['SIM02A'], // 相似三角形 -> 相似比
+            'ST04' => ['ST04D'], // 统计与概率 -> 数据的收集
+            'G03' => ['G03E', 'G03G'], // 三角形 -> 等腰三角形, 等边三角形
+        ];
+
+        // 直接通过代码前缀判断
+        if (strpos($childCode, $parentCode) === 0 && $childCode !== $parentCode) {
+            return true;
+        }
+
+        // 通过映射表判断
+        if (isset($parentChildMap[$parentCode]) && in_array($childCode, $parentChildMap[$parentCode])) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * 获取知识图谱统计信息(带缓存)
      */
@@ -308,29 +404,129 @@ class KnowledgeMasteryService
                 // 解析掌握度数据
                 $masteryData = json_decode($snapshot->mastery_data, true) ?: [];
 
-                // 提取知识点信息
+                // 提取知识点信息 - 修复字段名
                 $knowledgePoints = [];
-                foreach ($masteryData as $kpData) {
-                    $knowledgePoints[] = [
-                        'kp_code' => $kpData['kp_code'] ?? '',
-                        'kp_name' => $kpData['kp_name'] ?? ($kpData['kp_code'] ?? ''),
-                        'mastery_level' => floatval($kpData['mastery_level'] ?? 0),
-                    ];
+                $parentKnowledgePoints = []; // 存储父知识点信息
+                $kpCodes = array_keys($masteryData); // 收集所有kp_code用于批量获取名称
+
+                // 批量获取知识点名称
+                $kpNamesMap = $this->getKnowledgePointNamesMap($kpCodes);
+
+                // 第一步:分类父知识点和子知识点
+                foreach ($masteryData as $kpCode => $kpData) {
+                    $isParent = (bool) ($kpData['is_parent'] ?? false);
+                    $masteryLevel = floatval($kpData['current_mastery'] ?? 0);
+                    $change = floatval($kpData['change'] ?? 0);
+                    $weight = intval($kpData['weight'] ?? 1);
+
+                    if ($isParent) {
+                        // 父知识点 - 稍后重新计算
+                        $parentKnowledgePoints[$kpCode] = [
+                            'kp_code' => $kpCode,
+                            'kp_name' => $kpNamesMap[$kpCode] ?? $kpCode,
+                            'original_mastery' => $masteryLevel,
+                            'change' => $change,
+                            'weight' => $weight,
+                            'children' => [] // 存储子知识点
+                        ];
+                    } else {
+                        // 子知识点 - 直接添加
+                        $knowledgePoints[] = [
+                            'kp_code' => $kpCode,
+                            'kp_name' => $kpNamesMap[$kpCode] ?? $kpCode,
+                            'mastery_level' => round($masteryLevel, 2),
+                            'change' => round($change, 2),
+                            'is_child' => true,
+                            'parent_code' => null, // 稍后填入父知识点
+                        ];
+                    }
+                }
+
+                // 第二步:建立父子关系并计算父知识点掌握度
+                foreach ($masteryData as $kpCode => $kpData) {
+                    $isParent = (bool) ($kpData['is_parent'] ?? false);
+
+                    if (!$isParent) {
+                        // 查找父知识点(通过代码前缀匹配或其他逻辑)
+                        foreach ($parentKnowledgePoints as $parentCode => $parentData) {
+                            // 简单的父子关系判断:如果子知识点代码包含父知识点代码前缀
+                            if (strpos($kpCode, $parentCode) === 0 || $this->isChildOf($kpCode, $parentCode)) {
+                                // 添加到父知识点的子知识点列表
+                                $parentKnowledgePoints[$parentCode]['children'][] = $kpCode;
+
+                                // 更新子知识点的父知识点信息
+                                foreach ($knowledgePoints as &$child) {
+                                    if ($child['kp_code'] === $kpCode) {
+                                        $child['parent_code'] = $parentCode;
+                                        break;
+                                    }
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                // 第三步:计算父知识点的掌握度(加权平均)
+                foreach ($parentKnowledgePoints as $parentCode => &$parentData) {
+                    if (empty($parentData['children'])) {
+                        // 如果没有子知识点,使用原始值
+                        $parentData['mastery_level'] = round($parentData['original_mastery'], 2);
+                    } else {
+                        // 计算子知识点的加权平均
+                        $totalWeightedMastery = 0;
+                        $totalWeight = 0;
+
+                        foreach ($parentData['children'] as $childCode) {
+                            if (isset($masteryData[$childCode])) {
+                                $childMastery = floatval($masteryData[$childCode]['current_mastery'] ?? 0);
+                                $childWeight = intval($masteryData[$childCode]['weight'] ?? 1);
+                                $totalWeightedMastery += $childMastery * $childWeight;
+                                $totalWeight += $childWeight;
+                            }
+                        }
+
+                        if ($totalWeight > 0) {
+                            $parentData['mastery_level'] = round($totalWeightedMastery / $totalWeight, 2);
+                            // 重新计算变化值
+                            $parentData['change'] = round($parentData['mastery_level'] - $parentData['original_mastery'], 2);
+                        } else {
+                            $parentData['mastery_level'] = round($parentData['original_mastery'], 2);
+                        }
+                    }
+
+                    $parentData['is_parent'] = true;
+                    unset($parentData['original_mastery']); // 移除临时字段
+
+                    // 添加到知识点列表
+                    $knowledgePoints[] = $parentData;
                 }
+                unset($parentData); // 避免引用问题
+
+                // 第四步:按知识点代码排序
+                usort($knowledgePoints, function($a, $b) {
+                    return strcmp($a['kp_code'], $b['kp_code']);
+                });
 
                 return [
                     'snapshot_id' => $snapshot->snapshot_id,
                     'student_id' => $snapshot->student_id,
+                    'paper_id' => $snapshot->paper_id,
                     'overall_mastery' => floatval($snapshot->overall_mastery),
                     'weak_knowledge_points_count' => intval($snapshot->weak_knowledge_points_count),
                     'strong_knowledge_points_count' => intval($snapshot->strong_knowledge_points_count),
                     'knowledge_points' => $knowledgePoints,
+                    'knowledge_points_count' => count($knowledgePoints),
                     'created_at' => $snapshot->created_at,
                     'snapshot_time' => $snapshot->snapshot_time,
                 ];
             })->toArray();
 
-            Log::info('KnowledgeMasteryService::getGraphSnapshots (Local)', ['student_id' => $studentId, 'limit' => $limit]);
+            Log::info('KnowledgeMasteryService::getGraphSnapshots (Local)', [
+                'student_id' => $studentId,
+                'limit' => $limit,
+                'snapshot_count' => count($snapshotList)
+            ]);
             return [
                 'success' => true,
                 'data' => [