Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save highest ranks #77

Merged
merged 4 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions env_example
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
API_ROOT=
CLIENT_ID=
CLIENT_SECRET=
CONNECTION_STRING=
122 changes: 108 additions & 14 deletions src/database/db.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{model::structures::ruleset::Ruleset, utils::progress_utils::progress_bar};
use indexmap::map::Slice;
use itertools::Itertools;
use postgres_types::ToSql;
use std::{collections::HashMap, sync::Arc};
use tokio_postgres::{Client, Error, NoTls, Row};

use super::db_structs::{Game, GameScore, Match, Player, PlayerRating, RatingAdjustment, RulesetData};
use super::db_structs::{
Game, GameScore, Match, Player, PlayerHighestRank, PlayerRating, RatingAdjustment, RulesetData
};

#[derive(Clone)]
pub struct DbClient {
Expand Down Expand Up @@ -119,26 +119,22 @@ impl DbClient {
id: row.get("player_id"),
username: row.get("username"),
country: row.get("country"),
ruleset_data: match self.ruleset_data_from_row(&row) {
Some(data) => Some(vec![data]),
None => None
}
ruleset_data: self.ruleset_data_from_row(&row).map(|data| vec![data])
};
players.push(player);
current_player_id = row.get("player_id");
} else {
// Same player, new ruleset data

let data = self.ruleset_data_from_row(&row);
match data {
Some(ruleset_data) => players
if let Some(ruleset_data) = data {
players
.last_mut()
.unwrap()
.ruleset_data
.clone()
.unwrap_or_default()
.push(ruleset_data),
None => ()
.push(ruleset_data);
}
}
}
Expand All @@ -159,7 +155,7 @@ impl DbClient {
}

return Some(RulesetData {
ruleset: Ruleset::try_from(parsed_ruleset.unwrap()).unwrap(),
ruleset: parsed_ruleset.unwrap(),
global_rank: global_rank.unwrap(),
earliest_global_rank: earliest_global_rank.unwrap()
});
Expand All @@ -172,10 +168,16 @@ impl DbClient {
self.truncate_rating_adjustments().await;
self.truncate_player_ratings().await;

self.save_ratings_and_adjustments_with_mapping(&player_ratings).await;

self.insert_or_update_highest_ranks(player_ratings).await;
}

