From d88e16c0a9b86658a22ee950c04cdd48e2acefca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:53:08 +0000 Subject: [PATCH 1/3] build(deps-dev): bump standard from 1.55.0 to 1.56.0 Bumps [standard](https://github.com/standardrb/standard) from 1.55.0 to 1.56.0. - [Release notes](https://github.com/standardrb/standard/releases) - [Changelog](https://github.com/standardrb/standard/blob/main/CHANGELOG.md) - [Commits](https://github.com/standardrb/standard/compare/v1.55.0...v1.56.0) --- updated-dependencies: - dependency-name: standard dependency-version: 1.56.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 07a4352..70c8499 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -200,7 +200,7 @@ GEM racc (~> 1.4) ostruct (0.6.3) parallel (2.1.0) - parser (3.3.11.1) + parser (3.3.12.0) ast (~> 2.4.1) racc pp (0.6.4) @@ -273,7 +273,7 @@ GEM reline (0.6.3) io-console (~> 0.5) rexml (3.4.4) - rubocop (1.87.0) + rubocop (1.88.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -312,10 +312,10 @@ GEM sidekiq-scheduler (6.0.2) rufus-scheduler (~> 3.2) sidekiq (>= 7.3, < 9) - standard (1.55.0) + standard (1.56.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.87.0) + rubocop (~> 1.88.0) standard-custom (~> 1.0.0) standard-performance (~> 1.8) standard-custom (1.0.2) From fb3c3b60f2b82d0d493714073981f27cb5c6e516 Mon Sep 17 00:00:00 2001 From: "H. Wade Minter" Date: Thu, 16 Jul 2026 10:21:57 -0400 Subject: [PATCH 2/3] Fix StandardRB lint issues --- app/services/nhl/client.rb | 2 +- app/services/rod_the_bot/edge_replay/source.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/nhl/client.rb b/app/services/nhl/client.rb index 6304468..7bebbce 100644 --- a/app/services/nhl/client.rb +++ b/app/services/nhl/client.rb @@ -17,7 +17,7 @@ def get_json(path) response.parsed_response rescue JSON::ParserError => e raise RequestError, "Invalid JSON fetching #{path}: #{e.class} - #{e.message}" - rescue Net::OpenTimeout, Net::ReadTimeout, Timeout::Error, SocketError, + rescue Timeout::Error, SocketError, Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError => e raise RequestError, "Network error fetching #{path}: #{e.class} - #{e.message}" end diff --git a/app/services/rod_the_bot/edge_replay/source.rb b/app/services/rod_the_bot/edge_replay/source.rb index a04f66f..75dd363 100644 --- a/app/services/rod_the_bot/edge_replay/source.rb +++ b/app/services/rod_the_bot/edge_replay/source.rb @@ -20,7 +20,7 @@ def edge_json(game_id, event_id, output_dir) def game_data(game_id) data = Nhl::GameClient.landing(game_id) - data if data&.dig("homeTeam") && data&.dig("awayTeam") + data if data&.dig("homeTeam") && data.dig("awayTeam") rescue => e Rails.logger.error "Error fetching game data: #{e.message}" nil From 4695db064bb82d758d4705965cce8313742733c4 Mon Sep 17 00:00:00 2001 From: "H. Wade Minter" Date: Thu, 16 Jul 2026 10:22:45 -0400 Subject: [PATCH 3/3] Apply StandardRB formatting updates --- app/services/nhl/edge_client.rb | 4 +- app/services/nhl/game_client.rb | 1 - app/services/nhl/game_info.rb | 2 +- app/services/nhl/player_directory.rb | 1 - app/services/nhl/season_calendar.rb | 2 - .../rod_the_bot/draft_pick/formatter.rb | 4 +- .../rod_the_bot/edge_replay/post_formatter.rb | 2 +- .../rod_the_bot/edge_replay/renderer.rb | 89 +++++++++---------- app/services/rod_the_bot/game_matchup.rb | 2 +- app/services/rod_the_bot/goal/post_builder.rb | 6 +- app/services/rod_the_bot/goal/situation.rb | 2 +- .../rod_the_bot/penalty/post_builder.rb | 2 +- .../rod_the_bot/player_streaks/analyzer.rb | 4 +- .../rod_the_bot/player_streaks/formatter.rb | 2 +- .../rod_the_bot/scheduling/gameday_post.rb | 8 +- .../rod_the_bot/scoring_change/detector.rb | 2 +- .../rod_the_bot/season_stats/formatter.rb | 2 +- .../rod_the_bot/goalie_change_worker.rb | 16 ++-- .../rod_the_bot/milestone_checker_worker.rb | 2 +- app/workers/rod_the_bot/penalty_worker.rb | 2 +- app/workers/rod_the_bot/scheduler.rb | 3 +- app/workers/rod_the_bot/three_stars_worker.rb | 1 + config/initializers/inflections.rb | 1 - test/services/nhl/stats_client_test.rb | 1 - .../milestones/career_total_test.rb | 10 ++- .../player_streaks/analyzer_test.rb | 12 +-- 26 files changed, 90 insertions(+), 93 deletions(-) diff --git a/app/services/nhl/edge_client.rb b/app/services/nhl/edge_client.rb index d3a7778..706111e 100644 --- a/app/services/nhl/edge_client.rb +++ b/app/services/nhl/edge_client.rb @@ -1,6 +1,5 @@ module Nhl class EdgeClient < Client - base_uri "https://api-web.nhle.com/v1" ENDPOINTS = { @@ -17,12 +16,11 @@ class EdgeClient < Client class << self ENDPOINTS.each do |name, (path, ttl)| define_method("fetch_#{name}") do |subject_id, season: nil, game_type: nil| - period = season && game_type ? "#{season}/#{game_type}" : "now" + period = (season && game_type) ? "#{season}/#{game_type}" : "now" cache_key = "edge_#{name}_#{subject_id}_#{period.tr("/", "_")}" Rails.cache.fetch(cache_key, expires_in: ttl) { get_json("/edge/#{path}/#{subject_id}/#{period}") } end end - end end end diff --git a/app/services/nhl/game_client.rb b/app/services/nhl/game_client.rb index 82d2075..a52ff14 100644 --- a/app/services/nhl/game_client.rb +++ b/app/services/nhl/game_client.rb @@ -23,7 +23,6 @@ def landing(game_id) def right_rail(game_id) get_json("/gamecenter/#{game_id}/right-rail") end - end end end diff --git a/app/services/nhl/game_info.rb b/app/services/nhl/game_info.rb index 8f8564a..d5efbb7 100644 --- a/app/services/nhl/game_info.rb +++ b/app/services/nhl/game_info.rb @@ -42,7 +42,7 @@ def opponent_team_id(game_id, team_id: ENV.fetch("NHL_TEAM_ID").to_i) away_id = feed&.dig("awayTeam", "id") return unless home_id && away_id - home_id.to_i == team_id ? away_id.to_i : home_id.to_i + (home_id.to_i == team_id) ? away_id.to_i : home_id.to_i end private diff --git a/app/services/nhl/player_directory.rb b/app/services/nhl/player_directory.rb index e882c74..a839fb2 100644 --- a/app/services/nhl/player_directory.rb +++ b/app/services/nhl/player_directory.rb @@ -68,6 +68,5 @@ def sweater_number(player_id) def each(&block) @identities.each_value(&block) end - end end diff --git a/app/services/nhl/season_calendar.rb b/app/services/nhl/season_calendar.rb index d6c0f4a..85881a4 100644 --- a/app/services/nhl/season_calendar.rb +++ b/app/services/nhl/season_calendar.rb @@ -1,6 +1,5 @@ module Nhl class SeasonCalendar < Client - base_uri "https://api-web.nhle.com/v1" class << self @@ -32,7 +31,6 @@ def schedule def local_today Time.use_zone(ENV.fetch("TIME_ZONE")) { Time.zone.today } end - end end end diff --git a/app/services/rod_the_bot/draft_pick/formatter.rb b/app/services/rod_the_bot/draft_pick/formatter.rb index 33aaefe..79ec64a 100644 --- a/app/services/rod_the_bot/draft_pick/formatter.rb +++ b/app/services/rod_the_bot/draft_pick/formatter.rb @@ -40,7 +40,7 @@ def details(pick, ranking, position) lines << "Ranking: #{ranking["finalRank"].ordinalize} in #{category}" if ranking["finalRank"] && category lines << height_line(ranking["heightInInches"]) lines << weight_line(ranking["weightInPounds"]) - lines << "#{position == "G" ? "Catches" : "Shoots"}: #{ranking["shootsCatches"]}" if ranking["shootsCatches"] + lines << "#{(position == "G") ? "Catches" : "Shoots"}: #{ranking["shootsCatches"]}" if ranking["shootsCatches"] lines << "Birthday: #{Date.parse(ranking["birthDate"]).strftime("%m/%d/%Y")}" if ranking["birthDate"] lines << "Birthplace: #{birthplace(ranking)}" if birthplace(ranking).present? lines.compact.join("\n") @@ -60,7 +60,7 @@ def birthplace(ranking) province = ranking["birthStateProvince"] country = ranking["birthCountry"] return "#{city}, #{province}" if province.present? - city.present? && country.present? ? "#{city}, #{COUNTRIES[country] || country}" : city + (city.present? && country.present?) ? "#{city}, #{COUNTRIES[country] || country}" : city end end end diff --git a/app/services/rod_the_bot/edge_replay/post_formatter.rb b/app/services/rod_the_bot/edge_replay/post_formatter.rb index a1ebf9f..a913527 100644 --- a/app/services/rod_the_bot/edge_replay/post_formatter.rb +++ b/app/services/rod_the_bot/edge_replay/post_formatter.rb @@ -6,7 +6,7 @@ class PostFormatter def format(play, players, feed) scorer_id = play.dig("details", "scoringPlayerId") scorer = scorer_id ? players.name_with_number(scorer_id) : "Unknown Player" - scoring_team = feed[feed.dig("homeTeam", "id") == play.dig("details", "eventOwnerTeamId") ? "homeTeam" : "awayTeam"]["abbrev"] + scoring_team = feed[(feed.dig("homeTeam", "id") == play.dig("details", "eventOwnerTeamId")) ? "homeTeam" : "awayTeam"]["abbrev"] period = format_period_name(play.dig("periodDescriptor", "number")) assists = %w[assist1PlayerId assist2PlayerId].filter_map do |key| diff --git a/app/services/rod_the_bot/edge_replay/renderer.rb b/app/services/rod_the_bot/edge_replay/renderer.rb index 8f08d5c..c1c891b 100644 --- a/app/services/rod_the_bot/edge_replay/renderer.rb +++ b/app/services/rod_the_bot/edge_replay/renderer.rb @@ -23,32 +23,32 @@ def render(selected, options, frames_dir, tmpdir) background_path = File.join(frames_dir, "_background.png") game_data = options[:game_data] || {} build_background!(background_path, options, game_data, tmpdir) - + w = options[:width].to_i h = options[:height].to_i tf = rink_transform(options) - + # Determine home team ID from game data home_team_id = game_data.dig("homeTeam", "id") - + fps = options.fetch(:fps, 30).to_f speed = options.fetch(:speed, 1.0).to_f speed = 1.0 if speed <= 0 tick_seconds = options.fetch(:tick_seconds, 0.1).to_f - + # Render base images for each tracking frame base_dir = File.join(frames_dir, "_base") FileUtils.mkdir_p(base_dir) - + selected.each_with_index do |frame, i| on_ice = frame["onIce"] || {} entities = on_ice.values.select { |e| e.is_a?(Hash) } puck = entities.select { |e| puck_entity?(e) } players = entities.reject { |e| puck_entity?(e) } - + out_png = File.join(base_dir, format("base_%05d.png", i)) cmd = ["magick", background_path] - + # Draw puck first puck.each do |ent| x = map_x(ent["x"], tf) @@ -56,18 +56,18 @@ def render(selected, options, frames_dir, tmpdir) r = 6 cmd += ["-fill", "#111111", "-stroke", "none", "-draw", "circle #{x},#{y} #{(x + r).round(2)},#{y}"] end - + # Draw players players.each do |ent| team_abbrev = ent["teamAbbrev"].to_s team_id = ent["teamId"] is_home = team_id == home_team_id primary_color = TEAM_COLORS.fetch(team_abbrev, "#444444") - + x = map_x(ent["x"], tf) y = map_y(ent["y"], tf) r = 24 - + if is_home # Home team: solid primary color circle with white numbers cmd += [ @@ -86,10 +86,10 @@ def render(selected, options, frames_dir, tmpdir) ] number_color = primary_color end - + num = ent["sweaterNumber"] next if num.nil? || num == "" - + dx = (x - (w / 2.0)).round dy = (y - (h / 2.0)).round cmd += [ @@ -100,11 +100,11 @@ def render(selected, options, frames_dir, tmpdir) "-annotate", "#{"+" if dx >= 0}#{dx}#{"+" if dy >= 0}#{dy}", num.to_s ] end - + cmd << out_png run_cmd!(cmd, "ImageMagick frame render #{i}") end - + # Build output frame sequence with repeats based on timeStamp deltas time_stamps = selected.map { |f| f.is_a?(Hash) ? f["timeStamp"] : nil }.map { |v| v.is_a?(Numeric) ? v.to_i : nil } deltas = [] @@ -113,7 +113,7 @@ def render(selected, options, frames_dir, tmpdir) end positive = deltas.compact.select { |d| d > 0 } fallback_delta = positive.empty? ? 1 : positive.tally.max_by { |_, c| c }[0] - + out_idx = 0 selected.each_index do |i| delta_ticks = if i < deltas.length && deltas[i].is_a?(Integer) && deltas[i] > 0 @@ -124,7 +124,7 @@ def render(selected, options, frames_dir, tmpdir) seconds = delta_ticks * tick_seconds repeats = [(seconds * fps / speed).round, 1].max base_png = File.join(base_dir, format("base_%05d.png", i)) - + repeats.times do out_png = File.join(frames_dir, format("frame_%05d.png", out_idx)) FileUtils.cp(base_png, out_png) @@ -132,18 +132,18 @@ def render(selected, options, frames_dir, tmpdir) end end end - + def build_background!(background_path, options, game_data, tmpdir) w = options[:width].to_i h = options[:height].to_i tf = rink_transform(options) - + # Get SVG path svg_path = Rails.root.join("config", "rink", "Icehockeylayout.svg") unless File.exist?(svg_path) raise "SVG rink template not found at: #{svg_path}" end - + # SVG ice surface coordinates # These represent the actual ice surface in the SVG coordinate system # EDGE coordinates (0,0) to (2400, 1020) map to this ice surface @@ -151,60 +151,60 @@ def build_background!(background_path, options, game_data, tmpdir) svg_ice_surface_y = 27.09 svg_ice_surface_width = 690.18 svg_ice_surface_height = 293.32 - + # ViewBox for rendering (includes boards for rounded corners) svg_padding = 15.6 svg_render_x = svg_ice_surface_x - svg_padding svg_render_y = svg_ice_surface_y - svg_padding svg_render_width = svg_ice_surface_width + (svg_padding * 2) svg_render_height = svg_ice_surface_height + (svg_padding * 2) - + # Canvas pixel dimensions for the EDGE rink (0-2400, 0-1020) rink_width_px = (tf[:x1] - tf[:x0]).round rink_height_px = (tf[:y1] - tf[:y0]).round - + # Calculate the scale factor from SVG ice surface to canvas pixels # This ensures the SVG ice surface matches the EDGE rink size on canvas svg_to_canvas_scale_x = rink_width_px.to_f / svg_ice_surface_width svg_to_canvas_scale_y = rink_height_px.to_f / svg_ice_surface_height - + # Render the SVG (with padding) at the correct size # The rendered PNG will be larger than the ice surface due to padding render_width_px = (svg_render_width * svg_to_canvas_scale_x).round render_height_px = (svg_render_height * svg_to_canvas_scale_y).round - + # Convert SVG to PNG tmp_rink_png = File.join(File.dirname(background_path), "_rink_only.png") tmp_svg = File.join(File.dirname(background_path), "_rink_cropped.svg") - + svg_content = File.read(svg_path) svg_content = svg_content.sub( /viewBox="[^"]*"/, "viewBox=\"#{svg_render_x.round(2)} #{svg_render_y.round(2)} #{svg_render_width.round(2)} #{svg_render_height.round(2)}\"" ) File.write(tmp_svg, svg_content) - + run_cmd!( ["rsvg-convert", "-w", render_width_px.to_s, "-h", render_height_px.to_s, "-o", tmp_rink_png, tmp_svg], "Convert SVG to PNG" ) - + # Calculate where to position the rendered PNG on the canvas # The ice surface portion of the PNG must align with tf[:x0], tf[:y0] # The padding in the rendered PNG is at the edges padding_px_x = (svg_padding * svg_to_canvas_scale_x).round padding_px_y = (svg_padding * svg_to_canvas_scale_y).round - + # Position the PNG so the ice surface corner aligns with the EDGE rink corner png_x = tf[:x0].round - padding_px_x png_y = tf[:y0].round - padding_px_y - + # Composite rink onto canvas cmd = [ "magick", "-size", "#{w}x#{h}", "xc:#0b0f14", tmp_rink_png, "-geometry", "+#{png_x}+#{png_y}", "-composite" ] - + # Add home team logo overlay at center ice if available home_team_logo_path = source.team_logo(game_data.dig("homeTeam", "logo"), tmpdir) if game_data.dig("homeTeam", "logo") if home_team_logo_path && File.exist?(home_team_logo_path) @@ -216,54 +216,54 @@ def build_background!(background_path, options, game_data, tmpdir) logo_size = (340 * tf[:scale]).round logo_x = (center_x - logo_size / 2).round logo_y = (center_y - logo_size / 2).round - + # Convert SVG to PNG and make it semi-transparent (ghosted effect) tmp_logo_png = File.join(tmpdir, "_logo.png") tmp_logo_resized = File.join(tmpdir, "_logo_resized.png") - + # Convert SVG to PNG first run_cmd!( ["rsvg-convert", "-w", logo_size.to_s, "-h", logo_size.to_s, "-o", tmp_logo_png, home_team_logo_path], "Convert logo SVG to PNG" ) - + # Apply transparency (ghosted effect - 15% opacity) run_cmd!( ["magick", tmp_logo_png, "-alpha", "set", "-channel", "A", "-evaluate", "multiply", "0.15", "+channel", tmp_logo_resized], "Apply ghosted effect to logo" ) - + cmd += [tmp_logo_resized, "-geometry", "+#{logo_x}+#{logo_y}", "-composite"] end - + cmd << background_path run_cmd!(cmd, "Composite rink and logo onto canvas") end - + # Helper methods from script - + def puck_entity?(ent) pid = ent["playerId"] team = ent["teamAbbrev"] (pid.nil? || pid == "") && (team.nil? || team == "") end - + def rink_transform(options) w = options[:width].to_f h = options[:height].to_f rink_w = options[:rink_w].to_f rink_h = options[:rink_h].to_f pad = options.fetch(:pad, 14).to_f - + avail_w = [w - (2.0 * pad), 1.0].max avail_h = [h - (2.0 * pad), 1.0].max scale = [avail_w / rink_w, avail_h / rink_h].min - + drawn_w = rink_w * scale drawn_h = rink_h * scale x0 = ((w - drawn_w) / 2.0).round(2) y0 = ((h - drawn_h) / 2.0).round(2) - + { scale: scale, x0: x0, @@ -274,15 +274,15 @@ def rink_transform(options) rink_h: rink_h } end - + def map_x(x, tf) (tf[:x0] + (x.to_f * tf[:scale])).round(2) end - + def map_y(y, tf) (tf[:y0] + (y.to_f * tf[:scale])).round(2) end - + def default_options { width: 1280, @@ -296,7 +296,6 @@ def default_options frames: nil } end - private diff --git a/app/services/rod_the_bot/game_matchup.rb b/app/services/rod_the_bot/game_matchup.rb index d2f5e10..668e333 100644 --- a/app/services/rod_the_bot/game_matchup.rb +++ b/app/services/rod_the_bot/game_matchup.rb @@ -10,7 +10,7 @@ def self.for(game_id, team_id: ENV.fetch("NHL_TEAM_ID").to_i) home = feed["homeTeam"] away = feed["awayTeam"] - ours, opponent = home["id"].to_i == team_id ? [home, away] : [away, home] + ours, opponent = (home["id"].to_i == team_id) ? [home, away] : [away, home] Matchup.new( our_team_id: ours["id"].to_i, diff --git a/app/services/rod_the_bot/goal/post_builder.rb b/app/services/rod_the_bot/goal/post_builder.rb index d400621..1690805 100644 --- a/app/services/rod_the_bot/goal/post_builder.rb +++ b/app/services/rod_the_bot/goal/post_builder.rb @@ -12,11 +12,11 @@ def initialize(team_id: ENV.fetch("NHL_TEAM_ID").to_i) def build(play:, feed:, players:) home = feed.fetch("homeTeam") away = feed.fetch("awayTeam") - your_team, their_team = home["id"].to_i == team_id ? [home, away] : [away, home] + your_team, their_team = (home["id"].to_i == team_id) ? [home, away] : [away, home] scorer = players.fetch(play.dig("details", "scoringPlayerId")) return unless scorer - scoring_team = scorer.team_id == team_id ? your_team : their_team + scoring_team = (scorer.team_id == team_id) ? your_team : their_team situation = Situation.new(play["situationCode"].to_s, scoring_team_id: scorer.team_id, home_id: home["id"], away_id: away["id"]) period = format_period_name(play.dig("periodDescriptor", "number")) post = [header(scoring_team, your_team, situation.modifiers), "", details(players, play), score(play, period, away, home), ""].join("\n") @@ -29,7 +29,7 @@ def build(play:, feed:, players:) attr_reader :team_id def header(scoring_team, your_team, modifiers) - scoring_team == your_team ? "🎉 #{scoring_team.dig("commonName", "default")}#{modifiers} GOOOOOOOAL!" : "👎 #{scoring_team.dig("commonName", "default")}#{modifiers} Goal" + (scoring_team == your_team) ? "🎉 #{scoring_team.dig("commonName", "default")}#{modifiers} GOOOOOOOAL!" : "👎 #{scoring_team.dig("commonName", "default")}#{modifiers} Goal" end def details(players, play) diff --git a/app/services/rod_the_bot/goal/situation.rb b/app/services/rod_the_bot/goal/situation.rb index d4526fe..474fb04 100644 --- a/app/services/rod_the_bot/goal/situation.rb +++ b/app/services/rod_the_bot/goal/situation.rb @@ -6,7 +6,7 @@ def initialize(code, scoring_team_id:, home_id:, away_id:) away_goalies, away_skaters, home_skaters, home_goalies = code.chars.map(&:to_i) away_players = away_goalies + away_skaters home_players = home_goalies + home_skaters - @scoring_players, @opposing_players, @opposing_goalies = scoring_team_id.to_i == home_id.to_i ? [home_players, away_players, away_goalies] : [away_players, home_players, home_goalies] + @scoring_players, @opposing_players, @opposing_goalies = (scoring_team_id.to_i == home_id.to_i) ? [home_players, away_players, away_goalies] : [away_players, home_players, home_goalies] end def penalty_shot? diff --git a/app/services/rod_the_bot/penalty/post_builder.rb b/app/services/rod_the_bot/penalty/post_builder.rb index f384f3e..58ee035 100644 --- a/app/services/rod_the_bot/penalty/post_builder.rb +++ b/app/services/rod_the_bot/penalty/post_builder.rb @@ -30,7 +30,7 @@ def build(play:, players:, your_team:, their_team:, tracked_team_id:) served = details["servedByPlayerId"] main = players.fetch(committed || served) return unless main - post = main.team_id == tracked_team_id ? "🙃 #{your_team.dig("commonName", "default")} Penalty\n\n" : "😵‍💫 #{their_team.dig("commonName", "default")} Penalty!\n\n" + post = (main.team_id == tracked_team_id) ? "🙃 #{your_team.dig("commonName", "default")} Penalty\n\n" : "😵‍💫 #{their_team.dig("commonName", "default")} Penalty!\n\n" period = format_period_name(play.dig("periodDescriptor", "number")) post + case details["typeCode"] when "BEN" diff --git a/app/services/rod_the_bot/player_streaks/analyzer.rb b/app/services/rod_the_bot/player_streaks/analyzer.rb index 5627380..75b73a4 100644 --- a/app/services/rod_the_bot/player_streaks/analyzer.rb +++ b/app/services/rod_the_bot/player_streaks/analyzer.rb @@ -1,5 +1,3 @@ -require "set" - module RodTheBot module PlayerStreaks class Analyzer @@ -43,7 +41,7 @@ def skater_streaks(player_id, games) def goalie_streaks(player_id, games) result = goalie_win_streak(games) - result[:length] >= minimum_length ? [streak(player_id, "Wins", result)] : [] + (result[:length] >= minimum_length) ? [streak(player_id, "Wins", result)] : [] end def active_streak(games) diff --git a/app/services/rod_the_bot/player_streaks/formatter.rb b/app/services/rod_the_bot/player_streaks/formatter.rb index d9aa5b7..31c437f 100644 --- a/app/services/rod_the_bot/player_streaks/formatter.rb +++ b/app/services/rod_the_bot/player_streaks/formatter.rb @@ -2,7 +2,7 @@ module RodTheBot module PlayerStreaks class Formatter def chunks(streaks, season_type:) - header = season_type == "Playoffs" ? "🔥 Active Streaks (Playoffs):\n\n" : "🔥 Active Streaks:\n\n" + header = (season_type == "Playoffs") ? "🔥 Active Streaks (Playoffs):\n\n" : "🔥 Active Streaks:\n\n" PostThread.split_lines(streaks.map { |streak| line(streak) }, header: header) end diff --git a/app/services/rod_the_bot/scheduling/gameday_post.rb b/app/services/rod_the_bot/scheduling/gameday_post.rb index 509858c..097c4b7 100644 --- a/app/services/rod_the_bot/scheduling/gameday_post.rb +++ b/app/services/rod_the_bot/scheduling/gameday_post.rb @@ -5,7 +5,11 @@ class GamedayPost include ActiveSupport::Inflector def build(game:, away:, home:, tracked:, time:, television:, preseason:, postseason:, seed_labels: {}, series_status: nil) - title = preseason ? "Preseason Gameday" : (postseason ? "Playoff Gameday" : "Gameday") + title = if preseason + "Preseason Gameday" + else + (postseason ? "Playoff Gameday" : "Gameday") + end lines = ["🗣️ It's a #{tracked[:team_name]} #{title}!", ""] lines += [playoff_status_line(series_status), ""] if postseason && series_status lines += team_lines(away, seed_labels, show_record: !preseason && !postseason) @@ -30,7 +34,7 @@ def series_state(status) top = status["topSeedWins"] bottom = status["bottomSeedWins"] return "Series tied #{top}-#{bottom}" if top == bottom - top > bottom ? "#{status["topSeedTeamAbbrev"]} leads #{top}-#{bottom}" : "#{status["bottomSeedTeamAbbrev"]} leads #{bottom}-#{top}" + (top > bottom) ? "#{status["topSeedTeamAbbrev"]} leads #{top}-#{bottom}" : "#{status["bottomSeedTeamAbbrev"]} leads #{bottom}-#{top}" end def record(team) diff --git a/app/services/rod_the_bot/scoring_change/detector.rb b/app/services/rod_the_bot/scoring_change/detector.rb index 187e9b0..216e215 100644 --- a/app/services/rod_the_bot/scoring_change/detector.rb +++ b/app/services/rod_the_bot/scoring_change/detector.rb @@ -12,7 +12,7 @@ def detect(play_id:, original_play:) return Result.new(status: :overturned, play: nil, challenge: nearby_challenge(original_play)) unless play return Result.new(status: :unchanged, play: play, challenge: nil) unless play["typeDescKey"] == "goal" - status = participants(play) == participants(original_play) ? :unchanged : :corrected + status = (participants(play) == participants(original_play)) ? :unchanged : :corrected Result.new(status: status, play: play, challenge: nil) end diff --git a/app/services/rod_the_bot/season_stats/formatter.rb b/app/services/rod_the_bot/season_stats/formatter.rb index 580b5ff..fca215b 100644 --- a/app/services/rod_the_bot/season_stats/formatter.rb +++ b/app/services/rod_the_bot/season_stats/formatter.rb @@ -18,7 +18,7 @@ def skaters(players, stat, icon:, title:) end def team_rankings(rankings, part:) - keys = part == 1 ? [[:average_goals_scored, "Average Goals Scored"], [:average_goals_allowed, "Average Goals Allowed"], [:power_play_percentage, "Power Play Percentage"], [:penalty_kill_percentage, "Penalty Kill Percentage"]] : [[:shots_per_game, "Shots Per Game"], [:shots_allowed_per_game, "Shots Allowed Per Game"], [:faceoff_percentage, "Faceoff Percentage"], [:points_percentage, "Points Percentage"]] + keys = (part == 1) ? [[:average_goals_scored, "Average Goals Scored"], [:average_goals_allowed, "Average Goals Allowed"], [:power_play_percentage, "Power Play Percentage"], [:penalty_kill_percentage, "Penalty Kill Percentage"]] : [[:shots_per_game, "Shots Per Game"], [:shots_allowed_per_game, "Shots Allowed Per Game"], [:faceoff_percentage, "Faceoff Percentage"], [:points_percentage, "Points Percentage"]] body = keys.map { |key, label| "#{label}: #{rankings[key][:value]} (Rank: #{rankings[key][:rank]})" }.join("\n") "📊 #{@season_type} stats and NHL ranks for the #{@team_name} (#{part}/2)\n\n#{body}\n" end diff --git a/app/workers/rod_the_bot/goalie_change_worker.rb b/app/workers/rod_the_bot/goalie_change_worker.rb index c409b6d..50dd0c8 100644 --- a/app/workers/rod_the_bot/goalie_change_worker.rb +++ b/app/workers/rod_the_bot/goalie_change_worker.rb @@ -21,17 +21,17 @@ def perform(game_id, play) result = detector.detect(game_id: game_id, team_id: defending_team_id, goalie_id: goalie_id, event_id: play["eventId"], plays: @feed["plays"]) if result.status == :changed - new_goalie = player_directory(game_id).fetch(play["details"]["goalieInNetId"]) - return if new_goalie.nil? - detector.commit(game_id: game_id, team_id: defending_team_id, goalie_id: goalie_id) + new_goalie = player_directory(game_id).fetch(play["details"]["goalieInNetId"]) + return if new_goalie.nil? + detector.commit(game_id: game_id, team_id: defending_team_id, goalie_id: goalie_id) - post = build_post(defending_team, new_goalie) - headshot = get_goalie_headshot(play["details"]["goalieInNetId"]) # Use original integer - images = headshot ? [headshot] : [] + post = build_post(defending_team, new_goalie) + headshot = get_goalie_headshot(play["details"]["goalieInNetId"]) # Use original integer + images = headshot ? [headshot] : [] - RodTheBot::Post.perform_async(post, nil, nil, nil, images) + RodTheBot::Post.perform_async(post, nil, nil, nil, images) - Rails.logger.info "GoalieChangeWorker: Posted goalie change for team #{defending_team_id}, #{result.previous_goalie_id} → #{goalie_id} (#{new_goalie.name_with_number})" + Rails.logger.info "GoalieChangeWorker: Posted goalie change for team #{defending_team_id}, #{result.previous_goalie_id} → #{goalie_id} (#{new_goalie.name_with_number})" end end diff --git a/app/workers/rod_the_bot/milestone_checker_worker.rb b/app/workers/rod_the_bot/milestone_checker_worker.rb index 5934def..0241505 100644 --- a/app/workers/rod_the_bot/milestone_checker_worker.rb +++ b/app/workers/rod_the_bot/milestone_checker_worker.rb @@ -1,6 +1,7 @@ module RodTheBot class MilestoneCheckerWorker include Sidekiq::Worker + def perform(game_id, play) @game_id = game_id @@ -92,6 +93,5 @@ def player_on_tracked_team?(player_id) player.team_id == tracked_team_id end - end end diff --git a/app/workers/rod_the_bot/penalty_worker.rb b/app/workers/rod_the_bot/penalty_worker.rb index 28a96a8..aa78ed4 100644 --- a/app/workers/rod_the_bot/penalty_worker.rb +++ b/app/workers/rod_the_bot/penalty_worker.rb @@ -26,7 +26,7 @@ def perform(game_id, play, desc_retry_count = 0) home = feed["homeTeam"] away = feed["awayTeam"] tracked_team_id = ENV["NHL_TEAM_ID"].to_i - your_team, their_team = home["id"].to_i == tracked_team_id ? [home, away] : [away, home] + your_team, their_team = (home["id"].to_i == tracked_team_id) ? [home, away] : [away, home] players = Nhl::PlayerDirectory.for_game(game_id) main_player_id = penalty.dig("details", "committedByPlayerId") || penalty.dig("details", "servedByPlayerId") diff --git a/app/workers/rod_the_bot/scheduler.rb b/app/workers/rod_the_bot/scheduler.rb index 737eaf4..d13bf92 100644 --- a/app/workers/rod_the_bot/scheduler.rb +++ b/app/workers/rod_the_bot/scheduler.rb @@ -30,7 +30,7 @@ def perform away = @game["awayTeam"] your_team = (home["id"].to_i == ENV["NHL_TEAM_ID"].to_i) ? home : away @your_team_is = (home["id"].to_i == ENV["NHL_TEAM_ID"].to_i) ? "homeTeam" : "awayTeam" - venue = @game["venue"] + @game["venue"] game_id = @game["id"] @@ -121,6 +121,5 @@ def series_seed_abbrevs(series_letter) "bottomSeedTeamAbbrev" => series.dig("bottomSeed", "abbrev") }.compact end - end end diff --git a/app/workers/rod_the_bot/three_stars_worker.rb b/app/workers/rod_the_bot/three_stars_worker.rb index ccd8bef..616efb6 100644 --- a/app/workers/rod_the_bot/three_stars_worker.rb +++ b/app/workers/rod_the_bot/three_stars_worker.rb @@ -2,6 +2,7 @@ module RodTheBot class ThreeStarsWorker include Sidekiq::Worker include WorkerErrorHandling + attr_reader :feed MAX_RETRIES = 15 # 15 minutes max (15 retries * 60 seconds) diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 1cbf756..3860f65 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -10,7 +10,6 @@ # inflect.uncountable %w( fish sheep ) # end - # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym "RESTful" diff --git a/test/services/nhl/stats_client_test.rb b/test/services/nhl/stats_client_test.rb index bf678a5..2ced0aa 100644 --- a/test/services/nhl/stats_client_test.rb +++ b/test/services/nhl/stats_client_test.rb @@ -37,5 +37,4 @@ class Nhl::StatsClientTest < ActiveSupport::TestCase assert_equal [{"playerId" => 1}], Nhl::StatsClient.shift_charts(2025020660) end - end diff --git a/test/services/rod_the_bot/milestones/career_total_test.rb b/test/services/rod_the_bot/milestones/career_total_test.rb index af00851..2443f25 100644 --- a/test/services/rod_the_bot/milestones/career_total_test.rb +++ b/test/services/rod_the_bot/milestones/career_total_test.rb @@ -4,10 +4,12 @@ class RodTheBot::Milestones::CareerTotalTest < ActiveSupport::TestCase test "combines pregame totals with goals and assists from the recorded game feed shape" do redis = MockRedis.new redis.set("pregame:10:player:42:points", 99) - feed = -> { {"plays" => [ - {"typeDescKey" => "goal", "details" => {"scoringPlayerId" => 42}}, - {"typeDescKey" => "goal", "details" => {"assist1PlayerId" => 42}} - ]} } + feed = -> { + {"plays" => [ + {"typeDescKey" => "goal", "details" => {"scoringPlayerId" => 42}}, + {"typeDescKey" => "goal", "details" => {"assist1PlayerId" => 42}} + ]} + } total = RodTheBot::Milestones::CareerTotal.new(game_id: 10, feed: feed, redis: redis) diff --git a/test/services/rod_the_bot/player_streaks/analyzer_test.rb b/test/services/rod_the_bot/player_streaks/analyzer_test.rb index 084193f..d8fb3c9 100644 --- a/test/services/rod_the_bot/player_streaks/analyzer_test.rb +++ b/test/services/rod_the_bot/player_streaks/analyzer_test.rb @@ -26,11 +26,13 @@ class RodTheBot::PlayerStreaks::AnalyzerTest < ActiveSupport::TestCase end test "filters mixed season and game-type data" do - logs = ->(_id, _limit) { [ - {"seasonId" => "20252026", "gameTypeId" => 2, "points" => 1}, - {"seasonId" => "20242025", "gameTypeId" => 2, "points" => 1}, - {"seasonId" => "20252026", "gameTypeId" => 3, "points" => 1} - ] } + logs = ->(_id, _limit) { + [ + {"seasonId" => "20252026", "gameTypeId" => 2, "points" => 1}, + {"seasonId" => "20242025", "gameTypeId" => 2, "points" => 1}, + {"seasonId" => "20252026", "gameTypeId" => 3, "points" => 1} + ] + } analyzer = RodTheBot::PlayerStreaks::Analyzer.new(game_log: logs, season: "20252026", game_type: 2, minimum_length: 2) assert_empty analyzer.analyze(player_ids: [42])