Skip to content

Commit

Permalink
Stats view (#25)
Browse files Browse the repository at this point in the history
Stats can now be viewed in the `Stats` tab. Both player/team and hitting/pitching can be toggled. Additionally, individual columns can be toggled on and off.
  • Loading branch information
andschneider authored Jul 13, 2021
1 parent f84db7d commit 0314930
Show file tree
Hide file tree
Showing 19 changed files with 10,435 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Stats view: [PR 25](https://github.com/mlb-rs/mlbt/pull/25)
- Added a panic hook to print a nice stack trace on crash

### Fixed
Expand Down
13 changes: 8 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ edition = "2018"
members = [".", "api"]

[dependencies]
mlb-api = {path = "api/", version = "0.0.5-alpha1"}
mlb-api = {path = "api/", version = "0.0.6-alpha2"}

better-panic = "0.2"
chrono = "0.4"
chrono-tz = "0.5"
crossbeam-channel = "0.5"
crossterm = "0.20"
indexmap = "1.7.0"
lazy_static = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tui = { version = "0.15", default-features = false, features = ["crossterm","serde"] }
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ TODO - add to crates.io
- [X] standings
- [ ] team information

- [ ] stats
- [ ] player stats
- [ ] team stats
- [X] stats
- [X] player stats
- [X] team stats
- [ ] stat search (store in sqlite or an embedded db?)

- [ ] CLI
Expand Down Expand Up @@ -140,7 +140,26 @@ To switch the team displayed in the box score:

Press `3` to activate this tab.

TODO
You can switch between `pitching` and `hitting` stats and filter based on `team`
or `player` using:

- `p`: pitching
- `h`: hitting
- `t`: team
- `l`: player

Within each stat group (pitching or hitting) you can toggle the display of
individual stat columns by selecting the stat with `Enter`. This selection pane
can be turned on/off with `o`.

- `j`: move down
- `k`: move up
- `Enter`: toggle stat column
- `o`: toggle stat selection pane

> If your terminal is too small to display all columns, they will be turned off
> starting from the left side.

### Standings

Expand Down
2 changes: 1 addition & 1 deletion api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mlb-api"
version = "0.0.5-alpha1"
version = "0.0.6-alpha2"
authors = ["Andrew Schneider <[email protected]>"]
edition = "2018"

Expand Down
53 changes: 53 additions & 0 deletions api/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::fmt;

use crate::live::LiveResponse;
use crate::schedule::ScheduleResponse;
use crate::standings::StandingsResponse;
use crate::stats::StatResponse;

use chrono::{DateTime, Datelike, Local, NaiveDate};
use derive_builder::Builder;
Expand All @@ -19,6 +22,28 @@ pub struct MLBApi {
base_url: String,
}

/// The available stat groups. These are taken from the "meta" endpoint:
/// https://statsapi.mlb.com/api/v1/statGroups
/// I only need to use Hitting and Pitching for now.
#[derive(Clone, Debug)]
pub enum StatGroup {
Hitting,
Pitching,
// Fielding,
// Catching,
// Running,
// Game,
// Team,
// Streak,
}

/// Display the StatGroup in all lowercase.
impl fmt::Display for StatGroup {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", format!("{:?}", self).to_lowercase())
}
}

impl MLBApi {
pub fn get_todays_schedule(&self) -> ScheduleResponse {
let url = format!("{}v1/schedule?sportId=1", self.base_url);
Expand Down Expand Up @@ -56,6 +81,28 @@ impl MLBApi {
self.get::<StandingsResponse>(url)
}

pub fn get_team_stats(&self, group: StatGroup) -> StatResponse {
let local: DateTime<Local> = Local::now();
let url = format!(
"{}v1/teams/stats?sportId=1&stats=season&season={}&group={}",
self.base_url,
local.year().to_string(),
group
);
self.get::<StatResponse>(url)
}

pub fn get_player_stats(&self, group: StatGroup) -> StatResponse {
let local: DateTime<Local> = Local::now();
let url = format!(
"{}v1/stats?stats=season&season={}&group={}",
self.base_url,
local.year().to_string(),
group
);
self.get::<StatResponse>(url)
}

// TODO need better error handling, especially on parsing
fn get<T: Default + DeserializeOwned>(&self, url: String) -> T {
let response = self.client.get(url).send().unwrap_or_else(|err| {
Expand All @@ -67,3 +114,9 @@ impl MLBApi {
})
}
}

#[test]
fn test_stat_group_lowercase() {
assert_eq!("hitting".to_string(), StatGroup::Hitting.to_string());
assert_eq!("pitching".to_string(), StatGroup::Pitching.to_string());
}
1 change: 1 addition & 0 deletions api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pub mod live;
pub mod plays;
pub mod schedule;
pub mod standings;
pub mod stats;
151 changes: 151 additions & 0 deletions api/src/stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use crate::schedule::IdNameLink;
use serde::{Deserialize, Serialize};

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct StatResponse {
pub stats: Vec<Stat>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Stat {
#[serde(rename = "type")]
pub stat_type: DisplayName,
pub group: DisplayName,
pub total_splits: u16,
pub splits: Vec<Split>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DisplayName {
pub display_name: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Split {
season: String,
pub stat: StatSplit,
pub team: IdNameLink,
pub player: Option<Player>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Player {
pub id: u64,
pub full_name: String,
pub first_name: String,
pub last_name: String,
}

/// StatSplit stores the two options for deserializing a Split.
/// It uses the `untagged` enum representation to determine which one.
/// https://serde.rs/enum-representations.html#untagged
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StatSplit {
Pitching(PitchingStat),
Hitting(HittingStat),
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PitchingStat {
pub wins: u16,
pub losses: u16,
pub era: String,
pub games_played: u16,
pub games_started: u16,
pub complete_games: u16,
pub shutouts: u16,
pub saves: u16,
pub save_opportunities: u16,
ground_outs: u16,
air_outs: u16,
pub innings_pitched: String,
pub hits: u16,
pub runs: u16,
pub earned_runs: u16,
doubles: i64,
triples: i64,
pub home_runs: u16,
pub hit_batsmen: u16,
pub base_on_balls: u16,
pub strike_outs: u16,
intentional_walks: u16,
hit_by_pitch: u16,
pub whip: String,
pub avg: String,
at_bats: u16,
obp: String,
slg: String,
ops: String,
caught_stealing: u16,
stolen_bases: u16,
stolen_base_percentage: String,
ground_into_double_play: u16,
number_of_pitches: u16,
holds: u16,
blown_saves: u16,
batters_faced: u16,
outs: u16,
games_pitched: u16,
strikes: u32,
strike_percentage: String,
balks: u16,
wild_pitches: u16,
pickoffs: u16,
total_bases: u16,
ground_outs_to_airouts: String,
win_percentage: String,
pitches_per_inning: String,
games_finished: u16,
strikeout_walk_ratio: String,
strikeouts_per9_inn: String,
walks_per9_inn: String,
hits_per9_inn: String,
runs_scored_per9: String,
home_runs_per9: String,
catchers_interference: u16,
sac_bunts: u16,
sac_flies: u16,
}

#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HittingStat {
pub games_played: u16,
pub ground_outs: u16,
pub air_outs: u16,
pub runs: u16,
pub doubles: u16,
pub triples: u16,
pub home_runs: u16,
pub strike_outs: u16,
pub base_on_balls: u16,
pub intentional_walks: u16,
pub hits: u16,
pub hit_by_pitch: u16,
pub avg: String,
pub at_bats: u16,
pub obp: String,
pub slg: String,
pub ops: String,
pub caught_stealing: u16,
pub stolen_bases: u16,
pub stolen_base_percentage: String,
pub ground_into_double_play: u16,
pub number_of_pitches: u16,
pub plate_appearances: u16,
pub total_bases: u16,
pub rbi: u16,
pub left_on_base: u16,
pub sac_bunts: u16,
pub sac_flies: u16,
pub babip: String,
pub ground_outs_to_airouts: String,
pub catchers_interference: u16,
pub at_bats_per_home_run: String,
}
Loading

0 comments on commit 0314930

Please sign in to comment.