From 916bb29a5334b2567af1ea1fde6b3777d97f053e Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sat, 28 Feb 2026 16:47:08 +0100 Subject: [PATCH] =?UTF-8?q?feat(trending):=20switch=20trending=20endpoints?= =?UTF-8?q?=20to=20Ranking=20V2=20ranking=5Fscore\n\n-=20discoverTrending(?= =?UTF-8?q?)=20now=20sorts=20by=20ranking=5Fscore:desc=20+=20engagement=5F?= =?UTF-8?q?velocity:desc\n-=20HomepageService::getTrending()=20now=20sorts?= =?UTF-8?q?=20by=20ranking=5Fscore:desc=20+=20velocity\n-=20DB=20fallback?= =?UTF-8?q?=20joins=20artwork=5Fstats=20for=20ranking=5Fscore=20sort\n-=20?= =?UTF-8?q?Both=20trending=20endpoints=20filter=20to=20last=2030=20days=20?= =?UTF-8?q?(spec=20=C2=A76)\n-=20Add=20created=5Fat=20to=20Meilisearch=20f?= =?UTF-8?q?ilterableAttributes=20for=20date=20filtering\n-=20Synced=20inde?= =?UTF-8?q?x=20settings"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/ArtworkSearchService.php | 16 ++++++++++------ app/Services/HomepageService.php | 25 +++++++++++++++---------- config/scout.php | 1 + 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/Services/ArtworkSearchService.php b/app/Services/ArtworkSearchService.php index 7cc33f00..737355b5 100644 --- a/app/Services/ArtworkSearchService.php +++ b/app/Services/ArtworkSearchService.php @@ -249,17 +249,21 @@ final class ArtworkSearchService // ── Discover section helpers ─────────────────────────────────────────────── /** - * Trending: sorted by pre-computed trending_score_24h (recalculated every 30 min). - * Falls back to views:desc if the column is not yet populated. + * Trending: sorted by Ranking Engine V2 `ranking_score` (recalculated every 30 min). + * + * Spec §6: Uses ranking_score, limits to last 30 days, + * highlights high-velocity artworks via engagement_velocity tiebreaker. */ public function discoverTrending(int $perPage = 24): LengthAwarePaginator { - $page = (int) request()->get('page', 1); - return Cache::remember("discover.trending.{$page}", self::CACHE_TTL, function () use ($perPage) { + $page = (int) request()->get('page', 1); + $cutoff = now()->subDays(30)->toDateString(); + + return Cache::remember("discover.trending.{$page}", self::CACHE_TTL, function () use ($perPage, $cutoff) { return Artwork::search('') ->options([ - 'filter' => self::BASE_FILTER, - 'sort' => ['trending_score_24h:desc', 'trending_score_7d:desc', 'views:desc', 'created_at:desc'], + 'filter' => self::BASE_FILTER . ' AND created_at >= "' . $cutoff . '"', + 'sort' => ['ranking_score:desc', 'engagement_velocity:desc', 'views:desc'], ]) ->paginate($perPage); }); diff --git a/app/Services/HomepageService.php b/app/Services/HomepageService.php index 41e68a7c..e775bf31 100644 --- a/app/Services/HomepageService.php +++ b/app/Services/HomepageService.php @@ -133,20 +133,22 @@ final class HomepageService } /** - * Trending: up to 12 artworks sorted by pre-computed trending_score_7d. + * Trending: up to 12 artworks sorted by Ranking V2 `ranking_score`. * - * Uses Meilisearch sorted by the pre-computed score (updated every 30 min). - * Falls back to DB ORDER BY trending_score_7d if Meilisearch is unavailable. - * Spec: no heavy joins in the hot path. + * Uses Meilisearch sorted by the V2 score (updated every 30 min). + * Falls back to DB ORDER BY ranking_score if Meilisearch is unavailable. + * Spec §6: ranking_score, last 30 days, highlight high-velocity artworks. */ public function getTrending(int $limit = 10): array { - return Cache::remember("homepage.trending.{$limit}", self::CACHE_TTL, function () use ($limit): array { + $cutoff = now()->subDays(30)->toDateString(); + + return Cache::remember("homepage.trending.{$limit}", self::CACHE_TTL, function () use ($limit, $cutoff): array { try { $results = Artwork::search('') ->options([ - 'filter' => 'is_public = true AND is_approved = true', - 'sort' => ['trending_score_7d:desc', 'trending_score_24h:desc', 'views:desc'], + 'filter' => 'is_public = true AND is_approved = true AND created_at >= "' . $cutoff . '"', + 'sort' => ['ranking_score:desc', 'engagement_velocity:desc', 'views:desc'], ]) ->paginate($limit, 'page', 1); @@ -172,15 +174,18 @@ final class HomepageService /** * DB-only fallback for trending (Meilisearch unavailable). - * Uses pre-computed trending_score_7d column — no correlated subqueries. + * Joins artwork_stats to sort by V2 ranking_score. */ private function getTrendingFromDb(int $limit): array { return Artwork::public() ->published() ->with(['user:id,name,username', 'user.profile:user_id,avatar_hash']) - ->orderByDesc('trending_score_7d') - ->orderByDesc('trending_score_24h') + ->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id') + ->select('artworks.*') + ->where('artworks.published_at', '>=', now()->subDays(30)) + ->orderByDesc('artwork_stats.ranking_score') + ->orderByDesc('artwork_stats.engagement_velocity') ->limit($limit) ->get() ->map(fn ($a) => $this->serializeArtwork($a)) diff --git a/config/scout.php b/config/scout.php index 2df583a9..0e3e1230 100644 --- a/config/scout.php +++ b/config/scout.php @@ -100,6 +100,7 @@ return [ 'author_id', 'is_public', 'is_approved', + 'created_at', ], 'sortableAttributes' => [ 'created_at',