MistakeBookService.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Arr;
  4. use Illuminate\Support\Facades\Http;
  5. use Illuminate\Support\Facades\Log;
  6. class MistakeBookService
  7. {
  8. protected string $learningAnalyticsBase;
  9. protected string $questionBankBase;
  10. protected int $timeout;
  11. public function __construct(
  12. ?string $learningAnalyticsBase = null,
  13. ?string $questionBankBase = null,
  14. ?int $timeout = null
  15. ) {
  16. $this->learningAnalyticsBase = rtrim(
  17. $learningAnalyticsBase
  18. ?: config('services.learning_analytics.url', env('LEARNING_ANALYTICS_API_BASE', 'http://localhost:5016')),
  19. '/'
  20. );
  21. $this->questionBankBase = rtrim(
  22. $questionBankBase
  23. ?: config('services.question_bank.base_url', env('QUESTION_BANK_API_BASE', 'http://localhost:5015')),
  24. '/'
  25. );
  26. $this->timeout = $timeout ?? (int) config('services.learning_analytics.timeout', 20);
  27. }
  28. /**
  29. * 获取错题列表(支持多维筛选)
  30. */
  31. public function listMistakes(array $params = []): array
  32. {
  33. $query = array_filter([
  34. 'student_id' => $params['student_id'] ?? null,
  35. 'kp_ids' => $this->implodeIfArray($params['kp_ids'] ?? null),
  36. 'skill_ids' => $this->implodeIfArray($params['skill_ids'] ?? null),
  37. 'error_types' => $this->implodeIfArray($params['error_types'] ?? null),
  38. 'time_range' => $params['time_range'] ?? null,
  39. 'start_date' => $params['start_date'] ?? null,
  40. 'end_date' => $params['end_date'] ?? null,
  41. 'incorrect_only' => true, // ✅ 只获取错误记录(错题本的核心功能)
  42. 'page' => $params['page'] ?? 1,
  43. 'per_page' => $params['per_page'] ?? 20,
  44. ], fn ($value) => filled($value));
  45. try {
  46. $response = Http::timeout($this->timeout)
  47. ->get($this->learningAnalyticsBase . '/api/mistake-book', $query);
  48. if ($response->successful()) {
  49. info("MistakeBookService::listMistakes", [$response->json()]);
  50. $body = $response->json();
  51. $result = is_array($body) ? $body : ['data' => $body];
  52. // 补充知识点名称、技能信息和难度
  53. $result = $this->enrichMistakeData($result);
  54. // 获取统计数据
  55. if (!empty($params['student_id'])) {
  56. $summary = $this->summarize($params['student_id']);
  57. $result['statistics'] = [
  58. 'total_mistakes' => $summary['total'] ?? 0,
  59. 'this_week' => $summary['this_week'] ?? 0,
  60. 'pending_review' => $summary['pending_review'] ?? 0,
  61. 'mastery_rate' => $summary['mastery_rate'] ?? 0.0,
  62. ];
  63. }
  64. return $result;
  65. }
  66. Log::warning('MistakeBook list failed', [
  67. 'status' => $response->status(),
  68. 'body' => $response->body(),
  69. 'query' => $query,
  70. ]);
  71. } catch (\Throwable $e) {
  72. Log::error('MistakeBook list exception', [
  73. 'error' => $e->getMessage(),
  74. 'query' => $query,
  75. ]);
  76. }
  77. return [
  78. 'data' => [],
  79. 'meta' => ['total' => 0, 'page' => 1, 'per_page' => $query['per_page'] ?? 20],
  80. ];
  81. }
  82. /**
  83. * 获取单条错题详情(可选带学生ID保证隔离)
  84. */
  85. public function getMistakeDetail(string $mistakeId, ?string $studentId = null): array
  86. {
  87. $query = array_filter([
  88. 'student_id' => $studentId,
  89. ], fn ($value) => filled($value));
  90. try {
  91. $response = Http::timeout($this->timeout)
  92. ->get($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId, $query);
  93. if ($response->successful()) {
  94. $body = $response->json();
  95. $result = is_array($body) ? $body : [];
  96. // 补充知识点名称和技能信息
  97. $result = $this->enrichSingleMistakeData($result);
  98. return $result;
  99. }
  100. Log::warning('Mistake detail request failed', [
  101. 'mistake_id' => $mistakeId,
  102. 'status' => $response->status(),
  103. 'body' => $response->body(),
  104. ]);
  105. // 兼容:LearningAnalytics 当前未提供单条详情接口时,从列表中过滤
  106. if ($studentId) {
  107. $fallback = $this->listMistakes([
  108. 'student_id' => $studentId,
  109. 'per_page' => 100, // 兼容后端限制(<=100)
  110. ]);
  111. $matched = collect($fallback['data'] ?? [])->firstWhere('id', (int) $mistakeId);
  112. if ($matched) {
  113. return $matched;
  114. }
  115. }
  116. } catch (\Throwable $e) {
  117. Log::error('Mistake detail request exception', [
  118. 'mistake_id' => $mistakeId,
  119. 'student_id' => $studentId,
  120. 'error' => $e->getMessage(),
  121. ]);
  122. }
  123. return [];
  124. }
  125. /**
  126. * 获取错题统计概要
  127. */
  128. public function summarize(string $studentId): array
  129. {
  130. try {
  131. $response = Http::timeout($this->timeout)
  132. ->get($this->learningAnalyticsBase . '/api/mistake-book/summary', [
  133. 'student_id' => $studentId,
  134. ]);
  135. if ($response->successful()) {
  136. $body = $response->json();
  137. return is_array($body) ? $body : [];
  138. }
  139. Log::warning('MistakeBook summary failed', [
  140. 'status' => $response->status(),
  141. 'body' => $response->body(),
  142. 'student_id' => $studentId,
  143. ]);
  144. } catch (\Throwable $e) {
  145. Log::error('MistakeBook summary exception', [
  146. 'student_id' => $studentId,
  147. 'error' => $e->getMessage(),
  148. ]);
  149. }
  150. // fallback 返回一个空结构,避免前端崩溃
  151. return [
  152. 'total' => 0,
  153. 'this_week' => 0,
  154. 'pending_review' => 0,
  155. 'mastery_rate' => null,
  156. ];
  157. }
  158. /**
  159. * 获取错误模式与推荐路径
  160. */
  161. public function getMistakePatterns(string $studentId): array
  162. {
  163. try {
  164. $response = Http::timeout($this->timeout)
  165. ->get($this->learningAnalyticsBase . '/api/analytics/mistake-pattern', [
  166. 'student_id' => $studentId,
  167. ]);
  168. if ($response->successful()) {
  169. $body = $response->json();
  170. return is_array($body) ? $body : [];
  171. }
  172. Log::warning('Mistake pattern request failed', [
  173. 'status' => $response->status(),
  174. 'body' => $response->body(),
  175. 'student_id' => $studentId,
  176. ]);
  177. } catch (\Throwable $e) {
  178. Log::error('Mistake pattern request exception', [
  179. 'student_id' => $studentId,
  180. 'error' => $e->getMessage(),
  181. ]);
  182. }
  183. return [];
  184. }
  185. /**
  186. * 收藏/取消收藏错题
  187. */
  188. public function toggleFavorite(string $mistakeId, bool $favorite = true): bool
  189. {
  190. try {
  191. $response = Http::timeout($this->timeout)
  192. ->post($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId . '/favorite', [
  193. 'favorite' => $favorite,
  194. ]);
  195. return $response->successful();
  196. } catch (\Throwable $e) {
  197. Log::error('Toggle favorite failed', [
  198. 'mistake_id' => $mistakeId,
  199. 'favorite' => $favorite,
  200. 'error' => $e->getMessage(),
  201. ]);
  202. }
  203. return false;
  204. }
  205. /**
  206. * 标记已复习(向后兼容)
  207. */
  208. public function markReviewed(string $mistakeId): bool
  209. {
  210. try {
  211. $response = Http::timeout($this->timeout)
  212. ->post($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId . '/review');
  213. return $response->successful();
  214. } catch (\Throwable $e) {
  215. Log::error('Mark reviewed failed', [
  216. 'mistake_id' => $mistakeId,
  217. 'error' => $e->getMessage(),
  218. ]);
  219. }
  220. return false;
  221. }
  222. /**
  223. * 修改复习状态
  224. *
  225. * @param string $mistakeId 错题ID
  226. * @param string $action 操作类型:'increment' 或 'reset'
  227. * @param bool $forceReview 是否强制复习(仅对 increment 有效)
  228. * @return array 复习状态信息
  229. */
  230. public function updateReviewStatus(string $mistakeId, string $action = 'increment', bool $forceReview = false): array
  231. {
  232. try {
  233. $payload = array_filter([
  234. 'action' => $action,
  235. 'force_review' => $forceReview,
  236. ]);
  237. $response = Http::timeout($this->timeout)
  238. ->post($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId . '/review-status', $payload);
  239. if ($response->successful()) {
  240. $body = $response->json();
  241. return is_array($body) ? $body : [];
  242. }
  243. Log::warning('Update review status failed', [
  244. 'mistake_id' => $mistakeId,
  245. 'action' => $action,
  246. 'force_review' => $forceReview,
  247. 'status' => $response->status(),
  248. 'body' => $response->body(),
  249. ]);
  250. } catch (\Throwable $e) {
  251. Log::error('Update review status exception', [
  252. 'mistake_id' => $mistakeId,
  253. 'action' => $action,
  254. 'force_review' => $forceReview,
  255. 'error' => $e->getMessage(),
  256. ]);
  257. }
  258. return [
  259. 'success' => false,
  260. 'error' => '更新复习状态失败',
  261. ];
  262. }
  263. /**
  264. * 获取复习状态
  265. *
  266. * @param string $mistakeId 错题ID
  267. * @return array 复习状态信息
  268. */
  269. public function getReviewStatus(string $mistakeId): array
  270. {
  271. try {
  272. $response = Http::timeout($this->timeout)
  273. ->get($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId . '/review-status');
  274. if ($response->successful()) {
  275. $body = $response->json();
  276. return is_array($body) ? $body : [];
  277. }
  278. Log::warning('Get review status failed', [
  279. 'mistake_id' => $mistakeId,
  280. 'status' => $response->status(),
  281. 'body' => $response->body(),
  282. ]);
  283. } catch (\Throwable $e) {
  284. Log::error('Get review status exception', [
  285. 'mistake_id' => $mistakeId,
  286. 'error' => $e->getMessage(),
  287. ]);
  288. }
  289. return [
  290. 'success' => false,
  291. 'error' => '获取复习状态失败',
  292. ];
  293. }
  294. /**
  295. * 增加复习次数
  296. *
  297. * @param string $mistakeId 错题ID
  298. * @param bool $forceReview 是否强制复习
  299. * @return array 复习状态信息
  300. */
  301. public function incrementReviewCount(string $mistakeId, bool $forceReview = false): array
  302. {
  303. return $this->updateReviewStatus($mistakeId, 'increment', $forceReview);
  304. }
  305. /**
  306. * 重置为强制复习状态
  307. *
  308. * @param string $mistakeId 错题ID
  309. * @return array 复习状态信息
  310. */
  311. public function resetReviewStatus(string $mistakeId): array
  312. {
  313. return $this->updateReviewStatus($mistakeId, 'reset');
  314. }
  315. /**
  316. * 添加到重练清单
  317. */
  318. public function addToRetryList(string $mistakeId): bool
  319. {
  320. try {
  321. $response = Http::timeout($this->timeout)
  322. ->post($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId . '/retry-list');
  323. return $response->successful();
  324. } catch (\Throwable $e) {
  325. Log::error('Add to retry list failed', [
  326. 'mistake_id' => $mistakeId,
  327. 'error' => $e->getMessage(),
  328. ]);
  329. }
  330. return false;
  331. }
  332. /**
  333. * 基于错题推荐练习题
  334. */
  335. public function recommendPractice(string $studentId, array $kpIds = [], array $skillIds = []): array
  336. {
  337. $payload = array_filter([
  338. 'student_id' => $studentId,
  339. 'kp_ids' => array_values(array_unique($kpIds)),
  340. 'skill_ids' => array_values(array_unique($skillIds)),
  341. ], fn ($value) => $value !== null);
  342. try {
  343. $response = Http::timeout($this->timeout)
  344. ->post($this->questionBankBase . '/api/questions/recommend', $payload);
  345. if ($response->successful()) {
  346. $body = $response->json();
  347. return is_array($body) ? $body : ['data' => $body];
  348. }
  349. Log::warning('Recommend practice failed', [
  350. 'status' => $response->status(),
  351. 'body' => $response->body(),
  352. 'payload' => $payload,
  353. ]);
  354. } catch (\Throwable $e) {
  355. Log::error('Recommend practice exception', [
  356. 'payload' => $payload,
  357. 'error' => $e->getMessage(),
  358. ]);
  359. }
  360. return ['data' => []];
  361. }
  362. /**
  363. * 为学生仪表板提供快照数据
  364. */
  365. public function getPanelSnapshot(string $studentId, int $limit = 5): array
  366. {
  367. $list = $this->listMistakes([
  368. 'student_id' => $studentId,
  369. 'per_page' => $limit,
  370. ]);
  371. $patterns = $this->getMistakePatterns($studentId);
  372. $summary = $this->summarize($studentId);
  373. return [
  374. 'recent' => $list['data'] ?? [],
  375. 'weak_skills' => $patterns['top_skills'] ?? [],
  376. 'weak_kps' => $patterns['top_kps'] ?? [],
  377. 'error_types' => $patterns['error_types'] ?? [],
  378. 'recommend_path' => $patterns['recommend_path'] ?? [],
  379. 'stats' => [
  380. 'total' => $summary['total'] ?? Arr::get($list, 'meta.total', 0),
  381. 'this_week' => $summary['this_week'] ?? null,
  382. 'pending_review' => $summary['pending_review'] ?? null,
  383. 'mastery_rate' => $summary['mastery_rate'] ?? null,
  384. ],
  385. ];
  386. }
  387. private function implodeIfArray($value): ?string
  388. {
  389. if (is_array($value)) {
  390. return implode(',', array_filter($value));
  391. }
  392. return $value;
  393. }
  394. /**
  395. * 获取题目的全体正确率(LearningAnalytics 聚合)
  396. */
  397. public function getQuestionAccuracy(string $questionId): ?float
  398. {
  399. try {
  400. $response = Http::timeout($this->timeout)
  401. ->get($this->learningAnalyticsBase . '/api/analytics/question/' . $questionId . '/accuracy');
  402. if ($response->successful()) {
  403. $body = $response->json();
  404. return isset($body['accuracy']) ? floatval($body['accuracy']) : null;
  405. }
  406. } catch (\Throwable $e) {
  407. Log::warning('Get question accuracy failed', [
  408. 'question_id' => $questionId,
  409. 'error' => $e->getMessage(),
  410. ]);
  411. }
  412. return null;
  413. }
  414. /**
  415. * 补充错题数据中的知识点名称、技能信息和难度
  416. */
  417. private function enrichMistakeData(array $result): array
  418. {
  419. Log::info('MistakeBookService::enrichMistakeData - Starting enrichment', [
  420. 'data_count' => count($result['data'] ?? []),
  421. ]);
  422. if (!isset($result['data']) || !is_array($result['data'])) {
  423. Log::warning('MistakeBookService::enrichMistakeData - No data to enrich');
  424. return $result;
  425. }
  426. // 获取所有知识点代码
  427. $kpCodes = [];
  428. foreach ($result['data'] as $item) {
  429. if (!empty($item['question']['kp_code'])) {
  430. $kpCodes[] = $item['question']['kp_code'];
  431. }
  432. if (!empty($item['kp_ids']) && is_array($item['kp_ids'])) {
  433. $kpCodes = array_merge($kpCodes, $item['kp_ids']);
  434. }
  435. }
  436. $kpCodes = array_unique(array_filter($kpCodes));
  437. Log::info('MistakeBookService::enrichMistakeData - Found kp_codes', [
  438. 'kp_codes' => $kpCodes,
  439. ]);
  440. // 从KnowledgeService获取知识点详细信息
  441. $kpDetailMap = [];
  442. foreach ($kpCodes as $kpCode) {
  443. $detail = $this->getKnowledgePointDetail($kpCode);
  444. if (!empty($detail)) {
  445. $kpDetailMap[$kpCode] = $detail;
  446. }
  447. }
  448. Log::info('MistakeBookService::enrichMistakeData - Retrieved kp details', [
  449. 'kp_detail_map_keys' => array_keys($kpDetailMap),
  450. ]);
  451. // 补充数据
  452. foreach ($result['data'] as &$item) {
  453. // 补充知识点名称和技能信息
  454. if (isset($item['question']['kp_code'])) {
  455. $kpCode = $item['question']['kp_code'];
  456. if (isset($kpDetailMap[$kpCode])) {
  457. $detail = $kpDetailMap[$kpCode];
  458. $item['question']['kp_name'] = $detail['cn_name'] ?? $detail['en_name'] ?? $kpCode;
  459. $item['question']['skills'] = array_column($detail['skills'] ?? [], 'skill_name');
  460. Log::info('MistakeBookService::enrichMistakeData - Enriched item', [
  461. 'kp_code' => $kpCode,
  462. 'kp_name' => $item['question']['kp_name'],
  463. 'skills' => $item['question']['skills'],
  464. ]);
  465. } else {
  466. // 如果没有找到详细信息,使用代码作为名称
  467. $item['question']['kp_name'] = $kpCode;
  468. $item['question']['skills'] = [];
  469. Log::warning('MistakeBookService::enrichMistakeData - No detail found for kp', [
  470. 'kp_code' => $kpCode,
  471. ]);
  472. }
  473. }
  474. // 确保kp_ids数组存在且不为空
  475. if (!isset($item['kp_ids']) || !is_array($item['kp_ids'])) {
  476. $item['kp_ids'] = [];
  477. }
  478. // 如果kp_ids为空但有kp_code,则添加
  479. if (empty($item['kp_ids']) && !empty($item['question']['kp_code'])) {
  480. $item['kp_ids'] = [$item['question']['kp_code']];
  481. }
  482. // 确保skill_ids数组存在
  483. if (!isset($item['skill_ids']) || !is_array($item['skill_ids'])) {
  484. $item['skill_ids'] = [];
  485. }
  486. // 如果skill_ids为空但有skills,则使用skills
  487. if (empty($item['skill_ids']) && !empty($item['question']['skills'])) {
  488. $item['skill_ids'] = $item['question']['skills'];
  489. }
  490. // 补充难度值(如果缺失)
  491. if (!isset($item['question']['difficulty']) && isset($item['question']['kp_code'])) {
  492. $kpCode = $item['question']['kp_code'];
  493. if (isset($kpDetailMap[$kpCode])) {
  494. $detail = $kpDetailMap[$kpCode];
  495. // 使用importance作为难度(1-10)
  496. $item['question']['difficulty'] = ($detail['importance'] ?? 5) / 10.0;
  497. Log::info('MistakeBookService::enrichMistakeData - Set difficulty', [
  498. 'kp_code' => $kpCode,
  499. 'difficulty' => $item['question']['difficulty'],
  500. 'importance' => $detail['importance'] ?? 5,
  501. ]);
  502. }
  503. }
  504. }
  505. Log::info('MistakeBookService::enrichMistakeData - Completed enrichment', [
  506. 'processed_count' => count($result['data']),
  507. ]);
  508. return $result;
  509. }
  510. /**
  511. * 补充单条错题数据中的知识点名称、技能信息和难度
  512. */
  513. private function enrichSingleMistakeData(array $item): array
  514. {
  515. if (empty($item)) {
  516. return $item;
  517. }
  518. // 从KnowledgeService获取知识点详细信息
  519. $kpDetail = null;
  520. if (isset($item['question']['kp_code'])) {
  521. $kpCode = $item['question']['kp_code'];
  522. $kpDetail = $this->getKnowledgePointDetail($kpCode);
  523. }
  524. // 补充知识点名称和技能信息
  525. if (isset($item['question']['kp_code']) && $kpDetail) {
  526. $item['question']['kp_name'] = $kpDetail['cn_name'] ?? $kpDetail['en_name'] ?? $item['question']['kp_code'];
  527. $item['question']['skills'] = array_column($kpDetail['skills'] ?? [], 'skill_name');
  528. } else {
  529. if (isset($item['question']['kp_code'])) {
  530. $item['question']['kp_name'] = $item['question']['kp_code'];
  531. }
  532. $item['question']['skills'] = [];
  533. }
  534. // 确保kp_ids数组存在且不为空
  535. if (!isset($item['kp_ids']) || !is_array($item['kp_ids'])) {
  536. $item['kp_ids'] = [];
  537. }
  538. // 如果kp_ids为空但有kp_code,则添加
  539. if (empty($item['kp_ids']) && !empty($item['question']['kp_code'])) {
  540. $item['kp_ids'] = [$item['question']['kp_code']];
  541. }
  542. // 确保skill_ids数组存在
  543. if (!isset($item['skill_ids']) || !is_array($item['skill_ids'])) {
  544. $item['skill_ids'] = [];
  545. }
  546. // 如果skill_ids为空但有skills,则使用skills
  547. if (empty($item['skill_ids']) && !empty($item['question']['skills'])) {
  548. $item['skill_ids'] = $item['question']['skills'];
  549. }
  550. // 补充难度值(如果缺失)
  551. if (!isset($item['question']['difficulty']) && $kpDetail) {
  552. // 使用importance作为难度(1-10)
  553. $item['question']['difficulty'] = ($kpDetail['importance'] ?? 5) / 10.0;
  554. }
  555. return $item;
  556. }
  557. /**
  558. * 从QuestionBank服务获取知识点详细信息
  559. */
  560. private function getKnowledgePointDetail(string $kpCode): ?array
  561. {
  562. Log::info('MistakeBookService::getKnowledgePointDetail - Fetching detail from QuestionBank', [
  563. 'kp_code' => $kpCode,
  564. ]);
  565. try {
  566. // 获取QuestionBank中的题目来获取知识点信息
  567. $response = Http::timeout($this->timeout)
  568. ->get($this->questionBankBase . '/api/questions', [
  569. 'kp_code' => $kpCode,
  570. 'limit' => 1,
  571. ]);
  572. Log::info('MistakeBookService::getKnowledgePointDetail - Response from QuestionBank', [
  573. 'status' => $response->status(),
  574. 'has_body' => !empty($response->body()),
  575. ]);
  576. if ($response->successful()) {
  577. $data = $response->json();
  578. $questions = $data['data'] ?? [];
  579. if (!empty($questions)) {
  580. $question = $questions[0];
  581. $detail = [
  582. 'kp_code' => $kpCode,
  583. 'cn_name' => $question['kp_name'] ?? $kpCode,
  584. 'skills' => [],
  585. 'importance' => null,
  586. ];
  587. // 提取技能信息
  588. if (!empty($question['skills'])) {
  589. foreach ($question['skills'] as $skillCode) {
  590. $detail['skills'][] = [
  591. 'skill_code' => $skillCode,
  592. 'skill_name' => $skillCode,
  593. ];
  594. }
  595. }
  596. // 从difficulty计算importance(0-1转为1-10)
  597. if (isset($question['difficulty'])) {
  598. $detail['importance'] = intval($question['difficulty'] * 10);
  599. }
  600. Log::info('MistakeBookService::getKnowledgePointDetail - Success', [
  601. 'kp_code' => $kpCode,
  602. 'kp_name' => $detail['cn_name'],
  603. 'skills_count' => count($detail['skills']),
  604. 'importance' => $detail['importance'],
  605. ]);
  606. return $detail;
  607. }
  608. }
  609. } catch (\Throwable $e) {
  610. Log::error('Get knowledge point detail from QuestionBank failed', [
  611. 'kp_code' => $kpCode,
  612. 'error' => $e->getMessage(),
  613. ]);
  614. }
  615. return null;
  616. }
  617. }