Explorar o código

错题本出题

yemeishu hai 3 días
pai
achega
3b10829485

+ 156 - 0
app/Models/StudentKnowledgeMastery.php

@@ -0,0 +1,156 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+class StudentKnowledgeMastery extends Model
+{
+    use HasFactory;
+
+    protected $table = 'student_knowledge_mastery';
+
+    protected $fillable = [
+        'student_id',
+        'kp_code',
+        'mastery_level',
+        'confidence_level',
+        'total_attempts',
+        'correct_attempts',
+        'incorrect_attempts',
+        'partial_attempts',
+        'avg_time_seconds',
+        'fastest_time',
+        'slowest_time',
+        'attempts_easy',
+        'attempts_medium',
+        'attempts_hard',
+        'correct_easy',
+        'correct_medium',
+        'correct_hard',
+        'first_attempt_at',
+        'last_attempt_at',
+        'last_mastery_update',
+        'mastery_trend',
+        'mastery_change',
+        'calculation_version',
+        'notes',
+    ];
+
+    protected $casts = [
+        'mastery_level' => 'decimal:4',
+        'confidence_level' => 'decimal:4',
+        'mastery_change' => 'decimal:4',
+        'avg_time_seconds' => 'decimal:2',
+        'first_attempt_at' => 'datetime',
+        'last_attempt_at' => 'datetime',
+        'last_mastery_update' => 'datetime',
+        'created_at' => 'datetime',
+        'updated_at' => 'datetime',
+    ];
+
+    /**
+     * 关联学生
+     */
+    public function student(): BelongsTo
+    {
+        return $this->belongsTo(Student::class, 'student_id', 'student_id');
+    }
+
+    /**
+     * 关联知识点
+     */
+    public function knowledgePoint(): BelongsTo
+    {
+        return $this->belongsTo(KnowledgePoint::class, 'kp_code', 'kp_code');
+    }
+
+    /**
+     * 作用域:按学生筛选
+     */
+    public function scopeForStudent($query, string $studentId)
+    {
+        return $query->where('student_id', $studentId);
+    }
+
+    /**
+     * 作用域:按知识点筛选
+     */
+    public function scopeForKnowledgePoint($query, string $kpCode)
+    {
+        return $query->where('kp_code', $kpCode);
+    }
+
+    /**
+     * 作用域:薄弱点(掌握度低于阈值)
+     */
+    public function scopeWeaknesses($query, float $threshold = 0.7)
+    {
+        return $query->where('mastery_level', '<', $threshold);
+    }
+
+    /**
+     * 作用域:按掌握度排序
+     */
+    public function scopeOrderByMastery($query, string $direction = 'asc')
+    {
+        return $query->orderBy('mastery_level', $direction);
+    }
+
+    /**
+     * 获取薄弱点列表
+     */
+    public static function getWeaknesses(string $studentId, float $threshold = 0.7, int $limit = 20): array
+    {
+        return self::forStudent($studentId)
+            ->weaknesses($threshold)
+            ->orderByMastery('asc')
+            ->limit($limit)
+            ->get()
+            ->toArray();
+    }
+
+    /**
+     * 计算掌握度等级
+     */
+    public function getMasteryLevelAttribute($value): string
+    {
+        if ($value >= 0.85) {
+            return '优秀';
+        } elseif ($value >= 0.70) {
+            return '良好';
+        } elseif ($value >= 0.50) {
+            return '及格';
+        } else {
+            return '薄弱';
+        }
+    }
+
+    /**
+     * 获取趋势标签
+     */
+    public function getTrendLabelAttribute(): string
+    {
+        return match ($this->mastery_trend) {
+            'improving' => '上升',
+            'declining' => '下降',
+            'stable' => '稳定',
+            'insufficient' => '数据不足',
+            default => '未知',
+        };
+    }
+
+    /**
+     * 计算成功率
+     */
+    public function getSuccessRateAttribute(): float
+    {
+        if ($this->total_attempts <= 0) {
+            return 0.0;
+        }
+
+        return round(($this->correct_attempts / $this->total_attempts) * 100, 2);
+    }
+}

