Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Request consumer: cf_recording recommendations from spark cluster (#867)
* request recommendation from spark cluster * send recommendation engine data to lemmy * PEP-8 fixes * change col type to jsonb to enable recommendation update * add tests for cf recording recommendations request * add 'mbids' to var names
- Loading branch information
Showing
21 changed files
with
608 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
admin/sql/updates/2020-05-20-change-recommendation-cf-recording-col-type.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
BEGIN; | ||
|
||
ALTER TABLE recommendation.cf_recording DROP recording_mbid; | ||
ALTER TABLE recommendation.cf_recording ADD COLUMN recording_mbid JSONB NOT NULL; | ||
|
||
ALTER TABLE recommendation.cf_recording ADD CONSTRAINT user_id_unique UNIQUE (user_id); | ||
|
||
COMMIT; |
5 changes: 5 additions & 0 deletions
5
admin/sql/updates/2020-05-20-drop-recommendation-cf-recording-col.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
BEGIN; | ||
|
||
ALTER TABLE recommendation.cf_recording DROP TYPE; | ||
|
||
COMMIT; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
"""This module contains functions to insert and retrieve recommendations | ||
generated from Apache Spark into the database. | ||
""" | ||
|
||
# listenbrainz-server - Server for the ListenBrainz project. | ||
# | ||
# Copyright (C) 2020 MetaBrainz Foundation Inc. | ||
# | ||
# This program is free software; you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation; either version 2 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License along | ||
# with this program; if not, write to the Free Software Foundation, Inc., | ||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
|
||
|
||
import ujson | ||
import sqlalchemy | ||
|
||
from listenbrainz import db | ||
from flask import current_app | ||
|
||
|
||
def get_timestamp_for_last_recording_recommended(): | ||
""" Get the time when recommendation_cf_recording table was last updated | ||
""" | ||
with db.engine.connect() as connection: | ||
result = connection.execute(sqlalchemy.text(""" | ||
SELECT MAX(created) as created_ts | ||
FROM recommendation.cf_recording | ||
""") | ||
) | ||
row = result.fetchone() | ||
return row['created_ts'] if row else None | ||
|
||
|
||
def insert_user_recommendation(user_id, top_artist_recording_mbids, similar_artist_recording_mbids): | ||
""" Insert recommended recording for a user in the db. | ||
Args: | ||
user_id (int): row id of the user. | ||
top_artist_recording_mbids (list): recommended recording mbids that belong to top artists listened to by the user. | ||
similar_artist_recording_mbids (list): recommended recording mbids that belong to artists similar to top artists | ||
listened to by the user. | ||
""" | ||
recommendation = { | ||
'top_artist': top_artist_recording_mbids, | ||
'similar_artist': similar_artist_recording_mbids, | ||
} | ||
|
||
with db.engine.connect() as connection: | ||
connection.execute(sqlalchemy.text(""" | ||
INSERT INTO recommendation.cf_recording (user_id, recording_mbid) | ||
VALUES (:user_id, :recommendation) | ||
ON CONFLICT (user_id) | ||
DO UPDATE SET user_id = :user_id, | ||
recording_mbid = :recommendation, | ||
created = NOW() | ||
"""), { | ||
'user_id': user_id, | ||
'recommendation': ujson.dumps(recommendation), | ||
} | ||
) | ||
|
||
|
||
def get_user_recommendation(user_id): | ||
""" Get recommendations for a user with the given row ID. | ||
Args: | ||
user_id (int): the row ID of the user in the DB | ||
Returns: | ||
A dict of the following format | ||
{ | ||
'user_id' (int): the row ID of the user in the DB, | ||
'recording_mbid' (dict): recommended recording mbids | ||
'created' (datetime): datetime object representing when | ||
the recommendation for this user was last updated. | ||
} | ||
recording_mbid = { | ||
'top_artist_recording': [], | ||
'similar_artist_recording': [] | ||
} | ||
""" | ||
with db.engine.connect() as connection: | ||
result = connection.execute(sqlalchemy.text(""" | ||
SELECT user_id, recording_mbid, created | ||
FROM recommendation.cf_recording | ||
WHERE user_id = :user_id | ||
"""), { | ||
'user_id': user_id | ||
} | ||
) | ||
row = result.fetchone() | ||
return dict(row) if row else None |
55 changes: 55 additions & 0 deletions
55
listenbrainz/db/tests/test_recommendations_cf_recording.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import listenbrainz.db.user as db_user | ||
import listenbrainz.db.recommendations_cf_recording as db_recommendations_cf_recording | ||
|
||
from datetime import datetime, timezone | ||
from listenbrainz.db.testing import DatabaseTestCase | ||
|
||
|
||
class CFRecordingRecommendationDatabaseTestCase(DatabaseTestCase): | ||
|
||
def setUp(self): | ||
DatabaseTestCase.setUp(self) | ||
self.user = db_user.get_or_create(1, 'vansika') | ||
|
||
def test_insert_user_recommendation(self): | ||
top_artist_recording_mbids = ['a36d6fc9-49d0-4789-a7dd-a2b72369ca45, b36d6fc9-49d0-4789-a7dd-a2b72369ca45'] | ||
similar_artist_recording_mbids = ['c36d6fc9-49d0-4789-a7dd-a2b72369ca45', 'd36d6fc9-49d0-4789-a7dd-a2b72369ca45'] | ||
|
||
db_recommendations_cf_recording.insert_user_recommendation( | ||
user_id=self.user['id'], | ||
top_artist_recording_mbids=top_artist_recording_mbids, | ||
similar_artist_recording_mbids=similar_artist_recording_mbids | ||
) | ||
|
||
result = db_recommendations_cf_recording.get_user_recommendation(self.user['id']) | ||
self.assertEqual(result['recording_mbid']['top_artist'], top_artist_recording_mbids) | ||
self.assertEqual(result['recording_mbid']['similar_artist'], similar_artist_recording_mbids) | ||
self.assertGreater(int(result['created'].strftime('%s')), 0) | ||
|
||
def insert_test_data(self): | ||
top_artist_recording_mbids = ['x36d6fc9-49d0-4789-a7dd-a2b72369ca45, h36d6fc9-49d0-4789-a7dd-a2b72369ca45'] | ||
similar_artist_recording_mbids = ['v36d6fc9-49d0-4789-a7dd-a2b72369ca45', 'i36d6fc9-49d0-4789-a7dd-a2b72369ca45'] | ||
|
||
db_recommendations_cf_recording.insert_user_recommendation( | ||
user_id=self.user['id'], | ||
top_artist_recording_mbids=top_artist_recording_mbids, | ||
similar_artist_recording_mbids=similar_artist_recording_mbids | ||
) | ||
|
||
return { | ||
'top_artist_recording_mbids': top_artist_recording_mbids, | ||
'similar_artist_recording_mbids': similar_artist_recording_mbids, | ||
} | ||
|
||
def test_get_user_recommendation(self): | ||
data_inserted = self.insert_test_data() | ||
|
||
data_received = db_recommendations_cf_recording.get_user_recommendation(self.user['id']) | ||
self.assertEqual(data_received['recording_mbid']['top_artist'], data_inserted['top_artist_recording_mbids']) | ||
self.assertEqual(data_received['recording_mbid']['similar_artist'], data_inserted['similar_artist_recording_mbids']) | ||
|
||
def test_get_timestamp_for_last_recording_recommended(self): | ||
ts = datetime.now(timezone.utc) | ||
self.insert_test_data() | ||
received_ts = db_recommendations_cf_recording.get_timestamp_for_last_recording_recommended() | ||
self.assertGreaterEqual(received_ts, ts) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.