|
|
@@ -45,6 +45,7 @@ class MistakeBookService
|
|
|
'time_range' => $params['time_range'] ?? null,
|
|
|
'start_date' => $params['start_date'] ?? null,
|
|
|
'end_date' => $params['end_date'] ?? null,
|
|
|
+ 'incorrect_only' => true, // ✅ 只获取错误记录(错题本的核心功能)
|
|
|
'page' => $params['page'] ?? 1,
|
|
|
'per_page' => $params['per_page'] ?? 20,
|
|
|
], fn ($value) => filled($value));
|
|
|
@@ -56,7 +57,23 @@ class MistakeBookService
|
|
|
if ($response->successful()) {
|
|
|
info("MistakeBookService::listMistakes", [$response->json()]);
|
|
|
$body = $response->json();
|
|
|
- return is_array($body) ? $body : ['data' => $body];
|
|
|
+ $result = is_array($body) ? $body : ['data' => $body];
|
|
|
+
|
|
|
+ // 补充知识点名称、技能信息和难度
|
|
|
+ $result = $this->enrichMistakeData($result);
|
|
|
+
|
|
|
+ // 获取统计数据
|
|
|
+ if (!empty($params['student_id'])) {
|
|
|
+ $summary = $this->summarize($params['student_id']);
|
|
|
+ $result['statistics'] = [
|
|
|
+ 'total_mistakes' => $summary['total'] ?? 0,
|
|
|
+ 'this_week' => $summary['this_week'] ?? 0,
|
|
|
+ 'pending_review' => $summary['pending_review'] ?? 0,
|
|
|
+ 'mastery_rate' => $summary['mastery_rate'] ?? 0.0,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $result;
|
|
|
}
|
|
|
|
|
|
Log::warning('MistakeBook list failed', [
|
|
|
@@ -92,7 +109,12 @@ class MistakeBookService
|
|
|
|
|
|
if ($response->successful()) {
|
|
|
$body = $response->json();
|
|
|
- return is_array($body) ? $body : [];
|
|
|
+ $result = is_array($body) ? $body : [];
|
|
|
+
|
|
|
+ // 补充知识点名称和技能信息
|
|
|
+ $result = $this->enrichSingleMistakeData($result);
|
|
|
+
|
|
|
+ return $result;
|
|
|
}
|
|
|
|
|
|
Log::warning('Mistake detail request failed', [
|
|
|
@@ -216,7 +238,7 @@ class MistakeBookService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 标记已复习
|
|
|
+ * 标记已复习(向后兼容)
|
|
|
*/
|
|
|
public function markReviewed(string $mistakeId): bool
|
|
|
{
|
|
|
@@ -235,6 +257,110 @@ class MistakeBookService
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 修改复习状态
|
|
|
+ *
|
|
|
+ * @param string $mistakeId 错题ID
|
|
|
+ * @param string $action 操作类型:'increment' 或 'reset'
|
|
|
+ * @param bool $forceReview 是否强制复习(仅对 increment 有效)
|
|
|
+ * @return array 复习状态信息
|
|
|
+ */
|
|
|
+ public function updateReviewStatus(string $mistakeId, string $action = 'increment', bool $forceReview = false): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $payload = array_filter([
|
|
|
+ 'action' => $action,
|
|
|
+ 'force_review' => $forceReview,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $response = Http::timeout($this->timeout)
|
|
|
+ ->post($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId . '/review-status', $payload);
|
|
|
+
|
|
|
+ if ($response->successful()) {
|
|
|
+ $body = $response->json();
|
|
|
+ return is_array($body) ? $body : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::warning('Update review status failed', [
|
|
|
+ 'mistake_id' => $mistakeId,
|
|
|
+ 'action' => $action,
|
|
|
+ 'force_review' => $forceReview,
|
|
|
+ 'status' => $response->status(),
|
|
|
+ 'body' => $response->body(),
|
|
|
+ ]);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::error('Update review status exception', [
|
|
|
+ 'mistake_id' => $mistakeId,
|
|
|
+ 'action' => $action,
|
|
|
+ 'force_review' => $forceReview,
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'success' => false,
|
|
|
+ 'error' => '更新复习状态失败',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取复习状态
|
|
|
+ *
|
|
|
+ * @param string $mistakeId 错题ID
|
|
|
+ * @return array 复习状态信息
|
|
|
+ */
|
|
|
+ public function getReviewStatus(string $mistakeId): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $response = Http::timeout($this->timeout)
|
|
|
+ ->get($this->learningAnalyticsBase . '/api/mistake-book/' . $mistakeId . '/review-status');
|
|
|
+
|
|
|
+ if ($response->successful()) {
|
|
|
+ $body = $response->json();
|
|
|
+ return is_array($body) ? $body : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::warning('Get review status failed', [
|
|
|
+ 'mistake_id' => $mistakeId,
|
|
|
+ 'status' => $response->status(),
|
|
|
+ 'body' => $response->body(),
|
|
|
+ ]);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::error('Get review status exception', [
|
|
|
+ 'mistake_id' => $mistakeId,
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'success' => false,
|
|
|
+ 'error' => '获取复习状态失败',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 增加复习次数
|
|
|
+ *
|
|
|
+ * @param string $mistakeId 错题ID
|
|
|
+ * @param bool $forceReview 是否强制复习
|
|
|
+ * @return array 复习状态信息
|
|
|
+ */
|
|
|
+ public function incrementReviewCount(string $mistakeId, bool $forceReview = false): array
|
|
|
+ {
|
|
|
+ return $this->updateReviewStatus($mistakeId, 'increment', $forceReview);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置为强制复习状态
|
|
|
+ *
|
|
|
+ * @param string $mistakeId 错题ID
|
|
|
+ * @return array 复习状态信息
|
|
|
+ */
|
|
|
+ public function resetReviewStatus(string $mistakeId): array
|
|
|
+ {
|
|
|
+ return $this->updateReviewStatus($mistakeId, 'reset');
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 添加到重练清单
|
|
|
*/
|
|
|
@@ -349,4 +475,239 @@ class MistakeBookService
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 补充错题数据中的知识点名称、技能信息和难度
|
|
|
+ */
|
|
|
+ private function enrichMistakeData(array $result): array
|
|
|
+ {
|
|
|
+ Log::info('MistakeBookService::enrichMistakeData - Starting enrichment', [
|
|
|
+ 'data_count' => count($result['data'] ?? []),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if (!isset($result['data']) || !is_array($result['data'])) {
|
|
|
+ Log::warning('MistakeBookService::enrichMistakeData - No data to enrich');
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有知识点代码
|
|
|
+ $kpCodes = [];
|
|
|
+ foreach ($result['data'] as $item) {
|
|
|
+ if (!empty($item['question']['kp_code'])) {
|
|
|
+ $kpCodes[] = $item['question']['kp_code'];
|
|
|
+ }
|
|
|
+ if (!empty($item['kp_ids']) && is_array($item['kp_ids'])) {
|
|
|
+ $kpCodes = array_merge($kpCodes, $item['kp_ids']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $kpCodes = array_unique(array_filter($kpCodes));
|
|
|
+
|
|
|
+ Log::info('MistakeBookService::enrichMistakeData - Found kp_codes', [
|
|
|
+ 'kp_codes' => $kpCodes,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 从KnowledgeService获取知识点详细信息
|
|
|
+ $kpDetailMap = [];
|
|
|
+ foreach ($kpCodes as $kpCode) {
|
|
|
+ $detail = $this->getKnowledgePointDetail($kpCode);
|
|
|
+ if (!empty($detail)) {
|
|
|
+ $kpDetailMap[$kpCode] = $detail;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::info('MistakeBookService::enrichMistakeData - Retrieved kp details', [
|
|
|
+ 'kp_detail_map_keys' => array_keys($kpDetailMap),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 补充数据
|
|
|
+ foreach ($result['data'] as &$item) {
|
|
|
+ // 补充知识点名称和技能信息
|
|
|
+ if (isset($item['question']['kp_code'])) {
|
|
|
+ $kpCode = $item['question']['kp_code'];
|
|
|
+ if (isset($kpDetailMap[$kpCode])) {
|
|
|
+ $detail = $kpDetailMap[$kpCode];
|
|
|
+ $item['question']['kp_name'] = $detail['cn_name'] ?? $detail['en_name'] ?? $kpCode;
|
|
|
+ $item['question']['skills'] = array_column($detail['skills'] ?? [], 'skill_name');
|
|
|
+ Log::info('MistakeBookService::enrichMistakeData - Enriched item', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'kp_name' => $item['question']['kp_name'],
|
|
|
+ 'skills' => $item['question']['skills'],
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ // 如果没有找到详细信息,使用代码作为名称
|
|
|
+ $item['question']['kp_name'] = $kpCode;
|
|
|
+ $item['question']['skills'] = [];
|
|
|
+ Log::warning('MistakeBookService::enrichMistakeData - No detail found for kp', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保kp_ids数组存在且不为空
|
|
|
+ if (!isset($item['kp_ids']) || !is_array($item['kp_ids'])) {
|
|
|
+ $item['kp_ids'] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果kp_ids为空但有kp_code,则添加
|
|
|
+ if (empty($item['kp_ids']) && !empty($item['question']['kp_code'])) {
|
|
|
+ $item['kp_ids'] = [$item['question']['kp_code']];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保skill_ids数组存在
|
|
|
+ if (!isset($item['skill_ids']) || !is_array($item['skill_ids'])) {
|
|
|
+ $item['skill_ids'] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果skill_ids为空但有skills,则使用skills
|
|
|
+ if (empty($item['skill_ids']) && !empty($item['question']['skills'])) {
|
|
|
+ $item['skill_ids'] = $item['question']['skills'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 补充难度值(如果缺失)
|
|
|
+ if (!isset($item['question']['difficulty']) && isset($item['question']['kp_code'])) {
|
|
|
+ $kpCode = $item['question']['kp_code'];
|
|
|
+ if (isset($kpDetailMap[$kpCode])) {
|
|
|
+ $detail = $kpDetailMap[$kpCode];
|
|
|
+ // 使用importance作为难度(1-10)
|
|
|
+ $item['question']['difficulty'] = ($detail['importance'] ?? 5) / 10.0;
|
|
|
+ Log::info('MistakeBookService::enrichMistakeData - Set difficulty', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'difficulty' => $item['question']['difficulty'],
|
|
|
+ 'importance' => $detail['importance'] ?? 5,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::info('MistakeBookService::enrichMistakeData - Completed enrichment', [
|
|
|
+ 'processed_count' => count($result['data']),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 补充单条错题数据中的知识点名称、技能信息和难度
|
|
|
+ */
|
|
|
+ private function enrichSingleMistakeData(array $item): array
|
|
|
+ {
|
|
|
+ if (empty($item)) {
|
|
|
+ return $item;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从KnowledgeService获取知识点详细信息
|
|
|
+ $kpDetail = null;
|
|
|
+ if (isset($item['question']['kp_code'])) {
|
|
|
+ $kpCode = $item['question']['kp_code'];
|
|
|
+ $kpDetail = $this->getKnowledgePointDetail($kpCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 补充知识点名称和技能信息
|
|
|
+ if (isset($item['question']['kp_code']) && $kpDetail) {
|
|
|
+ $item['question']['kp_name'] = $kpDetail['cn_name'] ?? $kpDetail['en_name'] ?? $item['question']['kp_code'];
|
|
|
+ $item['question']['skills'] = array_column($kpDetail['skills'] ?? [], 'skill_name');
|
|
|
+ } else {
|
|
|
+ if (isset($item['question']['kp_code'])) {
|
|
|
+ $item['question']['kp_name'] = $item['question']['kp_code'];
|
|
|
+ }
|
|
|
+ $item['question']['skills'] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保kp_ids数组存在且不为空
|
|
|
+ if (!isset($item['kp_ids']) || !is_array($item['kp_ids'])) {
|
|
|
+ $item['kp_ids'] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果kp_ids为空但有kp_code,则添加
|
|
|
+ if (empty($item['kp_ids']) && !empty($item['question']['kp_code'])) {
|
|
|
+ $item['kp_ids'] = [$item['question']['kp_code']];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保skill_ids数组存在
|
|
|
+ if (!isset($item['skill_ids']) || !is_array($item['skill_ids'])) {
|
|
|
+ $item['skill_ids'] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果skill_ids为空但有skills,则使用skills
|
|
|
+ if (empty($item['skill_ids']) && !empty($item['question']['skills'])) {
|
|
|
+ $item['skill_ids'] = $item['question']['skills'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 补充难度值(如果缺失)
|
|
|
+ if (!isset($item['question']['difficulty']) && $kpDetail) {
|
|
|
+ // 使用importance作为难度(1-10)
|
|
|
+ $item['question']['difficulty'] = ($kpDetail['importance'] ?? 5) / 10.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $item;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从QuestionBank服务获取知识点详细信息
|
|
|
+ */
|
|
|
+ private function getKnowledgePointDetail(string $kpCode): ?array
|
|
|
+ {
|
|
|
+ Log::info('MistakeBookService::getKnowledgePointDetail - Fetching detail from QuestionBank', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取QuestionBank中的题目来获取知识点信息
|
|
|
+ $response = Http::timeout($this->timeout)
|
|
|
+ ->get($this->questionBankBase . '/api/questions', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'limit' => 1,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ Log::info('MistakeBookService::getKnowledgePointDetail - Response from QuestionBank', [
|
|
|
+ 'status' => $response->status(),
|
|
|
+ 'has_body' => !empty($response->body()),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if ($response->successful()) {
|
|
|
+ $data = $response->json();
|
|
|
+ $questions = $data['data'] ?? [];
|
|
|
+ if (!empty($questions)) {
|
|
|
+ $question = $questions[0];
|
|
|
+ $detail = [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'cn_name' => $question['kp_name'] ?? $kpCode,
|
|
|
+ 'skills' => [],
|
|
|
+ 'importance' => null,
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 提取技能信息
|
|
|
+ if (!empty($question['skills'])) {
|
|
|
+ foreach ($question['skills'] as $skillCode) {
|
|
|
+ $detail['skills'][] = [
|
|
|
+ 'skill_code' => $skillCode,
|
|
|
+ 'skill_name' => $skillCode,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从difficulty计算importance(0-1转为1-10)
|
|
|
+ if (isset($question['difficulty'])) {
|
|
|
+ $detail['importance'] = intval($question['difficulty'] * 10);
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::info('MistakeBookService::getKnowledgePointDetail - Success', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'kp_name' => $detail['cn_name'],
|
|
|
+ 'skills_count' => count($detail['skills']),
|
|
|
+ 'importance' => $detail['importance'],
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $detail;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::error('Get knowledge point detail from QuestionBank failed', [
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|