+ 392 - 0
app/Services/ExamTypeStrategy.php

@@ -0,0 +1,392 @@
+<?php
+
+namespace App\Services;
+
+use Illuminate\Support\Facades\Log;
+use App\Models\StudentKnowledgeMastery;
+use App\Models\MistakeRecord;
+use App\Models\KnowledgePoint;
+
+class ExamTypeStrategy
+{
+    protected QuestionExpansionService $questionExpansionService;
+
+    public function __construct(QuestionExpansionService $questionExpansionService)
+    {
+        $this->questionExpansionService = $questionExpansionService;
+    }
+
+    /**
+     * 根据组卷类型构建参数
+     */
+    public function buildParams(array $baseParams, string $examType): array
+    {
+        Log::info('ExamTypeStrategy: 构建组卷参数', [
+            'exam_type' => $examType,
+            'base_params_keys' => array_keys($baseParams)
+        ]);
+
+        return match($examType) {
+            'diagnostic' => $this->buildDiagnosticParams($baseParams),
+            'practice' => $this->buildPracticeParams($baseParams),
+            'mistake' => $this->buildMistakeParams($baseParams),
+            'textbook' => $this->buildTextbookParams($baseParams),
+            'knowledge' => $this->buildKnowledgeParams($baseParams),
+            default => $this->buildGeneralParams($baseParams)
+        };
+    }
+
+    /**
+     * 通用智能出卷(原有行为)
+     */
+    private function buildGeneralParams(array $params): array
+    {
+        Log::info('ExamTypeStrategy: 通用智能出卷参数', $params);
+
+        // 返回原始参数,不做特殊处理
+        return $params;
+    }
+
+    /**
+     * 摸底测试:评估当前水平
+     */
+    private function buildDiagnosticParams(array $params): array
+    {
+        Log::info('ExamTypeStrategy: 构建摸底测试参数', $params);
+
+        // 摸底测试:平衡所有难度,覆盖多个知识点
+        $enhanced = array_merge($params, [
+            // 难度配比:相对平衡,基础题稍多
+            'difficulty_ratio' => [
+                '基础' => 40,
+                '中等' => 40,
+                '拔高' => 20,
+            ],
+            // 题型配比:选择题多一些,便于快速评估
+            'question_type_ratio' => [
+                '选择题' => 50,
+                '填空题' => 25,
+                '解答题' => 25,
+            ],
+            // 确保覆盖多个知识点
+            'kp_codes' => $this->expandKpCodesForDiagnostic($params['kp_codes'] ?? []),
+            // 摸底测试名称
+            'paper_name' => $params['paper_name'] ?? ('摸底测试_' . now()->format('Ymd_His')),
+        ]);
+
+        Log::info('ExamTypeStrategy: 摸底测试参数构建完成', [
+            'difficulty_ratio' => $enhanced['difficulty_ratio'],
+            'question_type_ratio' => $enhanced['question_type_ratio'],
+            'kp_codes_count' => count($enhanced['kp_codes'])
+        ]);
+
+        return $enhanced;
+    }
+
+    /**
+     * 错题:针对薄弱点强化
+     * 使用 QuestionExpansionService 按优先级扩展题目
+     */
+    private function buildMistakeParams(array $params): array
+    {
+        Log::info('ExamTypeStrategy: 构建错题参数', $params);
+
+        $studentId = $params['student_id'] ?? null;
+        $totalQuestions = $params['total_questions'] ?? 20;
+        $mistakeOptions = $params['mistake_options'] ?? [];
+        $weaknessThreshold = $mistakeOptions['weakness_threshold'] ?? 0.7;
+        $intensity = $mistakeOptions['intensity'] ?? 'medium';
+        $focusWeaknesses = $mistakeOptions['focus_weaknesses'] ?? true;
+
+        // 根据强度调整难度配比
+        $difficultyRatio = match($intensity) {
+            'low' => [
+                '基础' => 60,
+                '中等' => 35,
+                '拔高' => 5,
+            ],
+            'medium' => [
+                '基础' => 45,
+                '中等' => 40,
+                '拔高' => 15,
+            ],
+            'high' => [
+                '基础' => 30,
+                '中等' => 45,
+                '拔高' => 25,
+            ],
+            default => [
+                '基础' => 45,
+                '中等' => 40,
+                '拔高' => 15,
+            ]
+        };
+
+        // 获取学生薄弱点
+        $weaknessFilter = [];
+        if ($studentId && $focusWeaknesses) {
+            $weaknessFilter = $this->getStudentWeaknesses($studentId, $weaknessThreshold);
+            Log::info('ExamTypeStrategy: 获取到学生薄弱点', [
+                'student_id' => $studentId,
+                'weakness_threshold' => $weaknessThreshold,
+                'weakness_count' => count($weaknessFilter)
+            ]);
+        }
+
+        // 使用 QuestionExpansionService 按优先级扩展题目
+        $questionStrategy = $this->questionExpansionService->expandQuestions(
+            $params,
+            $studentId,
+            $weaknessFilter,
+            $totalQuestions
+        );
+
+        // 获取错题知识点
+        $mistakeKnowledgePoints = [];
+        if ($studentId && !empty($questionStrategy['mistake_question_ids'])) {
+            $mistakeRecords = MistakeRecord::forStudent($studentId)
+                ->whereIn('question_id', $questionStrategy['mistake_question_ids'])
+                ->select(['knowledge_point'])
+                ->get();
+            $mistakeKnowledgePoints = array_unique(array_filter($mistakeRecords->pluck('knowledge_point')->toArray()));
+            Log::info('ExamTypeStrategy: 获取错题知识点', [
+                'knowledge_points' => $mistakeKnowledgePoints
+            ]);
+        }
+
+        // 获取扩展统计
+        $expansionStats = $this->questionExpansionService->getExpansionStats($questionStrategy);
+
+        $enhanced = array_merge($params, [
+            'difficulty_ratio' => $difficultyRatio,
+            'mistake_ids' => $questionStrategy['mistake_ids'],
+            'mistake_question_ids' => $questionStrategy['mistake_question_ids'],
+            // 错题回顾的知识点优先级
+            'priority_knowledge_points' => array_merge(
+                array_values($mistakeKnowledgePoints), // 错题知识点优先
+                array_column($weaknessFilter, 'kp_code') // 然后是薄弱点
+            ),
+            // 错题回顾更注重针对性
+            'question_type_ratio' => [
+                '选择题' => 35,
+                '填空题' => 30,
+                '解答题' => 35,
+            ],
+            'paper_name' => $params['paper_name'] ?? ('错题_' . now()->format('Ymd_His')),
+            // 标记这是错题,用于后续处理
+            'is_mistake_exam' => true,
+            'weakness_filter' => $weaknessFilter,
+            // 题目扩展统计
+            'question_expansion_stats' => $expansionStats
+        ]);
+
+        Log::info('ExamTypeStrategy: 错题参数构建完成', [
+            'intensity' => $intensity,
+            'total_questions_needed' => $totalQuestions,
+            'mistake_question_ids_count' => count($enhanced['mistake_question_ids']),
+            'priority_knowledge_points_count' => count($enhanced['priority_knowledge_points']),
+            'question_expansion_stats' => $enhanced['question_expansion_stats'],
+            'weakness_count' => count($weaknessFilter)
+        ]);
+
+        return $enhanced;
+    }
+
+    /**
+     * 专项练习:针对特定技能或知识点练习
+     */
+    private function buildPracticeParams(array $params): array
+    {
+        Log::info('ExamTypeStrategy: 构建专项练习参数', $params);
+
+        $studentId = $params['student_id'] ?? null;
+        $practiceOptions = $params['practice_options'] ?? [];
+        $weaknessThreshold = $practiceOptions['weakness_threshold'] ?? 0.7;
+        $intensity = $practiceOptions['intensity'] ?? 'medium';
+        $focusWeaknesses = $practiceOptions['focus_weaknesses'] ?? true;
+
+        // 根据强度调整难度配比
+        $difficultyRatio = match($intensity) {
+            'low' => [
+                '基础' => 60,
+                '中等' => 35,
+                '拔高' => 5,
+            ],
+            'medium' => [
+                '基础' => 45,
+                '中等' => 40,
+                '拔高' => 15,
+            ],
+            'high' => [
+                '基础' => 30,
+                '中等' => 45,
+                '拔高' => 25,
+            ],
+            default => [
+                '基础' => 45,
+                '中等' => 40,
+                '拔高' => 15,
+            ]
+        };
+
+        // 获取学生薄弱点
+        $weaknessFilter = [];
+        if ($studentId && $focusWeaknesses) {
+            $weaknessFilter = $this->getStudentWeaknesses($studentId, $weaknessThreshold);
+            Log::info('ExamTypeStrategy: 获取到学生薄弱点', [
+                'student_id' => $studentId,
+                'weakness_threshold' => $weaknessThreshold,
+                'weakness_count' => count($weaknessFilter)
+            ]);
+        }
+
+        // 优先使用薄弱点知识点,如果没有则使用用户选择的知识点
+        $kpCodes = $params['kp_codes'] ?? [];
+        if ($studentId && empty($kpCodes) && !empty($weaknessFilter)) {
+            $kpCodes = array_column($weaknessFilter, 'kp_code');
+            Log::info('ExamTypeStrategy: 使用薄弱点作为知识点', [
+                'kp_codes' => $kpCodes
+            ]);
+        }
+
+        $enhanced = array_merge($params, [
+            'difficulty_ratio' => $difficultyRatio,
+            'kp_codes' => $kpCodes,
+            // 专项练习更注重题型覆盖
+            'question_type_ratio' => [
+                '选择题' => 40,
+                '填空题' => 30,
+                '解答题' => 30,
+            ],
+            'paper_name' => $params['paper_name'] ?? ('专项练习_' . now()->format('Ymd_His')),
+            // 标记这是专项练习,用于后续处理
+            'is_practice_exam' => true,
+            'weakness_filter' => $weaknessFilter,
+        ]);
+
+        Log::info('ExamTypeStrategy: 专项练习参数构建完成', [
+            'intensity' => $intensity,
+            'difficulty_ratio' => $enhanced['difficulty_ratio'],
+            'kp_codes_count' => count($enhanced['kp_codes']),
+            'weakness_count' => count($weaknessFilter)
+        ]);
+
+        return $enhanced;
+    }
+
+    /**
+     * 教材同步:按教材章节出题
+     */
+    private function buildTextbookParams(array $params): array
+    {
+        Log::info('ExamTypeStrategy: 构建教材同步参数', $params);
+
+        // 教材同步:按章节顺序,难度递增
+        $textbookOptions = $params['textbook_options'] ?? [];
+
+        $enhanced = array_merge($params, [
+            // 教材同步:基础和中等题为主
+            'difficulty_ratio' => [
+                '基础' => 50,
+                '中等' => 40,
+                '拔高' => 10,
+            ],
+            'question_type_ratio' => [
+                '选择题' => 40,
+                '填空题' => 30,
+                '解答题' => 30,
+            ],
+            'paper_name' => $params['paper_name'] ?? ('教材同步_' . now()->format('Ymd_His')),
+            'textbook_options' => $textbookOptions,
+        ]);
+
+        return $enhanced;
+    }
+
+    /**
+     * 知识点专练:单个或少量知识点深练
+     */
+    private function buildKnowledgeParams(array $params): array
+    {
+        Log::info('ExamTypeStrategy: 构建知识点专练参数', $params);
+
+        // 知识点专练:深度挖掘,多角度考查
+        $knowledgeOptions = $params['knowledge_options'] ?? [];
+
+        $enhanced = array_merge($params, [
+            // 知识点专练:难度分布更均匀
+            'difficulty_ratio' => [
+                '基础' => 35,
+                '中等' => 45,
+                '拔高' => 20,
+            ],
+            'question_type_ratio' => [
+                '选择题' => 30,
+                '填空题' => 35,
+                '解答题' => 35,
+            ],
+            'paper_name' => $params['paper_name'] ?? ('知识点专练_' . now()->format('Ymd_His')),
+            'knowledge_options' => $knowledgeOptions,
+        ]);
+
+        return $enhanced;
+    }
+
+    /**
+     * 为摸底测试扩展知识点(确保覆盖全面)
+     */
+    private function expandKpCodesForDiagnostic(array $kpCodes): array
+    {
+        if (!empty($kpCodes)) {
+            return $kpCodes;
+        }
+
+        // 如果没有指定知识点,返回一些通用的数学知识点
+        return [
+            '一元二次方程',
+            '二次函数',
+            '旋转',
+            '圆',
+            '概率初步',
+        ];
+    }
+
+    /**
+     * 获取学生薄弱点
+     */
+    private function getStudentWeaknesses(string $studentId, float $threshold): array
+    {
+        try {
+            // 使用 StudentKnowledgeMastery 模型获取掌握度低于阈值的知识点
+            $weaknessRecords = StudentKnowledgeMastery::forStudent($studentId)
+                ->weaknesses($threshold)
+                ->orderByMastery('asc')
+                ->limit(20)
+                ->with('knowledgePoint') // 预加载知识点信息
+                ->get();
+
+            // 转换为统一格式
+            return $weaknessRecords->map(function ($record) {
+                return [
+                    'kp_code' => $record->kp_code,
+                    'kp_name' => $record->knowledgePoint->name ?? $record->kp_code,
+                    'mastery' => (float) ($record->mastery_level ?? 0),
+                    'attempts' => (int) ($record->total_attempts ?? 0),
+                    'correct' => (int) ($record->correct_attempts ?? 0),
+                    'incorrect' => (int) ($record->incorrect_attempts ?? 0),
+                    'confidence' => (float) ($record->confidence_level ?? 0),
+                    'trend' => $record->mastery_trend ?? 'stable',
+                ];
+            })->toArray();
+
+        } catch (\Exception $e) {
+            Log::error('ExamTypeStrategy: 获取学生薄弱点失败', [
+                'student_id' => $studentId,
+                'threshold' => $threshold,
+                'error' => $e->getMessage()
+            ]);
+
+            return [];
+        }
+    }
+}

