|
|
@@ -216,7 +216,7 @@ class QuestionBankService
|
|
|
// 增加超时时间到60秒,确保有足够时间启动异步任务
|
|
|
// 注意:API是异步的,只需等待任务启动(1-2秒),不需要等待AI生成完成
|
|
|
$response = Http::timeout(60)
|
|
|
- ->post($this->baseUrl . '/generate-intelligent-questions', $params);
|
|
|
+ ->post($this->baseUrl . '/ai/generate-intelligent-questions', $params);
|
|
|
|
|
|
if ($response->successful()) {
|
|
|
return $response->json();
|
|
|
@@ -1079,4 +1079,216 @@ class QuestionBankService
|
|
|
{
|
|
|
return $this->getTaskStatus($taskId) ?? ['status' => 'unknown'];
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取知识点题目统计信息
|
|
|
+ * 根据知识点代码,统计该知识点及其子知识点和技能点的题目数量
|
|
|
+ */
|
|
|
+ public function getKnowledgePointStatistics(?string $kpCode = null): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ // 获取知识图谱数据和题目统计数据
|
|
|
+ $knowledgeGraph = $this->getKnowledgeGraph();
|
|
|
+ $nodes = $knowledgeGraph['nodes'] ?? [];
|
|
|
+ $edges = $knowledgeGraph['edges'] ?? [];
|
|
|
+ $questionStats = $this->getQuestionsStatisticsFromApi();
|
|
|
+
|
|
|
+ // 构建知识点索引
|
|
|
+ $nodeMap = [];
|
|
|
+ foreach ($nodes as $node) {
|
|
|
+ if (!empty($node['kp_code'])) {
|
|
|
+ $nodeMap[$node['kp_code']] = $node;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建子知识点关系(从edges中提取)
|
|
|
+ $childrenMap = [];
|
|
|
+ $parentMap = [];
|
|
|
+ foreach ($edges as $edge) {
|
|
|
+ $source = $edge['source'] ?? '';
|
|
|
+ $target = $edge['target'] ?? '';
|
|
|
+ $direction = $edge['relation_direction'] ?? '';
|
|
|
+
|
|
|
+ if (!empty($source) && !empty($target)) {
|
|
|
+ if ($direction === 'DOWNSTREAM') {
|
|
|
+ $childrenMap[$source][] = $target;
|
|
|
+ $parentMap[$target] = $source;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建技能点统计
|
|
|
+ $skillStats = [];
|
|
|
+ foreach ($questionStats as $stat) {
|
|
|
+ $code = $stat['kp_code'] ?? '';
|
|
|
+ $skills = $stat['skills_list'] ?? [];
|
|
|
+
|
|
|
+ if (!empty($code)) {
|
|
|
+ foreach ($skills as $skillCode) {
|
|
|
+ if (!empty($skillCode)) {
|
|
|
+ if (!isset($skillStats[$code])) {
|
|
|
+ $skillStats[$code] = [];
|
|
|
+ }
|
|
|
+ if (!isset($skillStats[$code][$skillCode])) {
|
|
|
+ $skillStats[$code][$skillCode] = 0;
|
|
|
+ }
|
|
|
+ $skillStats[$code][$skillCode]++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果指定了特定知识点,只返回该知识点的统计
|
|
|
+ if ($kpCode && isset($nodeMap[$kpCode])) {
|
|
|
+ return $this->buildKnowledgePointStats($kpCode, $nodeMap, $childrenMap, $questionStats, $skillStats);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 否则返回所有顶级知识点的统计
|
|
|
+ $result = [];
|
|
|
+ $rootNodes = [];
|
|
|
+
|
|
|
+ // 找出根节点(没有父节点的节点)
|
|
|
+ foreach ($nodes as $node) {
|
|
|
+ $code = $node['kp_code'] ?? '';
|
|
|
+ if (!empty($code) && !isset($parentMap[$code])) {
|
|
|
+ $rootNodes[] = $code;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($rootNodes as $rootCode) {
|
|
|
+ $result[] = $this->buildKnowledgePointStats($rootCode, $nodeMap, $childrenMap, $questionStats, $skillStats);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按题目总数排序
|
|
|
+ usort($result, function($a, $b) {
|
|
|
+ return ($b['total_questions'] ?? 0) <=> ($a['total_questions'] ?? 0);
|
|
|
+ });
|
|
|
+
|
|
|
+ return $result;
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('获取知识点统计失败', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取知识图谱数据
|
|
|
+ */
|
|
|
+ private function getKnowledgeGraph(): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $knowledgeApiBase = config('services.knowledge_api.base_url', 'http://localhost:5011');
|
|
|
+ $response = Http::timeout(10)
|
|
|
+ ->get($knowledgeApiBase . '/graph/export');
|
|
|
+
|
|
|
+ if ($response->successful()) {
|
|
|
+ return $response->json();
|
|
|
+ }
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('获取知识图谱失败', ['error' => $e->getMessage()]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ['nodes' => [], 'edges' => []];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从 API 获取题目统计
|
|
|
+ */
|
|
|
+ private function getQuestionsStatisticsFromApi(): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ // 调用题库 API 获取统计数据
|
|
|
+ $response = Http::timeout(30)
|
|
|
+ ->get($this->baseUrl . '/questions/statistics');
|
|
|
+
|
|
|
+ if ($response->successful()) {
|
|
|
+ $data = $response->json();
|
|
|
+ return $data['by_kp'] ?? [];
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::warning('获取题目统计API失败', [
|
|
|
+ 'status' => $response->status(),
|
|
|
+ 'url' => $this->baseUrl . '/questions/statistics'
|
|
|
+ ]);
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('获取题目统计异常', [
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ 'url' => $this->baseUrl . '/questions/statistics'
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建单个知识点的统计信息
|
|
|
+ */
|
|
|
+ private function buildKnowledgePointStats(
|
|
|
+ string $kpCode,
|
|
|
+ array $nodeMap,
|
|
|
+ array $childrenMap,
|
|
|
+ array $questionStats,
|
|
|
+ array $skillStats
|
|
|
+ ): array {
|
|
|
+ $node = $nodeMap[$kpCode] ?? null;
|
|
|
+ if (!$node) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取直接子知识点
|
|
|
+ $children = $childrenMap[$kpCode] ?? [];
|
|
|
+ $directQuestionCount = 0;
|
|
|
+
|
|
|
+ // 查找当前知识点的题目数
|
|
|
+ foreach ($questionStats as $stat) {
|
|
|
+ if ($stat['kp_code'] === $kpCode) {
|
|
|
+ $directQuestionCount = $stat['question_count'] ?? 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算子知识点统计
|
|
|
+ $childrenStats = [];
|
|
|
+ foreach ($children as $childCode) {
|
|
|
+ $childStats = $this->buildKnowledgePointStats($childCode, $nodeMap, $childrenMap, $questionStats, $skillStats);
|
|
|
+ if (!empty($childStats)) {
|
|
|
+ $childrenStats[] = $childStats;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算子知识点题目总数
|
|
|
+ $childrenQuestionCount = 0;
|
|
|
+ foreach ($childrenStats as $child) {
|
|
|
+ $childrenQuestionCount += $child['total_questions'] ?? 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前知识点的技能点统计
|
|
|
+ $skillsCount = 0;
|
|
|
+ if (isset($skillStats[$kpCode])) {
|
|
|
+ $skillsCount = array_sum($skillStats[$kpCode]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'cn_name' => $node['cn_name'] ?? $kpCode,
|
|
|
+ 'en_name' => $node['en_name'] ?? '',
|
|
|
+ 'total_questions' => $directQuestionCount + $childrenQuestionCount,
|
|
|
+ 'direct_questions' => $directQuestionCount,
|
|
|
+ 'children_questions' => $childrenQuestionCount,
|
|
|
+ 'children' => $childrenStats,
|
|
|
+ 'skills_count' => count($skillStats[$kpCode] ?? []),
|
|
|
+ 'skills_total_questions' => $skillsCount,
|
|
|
+ 'skills' => array_map(function($skillCode, $count) use ($kpCode) {
|
|
|
+ return [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'skill_code' => $skillCode,
|
|
|
+ 'question_count' => $count
|
|
|
+ ];
|
|
|
+ }, array_keys($skillStats[$kpCode] ?? []), array_values($skillStats[$kpCode] ?? []))
|
|
|
+ ];
|
|
|
+ }
|
|
|
}
|