소스 검색

feat(exam): 摸底组卷时教材找不到则从同系列 fallback

- 新增 findFallbackTextbookInSeries() 方法
- 筛选 grade <= 目标年级,按 grade/semester 降序选择最接近的上一本教材
- 不再限制 9年级下/12年级下的硬编码逻辑

Made-with: Cursor
yemeishu 1 개월 전
부모
커밋
c797fc6df8
1개의 변경된 파일82개의 추가작업 그리고 1개의 파일을 삭제
  1. 82 1
      app/Http/Controllers/Api/IntelligentExamController.php

+ 82 - 1
app/Http/Controllers/Api/IntelligentExamController.php

@@ -1157,12 +1157,27 @@ class IntelligentExamController extends Controller
                 return (int) $textbook->id;
             }
 
-            Log::warning('未找到匹配的教材', [
+            Log::warning('未找到匹配的教材,尝试从同系列中寻找其他教材', [
                 'series_id' => $seriesId,
                 'semester_code' => $semesterCode,
                 'grade' => $grade,
             ]);
 
+            // Fallback:从同系列中按 grade 倒序、semester 倒序选一个有章节的教材
+            $fallbackTextbook = $this->findFallbackTextbookInSeries($seriesId, $grade, $semesterCode);
+            if ($fallbackTextbook) {
+                Log::info('成功从同系列中找到fallback教材', [
+                    'original_series_id' => $seriesId,
+                    'original_semester_code' => $semesterCode,
+                    'original_grade' => $grade,
+                    'fallback_textbook_id' => $fallbackTextbook->id,
+                    'fallback_grade' => $fallbackTextbook->grade,
+                    'fallback_semester' => $fallbackTextbook->semester,
+                ]);
+
+                return (int) $fallbackTextbook->id;
+            }
+
             return null;
 
         } catch (\Exception $e) {
@@ -1176,4 +1191,70 @@ class IntelligentExamController extends Controller
             return null;
         }
     }
+
+    /**
+     * 从同系列中查找一个有章节的教材
+     * 优先选择与目标最接近的教材:grade 倒序,semester 倒序
+     * 用于教材找不到时的 fallback
+     */
+    private function findFallbackTextbookInSeries(int $seriesId, ?int $targetGrade = null, ?int $targetSemester = null): ?\stdClass
+    {
+        try {
+            // 找出同系列下有章节的教材
+            // 筛选 grade <= 目标年级(确保不选择比目标更高的年级)
+            // 按 grade 降序、semester 降序排列
+            // 这样当目标教材不存在时,会选择最接近的"上一个年级/学期"的教材
+            // 例如:找 8年级下 -> 筛选 <=8年级 -> 7年级下 > 7年级上 > 6年级下 > 6年级上
+            $query = DB::connection('mysql')
+                ->table('textbooks as t')
+                ->leftJoin('textbook_catalog_nodes as c', 't.id', '=', 'c.textbook_id')
+                ->where('t.series_id', $seriesId)
+                ->where('c.node_type', 'chapter')
+                ->whereNotNull('c.id');
+
+            if ($targetGrade !== null) {
+                $query->where('t.grade', '<=', $targetGrade);
+            }
+
+            $textbooksWithChapters = $query
+                ->select('t.id', 't.grade', 't.semester')
+                ->groupBy('t.id', 't.grade', 't.semester')
+                ->orderByDesc('t.grade')
+                ->orderByDesc('t.semester')
+                ->get();
+
+            if ($textbooksWithChapters->isEmpty()) {
+                Log::warning('同系列中没有任何教材有章节', [
+                    'series_id' => $seriesId,
+                ]);
+                return null;
+            }
+
+            // 选择第一个有章节的教材(已按 grade 倒序、semester 倒序排列)
+            $selected = $textbooksWithChapters->first();
+
+            Log::info('按倒序选择fallback教材', [
+                'series_id' => $seriesId,
+                'target_grade' => $targetGrade,
+                'target_semester' => $targetSemester,
+                'selected_textbook_id' => $selected->id,
+                'selected_grade' => $selected->grade,
+                'selected_semester' => $selected->semester,
+                'available_count' => $textbooksWithChapters->count(),
+            ]);
+
+            // 返回完整的教材对象
+            return DB::connection('mysql')
+                ->table('textbooks')
+                ->where('id', $selected->id)
+                ->first();
+
+        } catch (\Exception $e) {
+            Log::error('查找fallback教材失败', [
+                'series_id' => $seriesId,
+                'error' => $e->getMessage(),
+            ]);
+            return null;
+        }
+    }
 }