| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- <?php
- namespace App\Services;
- use Illuminate\Support\Facades\Http;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Str;
- /**
- * 本地AI分析服务
- * 直接调用QuestionBankService的AI分析API,不依赖LearningAnalytics项目
- */
- class LocalAIAnalysisService
- {
- protected string $questionBankApiUrl;
- protected int $timeout = 60;
- public function __construct(
- private readonly QuestionBankService $questionBankService
- ) {
- $this->questionBankApiUrl = config('services.question_bank.url', env('QUESTION_BANK_API_URL', 'http://localhost:5015'));
- }
- /**
- * 分析学生答案
- *
- * @param array $answerData 包含 question_text, student_answer, score, max_score, kp_code 等
- * @return array 分析结果
- */
- public function analyzeAnswer(array $answerData): array
- {
- Log::info('LocalAIAnalysisService: 开始分析答案', [
- 'question_id' => $answerData['question_id'] ?? 'unknown',
- 'kp_code' => $answerData['kp_code'] ?? 'unknown',
- ]);
- // 直接使用规则分析(不再调用外部服务,避免超时)
- return [
- 'success' => true,
- 'data' => $this->ruleBasedAnalysis($answerData),
- 'model_used' => 'rule-based-analysis',
- 'fallback' => false,
- ];
- }
- /**
- * 批量分析学生答案
- *
- * @param array $answers 答案数组
- * @return array 分析结果数组
- */
- public function analyzeBatchAnswers(array $answers): array
- {
- $results = [];
- foreach ($answers as $answer) {
- $results[] = $this->analyzeAnswer($answer);
- }
- return $results;
- }
- /**
- * 基于规则的答案分析(备用方案)
- */
- private function ruleBasedAnalysis(array $answerData): array
- {
- $studentAnswer = $answerData['student_answer'] ?? '';
- $score = (float) ($answerData['score'] ?? 0);
- $maxScore = (float) ($answerData['max_score'] ?? 10);
- // 无答案情况
- if (empty(trim($studentAnswer))) {
- return [
- 'correct' => false,
- 'score' => 0,
- 'full_score' => $maxScore,
- 'partial_score_ratio' => 0.0,
- 'mistake_type' => '未作答',
- 'mistake_category' => '态度/习惯',
- 'reason' => '学生未作答',
- 'correct_solution' => '请参考标准答案',
- 'suggestions' => '建议鼓励学生尝试作答,不要留白',
- 'next_steps' => ['复习相关知识点', '尝试从已知条件入手'],
- 'analysis_confidence' => 1.0,
- 'analysis_tokens' => 0,
- ];
- }
- $scoreRatio = $maxScore > 0 ? $score / $maxScore : 0;
- // 满分
- if ($scoreRatio == 1.0) {
- return [
- 'correct' => true,
- 'score' => $score,
- 'full_score' => $maxScore,
- 'partial_score_ratio' => 1.0,
- 'mistake_type' => '无',
- 'mistake_category' => '无',
- 'reason' => '回答正确',
- 'correct_solution' => '回答正确',
- 'suggestions' => '继续保持',
- 'next_steps' => ['尝试更高难度的题目'],
- 'analysis_confidence' => 1.0,
- 'analysis_tokens' => 0,
- ];
- }
- // 零分或低分
- return [
- 'correct' => false,
- 'score' => $score,
- 'full_score' => $maxScore,
- 'partial_score_ratio' => $scoreRatio,
- 'mistake_type' => $scoreRatio < 0.3 ? '概念错误' : '计算错误/步骤缺失',
- 'mistake_category' => $scoreRatio < 0.3 ? '知识掌握' : '解题技巧',
- 'reason' => $scoreRatio < 0.3
- ? '学生对题目理解存在偏差或知识掌握不牢固,需要系统复习'
- : '解题方向有误,需要学习正确的解题方法',
- 'correct_solution' => '需要从基础概念开始,系统学习相关知识',
- 'suggestions' => '建议从基础概念开始,系统复习相关知识,多做基础练习',
- 'next_steps' => ['学习基础概念', '理解公式原理', '从简单题开始练习', '逐步提升难度'],
- 'analysis_confidence' => 0.7,
- 'analysis_tokens' => 100,
- ];
- }
- /**
- * 更新学生掌握度
- *
- * @param string $studentId 学生ID
- * @param string $kpCode 知识点编码
- * @param float $currentMastery 当前掌握度
- * @param bool $isCorrect 是否正确
- * @param float $difficulty 题目难度
- * @return array 更新后的掌握度
- */
- public function updateMastery(string $studentId, string $kpCode, float $currentMastery, bool $isCorrect, float $difficulty = 0.5): array
- {
- try {
- // 简化的掌握度更新算法
- // 基于BKT(贝叶斯知识追踪)模型的简化版本
- $learningRate = 0.1; // 学习速率
- $forgettingRate = 0.05; // 遗忘速率
- // 根据正确性和难度调整学习速率
- $adjustedLearningRate = $learningRate * (1 + (1 - $difficulty));
- $adjustedForgettingRate = $forgettingRate * (1 - (1 - $difficulty));
- $newMastery = $currentMastery;
- if ($isCorrect) {
- // 答对了,增加掌握度
- $newMastery = $currentMastery + ($adjustedLearningRate * (1 - $currentMastery));
- } else {
- // 答错了,遗忘一些
- $newMastery = $currentMastery * (1 - $adjustedForgettingRate);
- }
- // 确保在[0,1]范围内
- $newMastery = max(0, min(1, $newMastery));
- // 保存到数据库
- $this->saveMasteryToDatabase($studentId, $kpCode, $newMastery);
- return [
- 'kp_code' => $kpCode,
- 'old_mastery' => $currentMastery,
- 'new_mastery' => $newMastery,
- 'change' => $newMastery - $currentMastery,
- 'is_correct' => $isCorrect,
- ];
- } catch (\Exception $e) {
- Log::error('LocalAIAnalysisService: 更新掌握度失败', [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'error' => $e->getMessage(),
- ]);
- return [
- 'kp_code' => $kpCode,
- 'old_mastery' => $currentMastery,
- 'new_mastery' => $currentMastery,
- 'change' => 0,
- 'is_correct' => $isCorrect,
- 'error' => $e->getMessage(),
- ];
- }
- }
- /**
- * 保存掌握度到数据库
- */
- private function saveMasteryToDatabase(string $studentId, string $kpCode, float $mastery): void
- {
- try {
- // 尝试更新现有记录
- $updated = DB::table('student_knowledge_mastery')
- ->where('student_id', $studentId)
- ->where('kp_code', $kpCode)
- ->update([
- 'mastery_level' => $mastery,
- 'updated_at' => now(),
- ]);
- // 如果没有更新任何记录,插入新记录
- if ($updated === 0) {
- DB::table('student_knowledge_mastery')
- ->insert([
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'mastery_level' => $mastery,
- 'confidence_level' => 0.5, // 默认置信度
- 'created_at' => now(),
- 'updated_at' => now(),
- ]);
- }
- Log::debug('LocalAIAnalysisService: 掌握度已保存', [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'mastery' => $mastery,
- ]);
- } catch (\Exception $e) {
- // 表不存在时静默跳过
- Log::debug('LocalAIAnalysisService: 掌握度表不存在,跳过保存', [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'error' => $e->getMessage(),
- ]);
- }
- }
- /**
- * 获取学生掌握度
- *
- * @param string $studentId 学生ID
- * @param string|null $kpCode 知识点编码(可选)
- * @return array 掌握度数据
- */
- public function getStudentMastery(string $studentId, ?string $kpCode = null): array
- {
- try {
- $query = DB::table('student_knowledge_mastery')
- ->where('student_id', $studentId);
- if ($kpCode) {
- $query->where('kp_code', $kpCode);
- }
- $masteries = $query->get()->toArray();
- return [
- 'success' => true,
- 'data' => $masteries,
- ];
- } catch (\Exception $e) {
- // 表不存在时返回空数据
- Log::debug('LocalAIAnalysisService: 掌握度表不存在,返回空数据', [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'error' => $e->getMessage(),
- ]);
- return [
- 'success' => false,
- 'message' => '表不存在',
- 'data' => [],
- ];
- }
- }
- /**
- * 创建掌握度快照
- *
- * @param string $studentId 学生ID
- * @param string|null $paperId 关联试卷ID(可选)
- * @param string|null $answerRecordId 关联作答记录ID(可选)
- * @return array|null 快照数据
- */
- public function createMasterySnapshot(string $studentId, ?string $paperId = null, ?string $answerRecordId = null): ?array
- {
- try {
- // 获取最新掌握度数据
- $masteryData = $this->getStudentMastery($studentId);
- if (empty($masteryData['data'])) {
- Log::info('LocalAIAnalysisService: 没有掌握度数据,返回空快照', [
- 'student_id' => $studentId,
- ]);
- return [
- 'snapshot_id' => 'snap_' . Str::uuid()->toString(),
- 'student_id' => $studentId,
- 'paper_id' => $paperId,
- 'answer_record_id' => $answerRecordId,
- 'overall_mastery' => 0,
- 'weak_count' => 0,
- 'strong_count' => 0,
- 'mastery_changes' => [],
- ];
- }
- $snapshotId = 'snap_' . Str::uuid()->toString();
- $masteryItems = $masteryData['data'];
- $overallMastery = 0;
- $weakCount = 0;
- $strongCount = 0;
- foreach ($masteryItems as $item) {
- $level = (float) ($item->mastery_level ?? 0);
- $overallMastery += $level;
- if ($level < 0.6) {
- $weakCount++;
- } elseif ($level > 0.8) {
- $strongCount++;
- }
- }
- $overallMastery = count($masteryItems) > 0
- ? round($overallMastery / count($masteryItems), 4)
- : 0;
- return [
- 'snapshot_id' => $snapshotId,
- 'student_id' => $studentId,
- 'paper_id' => $paperId,
- 'answer_record_id' => $answerRecordId,
- 'overall_mastery' => $overallMastery,
- 'weak_count' => $weakCount,
- 'strong_count' => $strongCount,
- 'mastery_changes' => [],
- ];
- } catch (\Exception $e) {
- Log::error('LocalAIAnalysisService: 创建掌握度快照失败', [
- 'student_id' => $studentId,
- 'paper_id' => $paperId,
- 'error' => $e->getMessage(),
- ]);
- return null;
- }
- }
- }
|