|
@@ -10,6 +10,7 @@ use App\Services\ExamPdfExportService;
|
|
|
use App\Services\QuestionBankService;
|
|
use App\Services\QuestionBankService;
|
|
|
use App\Services\PaperPayloadService;
|
|
use App\Services\PaperPayloadService;
|
|
|
use App\Services\TaskManager;
|
|
use App\Services\TaskManager;
|
|
|
|
|
+use App\Models\MistakeRecord;
|
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\JsonResponse;
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Request;
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Http;
|
|
@@ -60,6 +61,10 @@ class IntelligentExamController extends Controller
|
|
|
'question_type_ratio' => 'array',
|
|
'question_type_ratio' => 'array',
|
|
|
'difficulty_ratio' => 'array',
|
|
'difficulty_ratio' => 'array',
|
|
|
'total_score' => 'nullable|numeric|min:1|max:1000',
|
|
'total_score' => 'nullable|numeric|min:1|max:1000',
|
|
|
|
|
+ 'mistake_ids' => 'nullable|array',
|
|
|
|
|
+ 'mistake_ids.*' => 'string',
|
|
|
|
|
+ 'mistake_question_ids' => 'nullable|array',
|
|
|
|
|
+ 'mistake_question_ids.*' => 'string',
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
if ($validator->fails()) {
|
|
if ($validator->fails()) {
|
|
@@ -82,27 +87,59 @@ class IntelligentExamController extends Controller
|
|
|
$difficultyRatio = $this->normalizeDifficultyRatio($data['difficulty_ratio'] ?? []);
|
|
$difficultyRatio = $this->normalizeDifficultyRatio($data['difficulty_ratio'] ?? []);
|
|
|
$paperName = $data['paper_name'] ?? ('智能试卷_' . now()->format('Ymd_His'));
|
|
$paperName = $data['paper_name'] ?? ('智能试卷_' . now()->format('Ymd_His'));
|
|
|
$difficultyCategory = $this->normalizeDifficultyCategory($data['difficulty_category'] ?? null);
|
|
$difficultyCategory = $this->normalizeDifficultyCategory($data['difficulty_category'] ?? null);
|
|
|
|
|
+ $mistakeIds = $data['mistake_ids'] ?? [];
|
|
|
|
|
+ $mistakeQuestionIds = $data['mistake_question_ids'] ?? [];
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // 第一步:生成智能试卷(同步)
|
|
|
|
|
- $result = $this->learningAnalyticsService->generateIntelligentExam([
|
|
|
|
|
- 'student_id' => $data['student_id'],
|
|
|
|
|
- 'grade' => $data['grade'] ?? null,
|
|
|
|
|
- 'total_questions' => $data['total_questions'],
|
|
|
|
|
- 'kp_codes' => $data['kp_codes'],
|
|
|
|
|
- 'skills' => $data['skills'] ?? [],
|
|
|
|
|
- 'question_type_ratio' => $questionTypeRatio,
|
|
|
|
|
- 'difficulty_ratio' => $difficultyRatio,
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+ $questions = [];
|
|
|
|
|
+ $result = null;
|
|
|
|
|
+
|
|
|
|
|
+ if (!empty($mistakeIds) || !empty($mistakeQuestionIds)) {
|
|
|
|
|
+ $questionIds = $this->resolveMistakeQuestionIds(
|
|
|
|
|
+ $data['student_id'],
|
|
|
|
|
+ $mistakeIds,
|
|
|
|
|
+ $mistakeQuestionIds
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($questionIds)) {
|
|
|
|
|
+ return response()->json([
|
|
|
|
|
+ 'success' => false,
|
|
|
|
|
+ 'message' => '未找到可用的错题题目,请检查错题ID或学生ID',
|
|
|
|
|
+ ], 400);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (empty($result['success'])) {
|
|
|
|
|
- return response()->json([
|
|
|
|
|
- 'success' => false,
|
|
|
|
|
- 'message' => $result['message'] ?? '智能出卷失败',
|
|
|
|
|
- ], 400);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ $bankQuestions = $this->questionBankService->getQuestionsByIds($questionIds)['data'] ?? [];
|
|
|
|
|
+ if (empty($bankQuestions)) {
|
|
|
|
|
+ return response()->json([
|
|
|
|
|
+ 'success' => false,
|
|
|
|
|
+ 'message' => '错题对应的题库题目不存在或不可用',
|
|
|
|
|
+ ], 400);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- $questions = $this->hydrateQuestions($result['questions'] ?? [], $data['kp_codes']);
|
|
|
|
|
|
|
+ $questions = $this->hydrateQuestions($bankQuestions, $data['kp_codes']);
|
|
|
|
|
+ $questions = $this->sortQuestionsByRequestedIds($questions, $questionIds);
|
|
|
|
|
+ $paperName = $data['paper_name'] ?? ('错题复习_' . $data['student_id'] . '_' . now()->format('Ymd_His'));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 第一步:生成智能试卷(同步)
|
|
|
|
|
+ $result = $this->learningAnalyticsService->generateIntelligentExam([
|
|
|
|
|
+ 'student_id' => $data['student_id'],
|
|
|
|
|
+ 'grade' => $data['grade'] ?? null,
|
|
|
|
|
+ 'total_questions' => $data['total_questions'],
|
|
|
|
|
+ 'kp_codes' => $data['kp_codes'],
|
|
|
|
|
+ 'skills' => $data['skills'] ?? [],
|
|
|
|
|
+ 'question_type_ratio' => $questionTypeRatio,
|
|
|
|
|
+ 'difficulty_ratio' => $difficultyRatio,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($result['success'])) {
|
|
|
|
|
+ return response()->json([
|
|
|
|
|
+ 'success' => false,
|
|
|
|
|
+ 'message' => $result['message'] ?? '智能出卷失败',
|
|
|
|
|
+ ], 400);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $questions = $this->hydrateQuestions($result['questions'] ?? [], $data['kp_codes']);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (empty($questions)) {
|
|
if (empty($questions)) {
|
|
|
return response()->json([
|
|
return response()->json([
|
|
@@ -112,6 +149,8 @@ class IntelligentExamController extends Controller
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$totalScore = array_sum(array_column($questions, 'score'));
|
|
$totalScore = array_sum(array_column($questions, 'score'));
|
|
|
|
|
+ $totalQuestions = min($data['total_questions'], count($questions));
|
|
|
|
|
+ $questions = array_slice($questions, 0, $totalQuestions);
|
|
|
|
|
|
|
|
// 第二步:保存试卷到数据库(同步)
|
|
// 第二步:保存试卷到数据库(同步)
|
|
|
$paperId = $this->questionBankService->saveExamToDatabase([
|
|
$paperId = $this->questionBankService->saveExamToDatabase([
|
|
@@ -165,7 +204,10 @@ class IntelligentExamController extends Controller
|
|
|
'exam_paper_pdf' => null,
|
|
'exam_paper_pdf' => null,
|
|
|
'grading_pdf' => null,
|
|
'grading_pdf' => null,
|
|
|
],
|
|
],
|
|
|
- 'stats' => $result['stats'] ?? null,
|
|
|
|
|
|
|
+ 'stats' => $result['stats'] ?? [
|
|
|
|
|
+ 'total_selected' => count($questions),
|
|
|
|
|
+ 'mistake_based' => !empty($mistakeIds) || !empty($mistakeQuestionIds),
|
|
|
|
|
+ ],
|
|
|
'created_at' => now()->toISOString(),
|
|
'created_at' => now()->toISOString(),
|
|
|
],
|
|
],
|
|
|
];
|
|
];
|
|
@@ -311,6 +353,19 @@ class IntelligentExamController extends Controller
|
|
|
$payload['skills'] = array_values(array_filter(array_map('trim', explode(',', $payload['skills']))));
|
|
$payload['skills'] = array_values(array_filter(array_map('trim', explode(',', $payload['skills']))));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ foreach (['mistake_ids', 'mistake_question_ids'] as $key) {
|
|
|
|
|
+ if (isset($payload[$key])) {
|
|
|
|
|
+ if (is_string($payload[$key])) {
|
|
|
|
|
+ $raw = trim($payload[$key]);
|
|
|
|
|
+ $payload[$key] = $raw === ''
|
|
|
|
|
+ ? []
|
|
|
|
|
+ : array_values(array_filter(array_map('trim', explode(',', $raw))));
|
|
|
|
|
+ } elseif (!is_array($payload[$key])) {
|
|
|
|
|
+ $payload[$key] = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return $payload;
|
|
return $payload;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -350,13 +405,13 @@ class IntelligentExamController extends Controller
|
|
|
private function normalizeQuestionTypeKey(string $key): ?string
|
|
private function normalizeQuestionTypeKey(string $key): ?string
|
|
|
{
|
|
{
|
|
|
$key = trim($key);
|
|
$key = trim($key);
|
|
|
- if (in_array($key, ['choice', '选择题', 'single_choice', 'multiple_choice'])) {
|
|
|
|
|
|
|
+ if (in_array($key, ['choice', '选择题', 'single_choice', 'multiple_choice', 'CHOICE', 'SINGLE_CHOICE', 'MULTIPLE_CHOICE'], true)) {
|
|
|
return '选择题';
|
|
return '选择题';
|
|
|
}
|
|
}
|
|
|
- if (in_array($key, ['fill', '填空题', 'blank'])) {
|
|
|
|
|
|
|
+ if (in_array($key, ['fill', '填空题', 'blank', 'FILL_IN_THE_BLANK', 'FILL'], true)) {
|
|
|
return '填空题';
|
|
return '填空题';
|
|
|
}
|
|
}
|
|
|
- if (in_array($key, ['answer', '解答题', '计算题'])) {
|
|
|
|
|
|
|
+ if (in_array($key, ['answer', '解答题', '计算题', 'CALCULATION', 'WORD_PROBLEM', 'PROOF'], true)) {
|
|
|
return '解答题';
|
|
return '解答题';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -455,4 +510,46 @@ class IntelligentExamController extends Controller
|
|
|
|
|
|
|
|
return 10;
|
|
return 10;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ private function resolveMistakeQuestionIds(string $studentId, array $mistakeIds, array $mistakeQuestionIds): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $questionIds = [];
|
|
|
|
|
+
|
|
|
|
|
+ if (!empty($mistakeQuestionIds)) {
|
|
|
|
|
+ $questionIds = array_merge($questionIds, $mistakeQuestionIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!empty($mistakeIds)) {
|
|
|
|
|
+ $mistakeQuestionIdsFromDb = MistakeRecord::query()
|
|
|
|
|
+ ->where('student_id', $studentId)
|
|
|
|
|
+ ->whereIn('id', $mistakeIds)
|
|
|
|
|
+ ->pluck('question_id')
|
|
|
|
|
+ ->filter()
|
|
|
|
|
+ ->values()
|
|
|
|
|
+ ->all();
|
|
|
|
|
+
|
|
|
|
|
+ $questionIds = array_merge($questionIds, $mistakeQuestionIdsFromDb);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $questionIds = array_values(array_unique(array_filter($questionIds)));
|
|
|
|
|
+ return $questionIds;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function sortQuestionsByRequestedIds(array $questions, array $requestedIds): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if (empty($requestedIds)) {
|
|
|
|
|
+ return $questions;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $order = array_flip($requestedIds);
|
|
|
|
|
+ usort($questions, function ($a, $b) use ($order) {
|
|
|
|
|
+ $aId = (string) ($a['id'] ?? '');
|
|
|
|
|
+ $bId = (string) ($b['id'] ?? '');
|
|
|
|
|
+ $aPos = $order[$aId] ?? PHP_INT_MAX;
|
|
|
|
|
+ $bPos = $order[$bId] ?? PHP_INT_MAX;
|
|
|
|
|
+ return $aPos <=> $bPos;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return $questions;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|