+ 311 - 0
app/Services/QuestionExpansionService.php

@@ -0,0 +1,311 @@
+<?php
+
+namespace App\Services;
+
+use Illuminate\Support\Facades\Log;
+use App\Models\StudentKnowledgeMastery;
+use App\Models\MistakeRecord;
+use App\Models\Question;
+use App\Models\KnowledgePoint;
+
+class QuestionExpansionService
+{
+    /**
+     * 按优先级扩展题目数量
+     * 优先级策略:
+     * 1. 优先使用错题记录的题目
+     * 2. 错题相同的知识点的其他题目
+     * 3. 相同知识点的子知识点的题目
+     * 4. 学生薄弱知识点相关题目
+     * 5. 薄弱知识点的子知识点的题目
+     */
+    public function expandQuestions(array $baseParams, ?string $studentId, array $weaknessFilter, int $totalQuestions): array
+    {
+        Log::info('QuestionExpansionService: 开始题目扩展', [
+            'student_id' => $studentId,
+            'total_needed' => $totalQuestions,
+            'weakness_count' => count($weaknessFilter)
+        ]);
+
+        $strategy = [
+            'mistake_ids' => $baseParams['mistake_ids'] ?? [],
+            'mistake_question_ids' => $baseParams['mistake_question_ids'] ?? [],
+            'step2_count' => 0,
+            'step3_count' => 0,
+            'step4_count' => 0,
+            'step5_count' => 0,
+            'expansion_details' => []
+        ];
+
+        $includeMistakes = ($baseParams['mistake_options']['review_mistakes'] ?? true) && $studentId;
+
+        if (!$includeMistakes) {
+            Log::info('QuestionExpansionService: 未开启错题回顾,跳过扩展');
+            return $strategy;
+        }
+
+        // Step 1: 获取学生历史错题(优先级:最高)
+        // 直接使用学生历史错题,学习价值最高
+        if (empty($strategy['mistake_question_ids'])) {
+            $studentMistakes = $this->getStudentMistakes($studentId);
+            $strategy['mistake_question_ids'] = array_values(array_unique(array_column($studentMistakes, 'question_id')));
+            $strategy['expansion_details']['step1_mistakes'] = count($strategy['mistake_question_ids']);
+            Log::info('QuestionExpansionService: Step1 - 获取历史错题', [
+                'count' => count($strategy['mistake_question_ids']),
+                'mistake_ids' => $strategy['mistake_question_ids']
+            ]);
+        }
+
+        // 如果错题数量已满足需求,直接返回
+        if (count($strategy['mistake_question_ids']) >= $totalQuestions) {
+            Log::info('QuestionExpansionService: Step1 - 错题数量已满足需求', [
+                'total_needed' => $totalQuestions,
+                'mistake_count' => count($strategy['mistake_question_ids'])
+            ]);
+            return $strategy;
+        }
+
+        // Step 2: 错题相同的知识点的其他题目(优先级:高)
+        // 强化特定知识点的掌握,题目类型多样化
+        $remaining = $totalQuestions - count($strategy['mistake_question_ids']);
+        $mistakeKps = $this->getMistakeKnowledgePoints($studentId, $strategy['mistake_question_ids']);
+        if (!empty($mistakeKps) && $remaining > 0) {
+            $step2Questions = $this->getQuestionsByKnowledgePoints($mistakeKps, $strategy['mistake_question_ids'], $remaining);
+            $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step2Questions);
+            $strategy['step2_count'] = count($step2Questions);
+            $strategy['expansion_details']['step2_same_kp'] = [
+                'knowledge_points' => $mistakeKps,
+                'added_count' => count($step2Questions)
+            ];
+            Log::info('QuestionExpansionService: Step2 - 获取同知识点其他题目', [
+                'knowledge_points' => $mistakeKps,
+                'added_count' => count($step2Questions),
+                'total_count' => count($strategy['mistake_question_ids'])
+            ]);
+        }
+
+        // 如果数量仍不足,继续扩展
+        $remaining = $totalQuestions - count($strategy['mistake_question_ids']);
+        if ($remaining <= 0) {
+            return $strategy;
+        }
+
+        // Step 3: 相同知识点的子知识点的题目(优先级:中高)
+        // 深入知识点的细分类别,实现精准练习
+        if (!empty($mistakeKps) && $remaining > 0) {
+            $childKps = $this->getChildKnowledgePoints($mistakeKps);
+            if (!empty($childKps)) {
+                $step3Questions = $this->getQuestionsByKnowledgePoints($childKps, $strategy['mistake_question_ids'], $remaining);
+                $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step3Questions);
+                $strategy['step3_count'] = count($step3Questions);
+                $strategy['expansion_details']['step3_child_kp'] = [
+                    'child_knowledge_points' => $childKps,
+                    'added_count' => count($step3Questions)
+                ];
+                Log::info('QuestionExpansionService: Step3 - 获取子知识点题目', [
+                    'child_knowledge_points' => $childKps,
+                    'added_count' => count($step3Questions),
+                    'total_count' => count($strategy['mistake_question_ids'])
+                ]);
+            }
+        }
+
+        // 如果数量仍不足,继续扩展
+        $remaining = $totalQuestions - count($strategy['mistake_question_ids']);
+        if ($remaining <= 0) {
+            return $strategy;
+        }
+
+        // Step 4: 学生薄弱知识点相关题目(优先级:中)
+        // 针对性强化薄弱环节,提升整体掌握度
+        if (!empty($weaknessFilter) && $remaining > 0) {
+            $weaknessKps = array_column($weaknessFilter, 'kp_code');
+            $step4Questions = $this->getQuestionsByKnowledgePoints($weaknessKps, $strategy['mistake_question_ids'], $remaining);
+            $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step4Questions);
+            $strategy['step4_count'] = count($step4Questions);
+            $strategy['expansion_details']['step4_weakness'] = [
+                'weakness_knowledge_points' => $weaknessKps,
+                'added_count' => count($step4Questions)
+            ];
+            Log::info('QuestionExpansionService: Step4 - 获取薄弱知识点题目', [
+                'weakness_knowledge_points' => $weaknessKps,
+                'added_count' => count($step4Questions),
+                'total_count' => count($strategy['mistake_question_ids'])
+            ]);
+        }
+
+        // 如果数量仍不足,继续扩展
+        $remaining = $totalQuestions - count($strategy['mistake_question_ids']);
+        if ($remaining <= 0) {
+            return $strategy;
+        }
+
+        // Step 5: 薄弱知识点的子知识点的题目(优先级:低)
+        // 全面覆盖薄弱环节的各个细分领域
+        if (!empty($weaknessFilter) && $remaining > 0) {
+            $weaknessKps = array_column($weaknessFilter, 'kp_code');
+            $childWeaknessKps = $this->getChildKnowledgePoints($weaknessKps);
+            if (!empty($childWeaknessKps)) {
+                $step5Questions = $this->getQuestionsByKnowledgePoints($childWeaknessKps, $strategy['mistake_question_ids'], $remaining);
+                $strategy['mistake_question_ids'] = array_merge($strategy['mistake_question_ids'], $step5Questions);
+                $strategy['step5_count'] = count($step5Questions);
+                $strategy['expansion_details']['step5_child_weakness'] = [
+                    'child_weakness_knowledge_points' => $childWeaknessKps,
+                    'added_count' => count($step5Questions)
+                ];
+                Log::info('QuestionExpansionService: Step5 - 获取薄弱点子知识点题目', [
+                    'child_weakness_knowledge_points' => $childWeaknessKps,
+                    'added_count' => count($step5Questions),
+                    'total_count' => count($strategy['mistake_question_ids'])
+                ]);
+            }
+        }
+
+        Log::info('QuestionExpansionService: 题目扩展完成', [
+            'total_final_count' => count($strategy['mistake_question_ids']),
+            'expansion_details' => $strategy['expansion_details']
+        ]);
+
+        return $strategy;
+    }
+
+    /**
+     * 获取学生错题(去重,按question_id分组)
+     */
+    private function getStudentMistakes(string $studentId): array
+    {
+        try {
+            // 使用 MistakeRecord 模型获取学生的错题(不限制数量,获取全部)
+            // 按 question_id 分组去重,保留第一条记录
+            $mistakeRecords = MistakeRecord::forStudent($studentId)
+                ->where('review_status', '!=', MistakeRecord::REVIEW_STATUS_MASTERED) // 排除已掌握的错题
+                ->select(['question_id', 'knowledge_point', 'error_type', 'difficulty'])
+                ->get()
+                ->unique('question_id') // 按 question_id 去重
+                ->values(); // 重新索引
+
+            Log::info('QuestionExpansionService: 获取学生错题(去重后)', [
+                'student_id' => $studentId,
+                'total_records' => MistakeRecord::forStudent($studentId)
+                    ->where('review_status', '!=', MistakeRecord::REVIEW_STATUS_MASTERED)->count(),
+                'unique_count' => $mistakeRecords->count()
+            ]);
+
+            return $mistakeRecords->map(function ($record) {
+                return [
+                    'question_id' => $record->question_id,
+                    'kp_code' => $record->knowledge_point,
+                    'error_type' => $record->error_type,
+                    'difficulty' => $record->difficulty,
+                ];
+            })->toArray();
+
+        } catch (\Exception $e) {
+            Log::error('QuestionExpansionService: 获取学生错题失败', [
+                'student_id' => $studentId,
+                'error' => $e->getMessage()
+            ]);
+
+            return [];
+        }
+    }
+
+    /**
+     * 获取错题对应的知识点
+     */
+    private function getMistakeKnowledgePoints(string $studentId, array $questionIds): array
+    {
+        $mistakeRecords = MistakeRecord::forStudent($studentId)
+            ->whereIn('question_id', $questionIds)
+            ->whereNotNull('knowledge_point')
+            ->select(['knowledge_point'])
+            ->get();
+
+        return array_values(array_unique(array_filter($mistakeRecords->pluck('knowledge_point')->toArray())));
+    }
+
+    /**
+     * 根据知识点获取题目
+     */
+    private function getQuestionsByKnowledgePoints(array $kpCodes, array $excludeQuestionIds, int $limit): array
+    {
+        if (empty($kpCodes)) {
+            return [];
+        }
+
+        try {
+            $questions = Question::whereIn('kp_code', $kpCodes)
+                ->whereNotIn('id', $excludeQuestionIds)
+                ->select(['id'])
+                ->limit($limit)
+                ->get();
+
+            return $questions->pluck('id')->toArray();
+
+        } catch (\Exception $e) {
+            Log::warning('QuestionExpansionService: 获取题目失败', [
+                'kp_codes' => $kpCodes,
+                'limit' => $limit,
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 获取子知识点
+     */
+    private function getChildKnowledgePoints(array $parentKpCodes): array
+    {
+        try {
+            $childKps = KnowledgePoint::whereIn('parent_kp_code', $parentKpCodes)
+                ->select(['kp_code'])
+                ->get();
+
+            return $childKps->pluck('kp_code')->toArray();
+
+        } catch (\Exception $e) {
+            Log::warning('QuestionExpansionService: 获取子知识点失败', [
+                'parent_kp_codes' => $parentKpCodes,
+                'error' => $e->getMessage()
+            ]);
+            return [];
+        }
+    }
+
+    /**
+     * 获取扩展统计信息
+     */
+    public function getExpansionStats(array $strategy): array
+    {
+        return [
+            'total_questions' => count($strategy['mistake_question_ids'] ?? []),
+            'step1_mistakes' => count($strategy['mistake_question_ids'] ?? []),
+            'step2_same_kp' => $strategy['step2_count'] ?? 0,
+            'step3_child_kp' => $strategy['step3_count'] ?? 0,
+            'step4_weakness' => $strategy['step4_count'] ?? 0,
+            'step5_child_weakness' => $strategy['step5_count'] ?? 0,
+            'expansion_rate' => $this->calculateExpansionRate($strategy),
+            'details' => $strategy['expansion_details'] ?? []
+        ];
+    }
+
+    /**
+     * 计算扩展率
+     */
+    private function calculateExpansionRate(array $strategy): float
+    {
+        $totalExpanded = ($strategy['step2_count'] ?? 0) +
+                        ($strategy['step3_count'] ?? 0) +
+                        ($strategy['step4_count'] ?? 0) +
+                        ($strategy['step5_count'] ?? 0);
+
+        $totalQuestions = count($strategy['mistake_question_ids'] ?? []);
+
+        if ($totalQuestions === 0) {
+            return 0.0;
+        }
+
+        return round(($totalExpanded / $totalQuestions) * 100, 2);
+    }
+}