feat: Ranking Engine V2 — intelligent scoring with shares, authority, decay & velocity\n\n- Add ArtworkRankingService with V2 formula:\n ranking_score = (base × authority × decay) + velocity_boost\n Base: views×0.2 + downloads×1.5 + favourites×2.5 + comments×3.0 + shares×4.0\n Authority: 1 + (log10(1+followers) + fav_received/1000) × 0.05\n Decay: 1 / (1 + hours/48)\n Velocity: 24h signals × velocity_weights × 0.5\n\n- Add nova:recalculate-rankings command (--chunk, --sync-rank-scores, --skip-index)\n- Add migration: ranking_score, engagement_velocity, shares/comments counts to artwork_stats\n- Upgrade RankingService.computeScores() with shares + comments + velocity\n- Update Meilisearch sortableAttributes: ranking_score, shares_count, engagement_velocity, comments_count\n- Update toSearchableArray() to expose V2 fields\n- Schedule every 30 min with overlap protection\n- Verified: 49733 artworks scored successfully"

This commit is contained in:
2026-02-28 16:41:15 +01:00
parent 90f244f264
commit de3ec22ee5
10 changed files with 837 additions and 14 deletions

View File

@@ -256,6 +256,11 @@ class Artwork extends Model
'favorites_count' => (int) ($stat?->favorites ?? 0),
'awards_received_count' => (int) ($awardStat?->score_total ?? 0),
'downloads_count' => (int) ($stat?->downloads ?? 0),
// ── Ranking V2 fields ───────────────────────────────────────────────
'ranking_score' => (float) ($stat?->ranking_score ?? 0),
'engagement_velocity' => (float) ($stat?->engagement_velocity ?? 0),
'shares_count' => (int) ($stat?->shares_count ?? 0),
'comments_count' => (int) ($stat?->comments_count ?? 0),
'awards' => [
'gold' => $awardStat?->gold_count ?? 0,
'silver' => $awardStat?->silver_count ?? 0,

View File

@@ -17,6 +17,8 @@ class ArtworkStats extends Model
public $incrementing = false;
public $timestamps = false;
protected $fillable = [
'artwork_id',
'views',
@@ -24,6 +26,14 @@ class ArtworkStats extends Model
'favorites',
'rating_avg',
'rating_count',
// V2 ranking columns
'comments_count',
'shares_count',
'ranking_score',
'engagement_velocity',
'shares_24h',
'comments_24h',
'favourites_24h',
];
public function artwork(): BelongsTo