option('days'); $chunk = (int) $this->option('chunk'); $dryRun = (bool) $this->option('dry-run'); $bucketHour = now()->startOfHour(); $this->info("[nova:metrics-snapshot-hourly] bucket={$bucketHour->toDateTimeString()} days={$days} chunk={$chunk}" . ($dryRun ? ' (dry-run)' : '')); $snapshotCount = 0; $skipCount = 0; // Query artworks eligible for snapshotting: // - created within $days OR has a ranking_score above 0 // First collect eligible IDs, then process in chunks $eligibleIds = DB::table('artworks') ->leftJoin('artwork_stats as s', 's.artwork_id', '=', 'artworks.id') ->where(function ($q) use ($days) { $q->where('artworks.created_at', '>=', now()->subDays($days)) ->orWhere(function ($q2) { $q2->whereNotNull('s.ranking_score') ->where('s.ranking_score', '>', 0); }); }) ->whereNull('artworks.deleted_at') ->where('artworks.is_approved', true) ->pluck('artworks.id'); if ($eligibleIds->isEmpty()) { $this->info('No eligible artworks found.'); return self::SUCCESS; } foreach ($eligibleIds->chunk($chunk) as $chunkIds) { $artworkIds = $chunkIds->values()->all(); $stats = DB::table('artwork_stats') ->whereIn('artwork_id', $artworkIds) ->get() ->keyBy('artwork_id'); $rows = []; foreach ($artworkIds as $artworkId) { $stat = $stats->get($artworkId); $rows[] = [ 'artwork_id' => $artworkId, 'bucket_hour' => $bucketHour, 'views_count' => (int) ($stat?->views ?? 0), 'downloads_count' => (int) ($stat?->downloads ?? 0), 'favourites_count' => (int) ($stat?->favorites ?? 0), 'comments_count' => (int) ($stat?->comments_count ?? 0), 'shares_count' => (int) ($stat?->shares_count ?? 0), 'created_at' => now(), ]; } if ($dryRun) { $snapshotCount += count($rows); continue; } if (!empty($rows)) { // Upsert: if (artwork_id, bucket_hour) already exists, update totals DB::table('artwork_metric_snapshots_hourly')->upsert( $rows, ['artwork_id', 'bucket_hour'], ['views_count', 'downloads_count', 'favourites_count', 'comments_count', 'shares_count'] ); $snapshotCount += count($rows); } } $this->info("Snapshots written: {$snapshotCount} | Skipped: {$skipCount}"); Log::info('[nova:metrics-snapshot-hourly] completed', [ 'bucket' => $bucketHour->toDateTimeString(), 'written' => $snapshotCount, 'skipped' => $skipCount, 'dry_run' => $dryRun, ]); return self::SUCCESS; } }