From e16b0be447a7d18a137ffeea50cbbe2614280456 Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Sat, 30 Sep 2023 18:16:00 -0400 Subject: [PATCH] Separate sync query to reduce complexity --- src/datasets.rs | 53 ---------- src/main.rs | 24 +---- src/queries.rs | 10 +- src/queries/event_sets.rs | 120 ++++++++++++++++++++++ src/queries/tournament_events.rs | 88 ++++++++++++++++ src/queries/tournament_sets.rs | 169 ------------------------------- src/sync.rs | 67 ++++++++++++ 7 files changed, 285 insertions(+), 246 deletions(-) create mode 100644 src/queries/event_sets.rs create mode 100644 src/queries/tournament_events.rs delete mode 100644 src/queries/tournament_sets.rs create mode 100644 src/sync.rs diff --git a/src/datasets.rs b/src/datasets.rs index 5b80c62..cfdbac3 100644 --- a/src/datasets.rs +++ b/src/datasets.rs @@ -95,34 +95,6 @@ pub fn update_last_sync(connection: &Connection, dataset: &str, sync: u64) -> sq .try_for_each(|x| x.map(|_| ())) } -// Score calculation - -/// Calculate the collective expected score for each team. -fn expected_scores(ratings: &Teams<&mut f64>) -> Vec { - let qs: Vec = ratings - .into_iter() - .map(|es| 10_f64.powf(es.iter().map(|x| **x).sum::() / es.len() as f64 / 400.0)) - .collect(); - let sumq: f64 = qs.iter().sum(); - qs.into_iter().map(|q| q / sumq).collect() -} - -/// Adjust the ratings of each player based on who won. -fn adjust_ratings(ratings: Teams<&mut f64>, winner: usize) { - let exp_scores = expected_scores(&ratings); - - ratings - .into_iter() - .zip(exp_scores.into_iter()) - .enumerate() - .for_each(|(i, (es, exp_sc))| { - let len = es.len() as f64; - let score = f64::from(winner == i); - es.into_iter() - .for_each(|e| *e += 40.0 * (score - exp_sc) / len); - }) -} - // Database Updating pub fn add_players( @@ -186,28 +158,3 @@ pub fn update_ratings( }) }) } - -fn update_from_set(connection: &Connection, dataset: &str, results: SetData) -> sqlite::Result<()> { - let players_data = results.teams; - add_players(connection, dataset, &players_data)?; - - let mut elos = get_ratings(connection, dataset, &players_data)?; - adjust_ratings( - elos.iter_mut() - .map(|v| v.iter_mut().map(|x| &mut x.1).collect()) - .collect(), - results.winner, - ); - update_ratings(connection, dataset, elos) -} - -pub fn update_from_tournament( - connection: &Connection, - dataset: &str, - results: TournamentData, -) -> sqlite::Result<()> { - results - .sets - .into_iter() - .try_for_each(|set| update_from_set(connection, dataset, set)) -} diff --git a/src/main.rs b/src/main.rs index 8b8d48e..da95457 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ mod queries; use queries::*; mod datasets; use datasets::*; +mod sync; +use sync::*; /// ## CLI Structs @@ -162,27 +164,5 @@ fn sync(datasets: Vec, all: bool, auth_token: Option) { for dataset in datasets { let last_sync = get_last_sync(&connection, &dataset).unwrap().unwrap(); - - let results = run_query::( - TournamentSetsVars { - last_query: Timestamp(last_sync), - game_id: VideogameId(1), - tournament: 1, - set_page: 1, - set_pagesize: 50, - event_limit: 9999999, - }, - &auth, - ) - .unwrap(); - - update_from_tournament(&connection, &dataset, results).unwrap(); - - let current_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - - update_last_sync(&connection, &dataset, current_time).unwrap(); } } diff --git a/src/queries.rs b/src/queries.rs index 9d593cc..6f1d9de 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -5,8 +5,10 @@ use std::path::Path; pub mod search_games; pub use search_games::*; -pub mod tournament_sets; -pub use tournament_sets::*; +pub mod tournament_events; +pub use tournament_events::*; +pub mod event_sets; +pub use event_sets::*; pub mod player_info; pub use player_info::*; @@ -47,6 +49,10 @@ pub fn get_auth_token(config_dir: &Path) -> Option { #[cynic(graphql_type = "ID")] pub struct VideogameId(pub u64); +#[derive(cynic::Scalar, Debug, Copy, Clone)] +#[cynic(graphql_type = "ID")] +pub struct EventId(pub u64); + #[derive(cynic::Scalar, Debug, Copy, Clone)] #[cynic(graphql_type = "ID")] pub struct EntrantId(pub u64); diff --git a/src/queries/event_sets.rs b/src/queries/event_sets.rs new file mode 100644 index 0000000..5f671a2 --- /dev/null +++ b/src/queries/event_sets.rs @@ -0,0 +1,120 @@ +use super::{EntrantId, EventId, PlayerData, PlayerId, QueryUnwrap}; +use cynic::GraphQlResponse; +use schema::schema; + +pub type Teams = Vec>; + +// Variables + +#[derive(cynic::QueryVariables, Debug)] +pub struct EventSetsVars { + pub event: EventId, + pub sets_page: i32, +} + +// Query + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "EventSetsVars")] +pub struct EventSets { + #[arguments(id: $event)] + event: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(variables = "EventSetsVars")] +struct Event { + #[arguments(page: $sets_page, perPage: 11)] + sets: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +struct SetConnection { + #[cynic(flatten)] + nodes: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +struct Set { + #[arguments(includeByes: true)] + #[cynic(flatten)] + slots: Vec, + winner_id: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +struct SetSlot { + entrant: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +struct Entrant { + id: Option, + #[cynic(flatten)] + participants: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +struct Participant { + player: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +struct Player { + id: Option, + gamer_tag: Option, + prefix: Option, +} + +// Unwrap + +pub struct SetData { + teams: Teams, + winner: usize, +} + +impl QueryUnwrap for EventSets { + type Unwrapped = Vec; + + // This might be the most spaghetti code I've ever written + fn unwrap_response(response: GraphQlResponse) -> Option> { + Some( + response + .data? + .event? + .sets? + .nodes + .into_iter() + .filter_map(|set| { + let winner_id = set.winner_id?; + let winner = set.slots.iter().position(|slot| { + slot.entrant + .as_ref() + .and_then(|x| x.id) + .map(|id| id.0 == winner_id as u64) + .unwrap_or(false) + })?; + let teams = set + .slots + .into_iter() + .map(|slot| { + slot.entrant? + .participants + .into_iter() + .map(|p| { + let p_ = p.player?; + Some(PlayerData { + id: p_.id?, + name: p_.gamer_tag, + prefix: p_.prefix, + }) + }) + .try_collect() + }) + .try_collect()?; + Some(SetData { teams, winner }) + }) + .collect::>(), + ) + } +} diff --git a/src/queries/tournament_events.rs b/src/queries/tournament_events.rs new file mode 100644 index 0000000..5a47751 --- /dev/null +++ b/src/queries/tournament_events.rs @@ -0,0 +1,88 @@ +use super::QueryUnwrap; +use super::{EventId, Timestamp, VideogameId}; +use cynic::GraphQlResponse; +use schema::schema; + +// Variables + +#[derive(cynic::QueryVariables, Debug)] +pub struct TournamentEventsVars { + // HACK: This should really be an optional variable, but there seems to be a + // server-side bug that completely breaks everything when this isn't passed. + // We can use a dummy value of 1 when we don't want to filter by time. + pub last_query: Timestamp, + pub game_id: VideogameId, + pub page: i32, +} + +// Query + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(graphql_type = "Query", variables = "TournamentEventsVars")] +pub struct TournamentEvents { + #[arguments(query: { + page: $page, + perPage: 300, + sortBy: "endAt asc", + filter: { + past: true, + afterDate: $last_query, + videogameIds: [$game_id], + }})] + tournaments: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(variables = "TournamentEventsVars")] +struct TournamentConnection { + #[cynic(flatten)] + nodes: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(variables = "TournamentEventsVars")] +struct Tournament { + name: Option, + #[arguments(limit: 99999, filter: { videogameId: [$game_id] })] + #[cynic(flatten)] + events: Vec, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(variables = "TournamentEventsVars")] +struct Event { + id: Option, +} + +// Unwrap + +#[derive(Debug, Clone)] +pub struct TournamentData { + pub name: String, + pub events: Vec, +} + +impl QueryUnwrap for TournamentEvents { + type Unwrapped = Vec; + + fn unwrap_response(response: GraphQlResponse) -> Option> { + Some( + response + .data? + .tournaments? + .nodes + .into_iter() + .filter_map(|tour| { + Some(TournamentData { + name: tour.name?, + events: tour + .events + .into_iter() + .filter_map(|event| event.id) + .collect(), + }) + }) + .collect(), + ) + } +} diff --git a/src/queries/tournament_sets.rs b/src/queries/tournament_sets.rs deleted file mode 100644 index 5be5904..0000000 --- a/src/queries/tournament_sets.rs +++ /dev/null @@ -1,169 +0,0 @@ -use super::{EntrantId, PlayerData, PlayerId, QueryUnwrap, Timestamp, VideogameId}; -use cynic::GraphQlResponse; -use schema::schema; - -pub type Teams = Vec>; - -// Variables - -#[derive(cynic::QueryVariables, Debug)] -pub struct TournamentSetsVars { - // HACK: This should really be an optional variable, but there seems to be a - // server-side bug that completely breaks everything when this isn't passed. - // We can use a dummy value of 1 when we don't want to filter by time. - pub last_query: Timestamp, - pub game_id: VideogameId, - - pub tournament: i32, - pub event_limit: i32, - pub set_page: i32, - pub set_pagesize: i32, -} - -// Query - -#[derive(cynic::QueryFragment, Debug)] -#[cynic(graphql_type = "Query", variables = "TournamentSetsVars")] -pub struct TournamentSets { - #[arguments(query: { - page: $tournament, - perPage: 1, - sortBy: "endAt asc", - filter: { - past: true, - afterDate: $last_query, - videogameIds: [$game_id], - }})] - tournaments: Option, -} - -#[derive(cynic::QueryFragment, Debug)] -#[cynic(variables = "TournamentSetsVars")] -struct TournamentConnection { - #[cynic(flatten)] - nodes: Vec, -} - -#[derive(cynic::QueryFragment, Debug)] -#[cynic(variables = "TournamentSetsVars")] -struct Tournament { - name: Option, - #[arguments(limit: $event_limit, filter: { videogameId: [$game_id] })] - #[cynic(flatten)] - events: Vec, -} - -#[derive(cynic::QueryFragment, Debug)] -#[cynic(variables = "TournamentSetsVars")] -struct Event { - #[arguments(page: $set_page, perPage: $set_pagesize)] - sets: Option, -} - -#[derive(cynic::QueryFragment, Debug)] -struct SetConnection { - #[cynic(flatten)] - nodes: Vec, -} - -#[derive(cynic::QueryFragment, Debug)] -struct Set { - #[arguments(includeByes: true)] - #[cynic(flatten)] - slots: Vec, - winner_id: Option, -} - -#[derive(cynic::QueryFragment, Debug)] -struct SetSlot { - entrant: Option, -} - -#[derive(cynic::QueryFragment, Debug)] -struct Entrant { - id: Option, - #[cynic(flatten)] - participants: Vec, -} - -#[derive(cynic::QueryFragment, Debug)] -struct Participant { - player: Option, -} - -#[derive(cynic::QueryFragment, Debug)] -struct Player { - id: Option, - gamer_tag: Option, - prefix: Option, -} - -// Unwrap - -#[derive(Debug, Clone)] -pub struct TournamentData { - pub name: String, - pub sets: Vec, -} - -#[derive(Debug, Clone)] -pub struct SetData { - pub teams: Teams, - pub winner: usize, -} - -impl QueryUnwrap for TournamentSets { - type Unwrapped = TournamentData; - - // This might be the most spaghetti code I've ever written - fn unwrap_response(response: GraphQlResponse) -> Option { - let tour = response.data?.tournaments?.nodes.into_iter().next()?; - let sets = tour - .events - .into_iter() - .filter_map(|event| { - Some( - event - .sets? - .nodes - .into_iter() - .filter_map(|set| { - let winner_id = set.winner_id?; - let winner = set.slots.iter().position(|slot| { - slot.entrant - .as_ref() - .and_then(|x| x.id) - .map(|id| id.0 == winner_id as u64) - .unwrap_or(false) - })?; - let teams = set - .slots - .into_iter() - .map(|slot| { - slot.entrant? - .participants - .into_iter() - .map(|p| { - let p_ = p.player?; - Some(PlayerData { - id: p_.id?, - name: p_.gamer_tag, - prefix: p_.prefix, - }) - }) - .try_collect() - }) - .try_collect()?; - Some(SetData { teams, winner }) - }) - .collect::>(), - ) - }) - .flatten() - .collect(); - Some(TournamentData { - name: tour.name?, - sets, - }) - } -} diff --git a/src/sync.rs b/src/sync.rs new file mode 100644 index 0000000..4068696 --- /dev/null +++ b/src/sync.rs @@ -0,0 +1,67 @@ +use crate::datasets::*; +use crate::queries::*; +use sqlite::*; + +// Score calculation + +/// Calculate the collective expected score for each team. +fn expected_scores(ratings: &Teams<&mut f64>) -> Vec { + let qs: Vec = ratings + .into_iter() + .map(|es| 10_f64.powf(es.iter().map(|x| **x).sum::() / es.len() as f64 / 400.0)) + .collect(); + let sumq: f64 = qs.iter().sum(); + qs.into_iter().map(|q| q / sumq).collect() +} + +/// Adjust the ratings of each player based on who won. +fn adjust_ratings(ratings: Teams<&mut f64>, winner: usize) { + let exp_scores = expected_scores(&ratings); + + ratings + .into_iter() + .zip(exp_scores.into_iter()) + .enumerate() + .for_each(|(i, (es, exp_sc))| { + let len = es.len() as f64; + let score = f64::from(winner == i); + es.into_iter() + .for_each(|e| *e += 40.0 * (score - exp_sc) / len); + }) +} + +// Extract set data + +fn get_event_sets(event: EventId, auth: &str) -> Option> { + let sets = run_query::(EventSetsVars { + event, + sets_page: 1, + }); +} + +/* +fn update_from_set(connection: &Connection, dataset: &str, results: SetData) -> sqlite::Result<()> { + let players_data = results.teams; + add_players(connection, dataset, &players_data)?; + + let mut elos = get_ratings(connection, dataset, &players_data)?; + adjust_ratings( + elos.iter_mut() + .map(|v| v.iter_mut().map(|x| &mut x.1).collect()) + .collect(), + results.winner, + ); + update_ratings(connection, dataset, elos) +} + +pub fn update_from_tournament( + connection: &Connection, + dataset: &str, + results: TournamentData, +) -> sqlite::Result<()> { + results + .sets + .into_iter() + .try_for_each(|set| update_from_set(connection, dataset, set)) +} +*/