$questionIds * @return array> question_bank_id => calibration snapshot */ public function mapCalibratedDifficulty(array $questionIds): array { if (! $this->isReady()) { return []; } $questionIds = collect($questionIds) ->map(fn ($id) => (int) $id) ->filter(fn ($id) => $id > 0) ->unique() ->values() ->all(); if ($questionIds === []) { return []; } return DB::table(self::TABLE) ->whereIn('question_bank_id', $questionIds) ->get(['question_bank_id', 'original_difficulty', 'calibrated_difficulty', 'weighted_attempts']) ->mapWithKeys(function ($row) { $qid = (int) ($row->question_bank_id ?? 0); if ($qid <= 0) { return []; } return [ $qid => [ 'original_difficulty' => $row->original_difficulty !== null ? (float) $row->original_difficulty : null, 'calibrated_difficulty' => $row->calibrated_difficulty !== null ? (float) $row->calibrated_difficulty : null, 'weighted_attempts' => $row->weighted_attempts !== null ? (float) $row->weighted_attempts : null, ], ]; }) ->all(); } /** * 批量给题目数组计算组卷使用难度 served_diff: * served_diff = alpha * calibrated + (1 - alpha) * original * alpha = min(1, weighted_attempts / 20) * 前提:仅当 calibrated_difficulty 存在时才融合;否则保持原始 difficulty 不变。 * * @param array> $questions * @return array> */ public function applyCalibratedDifficulty(array $questions): array { if ($questions === []) { return $questions; } $ids = []; foreach ($questions as $q) { $id = (int) ($q['id'] ?? $q['question_id'] ?? $q['question_bank_id'] ?? 0); if ($id > 0) { $ids[] = $id; } } $map = $this->mapCalibratedDifficulty($ids); if ($map === []) { return $questions; } foreach ($questions as &$q) { $id = (int) ($q['id'] ?? $q['question_id'] ?? $q['question_bank_id'] ?? 0); if ($id <= 0 || ! array_key_exists($id, $map)) { continue; } $snapshot = $map[$id] ?? []; $calibrated = $snapshot['calibrated_difficulty'] ?? null; if ($calibrated === null) { continue; } $original = isset($q['difficulty']) && is_numeric($q['difficulty']) ? (float) $q['difficulty'] : (float) ($snapshot['original_difficulty'] ?? $calibrated); $weightedAttempts = max(0.0, (float) ($snapshot['weighted_attempts'] ?? 0.0)); $alpha = min(1.0, $weightedAttempts / self::SERVED_DIFF_CONFIDENCE_DENOMINATOR); $servedDifficulty = ($alpha * (float) $calibrated) + ((1.0 - $alpha) * $original); $q['difficulty'] = round($servedDifficulty, 4); $q['difficulty_source'] = $alpha >= 0.9999 ? 'calibrated' : 'served_blend'; $q['difficulty_original'] = round($original, 4); $q['difficulty_calibrated'] = round((float) $calibrated, 4); $q['difficulty_alpha'] = round($alpha, 4); $q['difficulty_weighted_attempts'] = round($weightedAttempts, 4); } unset($q); return $questions; } private function isReady(): bool { if ($this->tableReady !== null) { return $this->tableReady; } $this->tableReady = Schema::hasTable(self::TABLE); return $this->tableReady; } }