feat(recommendations): collect demographics and advance age cohorts
ci / ts / ts (packages + web) (push) Successful in 3m19s
ci / db / db (push) Successful in 53s
ci / module boundaries / boundaries (push) Successful in 7s
ci / api / api (push) Successful in 51s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m3s
ci / recommender / recommender (push) Failing after 16s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m15s

This commit is contained in:
mrleo1nid committed 2026-10-22 14:19:52 +03:00
1 parent e158558da3
commit fff91c446b
24 files changed
+778 -324

No files matched your search

@@ -161,6 +161,9 @@ def load_profile(conn: psycopg.Connection[Any], user_id: str) -> Profile:
).fetchone()
if row and row[3] and len(scores) < ONBOARDING_UNTIL:
apply_onboarding(conn, scores, row[3])
apply_demographics(conn, scores, row[3], user_id)
for tid in disliked:
scores.pop(tid, None)
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 {}
@@ -212,6 +215,35 @@ def apply_onboarding(conn: psycopg.Connection[Any], scores: dict[str, float], ch
scores.setdefault(tid, v)
def apply_demographics(
conn: psycopg.Connection[Any], scores: dict[str, float], choice: dict[str, Any], user_id: str
) -> None:
"""A weak cold-start prior from actual likes of peers, without assumed musical stereotypes."""
gender, age = choice.get("gender"), choice.get("age_group")
if not gender and not age:
return
rows = conn.execute(
"""
WITH own AS (
SELECT onboarding FROM user_profiles_rec WHERE user_id = %s
), peers AS (
SELECT p.user_id FROM user_profiles_rec p JOIN users u ON u.id = p.user_id, own
WHERE p.user_id <> %s AND NOT u.is_blocked
AND (%s::text IS NULL OR p.onboarding->>'gender' = %s)
AND (%s::text IS NULL OR rec_age_group(p.onboarding) = rec_age_group(own.onboarding))
)
SELECT r.track_id::text, count(DISTINCT r.user_id) AS fans
FROM track_reactions r JOIN peers p ON p.user_id = r.user_id
WHERE r.reaction = 'like'
GROUP BY r.track_id HAVING count(DISTINCT r.user_id) >= 2
ORDER BY fans DESC, r.track_id LIMIT %s
""",
(user_id, user_id, gender, gender, age, ONBOARDING_LIMIT),
)
for tid, fans in rows:
scores.setdefault(tid, min(0.25, 0.05 * fans))
def seed_tracks(conn: psycopg.Connection[Any], seed_type: str, seed_id: str) -> list[str]:
"""Треки, от которых запущена волна: трек, альбом или исполнитель."""
queries = {