yemeishu vor 5 Tagen
Ursprung
Commit
7c49507fc8

+ 210 - 6
app/Http/Controllers/Api/IntelligentExamController.php

@@ -163,17 +163,22 @@ class IntelligentExamController extends Controller
                 ], 400);
             }
 
-            $totalScore = array_sum(array_column($questions, 'score'));
             $totalQuestions = min($data['total_questions'], count($questions));
             $questions = array_slice($questions, 0, $totalQuestions);
 
+            // 调整题目分值,确保符合中国中学卷子标准(总分100分)
+            $questions = $this->adjustQuestionScores($questions, 100.0);
+
+            // 计算总分
+            $totalScore = array_sum(array_column($questions, 'score'));
+
             // 第二步:保存试卷到数据库(同步)
             $paperId = $this->questionBankService->saveExamToDatabase([
                 'paper_name' => $paperName,
                 'student_id' => $data['student_id'],
                 'teacher_id' => $data['teacher_id'] ?? null,
                 'difficulty_category' => $difficultyCategory,
-                'total_score' => $data['total_score'] ?? $totalScore,
+                'total_score' => $data['total_score'] ?? 100.0, // 默认100分
                 'questions' => $questions,
             ]);
 
@@ -235,9 +240,21 @@ class IntelligentExamController extends Controller
                 'trace' => $e->getTraceAsString(),
             ]);
 
+            // 返回更具体的错误信息
+            $errorMessage = $e->getMessage();
+            if (strpos($errorMessage, 'Connection') !== false || strpos($errorMessage, 'connection') !== false) {
+                $errorMessage = '依赖服务连接失败,请检查服务状态';
+            } elseif (strpos($errorMessage, 'timeout') !== false || strpos($errorMessage, '超时') !== false) {
+                $errorMessage = '服务响应超时,请稍后重试';
+            } elseif (strpos($errorMessage, 'not found') !== false || strpos($errorMessage, '未找到') !== false) {
+                $errorMessage = '请求的资源不存在';
+            } elseif (strpos($errorMessage, 'invalid') !== false || strpos($errorMessage, '无效') !== false) {
+                $errorMessage = '请求参数无效';
+            }
+
             return response()->json([
                 'success' => false,
-                'message' => '服务异常,请稍后重试',
+                'message' => $errorMessage ?: '服务异常,请稍后重试',
             ], 500);
         }
     }
