diff --git a/kernelboard/api/leaderboard.py b/kernelboard/api/leaderboard.py index 4b86149..be5cef3 100644 --- a/kernelboard/api/leaderboard.py +++ b/kernelboard/api/leaderboard.py @@ -251,7 +251,7 @@ def _get_query(): SELECT r.* FROM scoped_submissions s JOIN LATERAL ( - SELECT submission_id, runner, score, secret, passed + SELECT id, submission_id, runner, score, secret, passed FROM leaderboard.runs WHERE submission_id = s.id OFFSET 0 @@ -284,7 +284,10 @@ def _get_query(): s.file_name AS file_name, r.submission_id AS submission_id, COALESCE(sc.submission_count, 0) AS submission_count, - RANK() OVER (PARTITION BY r.runner, u.id ORDER BY r.score ASC) AS rank + ROW_NUMBER() OVER ( + PARTITION BY r.runner, s.user_id + ORDER BY r.score ASC, s.submission_time ASC, s.id ASC, r.id ASC + ) AS rank FROM scoped_runs r JOIN scoped_submissions s ON r.submission_id = s.id LEFT JOIN leaderboard.user_info u ON s.user_id = u.id diff --git a/kernelboard/api/leaderboard_summaries.py b/kernelboard/api/leaderboard_summaries.py index a10f2b6..f175108 100644 --- a/kernelboard/api/leaderboard_summaries.py +++ b/kernelboard/api/leaderboard_summaries.py @@ -16,7 +16,7 @@ leaderboard_summaries_bp = Blueprint("leaderboard_summaries_bp", __name__, url_prefix="/leaderboard-summaries") # Redis cache key prefix for ended leaderboard top_users -CACHE_KEY_PREFIX = "lb_top_users:v2:" +CACHE_KEY_PREFIX = "lb_top_users:v3:" # ============================================================================= @@ -354,9 +354,9 @@ def _get_query_for_ids(): s.user_id, u.user_name, r.score, - RANK() OVER ( + ROW_NUMBER() OVER ( PARTITION BY s.leaderboard_id, r.runner, s.user_id - ORDER BY r.score ASC + ORDER BY r.score ASC, s.submission_time ASC, s.id ASC, r.id ASC ) AS personal_submission_rank FROM leaderboard.runs r JOIN leaderboard.submission s ON r.submission_id = s.id @@ -475,9 +475,9 @@ def _get_query(): s.user_id, u.user_name, r.score, - RANK() OVER ( + ROW_NUMBER() OVER ( PARTITION BY s.leaderboard_id, r.runner, s.user_id - ORDER BY r.score ASC + ORDER BY r.score ASC, s.submission_time ASC, s.id ASC, r.id ASC ) AS personal_submission_rank FROM leaderboard.runs r JOIN leaderboard.submission s ON r.submission_id = s.id diff --git a/kernelboard/index.py b/kernelboard/index.py index 81ca1ac..b8b64bd 100644 --- a/kernelboard/index.py +++ b/kernelboard/index.py @@ -75,8 +75,8 @@ def index(): s.leaderboard_id AS leaderboard_id, u.user_name AS user_name, r.score AS score, - RANK() OVER (PARTITION BY s.leaderboard_id, r.runner, u.id - ORDER BY r.score ASC) AS personal_submission_rank + ROW_NUMBER() OVER (PARTITION BY s.leaderboard_id, r.runner, s.user_id + ORDER BY r.score ASC, s.submission_time ASC, s.id ASC, r.id ASC) AS personal_submission_rank FROM leaderboard.runs r JOIN leaderboard.submission s ON r.submission_id = s.id JOIN active_leaderboards a ON s.leaderboard_id = a.id diff --git a/kernelboard/leaderboard.py b/kernelboard/leaderboard.py index 21f32d3..ec1573c 100644 --- a/kernelboard/leaderboard.py +++ b/kernelboard/leaderboard.py @@ -45,7 +45,10 @@ def leaderboard(leaderboard_id: int): r.score AS score, s.submission_time AS submission_time, s.file_name AS file_name, - RANK() OVER (PARTITION BY r.runner, u.id ORDER BY r.score ASC) AS rank + ROW_NUMBER() OVER ( + PARTITION BY r.runner, s.user_id + ORDER BY r.score ASC, s.submission_time ASC, s.id ASC, r.id ASC + ) AS rank FROM leaderboard.runs r JOIN leaderboard.submission s ON r.submission_id = s.id LEFT JOIN leaderboard.user_info u ON s.user_id = u.id diff --git a/ranking_worker.py b/ranking_worker.py index 4873a4d..14fc82a 100644 --- a/ranking_worker.py +++ b/ranking_worker.py @@ -117,9 +117,9 @@ def ensure_snapshot_table(conn): s.user_id, u.user_name, r.score, - RANK() OVER ( + ROW_NUMBER() OVER ( PARTITION BY s.leaderboard_id, r.runner, s.user_id - ORDER BY r.score ASC + ORDER BY r.score ASC, s.submission_time ASC, s.id ASC, r.id ASC ) AS personal_submission_rank FROM leaderboard.runs r JOIN leaderboard.submission s ON r.submission_id = s.id diff --git a/tests/api/test_leaderboard_api.py b/tests/api/test_leaderboard_api.py index 47ebfca..c3edcaf 100644 --- a/tests/api/test_leaderboard_api.py +++ b/tests/api/test_leaderboard_api.py @@ -430,7 +430,7 @@ def test_hacked_submissions_are_hidden_from_public_leaderboard(client, app): assert "visible_clean_status.py" in ranked_files -def test_equal_best_runs_preserve_ties_and_distinct_submission_count(client, app): +def test_equal_best_runs_keep_one_entry_and_distinct_submission_count(client, app): before = client.get("/api/leaderboard/339").get_json()["data"]["rankings"] gpu, entries = next(iter(before.items())) winner = entries[0] @@ -455,6 +455,53 @@ def test_equal_best_runs_preserve_ties_and_distinct_submission_count(client, app conn.commit() after = client.get("/api/leaderboard/339").get_json()["data"]["rankings"][gpu] tied = [item for item in after if item["submission_id"] == winner["submission_id"]] - assert len(tied) == 2 + assert len(tied) == 1 assert all(item["score"] == winner["score"] for item in tied) assert all(item["submission_count"] == winner["submission_count"] for item in tied) + + +def test_tied_submissions_keep_earliest_per_account_and_preserve_other_users(client, app): + before = client.get("/api/leaderboard/339").get_json()["data"]["rankings"] + gpu, entries = next(iter(before.items())) + winner = entries[0] + with app.app_context(): + conn = get_db_connection() + with conn.cursor() as cur: + cur.execute( + "INSERT INTO leaderboard.user_info (id, user_name) VALUES (%s, %s)", + ("tie-regression-user", winner["user_name"]), + ) + for submission_id, new_user in [(990101, False), (990102, False), (990103, True)]: + cur.execute( + """ + INSERT INTO leaderboard.submission ( + id, leaderboard_id, file_name, user_id, code_id, submission_time, done, status + ) + SELECT %s, leaderboard_id, 'tie_regression.py', + CASE WHEN %s THEN 'tie-regression-user' ELSE user_id END, + code_id, submission_time - INTERVAL '1 day', done, status + FROM leaderboard.submission WHERE id = %s + """, + (submission_id, new_user, winner["submission_id"]), + ) + cur.execute( + """ + INSERT INTO leaderboard.runs ( + id, submission_id, start_time, end_time, mode, secret, + runner, score, passed, compilation, meta, result, system_info + ) + SELECT %s + ROW_NUMBER() OVER (), %s, start_time, end_time, mode, secret, + runner, score, passed, compilation, meta, result, system_info + FROM leaderboard.runs + WHERE submission_id = %s AND runner = %s + """, + (submission_id * 100, submission_id, winner["submission_id"], gpu), + ) + conn.commit() + after = client.get("/api/leaderboard/339").get_json()["data"]["rankings"][gpu] + matches = [row for row in after if row["user_name"] == winner["user_name"]] + assert {row["submission_id"] for row in matches} == {990101, 990103} + assert len(matches) == 2 + assert all(row["score"] == winner["score"] for row in matches) + original = next(row for row in matches if row["submission_id"] == 990101) + assert original["submission_count"] == winner["submission_count"] + 2