4.3 KiB
Rising
Route
- URL:
GET /discover/rising - Controller:
App\Http\Controllers\Web\DiscoverController::rising() - Service:
App\Services\ArtworkSearchService::discoverRising() - RSS feed:
GET /rss/discover/risingviaApp\Http\Controllers\RSS\DiscoverFeedController::rising()
What the page reads
Like most Discover surfaces, this page ranks via Meilisearch and then hydrates the result IDs from MySQL for presentation.
If the search-backed query throws or returns no items, the controller falls back to a direct MySQL query against artworks + artwork_stats.
If the page receives a non-empty result set but every item has zero heat_score and zero engagement_velocity, it switches to a low-signal fallback policy instead of pretending that the zero-heat order is meaningful.
The RSS Rising feed now follows the same low-signal policy and the same adaptive lookback window, so it does not drift to a stale zero-heat ordering when recent engagement is sparse.
Primary ranking fields:
heat_scoreengagement_velocitypublished_at_tsas the final recency tie-breaker
Search query
discoverRising() uses:
- filter:
is_public = true AND is_approved = true AND created_at >= cutoff - sort:
heat_score:descengagement_velocity:descpublished_at_ts:desc
The cutoff comes from the same adaptive time-window service used by Trending.
Rising formula
heat_score is produced by App\Console\Commands\RecalculateHeatCommand.
Current formula:
raw_heat
= ((views_delta * 1)
+ (downloads_delta * 3)
+ (favourites_delta * 6)
+ (comments_delta * 8)
+ (shares_delta * 12)) / window_hours
age_factor
= 1 / (1 + hours_since_upload / 24)
heat_score
= raw_heat * age_factor
The heat command smooths deltas over a trailing lookback window, rather than relying only on the last single hour.
That matters on low-traffic periods, because a pure 1-hour delta often collapses to zero for almost every artwork.
An artwork still needs at least two snapshots inside that window for the smoothed heat delta to count. A single snapshot without an earlier baseline does not count as momentum.
The views_1h, downloads_1h, favourites_1h, comments_1h, and shares_1h columns are still stored from the previous-hour comparison for diagnostics and dashboards.
Data sources
The page depends on:
artwork_metric_snapshots_hourlyartwork_stats.heat_scoreartwork_stats.engagement_velocity- artwork publish timestamps
In zero-signal periods, the fallback policy also uses a 24-hour snapshot delta rollup from artwork_metric_snapshots_hourly and then falls back to published_at DESC.
engagement_velocity is not part of the heat command. It comes from the ranking engine and acts as a secondary momentum signal.
Intended background jobs
The intended pipeline is:
nova:metrics-snapshot-hourly- captures hourly totals into
artwork_metric_snapshots_hourly
- captures hourly totals into
nova:recalculate-heat- computes
heat_scorefrom snapshot deltas
- computes
- Meilisearch picks up the updated score after indexing
Runtime schedule
Rising depends on two active Laravel 11 runtime jobs in routes/console.php:
nova:metrics-snapshot-hourlynova:recalculate-heat
If either one disappears from php artisan schedule:list, Rising will quickly drift toward stale or low-signal ordering.
Active jobs that still affect Rising
nova:recalculate-rankings --sync-rank-scoresevery 30 minutes updatesengagement_velocityskinbase:flush-redis-statsevery 5 minutes keeps all-time stats fresher
Cache behavior
- Cache key:
discover.rising.{windowDays}d.{page} - TTL: 120 seconds
If Meilisearch sort settings are missing or the search result is empty, the controller falls back to the DB query instead of returning an empty page or a 500.
Notes
- If Rising looks frozen while Trending moves, the first place to check is whether
nova:metrics-snapshot-hourlyandnova:recalculate-heatare actually being executed in production. - The page no longer uses
GridFiller, so it should not pull in unrelated older artworks when the real result set is thin. - If all heat and velocity values are zero, Rising intentionally behaves like a low-signal discovery feed: recent activity in the last 24 hours first, then newest published artworks.