@@ -360,6 +377,12 @@ class IntelligentExamController extends Controller
      */
     private function normalizePayload(array $payload): array
     {
+        // 处理 question_count 参数:转换为 total_questions
+        if (isset($payload['question_count']) && !isset($payload['total_questions'])) {
+            $payload['total_questions'] = $payload['question_count'];
+            unset($payload['question_count']);
+        }
+
         // 处理 kp_codes:空字符串或null转换为空数组
         if (isset($payload['kp_codes'])) {
             if (is_string($payload['kp_codes'])) {
@@ -529,13 +552,194 @@ class IntelligentExamController extends Controller
         return '解答题';
     }
 
+    /**
+     * 根据题目类型获取默认分值(中国中学卷子标准)
+     * 选择题:5分/题,填空题:5分/题,解答题:10分/题
+     */
     private function defaultScore(string $type): int
     {
-        if ($type === '选择题' || $type === '填空题') {
-            return 5;
+        return match ($type) {
+            '选择题' => 5,
+            '填空题' => 5,
+            '解答题' => 10,
+            default => 5,
+        };
+    }
+
+    /**
+     * 计算试卷总分并调整各题目分值,确保总分接近目标分数
+     * 符合中国中学卷子标准:
+     * - 选择题:约40%总分(每题4-6分,整数分值)
+     * - 填空题:约25%总分(每题4-6分,整数分值)
+     * - 解答题:约35%总分(每题8-12分,整数分值)
+     * 使用组合优化算法确保:
+     * 1. 所有分值都是整数(无小数点)
+     * 2. 同类型题目分值均匀
+     * 3. 总分精确匹配目标分数(或最接近)
+     */
+    private function adjustQuestionScores(array $questions, float $targetTotalScore = 100.0): array
+    {
+        if (empty($questions)) {
+            return $questions;
+        }
+
+        // 统计各类型题目数量
+        $typeCounts = ['choice' => 0, 'fill' => 0, 'answer' => 0];
+
+        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]++;
+            }
+        }
+
+        // 标准分值范围
+        $standardScoreRanges = [
+            'choice' => ['min' => 4, 'max' => 6],
+            'fill' => ['min' => 4, 'max' => 6],
+            'answer' => ['min' => 8, 'max' => 12],
+        ];
+
+        // 目标比例
+        $typeRatios = ['choice' => 0.40, 'fill' => 0.25, 'answer' => 0.35];
+
+        // 检查可用题型
+        $availableTypes = array_filter($typeCounts, fn($count) => $count > 0);
+        $availableTypeCount = count($availableTypes);
+        $isPartialTypes = $availableTypeCount < 3 && $availableTypeCount > 0;
+
+        if ($isPartialTypes) {
+            $equalRatio = 1.0 / $availableTypeCount;
+            foreach ($typeCounts as $type => $count) {
+                if ($count > 0) {
+                    $typeRatios[$type] = $equalRatio;
+                } else {
+                    $typeRatios[$type] = 0;
+                }
+            }
+        }
+
+        $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';
+            }
+            $typeQuestionIndexes[$type][] = $index;
+        }
+
+        // 生成每种题型的可能分值选项
+        $typeScoreOptions = [];
+        foreach ($typeQuestionIndexes as $type => $indexes) {
+            if (empty($indexes)) {
+                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 ($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),
+                            ];
+                        }
+                    }
+                }
+            }
+
+            $typeScoreOptions[$type] = $options;
+        }
+
+        // 生成所有可能的组合
+        $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;
+                }
+            }
+            $allCombinations = $newCombinations;
+        }
+
+        // 找到最佳组合(优先精确匹配,其次最接近)
+        $bestCombination = null;
+        $bestDifference = PHP_FLOAT_MAX;
+        $exactMatchFound = false;
+
+        foreach ($allCombinations as $combo) {
+            $totalScore = array_sum(array_column($combo, 'total'));
+            $difference = abs($targetTotalScore - $totalScore);
+
+            if ($difference == 0) {
+                $bestCombination = $combo;
+                $exactMatchFound = true;
+                break;
+            }
+
+            if ($difference < $bestDifference) {
+                $bestDifference = $difference;
+                $bestCombination = $combo;
+            }
+        }
+
+        // 应用最佳组合
+        $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;
+                }
+            }
         }
 
-        return 10;
+        return array_values($adjustedQuestions);
     }
 
     private function resolveMistakeQuestionIds(string $studentId, array $mistakeIds, array $mistakeQuestionIds): array

+ 7 - 1
app/Services/QuestionBankService.php

@@ -246,7 +246,13 @@ class QuestionBankService
     public function generateIntelligentQuestions(array $params, ?string $callbackUrl = null): array
     {
         if ($this->useLocal()) {
-            return $this->local()->generateQuestions($params);
+            // 转换参数格式以匹配本地服务的期望
+            $localParams = $params;
+            if (isset($params['total_questions'])) {
+                $localParams['count'] = $params['total_questions'];
+                unset($localParams['total_questions']);
+            }
+            return $this->local()->generateQuestions($localParams);
         }
 
         try {

+ 26 - 4
app/Services/QuestionLocalService.php

@@ -174,11 +174,33 @@ class QuestionLocalService
     public function generateQuestions(array $params): array
     {
         $kpCode = $params['kp_code'] ?? null;
+        // 允许 kp_code 为空,此时从所有可用题目中选择
         if (!$kpCode) {
-            return [
-                'success' => false,
-                'message' => '缺少知识点代码',
-            ];
+            // 从 params 中获取 kp_codes 数组
+            $kpCodes = $params['kp_codes'] ?? [];
+            if (is_string($kpCodes)) {
+                $kpCodes = array_map('trim', explode(',', $kpCodes));
+            }
+            if (is_array($kpCodes) && !empty($kpCodes)) {
+                $kpCode = $kpCodes[0]; // 使用第一个知识点
+            } else {
+                // 如果没有指定知识点,从数据库中随机选择一个可用的知识点
+                $availableKp = Question::query()
+                    ->whereNotNull('kp_code')
+                    ->where('kp_code', '!=', '')
+                    ->distinct()
+                    ->pluck('kp_code')
+                    ->first();
+
+                if ($availableKp) {
+                    $kpCode = $availableKp;
+                } else {
+                    return [
+                        'success' => false,
+                        'message' => '系统中没有可用的题目,请先添加题目数据',
+                    ];
+                }
+            }
         }
 
         $count = max(1, (int) ($params['count'] ?? 1));