Browse Source

fix: 增加新的摸底组卷

yemeishu 1 tuần trước cách đây
mục cha
commit
00099b4973

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

@@ -82,7 +82,7 @@ class IntelligentExamController extends Controller
             'mistake_question_ids.*' => 'string',
             'callback_url' => 'nullable|url',  // 异步完成后推送通知的URL
             // 新增:组卷类型
-            'assemble_type' => 'nullable|integer|in:0,1,2,3,4,5,6',
+            'assemble_type' => 'nullable|integer|in:0,1,2,3,4,5,9',
             'exam_type' => 'nullable|string|in:general,diagnostic,practice,mistake,textbook,knowledge,knowledge_points',
             // 错题本类型专用参数
             'paper_ids' => 'nullable|array',

+ 1 - 1
app/Models/KnowledgePoint.php

@@ -9,7 +9,7 @@ class KnowledgePoint extends Model
 {
     use HasFactory;
 
-    protected $table = 'knowledge_points'; // 'knowledge_points_copy1'; // 正式环境回归:knowledge_points
+    protected $table = 'knowledge_points_copy1'; // 正式环境回归:knowledge_points
 
     protected $fillable = [
         'kp_code',

+ 22 - 0
app/Models/StudentKnowledgeMastery.php

@@ -112,6 +112,28 @@ class StudentKnowledgeMastery extends Model
             ->toArray();
     }
 
+    public static function allAtLeast(int $studentId, array $kpCodes, float $threshold): bool
+    {
+        if (empty($kpCodes)) {
+            return false;
+        }
+
+        $levels = self::query()
+            ->where('student_id', $studentId)
+            ->whereIn('kp_code', $kpCodes)
+            ->pluck('mastery_level', 'kp_code')
+            ->toArray();
+
+        foreach ($kpCodes as $kpCode) {
+            $level = isset($levels[$kpCode]) ? (float) $levels[$kpCode] : 0.0;
+            if ($level < $threshold) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     /**
      * 计算掌握度等级
      */

+ 12 - 0
app/Models/TextbookChapterKnowledgeRelation.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class TextbookChapterKnowledgeRelation extends Model
+{
+    protected $table = 'textbook_chapter_knowledge_relation';
+
+    protected $guarded = [];
+}

+ 165 - 0
app/Services/DiagnosticChapterService.php

@@ -0,0 +1,165 @@
+<?php
+
+namespace App\Services;
+
+use Illuminate\Support\Facades\Log;
+use App\Models\KnowledgePoint;
+use App\Models\StudentKnowledgeMastery;
+use App\Models\TextbookCatalog;
+use App\Models\TextbookChapterKnowledgeRelation;
+
+class DiagnosticChapterService
+{
+
+    public function getInitialChapterKnowledgePoints(int $textbookId): array
+    {
+        $chapter = TextbookCatalog::query()
+            ->where('textbook_id', $textbookId)
+            ->where('node_type', 'chapter')
+            ->orderBy('display_no')
+            ->orderBy('sort_order')
+            ->orderBy('id')
+            ->first();
+
+        if (!$chapter) {
+            Log::warning('DiagnosticChapterService: 未找到章节节点', [
+                'textbook_id' => $textbookId,
+            ]);
+            return [];
+        }
+
+        $sectionIds = TextbookCatalog::query()
+            ->where('parent_id', $chapter->id)
+            ->where('node_type', 'section')
+            ->orderBy('display_no')
+            ->orderBy('sort_order')
+            ->orderBy('id')
+            ->pluck('id')
+            ->toArray();
+
+        if (empty($sectionIds)) {
+            Log::warning('DiagnosticChapterService: 章节下未找到section节点', [
+                'textbook_id' => $textbookId,
+                'chapter_id' => $chapter->id,
+            ]);
+            return [];
+        }
+
+        $kpCodes = TextbookChapterKnowledgeRelation::query()
+            ->whereIn('catalog_chapter_id', $sectionIds)
+            ->pluck('kp_code')
+            ->filter()
+            ->unique()
+            ->values()
+            ->toArray();
+
+        $kpCodes = $this->expandWithChildKnowledgePoints($kpCodes);
+
+        Log::info('DiagnosticChapterService: 获取首章知识点', [
+            'textbook_id' => $textbookId,
+            'chapter_id' => $chapter->id,
+            'section_count' => count($sectionIds),
+            'kp_count' => count($kpCodes),
+        ]);
+
+        return [
+            'chapter_id' => $chapter->id,
+            'section_ids' => $sectionIds,
+            'kp_codes' => $kpCodes,
+        ];
+    }
+
+    public function getFirstUnmasteredChapterKnowledgePoints(int $textbookId, int $studentId, float $threshold = 0.9): array
+    {
+        $chapters = TextbookCatalog::query()
+            ->where('textbook_id', $textbookId)
+            ->where('node_type', 'chapter')
+            ->orderBy('display_no')
+            ->orderBy('sort_order')
+            ->orderBy('id')
+            ->get();
+
+        if ($chapters->isEmpty()) {
+            Log::warning('DiagnosticChapterService: 未找到章节节点', [
+                'textbook_id' => $textbookId,
+            ]);
+            return [];
+        }
+
+        foreach ($chapters as $chapter) {
+            $sectionIds = TextbookCatalog::query()
+                ->where('parent_id', $chapter->id)
+                ->where('node_type', 'section')
+                ->orderBy('display_no')
+                ->orderBy('sort_order')
+                ->orderBy('id')
+                ->pluck('id')
+                ->toArray();
+
+            if (empty($sectionIds)) {
+                continue;
+            }
+
+            $kpCodes = TextbookChapterKnowledgeRelation::query()
+                ->whereIn('catalog_chapter_id', $sectionIds)
+                ->pluck('kp_code')
+                ->filter()
+                ->unique()
+                ->values()
+                ->toArray();
+
+            $kpCodes = $this->expandWithChildKnowledgePoints($kpCodes);
+
+            if (empty($kpCodes)) {
+                continue;
+            }
+
+            $allMastered = StudentKnowledgeMastery::allAtLeast($studentId, $kpCodes, $threshold);
+
+            Log::info('DiagnosticChapterService: 章节掌握度评估', [
+                'student_id' => $studentId,
+                'textbook_id' => $textbookId,
+                'chapter_id' => $chapter->id,
+                'section_count' => count($sectionIds),
+                'kp_count' => count($kpCodes),
+                'all_mastered' => $allMastered,
+                'threshold' => $threshold,
+            ]);
+
+            if (!$allMastered) {
+                return [
+                    'chapter_id' => $chapter->id,
+                    'section_ids' => $sectionIds,
+                    'kp_codes' => $kpCodes,
+                    'all_mastered' => $allMastered,
+                ];
+            }
+        }
+
+        Log::info('DiagnosticChapterService: 所有章节均达到掌握度阈值', [
+            'student_id' => $studentId,
+            'textbook_id' => $textbookId,
+            'threshold' => $threshold,
+        ]);
+
+        return [];
+    }
+
+    private function expandWithChildKnowledgePoints(array $kpCodes): array
+    {
+        if (empty($kpCodes)) {
+            return [];
+        }
+
+        $baseCodes = collect($kpCodes)->filter()->unique()->values()->all();
+        $children = KnowledgePoint::query()
+            ->whereIn('parent_kp_code', $baseCodes)
+            ->pluck('kp_code')
+            ->filter()
+            ->unique()
+            ->values()
+            ->toArray();
+
+        return array_values(array_unique(array_merge($baseCodes, $children)));
+    }
+}

+ 58 - 4
app/Services/ExamTypeStrategy.php

@@ -30,7 +30,7 @@ class ExamTypeStrategy
 
     /**
      * 根据组卷类型构建参数
-     * assembleType: 0-摸底, 1-智能组卷, 2-知识点组卷, 3-教材组卷, 4-通用, 5-错题本, 6-按知识点组卷
+     * assembleType: 0-摸底, 1-智能组卷, 2-知识点组卷, 3-教材组卷, 4-通用, 5-错题本, 9-原摸底
      */
     public function buildParams(array $baseParams, int $assembleType): array
     {
@@ -40,13 +40,13 @@ class ExamTypeStrategy
         ]);
 
         return match($assembleType) {
-            0 => $this->applyDifficultyDistribution($this->buildDiagnosticParams($baseParams)), // 摸底
+            0 => $this->applyDifficultyDistribution($this->buildInitialDiagnosticParams($baseParams)), // 摸底
             1 => $this->applyDifficultyDistribution($this->buildIntelligentAssembleParams($baseParams)), // 智能组卷
             2 => $this->applyDifficultyDistribution($this->buildKnowledgePointAssembleParams($baseParams)), // 知识点组卷
             3 => $this->applyDifficultyDistribution($this->buildTextbookAssembleParams($baseParams)), // 教材组卷
             4 => $this->applyDifficultyDistribution($this->buildGeneralParams($baseParams)), // 通用
             5 => $this->applyDifficultyDistribution($this->buildMistakeParams($baseParams)), // 追练
-            6 => $this->applyDifficultyDistribution($this->buildKnowledgePointsParams($baseParams)), // 按知识点组卷
+            9 => $this->applyDifficultyDistribution($this->buildDiagnosticParams($baseParams)), // 原摸底
             default => $this->applyDifficultyDistribution($this->buildGeneralParams($baseParams))
         };
     }
@@ -59,7 +59,7 @@ class ExamTypeStrategy
     {
         // 兼容旧版 exam_type 参数
         $assembleType = match($examType) {
-            'diagnostic' => 0,
+            'diagnostic' => 9,
             'general' => 4,
             'practice' => 5,
             'mistake' => 5,
@@ -264,6 +264,60 @@ class ExamTypeStrategy
         return $enhanced;
     }
 
+    /**
+     * 新摸底测试:使用教材首章知识点作为起点,复用知识点组卷逻辑
+     */
+    private function buildInitialDiagnosticParams(array $params): array
+    {
+//        Log::info('ExamTypeStrategy: 构建新摸底测试参数', $params);
+
+        $diagnosticService = app(DiagnosticChapterService::class);
+        $textbookId = $params['textbook_id'] ?? null;
+        $grade = $params['grade'] ?? null;
+        $totalQuestions = $params['total_questions'] ?? 20;
+
+        Log::info('ExamTypeStrategy: 新摸底使用textbook_id', [
+            'textbook_id' => $textbookId,
+        ]);
+
+        if (!$textbookId) {
+            Log::warning('ExamTypeStrategy: 新摸底测试需要 textbook_id 参数');
+            return $this->buildGeneralParams($params);
+        }
+
+        $studentId = (int) ($params['student_id'] ?? 0);
+        $initial = $diagnosticService->getFirstUnmasteredChapterKnowledgePoints((int) $textbookId, $studentId, 0.9);
+        if (empty($initial)) {
+            $initial = $diagnosticService->getInitialChapterKnowledgePoints((int) $textbookId);
+        }
+
+        if (empty($initial['kp_codes'])) {
+            Log::warning('ExamTypeStrategy: 新摸底未找到首章知识点', [
+                'textbook_id' => $textbookId,
+            ]);
+            return $this->buildGeneralParams($params);
+        }
+
+        $enhanced = array_merge($params, [
+            'kp_code_list' => $initial['kp_codes'],
+            'chapter_id_list' => $initial['section_ids'],
+            'diagnostic_chapter_id' => $initial['chapter_id'],
+            'textbook_id' => $textbookId,
+            'grade' => $grade,
+            'paper_name' => $params['paper_name'] ?? ('摸底测试_' . now()->format('Ymd_His')),
+        ]);
+
+        Log::info('ExamTypeStrategy: 新摸底参数构建完成', [
+            'textbook_id' => $textbookId,
+            'chapter_id' => $initial['chapter_id'] ?? null,
+            'section_count' => count($initial['section_ids'] ?? []),
+            'kp_count' => count($initial['kp_codes'] ?? []),
+            'total_questions' => $totalQuestions,
+        ]);
+
+        return $this->buildKnowledgePointAssembleParams($enhanced);
+    }
+
     /**
      * 追练 (assembleType=5)
      * 根据 paper_ids 获取卷子题目知识点列表,再按知识点组卷

+ 1 - 1
app/Services/LearningAnalyticsService.php

@@ -1097,7 +1097,7 @@ class LearningAnalyticsService
             // 优先从 student_knowledge_mastery 表读取(更完整的掌握度数据)
             $weaknesses = DB::table('student_knowledge_mastery as skm')
                 ->where('skm.student_id', $studentId)
-                ->where('skm.mastery_level', '<', 0.7) // 掌握度低于70%视为薄弱点
+                ->where('skm.mastery_level', '<', 0.9) // 掌握度低于70%视为薄弱点
                 ->orderBy('skm.mastery_level', 'asc')
                 ->limit($limit)
                 ->select([