forked from Hanabi/hanabi-league
Implement rating change updates to DB
Still missing: The actual function to calculate the rating change
This commit is contained in:
parent
bbb2b19df0
commit
adccdf737b
2 changed files with 86 additions and 15 deletions
|
@ -260,7 +260,7 @@ CREATE TABLE variant_base_ratings (
|
|||
DROP TABLE IF EXISTS variant_ratings CASCADE;
|
||||
CREATE TABLE variant_ratings (
|
||||
/** This should reference the game that triggered the elo update */
|
||||
league_id INTEGER PRIMARY KEY REFERENCES games,
|
||||
league_id INTEGER PRIMARY KEY REFERENCES games (league_id),
|
||||
|
||||
variant_id SMALLINT NOT NULL,
|
||||
player_count SMALLINT NOT NULL,
|
||||
|
@ -293,7 +293,7 @@ CREATE TABLE user_ratings (
|
|||
* I would use the league_id here for proper ordering.
|
||||
* Also note that this can then be used to identify whether a given league game has already been processed for rating change.
|
||||
*/
|
||||
league_id INTEGER PRIMARY KEY REFERENCES games,
|
||||
league_id INTEGER REFERENCES games (league_id),
|
||||
|
||||
user_id SMALLINT NOT NULL,
|
||||
type SMALLINT NOT NULL,
|
||||
|
@ -301,12 +301,15 @@ CREATE TABLE user_ratings (
|
|||
/**
|
||||
* Do we want to store this here as well? Would be nice to be displayed in some elo page imo.
|
||||
* Note: We don't need to store the result (i guess), since we can easily retrieve that info by looking up the game using the league_id
|
||||
* TODO: Since I'm not even sure on the rating model yet (I could imagine something slightly different than a team rating),
|
||||
* I'll leave this here as potentially null for now and don't implement it.
|
||||
*/
|
||||
team_rating REAL NOT NULL,
|
||||
team_rating REAL,
|
||||
change REAL NOT NULL,
|
||||
value_after REAL NOT NULL,
|
||||
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
FOREIGN KEY (user_id) REFERENCES users (id),
|
||||
CONSTRAINT user_change_per_game_unique UNIQUE (league_id, user_id)
|
||||
);
|
||||
|
||||
|
||||
|
|
78
ratings.py
78
ratings.py
|
@ -1,11 +1,24 @@
|
|||
from typing import List, Dict
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
import utils
|
||||
from database import conn_manager
|
||||
import psycopg2.extras
|
||||
|
||||
from log_setup import logger
|
||||
|
||||
|
||||
def rating_change(user_ratings: Dict[int, float], variant_rating: float, win: bool) -> Tuple[Dict[int, float], float]:
|
||||
"""
|
||||
@param user_ratings: Mapping of user ids to ratings that played this game.
|
||||
@param variant_rating: Rating of the variant that was played
|
||||
@param win: Whether the team won the game
|
||||
@return: Mapping of user ids to their rating *changes* and *change* in variant rating
|
||||
"""
|
||||
# TODO: Implement this properly (We have not decided how this will work exactly)
|
||||
# For now, return +1 elo for players and -1 elo for variants
|
||||
return {user_id: 1 for user_id in user_ratings.keys()}, -1
|
||||
|
||||
|
||||
def next_game_to_rate():
|
||||
cur = conn_manager.get_new_cursor()
|
||||
cur.execute("SELECT games.id FROM games "
|
||||
|
@ -23,7 +36,7 @@ def next_game_to_rate():
|
|||
return game_id
|
||||
|
||||
|
||||
def get_current_ratings(user_ids: List[int], rating_type: int) -> Dict[int, float]:
|
||||
def get_current_user_ratings(user_ids: List[int], rating_type: int) -> Dict[int, float]:
|
||||
"""
|
||||
Fetches the current ratings for specified players and rating type from DB
|
||||
@return: Mapping user_id -> current rating
|
||||
|
@ -41,6 +54,8 @@ def get_current_ratings(user_ids: List[int], rating_type: int) -> Dict[int, floa
|
|||
" GROUP BY (user_id, type)"
|
||||
" ) AS latest_user_ratings "
|
||||
" ON user_ratings.league_id = latest_user_ratings.max_league_id "
|
||||
"WHERE user_ratings.user_id IN ({}) AND user_ratings.type = %s".format(", ".join("%s" for _ in user_ids)),
|
||||
user_ids + [rating_type]
|
||||
)
|
||||
current_ratings = cur.fetchall()
|
||||
|
||||
|
@ -53,6 +68,38 @@ def get_current_ratings(user_ids: List[int], rating_type: int) -> Dict[int, floa
|
|||
return ratings
|
||||
|
||||
|
||||
def get_current_variant_rating(variant_id: int, player_count: int) -> float:
|
||||
cur = conn_manager.get_new_cursor()
|
||||
cur.execute("SELECT value_after FROM variant_ratings "
|
||||
"INNER JOIN ("
|
||||
" SELECT variant_id, player_count, MAX(league_id) AS max_league_id"
|
||||
" FROM variant_ratings "
|
||||
" GROUP BY (variant_id, player_count)"
|
||||
" ) AS latest_variant_ratings "
|
||||
" ON variant_ratings.league_id = latest_variant_ratings.max_league_id "
|
||||
"WHERE variant_ratings.variant_id = %s AND variant_ratings.player_count = %s",
|
||||
(variant_id, player_count)
|
||||
)
|
||||
query_result = cur.fetchone()
|
||||
if query_result is not None:
|
||||
(current_rating, ) = query_result
|
||||
return current_rating
|
||||
|
||||
# Reaching this point of code execution just means this is the first game for this variant rating
|
||||
cur.execute("SELECT rating FROM variant_base_ratings "
|
||||
"WHERE variant_id = %s AND player_count = %s",
|
||||
(variant_id, player_count)
|
||||
)
|
||||
query_result = cur.fetchone()
|
||||
if query_result is None:
|
||||
err_msg = "Failed to get current variant rating for variant {}.".format(variant_id)
|
||||
logger.error(err_msg)
|
||||
raise ValueError(err_msg)
|
||||
|
||||
(base_rating, ) = query_result
|
||||
return base_rating
|
||||
|
||||
|
||||
def process_rating_of_next_game():
|
||||
game_id = next_game_to_rate()
|
||||
if game_id is None:
|
||||
|
@ -60,14 +107,16 @@ def process_rating_of_next_game():
|
|||
return
|
||||
logger.verbose("Processing rating for game {}".format)
|
||||
cur = conn_manager.get_new_cursor()
|
||||
cur.execute("SELECT games.league_id, games.num_players, games.score, variants.num_suits, variants.clue_starved "
|
||||
# Fetch data on the game played
|
||||
cur.execute(
|
||||
"SELECT games.league_id, games.num_players, games.score, variants.num_suits, variants.clue_starved, variants.id "
|
||||
"FROM games "
|
||||
"INNER JOIN variants "
|
||||
" ON games.variant_id = variants.id "
|
||||
"WHERE games.id = %s",
|
||||
(game_id,)
|
||||
)
|
||||
league_id, num_players, score, num_suits, clue_starved = cur.fetchone()
|
||||
league_id, num_players, score, num_suits, clue_starved, variant_id = cur.fetchone()
|
||||
|
||||
cur.execute("SELECT game_participants.user_id FROM games "
|
||||
"INNER JOIN game_participants "
|
||||
|
@ -84,4 +133,23 @@ def process_rating_of_next_game():
|
|||
raise ValueError(err_msg)
|
||||
|
||||
rating_type = utils.get_rating_type(clue_starved)
|
||||
ratings = get_current_ratings(user_ids, rating_type)
|
||||
user_ratings = get_current_user_ratings(user_ids, rating_type)
|
||||
variant_rating = get_current_variant_rating(variant_id, num_players)
|
||||
|
||||
user_changes, variant_change = rating_change(user_ratings, variant_rating, score == 5 * num_suits)
|
||||
cur.execute("INSERT INTO variant_ratings (league_id, variant_id, player_count, change, value_after) "
|
||||
"VALUES (%s, %s, %s, %s, %s)",
|
||||
(league_id, variant_id, num_players, variant_change, variant_rating + variant_change)
|
||||
)
|
||||
|
||||
user_ratings_vals = []
|
||||
for user_id, change in user_changes.items():
|
||||
user_ratings_vals.append((league_id, user_id, rating_type, change, user_ratings[user_id] + change))
|
||||
|
||||
psycopg2.extras.execute_values(
|
||||
cur,
|
||||
"INSERT INTO user_ratings (league_id, user_id, type, change, value_after) "
|
||||
"VALUES %s",
|
||||
user_ratings_vals
|
||||
)
|
||||
conn_manager.get_connection().commit()
|
||||
|
|
Loading…
Reference in a new issue