| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- <?php
- namespace App\Services;
- use App\Models\MistakeRecord;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Str;
- /**
- * 学生作答分析服务
- * 负责处理学生作答结果的保存、分析和掌握度更新
- */
- class StudentAnswerAnalysisService
- {
- public function __construct(
- private readonly LocalAIAnalysisService $aiAnalysisService,
- private readonly ExamAnswerAnalysisService $examAnswerAnalysisService
- ) {}
- /**
- * 保存作答记录
- */
- public function saveAnswerRecord(array $data): array
- {
- // 生成唯一记录ID
- $recordId = 'ans_' . Str::uuid()->toString();
- // 计算统计数据
- $answers = $data['answers'];
- $correctCount = 0;
- $wrongCount = 0;
- $missingCount = 0;
- $totalScore = 0;
- $obtainedScore = 0;
- foreach ($answers as $answer) {
- $score = (float) ($answer['score'] ?? 0);
- $maxScore = (float) ($answer['max_score'] ?? $score);
- // 缺题不计入对错统计
- if ($answer['is_missing'] ?? false) {
- $missingCount++;
- continue;
- }
- $totalScore += $maxScore;
- $obtainedScore += $score;
- if ($answer['is_correct']) {
- $correctCount++;
- } else {
- $wrongCount++;
- }
- }
- $accuracyRate = ($correctCount + $wrongCount) > 0
- ? round($correctCount / ($correctCount + $wrongCount), 4)
- : 0;
- // 只返回统计信息,不进行数据库操作(避免阻塞)
- // 错题记录将在后台任务中异步处理
- Log::info('作答记录已保存', [
- 'record_id' => $recordId,
- 'paper_id' => $data['paper_id'],
- 'student_id' => $data['student_id'],
- 'total_questions' => count($answers),
- 'correct_count' => $correctCount,
- 'wrong_count' => $wrongCount,
- 'accuracy_rate' => $accuracyRate,
- ]);
- return [
- 'record_id' => $recordId,
- 'paper_id' => $data['paper_id'],
- 'student_id' => $data['student_id'],
- 'total_score' => $totalScore,
- 'obtained_score' => $obtainedScore,
- 'accuracy_rate' => $accuracyRate,
- 'correct_count' => $correctCount,
- 'wrong_count' => $wrongCount,
- 'missing_count' => $missingCount,
- 'total_questions' => count($answers),
- ];
- }
- /**
- * 保存错题记录
- * 优化功能:
- * 1. 相同题目不重复出现在错题本中
- * 2. 记录错误次数和作答次数
- * 3. 增加错误类型分析
- * 4. 记录知识点掌握度变化
- */
- private function saveMistakeRecord(array $data, array $answer): void
- {
- try {
- // 检查是否已存在相同的错题记录
- $existing = MistakeRecord::where('student_id', $data['student_id'])
- ->where('question_id', $answer['question_id'])
- ->first();
- if ($existing) {
- // 更新现有记录(不重复创建)
- $oldReviewCount = $existing->review_count;
- // 递增错误次数和作答次数
- $existing->increment('review_count');
- // 记录每次错误的时间戳
- $errorHistory = json_decode($existing->remark ?? '[]', true) ?: [];
- $errorHistory[] = [
- 'timestamp' => now()->toISOString(),
- 'paper_id' => $data['paper_id'],
- 'student_answer' => $answer['student_answer'] ?? '',
- 'correct_answer' => $answer['correct_answer'] ?? '',
- 'error_type' => $this->guessErrorType($answer),
- 'is_missing' => $answer['is_missing'] ?? false,
- ];
- // 更新记录
- $existing->update([
- 'student_answer' => $answer['student_answer'] ?? '',
- 'correct_answer' => $answer['correct_answer'] ?? '',
- 'knowledge_point' => $answer['knowledge_point'] ?? $existing->knowledge_point,
- 'error_type' => $this->guessErrorType($answer),
- 'remark' => json_encode($errorHistory),
- 'updated_at' => now(),
- // 如果是缺题,更新为待复习状态
- 'review_status' => ($answer['is_missing'] ?? false)
- ? MistakeRecord::REVIEW_STATUS_PENDING
- : $existing->review_status,
- ]);
- Log::info('错题记录已更新', [
- 'student_id' => $data['student_id'],
- 'question_id' => $answer['question_id'],
- 'old_review_count' => $oldReviewCount,
- 'new_review_count' => $existing->review_count,
- 'total_errors' => count($errorHistory),
- ]);
- } else {
- // 创建新记录
- $errorHistory = [[
- 'timestamp' => now()->toISOString(),
- 'paper_id' => $data['paper_id'],
- 'student_answer' => $answer['student_answer'] ?? '',
- 'correct_answer' => $answer['correct_answer'] ?? '',
- 'error_type' => $this->guessErrorType($answer),
- 'is_missing' => $answer['is_missing'] ?? false,
- ]];
- $mistakeRecord = MistakeRecord::create([
- 'student_id' => $data['student_id'],
- 'question_id' => $answer['question_id'],
- 'paper_id' => $data['paper_id'] ?? null,
- 'source' => MistakeRecord::SOURCE_EXAM,
- 'question_text' => json_encode([
- 'question_number' => $answer['question_number'] ?? null,
- 'question_type' => $answer['question_type'] ?? null,
- 'is_missing' => $answer['is_missing'] ?? false,
- ]),
- 'student_answer' => $answer['student_answer'] ?? '',
- 'correct_answer' => $answer['correct_answer'] ?? '',
- 'knowledge_point' => $answer['knowledge_point'] ?? null,
- 'error_type' => $this->guessErrorType($answer),
- 'review_status' => ($answer['is_missing'] ?? false)
- ? MistakeRecord::REVIEW_STATUS_PENDING
- : MistakeRecord::REVIEW_STATUS_PENDING,
- 'review_count' => 1, // 第一次错误
- 'force_review' => false,
- 'is_favorite' => false,
- 'in_retry_list' => false,
- 'difficulty' => 0.5,
- 'mastery_level' => 0.0,
- 'remark' => json_encode($errorHistory),
- ]);
- Log::info('新错题记录已创建', [
- 'student_id' => $data['student_id'],
- 'question_id' => $answer['question_id'],
- 'mistake_record_id' => $mistakeRecord->id,
- 'error_type' => $mistakeRecord->error_type,
- ]);
- }
- } catch (\Exception $e) {
- Log::error('保存错题记录失败', [
- 'student_id' => $data['student_id'],
- 'question_id' => $answer['question_id'],
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString(),
- ]);
- }
- }
- /**
- * 猜测错误类型
- */
- private function guessErrorType(array $answer): string
- {
- // 根据题型和答案特征猜测错误类型
- $questionType = $answer['question_type'] ?? '';
- if ($questionType === 'choice') {
- return MistakeRecord::ERROR_TYPE_CARELESS;
- }
- if ($questionType === 'fill' || $questionType === 'answer') {
- // 检查是否部分正确
- if (isset($answer['step_scores']) && is_array($answer['step_scores'])) {
- $totalSteps = count($answer['step_scores']);
- $correctSteps = array_sum($answer['step_scores']);
- if ($correctSteps > 0 && $correctSteps < $totalSteps) {
- return MistakeRecord::ERROR_TYPE_PROCEDURE ?? MistakeRecord::ERROR_TYPE_CALCULATION;
- }
- }
- }
- return MistakeRecord::ERROR_TYPE_OTHER;
- }
- /**
- * 保存分析结果
- * 简化版:只保存错题记录,不依赖其他表
- */
- public function saveAnalysisResults(array $answerRecord, array $analysisData, array $questionAnalyses): void
- {
- try {
- // 只更新错题记录的掌握度(如果题目有错误)
- foreach ($questionAnalyses as $questionAnalysis) {
- // 更新掌握度(如果题目有知识点且答错了)
- if (!empty($questionAnalysis['kp_code']) && !($questionAnalysis['is_correct'] ?? true)) {
- $this->updateMasteryForQuestion(
- $answerRecord['student_id'],
- $questionAnalysis['kp_code'],
- $questionAnalysis['is_correct'],
- $questionAnalysis['difficulty'] ?? 0.5
- );
- }
- }
- Log::info('分析结果已保存', [
- 'student_id' => $answerRecord['student_id'],
- 'paper_id' => $answerRecord['paper_id'],
- 'question_count' => count($questionAnalyses),
- ]);
- } catch (\Exception $e) {
- Log::error('保存分析结果失败', [
- 'student_id' => $answerRecord['student_id'],
- 'paper_id' => $answerRecord['paper_id'],
- 'error' => $e->getMessage(),
- ]);
- }
- }
- /**
- * 为单个题目更新掌握度
- */
- private function updateMasteryForQuestion(string $studentId, string $kpCode, bool $isCorrect, float $difficulty): void
- {
- try {
- // 使用AI分析服务更新掌握度(如果表存在)
- $result = $this->aiAnalysisService->updateMastery(
- $studentId,
- $kpCode,
- 0.5, // 默认掌握度
- $isCorrect,
- $difficulty
- );
- Log::info('掌握度已更新', [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'is_correct' => $isCorrect,
- 'new_mastery' => $result['new_mastery'] ?? 'N/A',
- 'change' => $result['change'] ?? 'N/A',
- ]);
- } catch (\Exception $e) {
- Log::warning('更新掌握度失败(跳过)', [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'error' => $e->getMessage(),
- ]);
- }
- }
- /**
- * 创建掌握度快照
- */
- public function createMasterySnapshot(string $studentId, ?string $paperId = null, ?string $answerRecordId = null): ?array
- {
- // 使用AI分析服务创建快照
- return $this->aiAnalysisService->createMasterySnapshot($studentId, $paperId, $answerRecordId);
- }
- /**
- * 获取学生的学习历史
- */
- public function getStudentLearningHistory(string $studentId, int $limit = 10): array
- {
- try {
- // 获取错题记录历史(作为学习历史的主要数据)
- $mistakes = MistakeRecord::where('student_id', $studentId)
- ->orderBy('created_at', 'desc')
- ->limit($limit)
- ->get()
- ->toArray();
- // 使用AI分析服务获取掌握度数据(如果表存在)
- $masteryData = ['data' => []];
- try {
- $masteryData = $this->aiAnalysisService->getStudentMastery($studentId);
- } catch (\Exception $e) {
- Log::warning('获取掌握度数据失败', ['student_id' => $studentId, 'error' => $e->getMessage()]);
- }
- return [
- 'mistakes' => $mistakes,
- 'mastery_data' => $masteryData['data'] ?? [],
- 'summary' => [
- 'total_mistakes' => MistakeRecord::where('student_id', $studentId)->count(),
- 'total_mastery_items' => count($masteryData['data'] ?? []),
- ],
- ];
- } catch (\Exception $e) {
- Log::error('获取学习历史失败', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage(),
- ]);
- return [];
- }
- }
- /**
- * 使用增强版步骤级分析算法
- *
- * 这是基于《卷子分析思考.md》思路的增强版分析方法
- * 支持步骤级分析、知识点映射和智能出卷推荐
- *
- * @param array $examData 考试数据
- * @return array 分析结果
- */
- public function analyzeWithEnhancedSteps(array $examData): array
- {
- Log::info('StudentAnswerAnalysisService: 开始增强版步骤级分析', [
- 'exam_id' => $examData['exam_id'] ?? 'unknown',
- 'student_id' => $examData['student_id'] ?? 'unknown',
- 'has_steps' => !empty(array_filter($examData['questions'] ?? [], fn($q) => !empty($q['steps'])))
- ]);
- try {
- // 使用增强的分析算法
- $result = $this->examAnswerAnalysisService->analyzeExamAnswers($examData);
- Log::info('StudentAnswerAnalysisService: 增强版步骤级分析完成', [
- 'exam_id' => $examData['exam_id'],
- 'student_id' => $examData['student_id'],
- 'analyzed_knowledge_points' => count($result['knowledge_point_analysis'] ?? [])
- ]);
- return $result;
- } catch (\Exception $e) {
- Log::error('StudentAnswerAnalysisService: 增强版步骤级分析失败', [
- 'exam_id' => $examData['exam_id'] ?? 'unknown',
- 'student_id' => $examData['student_id'] ?? 'unknown',
- 'error' => $e->getMessage()
- ]);
- throw new \Exception('增强版步骤级分析失败:' . $e->getMessage());
- }
- }
- }
|