|
@@ -3,31 +3,32 @@
|
|
|
namespace App\Http\Controllers\Api;
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
+use App\Models\MistakeRecord;
|
|
|
use App\Models\Paper;
|
|
use App\Models\Paper;
|
|
|
-use App\Models\PaperQuestion;
|
|
|
|
|
-use App\Services\LearningAnalyticsService;
|
|
|
|
|
|
|
+use App\Models\Student;
|
|
|
use App\Services\ExamPdfExportService;
|
|
use App\Services\ExamPdfExportService;
|
|
|
use App\Services\ExternalIdService;
|
|
use App\Services\ExternalIdService;
|
|
|
-use App\Services\QuestionBankService;
|
|
|
|
|
|
|
+use App\Services\LearningAnalyticsService;
|
|
|
use App\Services\PaperPayloadService;
|
|
use App\Services\PaperPayloadService;
|
|
|
|
|
+use App\Services\QuestionBankService;
|
|
|
use App\Services\TaskManager;
|
|
use App\Services\TaskManager;
|
|
|
-use App\Models\MistakeRecord;
|
|
|
|
|
-use App\Models\Student;
|
|
|
|
|
-use App\Models\Teacher;
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\JsonResponse;
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Request;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
-use Illuminate\Support\Facades\Http;
|
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
-use Illuminate\Support\Facades\URL;
|
|
|
|
|
|
|
|
|
|
class IntelligentExamController extends Controller
|
|
class IntelligentExamController extends Controller
|
|
|
{
|
|
{
|
|
|
private LearningAnalyticsService $learningAnalyticsService;
|
|
private LearningAnalyticsService $learningAnalyticsService;
|
|
|
|
|
+
|
|
|
private QuestionBankService $questionBankService;
|
|
private QuestionBankService $questionBankService;
|
|
|
|
|
+
|
|
|
private ExamPdfExportService $pdfExportService;
|
|
private ExamPdfExportService $pdfExportService;
|
|
|
|
|
+
|
|
|
private PaperPayloadService $paperPayloadService;
|
|
private PaperPayloadService $paperPayloadService;
|
|
|
|
|
+
|
|
|
private TaskManager $taskManager;
|
|
private TaskManager $taskManager;
|
|
|
|
|
+
|
|
|
private ExternalIdService $externalIdService;
|
|
private ExternalIdService $externalIdService;
|
|
|
|
|
|
|
|
public function __construct(
|
|
public function __construct(
|
|
@@ -135,13 +136,13 @@ class IntelligentExamController extends Controller
|
|
|
|
|
|
|
|
// 确保 kp_codes 是数组
|
|
// 确保 kp_codes 是数组
|
|
|
$data['kp_codes'] = $data['kp_codes'] ?? [];
|
|
$data['kp_codes'] = $data['kp_codes'] ?? [];
|
|
|
- if (!is_array($data['kp_codes'])) {
|
|
|
|
|
|
|
+ if (! is_array($data['kp_codes'])) {
|
|
|
$data['kp_codes'] = [];
|
|
$data['kp_codes'] = [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$questionTypeRatio = $this->normalizeQuestionTypeRatio($data['question_type_ratio'] ?? []);
|
|
$questionTypeRatio = $this->normalizeQuestionTypeRatio($data['question_type_ratio'] ?? []);
|
|
|
// 注意: difficulty_ratio 参数已废弃,使用 difficulty_category 控制难度分布
|
|
// 注意: difficulty_ratio 参数已废弃,使用 difficulty_category 控制难度分布
|
|
|
- $paperName = $data['paper_name'] ?? ('智能试卷_' . now()->format('Ymd_His'));
|
|
|
|
|
|
|
+ $paperName = $data['paper_name'] ?? ('智能试卷_'.now()->format('Ymd_His'));
|
|
|
$difficultyCategory = $data['difficulty_category'] ?? 1; // 直接使用数字,不转换
|
|
$difficultyCategory = $data['difficulty_category'] ?? 1; // 直接使用数字,不转换
|
|
|
$mistakeIds = $data['mistake_ids'] ?? [];
|
|
$mistakeIds = $data['mistake_ids'] ?? [];
|
|
|
$mistakeQuestionIds = $data['mistake_question_ids'] ?? [];
|
|
$mistakeQuestionIds = $data['mistake_question_ids'] ?? [];
|
|
@@ -152,7 +153,7 @@ class IntelligentExamController extends Controller
|
|
|
$questions = [];
|
|
$questions = [];
|
|
|
$result = null;
|
|
$result = null;
|
|
|
|
|
|
|
|
- if (!empty($mistakeIds) || !empty($mistakeQuestionIds)) {
|
|
|
|
|
|
|
+ if (! empty($mistakeIds) || ! empty($mistakeQuestionIds)) {
|
|
|
$questionIds = $this->resolveMistakeQuestionIds(
|
|
$questionIds = $this->resolveMistakeQuestionIds(
|
|
|
$data['student_id'],
|
|
$data['student_id'],
|
|
|
$mistakeIds,
|
|
$mistakeIds,
|
|
@@ -176,7 +177,7 @@ class IntelligentExamController extends Controller
|
|
|
|
|
|
|
|
$questions = $this->hydrateQuestions($bankQuestions, $data['kp_codes']);
|
|
$questions = $this->hydrateQuestions($bankQuestions, $data['kp_codes']);
|
|
|
$questions = $this->sortQuestionsByRequestedIds($questions, $questionIds);
|
|
$questions = $this->sortQuestionsByRequestedIds($questions, $questionIds);
|
|
|
- $paperName = $data['paper_name'] ?? ('错题复习_' . $data['student_id'] . '_' . now()->format('Ymd_His'));
|
|
|
|
|
|
|
+ $paperName = $data['paper_name'] ?? ('错题复习_'.$data['student_id'].'_'.now()->format('Ymd_His'));
|
|
|
} else {
|
|
} else {
|
|
|
// 第一步:生成智能试卷(同步)
|
|
// 第一步:生成智能试卷(同步)
|
|
|
$params = [
|
|
$params = [
|
|
@@ -206,7 +207,7 @@ class IntelligentExamController extends Controller
|
|
|
$errorMsg = $result['message'] ?? '智能出卷失败';
|
|
$errorMsg = $result['message'] ?? '智能出卷失败';
|
|
|
Log::error('智能出卷失败', [
|
|
Log::error('智能出卷失败', [
|
|
|
'student_id' => $data['student_id'],
|
|
'student_id' => $data['student_id'],
|
|
|
- 'error' => $result
|
|
|
|
|
|
|
+ 'error' => $result,
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
// 提供更详细的错误信息
|
|
// 提供更详细的错误信息
|
|
@@ -238,7 +239,7 @@ class IntelligentExamController extends Controller
|
|
|
// 错题本:使用所有错题,不限制数量
|
|
// 错题本:使用所有错题,不限制数量
|
|
|
Log::info('错题本类型,使用所有错题', [
|
|
Log::info('错题本类型,使用所有错题', [
|
|
|
'assemble_type' => $assembleType,
|
|
'assemble_type' => $assembleType,
|
|
|
- 'question_count' => count($questions)
|
|
|
|
|
|
|
+ 'question_count' => count($questions),
|
|
|
]);
|
|
]);
|
|
|
} else {
|
|
} else {
|
|
|
// 其他类型:限制题目数量
|
|
// 其他类型:限制题目数量
|
|
@@ -246,8 +247,9 @@ class IntelligentExamController extends Controller
|
|
|
$questions = array_slice($questions, 0, $totalQuestions);
|
|
$questions = array_slice($questions, 0, $totalQuestions);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 调整题目分值,确保符合中国中学卷子标准(总分100分)
|
|
|
|
|
- $questions = $this->adjustQuestionScores($questions, 100.0);
|
|
|
|
|
|
|
+ // 调整题目分值,确保符合目标总分
|
|
|
|
|
+ $targetTotalScore = $data['total_score'] ?? 100.0;
|
|
|
|
|
+ $questions = $this->adjustQuestionScores($questions, $targetTotalScore);
|
|
|
|
|
|
|
|
// 计算总分
|
|
// 计算总分
|
|
|
$totalScore = array_sum(array_column($questions, 'score'));
|
|
$totalScore = array_sum(array_column($questions, 'score'));
|
|
@@ -259,11 +261,11 @@ class IntelligentExamController extends Controller
|
|
|
'teacher_id' => $data['teacher_id'] ?? null,
|
|
'teacher_id' => $data['teacher_id'] ?? null,
|
|
|
'assembleType' => $assembleType,
|
|
'assembleType' => $assembleType,
|
|
|
'difficulty_category' => $difficultyCategory,
|
|
'difficulty_category' => $difficultyCategory,
|
|
|
- 'total_score' => $data['total_score'] ?? 100.0, // 默认100分
|
|
|
|
|
|
|
+ 'total_score' => $totalScore, // 使用计算后的实际总分
|
|
|
'questions' => $questions,
|
|
'questions' => $questions,
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
- if (!$paperId) {
|
|
|
|
|
|
|
+ if (! $paperId) {
|
|
|
return response()->json([
|
|
return response()->json([
|
|
|
'success' => false,
|
|
'success' => false,
|
|
|
'message' => '试卷保存失败',
|
|
'message' => '试卷保存失败',
|
|
@@ -301,6 +303,7 @@ class IntelligentExamController extends Controller
|
|
|
'urls' => [
|
|
'urls' => [
|
|
|
'grading_url' => route('filament.admin.auth.intelligent-exam.grading', ['paper_id' => $paperId]),
|
|
'grading_url' => route('filament.admin.auth.intelligent-exam.grading', ['paper_id' => $paperId]),
|
|
|
'student_exam_url' => route('filament.admin.auth.intelligent-exam.pdf', ['paper_id' => $paperId, 'answer' => 'false']),
|
|
'student_exam_url' => route('filament.admin.auth.intelligent-exam.pdf', ['paper_id' => $paperId, 'answer' => 'false']),
|
|
|
|
|
+ 'knowledge_explanation_url' => route('filament.admin.auth.intelligent-exam.knowledge-explanation', ['paper_id' => $paperId]),
|
|
|
],
|
|
],
|
|
|
'pdfs' => [
|
|
'pdfs' => [
|
|
|
'exam_paper_pdf' => null,
|
|
'exam_paper_pdf' => null,
|
|
@@ -308,7 +311,7 @@ class IntelligentExamController extends Controller
|
|
|
],
|
|
],
|
|
|
'stats' => $result['stats'] ?? [
|
|
'stats' => $result['stats'] ?? [
|
|
|
'total_selected' => count($questions),
|
|
'total_selected' => count($questions),
|
|
|
- 'mistake_based' => !empty($mistakeIds) || !empty($mistakeQuestionIds),
|
|
|
|
|
|
|
+ 'mistake_based' => ! empty($mistakeIds) || ! empty($mistakeQuestionIds),
|
|
|
],
|
|
],
|
|
|
'created_at' => now()->toISOString(),
|
|
'created_at' => now()->toISOString(),
|
|
|
],
|
|
],
|
|
@@ -349,7 +352,7 @@ class IntelligentExamController extends Controller
|
|
|
try {
|
|
try {
|
|
|
$task = $this->taskManager->getTaskStatus($taskId);
|
|
$task = $this->taskManager->getTaskStatus($taskId);
|
|
|
|
|
|
|
|
- if (!$task) {
|
|
|
|
|
|
|
+ if (! $task) {
|
|
|
return response()->json([
|
|
return response()->json([
|
|
|
'success' => false,
|
|
'success' => false,
|
|
|
'message' => '任务不存在',
|
|
'message' => '任务不存在',
|
|
@@ -385,14 +388,14 @@ class IntelligentExamController extends Controller
|
|
|
dispatch(new \App\Jobs\GenerateExamPdfJob($taskId, $paperId));
|
|
dispatch(new \App\Jobs\GenerateExamPdfJob($taskId, $paperId));
|
|
|
Log::info('PDF生成任务已加入队列', [
|
|
Log::info('PDF生成任务已加入队列', [
|
|
|
'task_id' => $taskId,
|
|
'task_id' => $taskId,
|
|
|
- 'paper_id' => $paperId
|
|
|
|
|
|
|
+ 'paper_id' => $paperId,
|
|
|
]);
|
|
]);
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
|
Log::error('PDF生成任务队列失败,不回退到同步处理', [
|
|
Log::error('PDF生成任务队列失败,不回退到同步处理', [
|
|
|
'task_id' => $taskId,
|
|
'task_id' => $taskId,
|
|
|
'paper_id' => $paperId,
|
|
'paper_id' => $paperId,
|
|
|
'error' => $e->getMessage(),
|
|
'error' => $e->getMessage(),
|
|
|
- 'note' => '依赖队列重试机制,不进行同步处理以避免并发冲突'
|
|
|
|
|
|
|
+ 'note' => '依赖队列重试机制,不进行同步处理以避免并发冲突',
|
|
|
]);
|
|
]);
|
|
|
// 【优化】不回退到同步处理,避免与队列任务并发冲突
|
|
// 【优化】不回退到同步处理,避免与队列任务并发冲突
|
|
|
// 队列系统有重试机制,会自动处理失败情况
|
|
// 队列系统有重试机制,会自动处理失败情况
|
|
@@ -462,7 +465,7 @@ class IntelligentExamController extends Controller
|
|
|
private function normalizePayload(array $payload): array
|
|
private function normalizePayload(array $payload): array
|
|
|
{
|
|
{
|
|
|
// 处理 question_count 参数:转换为 total_questions
|
|
// 处理 question_count 参数:转换为 total_questions
|
|
|
- if (isset($payload['question_count']) && !isset($payload['total_questions'])) {
|
|
|
|
|
|
|
+ if (isset($payload['question_count']) && ! isset($payload['total_questions'])) {
|
|
|
$payload['total_questions'] = $payload['question_count'];
|
|
$payload['total_questions'] = $payload['question_count'];
|
|
|
unset($payload['question_count']);
|
|
unset($payload['question_count']);
|
|
|
}
|
|
}
|
|
@@ -487,7 +490,7 @@ class IntelligentExamController extends Controller
|
|
|
} else {
|
|
} else {
|
|
|
$payload['kp_codes'] = array_values(array_filter(array_map('trim', explode(',', $kpCodes))));
|
|
$payload['kp_codes'] = array_values(array_filter(array_map('trim', explode(',', $kpCodes))));
|
|
|
}
|
|
}
|
|
|
- } elseif (!is_array($payload['kp_codes'])) {
|
|
|
|
|
|
|
+ } elseif (! is_array($payload['kp_codes'])) {
|
|
|
$payload['kp_codes'] = [];
|
|
$payload['kp_codes'] = [];
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
@@ -505,7 +508,7 @@ class IntelligentExamController extends Controller
|
|
|
$payload[$key] = $raw === ''
|
|
$payload[$key] = $raw === ''
|
|
|
? []
|
|
? []
|
|
|
: array_values(array_filter(array_map('trim', explode(',', $raw))));
|
|
: array_values(array_filter(array_map('trim', explode(',', $raw))));
|
|
|
- } elseif (!is_array($payload[$key])) {
|
|
|
|
|
|
|
+ } elseif (! is_array($payload[$key])) {
|
|
|
$payload[$key] = [];
|
|
$payload[$key] = [];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -519,7 +522,7 @@ class IntelligentExamController extends Controller
|
|
|
$payload[$key] = $raw === ''
|
|
$payload[$key] = $raw === ''
|
|
|
? []
|
|
? []
|
|
|
: array_values(array_filter(array_map('trim', explode(',', $raw))));
|
|
: array_values(array_filter(array_map('trim', explode(',', $raw))));
|
|
|
- } elseif (!is_array($payload[$key])) {
|
|
|
|
|
|
|
+ } elseif (! is_array($payload[$key])) {
|
|
|
$payload[$key] = [];
|
|
$payload[$key] = [];
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
@@ -534,7 +537,7 @@ class IntelligentExamController extends Controller
|
|
|
if ($payload['series_id'] <= 0) {
|
|
if ($payload['series_id'] <= 0) {
|
|
|
unset($payload['series_id']);
|
|
unset($payload['series_id']);
|
|
|
}
|
|
}
|
|
|
- } elseif (!is_int($payload['series_id']) || $payload['series_id'] <= 0) {
|
|
|
|
|
|
|
+ } elseif (! is_int($payload['series_id']) || $payload['series_id'] <= 0) {
|
|
|
unset($payload['series_id']);
|
|
unset($payload['series_id']);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -545,13 +548,13 @@ class IntelligentExamController extends Controller
|
|
|
$payload['semester_code'] = (int) trim($payload['semester_code']);
|
|
$payload['semester_code'] = (int) trim($payload['semester_code']);
|
|
|
}
|
|
}
|
|
|
// 只保留1或2,其他值都移除
|
|
// 只保留1或2,其他值都移除
|
|
|
- if (!in_array($payload['semester_code'], [1, 2], true)) {
|
|
|
|
|
|
|
+ if (! in_array($payload['semester_code'], [1, 2], true)) {
|
|
|
unset($payload['semester_code']);
|
|
unset($payload['semester_code']);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 新增:处理组卷类型,默认值为 general
|
|
// 新增:处理组卷类型,默认值为 general
|
|
|
- if (!isset($payload['exam_type'])) {
|
|
|
|
|
|
|
+ if (! isset($payload['exam_type'])) {
|
|
|
$payload['exam_type'] = 'general';
|
|
$payload['exam_type'] = 'general';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -560,7 +563,7 @@ class IntelligentExamController extends Controller
|
|
|
if (is_string($payload['practice_options'])) {
|
|
if (is_string($payload['practice_options'])) {
|
|
|
$decoded = json_decode($payload['practice_options'], true);
|
|
$decoded = json_decode($payload['practice_options'], true);
|
|
|
$payload['practice_options'] = is_array($decoded) ? $decoded : [];
|
|
$payload['practice_options'] = is_array($decoded) ? $decoded : [];
|
|
|
- } elseif (!is_array($payload['practice_options'])) {
|
|
|
|
|
|
|
+ } elseif (! is_array($payload['practice_options'])) {
|
|
|
$payload['practice_options'] = [];
|
|
$payload['practice_options'] = [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -586,7 +589,7 @@ class IntelligentExamController extends Controller
|
|
|
if (is_string($payload['mistake_options'])) {
|
|
if (is_string($payload['mistake_options'])) {
|
|
|
$decoded = json_decode($payload['mistake_options'], true);
|
|
$decoded = json_decode($payload['mistake_options'], true);
|
|
|
$payload['mistake_options'] = is_array($decoded) ? $decoded : [];
|
|
$payload['mistake_options'] = is_array($decoded) ? $decoded : [];
|
|
|
- } elseif (!is_array($payload['mistake_options'])) {
|
|
|
|
|
|
|
+ } elseif (! is_array($payload['mistake_options'])) {
|
|
|
$payload['mistake_options'] = [];
|
|
$payload['mistake_options'] = [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -614,7 +617,7 @@ class IntelligentExamController extends Controller
|
|
|
if (is_string($payload['knowledge_points_options'])) {
|
|
if (is_string($payload['knowledge_points_options'])) {
|
|
|
$decoded = json_decode($payload['knowledge_points_options'], true);
|
|
$decoded = json_decode($payload['knowledge_points_options'], true);
|
|
|
$payload['knowledge_points_options'] = is_array($decoded) ? $decoded : [];
|
|
$payload['knowledge_points_options'] = is_array($decoded) ? $decoded : [];
|
|
|
- } elseif (!is_array($payload['knowledge_points_options'])) {
|
|
|
|
|
|
|
+ } elseif (! is_array($payload['knowledge_points_options'])) {
|
|
|
$payload['knowledge_points_options'] = [];
|
|
$payload['knowledge_points_options'] = [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -661,9 +664,10 @@ class IntelligentExamController extends Controller
|
|
|
if ($teacherId > 0 && (int) $student->teacher_id !== $teacherId) {
|
|
if ($teacherId > 0 && (int) $student->teacher_id !== $teacherId) {
|
|
|
$updates['teacher_id'] = $teacherId;
|
|
$updates['teacher_id'] = $teacherId;
|
|
|
}
|
|
}
|
|
|
- if (!empty($updates)) {
|
|
|
|
|
|
|
+ if (! empty($updates)) {
|
|
|
$student->update($updates);
|
|
$student->update($updates);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -685,7 +689,7 @@ class IntelligentExamController extends Controller
|
|
|
|
|
|
|
|
$normalized = [];
|
|
$normalized = [];
|
|
|
foreach ($input as $key => $value) {
|
|
foreach ($input as $key => $value) {
|
|
|
- if (!is_numeric($value)) {
|
|
|
|
|
|
|
+ if (! is_numeric($value)) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
$type = $this->normalizeQuestionTypeKey($key);
|
|
$type = $this->normalizeQuestionTypeKey($key);
|
|
@@ -733,7 +737,7 @@ class IntelligentExamController extends Controller
|
|
|
|
|
|
|
|
$normalized = [];
|
|
$normalized = [];
|
|
|
foreach ($input as $key => $value) {
|
|
foreach ($input as $key => $value) {
|
|
|
- if (!is_numeric($value)) {
|
|
|
|
|
|
|
+ if (! is_numeric($value)) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
$label = trim($key);
|
|
$label = trim($key);
|
|
@@ -773,12 +777,12 @@ class IntelligentExamController extends Controller
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return array_values(array_filter($normalized, fn ($q) => !empty($q['id'])));
|
|
|
|
|
|
|
+ return array_values(array_filter($normalized, fn ($q) => ! empty($q['id'])));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private function guessType(array $question): string
|
|
private function guessType(array $question): string
|
|
|
{
|
|
{
|
|
|
- if (!empty($question['options']) && is_array($question['options'])) {
|
|
|
|
|
|
|
+ if (! empty($question['options']) && is_array($question['options'])) {
|
|
|
return '选择题';
|
|
return '选择题';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -821,174 +825,231 @@ class IntelligentExamController extends Controller
|
|
|
return $questions;
|
|
return $questions;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 统计各类型题目数量
|
|
|
|
|
- $typeCounts = ['choice' => 0, 'fill' => 0, 'answer' => 0];
|
|
|
|
|
|
|
+ // 第一步:按题型排序
|
|
|
|
|
+ $sortedQuestions = [];
|
|
|
|
|
+ $choiceQuestions = [];
|
|
|
|
|
+ $fillQuestions = [];
|
|
|
|
|
+ $answerQuestions = [];
|
|
|
|
|
|
|
|
foreach ($questions as $question) {
|
|
foreach ($questions as $question) {
|
|
|
- $type = $question['question_type'] ?? 'answer';
|
|
|
|
|
- if (in_array($type, ['CHOICE', 'SINGLE_CHOICE', 'MULTIPLE_CHOICE'], true)) {
|
|
|
|
|
- $type = 'choice';
|
|
|
|
|
- } elseif (in_array($type, ['FILL_IN_THE_BLANK', 'FILL'], true)) {
|
|
|
|
|
- $type = 'fill';
|
|
|
|
|
- } elseif (in_array($type, ['CALCULATION', 'WORD_PROBLEM', 'PROOF', 'ANSWER'], true)) {
|
|
|
|
|
- $type = 'answer';
|
|
|
|
|
- }
|
|
|
|
|
- if (isset($typeCounts[$type])) {
|
|
|
|
|
- $typeCounts[$type]++;
|
|
|
|
|
|
|
+ $type = $this->normalizeQuestionType($question['question_type'] ?? 'answer');
|
|
|
|
|
+ if ($type === 'choice') {
|
|
|
|
|
+ $choiceQuestions[] = $question;
|
|
|
|
|
+ } elseif ($type === 'fill') {
|
|
|
|
|
+ $fillQuestions[] = $question;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $answerQuestions[] = $question;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 标准分值范围
|
|
|
|
|
- $standardScoreRanges = [
|
|
|
|
|
- 'choice' => ['min' => 4, 'max' => 6],
|
|
|
|
|
- 'fill' => ['min' => 4, 'max' => 6],
|
|
|
|
|
- 'answer' => ['min' => 8, 'max' => 12],
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ $sortedQuestions = array_merge($choiceQuestions, $fillQuestions, $answerQuestions);
|
|
|
|
|
|
|
|
- // 目标比例
|
|
|
|
|
- $typeRatios = ['choice' => 0.40, 'fill' => 0.25, 'answer' => 0.35];
|
|
|
|
|
|
|
+ // 调试日志
|
|
|
|
|
+ \Illuminate\Support\Facades\Log::info('adjustQuestionScores 开始', [
|
|
|
|
|
+ 'choice_count' => count($choiceQuestions),
|
|
|
|
|
+ 'fill_count' => count($fillQuestions),
|
|
|
|
|
+ 'answer_count' => count($answerQuestions),
|
|
|
|
|
+ ]);
|
|
|
|
|
|
|
|
- // 检查可用题型
|
|
|
|
|
- $availableTypes = array_filter($typeCounts, fn($count) => $count > 0);
|
|
|
|
|
- $availableTypeCount = count($availableTypes);
|
|
|
|
|
- $isPartialTypes = $availableTypeCount < 3 && $availableTypeCount > 0;
|
|
|
|
|
|
|
+ // 重新编号
|
|
|
|
|
+ foreach ($sortedQuestions as $idx => &$question) {
|
|
|
|
|
+ $question['question_number'] = $idx + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ unset($question);
|
|
|
|
|
|
|
|
- if ($isPartialTypes) {
|
|
|
|
|
- $equalRatio = 1.0 / $availableTypeCount;
|
|
|
|
|
- foreach ($typeCounts as $type => $count) {
|
|
|
|
|
- if ($count > 0) {
|
|
|
|
|
- $typeRatios[$type] = $equalRatio;
|
|
|
|
|
- } else {
|
|
|
|
|
- $typeRatios[$type] = 0;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 各题型数量
|
|
|
|
|
+ $typeCounts = [
|
|
|
|
|
+ 'choice' => count($choiceQuestions),
|
|
|
|
|
+ 'fill' => count($fillQuestions),
|
|
|
|
|
+ 'answer' => count($answerQuestions),
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 记录各题型索引
|
|
|
|
|
+ $typeIndexes = ['choice' => [], 'fill' => [], 'answer' => []];
|
|
|
|
|
+ foreach ($sortedQuestions as $index => $question) {
|
|
|
|
|
+ $type = $this->normalizeQuestionType($question['question_type'] ?? 'answer');
|
|
|
|
|
+ $typeIndexes[$type][] = $index;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $typeQuestionIndexes = ['choice' => [], 'fill' => [], 'answer' => []];
|
|
|
|
|
-
|
|
|
|
|
- // 记录每种题型的题目索引
|
|
|
|
|
- foreach ($questions as $index => $question) {
|
|
|
|
|
- $type = $question['question_type'] ?? 'answer';
|
|
|
|
|
- if (in_array($type, ['CHOICE', 'SINGLE_CHOICE', 'MULTIPLE_CHOICE'], true)) {
|
|
|
|
|
- $type = 'choice';
|
|
|
|
|
- } elseif (in_array($type, ['FILL_IN_THE_BLANK', 'FILL'], true)) {
|
|
|
|
|
- $type = 'fill';
|
|
|
|
|
- } elseif (in_array($type, ['CALCULATION', 'WORD_PROBLEM', 'PROOF', 'ANSWER'], true)) {
|
|
|
|
|
- $type = 'answer';
|
|
|
|
|
|
|
+ // 第二步:分配分值
|
|
|
|
|
+ $questionScores = [];
|
|
|
|
|
+
|
|
|
|
|
+ $totalQuestions = $typeCounts['choice'] + $typeCounts['fill'] + $typeCounts['answer'];
|
|
|
|
|
+ $globalBaseScore = floor($targetTotalScore / $totalQuestions);
|
|
|
|
|
+ $globalBaseScore = max(1, $globalBaseScore);
|
|
|
|
|
+
|
|
|
|
|
+ // 确定题型处理顺序(基于 sortedQuestions 中的顺序)
|
|
|
|
|
+ $typeOrder = [];
|
|
|
|
|
+ foreach ($sortedQuestions as $question) {
|
|
|
|
|
+ $type = $this->normalizeQuestionType($question['question_type'] ?? 'answer');
|
|
|
|
|
+ if (! in_array($type, $typeOrder)) {
|
|
|
|
|
+ $typeOrder[] = $type;
|
|
|
}
|
|
}
|
|
|
- $typeQuestionIndexes[$type][] = $index;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 生成每种题型的可能分值选项
|
|
|
|
|
- $typeScoreOptions = [];
|
|
|
|
|
- foreach ($typeQuestionIndexes as $type => $indexes) {
|
|
|
|
|
- if (empty($indexes)) {
|
|
|
|
|
|
|
+ // 记录当前剩余预算
|
|
|
|
|
+ $remainingBudget = $targetTotalScore;
|
|
|
|
|
+
|
|
|
|
|
+ // 按顺序处理每种题型
|
|
|
|
|
+ foreach ($typeOrder as $typeIndex => $type) {
|
|
|
|
|
+ $count = $typeCounts[$type];
|
|
|
|
|
+ if ($count === 0) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $typeQuestionCount = count($indexes);
|
|
|
|
|
- $minScore = $standardScoreRanges[$type]['min'];
|
|
|
|
|
- $maxScore = $standardScoreRanges[$type]['max'];
|
|
|
|
|
- $targetTotal = $targetTotalScore * $typeRatios[$type];
|
|
|
|
|
- $idealPerQuestion = $targetTotal / $typeQuestionCount;
|
|
|
|
|
-
|
|
|
|
|
- $options = [];
|
|
|
|
|
-
|
|
|
|
|
- // 添加标准范围内的选项
|
|
|
|
|
- for ($score = $minScore; $score <= $maxScore; $score++) {
|
|
|
|
|
- $total = $score * $typeQuestionCount;
|
|
|
|
|
- $options[] = [
|
|
|
|
|
- 'score' => $score,
|
|
|
|
|
- 'total' => $total,
|
|
|
|
|
- 'difference' => abs($targetTotalScore - $total),
|
|
|
|
|
- ];
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if ($typeIndex === 0) {
|
|
|
|
|
+ // 第一个题型:拿平均分,然后都-1
|
|
|
|
|
+ $thisBase = $globalBaseScore;
|
|
|
|
|
|
|
|
- // 如果是部分题型,大幅扩展搜索范围
|
|
|
|
|
- if ($isPartialTypes) {
|
|
|
|
|
- $idealScore = (int) round($idealPerQuestion);
|
|
|
|
|
- $searchMin = max($minScore, $idealScore - 10);
|
|
|
|
|
- $searchMax = $idealScore + 10;
|
|
|
|
|
-
|
|
|
|
|
- for ($score = $searchMin; $score <= $searchMax; $score++) {
|
|
|
|
|
- if ($score >= $minScore) {
|
|
|
|
|
- $total = $score * $typeQuestionCount;
|
|
|
|
|
- if (!in_array($total, array_column($options, 'total'))) {
|
|
|
|
|
- $options[] = [
|
|
|
|
|
- 'score' => $score,
|
|
|
|
|
- 'total' => $total,
|
|
|
|
|
- 'difference' => abs($targetTotalScore - $total),
|
|
|
|
|
- ];
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $questionScores[$idx] = $thisBase;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 都-1
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $questionScores[$idx] = max(1, $questionScores[$idx] - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算已分配的分数
|
|
|
|
|
+ $allocated = 0;
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $allocated += $questionScores[$idx];
|
|
|
|
|
+ }
|
|
|
|
|
+ $remainingBudget -= $allocated;
|
|
|
|
|
+ } elseif ($typeIndex === count($typeOrder) - 1) {
|
|
|
|
|
+ // 最后一个题型:用剩余分数分配
|
|
|
|
|
+ $thisBase = floor($remainingBudget / $count);
|
|
|
|
|
+ $thisBase = max(1, $thisBase);
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $questionScores[$idx] = $thisBase;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 余数补偿:分散到多道题,从后往前各+1
|
|
|
|
|
+ $total = $thisBase * $count;
|
|
|
|
|
+ $remainder = $remainingBudget - $total;
|
|
|
|
|
+ if ($remainder > 0) {
|
|
|
|
|
+ // 从最后一道题开始,往前 $remainder 道题各+1
|
|
|
|
|
+ $answerIndexes = array_values($typeIndexes[$type]);
|
|
|
|
|
+ $startIdx = max(0, count($answerIndexes) - $remainder);
|
|
|
|
|
+ for ($i = $startIdx; $i < count($answerIndexes); $i++) {
|
|
|
|
|
+ $questionScores[$answerIndexes[$i]] += 1;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 中间的题型:直接用全局平均分(不减)
|
|
|
|
|
+ $thisBase = $globalBaseScore;
|
|
|
|
|
|
|
|
- $typeScoreOptions[$type] = $options;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $questionScores[$idx] = $thisBase;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 生成所有可能的组合
|
|
|
|
|
- $types = array_keys(array_filter($typeQuestionIndexes, fn($indexes) => !empty($indexes)));
|
|
|
|
|
- $allCombinations = [[]];
|
|
|
|
|
-
|
|
|
|
|
- foreach ($types as $type) {
|
|
|
|
|
- $newCombinations = [];
|
|
|
|
|
- foreach ($allCombinations as $combo) {
|
|
|
|
|
- foreach ($typeScoreOptions[$type] as $option) {
|
|
|
|
|
- $newCombo = $combo;
|
|
|
|
|
- $newCombo[$type] = $option;
|
|
|
|
|
- $newCombinations[] = $newCombo;
|
|
|
|
|
|
|
+ // 计算已分配的分数
|
|
|
|
|
+ $allocated = 0;
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $allocated += $questionScores[$idx];
|
|
|
}
|
|
}
|
|
|
|
|
+ $remainingBudget -= $allocated;
|
|
|
}
|
|
}
|
|
|
- $allCombinations = $newCombinations;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 找到最佳组合(优先精确匹配,其次最接近)
|
|
|
|
|
- $bestCombination = null;
|
|
|
|
|
- $bestDifference = PHP_FLOAT_MAX;
|
|
|
|
|
- $exactMatchFound = false;
|
|
|
|
|
|
|
+ // 第三步:确保最后一类题型分数 > 前面所有题型
|
|
|
|
|
+ if (count($typeOrder) > 1) {
|
|
|
|
|
+ $lastType = end($typeOrder);
|
|
|
|
|
+ $otherTypes = array_slice($typeOrder, 0, -1);
|
|
|
|
|
|
|
|
- foreach ($allCombinations as $combo) {
|
|
|
|
|
- $totalScore = array_sum(array_column($combo, 'total'));
|
|
|
|
|
- $difference = abs($targetTotalScore - $totalScore);
|
|
|
|
|
|
|
+ // 前面题型的最高分
|
|
|
|
|
+ $maxOtherScore = 0;
|
|
|
|
|
+ foreach ($otherTypes as $type) {
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $maxOtherScore = max($maxOtherScore, $questionScores[$idx]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if ($difference == 0) {
|
|
|
|
|
- $bestCombination = $combo;
|
|
|
|
|
- $exactMatchFound = true;
|
|
|
|
|
- break;
|
|
|
|
|
|
|
+ // 最后一类题型的最低分
|
|
|
|
|
+ $minLastScore = PHP_INT_MAX;
|
|
|
|
|
+ foreach ($typeIndexes[$lastType] as $idx) {
|
|
|
|
|
+ $minLastScore = min($minLastScore, $questionScores[$idx]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if ($difference < $bestDifference) {
|
|
|
|
|
- $bestDifference = $difference;
|
|
|
|
|
- $bestCombination = $combo;
|
|
|
|
|
|
|
+ // 如果最后一类不够高,从前面扣分
|
|
|
|
|
+ if ($minLastScore <= $maxOtherScore) {
|
|
|
|
|
+ $diff = $maxOtherScore - $minLastScore + 1;
|
|
|
|
|
+
|
|
|
|
|
+ // 从前面题型扣分(每道最多扣2分)
|
|
|
|
|
+ $reductionPerQuestion = min($diff, 2);
|
|
|
|
|
+ foreach ($otherTypes as $type) {
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $questionScores[$idx] = max(1, $questionScores[$idx] - $reductionPerQuestion);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重新计算剩余给最后一类
|
|
|
|
|
+ $reallocated = $targetTotalScore;
|
|
|
|
|
+ foreach ($typeIndexes[$lastType] as $idx) {
|
|
|
|
|
+ $reallocated -= $questionScores[$idx];
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach ($otherTypes as $type) {
|
|
|
|
|
+ foreach ($typeIndexes[$type] as $idx) {
|
|
|
|
|
+ $reallocated -= $questionScores[$idx];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($reallocated > 0) {
|
|
|
|
|
+ $newBase = floor($reallocated / $typeCounts[$lastType]);
|
|
|
|
|
+ foreach ($typeIndexes[$lastType] as $idx) {
|
|
|
|
|
+ $questionScores[$idx] = $newBase;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $total = $newBase * $typeCounts[$lastType];
|
|
|
|
|
+ $remainder = $reallocated - $total;
|
|
|
|
|
+ if ($remainder > 0) {
|
|
|
|
|
+ // 余数分散到多道题,从后往前各+1
|
|
|
|
|
+ $lastIndexes = array_values($typeIndexes[$lastType]);
|
|
|
|
|
+ $startIdx = max(0, count($lastIndexes) - $remainder);
|
|
|
|
|
+ for ($i = $startIdx; $i < count($lastIndexes); $i++) {
|
|
|
|
|
+ $questionScores[$lastIndexes[$i]] += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 应用最佳组合
|
|
|
|
|
|
|
+ // 第三步:构建结果
|
|
|
$adjustedQuestions = [];
|
|
$adjustedQuestions = [];
|
|
|
- if ($bestCombination) {
|
|
|
|
|
- foreach ($bestCombination as $type => $option) {
|
|
|
|
|
- $score = $option['score'];
|
|
|
|
|
- foreach ($typeQuestionIndexes[$type] as $index) {
|
|
|
|
|
- $question = $questions[$index];
|
|
|
|
|
- $question['score'] = $score;
|
|
|
|
|
- $adjustedQuestions[$index] = $question;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ foreach ($sortedQuestions as $index => $question) {
|
|
|
|
|
+ $adjustedQuestions[$index] = $question;
|
|
|
|
|
+ $adjustedQuestions[$index]['score'] = $questionScores[$index] ?? 5;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $adjustedQuestions;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 标准化题目类型
|
|
|
|
|
+ */
|
|
|
|
|
+ private function normalizeQuestionType(string $type): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $type = strtolower(trim($type));
|
|
|
|
|
+ if (in_array($type, ['choice', 'single_choice', 'multiple_choice', '选择题', '单选', '多选'], true)) {
|
|
|
|
|
+ return 'choice';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (in_array($type, ['fill', 'fill_in_the_blank', 'blank', '填空题', '填空'], true)) {
|
|
|
|
|
+ return 'fill';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return array_values($adjustedQuestions);
|
|
|
|
|
|
|
+ return 'answer';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private function resolveMistakeQuestionIds(string $studentId, array $mistakeIds, array $mistakeQuestionIds): array
|
|
private function resolveMistakeQuestionIds(string $studentId, array $mistakeIds, array $mistakeQuestionIds): array
|
|
|
{
|
|
{
|
|
|
$questionIds = [];
|
|
$questionIds = [];
|
|
|
|
|
|
|
|
- if (!empty($mistakeQuestionIds)) {
|
|
|
|
|
|
|
+ if (! empty($mistakeQuestionIds)) {
|
|
|
$questionIds = array_merge($questionIds, $mistakeQuestionIds);
|
|
$questionIds = array_merge($questionIds, $mistakeQuestionIds);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!empty($mistakeIds)) {
|
|
|
|
|
|
|
+ if (! empty($mistakeIds)) {
|
|
|
$mistakeQuestionIdsFromDb = MistakeRecord::query()
|
|
$mistakeQuestionIdsFromDb = MistakeRecord::query()
|
|
|
->where('student_id', $studentId)
|
|
->where('student_id', $studentId)
|
|
|
->whereIn('id', $mistakeIds)
|
|
->whereIn('id', $mistakeIds)
|
|
@@ -1001,6 +1062,7 @@ class IntelligentExamController extends Controller
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$questionIds = array_values(array_unique(array_filter($questionIds)));
|
|
$questionIds = array_values(array_unique(array_filter($questionIds)));
|
|
|
|
|
+
|
|
|
return $questionIds;
|
|
return $questionIds;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1016,6 +1078,7 @@ class IntelligentExamController extends Controller
|
|
|
$bId = (string) ($b['id'] ?? '');
|
|
$bId = (string) ($b['id'] ?? '');
|
|
|
$aPos = $order[$aId] ?? PHP_INT_MAX;
|
|
$aPos = $order[$aId] ?? PHP_INT_MAX;
|
|
|
$bPos = $order[$bId] ?? PHP_INT_MAX;
|
|
$bPos = $order[$bId] ?? PHP_INT_MAX;
|
|
|
|
|
+
|
|
|
return $aPos <=> $bPos;
|
|
return $aPos <=> $bPos;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -1034,7 +1097,7 @@ class IntelligentExamController extends Controller
|
|
|
$grade = $data['grade'] ?? null;
|
|
$grade = $data['grade'] ?? null;
|
|
|
|
|
|
|
|
// 如果没有提供series_id或semester_code,则不设置textbook_id
|
|
// 如果没有提供series_id或semester_code,则不设置textbook_id
|
|
|
- if (!$seriesId || !$semesterCode) {
|
|
|
|
|
|
|
+ if (! $seriesId || ! $semesterCode) {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1057,16 +1120,18 @@ class IntelligentExamController extends Controller
|
|
|
'series_id' => $seriesId,
|
|
'series_id' => $seriesId,
|
|
|
'semester_code' => $semesterCode,
|
|
'semester_code' => $semesterCode,
|
|
|
'grade' => $grade,
|
|
'grade' => $grade,
|
|
|
- 'textbook_id' => $textbook->id
|
|
|
|
|
|
|
+ 'textbook_id' => $textbook->id,
|
|
|
]);
|
|
]);
|
|
|
|
|
+
|
|
|
return (int) $textbook->id;
|
|
return (int) $textbook->id;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Log::warning('未找到匹配的教材', [
|
|
Log::warning('未找到匹配的教材', [
|
|
|
'series_id' => $seriesId,
|
|
'series_id' => $seriesId,
|
|
|
'semester_code' => $semesterCode,
|
|
'semester_code' => $semesterCode,
|
|
|
- 'grade' => $grade
|
|
|
|
|
|
|
+ 'grade' => $grade,
|
|
|
]);
|
|
]);
|
|
|
|
|
+
|
|
|
return null;
|
|
return null;
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
@@ -1074,8 +1139,9 @@ class IntelligentExamController extends Controller
|
|
|
'series_id' => $seriesId,
|
|
'series_id' => $seriesId,
|
|
|
'semester_code' => $semesterCode,
|
|
'semester_code' => $semesterCode,
|
|
|
'grade' => $grade,
|
|
'grade' => $grade,
|
|
|
- 'error' => $e->getMessage()
|
|
|
|
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
]);
|
|
]);
|
|
|
|
|
+
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|