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

@@ -0,0 +1,54 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Ranking Engine V2 add shares_count, comments_count, ranking_score,
* and engagement_velocity to artwork_stats.
*
* Also adds shares_24h, comments_24h, favourites_24h, and comments_7d
* sliding-window counters needed for velocity calculation.
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('artwork_stats', function (Blueprint $table) {
// Denormalised counters (all-time)
$table->unsignedBigInteger('comments_count')->default(0)->after('favorites');
$table->unsignedBigInteger('shares_count')->default(0)->after('comments_count');
// Windowed counters for velocity calculation
$table->unsignedBigInteger('shares_24h')->default(0)->after('downloads_7d');
$table->unsignedBigInteger('comments_24h')->default(0)->after('shares_24h');
$table->unsignedBigInteger('favourites_24h')->default(0)->after('comments_24h');
// V2 computed scores
$table->double('ranking_score', 12, 4)->default(0)->after('rating_count');
$table->double('engagement_velocity', 10, 4)->default(0)->after('ranking_score');
// Indexes for sorting
$table->index('ranking_score');
$table->index('shares_count');
});
}
public function down(): void
{
Schema::table('artwork_stats', function (Blueprint $table) {
$table->dropIndex(['ranking_score']);
$table->dropIndex(['shares_count']);
$table->dropColumn([
'comments_count',
'shares_count',
'shares_24h',
'comments_24h',
'favourites_24h',
'ranking_score',
'engagement_velocity',
]);
});
}
};