Quellcode durchsuchen

feat(exam): sort questions by difficulty within type and add optional difficulty tag

yemeishu vor 3 Wochen
Ursprung
Commit
15d976b8c3

+ 1 - 0
.env.example

@@ -84,4 +84,5 @@ MATHRECSYS_TIMEOUT=30
 
 # PDF 配置
 EXAM_PDF_SHOW_QUESTION_ID=false
+EXAM_PDF_SHOW_QUESTION_DIFFICULTY=false
 EXAM_PDF_GRADING_SHOW_STEM=true

+ 57 - 0
app/Http/Controllers/Api/IntelligentExamController.php

@@ -262,6 +262,9 @@ class IntelligentExamController extends Controller
                 $questions = array_slice($questions, 0, $totalQuestions);
             }
 
+            // 每个题型内按难度升序排序(由易到难),并重排题号
+            $questions = $this->sortQuestionsWithinTypeByDifficulty($questions);
+
             // 调整题目分值
             if (($data['total_questions'] ?? 20) == 20) {
                 // 20题:沿用动态凑整算法,目标总分100
@@ -1208,6 +1211,60 @@ class IntelligentExamController extends Controller
         return $questions;
     }
 
+    /**
+     * 每个题型内按难度升序排序,并统一重排题号
+     * 题型顺序固定:选择题 -> 填空题 -> 解答题
+     */
+    private function sortQuestionsWithinTypeByDifficulty(array $questions): array
+    {
+        if (empty($questions)) {
+            return $questions;
+        }
+
+        $grouped = [
+            'choice' => [],
+            'fill' => [],
+            'answer' => [],
+        ];
+
+        foreach ($questions as $question) {
+            $type = $this->normalizeQuestionType((string) ($question['question_type'] ?? 'answer'));
+            $grouped[$type][] = $question;
+        }
+
+        $sortFn = function (array $a, array $b): int {
+            $aDifficulty = (float) ($a['difficulty'] ?? 0.5);
+            $bDifficulty = (float) ($b['difficulty'] ?? 0.5);
+
+            if ($aDifficulty !== $bDifficulty) {
+                return $aDifficulty <=> $bDifficulty;
+            }
+
+            $aId = (int) ($a['id'] ?? $a['question_id'] ?? 0);
+            $bId = (int) ($b['id'] ?? $b['question_id'] ?? 0);
+
+            return $aId <=> $bId;
+        };
+
+        usort($grouped['choice'], $sortFn);
+        usort($grouped['fill'], $sortFn);
+        usort($grouped['answer'], $sortFn);
+
+        $sorted = array_merge($grouped['choice'], $grouped['fill'], $grouped['answer']);
+        foreach ($sorted as $idx => &$question) {
+            $question['question_number'] = $idx + 1;
+        }
+        unset($question);
+
+        Log::info('题目已按题型内难度排序', [
+            'choice_difficulty' => array_map(fn ($q) => $q['difficulty'] ?? null, $grouped['choice']),
+            'fill_difficulty' => array_map(fn ($q) => $q['difficulty'] ?? null, $grouped['fill']),
+            'answer_difficulty' => array_map(fn ($q) => $q['difficulty'] ?? null, $grouped['answer']),
+        ]);
+
+        return $sorted;
+    }
+
     /**
      * 【新增】根据series_id、semester_code和grade获取textbook_id
      * 替代原来直接传入textbook_id的方式

+ 10 - 0
config/exam.php

@@ -12,6 +12,16 @@ return [
     */
     'show_question_id_in_pdf' => env('EXAM_PDF_SHOW_QUESTION_ID', false),
 
+    /*
+    |--------------------------------------------------------------------------
+    | PDF 是否显示题目难度
+    |--------------------------------------------------------------------------
+    |
+    | 控制生成PDF时是否在题号后显示难度,便于校验题目排序逻辑。
+    |
+    */
+    'show_question_difficulty_in_pdf' => env('EXAM_PDF_SHOW_QUESTION_DIFFICULTY', false),
+
     /*
     |--------------------------------------------------------------------------
     | 判卷PDF是否显示题目

+ 17 - 0
resources/views/components/exam/paper-body.blade.php

@@ -6,6 +6,8 @@
 
     // 是否在题干后显示题目ID (Q000123)
     $showQuestionId = config('exam.show_question_id_in_pdf', false);
+    // 是否在题号后显示题目难度(用于校验排序)
+    $showQuestionDifficulty = config('exam.show_question_difficulty_in_pdf', false);
     // 判卷模式是否显示题目题干与选项,默认显示;可通过 EXAM_PDF_GRADING_SHOW_STEM 关闭
     $showGradingStem = config('exam.pdf_grading_show_stem', true);
 
@@ -15,6 +17,12 @@
         return '(Q' . str_pad($id, 6, '0', STR_PAD_LEFT) . ')';
     };
 
+    $formatDifficulty = function($difficulty) {
+        if ($difficulty === null || $difficulty === '') return '';
+        $value = (float) $difficulty;
+        return sprintf('[%.2f]', $value);
+    };
+
     // 【新增】动态计算大题号 - 根据有题目的题型分配序号
     $sectionNumbers = [
         'choice' => null,
@@ -198,6 +206,9 @@
                         <span class="question-stem">{!! $renderedStem !!}</span>
                         @if($showQuestionId && !empty($q->id))
                             <span class="question-id" style="font-size:10px;color:#999;margin-left:4px;">{!! $formatQuestionId($q->id) !!}</span>
+                            @if($showQuestionDifficulty)
+                                <span style="font-size:10px;color:#999;margin-left:4px;white-space:nowrap;">{{ $formatDifficulty($q->difficulty ?? null) }}</span>
+                            @endif
                         @endif
                     </div>
                 @endif
@@ -349,6 +360,9 @@
                         <span class="question-stem">{!! $renderedContent !!}</span>
                         @if($showQuestionId && !empty($q->id))
                             <span class="question-id" style="font-size:10px;color:#999;margin-left:4px;">{!! $formatQuestionId($q->id) !!}</span>
+                            @if($showQuestionDifficulty)
+                                <span style="font-size:10px;color:#999;margin-left:4px;white-space:nowrap;">{{ $formatDifficulty($q->difficulty ?? null) }}</span>
+                            @endif
                         @endif
                     </div>
                 @endif
@@ -407,6 +421,9 @@
                             <span class="question-score-inline">(本小题满分 {{ $q->score ?? 10 }} 分)
                                 @if($showQuestionId && !empty($q->id))
                                     <span class="question-id" style="font-size:10px;color:#999;margin-left:4px;">{!! $formatQuestionId($q->id) !!}</span>
+                                    @if($showQuestionDifficulty)
+                                        <span style="font-size:10px;color:#999;margin-left:4px;white-space:nowrap;">{{ $formatDifficulty($q->difficulty ?? null) }}</span>
+                                    @endif
                                 @endif
                             </span>
                         @endunless