Add Elo rating system with match entry and CSV upload
- Implement EloCalculator service for 2v2 Euchre matches - Create background job processing with GoodJob - Add single match entry form - Add CSV upload for batch match entry - Create player rankings view - Add database schema for matches and elo snapshots
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module EuchreCamp
|
||||
module Actions
|
||||
module Admin
|
||||
module Matches
|
||||
class Create < EuchreCamp::Action
|
||||
include Deps[repo: 'repositories.matches']
|
||||
|
||||
def handle(request, response)
|
||||
match_params = request.params.to_h
|
||||
match_params[:played_at] = Time.parse(match_params[:played_at]) if match_params[:played_at]
|
||||
|
||||
# Create the match
|
||||
match = repo.create(match_params)
|
||||
|
||||
# Enqueue background job to calculate Elo
|
||||
EuchreCamp::Jobs::CalculateEloJob.perform_later(match[:id])
|
||||
|
||||
response.redirect_to("/admin/matches")
|
||||
rescue StandardError => e
|
||||
# Handle validation errors or other issues
|
||||
puts "Error: #{e.message}"
|
||||
puts e.backtrace.join("\n")
|
||||
response.redirect_to("/admin/matches/new")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module EuchreCamp
|
||||
module Actions
|
||||
module Admin
|
||||
module Matches
|
||||
class Index < EuchreCamp::Action
|
||||
include Deps[repo: 'repositories.matches']
|
||||
|
||||
def handle(_request, response)
|
||||
matches = repo.all
|
||||
response.render(view, matches: matches)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module EuchreCamp
|
||||
module Actions
|
||||
module Admin
|
||||
module Matches
|
||||
class New < EuchreCamp::Action
|
||||
include Deps[repo: 'repositories.players']
|
||||
|
||||
def handle(_request, response)
|
||||
players = repo.all
|
||||
response.render(view, players: players)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,86 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "csv"
|
||||
|
||||
module EuchreCamp
|
||||
module Actions
|
||||
module Admin
|
||||
module Matches
|
||||
class ProcessUpload < EuchreCamp::Action
|
||||
include Deps[repo: 'repositories.matches']
|
||||
|
||||
def handle(request, response)
|
||||
# In Hanami, file uploads are in request.params
|
||||
file = request.params[:csv_file]
|
||||
|
||||
unless file && file[:tempfile]
|
||||
response.redirect_to("/admin/matches/upload")
|
||||
return
|
||||
end
|
||||
|
||||
matches_created = 0
|
||||
errors = []
|
||||
|
||||
# Parse CSV
|
||||
CSV.foreach(file[:tempfile].path, headers: true, header_converters: :symbol) do |row|
|
||||
begin
|
||||
# Map CSV columns to our database structure
|
||||
# Expected columns: played_at, team_1_p1_name, team_1_p2_name, team_1_score, team_2_p1_name, team_2_p2_name, team_2_score
|
||||
|
||||
# Find or create players by name
|
||||
player_1 = find_or_create_player(row[:team_1_p1_name])
|
||||
player_2 = find_or_create_player(row[:team_1_p2_name])
|
||||
player_3 = find_or_create_player(row[:team_2_p1_name])
|
||||
player_4 = find_or_create_player(row[:team_2_p2_name])
|
||||
|
||||
played_at = begin
|
||||
Time.parse(row[:played_at])
|
||||
rescue
|
||||
Time.now
|
||||
end
|
||||
|
||||
match_data = {
|
||||
played_at: played_at,
|
||||
team_1_p1_id: player_1[:id],
|
||||
team_1_p2_id: player_2[:id],
|
||||
team_1_score: row[:team_1_score].to_i,
|
||||
team_2_p1_id: player_3[:id],
|
||||
team_2_p2_id: player_4[:id],
|
||||
team_2_score: row[:team_2_score].to_i,
|
||||
status: 'completed'
|
||||
}
|
||||
|
||||
match = repo.create(match_data)
|
||||
EuchreCamp::Jobs::CalculateEloJob.perform_later(match[:id])
|
||||
matches_created += 1
|
||||
|
||||
rescue => e
|
||||
errors << "Row #{row}: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
response.flash[:message] = "Created #{matches_created} matches.#{' Errors: ' + errors.join(', ') if errors.any?}"
|
||||
response.redirect_to("/admin/matches")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_or_create_player(name)
|
||||
rom = EuchreCamp::App["persistence.rom"]
|
||||
return nil unless name
|
||||
|
||||
name = name.strip
|
||||
player = rom.relations[:players].where(name: name).one
|
||||
|
||||
if player
|
||||
player
|
||||
else
|
||||
rom.relations[:players].insert(name: name, rating: 0, current_elo: 1000)
|
||||
rom.relations[:players].where(name: name).one
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "csv"
|
||||
|
||||
module EuchreCamp
|
||||
module Actions
|
||||
module Admin
|
||||
module Matches
|
||||
class Upload < EuchreCamp::Action
|
||||
def handle(request, response)
|
||||
response.render(view)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module EuchreCamp
|
||||
module Actions
|
||||
module Players
|
||||
class Rankings < EuchreCamp::Action
|
||||
include Deps[repo: 'repositories.players']
|
||||
|
||||
def handle(_request, response)
|
||||
# Get all players and sort by current Elo
|
||||
players = repo.all.sort_by { |p| -p.current_elo }
|
||||
response.render(view, players: players)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user