|
|
@@ -1277,6 +1277,8 @@ class LearningAnalyticsService
|
|
|
'填空题' => 30,
|
|
|
'解答题' => 30,
|
|
|
];
|
|
|
+ // 新增:题目分类筛选
|
|
|
+ $questionCategory = $params['question_category'] ?? null;
|
|
|
// 注意: difficulty_ratio 参数已废弃,使用 difficulty_category 控制难度分布
|
|
|
$difficultyLevels = $params['difficulty_levels'] ?? [];
|
|
|
// 如果用户没有选择任何难度,difficultyLevels 为空数组,表示随机难度
|
|
|
@@ -1288,6 +1290,7 @@ class LearningAnalyticsService
|
|
|
'skills' => $skills,
|
|
|
'assemble_type' => $assembleType,
|
|
|
'exam_type_legacy' => $examTypeLegacy,
|
|
|
+ 'question_category' => $questionCategory,
|
|
|
]);
|
|
|
|
|
|
// 1. 如果指定了学生,获取学生的薄弱点
|
|
|
@@ -1327,7 +1330,7 @@ class LearningAnalyticsService
|
|
|
]);
|
|
|
|
|
|
// 获取学生错题的详细信息
|
|
|
- $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, 200, $mistakeQuestionIds);
|
|
|
+ $priorityQuestions = $this->getQuestionsFromBank([], [], $studentId, $questionTypeRatio, 200, $mistakeQuestionIds, [], null);
|
|
|
|
|
|
Log::info('LearningAnalyticsService: 错题获取完成', [
|
|
|
'priority_questions_count' => count($priorityQuestions),
|
|
|
@@ -1358,7 +1361,7 @@ class LearningAnalyticsService
|
|
|
'assemble_type' => $assembleType
|
|
|
]);
|
|
|
|
|
|
- $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, 200);
|
|
|
+ $additionalQuestions = $this->getQuestionsFromBank($kpCodes, $skills, $studentId, $questionTypeRatio, 200, [], [], $questionCategory);
|
|
|
$allQuestions = array_merge($priorityQuestions, $additionalQuestions);
|
|
|
|
|
|
Log::info('getQuestionsFromBank 调用完成', [
|
|
|
@@ -1519,7 +1522,7 @@ class LearningAnalyticsService
|
|
|
* 从本地题库获取题目(错题回顾优先)
|
|
|
* 支持优先获取指定题目ID的题目
|
|
|
*/
|
|
|
- private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = []): array
|
|
|
+ private function getQuestionsFromBank(array $kpCodes, array $skills, ?string $studentId, array $questionTypeRatio = [], int $totalNeeded = 100, array $priorityQuestionIds = [], array $excludeQuestionIds = [], ?int $questionCategory = null): array
|
|
|
{
|
|
|
$startTime = microtime(true);
|
|
|
|
|
|
@@ -1571,6 +1574,18 @@ class LearningAnalyticsService
|
|
|
Log::info('应用技能筛选', ['skills' => $skills]);
|
|
|
}
|
|
|
|
|
|
+ // 排除学生已做过的题目
|
|
|
+ if (!empty($excludeQuestionIds)) {
|
|
|
+ $query->whereNotIn('id', $excludeQuestionIds);
|
|
|
+ Log::info('应用排除筛选', ['exclude_count' => count($excludeQuestionIds)]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按题目分类筛选(如果指定了 question_category)
|
|
|
+ if ($questionCategory !== null) {
|
|
|
+ $query->where('question_category', $questionCategory);
|
|
|
+ Log::info('应用题目分类筛选', ['question_category' => $questionCategory]);
|
|
|
+ }
|
|
|
+
|
|
|
// 筛选有解题思路的题目
|
|
|
$query->whereNotNull('solution')
|
|
|
->where('solution', '!=', '')
|
|
|
@@ -1904,21 +1919,37 @@ class LearningAnalyticsService
|
|
|
'avg_time_per_kp_ms' => count($kpCodes) > 0 ? round($totalWeightTime / count($kpCodes), 2) : 0
|
|
|
]);
|
|
|
|
|
|
- // 3. 按权重分配题目数量
|
|
|
+ // 3. 按权重分配题目数量(修复:按权重排序取前N道题)
|
|
|
$totalWeight = array_sum($kpWeights);
|
|
|
$selectedQuestions = [];
|
|
|
|
|
|
+ // 将所有题目合并并添加权重信息
|
|
|
+ $weightedQuestions = [];
|
|
|
foreach ($questionsByKp as $kpCode => $kpQuestions) {
|
|
|
- // 计算该知识点应该选择的题目数
|
|
|
- $kpQuestionCount = max(1, round(($totalQuestions * $kpWeights[$kpCode]) / $totalWeight));
|
|
|
+ $weight = $kpWeights[$kpCode];
|
|
|
+ foreach ($kpQuestions as $q) {
|
|
|
+ $q['kp_weight'] = $weight;
|
|
|
+ $q['kp_code'] = $kpCode;
|
|
|
+ $weightedQuestions[] = $q;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 打乱题目顺序(避免固定模式)
|
|
|
- shuffle($kpQuestions);
|
|
|
+ // 打乱题目顺序(避免固定模式)
|
|
|
+ shuffle($weightedQuestions);
|
|
|
|
|
|
- // 选择题目
|
|
|
- $selectedFromKp = array_slice($kpQuestions, 0, $kpQuestionCount);
|
|
|
- $selectedQuestions = array_merge($selectedQuestions, $selectedFromKp);
|
|
|
- }
|
|
|
+ // 按权重排序(权重高的在前)
|
|
|
+ usort($weightedQuestions, function ($a, $b) {
|
|
|
+ return ($b['kp_weight'] ?? 1.0) <=> ($a['kp_weight'] ?? 1.0);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 选择前 N 道题
|
|
|
+ $selectedQuestions = array_slice($weightedQuestions, 0, $totalQuestions);
|
|
|
+
|
|
|
+ Log::info('知识点题目分配完成', [
|
|
|
+ 'total_questions' => $totalQuestions,
|
|
|
+ 'selected_count' => count($selectedQuestions),
|
|
|
+ 'top_kp_distribution' => array_count_values(array_column($selectedQuestions, 'kp_code'))
|
|
|
+ ]);
|
|
|
|
|
|
// 4. 如果题目过多,按权重排序后截取
|
|
|
if (count($selectedQuestions) > $totalQuestions) {
|
|
|
@@ -2012,12 +2043,14 @@ class LearningAnalyticsService
|
|
|
$buckets[$type][] = $q;
|
|
|
}
|
|
|
|
|
|
- // 计算目标数(四舍五入,比例>0 则至少 1 道),并校正总数
|
|
|
- $targetCount = min($targetCount, count($questions));
|
|
|
+ // 计算目标数(四舍五入,比例>0 则至少 1 道)
|
|
|
+ // 修复:不自动减少目标数量,确保达到用户要求
|
|
|
+ $availableCount = count($questions);
|
|
|
$targets = $this->computeTypeTargets($targetCount, $typeRatio);
|
|
|
|
|
|
Log::info('题型配比调整前桶统计', [
|
|
|
'target_count' => $targetCount,
|
|
|
+ 'available_count' => $availableCount,
|
|
|
'targets' => $targets,
|
|
|
'bucket_counts' => [
|
|
|
'choice' => count($buckets['choice']),
|