shouldUseV2($userId, $algoVersion)) { return $this->v2Feed->getFeed($userId, $limit, $cursor, $algoVersion); } return $this->personalizedFeed->getFeed($userId, $limit, $cursor, $algoVersion); } public function regenerateCacheForUser(int $userId, ?string $algoVersion = null): void { if ($this->shouldUseV2($userId, $algoVersion)) { $this->v2Feed->regenerateCacheForUser($userId, $algoVersion); return; } $this->personalizedFeed->regenerateCacheForUser($userId, $algoVersion); } /** * @return array */ public function inspectDecision(int $userId, ?string $algoVersion = null): array { $requested = trim((string) ($algoVersion ?? '')); $v2AlgoVersion = trim((string) config('discovery.v2.algo_version', 'clip-cosine-v2-adaptive')); $v2Enabled = (bool) config('discovery.v2.enabled', false); $rollout = max(0, min(100, (int) config('discovery.v2.rollout_percentage', 0))); $bucket = abs((int) crc32((string) $userId)) % 100; $forcedByAlgoVersion = $requested !== '' && $requested === $v2AlgoVersion; $usesV2 = $forcedByAlgoVersion || ($v2Enabled && ($rollout >= 100 || ($rollout > 0 && $bucket < $rollout))); $reason = match (true) { $forcedByAlgoVersion => 'explicit_algo_override', ! $v2Enabled => 'v2_disabled', $rollout >= 100 => 'full_rollout', $rollout <= 0 => 'rollout_zero', $bucket < $rollout => 'bucket_in_rollout', default => 'bucket_outside_rollout', }; return [ 'user_id' => $userId, 'requested_algo_version' => $requested !== '' ? $requested : null, 'v2_algo_version' => $v2AlgoVersion, 'v2_enabled' => $v2Enabled, 'rollout_percentage' => $rollout, 'bucket' => $bucket, 'bucket_in_rollout' => $bucket < $rollout, 'forced_by_algo_version' => $forcedByAlgoVersion, 'uses_v2' => $usesV2, 'selected_engine' => $usesV2 ? 'v2' : 'v1', 'reason' => $reason, ]; } private function shouldUseV2(int $userId, ?string $algoVersion = null): bool { return (bool) ($this->inspectDecision($userId, $algoVersion)['uses_v2'] ?? false); } }