diff --git a/DESCRIPTION b/DESCRIPTION index 70517a5f..9cc9c3c4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: ffscrapr Title: API Client for Fantasy Football League Platforms -Version: 1.4.8.14 +Version: 1.4.8.15 Authors@R: c(person(given = "Tan", family = "Ho", diff --git a/NEWS.md b/NEWS.md index 832005d5..153ba5f3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -24,6 +24,8 @@ and keep NA data where a player is not in a slot. (v1.4.8.07) - Bugfix ff_scoringhistory to handle new-format `load_rosters()` now that it returns row per player-team-season (v1.4.8.13) (thanks @john-b-edwards!) - Bugfix espn `ff_franchises()` to return coalesce of name/nickname (v1.4.8.14) +- Bugfix espn `ff_starters()` to return handle multi-week formats (v1.4.8.15) (#421) +(h/t @tonyelhabr 🤠) # ffscrapr 1.4.8 diff --git a/R/espn_starters.R b/R/espn_starters.R index 139094db..6e22adbf 100644 --- a/R/espn_starters.R +++ b/R/espn_starters.R @@ -22,9 +22,19 @@ ff_starters.espn_conn <- function(conn, weeks = 1:17, ...) { checkmate::assert_numeric(weeks) - max_week <- .espn_week_checkmax(conn) + settings_url_query <- glue::glue( + "https://fantasy.espn.com/apis/v3/games/ffl/seasons/", + "{conn$season}/segments/0/leagues/{conn$league_id}", + "?scoringPeriodId=0&view=mSettings" + ) + + settings <- espn_getendpoint_raw(conn, settings_url_query) + + week_matchup_periods_mapping <- .espn_week_matchup_periods_mapping(settings) + max_week <- .espn_week_checkmax(settings) run_weeks <- weeks[weeks < max_week] + named_run_weeks <- week_matchup_periods_mapping[run_weeks] if (length(run_weeks) == 0) { warning( @@ -38,7 +48,16 @@ ff_starters.espn_conn <- function(conn, weeks = 1:17, ...) { return(NULL) } - starters <- purrr::map_dfr(run_weeks, .espn_week_starter, conn) %>% + starters <- purrr::imap_dfr( + named_run_weeks, + function(.x, .y) { + .espn_week_starter( + matchup_period = .x, + nfl_week = .y, + conn = conn + ) + } + ) %>% dplyr::mutate( lineup_slot = .espn_lineupslot_map()[as.character(.data$lineup_id)] %>% unname(), pos = .espn_pos_map()[as.character(.data$pos)] %>% unname(), @@ -67,14 +86,7 @@ ff_starters.espn_conn <- function(conn, weeks = 1:17, ...) { return(starters) } -.espn_week_checkmax <- function(conn) { - url_query <- glue::glue( - "https://fantasy.espn.com/apis/v3/games/ffl/seasons/", - "{conn$season}/segments/0/leagues/{conn$league_id}", - "?scoringPeriodId=0&view=mSettings" - ) - - settings <- espn_getendpoint_raw(conn, url_query) +.espn_week_checkmax <- function(settings) { current_week <- settings %>% purrr::pluck("content", "status", "latestScoringPeriod") @@ -87,11 +99,36 @@ ff_starters.espn_conn <- function(conn, weeks = 1:17, ...) { return(max_week) } -.espn_week_starter <- function(week, conn) { +.espn_week_matchup_periods_mapping <- function(settings) { + + matchup_periods <- settings %>% + purrr::pluck("content", "settings", "scheduleSettings", "matchupPeriods") + + ## first, flatten out the values. + mapping <- matchup_periods %>% + purrr::map( + function(.x) { + unlist(.x) + } + ) + + ## then, invert the mapping such that the NFL week is the name and the matchupPeriod + ## (whic is always <= NFL week) is the value. + inverted_mapping <- list() + for (name in names(mapping)) { + values <- mapping[[name]] + for (value in values) { + inverted_mapping[[as.character(value)]] <- as.integer(name) + } + } + return(inverted_mapping) +} + +.espn_week_starter <- function(matchup_period, nfl_week, conn) { url_query <- glue::glue( "https://fantasy.espn.com/apis/v3/games/ffl/seasons/", "{conn$season}/segments/0/leagues/{conn$league_id}", - "?scoringPeriodId={week}&view=mMatchupScore&view=mBoxscore&view=mSettings&view=mRosterSettings" + "?scoringPeriodId={nfl_week}&view=mMatchupScore&view=mBoxscore&view=mSettings&view=mRosterSettings" ) week_scores <- espn_getendpoint_raw(conn, url_query) %>% @@ -99,7 +136,7 @@ ff_starters.espn_conn <- function(conn, weeks = 1:17, ...) { tibble::tibble() %>% purrr::set_names("x") %>% tidyr::hoist(1, "week" = "matchupPeriodId", "home", "away") %>% - dplyr::filter(.data$week == .env$week) %>% + dplyr::filter(.data$week == .env$matchup_period) %>% tidyr::pivot_longer(c("home", "away"), names_to = NULL, values_to = "team") %>% tidyr::hoist( "team", diff --git a/tests/testthat/test-ff_starters.R b/tests/testthat/test-ff_starters.R index 8267980e..6c2eb0a8 100644 --- a/tests/testthat/test-ff_starters.R +++ b/tests/testthat/test-ff_starters.R @@ -32,3 +32,11 @@ test_that("ff_starters.espn finds the correct projected scores #397",{ n_projection_equal_actual <- sum(s$player_score == s$projected_score & s$player_score!=0) expect_equal(n_projection_equal_actual, 0) }) + +test_that("ff_starters.espn understands multi-week playoff formats #421",{ + local_mock_api() + tony_conn <- espn_connect(season = 2022, league_id = 899513) + tony_starters <- ff_starters(tony_conn, weeks = 15:18) + expect_tibble(tony_starters, min.rows = 100) +}) +