async fn save_ratings_and_adjustments_with_mapping(&self, player_ratings: &&[PlayerRating]) {
let p_bar = progress_bar(player_ratings.len() as u64, "Saving player ratings to db".to_string()).unwrap();

let mut mapping: HashMap<i32, Vec<RatingAdjustment>> = HashMap::new();
let parent_ids = self.save_player_ratings(&player_ratings).await;
let parent_ids = self.save_player_ratings(player_ratings).await;

p_bar.inc(1);
p_bar.finish();
Expand All @@ -188,6 +190,8 @@ impl DbClient {
println!("Adjustment parent_id mapping created");

self.save_rating_adjustments(&mapping).await;

println!("Rating adjustments saved");
}

/// Save all rating adjustments in a single batch query
Expand All @@ -200,7 +204,11 @@ impl DbClient {
// Collect parameters for batch insertion
let mut values: Vec<String> = Vec::new();

let p_bar = progress_bar(adjustment_mapping.len() as u64, "Creating rating adjustment queries".to_string()).unwrap();
let p_bar = progress_bar(
adjustment_mapping.len() as u64,
"Creating rating adjustment queries".to_string()
)
.unwrap();
for (player_rating_id, adjustments) in adjustment_mapping.iter() {
for adjustment in adjustments {
// Create a tuple for each adjustment
Expand Down Expand Up @@ -269,6 +277,92 @@ impl DbClient {
rows.iter().map(|row| row.get("id")).collect()
}

async fn insert_or_update_highest_ranks(&self, player_ratings: &[PlayerRating]) {
println!("Fetching all highest ranks");
let current_highest_ranks = self.get_highest_ranks().await;

println!("Found {} highest ranks", current_highest_ranks.len());
// If the current rank is None, create it. If the current rank is Some and
// either the PlayerRating's global rank or country rank is higher than the current highest
// rank, update it.
//
// Only update values which are higher than the current highest rank

let pbar = progress_bar(player_ratings.len() as u64, "Updating highest ranks".to_string()).unwrap();

for rating in player_ratings {
if let Some(Some(current_rank)) = current_highest_ranks.get(&(rating.player_id, rating.ruleset)) {
if rating.global_rank < current_rank.global_rank {
self.update_highest_rank(rating.player_id, rating).await;
}
} else {
self.insert_highest_rank(rating.player_id, rating).await;
}

pbar.inc(1);
}
}

async fn get_highest_ranks(&self) -> HashMap<(i32, Ruleset), Option<PlayerHighestRank>> {
let query = "SELECT * FROM player_highest_ranks";
let row = self.client.query(query, &[]).await.ok();

match row {
Some(rows) => {
let mut map: HashMap<(i32, Ruleset), Option<PlayerHighestRank>> = HashMap::new();
for row in rows {
let player_id = row.get::<_, i32>("player_id");
let ruleset = Ruleset::try_from(row.get::<_, i32>("ruleset")).unwrap();
map.insert(
(player_id, ruleset),
Some(PlayerHighestRank {
id: row.get("id"),
player_id,
global_rank: row.get("global_rank"),
global_rank_date: row.get("global_rank_date"),
country_rank: row.get("country_rank"),
country_rank_date: row.get("country_rank_date"),
ruleset
})
);
}

map
}
None => HashMap::new()
}
}

async fn insert_highest_rank(&self, player_id: i32, player_rating: &PlayerRating) {
let timestamp = player_rating.adjustments.last().unwrap().timestamp;
let query = "INSERT INTO player_highest_ranks (player_id, ruleset, global_rank, global_rank_date, country_rank, country_rank_date) VALUES ($1, $2, $3, $4, $5, $6)";
let values: &[&(dyn ToSql + Sync)] = &[
&player_id,
&(player_rating.ruleset as i32),
&player_rating.global_rank,
&timestamp,
&player_rating.country_rank,
&timestamp
];

self.client.execute(query, values).await.unwrap();
}

async fn update_highest_rank(&self, player_id: i32, player_rating: &PlayerRating) {
let timestamp = player_rating.adjustments.last().unwrap().timestamp;
let query = "UPDATE player_highest_ranks SET global_rank = $1, global_rank_date = $2, country_rank = $3, country_rank_date = $4 WHERE player_id = $5 AND ruleset = $6";
let values: &[&(dyn ToSql + Sync)] = &[
&player_rating.global_rank,
&timestamp,
&player_rating.country_rank,
&timestamp,
&player_id,
&(player_rating.ruleset as i32)
];

self.client.execute(query, values).await.unwrap();
}

async fn truncate_player_ratings(&self) {
self.client
.execute("TRUNCATE TABLE player_ratings CASCADE", &[])
Expand Down
11 changes: 11 additions & 0 deletions src/database/db_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,14 @@ pub struct RatingAdjustment {
pub timestamp: DateTime<FixedOffset>,
pub adjustment_type: RatingAdjustmentType
}

#[derive(Serialize)]
pub struct PlayerHighestRank {
pub id: i32,
pub ruleset: Ruleset,
pub global_rank: i32,
pub global_rank_date: DateTime<FixedOffset>,
pub country_rank: i32,
pub country_rank_date: DateTime<FixedOffset>,
pub player_id: i32
}
2 changes: 1 addition & 1 deletion src/model/rating_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn initial_rating(player: &Player, ruleset: &Ruleset) -> f64 {
Some(r) => mu_from_rank(r, *ruleset),
None => DEFAULT_RATING
}
},
}
None => DEFAULT_RATING
}
}
Expand Down
6 changes: 1 addition & 5 deletions src/utils/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,7 @@ pub fn generate_player_rating(
}
}

pub fn generate_ruleset_data(
ruleset: Ruleset,
global_rank: i32,
earliest_global_rank: Option<i32>
) -> RulesetData {
pub fn generate_ruleset_data(ruleset: Ruleset, global_rank: i32, earliest_global_rank: Option<i32>) -> RulesetData {
RulesetData {
ruleset,
global_rank,
Expand Down
Loading