option('chunk'); $syncRankScores = (bool) $this->option('sync-rank-scores'); $skipIndex = (bool) $this->option('skip-index'); // ── Step 1: Recalculate ranking_score + engagement_velocity ───── $this->info('Ranking V2: recalculating scores …'); $start = microtime(true); $updated = $this->ranking->recalculateAll($chunkSize); $elapsed = round(microtime(true) - $start, 2); $this->info(" ✓ {$updated} artworks scored in {$elapsed}s"); // ── Step 2 (optional): Sync to rank_artwork_scores ───────────── if ($syncRankScores) { $this->info('Syncing to rank_artwork_scores …'); $start2 = microtime(true); $synced = $this->ranking->syncToRankScores($chunkSize); $elapsed2 = round(microtime(true) - $start2, 2); $this->info(" ✓ {$synced} rank scores synced in {$elapsed2}s"); } // ── Step 3 (optional): Trigger Meilisearch re-index ──────────── if (! $skipIndex) { $this->info('Dispatching Meilisearch index jobs …'); $this->dispatchIndexJobs(); $this->info(' ✓ Index jobs dispatched'); } return self::SUCCESS; } /** * Dispatch IndexArtworkJob for artworks updated in the last 24 hours * (or recently scored). Keeps the search index current. */ private function dispatchIndexJobs(): void { \App\Models\Artwork::query() ->select('id') ->where('is_public', true) ->where('is_approved', true) ->whereNull('deleted_at') ->whereNotNull('published_at') ->where('published_at', '>=', now()->subDays(30)->toDateTimeString()) ->chunkById(500, function ($artworks): void { foreach ($artworks as $artwork) { \App\Jobs\IndexArtworkJob::dispatch($artwork->id); } }); } }