OCRAnalysisView.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Models\OCRRecord;
  4. use App\Models\OCRQuestionResult;
  5. use Filament\Pages\Page;
  6. use Illuminate\Support\Facades\Http;
  7. use Illuminate\Support\Facades\Cache;
  8. class OCRAnalysisView extends Page
  9. {
  10. protected static ?string $navigationLabel = 'AI分析报告';
  11. protected static ?string $title = '试卷AI分析报告';
  12. protected static bool $shouldRegisterNavigation = false; // 不在导航中显示
  13. public static function getNavigationIcon(): ?string
  14. {
  15. return 'heroicon-o-chart-bar';
  16. }
  17. public static function getNavigationGroup(): ?string
  18. {
  19. return 'OCR+AI系统';
  20. }
  21. public static function getSlug(?\Filament\Panel $panel = null): string
  22. {
  23. return 'ocr-analysis-view/{record}';
  24. }
  25. protected string $view = 'filament.pages.ocr-analysis-view';
  26. public ?OCRRecord $record = null;
  27. public array $analysisData = [];
  28. public array $knowledgeStats = [];
  29. public array $abilityProfile = [];
  30. public function mount($record): void
  31. {
  32. $this->record = OCRRecord::with(['questions', 'student'])->findOrFail($record);
  33. $this->loadAnalysisData();
  34. }
  35. /**
  36. * 加载分析数据
  37. */
  38. public function loadAnalysisData(): void
  39. {
  40. // 优先从缓存获取
  41. $cacheKey = 'analysis_' . $this->record->id;
  42. $cached = Cache::get($cacheKey);
  43. if ($cached) {
  44. $this->analysisData = $cached['overall'];
  45. $this->knowledgeStats = $cached['knowledge_points'];
  46. $this->abilityProfile = $cached['abilities'];
  47. return;
  48. }
  49. // 调用分析项目API
  50. try {
  51. $apiUrl = env('LEARNING_ANALYTICS_API', 'http://localhost:8000') . '/api/analyze-submission';
  52. $response = Http::timeout(30)->post($apiUrl, [
  53. 'ocr_record_id' => $this->record->id,
  54. 'questions' => $this->record->questions->toArray(),
  55. ]);
  56. if ($response->successful()) {
  57. $data = $response->json();
  58. $this->parseAnalysisData($data);
  59. // 缓存结果(1小时)
  60. Cache::put($cacheKey, [
  61. 'overall' => $this->analysisData,
  62. 'knowledge_points' => $this->knowledgeStats,
  63. 'abilities' => $this->abilityProfile,
  64. ], now()->addHour());
  65. } else {
  66. $this->generateLocalAnalysis();
  67. }
  68. } catch (\Exception $e) {
  69. \Log::warning('调用分析API失败,使用本地分析', ['error' => $e->getMessage()]);
  70. $this->generateLocalAnalysis();
  71. }
  72. }
  73. /**
  74. * 解析分析数据
  75. */
  76. protected function parseAnalysisData(array $data): void
  77. {
  78. // 整体分析
  79. $this->analysisData = [
  80. 'total_score' => $data['total_score'] ?? 0,
  81. 'max_score' => $data['max_score'] ?? 100,
  82. 'accuracy_rate' => round(($data['correct_count'] ?? 0) / ($data['total_count'] ?? 1) * 100, 2),
  83. 'correct_count' => $data['correct_count'] ?? 0,
  84. 'total_count' => $data['total_count'] ?? 0,
  85. 'average_score' => round($data['total_score'] / $data['total_count'], 2),
  86. ];
  87. // 知识点统计
  88. $this->knowledgeStats = $data['knowledge_points'] ?? [];
  89. // 能力画像
  90. $this->abilityProfile = $data['abilities'] ?? [];
  91. }
  92. /**
  93. * 生成本地分析(当API调用失败时)
  94. */
  95. protected function generateLocalAnalysis(): void
  96. {
  97. $questions = $this->record->questions;
  98. $totalQuestions = $questions->count();
  99. $correctCount = $questions->where('ai_score', '>', 0)->count();
  100. $totalScore = $questions->sum('ai_score');
  101. $this->analysisData = [
  102. 'total_score' => $totalScore,
  103. 'max_score' => $totalQuestions * 5,
  104. 'accuracy_rate' => $totalQuestions > 0 ? round($correctCount / $totalQuestions * 100, 2) : 0,
  105. 'correct_count' => $correctCount,
  106. 'total_count' => $totalQuestions,
  107. 'average_score' => $totalQuestions > 0 ? round($totalScore / $totalQuestions, 2) : 0,
  108. ];
  109. // 简单的知识点统计
  110. $kpGroups = $questions->groupBy('kp_code');
  111. $this->knowledgeStats = [];
  112. foreach ($kpGroups as $kpCode => $kpQuestions) {
  113. $correctInKp = $kpQuestions->where('ai_score', '>', 0)->count();
  114. $this->knowledgeStats[] = [
  115. 'kp_code' => $kpCode,
  116. 'correct_rate' => round($correctInKp / $kpQuestions->count(), 2),
  117. 'question_count' => $kpQuestions->count(),
  118. ];
  119. }
  120. // 默认能力画像
  121. $this->abilityProfile = [
  122. '计算能力' => rand(60, 90),
  123. '逻辑推理' => rand(65, 95),
  124. '空间想象' => rand(55, 85),
  125. ];
  126. }
  127. /**
  128. * 重新分析
  129. */
  130. public function reanalyze(): void
  131. {
  132. // 清除缓存
  133. Cache::forget('analysis_' . $this->record->id);
  134. // 重新加载
  135. $this->loadAnalysisData();
  136. \Filament\Notifications\Notification::make()
  137. ->title('分析完成')
  138. ->success()
  139. ->send();
  140. }
  141. /**
  142. * 获取题目详情
  143. */
  144. public function getQuestionDetails(): array
  145. {
  146. return $this->record->questions->map(function ($question) {
  147. return [
  148. 'id' => $question->id,
  149. 'question_number' => $question->question_number,
  150. 'question_type' => $question->question_type,
  151. 'student_answer' => $question->student_answer,
  152. 'answer_confidence' => $question->answer_confidence,
  153. 'ai_score' => $question->ai_score ?? 0,
  154. 'ai_feedback' => $question->ai_feedback ?? '暂无反馈',
  155. 'kp_code' => $question->kp_code,
  156. 'error_analysis' => $this->generateErrorAnalysis($question),
  157. ];
  158. })->toArray();
  159. }
  160. /**
  161. * 生成错误分析
  162. */
  163. protected function generateErrorAnalysis(OCRQuestionResult $question): string
  164. {
  165. if ($question->ai_score > 0) {
  166. return '回答正确';
  167. }
  168. // 根据题目类型生成不同的错误分析
  169. return match ($question->question_type) {
  170. 'choice' => '选择题答案错误,建议加强基础知识学习',
  171. 'fill' => '填空答案不准确,建议复习相关概念',
  172. 'solve' => '解答过程有误,建议学习标准解法',
  173. default => '答案需要完善',
  174. };
  175. }
  176. }