feat(recommender): Thompson-sampling bandit over candidate sources (M6)
Nightly Beta(α, β) per user and source from wave impressions (reward: complete or like, ×0.99 daily decay) stored in user_profiles_rec.bandit; the wave adds 0.2·(θ − 0.5) per candidate source, sources without data stay neutral. The user's exploration share now follows how well `fresh` does against the other sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f7722e28ad
commit
ff0aafdb1e
6 files changed
+130
-2
No files matched your search
@@ -156,10 +156,11 @@ def load_profile(conn: psycopg.Connection[Any], user_id: str) -> Profile:
|
||||
)
|
||||
}
|
||||
row = conn.execute(
|
||||
"SELECT exploration, taste_cf::text FROM user_profiles_rec WHERE user_id = %s", (user_id,)
|
||||
"SELECT exploration, taste_cf::text, bandit FROM user_profiles_rec WHERE user_id = %s", (user_id,)
|
||||
).fetchone()
|
||||
exploration = float(row[0]) if row else 0.15
|
||||
taste_cf = parse_vector(row[1]) if row else None
|
||||
bandit = {str(k): [float(x) for x in v] for k, v in (row[2] or {}).items()} if row else {}
|
||||
if len(played) < 30:
|
||||
exploration = max(exploration, 0.4) # холодный старт: больше исследования на первых треках
|
||||
day = 86400
|
||||
@@ -173,6 +174,7 @@ def load_profile(conn: psycopg.Connection[Any], user_id: str) -> Profile:
|
||||
rediscover={t for t, s in scores.items() if s > 1.5 and now - last.get(t, now) > 60 * day},
|
||||
exploration=exploration,
|
||||
taste_cf=taste_cf.tolist() if taste_cf is not None else None,
|
||||
bandit=bandit,
|
||||
recent_likes=recent_likes,
|
||||
)
|
||||
|
||||
|
||||
Reference in new issue
Block a user