learningAnalyticsBase = ''; $this->knowledgeServiceBase = rtrim( $knowledgeServiceBase ?: config('services.knowledge_service.url', env('KNOWLEDGE_SERVICE_API_BASE', 'http://localhost:5011')), '/' ); $this->timeout = 20; } /** * 获取学生知识点掌握情况统计 * * @param string $studentId 学生ID * @return array */ public function getStats(string $studentId): array { try { // 使用本地的MasteryCalculator $masteryCalculator = app(MasteryCalculator::class); $overview = $masteryCalculator->getStudentMasteryOverview($studentId); // 转换为兼容格式 $body = [ 'student_id' => $studentId, 'total_knowledge_points' => $overview['total_knowledge_points'], 'average_mastery' => $overview['average_mastery_level'], 'mastered_count' => $overview['mastered_knowledge_points'], 'good_count' => $overview['good_knowledge_points'], 'weak_count' => $overview['weak_knowledge_points'], 'details' => $overview['details'], ]; // 丰富知识点名称(如果有knowledge_points表) $body = $this->enrichWithKnowledgePointNames($body); // 添加知识图谱总数统计 $graphStats = $this->getKnowledgeGraphStats(); $body['graph_total_knowledge_points'] = $graphStats['total'] ?? 0; Log::info('KnowledgeMasteryService::getStats (Local)', ['student_id' => $studentId]); return [ 'success' => true, 'data' => $body, ]; } catch (\Throwable $e) { Log::error('Knowledge mastery stats exception', [ 'student_id' => $studentId, 'error' => $e->getMessage(), ]); return [ 'success' => false, 'error' => '获取知识点掌握情况异常: ' . $e->getMessage(), ]; } } /** * 丰富知识点名称 */ private function enrichWithKnowledgePointNames(array $data): array { if (empty($data['details'])) { return $data; } // 确保 details 是数组格式(处理 stdClass 对象) $details = $data['details']; if (!is_array($details)) { $details = []; } elseif (isset($details[0]) && is_object($details[0])) { // 将 stdClass 对象转换为关联数组 $details = array_map(function ($item) { return (array) $item; }, $details); } // 收集所有kp_code $kpCodes = array_column($details, 'kp_code'); if (empty($kpCodes)) { return $data; } // 批量获取知识点名称 $kpNames = $this->getKnowledgePointNames($kpCodes); // 丰富details数据 foreach ($details as &$detail) { $kpCode = $detail['kp_code'] ?? null; if ($kpCode && isset($kpNames[$kpCode])) { $detail['kp_name'] = $kpNames[$kpCode]; } else { $detail['kp_name'] = $kpCode; // fallback to code } } // 更新原始数据 $data['details'] = $details; return $data; } /** * 批量获取知识点名称 */ private function getKnowledgePointNames(array $kpCodes): array { $result = []; foreach ($kpCodes as $kpCode) { $name = $this->getKnowledgePointName($kpCode); if ($name) { $result[$kpCode] = $name; } } return $result; } /** * 获取单个知识点名称(带缓存) */ private function getKnowledgePointName(string $kpCode): ?string { $cacheKey = "kp_name_{$kpCode}"; 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; } } catch (\Throwable $e) { Log::debug('Failed to get knowledge point name', [ 'kp_code' => $kpCode, 'error' => $e->getMessage(), ]); } return null; }); } /** * 获取知识图谱统计信息(带缓存) */ public function getKnowledgeGraphStats(): array { return Cache::remember('knowledge_graph_stats', 3600, function () { try { $response = Http::timeout(10) ->get($this->knowledgeServiceBase . '/knowledge-points/'); if ($response->successful()) { $data = $response->json(); $items = $data['data'] ?? $data ?? []; return [ 'total' => count($items), 'updated_at' => now()->toISOString(), ]; } } catch (\Throwable $e) { Log::error('Failed to get knowledge graph stats', [ 'error' => $e->getMessage(), ]); } return ['total' => 0]; }); } /** * 获取学生知识点图谱数据 * * @param string $studentId 学生ID * @param string|null $examId 考试ID(可选,不指定则返回最新快照) * @return array */ public function getGraph(string $studentId, ?string $examId = null): array { try { // 使用本地的MasteryCalculator $masteryCalculator = app(MasteryCalculator::class); $overview = $masteryCalculator->getStudentMasteryOverview($studentId); // 转换为图谱格式 $nodes = []; $edges = []; foreach ($overview['details'] as $detail) { $masteryLevel = floatval($detail->mastery_level ?? 0); $kpCode = $detail->kp_code; // 节点 $nodes[] = [ 'id' => $kpCode, 'label' => $kpCode, 'mastery' => $masteryLevel, 'mastery_level' => $this->getMasteryLevelLabel($masteryLevel), 'color' => $this->getMasteryColor($masteryLevel), 'size' => 20 + ($masteryLevel * 20), ]; // 这里可以添加知识点之间的依赖关系边 // 暂时为空,后续从knowledge_points表获取 } $graphData = [ 'nodes' => $nodes, 'edges' => $edges, 'statistics' => [ 'total_nodes' => count($nodes), 'total_edges' => count($edges), 'average_mastery' => $overview['average_mastery_level'], ], ]; Log::info('KnowledgeMasteryService::getGraph (Local)', ['student_id' => $studentId, 'exam_id' => $examId]); return [ 'success' => true, 'data' => $graphData, ]; } catch (\Throwable $e) { Log::error('Knowledge graph exception', [ 'student_id' => $studentId, 'exam_id' => $examId, 'error' => $e->getMessage(), ]); return [ 'success' => false, 'error' => '获取知识点图谱异常: ' . $e->getMessage(), ]; } } /** * 获取掌握度等级标签 */ private function getMasteryLevelLabel(float $mastery): string { if ($mastery >= 0.85) return 'mastered'; if ($mastery >= 0.70) return 'good'; if ($mastery >= 0.50) return 'fair'; return 'weak'; } /** * 获取掌握度颜色 */ private function getMasteryColor(float $mastery): string { if ($mastery >= 0.85) return '#52c41a'; // 绿色 - 掌握 if ($mastery >= 0.70) return '#1890ff'; // 蓝色 - 良好 if ($mastery >= 0.50) return '#faad14'; // 橙色 - 一般 return '#ff4d4f'; // 红色 - 薄弱 } /** * 获取学生知识点图谱快照列表 * * @param string $studentId 学生ID * @param int $limit 返回数量限制 * @return array */ public function getGraphSnapshots(string $studentId, int $limit = 10): array { try { // 从knowledge_point_mastery_snapshots表获取快照 $snapshots = \DB::table('knowledge_point_mastery_snapshots') ->where('student_id', $studentId) ->orderBy('created_at', 'desc') ->limit($limit) ->get(); $snapshotList = $snapshots->map(function ($snapshot) { // 解析掌握度数据 $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), ]; } return [ 'snapshot_id' => $snapshot->snapshot_id, 'student_id' => $snapshot->student_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, 'created_at' => $snapshot->created_at, 'snapshot_time' => $snapshot->snapshot_time, ]; })->toArray(); Log::info('KnowledgeMasteryService::getGraphSnapshots (Local)', ['student_id' => $studentId, 'limit' => $limit]); return [ 'success' => true, 'data' => [ 'snapshots' => $snapshotList, 'total' => count($snapshotList), ], ]; } catch (\Throwable $e) { Log::error('Knowledge graph snapshots exception', [ 'student_id' => $studentId, 'limit' => $limit, 'error' => $e->getMessage(), ]); return [ 'success' => false, 'error' => '获取知识点图谱快照列表异常: ' . $e->getMessage(), ]; } } /** * 获取学生知识点掌握摘要(简化版) * * @param string $studentId 学生ID * @return array */ public function getSummary(string $studentId): array { $stats = $this->getStats($studentId); if (!$stats['success']) { return $stats; } $data = $stats['data']; return [ 'success' => true, 'data' => [ 'student_id' => $data['student_id'] ?? $studentId, 'total' => $data['total_knowledge_points'] ?? 0, 'mastered' => $data['mastered_knowledge_points'] ?? 0, 'unmastered' => $data['unmastered_knowledge_points'] ?? 0, 'mastery_rate' => $data['mastery_rate'] ?? 0.0, 'mastery_percentage' => round(($data['mastery_rate'] ?? 0) * 100, 1) . '%', 'graph_total' => $data['graph_total_knowledge_points'] ?? 0, ], ]; } /** * 创建知识点掌握度快照 * * @param string $studentId 学生ID * @param string $snapshotType 快照类型 (exam/report/manual/scheduled) * @param string|null $sourceId 来源ID * @param string|null $sourceName 来源名称 * @param string|null $notes 备注 * @return array */ public function createSnapshot( string $studentId, string $snapshotType = 'report', ?string $sourceId = null, ?string $sourceName = null, ?string $notes = null ): array { try { // 使用LocalAIAnalysisService创建快照 $localAI = app(LocalAIAnalysisService::class); // 获取当前掌握度数据 $masteryCalculator = app(MasteryCalculator::class); $overview = $masteryCalculator->getStudentMasteryOverview($studentId); $snapshots = []; foreach ($overview['details'] as $detail) { $snapshotId = \DB::table('knowledge_point_mastery_snapshots')->insertGetId([ 'student_id' => $studentId, 'kp_code' => $detail->kp_code, 'mastery_level' => $detail->mastery_level, 'confidence_level' => $detail->confidence_level, 'snapshot_type' => $snapshotType, 'source_id' => $sourceId, 'source_name' => $sourceName, 'notes' => $notes, 'created_at' => now(), 'updated_at' => now(), ]); $snapshots[] = [ 'snapshot_id' => $snapshotId, 'kp_code' => $detail->kp_code, 'mastery_level' => $detail->mastery_level, ]; } Log::info('KnowledgeMasteryService::createSnapshot (Local)', [ 'student_id' => $studentId, 'snapshot_type' => $snapshotType, 'snapshot_count' => count($snapshots), ]); return [ 'success' => true, 'data' => [ 'snapshots' => $snapshots, 'total_snapshots' => count($snapshots), ], ]; } catch (\Throwable $e) { Log::error('Create knowledge mastery snapshot exception', [ 'student_id' => $studentId, 'error' => $e->getMessage(), ]); return [ 'success' => false, 'error' => '创建知识点掌握度快照异常: ' . $e->getMessage(), ]; } } }