'Tree data not found']; } $tree = json_decode(file_get_contents($treePath), true); $edges = file_exists($edgesPath) ? json_decode(file_get_contents($edgesPath), true) : []; $node = $this->findNodeInTree($tree, $nodeId); if (!$node) { return ['error' => 'Node not found']; } $masteryMap = $masteryData ?? $this->getActiveMasteryData(); $masteryInfo = $masteryMap[$nodeId] ?? null; $masteryLevel = $masteryInfo ? ($masteryInfo['mastery_level'] ?? 0) : 0; $totalAttempts = $masteryInfo ? ($masteryInfo['total_attempts'] ?? 0) : 0; $accuracy = $masteryInfo ? ($masteryInfo['accuracy_rate'] ?? 0) : 0; $prerequisites = []; $successors = []; foreach ($edges as $edge) { if (($edge['target'] ?? null) === $nodeId && ($edge['type'] ?? '') === 'prerequisite') { $prereqNode = $this->findNodeInTree($tree, $edge['source']); if ($prereqNode) { $prerequisites[] = [ 'id' => $edge['source'], 'name' => $prereqNode['name'] ?? $prereqNode['label'] ?? $edge['source'], 'mastery' => $masteryMap[$edge['source']]['mastery_level'] ?? 0, ]; } } if (($edge['source'] ?? null) === $nodeId && ($edge['type'] ?? '') === 'successor') { $succNode = $this->findNodeInTree($tree, $edge['target']); if ($succNode) { $successors[] = [ 'id' => $edge['target'], 'name' => $succNode['name'] ?? $succNode['label'] ?? $edge['target'], 'mastery' => $masteryMap[$edge['target']]['mastery_level'] ?? 0, ]; } } } $recommendations = $this->getRecommendations($nodeId); return [ 'id' => $nodeId, 'name' => $node['name'] ?? $node['label'] ?? $nodeId, 'code' => $node['code'] ?? $nodeId, 'mastery_level' => $masteryLevel, 'total_attempts' => $totalAttempts, 'accuracy_rate' => $accuracy, 'error_rate' => $totalAttempts > 0 ? (1 - $accuracy) : 0, 'prerequisites' => $prerequisites, 'successors' => $successors, 'recommendations' => $recommendations, 'skills' => $node['skills'] ?? [], ]; } protected function findNodeInTree(array $node, string $targetId): ?array { $nodeId = $node['code'] ?? $node['id'] ?? $node['label'] ?? null; if ($nodeId === $targetId) { return $node; } if (isset($node['children'])) { foreach ($node['children'] as $child) { $found = $this->findNodeInTree($child, $targetId); if ($found) { return $found; } } } return null; } protected function getRecommendations(string $nodeId): array { try { $questionBankService = app(\App\Services\QuestionBankService::class); $response = $questionBankService->getQuestionsByKpCode($nodeId, 3); if (empty($response['data'])) { return $this->getMockRecommendations($nodeId); } $recommendations = []; foreach ($response['data'] as $question) { $difficultyMap = [ 0.3 => '简单', 0.6 => '中等', 0.9 => '困难', ]; $difficulty = $difficultyMap[$question['difficulty'] ?? 0.6] ?? '中等'; $typeMap = [ 'choice' => '选择题', 'fill' => '填空题', 'answer' => '解答题', ]; $type = $typeMap[$question['question_type'] ?? 'answer'] ?? '解答题'; $stem = $question['stem'] ?? $question['content'] ?? '练习题'; $recommendations[] = [ 'id' => $question['id'] ?? $question['question_code'] ?? uniqid(), 'title' => mb_strimwidth($stem, 0, 50, '...'), 'difficulty' => $difficulty, 'type' => $type, ]; } return $recommendations; } catch (\Exception $e) { Log::error('Failed to fetch recommendations', [ 'node_id' => $nodeId, 'error' => $e->getMessage(), ]); return $this->getMockRecommendations($nodeId); } } protected function getMockRecommendations(string $nodeId): array { return [ [ 'id' => 1, 'title' => '基础练习:' . $nodeId, 'difficulty' => '简单', 'type' => '选择题', ], [ 'id' => 2, 'title' => '进阶练习:' . $nodeId, 'difficulty' => '中等', 'type' => '填空题', ], [ 'id' => 3, 'title' => '综合应用:' . $nodeId, 'difficulty' => '困难', 'type' => '解答题', ], ]; } protected function getActiveMasteryData(): array { if (property_exists($this, 'masteryData')) { return $this->masteryData ?? []; } if (property_exists($this, 'mindmapMasteryData')) { return $this->mindmapMasteryData ?? []; } return []; } }