nhl_edge_goalie_5v5_top_10(),
nhl_edge_goalie_edge_save_pctg_top_10(),
nhl_edge_goalie_shot_location_top_10(),
nhl_edge_skater_shot_location_top_10(),
nhl_edge_skater_shot_speed_top_10(),
nhl_edge_skater_speed_top_10(),
nhl_edge_skater_zone_time_top_10(),
nhl_edge_team_shot_location_top_10(), and
nhl_edge_team_skating_speed_top_10(). They now emit a
lifecycle::deprecate_warn() and return NULL. The Edge "top-10" wrappers
that remain live (skater distance, team skating-distance, team zone-time)
are unaffected.pwhl_playoff_bracket() errored with $ operator is invalid for atomic vectors and returned NULL. The HockeyTech feed returns
team1/team2 as bare id strings (not nested objects) with wins in
sibling team1_wins/team2_wins fields; the parser now reads these
correctly.nhl_schedule(include_data_flags = TRUE) failed with
`x` and `y` must share the same src and returned a schedule with no
flag columns. load_nhl_games() returns a data.table, so the flag
left-join was being routed through the dtplyr backend, and the data.table
[, <character>] subset evaluated to the literal column names rather than
selecting columns. The index is now coerced to a tibble before the join, so
the 16 data-availability flag columns attach correctly.terminate_on to httr::RETRY
so permanent client errors (400/401/403/404/410/422) are not retried — the
functions still return NULL gracefully, but without the wasteful retry
loop and log noise. Transient 5xx/timeouts are still retried.test-load_nhl.R and test-calculate_xg.R no
longer skip_on_ci(), so the NHL loaders and the xG pipeline are exercised
on CI (xgboost is installed explicitly in the workflow). Loader tests now
request seasons that are actually published per table and assert on the keys
each release carries.col_name | types | description table, matching the convention used in
hoopR, wehoop, and cfbfastR. Functions that return a named list of
data frames get one table per frame. Column names and types were captured
from live API responses, so the tables reflect the actual returned schema.nhl_schedule() gains a game_type parameter accepting "both" (default),
"regular", or "playoffs". In season mode the function now returns
regular-season and playoff games in a single tibble. Three new columns
(series_letter, playoff_round, series_game_number) are appended to
the output; they are NA on regular-season rows.nhl_schedule(season = ...) previously returned only
what the club-schedule-season endpoint provided, which mixed preseason
(PR), regular (R), and playoff (P) rows. With this release:
game_type defaults to "both", so playoff games are now included by
default. Pass game_type = "regular" to restore regular-only behavior.PR) rows are no longer returned by season-mode calls
regardless of game_type. Use nhl_schedule(day = ...) to retrieve
preseason game schedules.nhl_playoff_schedule() is unchanged in its public API; internally it now
delegates its HTTP call to a new shared internal helper
.fetch_playoff_series().nhl_schedule() previously returned the same home_team_name,
away_team_name, home_score, away_score, and venue value in every
row regardless of the actual game. Root cause was an ifelse(scalar, vec, NA) guard in .parse_schedule_games() and .parse_club_schedule_games()
that collapsed each vector to length 1, which tibble then recycled
across all rows. Replaced with if/else, and added regression tests
asserting per-row distinct values. home_team_abbr, away_team_abbr,
and the other directly-assigned columns were not affected.nhl_player_info() used the same ifelse(scalar, vec, NA)
idiom across all 18 of its nullable fields. The function returns a 1-row
tibble per player so the bug was masked today, but the latent shape was
identical to the nhl_schedule() bug above. Replaced with if/else
for consistency and to remove the foot-gun before it surfaces (e.g., if
the function is ever refactored to handle multiple players).pwhl_schedule() gains a game_type parameter accepting "both"
(default), "regular", "playoffs", or "preseason". A new game_type
column is appended labeling each row.game_type now defaults to "both", so a call such
as pwhl_schedule(season = 2024) returns regular-season and playoff
games in a single, chronologically ordered data frame. Pass
game_type = "regular" to restore the previous regular-only behavior.
Preseason is excluded from "both"; request it with
game_type = "preseason".game_type does not yet exist for a season (e.g. playoffs
for an in-progress season), it is skipped rather than erroring, so "both"
still returns the regular-season games that do exist."date_with_day" display string.pwhl_schedule()'s winner column is now derived from
game_status and an integer comparison of the goal counts. Previously the
character goal counts were compared with >, so a double-digit score
sorted lexically ("10" < "9") and could name the wrong winner; games that
have not been played now yield winner = NA (the prior code emitted a "-"
placeholder). PWHL games cannot end in a regulation tie, so the former
"Tie" branch is gone.pwhl_player_game_log() and pwhl_streaks() gain the same game_type
parameter ("both" default, plus "regular", "playoffs", "preseason")
and a game_type column labeling each row. With "both", a player's
regular-season and playoff data are returned together. A game type the
player/season has no data in (e.g. playoffs for a non-qualifying team) is
skipped rather than erroring, so the call still returns the data that does
exist.pwhl_transactions() is intentionally unchanged: transactions are not
scoped to a game type (the playoff season feed carries none), so a combined
view would add no information.Three new season-level NHL loaders that bring NHL coverage in line with
PWHL. Each is backed by a new release tag on
sportsdataverse/sportsdataverse-data
and powered by new extractors in fastRhockey-nhl-raw/R/scrape_nhl_raw.R
and fastRhockey-nhl-data/R/nhl_data_creation.R.
| function | release tag | rows per game |
|--------------------------------|-------------------------|------------------------------|
| load_nhl_officials() | nhl_officials | one per official (refs+lins) |
| load_nhl_shots_by_period() | nhl_shots_by_period | one per team per period |
| load_nhl_shootout() | nhl_shootout | one per shootout attempt |
nhl_schedule() gained an include_data_flags = FALSE parameter. When
TRUE, the live schedule is left-joined against the cached
nhl_games_in_data_repo index from the data repo and gains 16 logical
columns (PBP, team_box, player_box, skater_box, goalie_box,
game_info, game_rosters, scoring, penalties, scratches,
linescore, three_stars, shifts, officials, shots_by_period,
shootout) telling you which pre-compiled datasets cover each game.
The schedule files in fastRhockey-nhl-raw/nhl/schedules/ and the
master schedule on the data repo carry these same flags.
scrape_nhl_raw.R now writes four additional top-level keys into both
nhl/json/raw/{game_id}.json and nhl/json/final/{game_id}.json:
officials — list of {role, name} (referees first, linesmen second)shots_by_period — long-form per-team-per-period shot totalsshootout — per-attempt summary (NULL if game didn't reach SO)plays_by_period — OLD-format index of play_indices per periodon_ice / on_ice_plus / penalty_box (final-state reconstruction,
derived from the processed PBP) — populated only in the final/
variant since they need fastRhockey-enriched on-ice columnsAll NHL season-level loaders and the shared .nhl_release_loader()
helper now live in R/nhl_loaders.R, mirroring the PWHL convention
(R/pwhl_loaders.R). The empty R/nhl_pbp.R file was removed since
it never contained an actual nhl_pbp() scraper — only loaders.
Public signatures and return types are unchanged.
Wraps the NHL Edge advanced-metrics endpoints under
https://api-web.nhle.com/v1/edge/.... Every wrapper accepts an optional
season argument (4-digit end year, e.g., 2025) — when omitted, the
/now form is used to fetch the current season. All wrappers share an
internal .nhl_edge_api() helper in R/helpers_nhl_edge.R.
| family | functions |
|---------|-----------|
| Skater | nhl_edge_skater_detail(), nhl_edge_skater_landing(), nhl_edge_skater_comparison(), nhl_edge_skater_shot_location_detail(), nhl_edge_skater_shot_location_top_10(), nhl_edge_skater_shot_speed_detail(), nhl_edge_skater_shot_speed_top_10(), nhl_edge_skater_skating_speed_detail(), nhl_edge_skater_speed_top_10(), nhl_edge_skater_skating_distance_detail(), nhl_edge_skater_distance_top_10(), nhl_edge_skater_zone_time(), nhl_edge_skater_zone_time_top_10(), nhl_cat_edge_skater_detail() |
| Goalie | nhl_edge_goalie_detail(), nhl_edge_goalie_landing(), nhl_edge_goalie_comparison(), nhl_edge_goalie_5v5_detail(), nhl_edge_goalie_5v5_top_10(), nhl_edge_goalie_save_percentage_detail(), nhl_edge_goalie_edge_save_pctg_top_10(), nhl_edge_goalie_shot_location_detail(), nhl_edge_goalie_shot_location_top_10(), nhl_cat_edge_goalie_detail() |
| Team | nhl_edge_team_detail(), nhl_edge_team_landing(), nhl_edge_team_shot_location_detail(), nhl_edge_team_shot_location_top_10(), nhl_edge_team_shot_speed_detail(), nhl_edge_team_skating_speed_detail(), nhl_edge_team_skating_speed_top_10(), nhl_edge_team_skating_distance_detail(), nhl_edge_team_skating_distance_top_10(), nhl_edge_team_zone_time_details(), nhl_edge_team_zone_time_top_10() |
First-time integration with https://records.nhl.com/site/api/. All
wrappers share an internal .nhl_records_api() helper in
R/helpers_nhl_records.R that supports cayenneExp filters,
limit/start pagination, and the records-API response shape
({data, total}).
nhl_records_franchise(), nhl_records_franchise_detail(),
nhl_records_franchise_totals(), nhl_records_franchise_team_totals(),
nhl_records_franchise_season_results(),
nhl_records_franchise_playoff_appearances()nhl_records_player(), nhl_records_player_byteam(),
nhl_records_player_stats(),
nhl_records_skater_real_time_stats_season(),
nhl_records_skater_real_time_stats_career()nhl_records_goalie_career_stats(),
nhl_records_goalie_season_stats(), nhl_records_goalie_shutout_streak()nhl_records_draft(), nhl_records_draft_lottery_odds(),
nhl_records_draft_lottery_picks(), nhl_records_draft_prospect()nhl_records_trophy(), nhl_records_award_details(),
nhl_records_hof_players()nhl_records_officials(), nhl_records_attendance(),
nhl_records_venue(), nhl_records_combine()Promotes endpoints that were previously only reachable via
nhl_stats_misc()'s generic dispatcher into discoverable, dedicated
wrappers. Documentation for nhl_stats_misc() was also updated to
enumerate every valid endpoint value.
nhl_stats_franchise(), nhl_stats_players(), nhl_stats_glossary(),
nhl_stats_country(), nhl_stats_config(), nhl_stats_ping()nhl_stats_skater_leaders(), nhl_stats_goalie_leaders(),
nhl_stats_skater_milestones(), nhl_stats_goalie_milestones()nhl_stats_team_listing(), nhl_stats_game_listing(),
nhl_stats_content_module()nhl_wsc_pbp(game_id) -- narrative-format play-by-play from
wsc/play-by-play/{gameId} (distinct from nhl_game_pbp() which uses
gamecenter/{id}/play-by-play).nhl_draft_tracker() -- live draft tracker
(draft-tracker/picks/now), distinct from nhl_draft() which hits the
static draft/picks/now endpoint.nhl_ppt_replay(game_id, event_number) -- event-level replay metadata.nhl_ppt_replay_goal(game_id, event_number) -- goal-specific replay
metadata.nhl_postal_lookup(postal_code) -- broadcast region lookup by postal
code.nhl_smartlinks(handle = NULL) -- NHL.com smart-link router.nhl_scoreboard(date = NULL) -- now accepts an optional date
argument so callers can fetch historical scoreboards (was hardcoded to
/now).nhl_meta(game_id = NULL, year = NULL, series_letter = NULL) --
added a third branch for meta/playoff-series/{year}/{seriesLetter}.nhl_draft_year(year, round = NULL) -- when round is NULL or
"all", the function now hits the /draft/picks/{year}/all shortcut
in a single request instead of looping per round.Convenience wrappers that orchestrate multiple endpoint calls into one tidy data frame:
nhl_game_ids_by_season(season, game_types, team_abbr, sleep_rate) --
iterates every team's season schedule and returns the deduplicated set
of game IDs.nhl_all_players_by_season(season, sleep_rate) -- iterates every
team's roster and flattens forwards/defensemen/goalies.nhl_player_career_stats(player_id) -- combines player/{id}/landing
with the season-by-season seasonTotals payload into a career frame.nhl_team_summary_range(start_season, end_season) -- multi-season
team summary loop over nhl_stats_teams(report_type = "summary").nhl_skater_summary_range(start_season, end_season) -- same for
skater summary.nhl_goalie_summary_range(start_season, end_season) -- same for
goalie summary.data-raw/nhl_missing_endpoint_function_mapping.md -- table of every
documented NHL API endpoint and its proposed/implemented
fastRhockey wrapper, sourced from
dfleis/nhl-api-docs,
RentoSaijo/nhlscraper,
and coreyjs/nhl-api-py.data-raw/nhl_api_web_openapi.{json,yaml} -- OpenAPI 3.0.3 spec for
api-web.nhle.com/v1/ (132 endpoints).data-raw/nhl_stats_rest_openapi.{json,yaml} -- OpenAPI 3.0.3 spec
for api.nhle.com/stats/rest/{lang}/ (21 endpoints).data-raw/nhl_records_openapi.{json,yaml} -- OpenAPI 3.0.3 spec for
records.nhl.com/site/api/ (442 endpoints).data-raw/_gen_openapi.py -- regenerator script.nhl_stats_players() now detects data: [] empty-list responses
(the API returns this when no cayenne_exp filter is supplied) and
returns NULL with a friendly hint instead of crashing
clean_names().nhl_stats_skater_leaders() and nhl_stats_goalie_leaders() no
longer pass start/limit query parameters, which the
leaders/{skaters,goalies}/{attribute} endpoint rejects with a 500
Cayenne SQL error. Roxygen now documents the valid attribute
values: skaters accept assists/goals/points; goalies accept
savePctg/gaa/shutouts.nhl_records_franchise(franchise_id = ...) now translates the
franchise_id argument into a cayenneExp=id={id} filter. The
records API does not support a path-suffix franchise/{id} form
(returns 404).nhl_records_award_details() -- replaced the broken franchise_id
argument with season_id (the award-details endpoint accepts
seasonId filtering but rejects franchiseId).nhl_stats_content_module() now guards against unnamed CMS
responses that previously crashed clean_names().Ten new season-level loaders that pull pre-compiled NHL datasets from new
release tags on
sportsdataverse/sportsdataverse-data.
All accept a seasons vector (Min: 2011) and return a fastRhockey_data
data frame, mirroring the existing load_nhl_*() API:
| function | release tag | rows per game |
|----------------------------|------------------------|--------------------------|
| load_nhl_skater_box() | nhl_skater_boxscores | one per skater |
| load_nhl_goalie_box() | nhl_goalie_boxscores | one per goalie |
| load_nhl_game_rosters() | nhl_game_rosters | one per dressed player |
| load_nhl_game_info() | nhl_game_info | one |
| load_nhl_scoring() | nhl_scoring | one per goal |
| load_nhl_penalties() | nhl_penalties | one per penalty |
| load_nhl_three_stars() | nhl_three_stars | up to three |
| load_nhl_scratches() | nhl_scratches | one per scratched player |
| load_nhl_linescore() | nhl_linescore | one |
| load_nhl_shifts() | nhl_shifts | one per shift |
The six pre-existing loaders (load_nhl_pbp(), load_nhl_pbp_lite(),
load_nhl_player_box(), load_nhl_team_box(), load_nhl_schedule(),
load_nhl_rosters()) were refactored to share a single internal worker
(.nhl_release_loader()) without changing their public signatures or return
types.
Eleven new season-level loaders that pull pre-compiled PWHL datasets from
new release tags on
sportsdataverse/sportsdataverse-data.
All accept a seasons vector (Min: 2024) and return a fastRhockey_data
data frame, mirroring the existing load_pwhl_*() API:
| function | release tag | rows per game |
|--------------------------------|----------------------------|----------------------|
| load_pwhl_skater_box() | pwhl_skater_boxscores | one per skater |
| load_pwhl_goalie_box() | pwhl_goalie_boxscores | one per goalie |
| load_pwhl_team_box() | pwhl_team_boxscores | two (home/away) |
| load_pwhl_game_info() | pwhl_game_info | one |
| load_pwhl_scoring_summary() | pwhl_scoring_summary | one per goal |
| load_pwhl_penalty_summary() | pwhl_penalty_summary | one per penalty |
| load_pwhl_three_stars() | pwhl_three_stars | up to three |
| load_pwhl_officials() | pwhl_officials | one per official |
| load_pwhl_shots_by_period() | pwhl_shots_by_period | one per period |
| load_pwhl_shootout() | pwhl_shootout | one per attempt |
| load_pwhl_game_rosters() | pwhl_game_rosters | one per dressed player|
The four pre-existing loaders (load_pwhl_pbp(), load_pwhl_player_box(),
load_pwhl_schedule(), load_pwhl_rosters()) were refactored to share a
single internal worker (.pwhl_release_loader()) without changing their
public signatures or return types.
lifecycle. The Premier Hockey
Federation ceased operations; use PWHL functions instead. Functions will be
removed in a future release._v2 variants, since the original API endpoints were
deprecated by the NHL. This means nhl_game_feed(), nhl_game_boxscore(),
nhl_schedule(), nhl_teams(), nhl_teams_roster(), and
nhl_player_info() now use the new api-web.nhle.com endpoints directly.pwhl_pbp() -- PWHL play-by-play data.pwhl_player_box() -- PWHL player box scores (skaters and goalies).pwhl_game_info() -- PWHL game information and metadata.pwhl_game_summary() -- Detailed game summary (scoring, penalties, shots
by period, three stars).pwhl_standings() -- PWHL league standings.pwhl_stats() -- PWHL stat leaders (skaters and goalies).pwhl_leaders() -- League leaders (top scorers and top goalies).pwhl_season_id() -- PWHL season ID lookup (now API-driven with fallback).pwhl_player_info() -- Player biographical and profile information.pwhl_player_game_log() -- Per-game statistics for a player in a season.pwhl_player_stats() -- Career and season-by-season statistics for a player.pwhl_player_search() -- Search for players by name.pwhl_transactions() -- Player transactions for a season.pwhl_streaks() -- Player streak data for a season.pwhl_playoff_bracket() -- Playoff bracket / series data.pwhl_scorebar() -- Recent and upcoming game scores.most_recent_pwhl_season() -- Utility to get the current PWHL season year.load_pwhl_pbp() -- Load pre-scraped PWHL play-by-play data.load_pwhl_player_box() -- Load pre-scraped PWHL player box scores.load_pwhl_schedule() -- Load pre-scraped PWHL schedules.load_pwhl_rosters() -- Load pre-scraped PWHL team rosters.update_pwhl_db() -- Update or create a PWHL play-by-play database.nhl_where_to_watch() -- streaming/broadcast availability.nhl_partner_game_odds() -- partner game odds by country.api-web.nhle.com: nhl_game_feed(),
nhl_game_boxscore(), nhl_schedule(), nhl_teams(),
nhl_teams_roster(), nhl_player_info(), nhl_player_game_log(),
nhl_stats_goalies(), nhl_stats_skaters(), nhl_stats_teams(),
nhl_stats_misc(), nhl_gamecenter_landing(),
nhl_gamecenter_right_rail(), nhl_scoreboard(), nhl_scores(),
nhl_seasons(), nhl_tv_schedule(), nhl_game_story(),
nhl_game_content(), nhl_game_shifts(), nhl_playoff_bracket(),
nhl_playoff_schedule(), nhl_playoff_carousel().helper_nhl_calculate_xg().nhl_draft_year() -- NHL API removed the /v1/draft/picks/{year}
endpoint. The function now iterates over rounds 1-7 using
/v1/draft/picks/{year}/{round}.pwhl_stats() -- resolved "object 'players' not found" error when
API calls failed. Fixed team ID resolution for skater stats.pwhl_schedule() -- the season column was inadvertently dropped
from the output. It is now included.refresh_xg_models() -- resolved "cannot change value of locked
binding" error by storing xG models in a package environment (.xg_env)
instead of top-level bindings.import(tidyverse) which violated CRAN policy.
Individual packages (dplyr, tidyr, etc.) are already imported.phf_schedule(), phf_standings(), phf_pbp(),
phf_player_box(), phf_team_box(), phf_team_roster(),
phf_team_stats(), phf_player_stats(), phf_leaders(),
phf_league_info(), phf_game_all(), phf_game_raw(),
phf_game_details(), phf_game_summary().load_phf_pbp(), load_phf_team_box(),
load_phf_player_box(), load_phf_schedule(), load_phf_rosters(),
update_phf_db().most_recent_phf_season().pwhl_season_id() now retrieves season data dynamically from the HockeyTech
API instead of using a hardcoded lookup table. Falls back to hardcoded data
when the API is unavailable..pwhl_api(), .pwhl_modulekit_url(),
.pwhl_gc_url(), and .pwhl_resolve_season_id() to reduce JSONP parsing
boilerplate across PWHL functions.lifecycle package for formal deprecation management.testthat dependency to >= 3.0.0.RUN_NHL_TESTS,
RUN_PHF_TESTS, RUN_PWHL_TESTS) via tests/testthat/helper-skip.R.helper-skip.R names.nhl_where_to_watch() returns NULL gracefully when the NHL
/v1/where-to-watch endpoint is unavailable._pkgdown.yml with reorganized reference sections and deprecated
function categories.CONTRIBUTING.md with naming conventions, testing environment
variables, conventional commits guide, and deprecation process.CLAUDE.md and .github/copilot-instructions.md to reflect
v1.0.0 changes and new PWHL endpoints.data-raw/pr_devel.md development scratchpad.cran_comments.md for CRAN submission._pkgdown.yml with reorganized PWHL reference sections.pwhl_schedule() function added.pwhl_team_roster() function added.pwhl_teams() function added.nhl_game_pbp() function added to match hockeyR.phf_league_info() function for team name inconsistency.load_phf_rosters() function added.load_nhl_rosters() function added.phf_team_logos dataset to package for reference.helper_phf_pbp_data() penalty code.try() to function examples.espn_nhl_teams() function added.load_phf_pbp() function added.load_phf_team_box() function added.load_phf_player_box() function added.load_phf_schedule() function added.update_phf_db() function added.phf_leaders() function added.phf_standings() function added.phf_player_stats() function added.phf_team_stats() function added.phf_team_roster() function added.load_phf_game() --> phf_game_all()load_phf_pbp() --> phf_pbp()load_phf_boxscore() --> phf_team_box()load_phf_raw_data() --> phf_game_raw()powerplay to fastRhockey.NEWS.md file to track changes to the package.