ExamAnswerAnalysisService.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\DB;
  4. use Illuminate\Support\Facades\Log;
  5. use Illuminate\Support\Collection;
  6. /**
  7. * 考试答题分析服务(步骤级分析)
  8. * 基于卷子分析思考文档的思路实现
  9. *
  10. * 核心流程:
  11. * 1. 接收卷子ID和每道题的对错、简答题的分步骤对错
  12. * 2. 将原子信息映射到知识点/技能
  13. * 3. 计算知识点掌握度向量
  14. * 4. 生成详细分析报告
  15. * 5. 提供智能出卷推荐依据
  16. */
  17. class ExamAnswerAnalysisService
  18. {
  19. public function __construct(
  20. private readonly MasteryCalculator $masteryCalculator,
  21. private readonly KnowledgeMasteryService $knowledgeMasteryService,
  22. private readonly LocalAIAnalysisService $aiAnalysisService,
  23. private readonly QuestionBankService $questionBankService
  24. ) {}
  25. /**
  26. * 分析考试答题数据
  27. *
  28. * @param array $examData 考试数据
  29. * [
  30. * 'exam_id' => 'exam_001',
  31. * 'student_id' => 'student_001',
  32. * 'questions' => [
  33. * [
  34. * 'question_id' => 'Q1',
  35. * 'score' => 5,
  36. * 'score_obtained' => 5,
  37. * 'steps' => [
  38. * ['step_index' => 1, 'is_correct' => true, 'kp_id' => 'K-SQRT-SIMPLE'],
  39. * ['step_index' => 2, 'is_correct' => true, 'kp_id' => 'K-NUM-ADD-SUB']
  40. * ]
  41. * ]
  42. * ]
  43. * ]
  44. *
  45. * @return array 分析结果
  46. */
  47. public function analyzeExamAnswers(array $examData): array
  48. {
  49. Log::info('开始分析考试答题', [
  50. 'exam_id' => $examData['exam_id'] ?? 'unknown',
  51. 'student_id' => $examData['student_id'] ?? 'unknown',
  52. 'question_count' => count($examData['questions'] ?? [])
  53. ]);
  54. $studentId = $examData['student_id'];
  55. $questions = $examData['questions'] ?? [];
  56. // 1. 保存答题记录到数据库
  57. $this->saveExamAnswerRecords($examData);
  58. // 2. 获取题目知识点映射
  59. $questionMappings = $this->getQuestionKnowledgeMappings($questions);
  60. // 3. 计算每个知识点的加权掌握度
  61. $knowledgeMasteryVector = $this->calculateKnowledgeMasteryVector($questions, $questionMappings);
  62. // 4. 更新学生掌握度
  63. $updatedMastery = $this->updateStudentMastery($studentId, $knowledgeMasteryVector);
  64. // 5. 生成题目维度分析
  65. $questionAnalysis = $this->analyzeQuestions($questions, $questionMappings);
  66. // 6. 生成知识点维度分析
  67. $knowledgePointAnalysis = $this->analyzeKnowledgePoints($knowledgeMasteryVector, $questionMappings);
  68. // 7. 生成整体掌握度总结
  69. $overallSummary = $this->generateOverallSummary($updatedMastery);
  70. // 8. 生成智能出卷推荐依据
  71. $smartQuizRecommendation = $this->generateSmartQuizRecommendation($updatedMastery);
  72. // 9. 保存分析结果
  73. $analysisResult = [
  74. 'exam_id' => $examData['exam_id'],
  75. 'student_id' => $studentId,
  76. 'timestamp' => now()->toISOString(),
  77. 'question_analysis' => $questionAnalysis,
  78. 'knowledge_point_analysis' => $knowledgePointAnalysis,
  79. 'overall_summary' => $overallSummary,
  80. 'smart_quiz_recommendation' => $smartQuizRecommendation,
  81. 'mastery_vector' => $updatedMastery,
  82. ];
  83. $this->saveAnalysisResult($studentId, $examData['exam_id'], $analysisResult);
  84. Log::info('考试答题分析完成', [
  85. 'student_id' => $studentId,
  86. 'exam_id' => $examData['exam_id'],
  87. 'analyzed_knowledge_points' => count($knowledgeMasteryVector)
  88. ]);
  89. return $analysisResult;
  90. }
  91. /**
  92. * 获取题目知识点映射
  93. */
  94. private function getQuestionKnowledgeMappings(array $questions): array
  95. {
  96. $mappings = [];
  97. $questionIds = array_column($questions, 'question_id');
  98. // 从题库获取题目知识点映射
  99. try {
  100. $response = $this->questionBankService->getQuestionsByIds($questionIds);
  101. $questionsData = $response['data'] ?? $response;
  102. foreach ($questionsData as $questionData) {
  103. $questionId = $questionData['id'] ?? $questionData['question_id'];
  104. if (!$questionId) continue;
  105. // 提取知识点信息
  106. $kpMapping = [];
  107. if (!empty($questionData['kp_code'])) {
  108. $kpMapping[] = [
  109. 'kp_id' => $questionData['kp_code'],
  110. 'kp_name' => $questionData['kp_name'] ?? $questionData['kp_code'],
  111. 'weight' => 1.0
  112. ];
  113. } else {
  114. $kpMapping[] = [
  115. 'kp_id' => 'K-GENERAL',
  116. 'kp_name' => '综合',
  117. 'weight' => 1.0
  118. ];
  119. }
  120. $mappings[$questionId] = [
  121. 'question_id' => $questionId,
  122. 'kp_mapping' => $kpMapping
  123. ];
  124. }
  125. } catch (\Exception $e) {
  126. Log::warning('获取题目知识点映射失败,使用默认映射', [
  127. 'error' => $e->getMessage(),
  128. 'question_ids' => $questionIds
  129. ]);
  130. // 使用默认映射:每道题至少映射到一个知识点
  131. foreach ($questions as $question) {
  132. $mappings[$question['question_id']] = [
  133. 'question_id' => $question['question_id'],
  134. 'kp_mapping' => [
  135. ['kp_id' => 'K-GENERAL', 'kp_name' => '综合', 'weight' => 1.0]
  136. ]
  137. ];
  138. }
  139. }
  140. return $mappings;
  141. }
  142. /**
  143. * 计算知识点掌握度向量
  144. * 基于文档中的简单实用更新公式
  145. */
  146. private function calculateKnowledgeMasteryVector(array $questions, array $questionMappings): array
  147. {
  148. $knowledgeScores = [];
  149. foreach ($questions as $question) {
  150. $questionId = $question['question_id'];
  151. $score = floatval($question['score_obtained'] ?? 0);
  152. $maxScore = floatval($question['score'] ?? $score);
  153. $steps = $question['steps'] ?? [];
  154. $mapping = $questionMappings[$questionId] ?? null;
  155. if (!$mapping || !isset($mapping['kp_mapping'])) {
  156. continue;
  157. }
  158. // 如果有步骤级分析,使用步骤分析
  159. if (!empty($steps)) {
  160. foreach ($steps as $step) {
  161. $kpId = $step['kp_id'] ?? 'K-GENERAL';
  162. $stepScore = floatval($step['score'] ?? ($maxScore / count($steps)));
  163. $stepWeight = floatval($step['weight'] ?? 1.0);
  164. if (!isset($knowledgeScores[$kpId])) {
  165. $knowledgeScores[$kpId] = [
  166. 'total_weight' => 0,
  167. 'correct_weight' => 0,
  168. 'step_details' => []
  169. ];
  170. }
  171. $knowledgeScores[$kpId]['total_weight'] += $stepScore * $stepWeight;
  172. if ($step['is_correct']) {
  173. $knowledgeScores[$kpId]['correct_weight'] += $stepScore * $stepWeight;
  174. }
  175. $knowledgeScores[$kpId]['step_details'][] = [
  176. 'question_id' => $questionId,
  177. 'step_index' => $step['step_index'],
  178. 'score' => $stepScore,
  179. 'is_correct' => $step['is_correct']
  180. ];
  181. }
  182. } else {
  183. // 没有步骤级分析,使用题目整体分析
  184. foreach ($mapping['kp_mapping'] as $kpMapping) {
  185. $kpId = $kpMapping['kp_id'];
  186. $weight = floatval($kpMapping['weight'] ?? 1.0);
  187. $kpMaxScore = $maxScore * $weight;
  188. if (!isset($knowledgeScores[$kpId])) {
  189. $knowledgeScores[$kpId] = [
  190. 'total_weight' => 0,
  191. 'correct_weight' => 0,
  192. 'step_details' => []
  193. ];
  194. }
  195. $knowledgeScores[$kpId]['total_weight'] += $kpMaxScore;
  196. if ($score > 0) {
  197. $knowledgeScores[$kpId]['correct_weight'] += $score * $weight;
  198. }
  199. }
  200. }
  201. }
  202. // 计算掌握度
  203. $masteryVector = [];
  204. foreach ($knowledgeScores as $kpId => $data) {
  205. $mastery = $data['total_weight'] > 0
  206. ? $data['correct_weight'] / $data['total_weight']
  207. : 0;
  208. // 置信度校正:考得越多,评价越稳定
  209. $confidence = 1 - exp(-$data['total_weight'] / 5);
  210. $masteryVector[$kpId] = [
  211. 'kp_id' => $kpId,
  212. 'mastery' => $mastery,
  213. 'confidence' => $confidence,
  214. 'total_weight' => $data['total_weight'],
  215. 'correct_weight' => $data['correct_weight'],
  216. 'step_details' => $data['step_details'],
  217. ];
  218. }
  219. return $masteryVector;
  220. }
  221. /**
  222. * 更新学生掌握度(与历史数据合并)
  223. */
  224. private function updateStudentMastery(string $studentId, array $knowledgeMasteryVector): array
  225. {
  226. $updatedMastery = [];
  227. foreach ($knowledgeMasteryVector as $kpId => $data) {
  228. // 获取历史掌握度
  229. $historyMastery = DB::connection('mysql')
  230. ->table('student_knowledge_mastery')
  231. ->where('student_id', $studentId)
  232. ->where('kp_code', $kpId)
  233. ->first();
  234. $historyMasteryLevel = $historyMastery->mastery_level ?? 0.5;
  235. $historyWeight = $historyMastery->total_attempts ?? 0;
  236. $currentWeight = $data['total_weight'];
  237. // 合并计算:历史权重 + 当前权重
  238. $newMastery = $historyWeight > 0
  239. ? ($historyWeight * $historyMasteryLevel + $currentWeight * $data['mastery'])
  240. / ($historyWeight + $currentWeight)
  241. : $data['mastery'];
  242. $newConfidence = $data['confidence'];
  243. // 保存到数据库
  244. DB::connection('mysql')
  245. ->table('student_knowledge_mastery')
  246. ->updateOrInsert(
  247. ['student_id' => $studentId, 'kp_code' => $kpId],
  248. [
  249. 'mastery_level' => $newMastery,
  250. 'confidence_level' => $newConfidence,
  251. 'total_attempts' => ($historyMastery->total_attempts ?? 0) + 1,
  252. 'correct_attempts' => ($historyMastery->correct_attempts ?? 0) + intval($data['correct_weight'] > 0),
  253. 'mastery_trend' => $this->determineMasteryTrend($historyMasteryLevel, $newMastery),
  254. 'last_mastery_update' => now(),
  255. 'updated_at' => now(),
  256. ]
  257. );
  258. $updatedMastery[$kpId] = [
  259. 'kp_id' => $kpId,
  260. 'current_mastery' => $newMastery,
  261. 'previous_mastery' => $historyMasteryLevel,
  262. 'confidence' => $newConfidence,
  263. 'change' => $newMastery - $historyMasteryLevel,
  264. 'weight' => $currentWeight
  265. ];
  266. }
  267. return $updatedMastery;
  268. }
  269. /**
  270. * 生成题目维度分析
  271. */
  272. private function analyzeQuestions(array $questions, array $questionMappings): array
  273. {
  274. $analysis = [];
  275. foreach ($questions as $question) {
  276. $questionId = $question['question_id'];
  277. $score = floatval($question['score_obtained'] ?? 0);
  278. $maxScore = floatval($question['score'] ?? $score);
  279. $steps = $question['steps'] ?? [];
  280. $mapping = $questionMappings[$questionId] ?? ['kp_mapping' => []];
  281. // 步骤分析
  282. $stepAnalysis = [];
  283. if (!empty($steps)) {
  284. foreach ($steps as $step) {
  285. $kpId = $step['kp_id'] ?? 'K-GENERAL';
  286. $stepAnalysis[] = [
  287. 'step_index' => $step['step_index'],
  288. 'is_correct' => $step['is_correct'],
  289. 'kp_id' => $kpId,
  290. 'description' => $step['description'] ?? ''
  291. ];
  292. }
  293. }
  294. // 知识点关联
  295. $knowledgePoints = array_map(function($kp) {
  296. return [
  297. 'kp_id' => $kp['kp_id'],
  298. 'kp_name' => $kp['kp_name'] ?? $kp['kp_id'],
  299. 'weight' => $kp['weight'] ?? 1.0
  300. ];
  301. }, $mapping['kp_mapping']);
  302. $analysis[] = [
  303. 'question_id' => $questionId,
  304. 'score_obtained' => $score,
  305. 'max_score' => $maxScore,
  306. 'accuracy_rate' => $maxScore > 0 ? $score / $maxScore : 0,
  307. 'step_analysis' => $stepAnalysis,
  308. 'knowledge_points' => $knowledgePoints,
  309. 'performance_summary' => $this->generateQuestionPerformanceSummary($question, $stepAnalysis)
  310. ];
  311. }
  312. return $analysis;
  313. }
  314. /**
  315. * 生成知识点维度分析
  316. */
  317. private function analyzeKnowledgePoints(array $knowledgeMasteryVector, array $questionMappings): array
  318. {
  319. $analysis = [];
  320. foreach ($knowledgeMasteryVector as $kpId => $data) {
  321. $analysis[] = [
  322. 'kp_id' => $kpId,
  323. 'mastery_level' => $data['mastery'],
  324. 'confidence_level' => $data['confidence'],
  325. 'performance_in_exam' => $this->evaluatePerformanceLevel($data['mastery']),
  326. 'evidence_count' => count($data['step_details']),
  327. 'step_evidence' => $data['step_details'],
  328. 'recommendation' => $this->generateKnowledgePointRecommendation($data)
  329. ];
  330. }
  331. return $analysis;
  332. }
  333. /**
  334. * 生成整体掌握度总结
  335. */
  336. private function generateOverallSummary(array $updatedMastery): array
  337. {
  338. $knowledgePoints = array_values($updatedMastery);
  339. if (empty($knowledgePoints)) {
  340. return [
  341. 'total_knowledge_points' => 0,
  342. 'average_mastery' => 0,
  343. 'mastery_distribution' => [
  344. 'mastered' => 0,
  345. 'good' => 0,
  346. 'weak' => 0
  347. ],
  348. 'top_strengths' => [],
  349. 'top_weaknesses' => []
  350. ];
  351. }
  352. // 计算平均掌握度
  353. $averageMastery = array_sum(array_column($knowledgePoints, 'current_mastery')) / count($knowledgePoints);
  354. // 掌握度分布
  355. $mastered = array_filter($knowledgePoints, fn($kp) => $kp['current_mastery'] >= 0.85);
  356. $good = array_filter($knowledgePoints, fn($kp) => $kp['current_mastery'] >= 0.70 && $kp['current_mastery'] < 0.85);
  357. $weak = array_filter($knowledgePoints, fn($kp) => $kp['current_mastery'] < 0.70);
  358. // 排序找出优势和薄弱点
  359. usort($knowledgePoints, fn($a, $b) => $b['current_mastery'] <=> $a['current_mastery']);
  360. $topStrengths = array_slice($knowledgePoints, 0, 3);
  361. $topWeaknesses = array_slice(array_reverse($knowledgePoints), 0, 3);
  362. return [
  363. 'total_knowledge_points' => count($knowledgePoints),
  364. 'average_mastery' => round($averageMastery, 4),
  365. 'mastery_distribution' => [
  366. 'mastered' => count($mastered),
  367. 'good' => count($good),
  368. 'weak' => count($weak)
  369. ],
  370. 'top_strengths' => $topStrengths,
  371. 'top_weaknesses' => $topWeaknesses,
  372. 'overall_performance' => $this->evaluateOverallPerformance($averageMastery)
  373. ];
  374. }
  375. /**
  376. * 生成智能出卷推荐依据
  377. * 基于文档中的推荐优先级算法
  378. */
  379. private function generateSmartQuizRecommendation(array $updatedMastery): array
  380. {
  381. $recommendations = [];
  382. foreach ($updatedMastery as $kpId => $data) {
  383. $mastery = $data['current_mastery'];
  384. $confidence = $data['confidence'];
  385. $weight = $data['weight'];
  386. // 推荐优先级 = (1 - 掌握度) * 重要性 * 覆盖需求
  387. // 重要性可以根据知识点在中考/阶段考试中的权重,这里简化为1.0
  388. $importance = 1.0;
  389. // 覆盖需求:最近没考过或考得少,值大
  390. $coverageNeed = max(1.0, 1.5 - ($weight / 10));
  391. $priority = (1 - $mastery) * $importance * $coverageNeed;
  392. $recommendations[] = [
  393. 'kp_id' => $kpId,
  394. 'current_mastery' => $mastery,
  395. 'priority' => $priority,
  396. 'recommended_questions' => $this->calculateRecommendedQuestions($mastery),
  397. 'focus_type' => $this->determineFocusType($mastery)
  398. ];
  399. }
  400. // 按优先级排序
  401. usort($recommendations, fn($a, $b) => $b['priority'] <=> $a['priority']);
  402. // 控制难度节奏:40%巩固型 + 40%修补型 + 20%挑战型
  403. $totalRecommendations = count($recommendations);
  404. $consolidation = array_slice($recommendations, 0, intval($totalRecommendations * 0.4));
  405. $remediation = array_slice($recommendations, intval($totalRecommendations * 0.4), intval($totalRecommendations * 0.4));
  406. $challenge = array_slice($recommendations, intval($totalRecommendations * 0.8));
  407. return [
  408. 'priority_list' => $recommendations,
  409. 'quiz_structure' => [
  410. 'consolidation_type' => $consolidation,
  411. 'remediation_type' => $remediation,
  412. 'challenge_type' => $challenge
  413. ],
  414. 'total_recommended_questions' => array_sum(array_column($recommendations, 'recommended_questions'))
  415. ];
  416. }
  417. /**
  418. * 保存考试答题记录
  419. */
  420. private function saveExamAnswerRecords(array $examData): void
  421. {
  422. $studentId = $examData['student_id'];
  423. $examId = $examData['exam_id'];
  424. foreach ($examData['questions'] as $question) {
  425. $questionId = $question['question_id'];
  426. $steps = $question['steps'] ?? [];
  427. // 保存步骤级记录
  428. if (!empty($steps)) {
  429. foreach ($steps as $step) {
  430. DB::connection('mysql')->table('student_answer_steps')->insert([
  431. 'student_id' => $studentId,
  432. 'exam_id' => $examId,
  433. 'question_id' => $questionId,
  434. 'step_index' => $step['step_index'],
  435. 'kp_id' => $step['kp_id'] ?? 'K-GENERAL',
  436. 'is_correct' => $step['is_correct'],
  437. 'step_score' => $step['score'] ?? 0,
  438. 'created_at' => now(),
  439. 'updated_at' => now(),
  440. ]);
  441. }
  442. } else {
  443. // 保存题目级记录
  444. DB::connection('mysql')->table('student_answer_questions')->insert([
  445. 'student_id' => $studentId,
  446. 'exam_id' => $examId,
  447. 'question_id' => $questionId,
  448. 'score_obtained' => $question['score_obtained'] ?? 0,
  449. 'max_score' => $question['score'] ?? 0,
  450. 'created_at' => now(),
  451. 'updated_at' => now(),
  452. ]);
  453. }
  454. }
  455. }
  456. /**
  457. * 保存分析结果
  458. */
  459. private function saveAnalysisResult(string $studentId, string $examId, array $result): void
  460. {
  461. DB::connection('mysql')->table('exam_analysis_results')->insert([
  462. 'student_id' => $studentId,
  463. 'exam_id' => $examId,
  464. 'analysis_data' => json_encode($result),
  465. 'created_at' => now(),
  466. 'updated_at' => now(),
  467. ]);
  468. }
  469. /**
  470. * 判断掌握度趋势
  471. */
  472. private function determineMasteryTrend(float $previous, float $current): string
  473. {
  474. $change = $current - $previous;
  475. if ($change > 0.1) {
  476. return 'improving';
  477. } elseif ($change < -0.1) {
  478. return 'declining';
  479. } else {
  480. return 'stable';
  481. }
  482. }
  483. /**
  484. * 评估表现水平
  485. */
  486. private function evaluatePerformanceLevel(float $mastery): string
  487. {
  488. if ($mastery >= 0.85) {
  489. return 'excellent';
  490. } elseif ($mastery >= 0.70) {
  491. return 'good';
  492. } elseif ($mastery >= 0.50) {
  493. return 'fair';
  494. } else {
  495. return 'poor';
  496. }
  497. }
  498. /**
  499. * 生成题目表现总结
  500. */
  501. private function generateQuestionPerformanceSummary(array $question, array $stepAnalysis): string
  502. {
  503. if (empty($stepAnalysis)) {
  504. return '整题作答';
  505. }
  506. $correctSteps = count(array_filter($stepAnalysis, fn($s) => $s['is_correct']));
  507. $totalSteps = count($stepAnalysis);
  508. if ($correctSteps === $totalSteps) {
  509. return '所有步骤正确';
  510. } elseif ($correctSteps > 0) {
  511. return "部分正确 ({$correctSteps}/{$totalSteps} 步骤正确)";
  512. } else {
  513. return '所有步骤错误';
  514. }
  515. }
  516. /**
  517. * 生成知识点建议
  518. */
  519. private function generateKnowledgePointRecommendation(array $data): string
  520. {
  521. $mastery = $data['mastery'];
  522. if ($mastery >= 0.85) {
  523. return '掌握良好,可安排综合练习';
  524. } elseif ($mastery >= 0.70) {
  525. return '基本掌握,建议加强练习';
  526. } elseif ($mastery >= 0.50) {
  527. return '需要重点练习,建议安排专项训练';
  528. } else {
  529. return '薄弱知识点,建议系统学习和大量练习';
  530. }
  531. }
  532. /**
  533. * 评估整体表现
  534. */
  535. private function evaluateOverallPerformance(float $averageMastery): string
  536. {
  537. if ($averageMastery >= 0.85) {
  538. return '优秀';
  539. } elseif ($averageMastery >= 0.70) {
  540. return '良好';
  541. } elseif ($averageMastery >= 0.50) {
  542. return '一般';
  543. } else {
  544. return '需加强';
  545. }
  546. }
  547. /**
  548. * 计算推荐题目数量
  549. */
  550. private function calculateRecommendedQuestions(float $mastery): int
  551. {
  552. if ($mastery >= 0.85) {
  553. return 1; // 巩固型:1题
  554. } elseif ($mastery >= 0.50) {
  555. return 2; // 修补型:2题
  556. } else {
  557. return 3; // 挑战型:3题
  558. }
  559. }
  560. /**
  561. * 确定重点类型
  562. */
  563. private function determineFocusType(float $mastery): string
  564. {
  565. if ($mastery >= 0.70 && $mastery < 0.85) {
  566. return 'consolidation'; // 巩固型
  567. } elseif ($mastery < 0.70) {
  568. return 'remediation'; // 修补型
  569. } else {
  570. return 'challenge'; // 挑战型
  571. }
  572. }
  573. }