Save workspace changes
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\Ranking\ArtworkRankingService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* php artisan nova:recalculate-rankings [--chunk=500] [--sync-rank-scores] [--skip-index]
|
||||
*
|
||||
* Ranking Engine V2 — recalculates ranking_score and engagement_velocity
|
||||
* for all public, approved artworks. Designed to run every 30 minutes.
|
||||
*/
|
||||
class RecalculateRankingsCommand extends Command
|
||||
{
|
||||
protected $signature = 'nova:recalculate-rankings
|
||||
{--chunk=500 : DB chunk size for batch processing}
|
||||
{--sync-rank-scores : Also update rank_artwork_scores table with V2 formula}
|
||||
{--skip-index : Skip dispatching Meilisearch re-index jobs}';
|
||||
|
||||
protected $description = 'Recalculate V2 ranking scores (engagement + shares + decay + authority + velocity)';
|
||||
|
||||
public function __construct(private readonly ArtworkRankingService $ranking)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$chunkSize = (int) $this->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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user