minor fixes
This commit is contained in:
115
docs/Discover/rising.md
Normal file
115
docs/Discover/rising.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Rising
|
||||
|
||||
## Route
|
||||
|
||||
- URL: `GET /discover/rising`
|
||||
- Controller: `App\Http\Controllers\Web\DiscoverController::rising()`
|
||||
- Service: `App\Services\ArtworkSearchService::discoverRising()`
|
||||
- RSS feed: `GET /rss/discover/rising` via `App\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_score`
|
||||
- `engagement_velocity`
|
||||
- `published_at_ts` as the final recency tie-breaker
|
||||
|
||||
## Search query
|
||||
|
||||
`discoverRising()` uses:
|
||||
|
||||
- filter: `is_public = true AND is_approved = true AND created_at >= cutoff`
|
||||
- sort:
|
||||
- `heat_score:desc`
|
||||
- `engagement_velocity:desc`
|
||||
- `published_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:
|
||||
|
||||
```text
|
||||
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_hourly`
|
||||
- `artwork_stats.heat_score`
|
||||
- `artwork_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:
|
||||
|
||||
1. `nova:metrics-snapshot-hourly`
|
||||
- captures hourly totals into `artwork_metric_snapshots_hourly`
|
||||
2. `nova:recalculate-heat`
|
||||
- computes `heat_score` from snapshot deltas
|
||||
3. 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-hourly`
|
||||
- `nova: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-scores` every 30 minutes updates `engagement_velocity`
|
||||
- `skinbase:flush-redis-stats` every 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-hourly` and `nova:recalculate-heat` are 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.
|
||||
Reference in New Issue
Block a user