|
|
@@ -45,7 +45,7 @@ class ExamTypeStrategy
|
|
|
2 => $this->applyDifficultyDistribution($this->buildKnowledgePointAssembleParams($baseParams)), // 知识点组卷
|
|
|
3 => $this->applyDifficultyDistribution($this->buildTextbookAssembleParams($baseParams)), // 教材组卷
|
|
|
4 => $this->applyDifficultyDistribution($this->buildGeneralParams($baseParams)), // 通用
|
|
|
- 5 => $this->buildMistakeParams($baseParams), // 错题本不应用难度分布
|
|
|
+ 5 => $this->applyDifficultyDistribution($this->buildMistakeParams($baseParams)), // 追练
|
|
|
6 => $this->applyDifficultyDistribution($this->buildKnowledgePointsParams($baseParams)), // 按知识点组卷
|
|
|
default => $this->applyDifficultyDistribution($this->buildGeneralParams($baseParams))
|
|
|
};
|
|
|
@@ -93,17 +93,8 @@ class ExamTypeStrategy
|
|
|
*/
|
|
|
private function applyDifficultyDistribution(array $params, bool $forceApply = false): array
|
|
|
{
|
|
|
- // 检查是否为排除类型(错题本 assembleType=5)
|
|
|
+ // 统一应用难度分布(错题本类型也允许应用)
|
|
|
$assembleType = (int) ($params['assemble_type'] ?? 4);
|
|
|
- $isExcludedType = ($assembleType === 5); // 只有错题本类型不应用难度分布
|
|
|
-
|
|
|
- // 如果不是强制应用且为排除类型,则不应用难度分布
|
|
|
- if (!$forceApply && $isExcludedType) {
|
|
|
- Log::info('ExamTypeStrategy: 跳过难度分布(错题本类型)', [
|
|
|
- 'assemble_type' => $assembleType
|
|
|
- ]);
|
|
|
- return $params;
|
|
|
- }
|
|
|
|
|
|
$difficultyCategory = (int) ($params['difficulty_category'] ?? 1);
|
|
|
$totalQuestions = (int) ($params['total_questions'] ?? 20);
|
|
|
@@ -274,90 +265,110 @@ class ExamTypeStrategy
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 错题本 (assembleType=5)
|
|
|
- * 根据 paper_ids 数组查询卷子中的错题,组合成新卷子
|
|
|
+ * 追练 (assembleType=5)
|
|
|
+ * 根据 paper_ids 获取卷子题目知识点列表,再按知识点组卷
|
|
|
* 不需要 total_questions 参数
|
|
|
*/
|
|
|
private function buildMistakeParams(array $params): array
|
|
|
{
|
|
|
- Log::info('ExamTypeStrategy: 构建错题本参数', $params);
|
|
|
+ Log::info('ExamTypeStrategy: 构建追练参数', $params);
|
|
|
|
|
|
$paperIds = $params['paper_ids'] ?? [];
|
|
|
$studentId = $params['student_id'] ?? null;
|
|
|
|
|
|
// 检查是否有 paper_ids 参数
|
|
|
if (empty($paperIds)) {
|
|
|
- Log::warning('ExamTypeStrategy: 错题本需要 paper_ids 参数');
|
|
|
+ Log::warning('ExamTypeStrategy: 追练需要 paper_ids 参数');
|
|
|
return $this->buildGeneralParams($params);
|
|
|
}
|
|
|
|
|
|
- Log::info('ExamTypeStrategy: 错题本组卷', [
|
|
|
+ Log::info('ExamTypeStrategy: 追练组卷', [
|
|
|
'paper_ids' => $paperIds,
|
|
|
'student_id' => $studentId,
|
|
|
'paper_count' => count($paperIds)
|
|
|
]);
|
|
|
|
|
|
- // 通过 paper_ids 查询卷子中的错题
|
|
|
- $mistakeQuestionIds = $this->getMistakeQuestionsFromPapers($paperIds, $studentId);
|
|
|
+ // 通过 paper_ids 查询卷子中的全部题目
|
|
|
+ $paperQuestionIds = $this->getQuestionIdsFromPapers($paperIds);
|
|
|
|
|
|
- if (empty($mistakeQuestionIds)) {
|
|
|
- Log::warning('ExamTypeStrategy: 未找到错题', [
|
|
|
+ if (empty($paperQuestionIds)) {
|
|
|
+ Log::warning('ExamTypeStrategy: 卷子题目为空,无法生成追练', [
|
|
|
'paper_ids' => $paperIds
|
|
|
]);
|
|
|
return $this->buildGeneralParams($params);
|
|
|
}
|
|
|
|
|
|
- Log::info('ExamTypeStrategy: 获取到错题', [
|
|
|
+ Log::info('ExamTypeStrategy: 获取到卷子题目', [
|
|
|
'paper_count' => count($paperIds),
|
|
|
- 'mistake_count' => count($mistakeQuestionIds),
|
|
|
- 'mistake_question_ids' => array_slice($mistakeQuestionIds, 0, 10) // 只记录前10个
|
|
|
+ 'question_count' => count($paperQuestionIds),
|
|
|
+ 'question_ids' => array_slice($paperQuestionIds, 0, 10) // 只记录前10个
|
|
|
]);
|
|
|
|
|
|
- // 获取错题知识点
|
|
|
- $mistakeKnowledgePoints = $this->getKnowledgePointsFromQuestions($mistakeQuestionIds);
|
|
|
+ // 获取卷子题目知识点(直接从 paper_questions.knowledge_point)
|
|
|
+ $paperKnowledgePoints = $this->getKnowledgePointsFromPaperQuestions($paperIds);
|
|
|
|
|
|
- // 组装增强参数
|
|
|
- $mistakeCount = count($mistakeQuestionIds);
|
|
|
+ if (empty($paperKnowledgePoints)) {
|
|
|
+ Log::warning('ExamTypeStrategy: 卷子题目未找到知识点,无法生成错题本', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'question_count' => count($paperQuestionIds)
|
|
|
+ ]);
|
|
|
+ return $this->buildGeneralParams($params);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 取卷子中最小难度等级、最大题量与最高总分作为默认
|
|
|
+ $paperStats = $this->getPaperAggregateStats($paperIds);
|
|
|
+ $difficultyCategory = $paperStats['difficulty_category_min'] ?? ($params['difficulty_category'] ?? 1);
|
|
|
+ $maxTotalQuestions = $paperStats['total_questions_max'] ?? null;
|
|
|
+ $maxTotalScore = $paperStats['total_score_max'] ?? null;
|
|
|
+
|
|
|
+ // 组装增强参数(复用知识点组卷逻辑)
|
|
|
+ $questionCount = count($paperQuestionIds);
|
|
|
$maxQuestions = 50; // 错题本最大题目数限制
|
|
|
+ $targetQuestions = (int) ($maxTotalQuestions ?? $questionCount);
|
|
|
+ $targetQuestions = min($targetQuestions, $maxQuestions);
|
|
|
|
|
|
- // 如果错题超过最大值,截取到最大值
|
|
|
- if ($mistakeCount > $maxQuestions) {
|
|
|
+ // 如果题量超过最大值,按上限截取
|
|
|
+ if ($questionCount > $maxQuestions) {
|
|
|
Log::warning('ExamTypeStrategy: 错题数量超过最大值限制,已截取', [
|
|
|
- 'mistake_count' => $mistakeCount,
|
|
|
+ 'question_count' => $questionCount,
|
|
|
'max_limit' => $maxQuestions,
|
|
|
'truncated_count' => $maxQuestions
|
|
|
]);
|
|
|
- $mistakeQuestionIds = array_slice($mistakeQuestionIds, 0, $maxQuestions);
|
|
|
- $mistakeCount = $maxQuestions;
|
|
|
+ $questionCount = $maxQuestions;
|
|
|
}
|
|
|
|
|
|
$enhanced = array_merge($params, [
|
|
|
- 'mistake_question_ids' => $mistakeQuestionIds,
|
|
|
+ 'kp_code_list' => array_values($paperKnowledgePoints),
|
|
|
'paper_ids' => $paperIds,
|
|
|
- 'priority_knowledge_points' => array_values($mistakeKnowledgePoints),
|
|
|
- 'paper_name' => $params['paper_name'] ?? ('错题本_' . now()->format('Ymd_His')),
|
|
|
- 'total_questions' => $mistakeCount, // 错题本题目数量由实际错题数量决定
|
|
|
- // 错题本:保持原有题型配比
|
|
|
- 'question_type_ratio' => [
|
|
|
- '选择题' => 35,
|
|
|
- '填空题' => 30,
|
|
|
- '解答题' => 35,
|
|
|
- ],
|
|
|
- // 错题本不应用难度分布
|
|
|
+ 'paper_name' => $params['paper_name'] ?? ('追练_' . now()->format('Ymd_His')),
|
|
|
+ 'total_questions' => $targetQuestions, // 题目数量由卷子题目规模/参数决定
|
|
|
+ 'total_score' => $maxTotalScore ?? ($params['total_score'] ?? null),
|
|
|
+ 'difficulty_category' => $difficultyCategory,
|
|
|
'is_mistake_exam' => true,
|
|
|
'is_paper_based_mistake' => true, // 标记是基于卷子的错题本
|
|
|
- 'mistake_count' => $mistakeCount,
|
|
|
- 'knowledge_points_count' => count($mistakeKnowledgePoints),
|
|
|
+ 'source_paper_question_count' => $questionCount,
|
|
|
+ 'knowledge_points_count' => count($paperKnowledgePoints),
|
|
|
'max_questions_limit' => $maxQuestions,
|
|
|
]);
|
|
|
|
|
|
- Log::info('ExamTypeStrategy: 错题本参数构建完成', [
|
|
|
+ Log::info('ExamTypeStrategy: 错题本参数构建完成(按卷子知识点)', [
|
|
|
'paper_count' => count($paperIds),
|
|
|
- 'mistake_count' => count($mistakeQuestionIds),
|
|
|
- 'knowledge_points_count' => count($mistakeKnowledgePoints)
|
|
|
+ 'question_count' => $questionCount,
|
|
|
+ 'knowledge_points_count' => count($paperKnowledgePoints),
|
|
|
+ 'total_questions' => $targetQuestions,
|
|
|
+ 'difficulty_category' => $difficultyCategory,
|
|
|
+ 'total_score' => $maxTotalScore
|
|
|
]);
|
|
|
|
|
|
- return $enhanced;
|
|
|
+ Log::debug('ExamTypeStrategy: 错题本组卷关键参数', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'kp_code_list' => array_values($paperKnowledgePoints),
|
|
|
+ 'difficulty_category' => $difficultyCategory,
|
|
|
+ 'total_questions' => $targetQuestions,
|
|
|
+ 'total_score' => $maxTotalScore
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $this->buildKnowledgePointAssembleParams($enhanced);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -1282,6 +1293,135 @@ class ExamTypeStrategy
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 通过卷子ID列表获取卷子题目ID
|
|
|
+ */
|
|
|
+ private function getQuestionIdsFromPapers(array $paperIds): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $questionIds = DB::table('paper_questions')
|
|
|
+ ->whereIn('paper_id', $paperIds)
|
|
|
+ ->pluck('question_bank_id')
|
|
|
+ ->unique()
|
|
|
+ ->filter()
|
|
|
+ ->values()
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ Log::debug('ExamTypeStrategy: 获取卷子题目ID', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'question_count' => count($questionIds)
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $questionIds;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('ExamTypeStrategy: 获取卷子题目ID失败', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过卷子ID列表获取题目知识点(直接使用 paper_questions.knowledge_point)
|
|
|
+ */
|
|
|
+ private function getKnowledgePointsFromPaperQuestions(array $paperIds): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $knowledgePoints = DB::table('paper_questions')
|
|
|
+ ->whereIn('paper_id', $paperIds)
|
|
|
+ ->whereNotNull('knowledge_point')
|
|
|
+ ->where('knowledge_point', '!=', '')
|
|
|
+ ->pluck('knowledge_point')
|
|
|
+ ->unique()
|
|
|
+ ->filter()
|
|
|
+ ->values()
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ Log::debug('ExamTypeStrategy: 获取卷子题目知识点', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'kp_count' => count($knowledgePoints),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $knowledgePoints;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('ExamTypeStrategy: 获取卷子题目知识点失败', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过卷子ID列表获取难度/题量/总分的聚合信息
|
|
|
+ */
|
|
|
+ private function getPaperAggregateStats(array $paperIds): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $rows = DB::table('papers')
|
|
|
+ ->whereIn('paper_id', $paperIds)
|
|
|
+ ->select(['paper_id', 'difficulty_category', 'total_questions', 'total_score'])
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ $perPaper = [];
|
|
|
+ $difficultyValues = [];
|
|
|
+ $questionValues = [];
|
|
|
+ $scoreValues = [];
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $difficulty = is_null($row->difficulty_category) ? null : (int) $row->difficulty_category;
|
|
|
+ $totalQuestions = is_null($row->total_questions) ? null : (int) $row->total_questions;
|
|
|
+ $totalScore = is_null($row->total_score) ? null : (float) $row->total_score;
|
|
|
+
|
|
|
+ $perPaper[$row->paper_id] = [
|
|
|
+ 'difficulty_category' => $difficulty,
|
|
|
+ 'total_questions' => $totalQuestions,
|
|
|
+ 'total_score' => $totalScore,
|
|
|
+ 'raw_total_questions' => $row->total_questions,
|
|
|
+ ];
|
|
|
+
|
|
|
+ if (!is_null($difficulty)) {
|
|
|
+ $difficultyValues[] = $difficulty;
|
|
|
+ }
|
|
|
+ if (!empty($totalQuestions)) {
|
|
|
+ $questionValues[] = $totalQuestions;
|
|
|
+ }
|
|
|
+ if (!is_null($totalScore)) {
|
|
|
+ $scoreValues[] = $totalScore;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $stats = [
|
|
|
+ 'difficulty_category_min' => empty($difficultyValues) ? null : min($difficultyValues),
|
|
|
+ 'total_questions_max' => empty($questionValues) ? null : max($questionValues),
|
|
|
+ 'total_score_max' => empty($scoreValues) ? null : max($scoreValues),
|
|
|
+ ];
|
|
|
+
|
|
|
+ if (empty($stats['total_questions_max'])) {
|
|
|
+ $stats['total_questions_max'] = DB::table('paper_questions')
|
|
|
+ ->whereIn('paper_id', $paperIds)
|
|
|
+ ->selectRaw('paper_id, COUNT(*) as cnt')
|
|
|
+ ->groupBy('paper_id')
|
|
|
+ ->pluck('cnt')
|
|
|
+ ->max();
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::info('ExamTypeStrategy: 获取卷子聚合信息', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'per_paper' => $perPaper,
|
|
|
+ 'stats' => $stats,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $stats;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('ExamTypeStrategy: 获取卷子聚合信息失败', [
|
|
|
+ 'paper_ids' => $paperIds,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 【新增】获取每个章节对应的知识点数量统计
|
|
|
* 通过textbook_chapter_knowledge_relation表关联查询
|