Separate sync query to reduce complexity

This commit is contained in:
Kiana Sheibani 2023-09-30 18:16:00 -04:00
parent c70fc7506a
commit e16b0be447
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
7 changed files with 285 additions and 246 deletions

View file

@ -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<f64> {
let qs: Vec<f64> = ratings
.into_iter()
.map(|es| 10_f64.powf(es.iter().map(|x| **x).sum::<f64>() / 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))
}

View file

@ -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<String>, all: bool, auth_token: Option<String>) {
for dataset in datasets {
let last_sync = get_last_sync(&connection, &dataset).unwrap().unwrap();
let results = run_query::<TournamentSets, _>(
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();
}
}

View file

@ -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<String> {
#[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);

120
src/queries/event_sets.rs Normal file
View file

@ -0,0 +1,120 @@
use super::{EntrantId, EventId, PlayerData, PlayerId, QueryUnwrap};
use cynic::GraphQlResponse;
use schema::schema;
pub type Teams<T> = Vec<Vec<T>>;
// 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<Event>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(variables = "EventSetsVars")]
struct Event {
#[arguments(page: $sets_page, perPage: 11)]
sets: Option<SetConnection>,
}
#[derive(cynic::QueryFragment, Debug)]
struct SetConnection {
#[cynic(flatten)]
nodes: Vec<Set>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Set {
#[arguments(includeByes: true)]
#[cynic(flatten)]
slots: Vec<SetSlot>,
winner_id: Option<i32>,
}
#[derive(cynic::QueryFragment, Debug)]
struct SetSlot {
entrant: Option<Entrant>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Entrant {
id: Option<EntrantId>,
#[cynic(flatten)]
participants: Vec<Participant>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Participant {
player: Option<Player>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Player {
id: Option<PlayerId>,
gamer_tag: Option<String>,
prefix: Option<String>,
}
// Unwrap
pub struct SetData {
teams: Teams<PlayerData>,
winner: usize,
}
impl QueryUnwrap<EventSetsVars> for EventSets {
type Unwrapped = Vec<SetData>;
// This might be the most spaghetti code I've ever written
fn unwrap_response(response: GraphQlResponse<EventSets>) -> Option<Vec<SetData>> {
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::<Vec<_>>(),
)
}
}

View file

@ -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<TournamentConnection>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(variables = "TournamentEventsVars")]
struct TournamentConnection {
#[cynic(flatten)]
nodes: Vec<Tournament>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(variables = "TournamentEventsVars")]
struct Tournament {
name: Option<String>,
#[arguments(limit: 99999, filter: { videogameId: [$game_id] })]
#[cynic(flatten)]
events: Vec<Event>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(variables = "TournamentEventsVars")]
struct Event {
id: Option<EventId>,
}
// Unwrap
#[derive(Debug, Clone)]
pub struct TournamentData {
pub name: String,
pub events: Vec<EventId>,
}
impl QueryUnwrap<TournamentEventsVars> for TournamentEvents {
type Unwrapped = Vec<TournamentData>;
fn unwrap_response(response: GraphQlResponse<TournamentEvents>) -> Option<Vec<TournamentData>> {
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(),
)
}
}

View file

@ -1,169 +0,0 @@
use super::{EntrantId, PlayerData, PlayerId, QueryUnwrap, Timestamp, VideogameId};
use cynic::GraphQlResponse;
use schema::schema;
pub type Teams<T> = Vec<Vec<T>>;
// 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<TournamentConnection>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(variables = "TournamentSetsVars")]
struct TournamentConnection {
#[cynic(flatten)]
nodes: Vec<Tournament>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(variables = "TournamentSetsVars")]
struct Tournament {
name: Option<String>,
#[arguments(limit: $event_limit, filter: { videogameId: [$game_id] })]
#[cynic(flatten)]
events: Vec<Event>,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(variables = "TournamentSetsVars")]
struct Event {
#[arguments(page: $set_page, perPage: $set_pagesize)]
sets: Option<SetConnection>,
}
#[derive(cynic::QueryFragment, Debug)]
struct SetConnection {
#[cynic(flatten)]
nodes: Vec<Set>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Set {
#[arguments(includeByes: true)]
#[cynic(flatten)]
slots: Vec<SetSlot>,
winner_id: Option<i32>,
}
#[derive(cynic::QueryFragment, Debug)]
struct SetSlot {
entrant: Option<Entrant>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Entrant {
id: Option<EntrantId>,
#[cynic(flatten)]
participants: Vec<Participant>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Participant {
player: Option<Player>,
}
#[derive(cynic::QueryFragment, Debug)]
struct Player {
id: Option<PlayerId>,
gamer_tag: Option<String>,
prefix: Option<String>,
}
// Unwrap
#[derive(Debug, Clone)]
pub struct TournamentData {
pub name: String,
pub sets: Vec<SetData>,
}
#[derive(Debug, Clone)]
pub struct SetData {
pub teams: Teams<PlayerData>,
pub winner: usize,
}
impl QueryUnwrap<TournamentSetsVars> for TournamentSets {
type Unwrapped = TournamentData;
// This might be the most spaghetti code I've ever written
fn unwrap_response(response: GraphQlResponse<TournamentSets>) -> Option<TournamentData> {
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::<Vec<_>>(),
)
})
.flatten()
.collect();
Some(TournamentData {
name: tour.name?,
sets,
})
}
}

67
src/sync.rs Normal file
View file

@ -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<f64> {
let qs: Vec<f64> = ratings
.into_iter()
.map(|es| 10_f64.powf(es.iter().map(|x| **x).sum::<f64>() / 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<Vec<SetData>> {
let sets = run_query::<EventSets, _>(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))
}
*/