|
|
@@ -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' => [
|