PaperPayloadService.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. namespace App\Services;
  3. use App\Models\Paper;
  4. use App\Models\PaperQuestion;
  5. class PaperPayloadService
  6. {
  7. public function buildExamContent(Paper $paper): array
  8. {
  9. $paper->loadMissing('questions');
  10. $questions = $paper->questions ?? collect();
  11. $codes = $this->generatePaperCodes($paper->paper_id);
  12. return [
  13. 'paper_info' => [
  14. 'paper_id' => $paper->paper_id,
  15. 'paper_name' => $paper->paper_name ?? '',
  16. 'student_id' => $paper->student_id ?? '',
  17. 'teacher_id' => $paper->teacher_id ?? '',
  18. 'total_questions' => $questions->count(),
  19. 'total_score' => $paper->total_score ?? 0,
  20. 'difficulty_category' => $paper->difficulty_category ?? '基础',
  21. 'created_at' => $paper->created_at?->toISOString(),
  22. 'updated_at' => $paper->updated_at?->toISOString(),
  23. 'exam_code' => $codes['exam_code'],
  24. 'grading_code' => $codes['grading_code'],
  25. 'paper_id_num' => $codes['paper_id_num'],
  26. ],
  27. 'questions' => $questions->map(function (PaperQuestion $q) {
  28. $options = [];
  29. if ($q->question_type === 'choice') {
  30. $questionText = $q->question_text ?? '';
  31. preg_match_all('/([A-D])\s*[\.\、\:]\s*([^A-D]+?)(?=[A-D]\s*[\.\、\:]|$)/u', $questionText, $matches, PREG_SET_ORDER);
  32. foreach ($matches as $match) {
  33. $options[] = [
  34. 'label' => $match[1],
  35. 'content' => trim($match[2]),
  36. ];
  37. }
  38. }
  39. return [
  40. 'question_number' => $q->question_number,
  41. 'question_id' => $q->question_id,
  42. 'question_bank_id' => $q->question_bank_id,
  43. 'question_type' => $q->question_type,
  44. 'knowledge_point' => $q->knowledge_point,
  45. 'difficulty' => $q->difficulty,
  46. 'score' => $q->score,
  47. 'estimated_time' => $q->estimated_time,
  48. 'stem' => $q->question_text ?? '',
  49. 'options' => $options,
  50. 'correct_answer' => $q->correct_answer ?? '',
  51. 'solution' => $q->solution ?? '',
  52. 'student_answer' => $q->student_answer,
  53. 'is_correct' => $q->is_correct,
  54. 'score_obtained' => $q->score_obtained,
  55. 'score_ratio' => $q->score_ratio,
  56. 'teacher_comment' => $q->teacher_comment,
  57. 'graded_at' => $q->graded_at?->toISOString(),
  58. 'graded_by' => $q->graded_by,
  59. 'metadata' => [
  60. 'has_solution' => !empty($q->solution),
  61. 'is_choice' => $q->question_type === 'choice',
  62. 'is_fill' => $q->question_type === 'fill',
  63. 'is_answer' => $q->question_type === 'answer',
  64. 'difficulty_label' => $this->getDifficultyLabel($q->difficulty),
  65. 'question_type_label' => $this->getQuestionTypeLabel($q->question_type),
  66. ],
  67. ];
  68. })->toArray(),
  69. 'statistics' => [
  70. 'type_distribution' => $this->getTypeDistribution($questions),
  71. 'difficulty_distribution' => $this->getDifficultyDistribution($questions),
  72. 'knowledge_point_distribution' => $this->getKnowledgePointDistribution($questions),
  73. 'average_difficulty' => $questions->avg('difficulty'),
  74. 'total_estimated_time' => $questions->sum('estimated_time'),
  75. ],
  76. 'knowledge_points' => $questions->pluck('knowledge_point')->unique()->filter()->values()->toArray(),
  77. 'skills' => $this->extractSkillsFromQuestions($questions),
  78. ];
  79. }
  80. public function buildPaperApiPayload(Paper $paper): array
  81. {
  82. $examContent = $this->buildExamContent($paper);
  83. $codes = $this->generatePaperCodes($paper->paper_id);
  84. return [
  85. 'success' => true,
  86. 'paper_id' => $paper->paper_id,
  87. 'status' => 'ready',
  88. 'exam_code' => $codes['exam_code'],
  89. 'grading_code' => $codes['grading_code'],
  90. 'paper_id_num' => $codes['paper_id_num'],
  91. 'exam_content' => $examContent,
  92. 'urls' => [
  93. 'grading_url' => route('filament.admin.auth.intelligent-exam.grading', ['paper_id' => $paper->paper_id]),
  94. 'student_exam_url' => route('filament.admin.auth.intelligent-exam.pdf', ['paper_id' => $paper->paper_id, 'answer' => 'false']),
  95. ],
  96. 'pdfs' => [
  97. 'exam_paper_pdf' => $paper->exam_pdf_url,
  98. 'grading_pdf' => $paper->grading_pdf_url,
  99. ],
  100. 'stats' => $examContent['statistics'] ?? null,
  101. 'created_at' => $paper->created_at?->toISOString(),
  102. 'updated_at' => $paper->updated_at?->toISOString(),
  103. ];
  104. }
  105. public function generatePaperCodes(string $paperId): array
  106. {
  107. if (preg_match('/paper_(\d{12})/', $paperId, $matches)) {
  108. $paperIdNum = $matches[1];
  109. } else {
  110. $paperIdNum = preg_replace('/[^0-9]/', '', $paperId);
  111. $paperIdNum = str_pad(substr($paperIdNum, 0, 12), 12, '0', STR_PAD_LEFT);
  112. }
  113. return [
  114. 'paper_id_num' => $paperIdNum,
  115. 'exam_code' => '1' . $paperIdNum,
  116. 'grading_code' => '2' . $paperIdNum,
  117. ];
  118. }
  119. private function getQuestionTypeLabel(?string $type): string
  120. {
  121. return match ($type) {
  122. 'choice' => '选择题',
  123. 'fill' => '填空题',
  124. 'answer' => '解答题',
  125. default => '未知题型',
  126. };
  127. }
  128. private function getDifficultyLabel(?float $difficulty): string
  129. {
  130. if ($difficulty === null) {
  131. return '未知';
  132. }
  133. if ($difficulty <= 0.4) {
  134. return '基础';
  135. }
  136. if ($difficulty <= 0.7) {
  137. return '中等';
  138. }
  139. return '拔高';
  140. }
  141. private function getTypeDistribution($questions): array
  142. {
  143. $distribution = [];
  144. foreach ($questions as $question) {
  145. $type = $question->question_type;
  146. $distribution[$type] = ($distribution[$type] ?? 0) + 1;
  147. }
  148. return $distribution;
  149. }
  150. private function getDifficultyDistribution($questions): array
  151. {
  152. $distribution = [];
  153. foreach ($questions as $question) {
  154. $label = $this->getDifficultyLabel($question->difficulty);
  155. $distribution[$label] = ($distribution[$label] ?? 0) + 1;
  156. }
  157. return $distribution;
  158. }
  159. private function getKnowledgePointDistribution($questions): array
  160. {
  161. $distribution = [];
  162. foreach ($questions as $question) {
  163. $kp = $question->knowledge_point;
  164. if ($kp) {
  165. $distribution[$kp] = ($distribution[$kp] ?? 0) + 1;
  166. }
  167. }
  168. return $distribution;
  169. }
  170. private function extractSkillsFromQuestions($questions): array
  171. {
  172. $skills = [];
  173. foreach ($questions as $question) {
  174. $solution = $question->solution ?? '';
  175. if ($solution) {
  176. $skillKeywords = ['代入法', '配方法', '因式分解', '换元法', '判别式', '求根公式', '韦达定理'];
  177. foreach ($skillKeywords as $keyword) {
  178. if (strpos($solution, $keyword) !== false) {
  179. $skills[] = $keyword;
  180. }
  181. }
  182. }
  183. $stem = $question->question_text ?? '';
  184. if ($stem) {
  185. preg_match_all('/\{([^}]+)\}/', $stem, $matches);
  186. foreach ($matches[1] as $match) {
  187. $skillList = array_map('trim', explode(',', $match));
  188. $skills = array_merge($skills, $skillList);
  189. }
  190. }
  191. }
  192. return array_unique(array_filter($skills));
  193. }
  194. }