Browse Source

fix(diagnostic): merge full-textbook KPs when restart chapter has no questions

When all chapters are diagnosed and single-chapter payloads (anchor/first)
return null after filtering KPs with questions, build a textbook-wide
payload (ordered sections + deduped KPs) with is_restart=true instead
of failing chapter diagnostic assembly.

Made-with: Cursor
yemeishu 1 month ago
parent
commit
deb81e6e4b
1 changed files with 97 additions and 9 deletions
  1. 97 9
      app/Services/DiagnosticChapterService.php

+ 97 - 9
app/Services/DiagnosticChapterService.php

@@ -413,6 +413,20 @@ class DiagnosticChapterService
                     }
                 }
 
+                $widePayload = $this->buildTextbookWideChapterPayload($chaptersInTextbook);
+                if ($widePayload !== null) {
+                    $widePayload['is_restart'] = true;
+                    Log::info('DiagnosticChapterService: 全教材已摸底且单章无有效题,合并全教材知识点重启', [
+                        'textbook_id' => $anchorTextbookId,
+                        'student_id' => $studentId,
+                        'diagnostic_chapter_id' => $widePayload['chapter_id'],
+                        'kp_count' => count($widePayload['kp_codes']),
+                        'section_count' => count($widePayload['section_ids']),
+                    ]);
+
+                    return $widePayload;
+                }
+
                 return null;
             }
 
@@ -529,18 +543,32 @@ class DiagnosticChapterService
         // 所有章节都已摸底,返回第一章(重新开始)
         $firstChapter = $chapters->first();
         $firstPayload = $firstChapter ? $this->buildChapterPayload((int) $firstChapter->id, $firstChapter, $payloadCache) : null;
-        if ($firstPayload === null) {
-            return null;
+        if ($firstPayload !== null) {
+            Log::info('DiagnosticChapterService: 所有章节都已摸底,返回第一章', [
+                'textbook_id' => $textbookId,
+                'student_id' => $studentId,
+                'chapter_id' => $firstChapter->id,
+            ]);
+            $firstPayload['is_restart'] = true;
+
+            return $firstPayload;
         }
 
-        Log::info('DiagnosticChapterService: 所有章节都已摸底,返回第一章', [
-            'textbook_id' => $textbookId,
-            'student_id' => $studentId,
-            'chapter_id' => $firstChapter->id,
-        ]);
+        $widePayload = $this->buildTextbookWideChapterPayload($chapters);
+        if ($widePayload !== null) {
+            $widePayload['is_restart'] = true;
+            Log::info('DiagnosticChapterService: 所有章节已摸底且第一章无有效题,合并全教材知识点重启', [
+                'textbook_id' => $textbookId,
+                'student_id' => $studentId,
+                'diagnostic_chapter_id' => $widePayload['chapter_id'],
+                'kp_count' => count($widePayload['kp_codes']),
+                'section_count' => count($widePayload['section_ids']),
+            ]);
 
-        $firstPayload['is_restart'] = true;
-        return $firstPayload; // 标记是重新开始
+            return $widePayload;
+        }
+
+        return null;
     }
 
     /**
@@ -597,6 +625,66 @@ class DiagnosticChapterService
         return $chapterIds;
     }
 
+    /**
+     * 按教材章节顺序合并全部 section 与知识点,再过滤有题知识点。
+     * 用于「全章已摸底」且单章(含第一章)无可用题时的兜底。
+     *
+     * @param  iterable<int, \App\Models\TextbookCatalog>  $chapters
+     */
+    private function buildTextbookWideChapterPayload(iterable $chapters): ?array
+    {
+        $firstChapter = null;
+        $allSectionIds = [];
+        $seenSection = [];
+        $orderedKp = [];
+        $seenKp = [];
+
+        foreach ($chapters as $chapter) {
+            if ($firstChapter === null) {
+                $firstChapter = $chapter;
+            }
+
+            $chapterId = (int) $chapter->id;
+            $chapterData = $this->getChapterKnowledgePointsSimple($chapterId);
+
+            foreach ($chapterData['section_ids'] as $sid) {
+                $sid = (int) $sid;
+                if ($sid <= 0 || isset($seenSection[$sid])) {
+                    continue;
+                }
+                $seenSection[$sid] = true;
+                $allSectionIds[] = $sid;
+            }
+
+            foreach ($chapterData['kp_codes'] as $kpCode) {
+                if ($kpCode === null || $kpCode === '') {
+                    continue;
+                }
+                if (isset($seenKp[$kpCode])) {
+                    continue;
+                }
+                $seenKp[$kpCode] = true;
+                $orderedKp[] = $kpCode;
+            }
+        }
+
+        if ($firstChapter === null) {
+            return null;
+        }
+
+        $kpCodesWithQuestions = $this->filterKpCodesWithQuestions($orderedKp);
+        if (empty($kpCodesWithQuestions)) {
+            return null;
+        }
+
+        return [
+            'chapter_id' => (int) $firstChapter->id,
+            'chapter_name' => $firstChapter->name ?? $firstChapter->title ?? '',
+            'section_ids' => $allSectionIds,
+            'kp_codes' => $kpCodesWithQuestions,
+        ];
+    }
+
     /**
      * 根据 chapter_id 生成摸底章节载荷,若章节无可用题目知识点则返回 null。
      */